./0000700000175000017500000000000011425360453010176 5ustar lambylamby./pyke-1.1.1/0000755000175000017500000000000011425360453011614 5ustar lambylamby./pyke-1.1.1/experimental/0000755000175000017500000000000011425360453014311 5ustar lambylamby./pyke-1.1.1/experimental/metaclass.py0000644000175000017500000003040611346504626016646 0ustar lambylamby# metaclass.py from pyke.unique import unique ''' this metaclass is intended to be used by deriving from tracked_object as base class pros: - probably works with IronPython or Jython - easier to understand cons: - __setattr__ defined by classes poses problems ''' class metaclass_option1(type): # this _must_ be derived from 'type'! _ignore_setattr = False def __init__(self, name, bases, dict): # This gets called when new derived classes are created. # # We don't need to define an __init__ method here, but I was just # curious about how this thing works... print "metaclass: name", name, ", bases", bases, \ ", dict keys", tuple(sorted(dict.keys())) super(metaclass_option1, self).__init__(name, bases, dict) def __call__(self, *args, **kws): # This gets called when new instances are created (using the class as # a function). obj = super(metaclass_option1, self).__call__(*args, **kws) del obj._ignore_setattr print "add instance", obj, "to", self.knowledge_base return obj ''' this metaclass requires the __metaclass__ = metaclass_option2 attribute of classes to be used with the object knowledge base of pyke pros: - solves the problem of classes defining their own __setattr__ method - does not require any multiple inheritance cons: - hard to understand - possibly does not work with IronPython or Jython ''' class metaclass_option2(type): # this _must_ be derived from 'type'! def __new__(mcl, name, bases, clsdict): print "metaclass_option2.__new__: class dict before __new__: name", name, ", bases", bases, \ ", dict keys", tuple(clsdict.keys()), ", dict values", tuple(clsdict.values()) def __setattr__(self, attr, value): # This gets called when any attribute is changed. We would need to # figure out how to ignore attribute setting by the __init__ # function... # # Also the check to see if the attribute has actually changed by doing # a '!=' check could theoretically lead to problems. For example this # would fail to change the attribute to another value that wasn't # identical to the first, but '==' to it: for example, 4 and 4.0. if self.__instance__.get(self, False) : if getattr(self, attr) != value: print "metaclass.__new__: notify knowledge base", \ "of attribute change: (%s, %s, %s)" % (self, attr, value) if self.__cls__setattr__ != None: self.__cls__setattr__(attr, value) else: super(self.__class__, self).__setattr__(attr, value) else: # does not work to call super.__setattr__ #super(self.__class__, self).__setattr__(attr, value) # self.__dict__[attr] = value def __getattr__(self, name): return self.__dict__[name] cls__setattr__ = None if clsdict.get('__setattr__', None) != None: cls__setattr__ = clsdict['__setattr__'] clsdict['__setattr__'] = __setattr__ clsdict['__getattr__'] = __getattr__ clsdict['__cls__setattr__'] = cls__setattr__ clsdict['__instance__'] = {} print "metaclass_option2.__new__: class dict after __new__: name", name, ", bases", bases, \ ", dict keys", tuple(sorted(clsdict.keys())), ", dict values", tuple(clsdict.values()) return super(metaclass_option2, mcl).__new__(mcl, name, bases, clsdict) ''' def __init__(cls, name, bases, clsdict): # This gets called when new derived classes are created. # # We don't need to define an __init__ method here, but I was just # curious about how this thing works... super(metaclass_option2, cls).__init__(name, bases, clsdict) print "class dict after __init__: name", name, ", bases", bases, \ ", dict keys", tuple(sorted(clsdict.keys())) # does not work to create __instance class member here #clsdict['__instance__'] = {} ''' def __call__(cls, *args, **kws): # This gets called when new instances are created (using the class as # a function). obj = super(metaclass_option2, cls).__call__(*args, **kws) obj.__instance__[obj] = True print "add instance of class", cls.__name__, "to knowledge base" return obj class tracked_object(object): r''' All classes to be tracked by an object base would be derived from this one: >>> class foo(tracked_object): ... def __init__(self, arg): ... super(foo, self).__init__() ... print "foo.__init__:", arg ... self.x = arg # should be ignored ... # doctest: +NORMALIZE_WHITESPACE metaclass: name foo , bases (,) , dict keys ('__init__', '__module__') And we can keep deriving classes: >>> class bar(foo): ... def __init__(self, arg1, arg2): ... super(bar, self).__init__(arg1) ... print "bar.__init__:", arg1, arg2 ... self.y = arg2 # should be ignored ... # doctest: +NORMALIZE_WHITESPACE metaclass: name bar , bases (,) , dict keys ('__init__', '__module__') We can't do the next step directly in the class definition because the knowledge_engine.engine hasn't been created yet and so the object bases don't exist at that point in time. So this simulates adding the knowledge_base to the class later, after the knowledge_engine.engine and object bases have been created. >>> foo.knowledge_base = 'foo base' >>> bar.knowledge_base = 'bar base' And now we create some instances (shouldn't see any attribute change notifications here!): >>> f = foo(44) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS tracked_object.__setattr__ called on object with property _ignore_setattr and value True tracked_object.__setattr__ called on object with property knowledgebase and value None foo.__init__: 44 tracked_object.__setattr__ called on object with property x and value 44 add instance to foo base >>> b = bar(55, 66) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS tracked_object.__setattr__ called on object with property _ignore_setattr and value True tracked_object.__setattr__ called on object with property knowledgebase and value None foo.__init__: 55 tracked_object.__setattr__ called on object with property x and value 55 bar.__init__: 55 66 tracked_object.__setattr__ called on object with property y and value 66 add instance to bar base And modify some attributes: >>> f.x = 'y' # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS tracked_object.__setattr__ called on object with property x and value y tracked_object.__setattr__: notify foo base of attribute change: (, x, y) >>> b.y = 'z' # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS tracked_object.__setattr__ called on object with property y and value z tracked_object.__setattr__: notify bar base of attribute change: (, y, z) >>> b.y = 'z' # should be ignored ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS tracked_object.__setattr__ called on object with property y and value z >>> b.z = "wasn't set" # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS tracked_object.__setattr__ called on object with property z and value wasn't set tracked_object.__setattr__: notify bar base of attribute change: (, z, wasn't set) ''' __metaclass__ = metaclass_option1 _not_bound = unique('_not_bound') # a value that should != any other value! def __init__(self): self._ignore_setattr = True self.knowledgebase = None def __setattr__(self, attr, value): # This gets called when any attribute is changed. We would need to # figure out how to ignore attribute setting by the __init__ # function... # # Also the check to see if the attribute has actually changed by doing # a '!=' check could theoretically lead to problems. For example this # would fail to change the attribute to another value that wasn't # identical to the first, but '==' to it: for example, 4 and 4.0. print "tracked_object.__setattr__ called on object %s with property %s and value %s" % (self, attr, value) if getattr(self, attr, self._not_bound) != value: super(tracked_object, self).__setattr__(attr, value) if not hasattr(self, '_ignore_setattr'): print "tracked_object.__setattr__: notify", self.knowledge_base, \ "of attribute change: (%s, %s, %s)" % (self, attr, value) ''' tracked_object and foo_tracked use metaclass_option1 ''' class foo_tracked(tracked_object): def __init__(self, arg): super(foo_tracked, self).__init__() self.prop = arg ''' the following classes use metaclass_option2 ''' class foo_base(object): def __setattr__(self, attr, value): print "foo_base.__setattr__ called on object %s with property %s and value %s" % (self, attr, value) class foo_attribute_base(foo_base): __metaclass__ = metaclass_option2 def __init__(self, arg): super(foo_attribute_base, self).__init__() self.prop = arg class foo_attribute(object): __metaclass__ = metaclass_option2 def __init__(self, arg): super(foo_attribute, self).__init__() self.prop = arg def __setattr__(self, attr, value): print "foo_attribute.__setattr__ called on object %s with property %s and value %s" % (self, attr, value) class foo(object): __metaclass__ = metaclass_option2 def __init__(self, arg): super(foo, self).__init__() self.prop = arg #self.knowledge_base = "foo" def foo_method(self): print "foo_method called" def test_foo_option2(): f1 = foo(1) # should add instance to knowledge base f1.prop = 2 # should notify knowledge base of property change f2 = foo("egon") # should add instance to knowledge base f2.prop = "ralf" # should notify knowledge base of property change f3 = foo_attribute(3) f3.prop = 4 f4 = foo_attribute("karin") f4.prop = "sabine" f5 = foo_attribute_base(5) f5.prop = 6 f6 = foo_attribute_base("sebastian") f6.prop = "philipp" def test_foo_option1(): import sys import doctest sys.exit(doctest.testmod(optionflags = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE) [0]) if __name__ == "__main__": #test_foo_option1() test_foo_option2() ./pyke-1.1.1/experimental/__init__.py0000644000175000017500000000000011346504626016414 0ustar lambylamby./pyke-1.1.1/experimental/form.html0000644000175000017500000000137211346504626016151 0ustar lambylamby Add Movie

Add Movie

Title
Year
Length
./pyke-1.1.1/experimental/multi_test.py0000644000175000017500000000235511346504626017065 0ustar lambylamby# multi_test.py class top(object): def foo(self, indent = 0): print ' ' * indent + "top.foo" def bar(self): print "top.bar" class left(top): r''' >>> l = left() >>> l.foo() # here left.foo calls top.foo left.foo top.foo >>> l.bar() top.bar ''' def foo(self, indent = 0): print ' ' * indent + "left.foo" super(left, self).foo(indent + 4) class right(top): r''' >>> r = right() >>> r.foo() right.foo top.foo ''' def foo(self, indent = 0): print ' ' * indent + "right.foo" super(right, self).foo(indent + 4) def bar(self): print "right.bar" class bottom(left, right): r''' >>> b = bottom() >>> b.foo() # here left.foo calls right.foo bottom.foo left.foo right.foo top.foo >>> b.bar() # gets right.bar, not left->top.bar right.bar ''' def foo(self, indent = 0): print ' ' * indent + "bottom.foo" super(bottom, self).foo(indent + 4) def test(): import sys import doctest sys.exit(doctest.testmod()[0]) if __name__ == "__main__": test() ./pyke-1.1.1/experimental/cache_args.py0000644000175000017500000001475311346504626016760 0ustar lambylamby# $Id: cache_args.py 97f0531f7c21 2008-08-30 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. r''' ''' import itertools from pyke import contexts class selector(object): def __init__(self): self.all = [] # [(pat_context, pattern, link)...] self.full_vars = [] # [(pat_context, pattern, [link])...] self.partial_vars = [] # [(pat_context, pattern, [link])...] self.data = {} # {data: [link]} def add(self, pat_context, pattern, link): self.all.append((pat_context, pattern, link)) if pattern.is_data(pat_context): self.data.set_default(pattern.as_data(pat_context), []).append(link) else: self.vars # FIX ?? def gen_subset(self, pat_context, pattern): ''' yields links without binding any pattern variables. ''' if pattern.is_data(pat_context): for link in self.data[pattern.as_data(pat_context)]: yield link else: for p_c, p, links in self.full_vars: for link in links: yield link if not isinstance(pattern, contexts.variable): self.partial_vars # FIX subsets def gen_match(self, pat_context, pattern): ''' yields links binding pattern variables. ''' if pattern.is_data(pat_context): for link in self.data[pattern.as_data(pat_context)]: yield link elif isinstance(pattern, contexts.variable): self.all # FIX all else: self.partial_vars # FIX matches self.full_vars # FIX all class cache_args(object): def __init__(self): self.args_list = [] # [(pat_context, (pattern...))...] self.hashes = {} # (len, (index...)): (other_indices, # {(arg...): [other_args_from_factn...]}) def reset(self): self.args_list = [] self.hashes.clear() def lookup(self, bindings, pat_context, patterns): """ Binds patterns to successive facts, yielding None for each successful match. Undoes bindings upon continuation, so that no bindings remain at StopIteration. """ indices = tuple(enum for enum in enumerate(patterns) if enum[1].is_data(pat_context)) other_indices, other_arg_lists = \ self._get_hashed(len(patterns), tuple(index[0] for index in indices), tuple(index[1].as_data(pat_context) for index in indices)) if other_arg_lists: for args in other_arg_lists: mark = bindings.mark(True) try: if all(itertools.imap(lambda i, arg: patterns[i].match_data(bindings, pat_context, arg), other_indices, args)): bindings.end_save_all_undo() yield else: bindings.end_save_all_undo() finally: bindings.undo_to_mark(mark) def _get_hashed(self, len, indices, args): ans = self.hashes.get((len, indices)) if ans is None: ans = self._hash(len, indices) other_indices, arg_map = ans return other_indices, arg_map.get(args, ()) def _hash(self, length, indices): args_hash = {} new_entry = (tuple(i for i in range(length) if i not in indices), args_hash) self.hashes[length, indices] = new_entry for args in itertools.chain(self.universal_facts, self.case_specific_facts): if len(args) == length: selected_args = tuple(arg for i, arg in enumerate(args) if i in indices) args_hash.setdefault(selected_args, []) \ .append(tuple(arg for i, arg in enumerate(args) if i not in indices)) return new_entry def add_universal_fact(self, args): assert args not in self.case_specific_facts, \ "add_universal_fact: fact already present as specific fact" if args not in self.universal_facts: self.universal_facts.append(args) self.add_args(args) def add_case_specific_fact(self, args): if args not in self.universal_facts and \ args not in self.case_specific_facts: self.case_specific_facts.append(args) self.add_args(args) for fc_rule, foreach_index in self.fc_rule_refs: fc_rule.new_fact(args, foreach_index) def add_args(self, args): for (length, indices), (other_indices, arg_map) \ in self.hashes.iteritems(): if length == len(args): selected_args = tuple(arg for i, arg in enumerate(args) if i in indices) arg_map.setdefault(selected_args, []) \ .append(tuple(arg for i, arg in enumerate(args) if i not in indices)) def test(): import doctest import sys # FIX sys.exit(doctest.testmod()[0]) if __name__ == "__main__": test() ./pyke-1.1.1/experimental/testall.config0000644000175000017500000000002511346504626017151 0ustar lambylambyexclude metaclass.py ./pyke-1.1.1/.hgtags0000644000175000017500000000121411346504626013074 0ustar lambylamby00a03bbc9eeacd589b6f269a0980927e0ccbc100 0.4 3315cbe4f46e6760509979ee632fe9f838e9d5d7 1.0 4ca57fd63b60bd0a198b4415d11f346558e462b1 0.3 5a44bd4649a1f9a2bce9836e8c3867d31c3ee332 0.2 62dbd4b5fb78d2c3c710d23771f37aa6403c07ff 0.6 66bbffda843586635e0232de2fca8df18534eb37 0.1.alpha2 86ef6c004956c6c0930752f212ba5c5a631ec9aa 0.1.alpha1 99a8d3c03fcc646659bb89ff0044b4eceaf94323 0.7 80220df5730f41ffb21989e71dc32f365e5f2f76 1.0.2 f17c13400c68987a9978fc80a59a292c6be6514b 0.5 32c3f5b90395efeedad7a7239a7f9934670ad6d1 1.0.1 ca5211a03a2d9999a173bb57689a9f28ef7a70e6 1.0.3 9ac085d7efe8649fafa4cb7201bda3cb4971491c 1.0.4 c3939a649e0ac0fb9a2c9b24fd8345e009d31529 1.1 ./pyke-1.1.1/.hgignore0000644000175000017500000000012511346504626013421 0ustar lambylambysyntax: glob .pyhist *.pyc *.pyo .*.swp *.egg-info build dist compiled_krb MANIFEST ./pyke-1.1.1/testpyke0000755000175000017500000000424611346504630013417 0ustar lambylamby#!/bin/bash # testpyke [-x.y] [-3] # # Deletes all compiled_krb directories, then runs testall.py twice. # # exit status > 0 if errors found. TMP1=/tmp/testpyke1.$$ TMP2=/tmp/testpyke2.$$ TMP3=/tmp/testpyke3.$$ usage() { echo "usage: testpyke [-x.y] [-3] [suffix...]" >&2 echo " x.y is the Python version to use" >&2 exit 2 } options=(`getopt -uqan testpyke -o 3 -l 2.5,2.6,2.7,2.8,3.0,3.1,3.2,3.3 -- "$@"`) [ $? -eq 0 ] || usage #echo options "${options[@]}" #echo num options ${#options[*]} opt= args1= args2= first=1 suffix= i=0 while [ $i -lt ${#options[*]} ] do case ${options[$i]} in -3) opt="-3";; --) ;; --*) suffix=_${options[$i]//[-.]};; py) args1="$args1 py";; *) args1="$args1 ${options[$i]}" args2="$args2 ${options[$i]}" ;; esac i=$(($i + 1)) done if [ ! "$args1" ] then args2="tst txt" fi CMD="testall${suffix}.py $opt" #echo args "$args" #echo suffix "$suffix" #echo $CMD #exit echo Removing all compiled_krb directories. echo find . -name krb_compiler -prune -o -name compiled_krb -exec rm -rf "{}" + echo Running all tests with no compiled_krb files: echo $CMD -s $TMP1 $args1 status1=$? if [ "$args2" ] then echo echo Running all tests with compiled_krb files: echo $CMD -s $TMP2 $args2 status2=$? else status2=0 fi echo results1=(`sed -n '1s/^Files: \([0-9]*\), Tests: \([0-9]*\), Errors: \(.*\)$/\1 \2 \3/p' $TMP1`) echo "Compiling compiled_krb:" Files: ${results1[0]}, \ Tests: ${results1[1]}, \ Errors: ${results1[2]} if [ "$args2" ] then results2=(`sed -n '1s/^Files: \([0-9]*\), Tests: \([0-9]*\), Errors: \(.*\)$/\1 \2 \3/p' $TMP2`) echo "Reusing compiled_krb:" Files: ${results2[0]}, \ Tests: ${results2[1]}, \ Errors: ${results2[2]} tail -q -n +3 $TMP1 $TMP2 | sort -u > $TMP3 if [ -s $TMP3 ] then num_error_files=`wc -l $TMP3 | cut -f1 '-d '` echo echo "********** ERRORS ************* $num_error_files files had errors:" cat $TMP3 fi elif [ -s $TMP1 ] then echo tail -q -n +2 $TMP1 fi rm -f $TMP1 $TMP2 $TMP3 if [ $status1 -ne 0 -o $status2 -ne 0 ] then exit 1 fi ./pyke-1.1.1/RELEASE_NOTES-pre1.0.txt0000644000175000017500000004046711346504626015427 0ustar lambylamby0.7 RELEASE NOTES Jan 17, 2009 ============================== This fixes a reported bug in Pyke and also changes the way that Pyke searches for Pyke source files during initialization so that it depends on the standard Python Path rather than the program's Current Working Directory. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 0.6 RELEASE: - The parameters to knowledge_engine.engine.__init__ have changed. Pyke no longer searchs the current working directory for your Pyke source files. Your Pyke source files must now be located (either directly or indirectly) under a Python package directory that is on the standard Python Path. You must now specify the package name containing your Pyke source files as a parameter to knowledge_engine.engine.__init__. If you pass any of the load_fc, load_bc, load_fb, or load_qb parameters to knowledge_engine.engine.__init__, you must now pass these as keyword parameters. FEATURE ENHANCEMENTS: Added the following features: - Pyke's initialization logic no longer depends on the program's current working directory, making it easier to run on Windows. - Pyke can now load its compiled files from a Python .egg file. BUGS FIXED: The following bugs have been fixed: - Fixed bug 2496575: "duplicate knowledge base names" bug 0.6 RELEASE NOTES Dec 30, 2008 ============================== This fixes a reported bug in Pyke and also sets up the examples to be easier to run by not requiring that they be run from a certain directory. This makes them easier to run from Windows. The sqlgen and web_framework examples were also changed to use the Sqlite3 database rather than MySQL, again, to make the examples easier to run. Pyke's use of the PLY lex and yacc modules has been changed to avoid compiling the scanner and parsers each time Pyke is run. These changes should also help when running Pyke on IronPython as PLY has some incompatibilities with IronPython. Finally, Pyke has been upgraded to run on Python 2.6 without any Deprecation Warnings. (And Pyke still runs fine on Python 2.5). But to run Pyke on Python 2.6 you will need the development version of PLY (from subversion) until PLY comes out with it's release 2.6. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 0.5 RELEASE: - You must upgrade to PLY version 2.5. This can be done with: - easy_install --upgrade ply - If you want to run the web_framework example, you need to upgrade the HTMLTemplate package to the 1.5 release. You can use easy_install to install the 1.5 release, but may need to unintall the older release manually (I don't remember the prior release of HTMLTemplate being set up for easy_install). - The meaning of the generated_root_dir parameter to knowledge_engine.engine.__init__ has been changed (as well as its name). The default value produces the same result, so you only need to make source changes here if you specified a value for this parameter. FEATURE ENHANCEMENTS: Added the following features: - The examples no longer depend on the program's current working directory, making them easier to run from Windows. - The sqlgen and web_framework examples no longer require MySQL. They have been converted to Sqlite3 and the Sqlite3 database is included in the examples directory. So these examples no longer require setting up the database first. - Pyke runs on Python 2.6 without any Deprecation Warnings. - But you need the development version of PLY until PLY comes out with its release 2.6. - Changed Pyke's use of PLY to save the compiled scanner and parser tables rather than regenerating them each time Pyke is run. These changes should also avoid incompatibilities between IronPython and PLY. BUGS FIXED: The following bugs have been fixed: - Fixed bug 2339448: BC rules with nonexistent dependencies - Fixed bug 2062109: Pyke does not run on IronPython 2.0B3 and Jython 2.5a1 - This fix has now been tested and runs OK on Jython. - The test on IronPython exposed a bug in IronPython that has been reported to the IronPython project (as item# 20143 on their bug tracking list). 0.5 RELEASE NOTES Nov 9, 2008 ============================= This is the first beta release of Pyke. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 0.4 RELEASE: - The following functions now return a context manager rather than a generator. Note that the parameters to these functions have _not_ changed: - engine.prove_n - engine.prove - engine.lookup - NOTE: engine.prove_1 is not affected by this change. - YOU NEED TO CHANGE ALL CALLS TO THESE FUNCTIONS FROM: def your_function(): for vars, plan in engine.prove_n(...): TO: from __future__ import with_statement def your_function(): with engine.prove_n(...) as it: for vars, plan in it: - Pyke no longer replicates the source code directory structure under the compiled_krb directory. So the compiled_krb directory now has no subdirectories. - YOU SHOULD DELETE ALL compiled_krb DIRECTORIES PRIOR TO RUNNING PYKE 0.5. - The two parameters, gen_root_location and gen_root_pkg, to knowledge_engine.engine.__init__ have been merged into one parameter. The default values produce the same result, so you only need to make source changes here if you specified values for either of the old parameters. - generated_root_dir = 'compiled_krb' FEATURE ENHANCEMENTS: Added the following features: - Changed "Page Last Modified" date to be last svn commit date in html documentation. - Added sitemap.xml to doc/html BUGS FIXED: The following bugs have been fixed: - Fixed bug 2062109: Pyke does not run on IronPython 2.0B3 and Jython 2.5a1 - But this fix can not be tested until new releases of IronPython and Jython are produced. 0.4 RELEASE NOTES Aug 30, 2008 ============================== I expect to move Pyke to beta status soon. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 0.3 RELEASE: - Two parameters to knowledge_engine.engine have changed name (but not meaning): - gen_dir is now gen_root_location - gen_root_dir is now gen_root_pkg FEATURE ENHANCEMENTS: Added the following features: - Added 'special' commands to be able to run programs from rules and examine their results: - special.check_command -- runs program and checks exit status - special.command -- runs program and returns stdout as tuple of lines - special.general_command -- runs program and returns (exit_status, stdout, stderr) - Added .kfb files to make universal facts easier. - Added question bases and .kqb files so that rules can ask an end user questions. - Added a database trace_cursor to examples/web_framework/wsgi_app.py which is enabled by setting the TRACE_SQL env variable to anything other than 'False'. - Added performance testing and profiling harnesses to examples/web_framework. - Added default parameter values to engine.prove_1 and engine.prove_n for fixed_args and num_returns parameters. Added the following web pages: - The web pages have been extensively revised with many new pages to help the person new to logic programming. Added the following examples: - learn_pyke as unfinished Computer Based Training example using the new queston bases. - towers_of_hanoi as a brute force solution to the Towers of Hanoi puzzle. This generates multiple solutions (though only one is optimal). BUGS FIXED: The following bugs have been fixed: - Fixed bug 2041883: installation error "Sorry: IndentationError" - Fixed: the web_framework example does not do database commits. - Fixed: small bug in examples/sqlgen/test.py cursor.execute parameters. - Fixed: examples/web_framework not placing the returned document into a singleton tuple as per WSGI spec. 0.3 RELEASE NOTES Jun 8, 2008 ============================= I expect only minor feature enhancements at this point and am focusing now on testing. Often the sources under subversion are more stable than the release. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 0.2 RELEASE: - Made use of ':' after rule names in .krb files deprecated. - Also eliminated use of ':' after 'python' in fc_assertions so that ':' is no longer used to introduce indented lines in .krb files. - Changed the url format in the web_framework example from "/1/movie.html" to "/movie/1/movie.html". FEATURE ENHANCEMENTS: Added the following features: - Added 'forall'/'require', 'notany', 'first', and 'python' premises to both fc and bc rules. - The web_framework example now caches plans. It should be extremely fast now! - Anonymous pattern variables are now any identifier starting with an underscore. This allows you to document the variable. - Allow the 'taking' clause to be indented on the next line after the 'use' clause without requiring a \ continuation. - Changed 'python' syntax to allow either a single python statement on the same line as 'python', or multiple python statements indented under 'python'. - All .krb files are now automatically recompiled whenever the pyke version changes. - Top-level testall script. Added the following web pages: - KRB Syntax => Compound Premise - Examples - PyCon 2008 Paper Added the following examples: - knapsack example. This is a small example that came up at the PyCon 2008 conference. - findall example. This is mostly for testing the new forall premise combined with the new 'python' premise. - forall example. This is mostly for testing the new forall premise. - notany example. This is mostly for testing the new notany premise. BUGS FIXED: The following bugs have been fixed: The 'python' assertion in fc rules didn't work. 0.2 RELEASE NOTES Mar 05, 2008 ============================== I expect only minor feature enhancements at this point and am focusing now on testing. Often the sources under subversion are more stable than the alpha release. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 0.1.alpha2 RELEASE: The top-level pyke package code has been moved into a standard module called knowledge_engine.py. Therefore, you need to: 1. Change all "import pyke" lines to "from pyke import knowledge_engine". 2. Change all "pyke.engine(...)" calls to "knowledge_engine.engine(...)". FEATURE ENHANCEMENTS: Added the following web pages: Logic Tricks Added the following features: - engine.print_stats() - engine.trace and engine.untrace functions to trace backward-chaining rules. - added "allow_vars" parameter to "as_data" methods. - added test.py module to pyke package. This lets you type in goals and run them interactively to help debug your rules. Also allows you to test the plan functions returned from goals. Added the following examples: - To family_relations: fc_example.krb bc_example.krb bc2_example.krb (a few rule optimizations make it 100 times faster than bc_example.krb) Many enhancements to test.py, including a "general" function to show how to create your own patterns to prove fully general goals with pattern variables anywhere in the arguments. - Added sqlgen example. This automatically generates sql statements, figuring out how to join tables together to get needed data. This runs against MySQL and introspects the schema automatically. - Added web_framework example. This uses the HTMLTemplate facility as its templating engine. It automatically builds the program to render the templates, including using the sqlgen rule base to automatically build the sql statements to retrieve the data needed by the template. BUGS FIXED: The following bugs have been fixed: #1908852 unpickling a plan causes most of pyke to be imported. #1908855 a comment on same line as "check" premise causes syntax errors in generated .py files. #1908856 tuple patterns treat strings as tuples #1908862 *_plans.py files not reloaded #1908867 engine.reset wasn't reseting forward-chaining rules. #1908870 variable binding loops were possible. #1908875 rest_var in pattern tuple not checked properly. #1908877 debug messages are being printed when .krb syntax errors are encountered. #1908880 prove_n and prove_1 were not converting prototype plans stored in pattern variables into plan functions. 0.1.alpha2 RELEASE NOTES Feb 12, 2008 ===================================== I expect only minor feature enhancements at this point and am focusing now on testing. Often the sources under subversion are more stable than the alpha release. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH PRIOR RELEASE: The pyke inference engine was converted into an object so that multiple instances can be used simultaniously running different rule bases. This changed the top-level interfaces into pyke. The "load" function is now the constructor on the pyke.engine class. There have been some new arguments added to it since the alpha1 release. See the new "Using Pyke" web page. All other top-level pyke.X functions are now .X methods. The function names and arguments remain the same. The .krb syntax has not changed, but the compiled python modules have. If you are upgrading from the alpha1 release, the compiled python modules are now placed into a ./compiled_krb directory which is created automatically. (You can change the location and/or name of this directory through the new arguments to the pyke.engine constructor). You'll need to delete the generated *_fc.py, *_bc.py, and *_plans.py files and their corresponding .pyc and .pyo files. If you are upgrading from a subversion release and already have generated python modules in a "compiled_krb" directory, you will need to either delete the generated *_fc.py, *_bc.py, and *_plans.py files or "touch" your *.krb files to cause them to be recompiled by the new system. FEATURE ENHANCEMENTS: The following Feature Requests have been implemented: #1865574: Create top-level pyke class See INCOMPATIBILITIES, above. A krb_traceback module has been added to translate filename, line, linenumber and function name information from the generated python modules into equivalent information for the source .krb files. See the new "Using Pyke" web page. Substantial changes to the following web pages: Using Pyke Overview -> Plans Overview -> Knowledge Bases -> Rule Bases KRB Syntax -> Bc_rule BUGS FIXED: The following Bugs have been fixed: #1864966: PYTHONPATH ignored for compiler invocation #1861999: update clause plan documentation #1861997: sys.path must match pyke.load() #1861993: extending mismatch between activate and prove #1861992: plan enumeration is broken #1861988: prove_1 exception error #1843445: KeyError in _unbind #1843408: facts are not matched in the correct order #1843395: "%(rule_name)s" in error text #1830762: "'python2.5' is not recognized" on windows 0.1.alpha1 RELEASE NOTES Nov 9, 2007 ==================================== 0.1.alpha1 is the first alpha release of pyke. I expect only minor feature enhancements at this point and am focusing now on testing. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs ./pyke-1.1.1/make_doc_tarball0000755000175000017500000000026011346504626015007 0ustar lambylamby#!/bin/bash usage() { echo "usage: make_doc_tarball tarfile" >&2 exit 2 } [ $# -eq 1 ] || usage tar --exclude='*/.*.swp' --exclude='*/.pyhist' -C doc -czf "$1" html ./pyke-1.1.1/README.txt0000644000175000017500000001024211365361720013312 0ustar lambylambyPyke: Python Knowledge Engine Version: 1.1.1 Both forward-chaining and backward-chaining rules (which may include python code) are compiled into python. Can also automatically assemble python programs out of python functions which are attached to backward-chaining rules. COPYRIGHT AND LICENSE: This is published under the MIT License. The copyright and license are in the file "LICENSE" in the source directory. DOCUMENTATION: The complete documentation is at: http://pyke.sourceforge.net A copy of the html documentation is also included in the "doc/html" directory within the source distribution. REQUIREMENTS: Pyke requires python 2.5 or later. Check with: $ python --version You can download python at: http://www.python.org TO INSTALL: 1. Download and unzip the source distribution for the version of Python that you want to use. If you want to use Python 2.5 or 2.6, you need to use the pyke-1.1.1.zip sources. If you want to use Python 3.x, you need to use the pyke3-1.1.1.zip sources. 2. Open a command line window in the directory above. 3. Run "python setup.py build" 4. As administrator, run: "python setup.py install" See http://pyke.sourceforge.net/about_pyke/installing_pyke.html for more information. SOURCE DISTRIBUTION: The source distribution contains the pyke source code, documentation (both source and html), unit tests, and examples. EXAMPLES: Each example is in a separate subdirectory under the "examples" directory. Each example has a README.txt file that explains how to run it. The family_relations example is a good place to start. It shows several solutions to the same problem. It also has an example of a few rule optimizations that result in a 100 times performance improvement on this problem. The sqlgen example uses Sqlite3 (or MySQL) and the python sqlite3 (or MySQLdb) modules. It has a function that reads the schema information into pyke facts. Then the rules in database.krb automatically figure out how to join tables together to retrieve a list of column names, generate the SQL select statements and return a plan to execute this SQL statement and return the results as a dictionary. The web_framework example uses the sqlgen example. This demonstrates the use of multiple rule bases. The web_framework is a WSGI application that uses the HTMLTemplate package (install this as administrator with "pip install HTMLTemplate" or "easy_install HTMLTemplate" -- be sure to get version 1.5 or later). It gets the column names from the HTMLTemplate and feeds those to the sqlgen example to generate a plan to retrieve the data. It then builds a plan to populate the template and return the finished HTML document. It also caches the plans so that they don't have to be re-generated for each request. This makes it run a full 10 times faster than the same example done in TurboGears 2! The example includes a wsgiref simple_server setup to run it as an http server so that you can access it through your browser. The learn_pyke example is an incomplete attempt at a computer based training program. It only deals with the topic of pattern matching. It is left here as an example of using question bases. The findall, forall, knapsack, notany and towers_of_hanoi examples are each very small. See http://pyke.sourceforge.net/examples.html for more information. RUNNING DOCTESTS: Pyke uses the doctest-tools package to run its doctests. You can run the "testall.py" program from doctest-tools in any subdirectory, or in the top-level directory. You can install doctest-tools as administrator with: # pip install doctest-tools The top-level directory also has it's own "testpyke" script that removes all compiled_krb directories, then runs the testall.py script (from doctest-tools) twice. The first time forces pyke to recompile everything, and the second time runs the same tests again having pyke re-use the compiled results from the previous run. If the "testpyke" program is not on your path, run it as: $ ./testpyke WORKING ON PYKE: See http://pyke.sourceforge.net/about_pyke/modifying_pyke.html for information about doing development work on Pyke. Contributions of any kind are welcome! ./pyke-1.1.1/LICENSE0000644000175000017500000000204411346504626012625 0ustar lambylambyCopyright © 2007 Bruce Frederiksen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ./pyke-1.1.1/PKG-INFO0000644000175000017500000000233011365364620012712 0ustar lambylambyMetadata-Version: 1.0 Name: pyke Version: 1.1.1 Summary: Python Knowledge Engine and Automatic Python Program Generator Home-page: http://sourceforge.net/projects/pyke Author: Bruce Frederiksen Author-email: dangyogi@gmail.com License: MIT License Download-URL: http://downloads.sourceforge.net/pyke/pyke-1.1.1.zip Description: Both forward-chaining and backward-chaining rules (which may include python code) are compiled into python. Can also automatically assemble python programs out of python functions which are attached to backward-chaining rules. Keywords: expert system forward backward chaining backtracking plans Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence Classifier: Topic :: Software Development :: Code Generators Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Internet :: Log Analysis ./pyke-1.1.1/hgrc_keywords0000644000175000017500000000044311346504626014416 0ustar lambylamby [extensions] hgext.keyword = [keyword] **.kqb = **.krb = **.kfb = **.py = **.sql = **.sqlite3 = **.txt = **copyright_license = **load_sqlite3 = **load_tables = [keywordmaps] Id = {file|basename} {node|short} {date|shortdate} {author|user} Date = {date|shortdate} Revision = {node|short} ./pyke-1.1.1/pyke/0000755000175000017500000000000011425360453012564 5ustar lambylamby./pyke-1.1.1/pyke/condensedPrint.py0000644000175000017500000001346011346504626016125 0ustar lambylamby# $Id: condensedPrint.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys import types import re def cprint(obj, maxlen = 80, maxdepth = 4, maxlines = 20): items = cprint2(obj, maxdepth) #sys.stderr.write("cprint items: %s\n" % str(items)) return format(items, maxlen, maxlen, maxlines)[0] def format_len(x): """ >>> format_len('abc') 3 >>> format_len(('(', ('(', 'def', ')'), 'yz', ')')) 11 """ if not isinstance(x, (list, tuple)): return len(x) if len(x) > 3: sep_len = 2 * (len(x) - 3) else: sep_len = 0 return sum(map(format_len, x)) + sep_len def format(x, lenleft, maxlen, maxlines, indent = 0): r""" >>> format('"hello mom this is a long str"', 7, 80, 9) ('"he..."', 0) >>> format(('(', 'a', 'b', 'c', ')'), 80, 80, 9) ('(a, b, c)', 0) >>> format(('(', 'a', 'b', 'c', ')'), 8, 80, 9) ('(a,\n b,\n c)', 2) """ if not isinstance(x, (list, tuple)): if len(x) <= lenleft: return x, 0 if isinstance(x, types.StringTypes) and x[-1] in "'\"": if lenleft >= 5: return x[:lenleft-4] + '...' + x[-1], 0 else: if lenleft >= 4: return x[:lenleft-3] + '...', 0 return '&', 0 if len(x) == 0: return '', 0 if format_len(x) <= lenleft: return x[0] + \ ', '.join(format(y, lenleft, maxlen, maxlines)[0] for y in x[1:-1]) + \ x[-1], 0 indent += 2 ans = x[0] lines_taken = 0 if len(x) > 2: first, taken = \ format(x[1], lenleft - len(ans), maxlen, maxlines, indent + 2) ans += first lines_taken += taken for y in x[2:-1]: if lines_taken >= maxlines: ans += ', ...' break line, taken = \ format(y, maxlen - indent, maxlen, maxlines - lines_taken, indent) ans += ',\n' + indent * ' ' + line lines_taken += taken + 1 return ans + x[-1], lines_taken def cprint2(obj, maxdepth): if isinstance(obj, types.TupleType): return printSeq('(', ')', obj, maxdepth) if isinstance(obj, types.ListType): return printSeq('[', ']', obj, maxdepth) if isinstance(obj, types.DictType): return printDict(obj, maxdepth) if isinstance(obj, types.StringTypes): return printStr(obj) try: return str(obj) except StandardError, e: exc_type, exc_value, exc_traceback = sys.exc_info() import traceback if isinstance(obj, types.InstanceType): obj_type = obj.__class__ else: obj_type = type(obj) return "While trying to cprint a %s, got: %s" % \ (obj_type, traceback.format_exception_only(exc_type, exc_value)) str_chk = re.compile('[a-zA-Z_][a-zA-Z0-9_]*$') def printStr(str): """ >>> printStr('hello_34_A') 'hello_34_A' >>> printStr('hello 34_A') "'hello 34_A'" """ if str_chk.match(str): return str return repr(str) def printSeq(startChar, endChar, seq, maxdepth): """ >>> printSeq('(', ')', (1, 2, 3), 4) ['(', '1', '2', '3', ')'] >>> printSeq('(', ')', (), 4) ['(', ')'] """ if maxdepth < 1: return '&' maxdepth -= 1 return [startChar] + [cprint2(x, maxdepth) for x in seq] + [endChar] def item(key, value, maxdepth, separator): """ >>> item('hello', 'bob', 3, '=') 'hello=bob' >>> item(('hello', 'there'), 'bob', 3, '=') ['(', 'hello', 'there', ')=bob'] >>> item('hello', ('extra', 'bob'), 3, '=') ['hello=(', 'extra', 'bob', ')'] >>> item(('hello', 'there'), ('extra', 'bob'), 3, '=') ['(', 'hello', 'there', ')=(', 'extra', 'bob', ')'] """ keyans = cprint2(key, maxdepth) valans = cprint2(value, maxdepth) if isinstance(keyans, list): keyans[-1] += separator if isinstance(valans, list): keyans[-1] += valans[0] keyans.extend(valans[1:]) else: keyans[-1] += valans return keyans if isinstance(valans, list): valans[0] = keyans + separator + valans[0] return valans return keyans + separator + valans def printDict(dict, maxdepth, startChar = '{', endChar = '}', separator = ': '): """ >>> printDict({1:2, 3:4, 5:(6,7)}, 5) ['{', '1: 2', '3: 4', ['5: (', '6', '7', ')'], '}'] >>> printDict({}, 5) ['{', '}'] """ if maxdepth < 1: return '&' maxdepth -= 1 keys = dict.keys() keys.sort() return [startChar] + \ [item(key, dict[key], maxdepth, separator) for key in keys] + \ [endChar] ./pyke-1.1.1/pyke/krb_traceback.py0000644000175000017500000001235711346504630015722 0ustar lambylamby# $Id: krb_traceback.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import linecache import traceback import os.path import sys def print_tb(traceback, limit=None, file=None): if file is None: file = sys.stderr for line in format_list(extract_tb(traceback, limit)): file.write(line) def print_exception(type, value, traceback, limit=None, file=None): if file is None: file = sys.stderr if traceback: file.write('Traceback (most recent call last):\n') print_tb(traceback, limit, file) lines = format_exception_only(type, value) file.write(lines[0]) for line in lines[1:]: file.write(' ' + line) def print_exc(limit=None, file=None): type, value, traceback = sys.exc_info() print_exception(type, value, traceback, limit, file) def format_exc(limit=None): type, value, traceback = sys.exc_info() return format_exception(type, value, traceback, limit) def print_last(limit=None, file=None): print_exception(sys.last_type, sys.last_value, sys.last_traceback, limit, file) def print_stack(f=None, limit=None, file=None): if file is None: file = sys.stderr for line in format_list(extract_stack(f, limit)): file.write(line) def extract_tb(tb, limit=None): ans = convert_tb(traceback.extract_tb(tb)) if limit is not None and len(ans) > limit: return ans[len(ans) - limit:] return ans def extract_stack(f=None, limit=None): ans = convert_tb(traceback.extract_stack(f)) if limit is not None and len(ans) > limit: return ans[len(ans) - limit:] return ans format_list = traceback.format_list format_exception_only = traceback.format_exception_only def format_exception(type, value, traceback, limit=None): ans = [] if traceback: ans.append('Traceback (most recent call last):\n') ans.extend(format_list(extract_tb(traceback, limit))) lines = format_exception_only(type, value) ans.append(lines[0]) for line in lines[1:]: ans.append(' ' + line) return ''.join(ans) def format_tb(tb, limit=None): return format_list(extract_tb(tb, limit)) def format_stack(f=None, limit=None): return format_list(extract_stack(f, limit)) def convert_lineno(module, lineno): for (py_start, py_end), (krb_start, krb_end) in module.Krb_lineno_map: if py_start <= lineno <= py_end: return krb_start def convert_tb(extracted_tb): ''' extracted_tb is list of (filename, lineno, functionname, line) ''' ans = [] batch = [] for info in extracted_tb: filename, lineno, functionname, line = info if filename.endswith('_fc.py') or filename.endswith('_bc.py'): pathname = filename[:-3] while True: module_name = pathname.replace(os.path.sep, '.') if module_name in sys.modules: module = sys.modules[module_name] if hasattr(module, 'Krb_filename'): krb_lineno = convert_lineno(module, lineno) if krb_lineno is not None: if not ans: ans = batch batch = [] krb_filename = \ os.path.normpath( os.path.join(os.path.dirname(module.__file__), module.Krb_filename)) linecache.checkcache(krb_filename) line = linecache.getline(krb_filename, krb_lineno) if line: line = line.strip() else: line = None ans.append((krb_filename, krb_lineno, functionname, line)) info = None else: ans.extend(batch) ans.append(info) batch = [] info = None break sep_index = pathname.find(os.path.sep) if sep_index < 0: break pathname = pathname[sep_index + 1:] if info: batch.append(info) return ans + batch ./pyke-1.1.1/pyke/pattern.py0000644000175000017500000001503311346504630014614 0ustar lambylamby# $Id: pattern.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import types import itertools class pattern(object): def __ne__(self, b): return not (self == b) def simple_match_pattern(self, bindings, my_context, pattern_b, b_context): return self.match_pattern(bindings, my_context, pattern_b, b_context) def lookup(self, context, allow_variable_in_ans = False): return self class pattern_literal(pattern): def __init__(self, literal): self.literal = literal def __hash__(self): return hash(self.literal) def __eq__(self, b): if isinstance(b, pattern_literal): return self.literal == b.literal return self.literal == b def match_data(self, bindings, my_context, data): return self.literal == data def match_pattern(self, bindings, my_context, pattern_b, b_context): if isinstance(pattern_b, pattern_literal): return self.literal == pattern_b.literal return pattern_b.match_data(bindings, b_context, self.literal) def as_data(self, my_context, allow_vars = False, final = None): return self.literal def is_data(self, my_context): return True class pattern_tuple(pattern): def __init__(self, elements, rest_var = None): self.elements = tuple(elements) self.rest_var = rest_var def __hash__(self): return hash(self.elements) ^ hash(self.rest_var) def __eq__(self, b): return isinstance(b, pattern_tuple) and \ self.elements == b.elements and self.rest_var == b.rest_var def match_data(self, bindings, my_context, data): if isinstance(data, types.StringTypes): return False try: data = tuple(data) except TypeError: return False if len(self.elements) > len(data) or \ self.rest_var is None and len(self.elements) < len(data): return False for x, y in itertools.izip(self.elements, data): if not x.match_data(bindings, my_context, y): return False if self.rest_var is not None: return self.rest_var.match_data(bindings, my_context, tuple(data[len(self.elements):])) return True def simple_match_pattern(self, bindings, my_context, pattern_b, b_context): return self, my_context def match_pattern(self, bindings, my_context, pattern_b, b_context): simple_ans = pattern_b.simple_match_pattern(bindings, b_context, self, my_context) if isinstance(simple_ans, bool): return simple_ans pattern_b, b_context = simple_ans if not isinstance(pattern_b, pattern): return self.match_data(bindings, my_context, pattern_b) assert isinstance(pattern_b, pattern_tuple), "Internal logic error" my_len = len(self.elements) b_len = len(pattern_b.elements) if pattern_b.rest_var is None and my_len > b_len or \ self.rest_var is None and my_len < b_len: return False for x, y in itertools.izip(self.elements, pattern_b.elements): if not x.match_pattern(bindings, my_context, y, b_context): return False if my_len <= b_len and self.rest_var is not None: # This is where the two rest_vars are bound together if my_len == # b_len. tail_val, tail_context = pattern_b._tail(my_len, b_context) if tail_context is None: if not self.rest_var.match_data(bindings, my_context, tail_val): return False else: if not self.rest_var.match_pattern(bindings, my_context, tail_val, tail_context): return False elif pattern_b.rest_var is not None: tail_val, tail_context = self._tail(b_len, my_context) if tail_context is None: if not pattern_b.rest_var.match_data(bindings, b_context, tail_val): return False else: if not pattern_b.rest_var.match_pattern(bindings, b_context, tail_val, tail_context): return False return True def as_data(self, my_context, allow_vars = False, final = None): ans = tuple(x.as_data(my_context, allow_vars, final) for x in self.elements) if self.rest_var is None: return ans rest = my_context.lookup_data(self.rest_var.name, allow_vars, final) if isinstance(rest, tuple): return ans + rest return ans + ('*' + rest,) def _tail(self, n, my_context): """ Return a copy of myself with the first n elements removed. """ if n == len(self.elements): if self.rest_var is None: return (), None return self.rest_var, my_context rest_elements = self.elements[n:] if self.rest_var is None and \ all(isinstance(x, pattern_literal) for x in rest_elements): return tuple(x.literal for x in rest_elements), None return pattern_tuple(self.elements[n:], self.rest_var), my_context def is_data(self, my_context): arg_test = all(arg_pat.is_data(my_context) for arg_pat in self.elements) if not arg_test or self.rest_var is None: return arg_test return self.rest_var.is_data(my_context) ./pyke-1.1.1/pyke/__init__.py0000644000175000017500000000016611365362354014705 0ustar lambylamby# $Id: __init__.py cb952835bf06 2010-04-26 mtnyogi $ version = '1.1.1' compiler_version = 1 target_pkg_version = 1 ./pyke-1.1.1/pyke/ask_wx.py0000644000175000017500000001560111346504626014441 0ustar lambylamby# $Id: ask_wx.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. r''' A "match" here is one of: - an instance of qa_helpers.regexp - msg (for error message) - prompt (without the []) - match(str) returns converted value (None if no match) - an instance of qa_helpers.qmap - test (a match) - value (value to use) - an instance of slice (step must be None) - a tuple of matches (implied "or") - some other python value (which stands for itself) A "review" here is a tuple of (match, review_string) "Alternatives" here is a tuple of (tag, label_string) ''' import sys if __name__ != "__main__" \ and ('__main__' not in sys.modules or 'doctest' not in sys.modules['__main__'].__file__): # So we don't screw up doctest runs on boxes that don't have wxPython # installed... import wx import itertools from pyke import qa_helpers def review_ans(dlg, ans, review=None): if review: def matches2(ans, test): try: qa_helpers.match(ans, test) return True except ValueError: return False def matches(ans, test): if isinstance(ans, (tuple, list)): return any(itertools.imap(lambda elem: matches2(elem, test), ans)) return matches2(ans, test) msg = u'\n\n'.join(review_str for review_test, review_str in review if matches(ans, review_test)) if msg: dlg2 = wx.MessageDialog(dlg, msg, 'Answer Information', wx.OK | wx.ICON_INFORMATION) dlg2.ShowModal() dlg2.Destroy() def get_answer(question, title, conv_fn=None, test=None, review=None): dlg = wx.TextEntryDialog(None, question, title, u'', wx.CENTRE | wx.RESIZE_BORDER | wx.OK) while True: status = dlg.ShowModal() if status != wx.ID_OK: raise AssertionError("dlg.ShowModal failed with %d" % status) ans = dlg.GetValue() try: if conv_fn: ans = conv_fn(ans) if test: ans = qa_helpers.match(ans, test) break except ValueError, e: err = wx.MessageDialog(dlg, u"Answer should be %s\nGot %s" % (e.message, repr(ans)), u"Error Notification", wx.OK | wx.ICON_ERROR) err.ShowModal() err.Destroy() dlg.SetValue(u"") review_ans(dlg, ans, review) dlg.Destroy() return ans def ask_yn(question, review=None): dlg = wx.MessageDialog(None, question, 'User Question', wx.YES_NO | wx.ICON_QUESTION) status = dlg.ShowModal() if status not in (wx.ID_YES, wx.ID_NO): raise AssertionError("dlg.ShowModal failed with %d" % status) ans = status == wx.ID_YES review_ans(dlg, ans, review) dlg.Destroy() return ans def ask_integer(question, match=None, review=None): return get_answer(question, qa_helpers.match_prompt(match, int, "Enter Integer %s", 'Enter Integer'), conv_fn=qa_helpers.to_int, test=match, review=review) def ask_float(question, match=None, review=None): return get_answer(question, qa_helpers.match_prompt(match, float, "Enter Number %s", 'Enter Number'), conv_fn=qa_helpers.to_float, test=match, review=review) def ask_number(question, match=None, review=None): return get_answer(question, qa_helpers.match_prompt(match, int, "Enter Number %s", 'Enter Number'), conv_fn=qa_helpers.to_number, test=match, review=review) def ask_string(question, match=None, review=None): return get_answer(question, qa_helpers.match_prompt(match, str, "Enter %s", 'User Question'), test=match, review=review) def ask_select_1(question, alternatives, review=None): dlg = wx.SingleChoiceDialog(None, question, u'User Question', [msg for tag, msg in alternatives], wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.OK) status = dlg.ShowModal() if status != wx.ID_OK: raise AssertionError("dlg.ShowModal failed with %d" % status) selection = dlg.GetSelection() #print 'Got selection: %s' % str(selection) ans = alternatives[selection][0] review_ans(dlg, ans, review) dlg.Destroy() return ans def ask_select_n(question, alternatives, review=None): #dlg = wx.lib.dialogs.MultiChoiceDialog(None, question, u'User Question', # [msg for tag, msg in alternatives]) dlg = wx.MultiChoiceDialog(None, question, u'User Question', [msg for tag, msg in alternatives], wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.OK) status = dlg.ShowModal() if status != wx.ID_OK: raise AssertionError("dlg.ShowModal failed with %d" % status) #selections = dlg.GetValue() selections = dlg.GetSelections() #print 'Got selections: %s' % str(selections) ans = tuple(alternatives[i][0] for i in selections) review_ans(dlg, ans, review) dlg.Destroy() return ans ./pyke-1.1.1/pyke/user_question.py0000644000175000017500000000726511346504630016054 0ustar lambylamby# $Id: user_question.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. r''' This uses a parser object which is expected to have the following methods: - get_token(check_token=None) - parse_match() - parse_alternatives() - parse_review() ''' from string import Template class user_question(object): match = None review = None def __init__(self, format): self.format = Template(format) def __repr__(self): head = "<%s" % self.__class__.__name__ if self.match: head += self.repr_match() head += ": %s" % repr(self.format.template) if self.review: head += " %s" % ' ! '.join(repr(m) for m, text in self.review) return head + ">" def repr_match(self): return "(%s)" % repr(self.match) def parse(self, parser): self.parse_args(parser) self.review = parser.parse_review() def parse_args(self, parser): pass def set_question_base(self, question_base): self.question_base = question_base def get_ask_module(self): return self.question_base.get_ask_module() def ask(self, format_params): ask_fn = getattr(self.get_ask_module(), 'ask_' + self.__class__.__name__) if self.review: review = tuple((match, template.substitute(format_params)) for match, template in self.review) else: review = None arg2 = self.prepare_arg2(format_params) if arg2: return ask_fn(self.format.substitute(format_params), arg2, review=review) return ask_fn(self.format.substitute(format_params), review=review) def prepare_arg2(self, format_params): return self.match class yn(user_question): pass class match_args(user_question): def parse_args(self, parser): token, value = parser.get_token() if token == 'lparen': self.match = parser.parse_match() parser.get_token('rparen') else: parser.push_token() class integer(match_args): pass class float(match_args): pass class number(match_args): pass class string(match_args): pass class select_1(user_question): def repr_match(self): return "(%s)" % ' '.join(repr(t) + ':' for t, text in self.match) def parse(self, parser): self.match, self.review = parser.parse_alternatives() def prepare_arg2(self, format_params): return tuple((tag, label.substitute(format_params)) for tag, label in self.match) class select_n(select_1): pass ./pyke-1.1.1/pyke/goal.py0000644000175000017500000000465311346504630014067 0ustar lambylamby# goal.py r'''goal.compile is what you're looking for here! EXAMPLE USAGE: from pyke import goal bruce_related_to = \ goal.compile('bc_example.how_related(bruce, $who, $ans)') def main(): with bruce_related_to(my_engine, who='thomas') as gen: for vars, plan in gen: print vars['ans'] ''' from __future__ import with_statement import itertools from pyke import contexts, knowledge_engine, krb_compiler def compile(goal_str): return prover(goal_str, *krb_compiler.compile_goal(goal_str)) class prover(object): def __init__(self, goal_str, rb_name, goal_name, patterns, pattern_vars): #print "prover", rb_name, goal_name, patterns, pattern_vars self.goal_str = goal_str self.rb_name = rb_name self.goal_name = goal_name self.patterns = patterns self.pattern_vars = pattern_vars def prove(self, engine, **args): context = contexts.simple_context() for var, value in args.iteritems(): context.bind(var, context, value) return producer(engine, self.rb_name, self.goal_name, self.patterns, context, self.pattern_vars) def prove_1(self, engine, **args): global knowledge_engine try: # All we need is the first one! with self.prove(engine, **args) as it: return iter(it).next() except StopIteration: raise knowledge_engine.CanNotProve("Can not prove " + self.goal_str) class producer(object): def __init__(self, engine, rb_name, goal_name, patterns, context, pattern_vars): self.engine = engine self.rb_name = rb_name self.goal_name = goal_name self.patterns = patterns self.context = context self.pattern_vars = pattern_vars def __enter__(self): self.context_manager = self.engine.prove(self.rb_name, self.goal_name, self.context, self.patterns) it = iter(self.context_manager.__enter__()) return itertools.imap(self.doctor_answer, it) def __exit__(self, type, value, tb): return self.context_manager.__exit__(type, value, tb) def doctor_answer(self, prototype_plan): return dict((name, self.context.lookup_data(name)) for name in self.pattern_vars), \ prototype_plan and prototype_plan.create_plan() ./pyke-1.1.1/pyke/special.py0000644000175000017500000002463211346504630014564 0ustar lambylamby# $Id: special.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import subprocess import contextlib from pyke import knowledge_base, rule_base # claim_goal, fact, prove_all, gather_all class special_knowledge_base(knowledge_base.knowledge_base): def __init__(self, engine): super(special_knowledge_base, self).__init__(engine, 'special') def add_fn(self, fn): if fn.name in self.entity_lists: raise KeyError("%s.%s already exists" % (self.name, fn.name)) self.entity_lists[fn.name] = fn def print_stats(self, f): pass class special_fn(knowledge_base.knowledge_entity_list): def __init__(self, special_base, name): super(special_fn, self).__init__(name) special_base.add_fn(self) def lookup(self, bindings, pat_context, patterns): raise AssertionError("special.%s may not be used in forward chaining " "rules" % self.name) def prove(self, bindings, pat_context, patterns): raise AssertionError("special.%s may not be used in backward chaining " "rules" % self.name) class special_both(special_fn): def prove(self, bindings, pat_context, patterns): return self.lookup(bindings, pat_context, patterns) class claim_goal(special_fn): r''' >>> class stub(object): ... def add_fn(self, fn): pass >>> cg = claim_goal(stub()) >>> mgr = cg.prove(None, None, None) >>> gen = iter(mgr.__enter__()) >>> gen.next() >>> gen.next() Traceback (most recent call last): ... StopProof >>> mgr.__exit__(None, None, None) >>> cg.lookup(None, None, None) Traceback (most recent call last): ... AssertionError: special.claim_goal may not be used in forward chaining rules ''' def __init__(self, special_base): super(claim_goal, self).__init__(special_base, 'claim_goal') def prove(self, bindings, pat_context, patterns): def gen(): yield raise rule_base.StopProof return contextlib.closing(gen()) def run_cmd(pat_context, cmd_pat, cwd_pat=None, stdin_pat=None): r''' >>> from pyke import pattern >>> run_cmd(None, pattern.pattern_literal(('true',))) (0, '', '') >>> run_cmd(None, pattern.pattern_literal(('false',))) (1, '', '') >>> ret, out, err = run_cmd(None, pattern.pattern_literal(('pwd',))) >>> ret 0 >>> err '' >>> import os >>> cwd = os.getcwd() + '\n' >>> out == cwd True >>> run_cmd(None, pattern.pattern_literal(('pwd',)), ... pattern.pattern_literal('/home/bruce')) (0, '/home/bruce\n', '') ''' stdin = None if stdin_pat is None \ else stdin_pat.as_data(pat_context) process = subprocess.Popen(cmd_pat.as_data(pat_context), bufsize=-1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd= None if cwd_pat is None else cwd_pat.as_data(pat_context)) out, err = process.communicate(stdin) return process.returncode, out, err class check_command(special_both): r''' >>> from pyke import pattern, contexts >>> class stub(object): ... def add_fn(self, fn): pass >>> cc = check_command(stub()) >>> ctxt = contexts.simple_context() >>> mgr = cc.lookup(ctxt, ctxt, (pattern.pattern_literal(('true',)),)) >>> gen = iter(mgr.__enter__()) >>> gen.next() >>> ctxt.dump() >>> gen.next() Traceback (most recent call last): ... StopIteration >>> ctxt.dump() >>> mgr.__exit__(None, None, None) >>> mgr = cc.lookup(ctxt, ctxt, (pattern.pattern_literal(('false',)),)) >>> gen = iter(mgr.__enter__()) >>> gen.next() Traceback (most recent call last): ... StopIteration >>> ctxt.dump() >>> mgr.__exit__(None, None, None) >>> mgr = cc.prove(ctxt, ctxt, (pattern.pattern_literal(('true',)),)) >>> gen = iter(mgr.__enter__()) >>> gen.next() >>> ctxt.dump() >>> gen.next() Traceback (most recent call last): ... StopIteration >>> ctxt.dump() >>> mgr.__exit__(None, None, None) ''' def __init__(self, special_base): super(check_command, self).__init__(special_base, 'check_command') def lookup(self, bindings, pat_context, patterns): if len(patterns) < 1: return knowledge_base.Gen_empty retcode, out, err = run_cmd(pat_context, patterns[0], patterns[1] if len(patterns) > 1 else None, patterns[2] if len(patterns) > 2 else None) if retcode: return knowledge_base.Gen_empty return knowledge_base.Gen_once class command(special_both): r''' >>> from pyke import pattern, contexts >>> class stub(object): ... def add_fn(self, fn): pass >>> c = command(stub()) >>> ctxt = contexts.simple_context() >>> mgr = c.lookup(ctxt, ctxt, ... (contexts.variable('ans'), ... pattern.pattern_literal(('echo', 'hi')))) >>> gen = iter(mgr.__enter__()) >>> gen.next() >>> ctxt.dump() ans: ('hi',) >>> gen.next() Traceback (most recent call last): ... StopIteration >>> ctxt.dump() >>> mgr.__exit__(None, None, None) >>> mgr = c.lookup(ctxt, ctxt, ... (contexts.variable('ans'), ... pattern.pattern_literal(('cat',)), ... pattern.pattern_literal(None), ... pattern.pattern_literal('line1\nline2\nline3\n'))) >>> gen = iter(mgr.__enter__()) >>> gen.next() >>> ctxt.dump() ans: ('line1', 'line2', 'line3') >>> gen.next() Traceback (most recent call last): ... StopIteration >>> ctxt.dump() >>> mgr.__exit__(None, None, None) ''' def __init__(self, special_base): super(command, self).__init__(special_base, 'command') def lookup(self, bindings, pat_context, patterns): if len(patterns) < 2: return knowledge_base.Gen_empty retcode, out, err = run_cmd(pat_context, patterns[1], patterns[2] if len(patterns) > 2 else None, patterns[3] if len(patterns) > 3 else None) if retcode != 0: raise subprocess.CalledProcessError( retcode, ' '.join(patterns[1].as_data(pat_context))) def gen(): mark = bindings.mark(True) try: outlines = tuple(out.rstrip('\n').split('\n')) if patterns[0].match_data(bindings, pat_context, outlines): bindings.end_save_all_undo() yield else: bindings.end_save_all_undo() finally: bindings.undo_to_mark(mark) return contextlib.closing(gen()) class general_command(special_both): r''' >>> from pyke import pattern, contexts >>> class stub(object): ... def add_fn(self, fn): pass >>> gc = general_command(stub()) >>> ctxt = contexts.simple_context() >>> ctxt.dump() >>> mgr = gc.lookup(ctxt, ctxt, ... (contexts.variable('ans'), ... pattern.pattern_literal(('echo', 'hi')))) >>> gen = iter(mgr.__enter__()) >>> gen.next() >>> ctxt.dump() ans: (0, 'hi\n', '') >>> gen.next() Traceback (most recent call last): ... StopIteration >>> ctxt.dump() >>> mgr.__exit__(None, None, None) ''' def __init__(self, special_base): super(general_command, self).__init__(special_base, 'general_command') def lookup(self, bindings, pat_context, patterns): if len(patterns) < 2: return knowledge_base.Gen_empty ans = run_cmd(pat_context, patterns[1], patterns[2] if len(patterns) > 2 else None, patterns[3] if len(patterns) > 3 else None) def gen(): mark = bindings.mark(True) try: if patterns[0].match_data(bindings, pat_context, ans): bindings.end_save_all_undo() yield else: bindings.end_save_all_undo() finally: bindings.undo_to_mark(mark) return contextlib.closing(gen()) def create_for(engine): special_base = special_knowledge_base(engine) claim_goal(special_base) check_command(special_base) command(special_base) general_command(special_base) ./pyke-1.1.1/pyke/krb_compiler/0000755000175000017500000000000011425360453015234 5ustar lambylamby./pyke-1.1.1/pyke/krb_compiler/kfbparser_tables.py0000644000175000017500000001056611365364034021131 0ustar lambylamby # /home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser_tables.py # This file is automatically generated. Do not edit. _tabversion = '3.2' _lr_method = 'LALR' _lr_signature = '4\xa4a\x00\xea\xcdZp5\xc6@\xa5\xfa\x1dCA' _lr_action_items = {'NONE_TOK':([8,12,24,26,],[11,11,11,11,]),'LP_TOK':([5,8,12,24,26,],[8,12,12,12,12,]),'STRING_TOK':([8,12,24,26,],[13,13,13,13,]),'RP_TOK':([8,11,12,13,14,16,17,18,19,20,22,23,26,27,28,29,],[15,-8,22,-14,-13,25,-17,-15,-16,-19,-18,-9,-10,29,-20,-21,]),',':([11,13,14,16,17,18,19,20,22,23,28,29,],[-8,-14,-13,24,-17,-15,-16,-19,-18,26,-20,-21,]),'NUMBER_TOK':([8,12,24,26,],[14,14,14,14,]),'NL_TOK':([0,6,7,15,21,25,],[3,10,-4,-6,-5,-7,]),'TRUE_TOK':([8,12,24,26,],[17,17,17,17,]),'IDENTIFIER_TOK':([0,1,3,8,10,12,24,26,],[-11,5,-12,18,5,18,18,18,]),'FALSE_TOK':([8,12,24,26,],[19,19,19,19,]),'$end':([0,1,2,3,4,6,7,9,10,15,21,25,],[-11,-2,0,-12,-1,-11,-4,-3,-12,-6,-5,-7,]),} _lr_action = { } for _k, _v in _lr_action_items.items(): for _x,_y in zip(_v[0],_v[1]): if not _x in _lr_action: _lr_action[_x] = { } _lr_action[_x][_k] = _y del _lr_action_items _lr_goto_items = {'facts_opt':([1,],[4,]),'nl_opt':([0,6,],[1,9,]),'comma_opt':([23,],[27,]),'data_list':([8,12,],[16,23,]),'file':([0,],[2,]),'facts':([1,],[6,]),'data':([8,12,24,26,],[20,20,28,28,]),'fact':([1,10,],[7,21,]),} _lr_goto = { } for _k, _v in _lr_goto_items.items(): for _x,_y in zip(_v[0],_v[1]): if not _x in _lr_goto: _lr_goto[_x] = { } _lr_goto[_x][_k] = _y del _lr_goto_items _lr_productions = [ ("S' -> file","S'",1,None,None,None), ('file -> nl_opt facts_opt','file',2,'p_file','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',36), ('facts_opt -> ','facts_opt',0,'p_file','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',37), ('facts_opt -> facts nl_opt','facts_opt',2,'p_file','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',38), ('facts -> fact','facts',1,'p_file','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',39), ('facts -> facts NL_TOK fact','facts',3,'p_file','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',40), ('fact -> IDENTIFIER_TOK LP_TOK RP_TOK','fact',3,'p_fact0','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',45), ('fact -> IDENTIFIER_TOK LP_TOK data_list RP_TOK','fact',4,'p_fact1','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',49), ('data -> NONE_TOK','data',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',53), ('comma_opt -> ','comma_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',54), ('comma_opt -> ,','comma_opt',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',55), ('nl_opt -> ','nl_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',56), ('nl_opt -> NL_TOK','nl_opt',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',57), ('data -> NUMBER_TOK','data',1,'p_number','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',62), ('data -> STRING_TOK','data',1,'p_string','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',67), ('data -> IDENTIFIER_TOK','data',1,'p_quoted_last','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',72), ('data -> FALSE_TOK','data',1,'p_false','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',77), ('data -> TRUE_TOK','data',1,'p_true','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',82), ('data -> LP_TOK RP_TOK','data',2,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',87), ('data_list -> data','data_list',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',92), ('data_list -> data_list , data','data_list',3,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',97), ('data -> LP_TOK data_list comma_opt RP_TOK','data',4,'p_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/kfbparser.py',103), ] ./pyke-1.1.1/pyke/krb_compiler/__init__.py0000644000175000017500000001750711346504630017356 0ustar lambylamby# $Id: __init__.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import with_statement import os, os.path import sys import pyke from pyke import knowledge_engine from pyke.krb_compiler import compiler_bc from pyke.krb_compiler import krbparser #from pyke import contexts #contexts.debug = ('patterns_out1', 'patterns_out',) Ast_names = frozenset(( 'file', 'parent', 'fc_rule', 'fc_predicate', 'assert', 'python_assertion', 'python_eq', 'python_in', 'python_check', 'counting', 'bc_rule', 'goal', 'bc_predicate', 'symbol', 'pattern_var', 'as', 'plan_spec', 'pattern_data', 'pattern_tuple', # 'anonymous_var', )) def dump(ast, f = sys.stderr, need_nl = False, indent = 0): if not isinstance(ast, tuple) or len(ast) == 0: f.write(repr(ast)) return False if ast[0] in Ast_names: indent += 2 if need_nl: f.write("\n") f.write(' ' * indent) f.write('(%s' % ast[0]) for arg in ast[1:]: f.write(', ') dump(arg, f, True, indent) f.write(')') return True f.write('(') did_nl = dump(ast[0], f, False, indent) for arg in ast[1:]: f.write(', ') did_nl |= dump(arg, f, did_nl, indent) f.write(')') return did_nl def to_relative(from_path, to_path): '''Calculates the relative path to get from from_path to to_path. >>> to_relative('/a/b/c', '/a/b/d/e') '../d/e' >>> to_relative('/a/b/c', '/b/d/e') '/b/d/e' >>> to_relative('/a/b/c', '/a/b/c/e') 'e' >>> to_relative('/a/b/c', '/a/b2/d/e') '../../b2/d/e' ''' from_path = os.path.abspath(from_path) to_path = os.path.abspath(to_path) prefix = '' while os.path.join(from_path, to_path[len(from_path) + 1:]) != to_path: new_from_path = os.path.dirname(from_path) if new_from_path == from_path: return to_path from_path = new_from_path prefix = os.path.join(prefix, '..') return os.path.join(prefix, to_path[len(from_path) + 1:]) def compile_krb(rb_name, generated_root_pkg, generated_root_dir, filename): engine = knowledge_engine.engine(('*direct*', compiler_bc)) try: fc_name = rb_name + '_fc.py' bc_name = rb_name + '_bc.py' plan_name = rb_name + '_plans.py' fc_path = os.path.join(generated_root_dir, fc_name) bc_path = os.path.join(generated_root_dir, bc_name) plan_path = os.path.join(generated_root_dir, plan_name) ast = krbparser.parse(krbparser, filename) #sys.stderr.write("got ast\n") # dump(ast) # sys.stderr.write('\n\n') engine.reset() engine.activate('compiler') (fc_lines, bc_lines, plan_lines), plan = \ engine.prove_1('compiler', 'compile', (generated_root_pkg, rb_name, ast), 3) krb_filename = to_relative(generated_root_dir, filename) ans = [] if fc_lines: sys.stderr.write("writing [%s]/%s\n" % (generated_root_pkg, os.path.basename(fc_path))) write_file(fc_lines + ("", "Krb_filename = %r" % krb_filename,), fc_path) ans.append(fc_name) elif os.path.lexists(fc_path): os.remove(fc_path) if bc_lines: sys.stderr.write("writing [%s]/%s\n" % (generated_root_pkg, os.path.basename(bc_path))) write_file(bc_lines + ("", "Krb_filename = %r" % krb_filename,), bc_path) ans.append(bc_name) elif os.path.lexists(bc_path): os.remove(bc_path) if plan_lines: sys.stderr.write("writing [%s]/%s\n" % (generated_root_pkg, os.path.basename(plan_path))) #sys.stderr.write("plan_lines:\n") #for line in plan_lines: # sys.stderr.write(" " + repr(line) + "\n") write_file(plan_lines + ("", "Krb_filename = %r" % krb_filename,), plan_path) ans.insert(len(ans) - 1, plan_name) # want this loaded before _bc elif os.path.lexists(plan_path): os.remove(plan_path) #sys.stderr.write("done!\n") return ans except: if os.path.lexists(fc_path): os.remove(fc_path) if os.path.lexists(bc_path): os.remove(bc_path) if os.path.lexists(plan_path): os.remove(plan_path) raise def compile_goal(goal_str): return krbparser.parse_goal(krbparser, goal_str) def compile_kfb(filename): global kfbparser try: kfbparser except NameError: from pyke.krb_compiler import kfbparser return kfbparser.parse(kfbparser, filename) def compile_kqb(filename): global kqb_parser try: kqb_parser except NameError: from pyke.krb_compiler import kqb_parser return kqb_parser.parse_kqb(filename) def write_file(lines, filename): with open(filename, 'w') as f: indents = [0] lineno_map = [] write_file2(lines, f, indents, lineno_map, 0) if lineno_map: f.write("Krb_lineno_map = (\n") for map_entry in lineno_map: f.write(" %s,\n" % str(map_entry)) f.write(")\n") def write_file2(lines, f, indents, lineno_map, lineno, starting_lineno = None): for line in lines: if line == 'POPINDENT': assert len(indents) > 1 del indents[-1] elif isinstance(line, tuple): if len(line) == 2 and line[0] == 'INDENT': indents.append(indents[-1] + line[1]) elif len(line) == 2 and line[0] == 'STARTING_LINENO': assert starting_lineno is None, \ "missing ENDING_LINENO for STARTING_LINENO %d" % \ starting_lineno[1] starting_lineno = line[1], lineno + 1 elif len(line) == 2 and line[0] == 'ENDING_LINENO': assert starting_lineno is not None, \ "missing STARTING_LINENO for ENDING_LINENO %d" % \ line[1] lineno_map.append(((starting_lineno[1], lineno), (starting_lineno[0], line[1]))) starting_lineno = None else: lineno, starting_lineno = \ write_file2(line, f, indents, lineno_map, lineno, starting_lineno) else: f.write(' ' * indents[-1] + line + '\n') lineno += 1 return lineno, starting_lineno ./pyke-1.1.1/pyke/krb_compiler/ply/0000755000175000017500000000000011425360453016040 5ustar lambylamby./pyke-1.1.1/pyke/krb_compiler/ply/yacc.py0000644000175000017500000037275411346504630017352 0ustar lambylamby# ----------------------------------------------------------------------------- # ply: yacc.py # # Copyright (C) 2001-2009, # David M. Beazley (Dabeaz LLC) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the David Beazley or Dabeaz LLC may be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # # This implements an LR parser that is constructed from grammar rules defined # as Python functions. The grammer is specified by supplying the BNF inside # Python documentation strings. The inspiration for this technique was borrowed # from John Aycock's Spark parsing system. PLY might be viewed as cross between # Spark and the GNU bison utility. # # The current implementation is only somewhat object-oriented. The # LR parser itself is defined in terms of an object (which allows multiple # parsers to co-exist). However, most of the variables used during table # construction are defined in terms of global variables. Users shouldn't # notice unless they are trying to define multiple parsers at the same # time using threads (in which case they should have their head examined). # # This implementation supports both SLR and LALR(1) parsing. LALR(1) # support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), # using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, # Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced # by the more efficient DeRemer and Pennello algorithm. # # :::::::: WARNING ::::::: # # Construction of LR parsing tables is fairly complicated and expensive. # To make this module run fast, a *LOT* of work has been put into # optimization---often at the expensive of readability and what might # consider to be good Python "coding style." Modify the code at your # own risk! # ---------------------------------------------------------------------------- __version__ = "3.3" __tabversion__ = "3.2" # Table version #----------------------------------------------------------------------------- # === User configurable parameters === # # Change these to modify the default behavior of yacc (if you wish) #----------------------------------------------------------------------------- yaccdebug = 1 # Debugging mode. If set, yacc generates a # a 'parser.out' file in the current directory debug_file = 'parser.out' # Default name of the debugging file tab_module = 'parsetab' # Default name of the table module default_lr = 'LALR' # Default LR table generation method error_count = 3 # Number of symbols that must be shifted to leave recovery mode yaccdevel = 0 # Set to True if developing yacc. This turns off optimized # implementations of certain functions. resultlimit = 40 # Size limit of results when running in debug mode. pickle_protocol = 0 # Protocol to use when writing pickle files import re, types, sys, os.path # Compatibility function for python 2.6/3.0 if sys.version_info[0] < 3: def func_code(f): return f.func_code else: def func_code(f): return f.__code__ # Compatibility try: MAXINT = sys.maxint except AttributeError: MAXINT = sys.maxsize # Python 2.x/3.0 compatibility. def load_ply_lex(): if sys.version_info[0] < 3: import lex else: import ply.lex as lex return lex # This object is a stand-in for a logging object created by the # logging module. PLY will use this by default to create things # such as the parser.out file. If a user wants more detailed # information, they can create their own logging object and pass # it into PLY. class PlyLogger(object): def __init__(self,f): self.f = f def debug(self,msg,*args,**kwargs): self.f.write((msg % args) + "\n") info = debug def warning(self,msg,*args,**kwargs): self.f.write("WARNING: "+ (msg % args) + "\n") def error(self,msg,*args,**kwargs): self.f.write("ERROR: " + (msg % args) + "\n") critical = debug # Null logger is used when no output is generated. Does nothing. class NullLogger(object): def __getattribute__(self,name): return self def __call__(self,*args,**kwargs): return self # Exception raised for yacc-related errors class YaccError(Exception): pass # Format the result message that the parser produces when running in debug mode. def format_result(r): repr_str = repr(r) if '\n' in repr_str: repr_str = repr(repr_str) if len(repr_str) > resultlimit: repr_str = repr_str[:resultlimit]+" ..." result = "<%s @ 0x%x> (%s)" % (type(r).__name__,id(r),repr_str) return result # Format stack entries when the parser is running in debug mode def format_stack_entry(r): repr_str = repr(r) if '\n' in repr_str: repr_str = repr(repr_str) if len(repr_str) < 16: return repr_str else: return "<%s @ 0x%x>" % (type(r).__name__,id(r)) #----------------------------------------------------------------------------- # === LR Parsing Engine === # # The following classes are used for the LR parser itself. These are not # used during table construction and are independent of the actual LR # table generation algorithm #----------------------------------------------------------------------------- # This class is used to hold non-terminal grammar symbols during parsing. # It normally has the following attributes set: # .type = Grammar symbol type # .value = Symbol value # .lineno = Starting line number # .endlineno = Ending line number (optional, set automatically) # .lexpos = Starting lex position # .endlexpos = Ending lex position (optional, set automatically) class YaccSymbol: def __str__(self): return self.type def __repr__(self): return str(self) # This class is a wrapper around the objects actually passed to each # grammar rule. Index lookup and assignment actually assign the # .value attribute of the underlying YaccSymbol object. # The lineno() method returns the line number of a given # item (or 0 if not defined). The linespan() method returns # a tuple of (startline,endline) representing the range of lines # for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) # representing the range of positional information for a symbol. class YaccProduction: def __init__(self,s,stack=None): self.slice = s self.stack = stack self.lexer = None self.parser= None def __getitem__(self,n): if n >= 0: return self.slice[n].value else: return self.stack[n].value def __setitem__(self,n,v): self.slice[n].value = v def __getslice__(self,i,j): return [s.value for s in self.slice[i:j]] def __len__(self): return len(self.slice) def lineno(self,n): return getattr(self.slice[n],"lineno",0) def set_lineno(self,n,lineno): self.slice[n].lineno = lineno def linespan(self,n): startline = getattr(self.slice[n],"lineno",0) endline = getattr(self.slice[n],"endlineno",startline) return startline,endline def lexpos(self,n): return getattr(self.slice[n],"lexpos",0) def lexspan(self,n): startpos = getattr(self.slice[n],"lexpos",0) endpos = getattr(self.slice[n],"endlexpos",startpos) return startpos,endpos def error(self): raise SyntaxError # ----------------------------------------------------------------------------- # == LRParser == # # The LR Parsing engine. # ----------------------------------------------------------------------------- class LRParser: def __init__(self,lrtab,errorf): self.productions = lrtab.lr_productions self.action = lrtab.lr_action self.goto = lrtab.lr_goto self.errorfunc = errorf def errok(self): self.errorok = 1 def restart(self): del self.statestack[:] del self.symstack[:] sym = YaccSymbol() sym.type = '$end' self.symstack.append(sym) self.statestack.append(0) def parse(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): if debug or yaccdevel: if isinstance(debug,int): debug = PlyLogger(sys.stderr) return self.parsedebug(input,lexer,debug,tracking,tokenfunc) elif tracking: return self.parseopt(input,lexer,debug,tracking,tokenfunc) else: return self.parseopt_notrack(input,lexer,debug,tracking,tokenfunc) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # parsedebug(). # # This is the debugging enabled version of parse(). All changes made to the # parsing engine should be made here. For the non-debugging version, # copy this code to a method parseopt() and delete all of the sections # enclosed in: # # #--! DEBUG # statements # #--! DEBUG # # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def parsedebug(self,input=None,lexer=None,debug=None,tracking=0,tokenfunc=None): lookahead = None # Current lookahead symbol lookaheadstack = [ ] # Stack of lookahead symbols actions = self.action # Local reference to action table (to avoid lookup on self.) goto = self.goto # Local reference to goto table (to avoid lookup on self.) prod = self.productions # Local reference to production list (to avoid lookup on self.) pslice = YaccProduction(None) # Production object passed to grammar rules errorcount = 0 # Used during error recovery # --! DEBUG debug.info("PLY: PARSE DEBUG START") # --! DEBUG # If no lexer was given, we will try to use the lex module if not lexer: lex = load_ply_lex() lexer = lex.lexer # Set up the lexer and parser objects on pslice pslice.lexer = lexer pslice.parser = self # If input was supplied, pass to lexer if input is not None: lexer.input(input) if tokenfunc is None: # Tokenize function get_token = lexer.token else: get_token = tokenfunc # Set up the state and symbol stacks statestack = [ ] # Stack of parsing states self.statestack = statestack symstack = [ ] # Stack of grammar symbols self.symstack = symstack pslice.stack = symstack # Put in the production errtoken = None # Err token # The start state is assumed to be (0,$end) statestack.append(0) sym = YaccSymbol() sym.type = "$end" symstack.append(sym) state = 0 while 1: # Get the next symbol on the input. If a lookahead symbol # is already set, we just use that. Otherwise, we'll pull # the next token off of the lookaheadstack or from the lexer # --! DEBUG debug.debug('') debug.debug('State : %s', state) # --! DEBUG if not lookahead: if not lookaheadstack: lookahead = get_token() # Get the next token else: lookahead = lookaheadstack.pop() if not lookahead: lookahead = YaccSymbol() lookahead.type = "$end" # --! DEBUG debug.debug('Stack : %s', ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) # --! DEBUG # Check the action table ltype = lookahead.type t = actions[state].get(ltype) if t is not None: if t > 0: # shift a symbol on the stack statestack.append(t) state = t # --! DEBUG debug.debug("Action : Shift and goto state %s", t) # --! DEBUG symstack.append(lookahead) lookahead = None # Decrease error count on successful shift if errorcount: errorcount -=1 continue if t < 0: # reduce a symbol on the stack, emit a production p = prod[-t] pname = p.name plen = p.len # Get production function sym = YaccSymbol() sym.type = pname # Production name sym.value = None # --! DEBUG if plen: debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, "["+",".join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+"]",-t) else: debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, [],-t) # --! DEBUG if plen: targ = symstack[-plen-1:] targ[0] = sym # --! TRACKING if tracking: t1 = targ[1] sym.lineno = t1.lineno sym.lexpos = t1.lexpos t1 = targ[-1] sym.endlineno = getattr(t1,"endlineno",t1.lineno) sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos) # --! TRACKING # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # below as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object del symstack[-plen:] del statestack[-plen:] p.callable(pslice) # --! DEBUG debug.info("Result : %s", format_result(pslice[0])) # --! DEBUG symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) symstack.pop() statestack.pop() state = statestack[-1] sym.type = 'error' lookahead = sym errorcount = error_count self.errorok = 0 continue # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! else: # --! TRACKING if tracking: sym.lineno = lexer.lineno sym.lexpos = lexer.lexpos # --! TRACKING targ = [ sym ] # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # above as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object p.callable(pslice) # --! DEBUG debug.info("Result : %s", format_result(pslice[0])) # --! DEBUG symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) symstack.pop() statestack.pop() state = statestack[-1] sym.type = 'error' lookahead = sym errorcount = error_count self.errorok = 0 continue # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if t == 0: n = symstack[-1] result = getattr(n,"value",None) # --! DEBUG debug.info("Done : Returning %s", format_result(result)) debug.info("PLY: PARSE DEBUG END") # --! DEBUG return result if t == None: # --! DEBUG debug.error('Error : %s', ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) # --! DEBUG # We have some kind of parsing error here. To handle # this, we are going to push the current token onto # the tokenstack and replace it with an 'error' token. # If there are any synchronization rules, they may # catch it. # # In addition to pushing the error token, we call call # the user defined p_error() function if this is the # first syntax error. This function is only called if # errorcount == 0. if errorcount == 0 or self.errorok: errorcount = error_count self.errorok = 0 errtoken = lookahead if errtoken.type == "$end": errtoken = None # End of file! if self.errorfunc: global errok,token,restart errok = self.errok # Set some special functions available in error recovery token = get_token restart = self.restart if errtoken and not hasattr(errtoken,'lexer'): errtoken.lexer = lexer tok = self.errorfunc(errtoken) del errok, token, restart # Delete special functions if self.errorok: # User must have done some kind of panic # mode recovery on their own. The # returned token is the next lookahead lookahead = tok errtoken = None continue else: if errtoken: if hasattr(errtoken,"lineno"): lineno = lookahead.lineno else: lineno = 0 if lineno: sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) else: sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) else: sys.stderr.write("yacc: Parse error in input. EOF\n") return else: errorcount = error_count # case 1: the statestack only has 1 entry on it. If we're in this state, the # entire parse has been rolled back and we're completely hosed. The token is # discarded and we just keep going. if len(statestack) <= 1 and lookahead.type != "$end": lookahead = None errtoken = None state = 0 # Nuke the pushback stack del lookaheadstack[:] continue # case 2: the statestack has a couple of entries on it, but we're # at the end of the file. nuke the top entry and generate an error token # Start nuking entries on the stack if lookahead.type == "$end": # Whoa. We're really hosed here. Bail out return if lookahead.type != 'error': sym = symstack[-1] if sym.type == 'error': # Hmmm. Error is on top of stack, we'll just nuke input # symbol and continue lookahead = None continue t = YaccSymbol() t.type = 'error' if hasattr(lookahead,"lineno"): t.lineno = lookahead.lineno t.value = lookahead lookaheadstack.append(lookahead) lookahead = t else: symstack.pop() statestack.pop() state = statestack[-1] # Potential bug fix continue # Call an error function here raise RuntimeError("yacc: internal parser error!!!\n") # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # parseopt(). # # Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY. # Edit the debug version above, then copy any modifications to the method # below while removing #--! DEBUG sections. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def parseopt(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): lookahead = None # Current lookahead symbol lookaheadstack = [ ] # Stack of lookahead symbols actions = self.action # Local reference to action table (to avoid lookup on self.) goto = self.goto # Local reference to goto table (to avoid lookup on self.) prod = self.productions # Local reference to production list (to avoid lookup on self.) pslice = YaccProduction(None) # Production object passed to grammar rules errorcount = 0 # Used during error recovery # If no lexer was given, we will try to use the lex module if not lexer: lex = load_ply_lex() lexer = lex.lexer # Set up the lexer and parser objects on pslice pslice.lexer = lexer pslice.parser = self # If input was supplied, pass to lexer if input is not None: lexer.input(input) if tokenfunc is None: # Tokenize function get_token = lexer.token else: get_token = tokenfunc # Set up the state and symbol stacks statestack = [ ] # Stack of parsing states self.statestack = statestack symstack = [ ] # Stack of grammar symbols self.symstack = symstack pslice.stack = symstack # Put in the production errtoken = None # Err token # The start state is assumed to be (0,$end) statestack.append(0) sym = YaccSymbol() sym.type = '$end' symstack.append(sym) state = 0 while 1: # Get the next symbol on the input. If a lookahead symbol # is already set, we just use that. Otherwise, we'll pull # the next token off of the lookaheadstack or from the lexer if not lookahead: if not lookaheadstack: lookahead = get_token() # Get the next token else: lookahead = lookaheadstack.pop() if not lookahead: lookahead = YaccSymbol() lookahead.type = '$end' # Check the action table ltype = lookahead.type t = actions[state].get(ltype) if t is not None: if t > 0: # shift a symbol on the stack statestack.append(t) state = t symstack.append(lookahead) lookahead = None # Decrease error count on successful shift if errorcount: errorcount -=1 continue if t < 0: # reduce a symbol on the stack, emit a production p = prod[-t] pname = p.name plen = p.len # Get production function sym = YaccSymbol() sym.type = pname # Production name sym.value = None if plen: targ = symstack[-plen-1:] targ[0] = sym # --! TRACKING if tracking: t1 = targ[1] sym.lineno = t1.lineno sym.lexpos = t1.lexpos t1 = targ[-1] sym.endlineno = getattr(t1,"endlineno",t1.lineno) sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos) # --! TRACKING # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # below as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object del symstack[-plen:] del statestack[-plen:] p.callable(pslice) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) symstack.pop() statestack.pop() state = statestack[-1] sym.type = 'error' lookahead = sym errorcount = error_count self.errorok = 0 continue # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! else: # --! TRACKING if tracking: sym.lineno = lexer.lineno sym.lexpos = lexer.lexpos # --! TRACKING targ = [ sym ] # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # above as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object p.callable(pslice) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) symstack.pop() statestack.pop() state = statestack[-1] sym.type = 'error' lookahead = sym errorcount = error_count self.errorok = 0 continue # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if t == 0: n = symstack[-1] return getattr(n,"value",None) if t == None: # We have some kind of parsing error here. To handle # this, we are going to push the current token onto # the tokenstack and replace it with an 'error' token. # If there are any synchronization rules, they may # catch it. # # In addition to pushing the error token, we call call # the user defined p_error() function if this is the # first syntax error. This function is only called if # errorcount == 0. if errorcount == 0 or self.errorok: errorcount = error_count self.errorok = 0 errtoken = lookahead if errtoken.type == '$end': errtoken = None # End of file! if self.errorfunc: global errok,token,restart errok = self.errok # Set some special functions available in error recovery token = get_token restart = self.restart if errtoken and not hasattr(errtoken,'lexer'): errtoken.lexer = lexer tok = self.errorfunc(errtoken) del errok, token, restart # Delete special functions if self.errorok: # User must have done some kind of panic # mode recovery on their own. The # returned token is the next lookahead lookahead = tok errtoken = None continue else: if errtoken: if hasattr(errtoken,"lineno"): lineno = lookahead.lineno else: lineno = 0 if lineno: sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) else: sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) else: sys.stderr.write("yacc: Parse error in input. EOF\n") return else: errorcount = error_count # case 1: the statestack only has 1 entry on it. If we're in this state, the # entire parse has been rolled back and we're completely hosed. The token is # discarded and we just keep going. if len(statestack) <= 1 and lookahead.type != '$end': lookahead = None errtoken = None state = 0 # Nuke the pushback stack del lookaheadstack[:] continue # case 2: the statestack has a couple of entries on it, but we're # at the end of the file. nuke the top entry and generate an error token # Start nuking entries on the stack if lookahead.type == '$end': # Whoa. We're really hosed here. Bail out return if lookahead.type != 'error': sym = symstack[-1] if sym.type == 'error': # Hmmm. Error is on top of stack, we'll just nuke input # symbol and continue lookahead = None continue t = YaccSymbol() t.type = 'error' if hasattr(lookahead,"lineno"): t.lineno = lookahead.lineno t.value = lookahead lookaheadstack.append(lookahead) lookahead = t else: symstack.pop() statestack.pop() state = statestack[-1] # Potential bug fix continue # Call an error function here raise RuntimeError("yacc: internal parser error!!!\n") # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # parseopt_notrack(). # # Optimized version of parseopt() with line number tracking removed. # DO NOT EDIT THIS CODE DIRECTLY. Copy the optimized version and remove # code in the #--! TRACKING sections # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def parseopt_notrack(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): lookahead = None # Current lookahead symbol lookaheadstack = [ ] # Stack of lookahead symbols actions = self.action # Local reference to action table (to avoid lookup on self.) goto = self.goto # Local reference to goto table (to avoid lookup on self.) prod = self.productions # Local reference to production list (to avoid lookup on self.) pslice = YaccProduction(None) # Production object passed to grammar rules errorcount = 0 # Used during error recovery # If no lexer was given, we will try to use the lex module if not lexer: lex = load_ply_lex() lexer = lex.lexer # Set up the lexer and parser objects on pslice pslice.lexer = lexer pslice.parser = self # If input was supplied, pass to lexer if input is not None: lexer.input(input) if tokenfunc is None: # Tokenize function get_token = lexer.token else: get_token = tokenfunc # Set up the state and symbol stacks statestack = [ ] # Stack of parsing states self.statestack = statestack symstack = [ ] # Stack of grammar symbols self.symstack = symstack pslice.stack = symstack # Put in the production errtoken = None # Err token # The start state is assumed to be (0,$end) statestack.append(0) sym = YaccSymbol() sym.type = '$end' symstack.append(sym) state = 0 while 1: # Get the next symbol on the input. If a lookahead symbol # is already set, we just use that. Otherwise, we'll pull # the next token off of the lookaheadstack or from the lexer if not lookahead: if not lookaheadstack: lookahead = get_token() # Get the next token else: lookahead = lookaheadstack.pop() if not lookahead: lookahead = YaccSymbol() lookahead.type = '$end' # Check the action table ltype = lookahead.type t = actions[state].get(ltype) if t is not None: if t > 0: # shift a symbol on the stack statestack.append(t) state = t symstack.append(lookahead) lookahead = None # Decrease error count on successful shift if errorcount: errorcount -=1 continue if t < 0: # reduce a symbol on the stack, emit a production p = prod[-t] pname = p.name plen = p.len # Get production function sym = YaccSymbol() sym.type = pname # Production name sym.value = None if plen: targ = symstack[-plen-1:] targ[0] = sym # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # below as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object del symstack[-plen:] del statestack[-plen:] p.callable(pslice) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) symstack.pop() statestack.pop() state = statestack[-1] sym.type = 'error' lookahead = sym errorcount = error_count self.errorok = 0 continue # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! else: targ = [ sym ] # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # above as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object p.callable(pslice) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) symstack.pop() statestack.pop() state = statestack[-1] sym.type = 'error' lookahead = sym errorcount = error_count self.errorok = 0 continue # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if t == 0: n = symstack[-1] return getattr(n,"value",None) if t == None: # We have some kind of parsing error here. To handle # this, we are going to push the current token onto # the tokenstack and replace it with an 'error' token. # If there are any synchronization rules, they may # catch it. # # In addition to pushing the error token, we call call # the user defined p_error() function if this is the # first syntax error. This function is only called if # errorcount == 0. if errorcount == 0 or self.errorok: errorcount = error_count self.errorok = 0 errtoken = lookahead if errtoken.type == '$end': errtoken = None # End of file! if self.errorfunc: global errok,token,restart errok = self.errok # Set some special functions available in error recovery token = get_token restart = self.restart if errtoken and not hasattr(errtoken,'lexer'): errtoken.lexer = lexer tok = self.errorfunc(errtoken) del errok, token, restart # Delete special functions if self.errorok: # User must have done some kind of panic # mode recovery on their own. The # returned token is the next lookahead lookahead = tok errtoken = None continue else: if errtoken: if hasattr(errtoken,"lineno"): lineno = lookahead.lineno else: lineno = 0 if lineno: sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) else: sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) else: sys.stderr.write("yacc: Parse error in input. EOF\n") return else: errorcount = error_count # case 1: the statestack only has 1 entry on it. If we're in this state, the # entire parse has been rolled back and we're completely hosed. The token is # discarded and we just keep going. if len(statestack) <= 1 and lookahead.type != '$end': lookahead = None errtoken = None state = 0 # Nuke the pushback stack del lookaheadstack[:] continue # case 2: the statestack has a couple of entries on it, but we're # at the end of the file. nuke the top entry and generate an error token # Start nuking entries on the stack if lookahead.type == '$end': # Whoa. We're really hosed here. Bail out return if lookahead.type != 'error': sym = symstack[-1] if sym.type == 'error': # Hmmm. Error is on top of stack, we'll just nuke input # symbol and continue lookahead = None continue t = YaccSymbol() t.type = 'error' if hasattr(lookahead,"lineno"): t.lineno = lookahead.lineno t.value = lookahead lookaheadstack.append(lookahead) lookahead = t else: symstack.pop() statestack.pop() state = statestack[-1] # Potential bug fix continue # Call an error function here raise RuntimeError("yacc: internal parser error!!!\n") # ----------------------------------------------------------------------------- # === Grammar Representation === # # The following functions, classes, and variables are used to represent and # manipulate the rules that make up a grammar. # ----------------------------------------------------------------------------- import re # regex matching identifiers _is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') # ----------------------------------------------------------------------------- # class Production: # # This class stores the raw information about a single production or grammar rule. # A grammar rule refers to a specification such as this: # # expr : expr PLUS term # # Here are the basic attributes defined on all productions # # name - Name of the production. For example 'expr' # prod - A list of symbols on the right side ['expr','PLUS','term'] # prec - Production precedence level # number - Production number. # func - Function that executes on reduce # file - File where production function is defined # lineno - Line number where production function is defined # # The following attributes are defined or optional. # # len - Length of the production (number of symbols on right hand side) # usyms - Set of unique symbols found in the production # ----------------------------------------------------------------------------- class Production(object): reduced = 0 def __init__(self,number,name,prod,precedence=('right',0),func=None,file='',line=0): self.name = name self.prod = tuple(prod) self.number = number self.func = func self.callable = None self.file = file self.line = line self.prec = precedence # Internal settings used during table construction self.len = len(self.prod) # Length of the production # Create a list of unique production symbols used in the production self.usyms = [ ] for s in self.prod: if s not in self.usyms: self.usyms.append(s) # List of all LR items for the production self.lr_items = [] self.lr_next = None # Create a string representation if self.prod: self.str = "%s -> %s" % (self.name," ".join(self.prod)) else: self.str = "%s -> " % self.name def __str__(self): return self.str def __repr__(self): return "Production("+str(self)+")" def __len__(self): return len(self.prod) def __nonzero__(self): return 1 def __getitem__(self,index): return self.prod[index] # Return the nth lr_item from the production (or None if at the end) def lr_item(self,n): if n > len(self.prod): return None p = LRItem(self,n) # Precompute the list of productions immediately following. Hack. Remove later try: p.lr_after = Prodnames[p.prod[n+1]] except (IndexError,KeyError): p.lr_after = [] try: p.lr_before = p.prod[n-1] except IndexError: p.lr_before = None return p # Bind the production function name to a callable def bind(self,pdict): if self.func: self.callable = pdict[self.func] # This class serves as a minimal standin for Production objects when # reading table data from files. It only contains information # actually used by the LR parsing engine, plus some additional # debugging information. class MiniProduction(object): def __init__(self,str,name,len,func,file,line): self.name = name self.len = len self.func = func self.callable = None self.file = file self.line = line self.str = str def __str__(self): return self.str def __repr__(self): return "MiniProduction(%s)" % self.str # Bind the production function name to a callable def bind(self,pdict): if self.func: self.callable = pdict[self.func] # ----------------------------------------------------------------------------- # class LRItem # # This class represents a specific stage of parsing a production rule. For # example: # # expr : expr . PLUS term # # In the above, the "." represents the current location of the parse. Here # basic attributes: # # name - Name of the production. For example 'expr' # prod - A list of symbols on the right side ['expr','.', 'PLUS','term'] # number - Production number. # # lr_next Next LR item. Example, if we are ' expr -> expr . PLUS term' # then lr_next refers to 'expr -> expr PLUS . term' # lr_index - LR item index (location of the ".") in the prod list. # lookaheads - LALR lookahead symbols for this item # len - Length of the production (number of symbols on right hand side) # lr_after - List of all productions that immediately follow # lr_before - Grammar symbol immediately before # ----------------------------------------------------------------------------- class LRItem(object): def __init__(self,p,n): self.name = p.name self.prod = list(p.prod) self.number = p.number self.lr_index = n self.lookaheads = { } self.prod.insert(n,".") self.prod = tuple(self.prod) self.len = len(self.prod) self.usyms = p.usyms def __str__(self): if self.prod: s = "%s -> %s" % (self.name," ".join(self.prod)) else: s = "%s -> " % self.name return s def __repr__(self): return "LRItem("+str(self)+")" # ----------------------------------------------------------------------------- # rightmost_terminal() # # Return the rightmost terminal from a list of symbols. Used in add_production() # ----------------------------------------------------------------------------- def rightmost_terminal(symbols, terminals): i = len(symbols) - 1 while i >= 0: if symbols[i] in terminals: return symbols[i] i -= 1 return None # ----------------------------------------------------------------------------- # === GRAMMAR CLASS === # # The following class represents the contents of the specified grammar along # with various computed properties such as first sets, follow sets, LR items, etc. # This data is used for critical parts of the table generation process later. # ----------------------------------------------------------------------------- class GrammarError(YaccError): pass class Grammar(object): def __init__(self,terminals): self.Productions = [None] # A list of all of the productions. The first # entry is always reserved for the purpose of # building an augmented grammar self.Prodnames = { } # A dictionary mapping the names of nonterminals to a list of all # productions of that nonterminal. self.Prodmap = { } # A dictionary that is only used to detect duplicate # productions. self.Terminals = { } # A dictionary mapping the names of terminal symbols to a # list of the rules where they are used. for term in terminals: self.Terminals[term] = [] self.Terminals['error'] = [] self.Nonterminals = { } # A dictionary mapping names of nonterminals to a list # of rule numbers where they are used. self.First = { } # A dictionary of precomputed FIRST(x) symbols self.Follow = { } # A dictionary of precomputed FOLLOW(x) symbols self.Precedence = { } # Precedence rules for each terminal. Contains tuples of the # form ('right',level) or ('nonassoc', level) or ('left',level) self.UsedPrecedence = { } # Precedence rules that were actually used by the grammer. # This is only used to provide error checking and to generate # a warning about unused precedence rules. self.Start = None # Starting symbol for the grammar def __len__(self): return len(self.Productions) def __getitem__(self,index): return self.Productions[index] # ----------------------------------------------------------------------------- # set_precedence() # # Sets the precedence for a given terminal. assoc is the associativity such as # 'left','right', or 'nonassoc'. level is a numeric level. # # ----------------------------------------------------------------------------- def set_precedence(self,term,assoc,level): assert self.Productions == [None],"Must call set_precedence() before add_production()" if term in self.Precedence: raise GrammarError("Precedence already specified for terminal '%s'" % term) if assoc not in ['left','right','nonassoc']: raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'") self.Precedence[term] = (assoc,level) # ----------------------------------------------------------------------------- # add_production() # # Given an action function, this function assembles a production rule and # computes its precedence level. # # The production rule is supplied as a list of symbols. For example, # a rule such as 'expr : expr PLUS term' has a production name of 'expr' and # symbols ['expr','PLUS','term']. # # Precedence is determined by the precedence of the right-most non-terminal # or the precedence of a terminal specified by %prec. # # A variety of error checks are performed to make sure production symbols # are valid and that %prec is used correctly. # ----------------------------------------------------------------------------- def add_production(self,prodname,syms,func=None,file='',line=0): if prodname in self.Terminals: raise GrammarError("%s:%d: Illegal rule name '%s'. Already defined as a token" % (file,line,prodname)) if prodname == 'error': raise GrammarError("%s:%d: Illegal rule name '%s'. error is a reserved word" % (file,line,prodname)) if not _is_identifier.match(prodname): raise GrammarError("%s:%d: Illegal rule name '%s'" % (file,line,prodname)) # Look for literal tokens for n,s in enumerate(syms): if s[0] in "'\"": try: c = eval(s) if (len(c) > 1): raise GrammarError("%s:%d: Literal token %s in rule '%s' may only be a single character" % (file,line,s, prodname)) if not c in self.Terminals: self.Terminals[c] = [] syms[n] = c continue except SyntaxError: pass if not _is_identifier.match(s) and s != '%prec': raise GrammarError("%s:%d: Illegal name '%s' in rule '%s'" % (file,line,s, prodname)) # Determine the precedence level if '%prec' in syms: if syms[-1] == '%prec': raise GrammarError("%s:%d: Syntax error. Nothing follows %%prec" % (file,line)) if syms[-2] != '%prec': raise GrammarError("%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule" % (file,line)) precname = syms[-1] prodprec = self.Precedence.get(precname,None) if not prodprec: raise GrammarError("%s:%d: Nothing known about the precedence of '%s'" % (file,line,precname)) else: self.UsedPrecedence[precname] = 1 del syms[-2:] # Drop %prec from the rule else: # If no %prec, precedence is determined by the rightmost terminal symbol precname = rightmost_terminal(syms,self.Terminals) prodprec = self.Precedence.get(precname,('right',0)) # See if the rule is already in the rulemap map = "%s -> %s" % (prodname,syms) if map in self.Prodmap: m = self.Prodmap[map] raise GrammarError("%s:%d: Duplicate rule %s. " % (file,line, m) + "Previous definition at %s:%d" % (m.file, m.line)) # From this point on, everything is valid. Create a new Production instance pnumber = len(self.Productions) if not prodname in self.Nonterminals: self.Nonterminals[prodname] = [ ] # Add the production number to Terminals and Nonterminals for t in syms: if t in self.Terminals: self.Terminals[t].append(pnumber) else: if not t in self.Nonterminals: self.Nonterminals[t] = [ ] self.Nonterminals[t].append(pnumber) # Create a production and add it to the list of productions p = Production(pnumber,prodname,syms,prodprec,func,file,line) self.Productions.append(p) self.Prodmap[map] = p # Add to the global productions list try: self.Prodnames[prodname].append(p) except KeyError: self.Prodnames[prodname] = [ p ] return 0 # ----------------------------------------------------------------------------- # set_start() # # Sets the starting symbol and creates the augmented grammar. Production # rule 0 is S' -> start where start is the start symbol. # ----------------------------------------------------------------------------- def set_start(self,start=None): if not start: start = self.Productions[1].name if start not in self.Nonterminals: raise GrammarError("start symbol %s undefined" % start) self.Productions[0] = Production(0,"S'",[start]) self.Nonterminals[start].append(0) self.Start = start # ----------------------------------------------------------------------------- # find_unreachable() # # Find all of the nonterminal symbols that can't be reached from the starting # symbol. Returns a list of nonterminals that can't be reached. # ----------------------------------------------------------------------------- def find_unreachable(self): # Mark all symbols that are reachable from a symbol s def mark_reachable_from(s): if reachable[s]: # We've already reached symbol s. return reachable[s] = 1 for p in self.Prodnames.get(s,[]): for r in p.prod: mark_reachable_from(r) reachable = { } for s in list(self.Terminals) + list(self.Nonterminals): reachable[s] = 0 mark_reachable_from( self.Productions[0].prod[0] ) return [s for s in list(self.Nonterminals) if not reachable[s]] # ----------------------------------------------------------------------------- # infinite_cycles() # # This function looks at the various parsing rules and tries to detect # infinite recursion cycles (grammar rules where there is no possible way # to derive a string of only terminals). # ----------------------------------------------------------------------------- def infinite_cycles(self): terminates = {} # Terminals: for t in self.Terminals: terminates[t] = 1 terminates['$end'] = 1 # Nonterminals: # Initialize to false: for n in self.Nonterminals: terminates[n] = 0 # Then propagate termination until no change: while 1: some_change = 0 for (n,pl) in self.Prodnames.items(): # Nonterminal n terminates iff any of its productions terminates. for p in pl: # Production p terminates iff all of its rhs symbols terminate. for s in p.prod: if not terminates[s]: # The symbol s does not terminate, # so production p does not terminate. p_terminates = 0 break else: # didn't break from the loop, # so every symbol s terminates # so production p terminates. p_terminates = 1 if p_terminates: # symbol n terminates! if not terminates[n]: terminates[n] = 1 some_change = 1 # Don't need to consider any more productions for this n. break if not some_change: break infinite = [] for (s,term) in terminates.items(): if not term: if not s in self.Prodnames and not s in self.Terminals and s != 'error': # s is used-but-not-defined, and we've already warned of that, # so it would be overkill to say that it's also non-terminating. pass else: infinite.append(s) return infinite # ----------------------------------------------------------------------------- # undefined_symbols() # # Find all symbols that were used the grammar, but not defined as tokens or # grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol # and prod is the production where the symbol was used. # ----------------------------------------------------------------------------- def undefined_symbols(self): result = [] for p in self.Productions: if not p: continue for s in p.prod: if not s in self.Prodnames and not s in self.Terminals and s != 'error': result.append((s,p)) return result # ----------------------------------------------------------------------------- # unused_terminals() # # Find all terminals that were defined, but not used by the grammar. Returns # a list of all symbols. # ----------------------------------------------------------------------------- def unused_terminals(self): unused_tok = [] for s,v in self.Terminals.items(): if s != 'error' and not v: unused_tok.append(s) return unused_tok # ------------------------------------------------------------------------------ # unused_rules() # # Find all grammar rules that were defined, but not used (maybe not reachable) # Returns a list of productions. # ------------------------------------------------------------------------------ def unused_rules(self): unused_prod = [] for s,v in self.Nonterminals.items(): if not v: p = self.Prodnames[s][0] unused_prod.append(p) return unused_prod # ----------------------------------------------------------------------------- # unused_precedence() # # Returns a list of tuples (term,precedence) corresponding to precedence # rules that were never used by the grammar. term is the name of the terminal # on which precedence was applied and precedence is a string such as 'left' or # 'right' corresponding to the type of precedence. # ----------------------------------------------------------------------------- def unused_precedence(self): unused = [] for termname in self.Precedence: if not (termname in self.Terminals or termname in self.UsedPrecedence): unused.append((termname,self.Precedence[termname][0])) return unused # ------------------------------------------------------------------------- # _first() # # Compute the value of FIRST1(beta) where beta is a tuple of symbols. # # During execution of compute_first1, the result may be incomplete. # Afterward (e.g., when called from compute_follow()), it will be complete. # ------------------------------------------------------------------------- def _first(self,beta): # We are computing First(x1,x2,x3,...,xn) result = [ ] for x in beta: x_produces_empty = 0 # Add all the non- symbols of First[x] to the result. for f in self.First[x]: if f == '': x_produces_empty = 1 else: if f not in result: result.append(f) if x_produces_empty: # We have to consider the next x in beta, # i.e. stay in the loop. pass else: # We don't have to consider any further symbols in beta. break else: # There was no 'break' from the loop, # so x_produces_empty was true for all x in beta, # so beta produces empty as well. result.append('') return result # ------------------------------------------------------------------------- # compute_first() # # Compute the value of FIRST1(X) for all symbols # ------------------------------------------------------------------------- def compute_first(self): if self.First: return self.First # Terminals: for t in self.Terminals: self.First[t] = [t] self.First['$end'] = ['$end'] # Nonterminals: # Initialize to the empty set: for n in self.Nonterminals: self.First[n] = [] # Then propagate symbols until no change: while 1: some_change = 0 for n in self.Nonterminals: for p in self.Prodnames[n]: for f in self._first(p.prod): if f not in self.First[n]: self.First[n].append( f ) some_change = 1 if not some_change: break return self.First # --------------------------------------------------------------------- # compute_follow() # # Computes all of the follow sets for every non-terminal symbol. The # follow set is the set of all symbols that might follow a given # non-terminal. See the Dragon book, 2nd Ed. p. 189. # --------------------------------------------------------------------- def compute_follow(self,start=None): # If already computed, return the result if self.Follow: return self.Follow # If first sets not computed yet, do that first. if not self.First: self.compute_first() # Add '$end' to the follow list of the start symbol for k in self.Nonterminals: self.Follow[k] = [ ] if not start: start = self.Productions[1].name self.Follow[start] = [ '$end' ] while 1: didadd = 0 for p in self.Productions[1:]: # Here is the production set for i in range(len(p.prod)): B = p.prod[i] if B in self.Nonterminals: # Okay. We got a non-terminal in a production fst = self._first(p.prod[i+1:]) hasempty = 0 for f in fst: if f != '' and f not in self.Follow[B]: self.Follow[B].append(f) didadd = 1 if f == '': hasempty = 1 if hasempty or i == (len(p.prod)-1): # Add elements of follow(a) to follow(b) for f in self.Follow[p.name]: if f not in self.Follow[B]: self.Follow[B].append(f) didadd = 1 if not didadd: break return self.Follow # ----------------------------------------------------------------------------- # build_lritems() # # This function walks the list of productions and builds a complete set of the # LR items. The LR items are stored in two ways: First, they are uniquely # numbered and placed in the list _lritems. Second, a linked list of LR items # is built for each production. For example: # # E -> E PLUS E # # Creates the list # # [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] # ----------------------------------------------------------------------------- def build_lritems(self): for p in self.Productions: lastlri = p i = 0 lr_items = [] while 1: if i > len(p): lri = None else: lri = LRItem(p,i) # Precompute the list of productions immediately following try: lri.lr_after = self.Prodnames[lri.prod[i+1]] except (IndexError,KeyError): lri.lr_after = [] try: lri.lr_before = lri.prod[i-1] except IndexError: lri.lr_before = None lastlri.lr_next = lri if not lri: break lr_items.append(lri) lastlri = lri i += 1 p.lr_items = lr_items # ----------------------------------------------------------------------------- # == Class LRTable == # # This basic class represents a basic table of LR parsing information. # Methods for generating the tables are not defined here. They are defined # in the derived class LRGeneratedTable. # ----------------------------------------------------------------------------- class VersionError(YaccError): pass class LRTable(object): def __init__(self): self.lr_action = None self.lr_goto = None self.lr_productions = None self.lr_method = None def read_table(self,module): if isinstance(module,types.ModuleType): parsetab = module else: if sys.version_info[0] < 3: exec("import %s as parsetab" % module) else: env = { } exec("import %s as parsetab" % module, env, env) parsetab = env['parsetab'] if parsetab._tabversion != __tabversion__: raise VersionError("yacc table file version is out of date") self.lr_action = parsetab._lr_action self.lr_goto = parsetab._lr_goto self.lr_productions = [] for p in parsetab._lr_productions: self.lr_productions.append(MiniProduction(*p)) self.lr_method = parsetab._lr_method return parsetab._lr_signature def read_pickle(self,filename): try: import cPickle as pickle except ImportError: import pickle in_f = open(filename,"rb") tabversion = pickle.load(in_f) if tabversion != __tabversion__: raise VersionError("yacc table file version is out of date") self.lr_method = pickle.load(in_f) signature = pickle.load(in_f) self.lr_action = pickle.load(in_f) self.lr_goto = pickle.load(in_f) productions = pickle.load(in_f) self.lr_productions = [] for p in productions: self.lr_productions.append(MiniProduction(*p)) in_f.close() return signature # Bind all production function names to callable objects in pdict def bind_callables(self,pdict): for p in self.lr_productions: p.bind(pdict) # ----------------------------------------------------------------------------- # === LR Generator === # # The following classes and functions are used to generate LR parsing tables on # a grammar. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # digraph() # traverse() # # The following two functions are used to compute set valued functions # of the form: # # F(x) = F'(x) U U{F(y) | x R y} # # This is used to compute the values of Read() sets as well as FOLLOW sets # in LALR(1) generation. # # Inputs: X - An input set # R - A relation # FP - Set-valued function # ------------------------------------------------------------------------------ def digraph(X,R,FP): N = { } for x in X: N[x] = 0 stack = [] F = { } for x in X: if N[x] == 0: traverse(x,N,stack,F,X,R,FP) return F def traverse(x,N,stack,F,X,R,FP): stack.append(x) d = len(stack) N[x] = d F[x] = FP(x) # F(X) <- F'(x) rel = R(x) # Get y's related to x for y in rel: if N[y] == 0: traverse(y,N,stack,F,X,R,FP) N[x] = min(N[x],N[y]) for a in F.get(y,[]): if a not in F[x]: F[x].append(a) if N[x] == d: N[stack[-1]] = MAXINT F[stack[-1]] = F[x] element = stack.pop() while element != x: N[stack[-1]] = MAXINT F[stack[-1]] = F[x] element = stack.pop() class LALRError(YaccError): pass # ----------------------------------------------------------------------------- # == LRGeneratedTable == # # This class implements the LR table generation algorithm. There are no # public methods except for write() # ----------------------------------------------------------------------------- class LRGeneratedTable(LRTable): def __init__(self,grammar,method='LALR',log=None): if method not in ['SLR','LALR']: raise LALRError("Unsupported method %s" % method) self.grammar = grammar self.lr_method = method # Set up the logger if not log: log = NullLogger() self.log = log # Internal attributes self.lr_action = {} # Action table self.lr_goto = {} # Goto table self.lr_productions = grammar.Productions # Copy of grammar Production array self.lr_goto_cache = {} # Cache of computed gotos self.lr0_cidhash = {} # Cache of closures self._add_count = 0 # Internal counter used to detect cycles # Diagonistic information filled in by the table generator self.sr_conflict = 0 self.rr_conflict = 0 self.conflicts = [] # List of conflicts self.sr_conflicts = [] self.rr_conflicts = [] # Build the tables self.grammar.build_lritems() self.grammar.compute_first() self.grammar.compute_follow() self.lr_parse_table() # Compute the LR(0) closure operation on I, where I is a set of LR(0) items. def lr0_closure(self,I): self._add_count += 1 # Add everything in I to J J = I[:] didadd = 1 while didadd: didadd = 0 for j in J: for x in j.lr_after: if getattr(x,"lr0_added",0) == self._add_count: continue # Add B --> .G to J J.append(x.lr_next) x.lr0_added = self._add_count didadd = 1 return J # Compute the LR(0) goto function goto(I,X) where I is a set # of LR(0) items and X is a grammar symbol. This function is written # in a way that guarantees uniqueness of the generated goto sets # (i.e. the same goto set will never be returned as two different Python # objects). With uniqueness, we can later do fast set comparisons using # id(obj) instead of element-wise comparison. def lr0_goto(self,I,x): # First we look for a previously cached entry g = self.lr_goto_cache.get((id(I),x),None) if g: return g # Now we generate the goto set in a way that guarantees uniqueness # of the result s = self.lr_goto_cache.get(x,None) if not s: s = { } self.lr_goto_cache[x] = s gs = [ ] for p in I: n = p.lr_next if n and n.lr_before == x: s1 = s.get(id(n),None) if not s1: s1 = { } s[id(n)] = s1 gs.append(n) s = s1 g = s.get('$end',None) if not g: if gs: g = self.lr0_closure(gs) s['$end'] = g else: s['$end'] = gs self.lr_goto_cache[(id(I),x)] = g return g # Compute the LR(0) sets of item function def lr0_items(self): C = [ self.lr0_closure([self.grammar.Productions[0].lr_next]) ] i = 0 for I in C: self.lr0_cidhash[id(I)] = i i += 1 # Loop over the items in C and each grammar symbols i = 0 while i < len(C): I = C[i] i += 1 # Collect all of the symbols that could possibly be in the goto(I,X) sets asyms = { } for ii in I: for s in ii.usyms: asyms[s] = None for x in asyms: g = self.lr0_goto(I,x) if not g: continue if id(g) in self.lr0_cidhash: continue self.lr0_cidhash[id(g)] = len(C) C.append(g) return C # ----------------------------------------------------------------------------- # ==== LALR(1) Parsing ==== # # LALR(1) parsing is almost exactly the same as SLR except that instead of # relying upon Follow() sets when performing reductions, a more selective # lookahead set that incorporates the state of the LR(0) machine is utilized. # Thus, we mainly just have to focus on calculating the lookahead sets. # # The method used here is due to DeRemer and Pennelo (1982). # # DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) # Lookahead Sets", ACM Transactions on Programming Languages and Systems, # Vol. 4, No. 4, Oct. 1982, pp. 615-649 # # Further details can also be found in: # # J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", # McGraw-Hill Book Company, (1985). # # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # compute_nullable_nonterminals() # # Creates a dictionary containing all of the non-terminals that might produce # an empty production. # ----------------------------------------------------------------------------- def compute_nullable_nonterminals(self): nullable = {} num_nullable = 0 while 1: for p in self.grammar.Productions[1:]: if p.len == 0: nullable[p.name] = 1 continue for t in p.prod: if not t in nullable: break else: nullable[p.name] = 1 if len(nullable) == num_nullable: break num_nullable = len(nullable) return nullable # ----------------------------------------------------------------------------- # find_nonterminal_trans(C) # # Given a set of LR(0) items, this functions finds all of the non-terminal # transitions. These are transitions in which a dot appears immediately before # a non-terminal. Returns a list of tuples of the form (state,N) where state # is the state number and N is the nonterminal symbol. # # The input C is the set of LR(0) items. # ----------------------------------------------------------------------------- def find_nonterminal_transitions(self,C): trans = [] for state in range(len(C)): for p in C[state]: if p.lr_index < p.len - 1: t = (state,p.prod[p.lr_index+1]) if t[1] in self.grammar.Nonterminals: if t not in trans: trans.append(t) state = state + 1 return trans # ----------------------------------------------------------------------------- # dr_relation() # # Computes the DR(p,A) relationships for non-terminal transitions. The input # is a tuple (state,N) where state is a number and N is a nonterminal symbol. # # Returns a list of terminals. # ----------------------------------------------------------------------------- def dr_relation(self,C,trans,nullable): dr_set = { } state,N = trans terms = [] g = self.lr0_goto(C[state],N) for p in g: if p.lr_index < p.len - 1: a = p.prod[p.lr_index+1] if a in self.grammar.Terminals: if a not in terms: terms.append(a) # This extra bit is to handle the start state if state == 0 and N == self.grammar.Productions[0].prod[0]: terms.append('$end') return terms # ----------------------------------------------------------------------------- # reads_relation() # # Computes the READS() relation (p,A) READS (t,C). # ----------------------------------------------------------------------------- def reads_relation(self,C, trans, empty): # Look for empty transitions rel = [] state, N = trans g = self.lr0_goto(C[state],N) j = self.lr0_cidhash.get(id(g),-1) for p in g: if p.lr_index < p.len - 1: a = p.prod[p.lr_index + 1] if a in empty: rel.append((j,a)) return rel # ----------------------------------------------------------------------------- # compute_lookback_includes() # # Determines the lookback and includes relations # # LOOKBACK: # # This relation is determined by running the LR(0) state machine forward. # For example, starting with a production "N : . A B C", we run it forward # to obtain "N : A B C ." We then build a relationship between this final # state and the starting state. These relationships are stored in a dictionary # lookdict. # # INCLUDES: # # Computes the INCLUDE() relation (p,A) INCLUDES (p',B). # # This relation is used to determine non-terminal transitions that occur # inside of other non-terminal transition states. (p,A) INCLUDES (p', B) # if the following holds: # # B -> LAT, where T -> epsilon and p' -L-> p # # L is essentially a prefix (which may be empty), T is a suffix that must be # able to derive an empty string. State p' must lead to state p with the string L. # # ----------------------------------------------------------------------------- def compute_lookback_includes(self,C,trans,nullable): lookdict = {} # Dictionary of lookback relations includedict = {} # Dictionary of include relations # Make a dictionary of non-terminal transitions dtrans = {} for t in trans: dtrans[t] = 1 # Loop over all transitions and compute lookbacks and includes for state,N in trans: lookb = [] includes = [] for p in C[state]: if p.name != N: continue # Okay, we have a name match. We now follow the production all the way # through the state machine until we get the . on the right hand side lr_index = p.lr_index j = state while lr_index < p.len - 1: lr_index = lr_index + 1 t = p.prod[lr_index] # Check to see if this symbol and state are a non-terminal transition if (j,t) in dtrans: # Yes. Okay, there is some chance that this is an includes relation # the only way to know for certain is whether the rest of the # production derives empty li = lr_index + 1 while li < p.len: if p.prod[li] in self.grammar.Terminals: break # No forget it if not p.prod[li] in nullable: break li = li + 1 else: # Appears to be a relation between (j,t) and (state,N) includes.append((j,t)) g = self.lr0_goto(C[j],t) # Go to next set j = self.lr0_cidhash.get(id(g),-1) # Go to next state # When we get here, j is the final state, now we have to locate the production for r in C[j]: if r.name != p.name: continue if r.len != p.len: continue i = 0 # This look is comparing a production ". A B C" with "A B C ." while i < r.lr_index: if r.prod[i] != p.prod[i+1]: break i = i + 1 else: lookb.append((j,r)) for i in includes: if not i in includedict: includedict[i] = [] includedict[i].append((state,N)) lookdict[(state,N)] = lookb return lookdict,includedict # ----------------------------------------------------------------------------- # compute_read_sets() # # Given a set of LR(0) items, this function computes the read sets. # # Inputs: C = Set of LR(0) items # ntrans = Set of nonterminal transitions # nullable = Set of empty transitions # # Returns a set containing the read sets # ----------------------------------------------------------------------------- def compute_read_sets(self,C, ntrans, nullable): FP = lambda x: self.dr_relation(C,x,nullable) R = lambda x: self.reads_relation(C,x,nullable) F = digraph(ntrans,R,FP) return F # ----------------------------------------------------------------------------- # compute_follow_sets() # # Given a set of LR(0) items, a set of non-terminal transitions, a readset, # and an include set, this function computes the follow sets # # Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} # # Inputs: # ntrans = Set of nonterminal transitions # readsets = Readset (previously computed) # inclsets = Include sets (previously computed) # # Returns a set containing the follow sets # ----------------------------------------------------------------------------- def compute_follow_sets(self,ntrans,readsets,inclsets): FP = lambda x: readsets[x] R = lambda x: inclsets.get(x,[]) F = digraph(ntrans,R,FP) return F # ----------------------------------------------------------------------------- # add_lookaheads() # # Attaches the lookahead symbols to grammar rules. # # Inputs: lookbacks - Set of lookback relations # followset - Computed follow set # # This function directly attaches the lookaheads to productions contained # in the lookbacks set # ----------------------------------------------------------------------------- def add_lookaheads(self,lookbacks,followset): for trans,lb in lookbacks.items(): # Loop over productions in lookback for state,p in lb: if not state in p.lookaheads: p.lookaheads[state] = [] f = followset.get(trans,[]) for a in f: if a not in p.lookaheads[state]: p.lookaheads[state].append(a) # ----------------------------------------------------------------------------- # add_lalr_lookaheads() # # This function does all of the work of adding lookahead information for use # with LALR parsing # ----------------------------------------------------------------------------- def add_lalr_lookaheads(self,C): # Determine all of the nullable nonterminals nullable = self.compute_nullable_nonterminals() # Find all non-terminal transitions trans = self.find_nonterminal_transitions(C) # Compute read sets readsets = self.compute_read_sets(C,trans,nullable) # Compute lookback/includes relations lookd, included = self.compute_lookback_includes(C,trans,nullable) # Compute LALR FOLLOW sets followsets = self.compute_follow_sets(trans,readsets,included) # Add all of the lookaheads self.add_lookaheads(lookd,followsets) # ----------------------------------------------------------------------------- # lr_parse_table() # # This function constructs the parse tables for SLR or LALR # ----------------------------------------------------------------------------- def lr_parse_table(self): Productions = self.grammar.Productions Precedence = self.grammar.Precedence goto = self.lr_goto # Goto array action = self.lr_action # Action array log = self.log # Logger for output actionp = { } # Action production array (temporary) log.info("Parsing method: %s", self.lr_method) # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items # This determines the number of states C = self.lr0_items() if self.lr_method == 'LALR': self.add_lalr_lookaheads(C) # Build the parser table, state by state st = 0 for I in C: # Loop over each production in I actlist = [ ] # List of actions st_action = { } st_actionp = { } st_goto = { } log.info("") log.info("state %d", st) log.info("") for p in I: log.info(" (%d) %s", p.number, str(p)) log.info("") for p in I: if p.len == p.lr_index + 1: if p.name == "S'": # Start symbol. Accept! st_action["$end"] = 0 st_actionp["$end"] = p else: # We are at the end of a production. Reduce! if self.lr_method == 'LALR': laheads = p.lookaheads[st] else: laheads = self.grammar.Follow[p.name] for a in laheads: actlist.append((a,p,"reduce using rule %d (%s)" % (p.number,p))) r = st_action.get(a,None) if r is not None: # Whoa. Have a shift/reduce or reduce/reduce conflict if r > 0: # Need to decide on shift or reduce here # By default we favor shifting. Need to add # some precedence rules here. sprec,slevel = Productions[st_actionp[a].number].prec rprec,rlevel = Precedence.get(a,('right',0)) if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): # We really need to reduce here. st_action[a] = -p.number st_actionp[a] = p if not slevel and not rlevel: log.info(" ! shift/reduce conflict for %s resolved as reduce",a) self.sr_conflicts.append((st,a,'reduce')) Productions[p.number].reduced += 1 elif (slevel == rlevel) and (rprec == 'nonassoc'): st_action[a] = None else: # Hmmm. Guess we'll keep the shift if not rlevel: log.info(" ! shift/reduce conflict for %s resolved as shift",a) self.sr_conflicts.append((st,a,'shift')) elif r < 0: # Reduce/reduce conflict. In this case, we favor the rule # that was defined first in the grammar file oldp = Productions[-r] pp = Productions[p.number] if oldp.line > pp.line: st_action[a] = -p.number st_actionp[a] = p chosenp,rejectp = pp,oldp Productions[p.number].reduced += 1 Productions[oldp.number].reduced -= 1 else: chosenp,rejectp = oldp,pp self.rr_conflicts.append((st,chosenp,rejectp)) log.info(" ! reduce/reduce conflict for %s resolved using rule %d (%s)", a,st_actionp[a].number, st_actionp[a]) else: raise LALRError("Unknown conflict in state %d" % st) else: st_action[a] = -p.number st_actionp[a] = p Productions[p.number].reduced += 1 else: i = p.lr_index a = p.prod[i+1] # Get symbol right after the "." if a in self.grammar.Terminals: g = self.lr0_goto(I,a) j = self.lr0_cidhash.get(id(g),-1) if j >= 0: # We are in a shift state actlist.append((a,p,"shift and go to state %d" % j)) r = st_action.get(a,None) if r is not None: # Whoa have a shift/reduce or shift/shift conflict if r > 0: if r != j: raise LALRError("Shift/shift conflict in state %d" % st) elif r < 0: # Do a precedence check. # - if precedence of reduce rule is higher, we reduce. # - if precedence of reduce is same and left assoc, we reduce. # - otherwise we shift rprec,rlevel = Productions[st_actionp[a].number].prec sprec,slevel = Precedence.get(a,('right',0)) if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): # We decide to shift here... highest precedence to shift Productions[st_actionp[a].number].reduced -= 1 st_action[a] = j st_actionp[a] = p if not rlevel: log.info(" ! shift/reduce conflict for %s resolved as shift",a) self.sr_conflicts.append((st,a,'shift')) elif (slevel == rlevel) and (rprec == 'nonassoc'): st_action[a] = None else: # Hmmm. Guess we'll keep the reduce if not slevel and not rlevel: log.info(" ! shift/reduce conflict for %s resolved as reduce",a) self.sr_conflicts.append((st,a,'reduce')) else: raise LALRError("Unknown conflict in state %d" % st) else: st_action[a] = j st_actionp[a] = p # Print the actions associated with each terminal _actprint = { } for a,p,m in actlist: if a in st_action: if p is st_actionp[a]: log.info(" %-15s %s",a,m) _actprint[(a,m)] = 1 log.info("") # Print the actions that were not used. (debugging) not_used = 0 for a,p,m in actlist: if a in st_action: if p is not st_actionp[a]: if not (a,m) in _actprint: log.debug(" ! %-15s [ %s ]",a,m) not_used = 1 _actprint[(a,m)] = 1 if not_used: log.debug("") # Construct the goto table for this state nkeys = { } for ii in I: for s in ii.usyms: if s in self.grammar.Nonterminals: nkeys[s] = None for n in nkeys: g = self.lr0_goto(I,n) j = self.lr0_cidhash.get(id(g),-1) if j >= 0: st_goto[n] = j log.info(" %-30s shift and go to state %d",n,j) action[st] = st_action actionp[st] = st_actionp goto[st] = st_goto st += 1 # ----------------------------------------------------------------------------- # write() # # This function writes the LR parsing tables to a file # ----------------------------------------------------------------------------- def write_table(self,modulename,outputdir='',signature=""): basemodulename = modulename.split(".")[-1] filename = os.path.join(outputdir,basemodulename) + ".py" try: f = open(filename,"w") f.write(""" # %s # This file is automatically generated. Do not edit. _tabversion = %r _lr_method = %r _lr_signature = %r """ % (filename, __tabversion__, self.lr_method, signature)) # Change smaller to 0 to go back to original tables smaller = 1 # Factor out names to try and make smaller if smaller: items = { } for s,nd in self.lr_action.items(): for name,v in nd.items(): i = items.get(name) if not i: i = ([],[]) items[name] = i i[0].append(s) i[1].append(v) f.write("\n_lr_action_items = {") for k,v in items.items(): f.write("%r:([" % k) for i in v[0]: f.write("%r," % i) f.write("],[") for i in v[1]: f.write("%r," % i) f.write("]),") f.write("}\n") f.write(""" _lr_action = { } for _k, _v in _lr_action_items.items(): for _x,_y in zip(_v[0],_v[1]): if not _x in _lr_action: _lr_action[_x] = { } _lr_action[_x][_k] = _y del _lr_action_items """) else: f.write("\n_lr_action = { "); for k,v in self.lr_action.items(): f.write("(%r,%r):%r," % (k[0],k[1],v)) f.write("}\n"); if smaller: # Factor out names to try and make smaller items = { } for s,nd in self.lr_goto.items(): for name,v in nd.items(): i = items.get(name) if not i: i = ([],[]) items[name] = i i[0].append(s) i[1].append(v) f.write("\n_lr_goto_items = {") for k,v in items.items(): f.write("%r:([" % k) for i in v[0]: f.write("%r," % i) f.write("],[") for i in v[1]: f.write("%r," % i) f.write("]),") f.write("}\n") f.write(""" _lr_goto = { } for _k, _v in _lr_goto_items.items(): for _x,_y in zip(_v[0],_v[1]): if not _x in _lr_goto: _lr_goto[_x] = { } _lr_goto[_x][_k] = _y del _lr_goto_items """) else: f.write("\n_lr_goto = { "); for k,v in self.lr_goto.items(): f.write("(%r,%r):%r," % (k[0],k[1],v)) f.write("}\n"); # Write production table f.write("_lr_productions = [\n") for p in self.lr_productions: if p.func: f.write(" (%r,%r,%d,%r,%r,%d),\n" % (p.str,p.name, p.len, p.func,p.file,p.line)) else: f.write(" (%r,%r,%d,None,None,None),\n" % (str(p),p.name, p.len)) f.write("]\n") f.close() except IOError: e = sys.exc_info()[1] sys.stderr.write("Unable to create '%s'\n" % filename) sys.stderr.write(str(e)+"\n") return # ----------------------------------------------------------------------------- # pickle_table() # # This function pickles the LR parsing tables to a supplied file object # ----------------------------------------------------------------------------- def pickle_table(self,filename,signature=""): try: import cPickle as pickle except ImportError: import pickle outf = open(filename,"wb") pickle.dump(__tabversion__,outf,pickle_protocol) pickle.dump(self.lr_method,outf,pickle_protocol) pickle.dump(signature,outf,pickle_protocol) pickle.dump(self.lr_action,outf,pickle_protocol) pickle.dump(self.lr_goto,outf,pickle_protocol) outp = [] for p in self.lr_productions: if p.func: outp.append((p.str,p.name, p.len, p.func,p.file,p.line)) else: outp.append((str(p),p.name,p.len,None,None,None)) pickle.dump(outp,outf,pickle_protocol) outf.close() # ----------------------------------------------------------------------------- # === INTROSPECTION === # # The following functions and classes are used to implement the PLY # introspection features followed by the yacc() function itself. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # get_caller_module_dict() # # This function returns a dictionary containing all of the symbols defined within # a caller further down the call stack. This is used to get the environment # associated with the yacc() call if none was provided. # ----------------------------------------------------------------------------- def get_caller_module_dict(levels): try: raise RuntimeError except RuntimeError: e,b,t = sys.exc_info() f = t.tb_frame while levels > 0: f = f.f_back levels -= 1 ldict = f.f_globals.copy() if f.f_globals != f.f_locals: ldict.update(f.f_locals) return ldict # ----------------------------------------------------------------------------- # parse_grammar() # # This takes a raw grammar rule string and parses it into production data # ----------------------------------------------------------------------------- def parse_grammar(doc,file,line): grammar = [] # Split the doc string into lines pstrings = doc.splitlines() lastp = None dline = line for ps in pstrings: dline += 1 p = ps.split() if not p: continue try: if p[0] == '|': # This is a continuation of a previous rule if not lastp: raise SyntaxError("%s:%d: Misplaced '|'" % (file,dline)) prodname = lastp syms = p[1:] else: prodname = p[0] lastp = prodname syms = p[2:] assign = p[1] if assign != ':' and assign != '::=': raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file,dline)) grammar.append((file,dline,prodname,syms)) except SyntaxError: raise except Exception: raise SyntaxError("%s:%d: Syntax error in rule '%s'" % (file,dline,ps.strip())) return grammar # ----------------------------------------------------------------------------- # ParserReflect() # # This class represents information extracted for building a parser including # start symbol, error function, tokens, precedence list, action functions, # etc. # ----------------------------------------------------------------------------- class ParserReflect(object): def __init__(self,pdict,log=None): self.pdict = pdict self.start = None self.error_func = None self.tokens = None self.files = {} self.grammar = [] self.error = 0 if log is None: self.log = PlyLogger(sys.stderr) else: self.log = log # Get all of the basic information def get_all(self): self.get_start() self.get_error_func() self.get_tokens() self.get_precedence() self.get_pfunctions() # Validate all of the information def validate_all(self): self.validate_start() self.validate_error_func() self.validate_tokens() self.validate_precedence() self.validate_pfunctions() self.validate_files() return self.error # Compute a signature over the grammar def signature(self): try: from hashlib import md5 except ImportError: from md5 import md5 try: sig = md5() if self.start: sig.update(self.start.encode('latin-1')) if self.prec: sig.update("".join(["".join(p) for p in self.prec]).encode('latin-1')) if self.tokens: sig.update(" ".join(self.tokens).encode('latin-1')) for f in self.pfuncs: if f[3]: sig.update(f[3].encode('latin-1')) except (TypeError,ValueError): pass return sig.digest() # ----------------------------------------------------------------------------- # validate_file() # # This method checks to see if there are duplicated p_rulename() functions # in the parser module file. Without this function, it is really easy for # users to make mistakes by cutting and pasting code fragments (and it's a real # bugger to try and figure out why the resulting parser doesn't work). Therefore, # we just do a little regular expression pattern matching of def statements # to try and detect duplicates. # ----------------------------------------------------------------------------- def validate_files(self): # Match def p_funcname( fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') for filename in self.files.keys(): base,ext = os.path.splitext(filename) if ext != '.py': return 1 # No idea. Assume it's okay. try: f = open(filename) lines = f.readlines() f.close() except IOError: continue counthash = { } for linen,l in enumerate(lines): linen += 1 m = fre.match(l) if m: name = m.group(1) prev = counthash.get(name) if not prev: counthash[name] = linen else: self.log.warning("%s:%d: Function %s redefined. Previously defined on line %d", filename,linen,name,prev) # Get the start symbol def get_start(self): self.start = self.pdict.get('start') # Validate the start symbol def validate_start(self): if self.start is not None: if not isinstance(self.start,str): self.log.error("'start' must be a string") # Look for error handler def get_error_func(self): self.error_func = self.pdict.get('p_error') # Validate the error function def validate_error_func(self): if self.error_func: if isinstance(self.error_func,types.FunctionType): ismethod = 0 elif isinstance(self.error_func, types.MethodType): ismethod = 1 else: self.log.error("'p_error' defined, but is not a function or method") self.error = 1 return eline = func_code(self.error_func).co_firstlineno efile = func_code(self.error_func).co_filename self.files[efile] = 1 if (func_code(self.error_func).co_argcount != 1+ismethod): self.log.error("%s:%d: p_error() requires 1 argument",efile,eline) self.error = 1 # Get the tokens map def get_tokens(self): tokens = self.pdict.get("tokens",None) if not tokens: self.log.error("No token list is defined") self.error = 1 return if not isinstance(tokens,(list, tuple)): self.log.error("tokens must be a list or tuple") self.error = 1 return if not tokens: self.log.error("tokens is empty") self.error = 1 return self.tokens = tokens # Validate the tokens def validate_tokens(self): # Validate the tokens. if 'error' in self.tokens: self.log.error("Illegal token name 'error'. Is a reserved word") self.error = 1 return terminals = {} for n in self.tokens: if n in terminals: self.log.warning("Token '%s' multiply defined", n) terminals[n] = 1 # Get the precedence map (if any) def get_precedence(self): self.prec = self.pdict.get("precedence",None) # Validate and parse the precedence map def validate_precedence(self): preclist = [] if self.prec: if not isinstance(self.prec,(list,tuple)): self.log.error("precedence must be a list or tuple") self.error = 1 return for level,p in enumerate(self.prec): if not isinstance(p,(list,tuple)): self.log.error("Bad precedence table") self.error = 1 return if len(p) < 2: self.log.error("Malformed precedence entry %s. Must be (assoc, term, ..., term)",p) self.error = 1 return assoc = p[0] if not isinstance(assoc,str): self.log.error("precedence associativity must be a string") self.error = 1 return for term in p[1:]: if not isinstance(term,str): self.log.error("precedence items must be strings") self.error = 1 return preclist.append((term,assoc,level+1)) self.preclist = preclist # Get all p_functions from the grammar def get_pfunctions(self): p_functions = [] for name, item in self.pdict.items(): if name[:2] != 'p_': continue if name == 'p_error': continue if isinstance(item,(types.FunctionType,types.MethodType)): line = func_code(item).co_firstlineno file = func_code(item).co_filename p_functions.append((line,file,name,item.__doc__)) # Sort all of the actions by line number p_functions.sort() self.pfuncs = p_functions # Validate all of the p_functions def validate_pfunctions(self): grammar = [] # Check for non-empty symbols if len(self.pfuncs) == 0: self.log.error("no rules of the form p_rulename are defined") self.error = 1 return for line, file, name, doc in self.pfuncs: func = self.pdict[name] if isinstance(func, types.MethodType): reqargs = 2 else: reqargs = 1 if func_code(func).co_argcount > reqargs: self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,func.__name__) self.error = 1 elif func_code(func).co_argcount < reqargs: self.log.error("%s:%d: Rule '%s' requires an argument",file,line,func.__name__) self.error = 1 elif not func.__doc__: self.log.warning("%s:%d: No documentation string specified in function '%s' (ignored)",file,line,func.__name__) else: try: parsed_g = parse_grammar(doc,file,line) for g in parsed_g: grammar.append((name, g)) except SyntaxError: e = sys.exc_info()[1] self.log.error(str(e)) self.error = 1 # Looks like a valid grammar rule # Mark the file in which defined. self.files[file] = 1 # Secondary validation step that looks for p_ definitions that are not functions # or functions that look like they might be grammar rules. for n,v in self.pdict.items(): if n[0:2] == 'p_' and isinstance(v, (types.FunctionType, types.MethodType)): continue if n[0:2] == 't_': continue if n[0:2] == 'p_' and n != 'p_error': self.log.warning("'%s' not defined as a function", n) if ((isinstance(v,types.FunctionType) and func_code(v).co_argcount == 1) or (isinstance(v,types.MethodType) and func_code(v).co_argcount == 2)): try: doc = v.__doc__.split(" ") if doc[1] == ':': self.log.warning("%s:%d: Possible grammar rule '%s' defined without p_ prefix", func_code(v).co_filename, func_code(v).co_firstlineno,n) except Exception: pass self.grammar = grammar # ----------------------------------------------------------------------------- # yacc(module) # # Build a parser # ----------------------------------------------------------------------------- def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None, check_recursion=1, optimize=0, write_tables=1, debugfile=debug_file,outputdir='', debuglog=None, errorlog = None, picklefile=None): global parse # Reference to the parsing method of the last built parser # If pickling is enabled, table files are not created if picklefile: write_tables = 0 if errorlog is None: errorlog = PlyLogger(sys.stderr) # Get the module dictionary used for the parser if module: _items = [(k,getattr(module,k)) for k in dir(module)] pdict = dict(_items) else: pdict = get_caller_module_dict(2) # Collect parser information from the dictionary pinfo = ParserReflect(pdict,log=errorlog) pinfo.get_all() if pinfo.error: raise YaccError("Unable to build parser") # Check signature against table files (if any) signature = pinfo.signature() # Read the tables try: lr = LRTable() if picklefile: read_signature = lr.read_pickle(picklefile) else: read_signature = lr.read_table(tabmodule) if optimize or (read_signature == signature): try: lr.bind_callables(pinfo.pdict) parser = LRParser(lr,pinfo.error_func) parse = parser.parse return parser except Exception: e = sys.exc_info()[1] errorlog.warning("There was a problem loading the table file: %s", repr(e)) except VersionError: e = sys.exc_info() errorlog.warning(str(e)) except Exception: pass if debuglog is None: if debug: debuglog = PlyLogger(open(debugfile,"w")) else: debuglog = NullLogger() debuglog.info("Created by PLY version %s (http://www.dabeaz.com/ply)", __version__) errors = 0 # Validate the parser information if pinfo.validate_all(): raise YaccError("Unable to build parser") if not pinfo.error_func: errorlog.warning("no p_error() function is defined") # Create a grammar object grammar = Grammar(pinfo.tokens) # Set precedence level for terminals for term, assoc, level in pinfo.preclist: try: grammar.set_precedence(term,assoc,level) except GrammarError: e = sys.exc_info()[1] errorlog.warning("%s",str(e)) # Add productions to the grammar for funcname, gram in pinfo.grammar: file, line, prodname, syms = gram try: grammar.add_production(prodname,syms,funcname,file,line) except GrammarError: e = sys.exc_info()[1] errorlog.error("%s",str(e)) errors = 1 # Set the grammar start symbols try: if start is None: grammar.set_start(pinfo.start) else: grammar.set_start(start) except GrammarError: e = sys.exc_info()[1] errorlog.error(str(e)) errors = 1 if errors: raise YaccError("Unable to build parser") # Verify the grammar structure undefined_symbols = grammar.undefined_symbols() for sym, prod in undefined_symbols: errorlog.error("%s:%d: Symbol '%s' used, but not defined as a token or a rule",prod.file,prod.line,sym) errors = 1 unused_terminals = grammar.unused_terminals() if unused_terminals: debuglog.info("") debuglog.info("Unused terminals:") debuglog.info("") for term in unused_terminals: errorlog.warning("Token '%s' defined, but not used", term) debuglog.info(" %s", term) # Print out all productions to the debug log if debug: debuglog.info("") debuglog.info("Grammar") debuglog.info("") for n,p in enumerate(grammar.Productions): debuglog.info("Rule %-5d %s", n, p) # Find unused non-terminals unused_rules = grammar.unused_rules() for prod in unused_rules: errorlog.warning("%s:%d: Rule '%s' defined, but not used", prod.file, prod.line, prod.name) if len(unused_terminals) == 1: errorlog.warning("There is 1 unused token") if len(unused_terminals) > 1: errorlog.warning("There are %d unused tokens", len(unused_terminals)) if len(unused_rules) == 1: errorlog.warning("There is 1 unused rule") if len(unused_rules) > 1: errorlog.warning("There are %d unused rules", len(unused_rules)) if debug: debuglog.info("") debuglog.info("Terminals, with rules where they appear") debuglog.info("") terms = list(grammar.Terminals) terms.sort() for term in terms: debuglog.info("%-20s : %s", term, " ".join([str(s) for s in grammar.Terminals[term]])) debuglog.info("") debuglog.info("Nonterminals, with rules where they appear") debuglog.info("") nonterms = list(grammar.Nonterminals) nonterms.sort() for nonterm in nonterms: debuglog.info("%-20s : %s", nonterm, " ".join([str(s) for s in grammar.Nonterminals[nonterm]])) debuglog.info("") if check_recursion: unreachable = grammar.find_unreachable() for u in unreachable: errorlog.warning("Symbol '%s' is unreachable",u) infinite = grammar.infinite_cycles() for inf in infinite: errorlog.error("Infinite recursion detected for symbol '%s'", inf) errors = 1 unused_prec = grammar.unused_precedence() for term, assoc in unused_prec: errorlog.error("Precedence rule '%s' defined for unknown symbol '%s'", assoc, term) errors = 1 if errors: raise YaccError("Unable to build parser") # Run the LRGeneratedTable on the grammar if debug: errorlog.debug("Generating %s tables", method) lr = LRGeneratedTable(grammar,method,debuglog) if debug: num_sr = len(lr.sr_conflicts) # Report shift/reduce and reduce/reduce conflicts if num_sr == 1: errorlog.warning("1 shift/reduce conflict") elif num_sr > 1: errorlog.warning("%d shift/reduce conflicts", num_sr) num_rr = len(lr.rr_conflicts) if num_rr == 1: errorlog.warning("1 reduce/reduce conflict") elif num_rr > 1: errorlog.warning("%d reduce/reduce conflicts", num_rr) # Write out conflicts to the output file if debug and (lr.sr_conflicts or lr.rr_conflicts): debuglog.warning("") debuglog.warning("Conflicts:") debuglog.warning("") for state, tok, resolution in lr.sr_conflicts: debuglog.warning("shift/reduce conflict for %s in state %d resolved as %s", tok, state, resolution) already_reported = {} for state, rule, rejected in lr.rr_conflicts: if (state,id(rule),id(rejected)) in already_reported: continue debuglog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule) debuglog.warning("rejected rule (%s) in state %d", rejected,state) errorlog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule) errorlog.warning("rejected rule (%s) in state %d", rejected, state) already_reported[state,id(rule),id(rejected)] = 1 warned_never = [] for state, rule, rejected in lr.rr_conflicts: if not rejected.reduced and (rejected not in warned_never): debuglog.warning("Rule (%s) is never reduced", rejected) errorlog.warning("Rule (%s) is never reduced", rejected) warned_never.append(rejected) # Write the table file if requested if write_tables: lr.write_table(tabmodule,outputdir,signature) # Write a pickled version of the tables if picklefile: lr.pickle_table(picklefile,signature) # Build the parser lr.bind_callables(pinfo.pdict) parser = LRParser(lr,pinfo.error_func) parse = parser.parse return parser ./pyke-1.1.1/pyke/krb_compiler/ply/__init__.py0000644000175000017500000000012211346504630020143 0ustar lambylamby# PLY package # Author: David Beazley (dave@dabeaz.com) __all__ = ['lex','yacc'] ./pyke-1.1.1/pyke/krb_compiler/ply/README0000644000175000017500000002054311346504630016723 0ustar lambylambyPLY (Python Lex-Yacc) Version 3.3 Copyright (C) 2001-2009, David M. Beazley (Dabeaz LLC) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the David Beazley or Dabeaz LLC may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Introduction ============ PLY is a 100% Python implementation of the common parsing tools lex and yacc. Here are a few highlights: - PLY is very closely modeled after traditional lex/yacc. If you know how to use these tools in C, you will find PLY to be similar. - PLY provides *very* extensive error reporting and diagnostic information to assist in parser construction. The original implementation was developed for instructional purposes. As a result, the system tries to identify the most common types of errors made by novice users. - PLY provides full support for empty productions, error recovery, precedence specifiers, and moderately ambiguous grammars. - Parsing is based on LR-parsing which is fast, memory efficient, better suited to large grammars, and which has a number of nice properties when dealing with syntax errors and other parsing problems. Currently, PLY builds its parsing tables using the LALR(1) algorithm used in yacc. - PLY uses Python introspection features to build lexers and parsers. This greatly simplifies the task of parser construction since it reduces the number of files and eliminates the need to run a separate lex/yacc tool before running your program. - PLY can be used to build parsers for "real" programming languages. Although it is not ultra-fast due to its Python implementation, PLY can be used to parse grammars consisting of several hundred rules (as might be found for a language like C). The lexer and LR parser are also reasonably efficient when parsing typically sized programs. People have used PLY to build parsers for C, C++, ADA, and other real programming languages. How to Use ========== PLY consists of two files : lex.py and yacc.py. These are contained within the 'ply' directory which may also be used as a Python package. To use PLY, simply copy the 'ply' directory to your project and import lex and yacc from the associated 'ply' package. For example: import ply.lex as lex import ply.yacc as yacc Alternatively, you can copy just the files lex.py and yacc.py individually and use them as modules. For example: import lex import yacc The file setup.py can be used to install ply using distutils. The file doc/ply.html contains complete documentation on how to use the system. The example directory contains several different examples including a PLY specification for ANSI C as given in K&R 2nd Ed. A simple example is found at the end of this document Requirements ============ PLY requires the use of Python 2.2 or greater. However, you should use the latest Python release if possible. It should work on just about any platform. PLY has been tested with both CPython and Jython. It also seems to work with IronPython. Resources ========= More information about PLY can be obtained on the PLY webpage at: http://www.dabeaz.com/ply For a detailed overview of parsing theory, consult the excellent book "Compilers : Principles, Techniques, and Tools" by Aho, Sethi, and Ullman. The topics found in "Lex & Yacc" by Levine, Mason, and Brown may also be useful. A Google group for PLY can be found at http://groups.google.com/group/ply-hack Acknowledgments =============== A special thanks is in order for all of the students in CS326 who suffered through about 25 different versions of these tools :-). The CHANGES file acknowledges those who have contributed patches. Elias Ioup did the first implementation of LALR(1) parsing in PLY-1.x. Andrew Waters and Markus Schoepflin were instrumental in reporting bugs and testing a revised LALR(1) implementation for PLY-2.0. Special Note for PLY-3.0 ======================== PLY-3.0 the first PLY release to support Python 3. However, backwards compatibility with Python 2.2 is still preserved. PLY provides dual Python 2/3 compatibility by restricting its implementation to a common subset of basic language features. You should not convert PLY using 2to3--it is not necessary and may in fact break the implementation. Example ======= Here is a simple example showing a PLY implementation of a calculator with variables. # ----------------------------------------------------------------------------- # calc.py # # A simple calculator with variables. # ----------------------------------------------------------------------------- tokens = ( 'NAME','NUMBER', 'PLUS','MINUS','TIMES','DIVIDE','EQUALS', 'LPAREN','RPAREN', ) # Tokens t_PLUS = r'\+' t_MINUS = r'-' t_TIMES = r'\*' t_DIVIDE = r'/' t_EQUALS = r'=' t_LPAREN = r'\(' t_RPAREN = r'\)' t_NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' def t_NUMBER(t): r'\d+' t.value = int(t.value) return t # Ignored characters t_ignore = " \t" def t_newline(t): r'\n+' t.lexer.lineno += t.value.count("\n") def t_error(t): print "Illegal character '%s'" % t.value[0] t.lexer.skip(1) # Build the lexer import ply.lex as lex lex.lex() # Precedence rules for the arithmetic operators precedence = ( ('left','PLUS','MINUS'), ('left','TIMES','DIVIDE'), ('right','UMINUS'), ) # dictionary of names (for storing variables) names = { } def p_statement_assign(p): 'statement : NAME EQUALS expression' names[p[1]] = p[3] def p_statement_expr(p): 'statement : expression' print p[1] def p_expression_binop(p): '''expression : expression PLUS expression | expression MINUS expression | expression TIMES expression | expression DIVIDE expression''' if p[2] == '+' : p[0] = p[1] + p[3] elif p[2] == '-': p[0] = p[1] - p[3] elif p[2] == '*': p[0] = p[1] * p[3] elif p[2] == '/': p[0] = p[1] / p[3] def p_expression_uminus(p): 'expression : MINUS expression %prec UMINUS' p[0] = -p[2] def p_expression_group(p): 'expression : LPAREN expression RPAREN' p[0] = p[2] def p_expression_number(p): 'expression : NUMBER' p[0] = p[1] def p_expression_name(p): 'expression : NAME' try: p[0] = names[p[1]] except LookupError: print "Undefined name '%s'" % p[1] p[0] = 0 def p_error(p): print "Syntax error at '%s'" % p.value import ply.yacc as yacc yacc.yacc() while 1: try: s = raw_input('calc > ') except EOFError: break yacc.parse(s) Bug Reports and Patches ======================= My goal with PLY is to simply have a decent lex/yacc implementation for Python. As a general rule, I don't spend huge amounts of time working on it unless I receive very specific bug reports and/or patches to fix problems. I also try to incorporate submitted feature requests and enhancements into each new version. To contact me about bugs and/or new features, please send email to dave@dabeaz.com. In addition there is a Google group for discussing PLY related issues at http://groups.google.com/group/ply-hack -- Dave ./pyke-1.1.1/pyke/krb_compiler/ply/README.pyke0000644000175000017500000000103311346504630017663 0ustar lambylambyA copy of the ply library is provided here so that the pyke code is using a known version of the ply library. The ply library is covered under different copyrights and a different license than Pyke. The license is included in this directory as the file called README, which is copied from the ply distribution. This directory only includes the files needed by Pyke to function. The complete ply library may be found at http://www.dabeaz.com/ply/. Pyke has not made any modification to these files. -bruce frederiksen, April 21, 2009 ./pyke-1.1.1/pyke/krb_compiler/ply/lex.py0000644000175000017500000011744311346504630017213 0ustar lambylamby# ----------------------------------------------------------------------------- # ply: lex.py # # Copyright (C) 2001-2009, # David M. Beazley (Dabeaz LLC) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the David Beazley or Dabeaz LLC may be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- __version__ = "3.3" __tabversion__ = "3.2" # Version of table file used import re, sys, types, copy, os # This tuple contains known string types try: # Python 2.6 StringTypes = (types.StringType, types.UnicodeType) except AttributeError: # Python 3.0 StringTypes = (str, bytes) # Extract the code attribute of a function. Different implementations # are for Python 2/3 compatibility. if sys.version_info[0] < 3: def func_code(f): return f.func_code else: def func_code(f): return f.__code__ # This regular expression is used to match valid token names _is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') # Exception thrown when invalid token encountered and no default error # handler is defined. class LexError(Exception): def __init__(self,message,s): self.args = (message,) self.text = s # Token class. This class is used to represent the tokens produced. class LexToken(object): def __str__(self): return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos) def __repr__(self): return str(self) # This object is a stand-in for a logging object created by the # logging module. class PlyLogger(object): def __init__(self,f): self.f = f def critical(self,msg,*args,**kwargs): self.f.write((msg % args) + "\n") def warning(self,msg,*args,**kwargs): self.f.write("WARNING: "+ (msg % args) + "\n") def error(self,msg,*args,**kwargs): self.f.write("ERROR: " + (msg % args) + "\n") info = critical debug = critical # Null logger is used when no output is generated. Does nothing. class NullLogger(object): def __getattribute__(self,name): return self def __call__(self,*args,**kwargs): return self # ----------------------------------------------------------------------------- # === Lexing Engine === # # The following Lexer class implements the lexer runtime. There are only # a few public methods and attributes: # # input() - Store a new string in the lexer # token() - Get the next token # clone() - Clone the lexer # # lineno - Current line number # lexpos - Current position in the input string # ----------------------------------------------------------------------------- class Lexer: def __init__(self): self.lexre = None # Master regular expression. This is a list of # tuples (re,findex) where re is a compiled # regular expression and findex is a list # mapping regex group numbers to rules self.lexretext = None # Current regular expression strings self.lexstatere = {} # Dictionary mapping lexer states to master regexs self.lexstateretext = {} # Dictionary mapping lexer states to regex strings self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names self.lexstate = "INITIAL" # Current lexer state self.lexstatestack = [] # Stack of lexer states self.lexstateinfo = None # State information self.lexstateignore = {} # Dictionary of ignored characters for each state self.lexstateerrorf = {} # Dictionary of error functions for each state self.lexreflags = 0 # Optional re compile flags self.lexdata = None # Actual input data (as a string) self.lexpos = 0 # Current position in input text self.lexlen = 0 # Length of the input text self.lexerrorf = None # Error rule (if any) self.lextokens = None # List of valid tokens self.lexignore = "" # Ignored characters self.lexliterals = "" # Literal characters that can be passed through self.lexmodule = None # Module self.lineno = 1 # Current line number self.lexoptimize = 0 # Optimized mode def clone(self,object=None): c = copy.copy(self) # If the object parameter has been supplied, it means we are attaching the # lexer to a new object. In this case, we have to rebind all methods in # the lexstatere and lexstateerrorf tables. if object: newtab = { } for key, ritem in self.lexstatere.items(): newre = [] for cre, findex in ritem: newfindex = [] for f in findex: if not f or not f[0]: newfindex.append(f) continue newfindex.append((getattr(object,f[0].__name__),f[1])) newre.append((cre,newfindex)) newtab[key] = newre c.lexstatere = newtab c.lexstateerrorf = { } for key, ef in self.lexstateerrorf.items(): c.lexstateerrorf[key] = getattr(object,ef.__name__) c.lexmodule = object return c # ------------------------------------------------------------ # writetab() - Write lexer information to a table file # ------------------------------------------------------------ def writetab(self,tabfile,outputdir=""): if isinstance(tabfile,types.ModuleType): return basetabfilename = tabfile.split(".")[-1] filename = os.path.join(outputdir,basetabfilename)+".py" tf = open(filename,"w") tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__)) tf.write("_tabversion = %s\n" % repr(__version__)) tf.write("_lextokens = %s\n" % repr(self.lextokens)) tf.write("_lexreflags = %s\n" % repr(self.lexreflags)) tf.write("_lexliterals = %s\n" % repr(self.lexliterals)) tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo)) tabre = { } # Collect all functions in the initial state initial = self.lexstatere["INITIAL"] initialfuncs = [] for part in initial: for f in part[1]: if f and f[0]: initialfuncs.append(f) for key, lre in self.lexstatere.items(): titem = [] for i in range(len(lre)): titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1],self.lexstaterenames[key][i]))) tabre[key] = titem tf.write("_lexstatere = %s\n" % repr(tabre)) tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore)) taberr = { } for key, ef in self.lexstateerrorf.items(): if ef: taberr[key] = ef.__name__ else: taberr[key] = None tf.write("_lexstateerrorf = %s\n" % repr(taberr)) tf.close() # ------------------------------------------------------------ # readtab() - Read lexer information from a tab file # ------------------------------------------------------------ def readtab(self,tabfile,fdict): if isinstance(tabfile,types.ModuleType): lextab = tabfile else: if sys.version_info[0] < 3: exec("import %s as lextab" % tabfile) else: env = { } exec("import %s as lextab" % tabfile, env,env) lextab = env['lextab'] if getattr(lextab,"_tabversion","0.0") != __version__: raise ImportError("Inconsistent PLY version") self.lextokens = lextab._lextokens self.lexreflags = lextab._lexreflags self.lexliterals = lextab._lexliterals self.lexstateinfo = lextab._lexstateinfo self.lexstateignore = lextab._lexstateignore self.lexstatere = { } self.lexstateretext = { } for key,lre in lextab._lexstatere.items(): titem = [] txtitem = [] for i in range(len(lre)): titem.append((re.compile(lre[i][0],lextab._lexreflags | re.VERBOSE),_names_to_funcs(lre[i][1],fdict))) txtitem.append(lre[i][0]) self.lexstatere[key] = titem self.lexstateretext[key] = txtitem self.lexstateerrorf = { } for key,ef in lextab._lexstateerrorf.items(): self.lexstateerrorf[key] = fdict[ef] self.begin('INITIAL') # ------------------------------------------------------------ # input() - Push a new string into the lexer # ------------------------------------------------------------ def input(self,s): # Pull off the first character to see if s looks like a string c = s[:1] if not isinstance(c,StringTypes): raise ValueError("Expected a string") self.lexdata = s self.lexpos = 0 self.lexlen = len(s) # ------------------------------------------------------------ # begin() - Changes the lexing state # ------------------------------------------------------------ def begin(self,state): if not state in self.lexstatere: raise ValueError("Undefined state") self.lexre = self.lexstatere[state] self.lexretext = self.lexstateretext[state] self.lexignore = self.lexstateignore.get(state,"") self.lexerrorf = self.lexstateerrorf.get(state,None) self.lexstate = state # ------------------------------------------------------------ # push_state() - Changes the lexing state and saves old on stack # ------------------------------------------------------------ def push_state(self,state): self.lexstatestack.append(self.lexstate) self.begin(state) # ------------------------------------------------------------ # pop_state() - Restores the previous state # ------------------------------------------------------------ def pop_state(self): self.begin(self.lexstatestack.pop()) # ------------------------------------------------------------ # current_state() - Returns the current lexing state # ------------------------------------------------------------ def current_state(self): return self.lexstate # ------------------------------------------------------------ # skip() - Skip ahead n characters # ------------------------------------------------------------ def skip(self,n): self.lexpos += n # ------------------------------------------------------------ # opttoken() - Return the next token from the Lexer # # Note: This function has been carefully implemented to be as fast # as possible. Don't make changes unless you really know what # you are doing # ------------------------------------------------------------ def token(self): # Make local copies of frequently referenced attributes lexpos = self.lexpos lexlen = self.lexlen lexignore = self.lexignore lexdata = self.lexdata while lexpos < lexlen: # This code provides some short-circuit code for whitespace, tabs, and other ignored characters if lexdata[lexpos] in lexignore: lexpos += 1 continue # Look for a regular expression match for lexre,lexindexfunc in self.lexre: m = lexre.match(lexdata,lexpos) if not m: continue # Create a token for return tok = LexToken() tok.value = m.group() tok.lineno = self.lineno tok.lexpos = lexpos i = m.lastindex func,tok.type = lexindexfunc[i] if not func: # If no token type was set, it's an ignored token if tok.type: self.lexpos = m.end() return tok else: lexpos = m.end() break lexpos = m.end() # If token is processed by a function, call it tok.lexer = self # Set additional attributes useful in token rules self.lexmatch = m self.lexpos = lexpos newtok = func(tok) # Every function must return a token, if nothing, we just move to next token if not newtok: lexpos = self.lexpos # This is here in case user has updated lexpos. lexignore = self.lexignore # This is here in case there was a state change break # Verify type of the token. If not in the token map, raise an error if not self.lexoptimize: if not newtok.type in self.lextokens: raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( func_code(func).co_filename, func_code(func).co_firstlineno, func.__name__, newtok.type),lexdata[lexpos:]) return newtok else: # No match, see if in literals if lexdata[lexpos] in self.lexliterals: tok = LexToken() tok.value = lexdata[lexpos] tok.lineno = self.lineno tok.type = tok.value tok.lexpos = lexpos self.lexpos = lexpos + 1 return tok # No match. Call t_error() if defined. if self.lexerrorf: tok = LexToken() tok.value = self.lexdata[lexpos:] tok.lineno = self.lineno tok.type = "error" tok.lexer = self tok.lexpos = lexpos self.lexpos = lexpos newtok = self.lexerrorf(tok) if lexpos == self.lexpos: # Error method didn't change text position at all. This is an error. raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) lexpos = self.lexpos if not newtok: continue return newtok self.lexpos = lexpos raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:]) self.lexpos = lexpos + 1 if self.lexdata is None: raise RuntimeError("No input string given with input()") return None # Iterator interface def __iter__(self): return self def next(self): t = self.token() if t is None: raise StopIteration return t __next__ = next # ----------------------------------------------------------------------------- # ==== Lex Builder === # # The functions and classes below are used to collect lexing information # and build a Lexer object from it. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # get_caller_module_dict() # # This function returns a dictionary containing all of the symbols defined within # a caller further down the call stack. This is used to get the environment # associated with the yacc() call if none was provided. # ----------------------------------------------------------------------------- def get_caller_module_dict(levels): try: raise RuntimeError except RuntimeError: e,b,t = sys.exc_info() f = t.tb_frame while levels > 0: f = f.f_back levels -= 1 ldict = f.f_globals.copy() if f.f_globals != f.f_locals: ldict.update(f.f_locals) return ldict # ----------------------------------------------------------------------------- # _funcs_to_names() # # Given a list of regular expression functions, this converts it to a list # suitable for output to a table file # ----------------------------------------------------------------------------- def _funcs_to_names(funclist,namelist): result = [] for f,name in zip(funclist,namelist): if f and f[0]: result.append((name, f[1])) else: result.append(f) return result # ----------------------------------------------------------------------------- # _names_to_funcs() # # Given a list of regular expression function names, this converts it back to # functions. # ----------------------------------------------------------------------------- def _names_to_funcs(namelist,fdict): result = [] for n in namelist: if n and n[0]: result.append((fdict[n[0]],n[1])) else: result.append(n) return result # ----------------------------------------------------------------------------- # _form_master_re() # # This function takes a list of all of the regex components and attempts to # form the master regular expression. Given limitations in the Python re # module, it may be necessary to break the master regex into separate expressions. # ----------------------------------------------------------------------------- def _form_master_re(relist,reflags,ldict,toknames): if not relist: return [] regex = "|".join(relist) try: lexre = re.compile(regex,re.VERBOSE | reflags) # Build the index to function map for the matching engine lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1) lexindexnames = lexindexfunc[:] for f,i in lexre.groupindex.items(): handle = ldict.get(f,None) if type(handle) in (types.FunctionType, types.MethodType): lexindexfunc[i] = (handle,toknames[f]) lexindexnames[i] = f elif handle is not None: lexindexnames[i] = f if f.find("ignore_") > 0: lexindexfunc[i] = (None,None) else: lexindexfunc[i] = (None, toknames[f]) return [(lexre,lexindexfunc)],[regex],[lexindexnames] except Exception: m = int(len(relist)/2) if m == 0: m = 1 llist, lre, lnames = _form_master_re(relist[:m],reflags,ldict,toknames) rlist, rre, rnames = _form_master_re(relist[m:],reflags,ldict,toknames) return llist+rlist, lre+rre, lnames+rnames # ----------------------------------------------------------------------------- # def _statetoken(s,names) # # Given a declaration name s of the form "t_" and a dictionary whose keys are # state names, this function returns a tuple (states,tokenname) where states # is a tuple of state names and tokenname is the name of the token. For example, # calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') # ----------------------------------------------------------------------------- def _statetoken(s,names): nonstate = 1 parts = s.split("_") for i in range(1,len(parts)): if not parts[i] in names and parts[i] != 'ANY': break if i > 1: states = tuple(parts[1:i]) else: states = ('INITIAL',) if 'ANY' in states: states = tuple(names) tokenname = "_".join(parts[i:]) return (states,tokenname) # ----------------------------------------------------------------------------- # LexerReflect() # # This class represents information needed to build a lexer as extracted from a # user's input file. # ----------------------------------------------------------------------------- class LexerReflect(object): def __init__(self,ldict,log=None,reflags=0): self.ldict = ldict self.error_func = None self.tokens = [] self.reflags = reflags self.stateinfo = { 'INITIAL' : 'inclusive'} self.files = {} self.error = 0 if log is None: self.log = PlyLogger(sys.stderr) else: self.log = log # Get all of the basic information def get_all(self): self.get_tokens() self.get_literals() self.get_states() self.get_rules() # Validate all of the information def validate_all(self): self.validate_tokens() self.validate_literals() self.validate_rules() return self.error # Get the tokens map def get_tokens(self): tokens = self.ldict.get("tokens",None) if not tokens: self.log.error("No token list is defined") self.error = 1 return if not isinstance(tokens,(list, tuple)): self.log.error("tokens must be a list or tuple") self.error = 1 return if not tokens: self.log.error("tokens is empty") self.error = 1 return self.tokens = tokens # Validate the tokens def validate_tokens(self): terminals = {} for n in self.tokens: if not _is_identifier.match(n): self.log.error("Bad token name '%s'",n) self.error = 1 if n in terminals: self.log.warning("Token '%s' multiply defined", n) terminals[n] = 1 # Get the literals specifier def get_literals(self): self.literals = self.ldict.get("literals","") # Validate literals def validate_literals(self): try: for c in self.literals: if not isinstance(c,StringTypes) or len(c) > 1: self.log.error("Invalid literal %s. Must be a single character", repr(c)) self.error = 1 continue except TypeError: self.log.error("Invalid literals specification. literals must be a sequence of characters") self.error = 1 def get_states(self): self.states = self.ldict.get("states",None) # Build statemap if self.states: if not isinstance(self.states,(tuple,list)): self.log.error("states must be defined as a tuple or list") self.error = 1 else: for s in self.states: if not isinstance(s,tuple) or len(s) != 2: self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')",repr(s)) self.error = 1 continue name, statetype = s if not isinstance(name,StringTypes): self.log.error("State name %s must be a string", repr(name)) self.error = 1 continue if not (statetype == 'inclusive' or statetype == 'exclusive'): self.log.error("State type for state %s must be 'inclusive' or 'exclusive'",name) self.error = 1 continue if name in self.stateinfo: self.log.error("State '%s' already defined",name) self.error = 1 continue self.stateinfo[name] = statetype # Get all of the symbols with a t_ prefix and sort them into various # categories (functions, strings, error functions, and ignore characters) def get_rules(self): tsymbols = [f for f in self.ldict if f[:2] == 't_' ] # Now build up a list of functions and a list of strings self.toknames = { } # Mapping of symbols to token names self.funcsym = { } # Symbols defined as functions self.strsym = { } # Symbols defined as strings self.ignore = { } # Ignore strings by state self.errorf = { } # Error functions by state for s in self.stateinfo: self.funcsym[s] = [] self.strsym[s] = [] if len(tsymbols) == 0: self.log.error("No rules of the form t_rulename are defined") self.error = 1 return for f in tsymbols: t = self.ldict[f] states, tokname = _statetoken(f,self.stateinfo) self.toknames[f] = tokname if hasattr(t,"__call__"): if tokname == 'error': for s in states: self.errorf[s] = t elif tokname == 'ignore': line = func_code(t).co_firstlineno file = func_code(t).co_filename self.log.error("%s:%d: Rule '%s' must be defined as a string",file,line,t.__name__) self.error = 1 else: for s in states: self.funcsym[s].append((f,t)) elif isinstance(t, StringTypes): if tokname == 'ignore': for s in states: self.ignore[s] = t if "\\" in t: self.log.warning("%s contains a literal backslash '\\'",f) elif tokname == 'error': self.log.error("Rule '%s' must be defined as a function", f) self.error = 1 else: for s in states: self.strsym[s].append((f,t)) else: self.log.error("%s not defined as a function or string", f) self.error = 1 # Sort the functions by line number for f in self.funcsym.values(): if sys.version_info[0] < 3: f.sort(lambda x,y: cmp(func_code(x[1]).co_firstlineno,func_code(y[1]).co_firstlineno)) else: # Python 3.0 f.sort(key=lambda x: func_code(x[1]).co_firstlineno) # Sort the strings by regular expression length for s in self.strsym.values(): if sys.version_info[0] < 3: s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1]))) else: # Python 3.0 s.sort(key=lambda x: len(x[1]),reverse=True) # Validate all of the t_rules collected def validate_rules(self): for state in self.stateinfo: # Validate all rules defined by functions for fname, f in self.funcsym[state]: line = func_code(f).co_firstlineno file = func_code(f).co_filename self.files[file] = 1 tokname = self.toknames[fname] if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 nargs = func_code(f).co_argcount if nargs > reqargs: self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) self.error = 1 continue if nargs < reqargs: self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) self.error = 1 continue if not f.__doc__: self.log.error("%s:%d: No regular expression defined for rule '%s'",file,line,f.__name__) self.error = 1 continue try: c = re.compile("(?P<%s>%s)" % (fname,f.__doc__), re.VERBOSE | self.reflags) if c.match(""): self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file,line,f.__name__) self.error = 1 except re.error: _etype, e, _etrace = sys.exc_info() self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file,line,f.__name__,e) if '#' in f.__doc__: self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'",file,line, f.__name__) self.error = 1 # Validate all rules defined by strings for name,r in self.strsym[state]: tokname = self.toknames[name] if tokname == 'error': self.log.error("Rule '%s' must be defined as a function", name) self.error = 1 continue if not tokname in self.tokens and tokname.find("ignore_") < 0: self.log.error("Rule '%s' defined for an unspecified token %s",name,tokname) self.error = 1 continue try: c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | self.reflags) if (c.match("")): self.log.error("Regular expression for rule '%s' matches empty string",name) self.error = 1 except re.error: _etype, e, _etrace = sys.exc_info() self.log.error("Invalid regular expression for rule '%s'. %s",name,e) if '#' in r: self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'",name) self.error = 1 if not self.funcsym[state] and not self.strsym[state]: self.log.error("No rules defined for state '%s'",state) self.error = 1 # Validate the error function efunc = self.errorf.get(state,None) if efunc: f = efunc line = func_code(f).co_firstlineno file = func_code(f).co_filename self.files[file] = 1 if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 nargs = func_code(f).co_argcount if nargs > reqargs: self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) self.error = 1 if nargs < reqargs: self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) self.error = 1 for f in self.files: self.validate_file(f) # ----------------------------------------------------------------------------- # validate_file() # # This checks to see if there are duplicated t_rulename() functions or strings # in the parser input file. This is done using a simple regular expression # match on each line in the given file. # ----------------------------------------------------------------------------- def validate_file(self,filename): import os.path base,ext = os.path.splitext(filename) if ext != '.py': return # No idea what the file is. Return OK try: f = open(filename) lines = f.readlines() f.close() except IOError: return # Couldn't find the file. Don't worry about it fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') counthash = { } linen = 1 for l in lines: m = fre.match(l) if not m: m = sre.match(l) if m: name = m.group(1) prev = counthash.get(name) if not prev: counthash[name] = linen else: self.log.error("%s:%d: Rule %s redefined. Previously defined on line %d",filename,linen,name,prev) self.error = 1 linen += 1 # ----------------------------------------------------------------------------- # lex(module) # # Build all of the regular expression rules from definitions in the supplied module # ----------------------------------------------------------------------------- def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir="", debuglog=None, errorlog=None): global lexer ldict = None stateinfo = { 'INITIAL' : 'inclusive'} lexobj = Lexer() lexobj.lexoptimize = optimize global token,input if errorlog is None: errorlog = PlyLogger(sys.stderr) if debug: if debuglog is None: debuglog = PlyLogger(sys.stderr) # Get the module dictionary used for the lexer if object: module = object if module: _items = [(k,getattr(module,k)) for k in dir(module)] ldict = dict(_items) else: ldict = get_caller_module_dict(2) # Collect parser information from the dictionary linfo = LexerReflect(ldict,log=errorlog,reflags=reflags) linfo.get_all() if not optimize: if linfo.validate_all(): raise SyntaxError("Can't build lexer") if optimize and lextab: try: lexobj.readtab(lextab,ldict) token = lexobj.token input = lexobj.input lexer = lexobj return lexobj except ImportError: pass # Dump some basic debugging information if debug: debuglog.info("lex: tokens = %r", linfo.tokens) debuglog.info("lex: literals = %r", linfo.literals) debuglog.info("lex: states = %r", linfo.stateinfo) # Build a dictionary of valid token names lexobj.lextokens = { } for n in linfo.tokens: lexobj.lextokens[n] = 1 # Get literals specification if isinstance(linfo.literals,(list,tuple)): lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals) else: lexobj.lexliterals = linfo.literals # Get the stateinfo dictionary stateinfo = linfo.stateinfo regexs = { } # Build the master regular expressions for state in stateinfo: regex_list = [] # Add rules defined by functions first for fname, f in linfo.funcsym[state]: line = func_code(f).co_firstlineno file = func_code(f).co_filename regex_list.append("(?P<%s>%s)" % (fname,f.__doc__)) if debug: debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",fname,f.__doc__, state) # Now add all of the simple rules for name,r in linfo.strsym[state]: regex_list.append("(?P<%s>%s)" % (name,r)) if debug: debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",name,r, state) regexs[state] = regex_list # Build the master regular expressions if debug: debuglog.info("lex: ==== MASTER REGEXS FOLLOW ====") for state in regexs: lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,linfo.toknames) lexobj.lexstatere[state] = lexre lexobj.lexstateretext[state] = re_text lexobj.lexstaterenames[state] = re_names if debug: for i in range(len(re_text)): debuglog.info("lex: state '%s' : regex[%d] = '%s'",state, i, re_text[i]) # For inclusive states, we need to add the regular expressions from the INITIAL state for state,stype in stateinfo.items(): if state != "INITIAL" and stype == 'inclusive': lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) lexobj.lexstateinfo = stateinfo lexobj.lexre = lexobj.lexstatere["INITIAL"] lexobj.lexretext = lexobj.lexstateretext["INITIAL"] lexobj.lexreflags = reflags # Set up ignore variables lexobj.lexstateignore = linfo.ignore lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","") # Set up error functions lexobj.lexstateerrorf = linfo.errorf lexobj.lexerrorf = linfo.errorf.get("INITIAL",None) if not lexobj.lexerrorf: errorlog.warning("No t_error rule is defined") # Check state information for ignore and error rules for s,stype in stateinfo.items(): if stype == 'exclusive': if not s in linfo.errorf: errorlog.warning("No error rule is defined for exclusive state '%s'", s) if not s in linfo.ignore and lexobj.lexignore: errorlog.warning("No ignore rule is defined for exclusive state '%s'", s) elif stype == 'inclusive': if not s in linfo.errorf: linfo.errorf[s] = linfo.errorf.get("INITIAL",None) if not s in linfo.ignore: linfo.ignore[s] = linfo.ignore.get("INITIAL","") # Create global versions of the token() and input() functions token = lexobj.token input = lexobj.input lexer = lexobj # If in optimize mode, we write the lextab if lextab and optimize: lexobj.writetab(lextab,outputdir) return lexobj # ----------------------------------------------------------------------------- # runmain() # # This runs the lexer as a main program # ----------------------------------------------------------------------------- def runmain(lexer=None,data=None): if not data: try: filename = sys.argv[1] f = open(filename) data = f.read() f.close() except IndexError: sys.stdout.write("Reading from standard input (type EOF to end):\n") data = sys.stdin.read() if lexer: _input = lexer.input else: _input = input _input(data) if lexer: _token = lexer.token else: _token = token while 1: tok = _token() if not tok: break sys.stdout.write("(%s,%r,%d,%d)\n" % (tok.type, tok.value, tok.lineno,tok.lexpos)) # ----------------------------------------------------------------------------- # @TOKEN(regex) # # This decorator function can be used to set the regex expression on a function # when its docstring might need to be set in an alternative way # ----------------------------------------------------------------------------- def TOKEN(r): def set_doc(f): if hasattr(r,"__call__"): f.__doc__ = r.__doc__ else: f.__doc__ = r return f return set_doc # Alternative spelling of the TOKEN decorator Token = TOKEN ./pyke-1.1.1/pyke/krb_compiler/scanner_tables.py0000644000175000017500000000726011365364034020600 0ustar lambylamby# pyke.krb_compiler.scanner_tables.py. This file automatically created by PLY (version 3.3). Don't edit! _tabversion = '3.3' _lextokens = {'AS_TOK': 1, 'LP_TOK': 1, 'FOREACH_TOK': 1, 'TAKING_TOK': 1, 'PYTHON_TOK': 1, 'NUMBER_TOK': 1, 'DEINDENT_TOK': 1, 'STEP_TOK': 1, 'EXTENDING_TOK': 1, 'ASSERT_TOK': 1, 'ANONYMOUS_VAR_TOK': 1, 'RP_TOK': 1, 'NOTANY_TOK': 1, 'WITHOUT_TOK': 1, 'INDENT_TOK': 1, 'BC_EXTRAS_TOK': 1, 'CODE_TOK': 1, 'REQUIRE_TOK': 1, 'IN_TOK': 1, 'TRUE_TOK': 1, 'PLAN_EXTRAS_TOK': 1, 'NOT_NL_TOK': 1, 'NONE_TOK': 1, 'WHEN_TOK': 1, 'WITH_TOK': 1, 'FALSE_TOK': 1, 'PATTERN_VAR_TOK': 1, 'CHECK_TOK': 1, 'IDENTIFIER_TOK': 1, 'USE_TOK': 1, 'FC_EXTRAS_TOK': 1, 'STRING_TOK': 1, 'FIRST_TOK': 1, 'NL_TOK': 1, 'FORALL_TOK': 1} _lexreflags = 0 _lexliterals = '*:,!.=' _lexstateinfo = {'code': 'exclusive', 'checknl': 'exclusive', 'INITIAL': 'inclusive', 'indent': 'exclusive'} _lexstatere = {'checknl': [('(?P(\\#.*)?(\\r)?\\n)|(?P[^\\#\\r\\n])', [None, ('t_checknl_nl', 'nl'), None, None, ('t_checknl_other', 'other')])], 'code': [('(?P\'\'\'([^\\\\]|\\\\.)*?\'\'\'|"""([^\\\\]|\\\\.)*?"""|\'([^\'\\\\\\n\\r]|\\\\.|\\\\(\\r)?\\n)*?\'|"([^"\\\\\\n\\r]|\\\\.|\\\\(\\r)?\\n)*?")|(?P[ \\t\\f\\r]*\\#.*)|(?P\\$\\$)|(?P\\$[a-zA-Z_][a-zA-Z0-9_]*\\b)|(?P\\\\(\\r)?\\n)|(?P[{([])|(?P[]})])|(?P[0-9a-zA-Z_]+)|(?P[ \\t]+)|(?P[^][(){}$\\\\\'"\\r\\n0-9a-zA-Z_ \\t]+)|(?P(\\r)?\\n([ \\t]*(\\#.*)?(\\r)?\\n)*[ \\t]*)', [None, ('t_code_string', 'string'), None, None, None, None, None, None, ('t_code_comment', 'comment'), ('t_code_plan', 'plan'), ('t_code_pattern_var', 'pattern_var'), ('t_code_continuation', 'continuation'), None, ('t_code_open', 'open'), ('t_code_close', 'close'), ('t_code_symbol', 'symbol'), ('t_code_space', 'space'), ('t_code_other', 'other'), ('t_code_NL_TOK', 'NL_TOK')])], 'INITIAL': [('(?P\\\\(\\r)?\\n)|(?P(\\r)?\\n([ \\t]*(\\#.*)?(\\r)?\\n)*)|(?P[uU]?[rR]?\'\'\'([^\\\\]|\\\\.)*?\'\'\')|(?P[uU]?[rR]?"""([^\\\\]|\\\\.)*?""")|(?P[uU]?[rR]?\'([^\'\\\\\\n\\r]|\\\\.|\\\\(\\r)?\\n)*?\')|(?P[uU]?[rR]?"([^"\\\\\\n\\r]|\\\\.|\\\\(\\r)?\\n)*?")|(?P\\$_([a-zA-Z_][a-zA-Z0-9_]*)?)|(?P\\$[a-zA-Z][a-zA-Z0-9_]*)|(?P[a-zA-Z_][a-zA-Z0-9_]*)|(?P[-+]?([0-9]+(\\.[0-9]*([eE][-+]?[0-9]+)?|[eE][-+]?[0-9]+)|\\.[0-9]+([eE][-+]?[0-9]+)?))|(?P[-+]?0[xX][0-9a-fA-F]+)|(?P[-+]?0[0-7]*)|(?P[-+]?[1-9][0-9]*)|(?P\\[)|(?P\\{)|(?P\\()|(?P\\])|(?P\\})|(?P\\))|(?P\\#.*)', [None, ('t_continuation', 'continuation'), None, ('t_NL_TOK', 'NL_TOK'), None, None, None, None, ('t_tsqstring', 'tsqstring'), None, ('t_tdqstring', 'tdqstring'), None, ('t_sqstring', 'sqstring'), None, None, ('t_dqstring', 'dqstring'), None, None, ('t_ANONYMOUS_VAR_TOK', 'ANONYMOUS_VAR_TOK'), None, ('t_PATTERN_VAR_TOK', 'PATTERN_VAR_TOK'), ('t_IDENTIFIER_TOK', 'IDENTIFIER_TOK'), ('t_float', 'float'), None, None, None, None, ('t_hexint', 'hexint'), ('t_octalint', 'octalint'), ('t_int', 'int'), ('t_LB_TOK', 'LB_TOK'), ('t_LC_TOK', 'LC_TOK'), ('t_LP_TOK', 'LP_TOK'), ('t_RB_TOK', 'RB_TOK'), ('t_RC_TOK', 'RC_TOK'), ('t_RP_TOK', 'RP_TOK'), (None, None)])], 'indent': [('(?P\\n[ \\t]*)', [None, ('t_indent_sp', 'sp')])]} _lexstateignore = {'code': '', 'checknl': ' \t', 'INITIAL': ' \t', 'indent': ''} _lexstateerrorf = {'checknl': 't_ANY_error', 'code': 't_ANY_error', 'INITIAL': 't_ANY_error', 'indent': 't_ANY_error'} ./pyke-1.1.1/pyke/krb_compiler/generated_files.tst0000644000175000017500000000241611346504630021112 0ustar lambylambyThis tests to make sure that all of the files generated by PLY are up to date. >>> import os.path >>> def check(src, dest): ... assert os.path.getmtime(src) <= os.path.getmtime(dest), \ ... dest + " out of date" >>> check('scanner.py', 'scanner_tables.py') >>> check('scanner.py', 'krbparser_tables.py') >>> check('krbparser.py', 'krbparser_tables.py') >>> check('scanner.py', 'kfbparser_tables.py') >>> check('kfbparser.py', 'kfbparser_tables.py') Check that compiler_bc.py is up to date: >>> if not os.path.isdir('compiled_krb'): os.mkdir('compiled_krb') >>> from pyke import krb_compiler >>> krb_compiler.compile_krb('compiler', 'compiled_krb', 'compiled_krb', ... 'compiler.krb') ['compiler_bc.py'] >>> from __future__ import with_statement >>> import re >>> del_krb_filename = re.compile(r"^Krb_filename = .*") >>> with open('compiler_bc.py') as f: ... f1_text = del_krb_filename.sub('', f.read(), 1) >>> with open('compiled_krb/compiler_bc.py') as f: ... f2_text = del_krb_filename.sub('', f.read(), 1) >>> assert f1_text == f2_text, "krb_compiler/compiler.krb not compiled" >>> if f1_text == f2_text: os.remove('compiled_krb/compiler_bc.py') ./pyke-1.1.1/pyke/krb_compiler/kqb_parser.py0000644000175000017500000005061611353376030017745 0ustar lambylamby# $Id: kqb_parser.py 49507964ae64 2010-03-27 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import with_statement from string import Template import re import os.path from pyke import question_base from pyke import user_question from pyke import qa_helpers from pyke.krb_compiler import scanner class kqb_parser(object): blank_line = re.compile(ur'(\s*#)|(\s*$)', re.UNICODE) tokenizer = re.compile(ur''' [ \t\f\r\v]* (?: \#.* )? (?: (["']) (?P (?: \\. | .)*? ) \1 | # this must be first! [[] (?P (?: \\. | .)*? ) []] | [$] (?P [a-zA-Z_] [a-zA-Z_0-9]* ) | / (?P (?: \\. | .)*? ) / | /// (?P (?: \\. | \n | .)*? ) /// | # FIX: this won't work! (?P True | False | None ) | (?P [a-zA-Z_] [a-zA-Z_0-9]* ) | (?P (?: \d+ (?: \.\d* )? | \.\d+ ) (?: [eE][-+]?\d+ )? ) | (?P [(] ) | (?P [)] ) | (?P , ) | (?P [|] ) | (?P ! ) | (?P = ) | (?P - ) | (?P : ) ) [ \t\f\r\v]* (?: \#.* )? ''', re.UNICODE | re.VERBOSE) pushed_token = None def __init__(self, f): # f needs readline() and name. self.f = f self.lineno = 0 self.line = '' self.column = 0 self.eof = False def readline(self): r''' >>> from StringIO import StringIO >>> p = kqb_parser(StringIO(""" ... line 1 ... # this should be ignored ... line 2 ... ... line 3 ... """)) >>> p.readline() >>> p.indent, p.line, p.lineno, p.column (0, 'line 1', 2, 0) >>> p.readline() >>> p.indent, p.line, p.lineno, p.column (1, ' line 2', 4, 1) >>> p.readline() >>> p.indent, p.line, p.lineno, p.column (2, ' line 3', 6, 2) >>> p.eof False >>> p.readline() >>> p.eof True ''' while True: line = self.f.readline() if line == '': self.eof = True break self.lineno += 1 line = line.rstrip('\n') if not self.blank_line.match(line): self.indent, self.column = scanner.count_indent(line) self.line = line break def SyntaxError(self, msg, last_token=True): if last_token: raise SyntaxError(msg, (self.f.name, self.last_lineno, self.last_column + 1, self.last_line)) raise SyntaxError(msg, (self.f.name, self.lineno, self.column + 1, self.line)) def push_token(self): #print "push_token:", self.last_token # FIX self.pushed_token = self.last_token self.pushed_indent = self.indent self.pushed_column = self.column self.indent = self.last_indent self.column = self.last_column def get_token(self, check_token=None): r''' >>> from StringIO import StringIO >>> f = StringIO(r""" ... line 1=2.5: ( /\s* /, $foo) # comment ... ,|!-True "hi\n" [mom] ... """) >>> f.name = 'StringIO' >>> p = kqb_parser(f) >>> p.get_token() ('id', 'line') >>> p.get_token() ('number', 1) >>> p.get_token() ('equal', None) >>> p.get_token() ('number', 2.5) >>> p.get_token() ('colon', None) >>> p.get_token() ('lparen', None) >>> p.get_token() ('regexp', '\\s* ') >>> p.get_token() ('comma', None) >>> p.get_token() ('param', 'foo') >>> p.get_token() ('rparen', None) >>> p.get_token() ('comma', None) >>> p.get_token() ('bar', None) >>> p.get_token() ('bang', None) >>> p.get_token() ('hyphen', None) >>> p.get_token() ('const', True) >>> p.get_token() ('str', 'hi\n') >>> p.get_token() ('prompt', 'mom') >>> p.get_token() (None, None) ''' if self.pushed_token: ans = self.pushed_token self.indent = self.pushed_indent self.column = self.pushed_column self.pushed_token = self.pushed_column = self.pushed_indent = None if check_token and check_token != ans[0]: self.SyntaxError("expected %s, got %s" % (check_token, ans[0])) #print "get_token: returning pushed_token", ans # FIX return ans if self.column < len(self.line): self.skip_spaces() if self.column >= len(self.line): self.readline() if self.eof: self.last_token = None, None #print "get_token: returning EOF" # FIX return self.last_token self.last_line = self.line self.last_lineno = self.lineno self.last_column = self.column self.last_indent = self.indent match = self.tokenizer.match(self.line, self.column) if not match: self.SyntaxError("Scanner error: no legal token") token = match.lastgroup chars = match.group(token) end = match.end() indent, ignore = scanner.count_indent(self.line[self.column:end], True) self.indent += indent self.column = end if token == 'str' or token == 'prompt': value = scanner.unescape(chars) elif token == 'const': value = eval(chars) elif token == 'number': try: value = int(chars) except ValueError: value = float(chars) elif token == 'param' or token == 'id': value = chars elif token == 'regexp1' or token == 'regexp2': # FIX token = 'regexp' value = chars self.lineno += chars.count('\n') last_nl = chars.rfind('\n') if last_nl >= 0: self.column = len(chars) - last_nl + 4 else: value = None if check_token and check_token != token: self.SyntaxError("expected %s, got %s" % (check_token, token)) self.last_token = str(token), value #print "get_token: returning", self.last_token # FIX return self.last_token def get_block_string(self, stop=None, hanging=False, ending_newlines=False): r''' >>> from StringIO import StringIO >>> f = StringIO(r""" ... line 1 # comment ... more stuff ... last line ... blah hanging line 1 ... ... line 2 ... indented ... last line ... ! the end ! ... """) >>> f.name = 'StringIO' >>> p = kqb_parser(f) >>> p.get_block_string() u'line 1 # comment\n more stuff\nlast line' >>> p.column = 4 >>> p.indent = 4 >>> p.get_block_string('!', True) u'hanging line 1\n\nline 2\n indented\nlast line' >>> f = StringIO(r""" ... ! line 1 # comment ... more stuff ... last line ... blah ... """) >>> f.name = 'StringIO' >>> p = kqb_parser(f) >>> p.readline() >>> p.get_token('bang') ('bang', None) >>> p.get_block_string(hanging=True) u'line 1 # comment\n more stuff\nlast line' ''' if hanging: indent, more_chars = \ scanner.count_indent(self.line[self.column:]) self.indent += indent self.column += more_chars else: self.readline() indent = self.indent if self.eof: self.SyntaxError("expected block string, got EOF", False) rest_line = self.line[self.column:] if self.blank_line.match(rest_line): ans = [] else: ans = [rest_line] while not self.eof: last_lineno = self.lineno self.readline() if ending_newlines: for i in range(self.lineno - last_lineno - 1): ans.append('') if self.eof or self.indent < indent or \ stop and self.line[self.column:].startswith(stop): break if not ending_newlines: for i in range(self.lineno - last_lineno - 1): ans.append('') ans.append(' ' * (self.indent - indent) + self.line[self.column:]) if not ans: self.SyntaxError("expected block string", False) return u'\n'.join(scanner.unescape(str) for str in ans) def parse_simple_match(self): token, value = self.get_token() if token == 'str' or token == 'id' or token == 'number' or \ token == 'const': next_token, next_value = self.get_token() if next_token == 'prompt' and token == 'str': final_token, final_value = self.get_token('regexp') return qa_helpers.regexp(final_value, value, next_value) if next_token == 'regexp' and token == 'str': return qa_helpers.regexp(next_value, value) if next_token == 'equal': return qa_helpers.qmap(self.parse_simple_match(), value) if next_token == 'hyphen' and token == 'number': final_token, final_value = self.get_token() if final_token == 'number': return slice(value, final_value) self.push_token() return slice(value, None) self.push_token() return value if token == 'prompt': next_token, next_value = self.get_token('regexp') return qa_helpers.regexp(next_value, prompt=value) if token == 'lparen': ans = self.parse_match() self.get_token('rparen') return ans if token == 'regexp': return qa_helpers.regexp(value) if token == 'hyphen': next_token, next_value = self.get_token('number') return slice(None, next_value) self.SyntaxError("expected match, got %s" % token) def parse_match(self): r''' >>> from StringIO import StringIO >>> def do(str): ... ans = StringIO(str) ... ans.name = 'StringIO' ... return kqb_parser(ans).parse_match() >>> do(r'/reg\exp/ bob') >>> do(r'"msg"/reg\exp/ bob') >>> do(r'[prompt]/reg\exp/ bob') >>> do(r'"msg"[prompt]/reg\exp/ bob') >>> do(r"44 = id") >>> do(r"-5 bob") slice(None, 5, None) >>> do(r"0-5 bob") slice(0, 5, None) >>> do(r"0- bob") slice(0, None, None) >>> do(r"/regexp/|44|3-5 bob") (, 44, slice(3, 5, None)) >>> do(r"44 id") 44 ''' ans = [self.parse_simple_match()] token, value = self.get_token() while token == 'bar': ans.append(self.parse_simple_match()) token, value = self.get_token() self.push_token() if len(ans) == 1: return ans[0] return tuple(ans) def get_value(self): token, value = self.get_token() if token not in ('const', 'number', 'id', 'str'): self.SyntaxError("expected value, got %s" % token) return value def skip_spaces(self, pre_increment=0): if pre_increment: indent, chars = \ scanner.count_indent(self.line[self.column: self.column+pre_increment], True) self.indent += indent self.column += chars indent, chars = scanner.count_indent(self.line[self.column:]) self.indent += indent self.column += chars def parse_alternatives(self): r''' >>> from StringIO import StringIO >>> def do(str): ... ans = StringIO(str) ... ans.name = 'StringIO' ... p = kqb_parser(ans) ... alt, review = p.parse_alternatives() ... for tag, msg in alt: ... print '%s: %s' % (repr(tag), repr(msg.template)) ... for key, msg in sorted(review, key=lambda x: repr(x[0])): ... print repr(key), '!', repr(msg.template) >>> do(r""" ... 1: hi mom ... how are you? ... ! Just reward! ... bob: yep this is bob ... ! =1 ... 44: nope, this is just 44 ... ! = bob ... next ... """) 1: u'hi mom\nhow are you?' 'bob': u'yep this is bob' 44: u'nope, this is just 44' (1, 'bob', 44) ! u'Just reward!' ''' if self.column >= len(self.line): self.readline() if self.eof or self.indent == 0: self.SyntaxError("no alternatives", False) indent = self.indent review = {} ans = [] while not self.eof and self.indent == indent: tag = self.get_value() if tag in review: self.SyntaxError("duplicate tag: %s" % tag) self.get_token('colon') ans.append((tag, Template(self.get_block_string(stop='!', hanging=True)))) if self.line[self.column] == '!': self.skip_spaces(1) if self.line[self.column] == '=': self.indent += 1 self.column += 1 old_value = self.get_value() while not isinstance(review[old_value], tuple): old_value = review[old_value] review[old_value][0].append(tag) review[tag] = old_value self.readline() else: review[tag] = \ [tag], Template(self.get_block_string(hanging=True)) if not self.eof and self.indent > indent: self.SyntaxError("unexpected indent", False) return tuple(ans), \ tuple((value[0][0] if len(value[0]) == 1 else tuple(value[0]), value[1]) for value in review.itervalues() if isinstance(value, tuple)) \ if review \ else None def parse_review(self): r''' >>> from StringIO import StringIO >>> def do(str): ... ans = StringIO(str) ... ans.name = 'StringIO' ... p = kqb_parser(ans) ... review = p.parse_review() ... for key, msg in sorted(review, key=lambda x: repr(x[0])): ... print repr(key), '!', repr(msg.template) >>> do(r""" ... 1 ! hi mom ... how are you? ... ! Just reward! ... bob! yep this is bob ... 3-5! nope, this is just 44 ... next ... """) 'bob' ! u'yep this is bob' 1 ! u'hi mom\nhow are you?\n! Just reward!' slice(3, 5, None) ! u'nope, this is just 44' ''' if self.column >= len(self.line): self.readline() if self.eof or self.indent == 0: #print "parse_review: None" # FIX return None #print "parse_review: self.indent", self.indent, \ # "self.column", self.column # FIX indent = self.indent review = [] while not self.eof and self.indent == indent: match = self.parse_match() self.get_token('bang') review.append( (match, Template(self.get_block_string(hanging=True)))) if not self.eof and self.indent > indent: self.SyntaxError("unexpected indent", False) #print "parse_review:", tuple(review) # FIX return tuple(review) def parse_questions(self): r''' question_base.question generator. >>> from StringIO import StringIO >>> def do(str): ... ans = StringIO(str) ... ans.name = 'StringIO' ... p = kqb_parser(ans) ... for q in p.parse_questions(): ... print q >>> do(r""" ... question1($ans) ... This is the question? ... --- ... $ans = yn ... ... question2($ans) ... This is the second question? ... --- ... $ans = select_1 ... 1: first ... 2: second ... 3: third ... ... """) > > ''' self.readline() while not self.eof: if self.indent > 0: self.SyntaxError("unexpected indent", False) token, name = self.get_token('id') self.get_token('lparen') params = [] token, param = self.get_token() if token != 'rparen': while True: if token != 'param': self.SyntaxError("expected $param, got %s" % token) params.append(param) token, ignore = self.get_token() if token == 'rparen': break if token != 'comma': self.SyntaxError("expected comma or rparen, got %s" % token) token, param = self.get_token() format = self.get_block_string(stop='---', ending_newlines=True) self.readline() # --- token, answer_param = self.get_token('param') self.get_token('equal') token, cls = self.get_token('id') user_q = getattr(user_question, cls)(format) user_q.parse(self) yield question_base.question(name, tuple(params), answer_param, user_q) if self.column >= len(self.line): self.readline() def parse_kqb(filename): name = os.path.basename(filename)[:-4] with open(filename, 'rU') as f: base = question_base.question_base(name) parser = kqb_parser(f) for question in parser.parse_questions(): base.add_question(question) return base ./pyke-1.1.1/pyke/krb_compiler/krbparser.py0000644000175000017500000005340511346504630017607 0ustar lambylamby# $Id: krbparser.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ See http://www.dabeaz.com/ply/ply.html for syntax of grammer definitions. """ from __future__ import with_statement import itertools import warnings import os, os.path from pyke.krb_compiler.ply import yacc from pyke.krb_compiler import scanner from pyke import pattern, contexts tokens = scanner.tokens goal_mode = False def p_top(p): ''' top : file | python_goal ''' p[0] = p[1] def p_goal(p): ''' python_goal : CHECK_TOK IDENTIFIER_TOK '.' IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK ''' p[0] = (p[2], p[4], p[6], pattern_vars) def p_file(p): ''' file : nl_opt parent_opt fc_rules bc_rules_opt ''' p[0] = ('file', p[2], (tuple(p[3]), ()), p[4]) def p_file_fc_extras(p): ''' file : nl_opt parent_opt fc_rules fc_extras bc_rules_opt ''' p[0] = ('file', p[2], (tuple(p[3]), p[4]), p[5]) def p_file_bc(p): ''' file : nl_opt parent_opt bc_rules_section ''' p[0] = ('file', p[2], ((), ()), p[3]) # Uncomment this to generate an error in the grammer. #def p_bogus(p): # ''' file : nl_opt parent_opt bc_rules_opt # ''' # p[0] = ('file', p[2], ((), ()), p[3]) def p_parent(p): ''' parent_opt : EXTENDING_TOK IDENTIFIER_TOK without_opt NL_TOK ''' p[0] = ('parent', p[2], tuple(p[3])) def p_second(p): ''' without_opt : WITHOUT_TOK without_names comma_opt ''' p[0] = p[2] def p_fourth(p): ''' when_opt : WHEN_TOK NL_TOK reset_plan_vars INDENT_TOK bc_premises DEINDENT_TOK ''' p[0] = p[5] def p_reset_plan_vars(p): ''' reset_plan_vars : ''' global plan_var, plan_var_number plan_var_number = 1 plan_var = 'plan#%d' % plan_var_number p[0] = None def p_inc_plan_vars(p): ''' inc_plan_vars : ''' global plan_var, plan_var_number plan_var_number += 1 plan_var = 'plan#%d' % plan_var_number p[0] = None def p_fifth(p): ''' bc_extras_opt : BC_EXTRAS_TOK NL_TOK start_extra_statements INDENT_TOK python_extras_code NL_TOK DEINDENT_TOK fc_extras : FC_EXTRAS_TOK NL_TOK start_extra_statements INDENT_TOK python_extras_code NL_TOK DEINDENT_TOK plan_extras_opt : PLAN_EXTRAS_TOK NL_TOK start_extra_statements INDENT_TOK python_extras_code NL_TOK DEINDENT_TOK with_opt : WITH_TOK NL_TOK start_python_statements INDENT_TOK python_plan_code NL_TOK DEINDENT_TOK ''' p[0] = p[5] def p_none(p): ''' bc_require_opt : comma_opt : comma_opt : ',' colon_opt : data : NONE_TOK fc_require_opt : nl_opt : nl_opt : NL_TOK parent_opt : plan_spec : NL_TOK rest_opt : comma_opt ''' p[0] = None def p_colon_deprication(p): ''' colon_opt : ':' ''' warnings.warn_explicit("use of ':' deprecated after rule names", DeprecationWarning, scanner.lexer.filename, p.lineno(1)) p[0] = None def p_fc_rule(p): ''' fc_rule : IDENTIFIER_TOK colon_opt NL_TOK INDENT_TOK foreach_opt ASSERT_TOK NL_TOK INDENT_TOK assertions DEINDENT_TOK DEINDENT_TOK ''' p[0] = ('fc_rule', p[1], p[5], tuple(p[9])) def p_foreach(p): ''' foreach_opt : FOREACH_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK ''' p[0] = tuple(p[4]) def p_fc_premise(p): ''' fc_premise : IDENTIFIER_TOK '.' IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK NL_TOK ''' p[0] = ('fc_premise', p[1], p[3], tuple(p[5]), p.lineno(1), p.lineno(6)) def p_fc_first_1(p): ''' fc_premise : FIRST_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK ''' p[0] = ('fc_first', tuple(p[4]), p.lineno(1)) def p_fc_first_n(p): ''' fc_premise : FIRST_TOK fc_premise ''' p[0] = ('fc_first', (p[2],), p.lineno(1)) def p_fc_notany(p): ''' fc_premise : NOTANY_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK ''' p[0] = ('fc_notany', tuple(p[4]), p.lineno(1)) def p_fc_forall(p): ''' fc_premise : FORALL_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK fc_require_opt ''' p[0] = ('fc_forall', tuple(p[4]), p[6], p.lineno(1), p.linespan(6)[1]) def p_fc_require_opt(p): ''' fc_require_opt : REQUIRE_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK ''' p[0] = tuple(p[4]) def p_python_eq(p): ''' python_premise : pattern start_python_code '=' python_rule_code NL_TOK ''' p[0] = ('python_eq', p[1], p[4], p.linespan(1)[0], p.linespan(4)[1]) def p_python_in(p): ''' python_premise : pattern start_python_code IN_TOK python_rule_code NL_TOK ''' p[0] = ('python_in', p[1], p[4], p.linespan(1)[0], p.linespan(4)[1]) def p_python_check(p): ''' python_premise : start_python_code CHECK_TOK python_rule_code NL_TOK ''' p[0] = ('python_check', p[3], p.lineno(2), p.linespan(3)[1]) def p_python_block_n(p): ''' python_premise : check_nl PYTHON_TOK NL_TOK start_python_assertion INDENT_TOK python_rule_code NL_TOK DEINDENT_TOK ''' p[0] = ('python_block', p[6], p.lineno(2), p.linespan(6)[1]) def p_python_block_1(p): ''' python_premise : check_nl PYTHON_TOK start_python_code NOT_NL_TOK python_rule_code NL_TOK ''' p[0] = ('python_block', p[5], p.lineno(2), p.linespan(5)[1]) def p_assertion(p): ''' assertion : IDENTIFIER_TOK '.' IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK NL_TOK ''' p[0] = ('assert', p[1], p[3], tuple(p[5]), p.lineno(1), p.lineno(6)) def p_python_assertion_n(p): ''' assertion : check_nl PYTHON_TOK NL_TOK start_python_assertion INDENT_TOK python_rule_code NL_TOK DEINDENT_TOK ''' p[0] = ('python_assertion', p[6], p.lineno(2), p.linespan(6)[1]) def p_python_assertion_1(p): ''' assertion : check_nl PYTHON_TOK start_python_code NOT_NL_TOK python_rule_code NL_TOK ''' p[0] = ('python_assertion', p[5], p.lineno(2), p.linespan(5)[1]) def p_check_nl(p): ''' check_nl : ''' scanner.lexer.begin('checknl') p[0] = None def p_bc_rule(p): ''' bc_rule : IDENTIFIER_TOK colon_opt NL_TOK INDENT_TOK USE_TOK goal when_opt with_opt DEINDENT_TOK ''' p[0] = ('bc_rule', p[1], p[6], tuple(p[7]), tuple(p[8][0]), tuple(p[8][1])) def p_empty_bc_rules_opt(p): ''' bc_rules_opt : ''' p[0] = ((), (), ()) def p_bc_rules_section(p): ''' bc_rules_section : bc_rules bc_extras_opt plan_extras_opt ''' p[0] = (tuple(p[1]), p[2], p[3]) def p_goal_no_taking(p): ''' goal : IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK NL_TOK ''' p[0] = ('goal', p[1], tuple(p[3]), (), p.lineno(1), p.lineno(4)) def p_goal_taking(p): ''' goal : IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK taking ''' p[0] = ('goal', p[1], tuple(p[3]), p[5], p.lineno(1), p.lineno(4)) def p_name_sym(p): ''' name : IDENTIFIER_TOK ''' p[0] = repr(p[1]) def p_name_pat_var(p): ''' name : PATTERN_VAR_TOK ''' p[0] = "context.lookup_data(%s)" % p[1] def p_bc_premise1(p): ''' bc_premise : name LP_TOK patterns_opt RP_TOK plan_spec ''' p[0] = ('bc_premise', False, None, p[1], tuple(p[3]), p[5], p.linespan(1)[0], p.lineno(4)) def p_bc_premise2(p): ''' bc_premise : '!' name LP_TOK patterns_opt RP_TOK plan_spec ''' p[0] = ('bc_premise', True, None, p[2], tuple(p[4]), p[6], p.lineno(1), p.lineno(5)) def p_bc_premise3(p): ''' bc_premise : name '.' name LP_TOK patterns_opt RP_TOK plan_spec ''' p[0] = ('bc_premise', False, p[1], p[3], tuple(p[5]), p[7], p.linespan(1)[0], p.lineno(6)) def p_bc_premise4(p): ''' bc_premise : '!' name '.' name LP_TOK patterns_opt RP_TOK plan_spec ''' p[0] = ('bc_premise', True, p[2], p[4], tuple(p[6]), p[8], p.lineno(1), p.lineno(7)) def p_bc_first_1f(p): ''' bc_premise : FIRST_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK ''' p[0] = ('bc_first', False, tuple(p[4]), p.lineno(1)) def p_bc_first_nf(p): ''' bc_premise : FIRST_TOK bc_premise ''' p[0] = ('bc_first', False, (p[2],), p.lineno(1)) def p_bc_first_1t(p): ''' bc_premise : '!' FIRST_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK ''' p[0] = ('bc_first', True, tuple(p[4]), p.lineno(1)) def p_bc_first_nt(p): ''' bc_premise : '!' FIRST_TOK bc_premise ''' p[0] = ('bc_first', True, (p[2],), p.lineno(1)) def p_bc_notany(p): ''' bc_premise : NOTANY_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK ''' p[0] = ('bc_notany', tuple(p[4]), p.lineno(1)) def p_bc_forall(p): ''' bc_premise : FORALL_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK bc_require_opt ''' p[0] = ('bc_forall', tuple(p[4]), p[6], p.lineno(1), p.linespan(6)[1]) def p_bc_require_opt(p): ''' bc_require_opt : REQUIRE_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK ''' p[0] = tuple(p[4]) def p_as(p): ''' plan_spec : AS_TOK PATTERN_VAR_TOK NL_TOK ''' p[0] = ('as', p[2][1:-1]) def p_step_code(p): ''' plan_spec : STEP_TOK NUMBER_TOK NL_TOK start_python_plan_call INDENT_TOK python_plan_code NL_TOK DEINDENT_TOK ''' p[0] = ('plan_spec', p[2], plan_var, p[6][0], p[6][1], p.lineno(1), p.lexpos(1)) def p_code(p): ''' plan_spec : NL_TOK start_python_plan_call INDENT_TOK python_plan_code NL_TOK DEINDENT_TOK ''' p[0] = ('plan_spec', None, plan_var, p[4][0], p[4][1], p[4][2], p[4][3]) def p_start_python_code(p): ''' start_python_code : ''' scanner.start_code(var_format = "context.lookup_data('%s')") p[0] = None def p_start_python_plan_call(p): ''' start_python_plan_call : ''' scanner.start_code(plan_name = plan_var, multiline = True) p[0] = None def p_start_python_statements(p): ''' start_python_statements : ''' scanner.start_code(multiline = True) p[0] = None def p_start_extra_statements(p): ''' start_extra_statements : ''' scanner.start_code(multiline = True, var_format = None) p[0] = None def p_start_python_assertion(p): ''' start_python_assertion : ''' scanner.start_code(multiline = True, var_format = "context.lookup_data('%s')") p[0] = None def p_python_rule_code(p): ''' python_rule_code : CODE_TOK ''' p[0] = p[1] def p_python_plan_code(p): ''' python_plan_code : CODE_TOK ''' p[0] = p[1] def p_python_extras_code(p): ''' python_extras_code : CODE_TOK ''' p[0] = p[1][0] def p_pattern_var(p): ''' variable : PATTERN_VAR_TOK ''' global pattern_vars if goal_mode: pattern_vars.append(p[1]) p[0] = contexts.variable(p[1]) else: p[0] = "contexts.variable(%s)" % p[1] def p_anonymous_var(p): ''' variable : ANONYMOUS_VAR_TOK ''' if goal_mode: p[0] = contexts.anonymous(p[1]) else: p[0] = "contexts.anonymous(%s)" % p[1] def p_first(p): ''' bc_premise : python_premise bc_rules_opt : bc_rules_section data : NUMBER_TOK fc_premise : python_premise pattern : pattern_proper pattern_proper : variable patterns_opt : patterns comma_opt ''' p[0] = p[1] def p_last(p): ''' rest_opt : ',' '*' variable ''' p[0] = p[len(p)-1] def p_data_string(p): ''' data : STRING_TOK ''' if goal_mode: if p[1].startswith("'''") or p[1].startswith('"""'): p[0] = scanner.unescape(p[1][3:-3]) else: p[0] = scanner.unescape(p[1][1:-1]) else: p[0] = p[1] def p_taking(p): ''' taking : start_python_code TAKING_TOK python_rule_code NL_TOK ''' p[0] = p[3][0] def p_taking2(p): ''' taking : NL_TOK INDENT_TOK start_python_code TAKING_TOK python_rule_code NL_TOK DEINDENT_TOK ''' p[0] = p[5][0] def p_quoted_last(p): ''' data : IDENTIFIER_TOK ''' if goal_mode: p[0] = p[len(p)-1] else: p[0] = "'" + p[len(p)-1] + "'" def p_false(p): ''' data : FALSE_TOK ''' p[0] = False def p_true(p): ''' data : TRUE_TOK ''' p[0] = True def p_start_list(p): ''' assertions : assertion bc_premises : bc_premise bc_rules : bc_rule data_list : data fc_premises : fc_premise fc_rules : fc_rule patterns : pattern patterns_proper : pattern_proper without_names : IDENTIFIER_TOK ''' p[0] = [p[1]] def p_empty_tuple(p): ''' bc_extras_opt : data : LP_TOK RP_TOK foreach_opt : patterns_opt : plan_extras_opt : when_opt : without_opt : ''' p[0] = () def p_double_empty_tuple(p): ''' with_opt : ''' p[0] = (), () def p_append_list(p): ''' assertions : assertions assertion bc_premises : bc_premises inc_plan_vars bc_premise bc_rules : bc_rules bc_rule data_list : data_list ',' data fc_premises : fc_premises fc_premise fc_rules : fc_rules fc_rule patterns : patterns ',' pattern patterns_proper : patterns_proper ',' pattern without_names : without_names ',' IDENTIFIER_TOK ''' p[1].append(p[len(p)-1]) p[0] = p[1] def p_pattern(p): ''' pattern : data ''' if goal_mode: p[0] = pattern.pattern_literal(p[1]) else: p[0] = "pattern.pattern_literal(%s)" % str(p[1]) def p_pattern_tuple1(p): ''' pattern_proper : LP_TOK '*' variable RP_TOK ''' if goal_mode: p[0] = pattern.pattern_tuple((), p[3]) else: p[0] = "pattern.pattern_tuple((), %s)" % p[3] def p_pattern_tuple2(p): ''' pattern_proper : LP_TOK data_list ',' '*' variable RP_TOK ''' if goal_mode: p[0] = pattern.pattern_tuple( tuple(pattern.pattern_literal(x) for x in p[2]), p[5]) else: p[0] = "pattern.pattern_tuple((%s), %s)" % \ (' '.join("pattern.pattern_literal(%s)," % str(x) for x in p[2]), p[5]) def p_pattern_tuple3(p): ''' pattern_proper : LP_TOK data_list ',' patterns_proper rest_opt RP_TOK ''' if goal_mode: p[0] = pattern.pattern_tuple( tuple(itertools.chain( (pattern.pattern_literal(x) for x in p[2]), p[4])), p[5]) else: p[0] = "pattern.pattern_tuple((%s), %s)" % \ (' '.join(itertools.chain( ("pattern.pattern_literal(%s)," % str(x) for x in p[2]), (str(x) + ',' for x in p[4]))), p[5]) def p_pattern_tuple4(p): ''' pattern_proper : LP_TOK patterns_proper rest_opt RP_TOK ''' if goal_mode: p[0] = pattern.pattern_tuple(p[2], p[3]) else: p[0] = "pattern.pattern_tuple((%s), %s)" % \ (' '.join(str(x) + ',' for x in p[2]), p[3]) def p_tuple(p): ''' data : LP_TOK data_list comma_opt RP_TOK ''' if goal_mode: p[0] = tuple(p[2]) else: p[0] = '(' + ' '.join(str(x) + ',' for x in p[2]) + ')' def p_error(t): if t is None: raise SyntaxError("invalid syntax", scanner.syntaxerror_params()) else: raise SyntaxError("invalid syntax", scanner.syntaxerror_params(t.lexpos, t.lineno)) parser = None def init(this_module, check_tables = False, debug = 0): global parser if parser is None: outputdir = os.path.dirname(this_module.__file__) if debug: parser = yacc.yacc(module=this_module, write_tables=0, debug=debug, debugfile='krbparser.yacc.out', outputdir=outputdir) else: if check_tables: krbparser_mtime = os.path.getmtime(this_module.__file__) tables_name = os.path.join(outputdir, 'krbparser_tables.py') try: ok = os.path.getmtime(tables_name) >= krbparser_mtime except OSError: ok = False if not ok: #print "regenerating krbparser_tables" try: os.remove(tables_name) except OSError: pass try: os.remove(tables_name + 'c') except OSError: pass try: os.remove(tables_name + 'o') except OSError: pass parser = yacc.yacc(module=this_module, debug=0, optimize=1, write_tables=1, tabmodule='pyke.krb_compiler.krbparser_tables', outputdir=outputdir) # Use the first line for normal use, the second for testing changes in the # grammer (the first line does not report grammer errors!). def parse(this_module, filename, check_tables = False, debug = 0): #def parse(this_module, filename, check_tables = True, debug = 1): global goal_mode init(this_module, check_tables, debug) with open(filename) as f: scanner.init(scanner, debug, check_tables) scanner.lexer.lineno = 1 scanner.lexer.filename = filename scanner.kfb_mode = False scanner.goal_mode = False goal_mode = False #parser.restart() return parser.parse(f.read() + '\n', lexer=scanner.lexer, tracking=True, debug=debug) def parse_goal(this_module, s, check_tables = False, debug = 0): global goal_mode, pattern_vars init(this_module, check_tables, debug) scanner.init(scanner, debug, check_tables) scanner.lexer.lineno = 1 scanner.lexer.filename = s scanner.kfb_mode = False scanner.goal_mode = True goal_mode = True pattern_vars = [] #parser.restart() return parser.parse('check ' + s, lexer=scanner.lexer, tracking=True, debug=debug) def run(this_module, filename='TEST/krbparse_test.krb'): r""" Used for testing. >>> import os, os.path >>> from pyke.krb_compiler import krbparser >>> run(krbparser, os.path.join(os.path.dirname(__file__), ... 'TEST/krbparse_test.krb')) ('file', None, ((('fc_rule', 'name1', (('fc_premise', 'a', 'b', ("pattern.pattern_literal('x')", "contexts.variable('b')"), 7, 7),), (('assert', 'c', 'd', ("contexts.variable('b')",), 9, 9),)),), ()), ((('bc_rule', 'name2', ('goal', 'x', ('pattern.pattern_literal(1)', "contexts.variable('c')", "pattern.pattern_literal((1, 'b',))", "pattern.pattern_tuple((pattern.pattern_literal(1), pattern.pattern_literal('b'), contexts.variable('c'),), None)"), (' (a, b, c)',), 12, 12), (('bc_premise', False, "'a'", "'b'", ("pattern.pattern_literal('x')", "contexts.variable('c')"), ('plan_spec', None, 'plan#1', ('line1', "line2 (context['d']) end2"), ('d',), 15, 219), 14, 14), ('bc_premise', False, None, "'x'", ('pattern.pattern_literal(1)', 'pattern.pattern_literal(2)', 'pattern.pattern_literal(3)'), ('plan_spec', None, 'plan#2', ("some (context['plan'])(stuff) \\", ' and more stuff fail here'), ('plan',), 18, 280), 17, 17)), ("a (context['plan']) b",), ('plan',)), ('bc_rule', 'name3', ('goal', 'x', ('pattern.pattern_literal(1)', "contexts.variable('c')", "pattern.pattern_literal((1, 'b',))", "pattern.pattern_tuple((pattern.pattern_literal(1), pattern.pattern_literal('b'), contexts.variable('c'),), None)"), (), 24, 24), (('bc_premise', False, "'a'", "'b'", ("pattern.pattern_literal('x')", "contexts.variable('c')"), ('plan_spec', None, 'plan#1', ('line1', "line2 (context['d']) end2"), ('d',), 27, 452), 26, 26), ('bc_premise', False, None, "'x'", ('pattern.pattern_literal(1)', 'pattern.pattern_literal(2)', 'pattern.pattern_literal(3)'), ('as', 'foo_fn'), 29, 29)), (), ())), (), ())) """ import pprint pprint.pprint(parse(this_module, filename, True)) ./pyke-1.1.1/pyke/krb_compiler/scanner.py0000644000175000017500000005016011346504630017240 0ustar lambylamby# $Id: scanner.py 9f7068449a4b 2010-03-08 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ See http://www.dabeaz.com/ply/ply.html for syntax of grammer definitions. """ from __future__ import with_statement import string import os, os.path from pyke.krb_compiler.ply import lex debug=0 kfb_mode = False goal_mode = False states = ( ('indent', 'exclusive'), ('code', 'exclusive'), ('checknl', 'exclusive'), ) kfb_keywords = frozenset(( 'False', 'None', 'True', )) keywords = frozenset(( 'as', 'assert', 'bc_extras', 'check', 'extending', 'False', 'fc_extras', 'first', 'forall', 'foreach', 'in', 'None', 'notany', 'plan_extras', 'python', 'require', 'step', 'taking', 'True', 'use', 'when', 'with', 'without', )) base_kfb_tokens = ( # 'DATE_TOK', # FIX: Add the definition for this! 'IDENTIFIER_TOK', # 'LB_TOK', # 'LC_TOK', 'LP_TOK', 'NL_TOK', 'NUMBER_TOK', # 'RB_TOK', # 'RC_TOK', 'RP_TOK', 'STRING_TOK', ) base_krb_tokens = base_kfb_tokens + ( 'ANONYMOUS_VAR_TOK', 'CODE_TOK', 'DEINDENT_TOK', 'INDENT_TOK', 'NOT_NL_TOK', 'PATTERN_VAR_TOK', ) kfb_tokens = tuple(x.upper() + '_TOK' for x in kfb_keywords) + base_kfb_tokens tokens = tuple(x.upper() + '_TOK' for x in keywords) + base_krb_tokens literals = '*:,!.=' # FIX: delete ':' t_ignore = ' \t' t_ignore_comment = r'\#.*' def t_continuation(t): r'\\(\r)?\n' t.lexer.lineno += 1 def t_NL_TOK(t): # newline, followed by any number of empty or comment only lines r'(\r)?\n([ \t]*(\#.*)?(\r)?\n)*' t.lexer.lineno += t.value.count('\n') if kfb_mode: return t if nesting_level == 0: t.lexer.begin('indent') t.lexer.skip(-1) # put the final '\n' back for tp_indent_sp! return t indent_levels = [] # to prevent getting a warning... t_indent_ignore = '' def t_indent_sp(t): # ply doesn't like re's that can be empty, so we'll include the prior # newline char in the re and then skip over it when we count the indent # level. The tp_NL_TOK function does a skip(-1) to retain the final '\n' # for t_indent_sp. r'\n[ \t]*' indent = count_indent(t.value[1:])[0] current_indent = indent_levels[-1] if indent_levels else 0 if debug: print "t_indent_sp: t.value", repr(t.value), "indent", indent, \ "current_indent", current_indent, \ "indent_levels", indent_levels, \ "t.lexpos", t.lexpos, \ "t.lexer.lexpos", t.lexer.lexpos, \ "t.lexer.lexdata[]", repr(t.lexer.lexdata[t.lexpos]) if indent > current_indent: t.type = 'INDENT_TOK' indent_levels.append(indent) t.lexer.begin('INITIAL') if debug: print "INDENT_TOK: indent_levels", indent_levels return t if indent < current_indent: if indent > 0 and indent not in indent_levels: raise SyntaxError( "deindent doesn't match any previous indent level", syntaxerror_params(t.lexpos)) t.type = 'DEINDENT_TOK' del indent_levels[-1] if indent < (indent_levels[-1] if indent_levels else 0): if debug: print " -- pushing indent back" t.lexer.skip(-len(t.value)) else: if debug: print " -- doing begin('INITIAL')" t.lexer.begin('INITIAL') if debug: print "DEINDENT_TOK: indent_levels", indent_levels return t # else indent == current_indent t.lexer.begin('INITIAL') if debug: print "no indent: indent_levels", indent_levels t_checknl_ignore = ' \t' def t_checknl_nl(t): # optional comment followed by newline r'(\#.*)?(\r)?\n' t.lexer.lineno += 1 t.lexer.begin('indent') t.lexer.skip(-1) # put the final '\n' back for tp_indent_sp! t.type = 'NL_TOK' return t def t_checknl_other(t): # something other than newline r'[^\#\r\n]' t.lexer.skip(-1) # put the final char back! t.type = 'NOT_NL_TOK' return t def start_code(plan_name = None, multiline = False, var_format = "(context['%s'])"): global current_line, code, current_plan_name, code__level global pattern_var_format, plan_vars_needed, code_nesting_level global code_lineno, code_lexpos global code_indent_level pattern_var_format = var_format plan_vars_needed = [] current_line = '' code = [] if multiline: code_indent_level = indent_levels[-1] else: code_indent_level = 1000000000 current_plan_name = plan_name code_nesting_level = 0 code_lineno = code_lexpos = None lexer.begin('code') def mark(t): global code_lineno, code_lexpos if code_lineno is None: code_lineno = t.lexer.lineno code_lexpos = t.lexpos # to prevent getting a warning... t_code_ignore = '' def t_code_string(t): r"'''([^\\]|\\.)*?'''|" \ r'"""([^\\]|\\.)*?"""|' \ r"'([^'\\\n\r]|\\.|\\(\r)?\n)*?'|" \ r'"([^"\\\n\r]|\\.|\\(\r)?\n)*?"' global current_line current_line += t.value mark(t) if debug: print "scanner saw string:", t.value t.lexer.lineno += t.value.count('\n') def t_code_comment(t): r'[ \t\f\r]*\#.*' global current_line if debug: print "scanner saw comment:", t.value #current_line += t.value def t_code_plan(t): r'\$\$' global current_line mark(t) if debug: print "scanner saw '$$', current_plan_name is", current_plan_name if not current_plan_name: raise SyntaxError("'$$' only allowed in plan_specs within the " "'when' clause", syntaxerror_params(t.lexpos)) current_line += pattern_var_format % current_plan_name plan_vars_needed.append(current_plan_name) def t_code_pattern_var(t): r'\$[a-zA-Z_][a-zA-Z0-9_]*\b' global current_line mark(t) if not pattern_var_format: raise SyntaxError("$ only allowed in backward chaining rules", syntaxerror_params(t.lexpos)) current_line += pattern_var_format % t.value[1:] plan_vars_needed.append(t.value[1:]) if debug: print "scanner saw pattern_var:", t.value def t_code_continuation(t): r'\\(\r)?\n' global current_line t.lexer.lineno += 1 current_line += '\\' code.append(current_line) current_line = '' if debug: print "scanner saw continuation:", t.value def t_code_open(t): r'[{([]' global current_line, code_nesting_level mark(t) code_nesting_level += 1 current_line += t.value def t_code_close(t): r'[]})]' global current_line, code_nesting_level mark(t) if code_nesting_level <= 0: raise SyntaxError("unmatched %s" % repr(t.value), syntaxerror_params(t.lexpos)) code_nesting_level -= 1 current_line += t.value def t_code_symbol(t): r'''[0-9a-zA-Z_]+''' global current_line mark(t) current_line += t.value if debug: print "scanner saw symbol:", t.value def t_code_space(t): r'''[ \t]+''' global current_line current_line += t.value if debug: print "scanner saw space chars:", t.value def t_code_other(t): r'''[^][(){}$\\'"\r\n0-9a-zA-Z_ \t]+''' global current_line mark(t) current_line += t.value if debug: print "scanner saw other chars:", t.value def t_code_NL_TOK(t): r'(\r)?\n([ \t]*(\#.*)?(\r)?\n)*[ \t]*' global current_line if current_line: code.append(current_line) current_line = '' indent = count_indent(t.value[t.value.rindex('\n') + 1:])[0] if debug: print "scanner saw nl:", t.value, "new indent is", indent if indent < code_indent_level and code_nesting_level == 0: t.lexer.skip(-len(t.value)) t.type = 'CODE_TOK' t.value = tuple(code), tuple(plan_vars_needed), code_lineno, code_lexpos if debug: print "scanner begin('INITIAL')" t.lexer.begin('INITIAL') return t t.lexer.lineno += t.value.count('\n') current_line = ' ' * (indent - code_indent_level) # strings: def t_tsqstring(t): r"[uU]?[rR]?'''([^\\]|\\.)*?'''" #t.value = unescape(t.value[3:-3]) t.type = 'STRING_TOK' t.lexer.lineno += t.value.count('\n') return t def t_tdqstring(t): r'[uU]?[rR]?"""([^\\]|\\.)*?"""' #t.value = unescape(t.value[3:-3]) t.type = 'STRING_TOK' t.lexer.lineno += t.value.count('\n') return t def t_sqstring(t): r"[uU]?[rR]?'([^'\\\n\r]|\\.|\\(\r)?\n)*?'" #t.value = unescape(t.value[1:-1]) t.lexer.lineno += t.value.count('\n') t.type = 'STRING_TOK' return t def t_dqstring(t): r'[uU]?[rR]?"([^"\\\n\r]|\\.|\\(\r)?\n)*?"' #t.value = unescape(t.value[1:-1]) t.type = 'STRING_TOK' t.lexer.lineno += t.value.count('\n') return t # end strings def t_ANONYMOUS_VAR_TOK(t): r'\$_([a-zA-Z_][a-zA-Z0-9_]*)?' if kfb_mode: t_ANY_error(t) if goal_mode: t.value = t.value[1:] else: t.value = "'" + t.value[1:] + "'" return t def t_PATTERN_VAR_TOK(t): r'\$[a-zA-Z][a-zA-Z0-9_]*' if kfb_mode: t_ANY_error(t) if goal_mode: t.value = t.value[1:] else: t.value = "'" + t.value[1:] + "'" return t def t_IDENTIFIER_TOK(t): r'[a-zA-Z_][a-zA-Z0-9_]*' if kfb_mode and t.value in kfb_keywords or \ not kfb_mode and t.value in keywords: t.type = t.value.upper() + '_TOK' return t # numbers: def t_float(t): r'[-+]?([0-9]+(\.[0-9]*([eE][-+]?[0-9]+)?|[eE][-+]?[0-9]+)|\.[0-9]+([eE][-+]?[0-9]+)?)' t.value = float(t.value) t.type = 'NUMBER_TOK' return t def t_hexint(t): r'[-+]?0[xX][0-9a-fA-F]+' t.value = int(t.value, 16) t.type = 'NUMBER_TOK' return t def t_octalint(t): r'[-+]?0[0-7]*' t.value = int(t.value, 8) t.type = 'NUMBER_TOK' return t def t_int(t): r'[-+]?[1-9][0-9]*' t.value = int(t.value) t.type = 'NUMBER_TOK' return t # end numbers nesting_level = 0 def t_LB_TOK(t): r'\[' global nesting_level nesting_level += 1 #return t def t_LC_TOK(t): r'\{' global nesting_level nesting_level += 1 #return t def t_LP_TOK(t): r'\(' global nesting_level nesting_level += 1 return t def t_RB_TOK(t): r'\]' global nesting_level assert nesting_level > 0 nesting_level -= 1 #return t def t_RC_TOK(t): r'\}' global nesting_level assert nesting_level > 0 nesting_level -= 1 #return t def t_RP_TOK(t): r'\)' global nesting_level assert nesting_level > 0 nesting_level -= 1 return t def t_ANY_error(t): raise SyntaxError("illegal character %s" % repr(t.value[0]), syntaxerror_params(t.lexpos)) # helper functions: def count_indent(s, count_all=False): r''' >>> count_indent('') (0, 0) >>> count_indent(' ') (3, 3) >>> count_indent(' stuff') (3, 3) >>> count_indent('\t') (8, 1) >>> count_indent('\t ') (9, 2) >>> count_indent('\t\t') (16, 2) >>> count_indent(' \t') (8, 4) >>> count_indent(' \t') (8, 8) >>> count_indent(' \t') (16, 9) >>> count_indent(' a\t', True) (8, 3) >>> count_indent(' a ', True) (3, 3) ''' indent = 0 chars = 0 for c in s: if c == '\t': indent = (indent + 8) & ~7 elif c == ' ' or count_all: indent += 1 else: break chars += 1 return indent, chars escapes = { 'a': '\a', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v', '\\': '\\', '\'': '\'', '\"': '\"', } def unescape(s): start = 0 ans = [] i = s.find('\\', start) while i >= 0: ans.append(s[start:i]) e = escapes.get(s[i+1]) if e: # single char escape code ans.append(e) start = i + 2 elif s[i+1] == '\n': # ignore \ at end of line start = i + 2 elif s[i+1] == '\r': # ignore \ at end of line if s[i+2] == '\n': start = i + 3 else: start = i + 2 elif s[i+1:i+3] == 'N{': end = s.index('}', i + 3) ans.append(unicodedata.lookup(s[i+3:end])) start = end + 1 elif s[i+1] == 'u': ans.append(unichr(int(s[i+2:i+6], 16))) start = i + 6 elif s[i+1] == 'U': ans.append(unichr(int(s[i+2:i+10], 16))) start = i + 10 elif s[i+1] in string.octdigits: if s[i+2] not in string.octdigits: ans.append(unichr(int(s[i+2:i+3], 8))) start = i + 3 elif s[i+3] not in string.octdigits: ans.append(unichr(int(s[i+2:i+4], 8))) start = i + 4 else: ans.append(unichr(int(s[i+2:i+5], 8))) start = i + 5 elif s[i+1] == 'x': if s[i+3] not in string.hexdigits: ans.append(unichr(int(s[i+2:i+3], 16))) start = i + 3 else: ans.append(unichr(int(s[i+2:i+4], 16))) start = i + 4 else: ans.append(s[i]) start = i + 1 i = s.find('\\', start) ans.append(s[start:]) return ''.join(ans) class token_iterator(object): ''' This is only used for testing the scanner. ''' def __init__(self, input): lexer.lineno = 1 lexer.input(input) def __iter__(self): return self def next(self): t = lex.token() if t: return t raise StopIteration def tokenize(s): r''' >>> from pyke.krb_compiler import scanner >>> init(scanner, 0, True) >>> tokenize("# This is a comment\n# line 2 of comment\n\n" ... "# comment after blank line\n") LexToken(NL_TOK,'\n# line 2 of comment\n\n# comment after blank line\n',1,19) >>> tokenize('name1\n forall foreach\n \nname2') LexToken(IDENTIFIER_TOK,'name1',1,0) LexToken(NL_TOK,'\n',1,5) LexToken(INDENT_TOK,'\n ',2,5) LexToken(FORALL_TOK,'forall',2,10) LexToken(FOREACH_TOK,'foreach',2,19) LexToken(NL_TOK,'\n \n',2,26) LexToken(DEINDENT_TOK,'\n',4,38) LexToken(IDENTIFIER_TOK,'name2',4,39) ''' for t in token_iterator(s): print t def tokenize_file(filename = 'TEST/scan_test'): r""" Used for testing. >>> from pyke.krb_compiler import scanner >>> init(scanner, 0, True) >>> import os, os.path >>> tokenize_file(os.path.join(os.path.dirname(__file__), ... 'TEST/scan_test')) LexToken(NL_TOK,'\n# line 2 of comment\n\n# comment after blank line\n',1,19) LexToken(IDENTIFIER_TOK,'name1',5,68) LexToken(:,':',5,73) LexToken(NL_TOK,'\n',5,74) LexToken(INDENT_TOK,'\n ',6,74) LexToken(FOREACH_TOK,'foreach',6,79) LexToken(NL_TOK,'\n',6,86) LexToken(INDENT_TOK,'\n\t',7,86) LexToken(LP_TOK,'(',7,88) LexToken(NUMBER_TOK,100,7,89) LexToken(NUMBER_TOK,64,7,93) LexToken(ANONYMOUS_VAR_TOK,"'_'",7,98) LexToken(PATTERN_VAR_TOK,"'foo'",7,101) LexToken(NUMBER_TOK,256,8,118) LexToken(NUMBER_TOK,0,8,124) LexToken(RP_TOK,')',8,125) LexToken(NL_TOK,'\n',8,126) LexToken(NUMBER_TOK,3.1400000000000001,9,129) LexToken(NUMBER_TOK,0.98999999999999999,9,134) LexToken(NUMBER_TOK,3.0,10,143) LexToken(NUMBER_TOK,0.29999999999999999,10,146) LexToken(NUMBER_TOK,3000000.0,10,149) LexToken(NUMBER_TOK,3.0000000000000001e-06,10,153) LexToken(NL_TOK,'\n',10,158) LexToken(DEINDENT_TOK,'\n ',11,158) LexToken(ASSERT_TOK,'assert',11,163) LexToken(NL_TOK,'\n',11,169) LexToken(INDENT_TOK,'\n\t',12,169) LexToken(STRING_TOK,"'this is a string'",12,172) LexToken(STRING_TOK,'"so is this"',12,191) LexToken(STRING_TOK,"'''\n\tand this \\t too'''",12,204) LexToken(STRING_TOK,"'should be\\\n able to do this too'",13,229) LexToken(TRUE_TOK,'True',15,278) LexToken(NL_TOK,'\n',15,283) LexToken(!,'!',16,292) LexToken(IDENTIFIER_TOK,'can',16,293) LexToken(IDENTIFIER_TOK,'I',17,311) LexToken(IDENTIFIER_TOK,'do',17,313) LexToken(IDENTIFIER_TOK,'this',17,316) LexToken(NL_TOK,'\n',17,320) LexToken(IDENTIFIER_TOK,'too',18,329) LexToken(NL_TOK,'\n',18,332) LexToken(DEINDENT_TOK,'\n',19,332) LexToken(DEINDENT_TOK,'\n',19,332) """ with open(filename) as f: tokenize(f.read()) def syntaxerror_params(pos = None, lineno = None): ''' Returns (filename, lineno, column, line) for use in as the second argument to SyntaxError exceptions. ''' if pos is None: pos = lexer.lexpos if pos > len(lexer.lexdata): pos = len(lexer.lexdata) end = pos if lineno is None: lineno = lexer.lineno while end > 0 and (end >= len(lexer.lexdata) or lexer.lexdata[end] in '\r\n'): end -= 1 start = end if debug: print "pos", pos, "lineno", lineno, "end", end start = max(lexer.lexdata.rfind('\r', 0, end), lexer.lexdata.rfind('\n', 0, end)) + 1 column = pos - start + 1 end1 = lexer.lexdata.find('\r', end) end2 = lexer.lexdata.find('\n', end) if end1 < 0: if end2 < 0: end = len(lexer.lexdata) else: end = end2 elif end2 < 0: end = end1 else: end = min(end1, end2) if goal_mode and start == 0 and lexer.lexdata.startswith('check ', start): start += 6 column -= 6 if debug: print "start", start, "column", column, "end", end return (lexer.filename, lineno, column, lexer.lexdata[start:end]) lexer = None def init(this_module, debug_param, check_tables = False, kfb = False): global indent_levels, nesting_level, kfb_mode, lexer, debug indent_levels = [] nesting_level = 0 kfb_mode = kfb debug = debug_param if lexer is None: if debug_param: lexer = lex.lex(module=this_module, debug=1) else: if check_tables: scanner_mtime = os.path.getmtime(this_module.__file__) tables_name = \ os.path.join(os.path.dirname(this_module.__file__), 'scanner_tables.py') try: ok = os.path.getmtime(tables_name) >= scanner_mtime except OSError: ok = False if not ok: #print "regenerating scanner_tables" try: os.remove(tables_name) except OSError: pass try: os.remove(tables_name + 'c') except OSError: pass try: os.remove(tables_name + 'o') except OSError: pass lexer = lex.lex(module=this_module, optimize=1, lextab='pyke.krb_compiler.scanner_tables', outputdir=os.path.dirname(this_module.__file__)) ./pyke-1.1.1/pyke/krb_compiler/compiler_bc.py0000644000175000017500000045057211365364000020074 0ustar lambylamby# compiler_bc.py from __future__ import with_statement import itertools from pyke import contexts, pattern, bc_rule pyke_version = '1.1.1' compiler_version = 1 def file(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, helpers.fc_head(context.lookup_data('rb_name'))): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, helpers.bc_head(context.lookup_data('rb_name'))): context.end_save_all_undo() mark3 = context.mark(True) if rule.pattern(2).match_data(context, context, helpers.plan_head(context.lookup_data('rb_name'))): context.end_save_all_undo() flag_4 = False with engine.prove(rule.rule_base.root_name, 'rule_decl', context, (rule.pattern(3), rule.pattern(4), rule.pattern(5),)) \ as gen_4: for x_4 in gen_4: flag_4 = True assert x_4 is None, \ "compiler.file: got unexpected plan from when clause 4" flag_5 = False with engine.prove(rule.rule_base.root_name, 'fc_rules', context, (rule.pattern(6), rule.pattern(7), rule.pattern(8),)) \ as gen_5: for x_5 in gen_5: flag_5 = True assert x_5 is None, \ "compiler.file: got unexpected plan from when clause 5" flag_6 = False with engine.prove(rule.rule_base.root_name, 'bc_rules', context, (rule.pattern(3), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12),)) \ as gen_6: for x_6 in gen_6: flag_6 = True assert x_6 is None, \ "compiler.file: got unexpected plan from when clause 6" mark7 = context.mark(True) if rule.pattern(13).match_data(context, context, (context.lookup_data('fc_head'), context.lookup_data('fc_fun_lines'), "", "def populate(engine):", ('INDENT', 2), context.lookup_data('decl_line'), context.lookup_data('fc_init_lines'), 'POPINDENT', "", context.lookup_data('fc_extra_lines'), ) \ if context.lookup_data('fc_fun_lines') \ else ()): context.end_save_all_undo() mark8 = context.mark(True) if rule.pattern(14).match_data(context, context, (context.lookup_data('plan_head'), context.lookup_data('bc_plan_lines'), "", context.lookup_data('plan_extra_lines')) \ if context.lookup_data('bc_plan_lines') \ else ()): context.end_save_all_undo() mark9 = context.mark(True) if rule.pattern(15).match_data(context, context, (context.lookup_data('bc_head'), ("from %s import %s_plans" % (context.lookup_data('generated_root_pkg'), context.lookup_data('rb_name')) if context.lookup_data('bc_plan_lines') else ()), context.lookup_data('bc_bc_fun_lines'), "", "def populate(engine):", ('INDENT', 2), context.lookup_data('decl_line'), context.lookup_data('bc_bc_init_lines'), 'POPINDENT', "", context.lookup_data('bc_extra_lines')) \ if context.lookup_data('bc_bc_fun_lines') \ else ()): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark9) else: context.end_save_all_undo() context.undo_to_mark(mark8) else: context.end_save_all_undo() context.undo_to_mark(mark7) if not flag_6: raise AssertionError("compiler.file: 'when' clause 6 failed") if not flag_5: raise AssertionError("compiler.file: 'when' clause 5 failed") if not flag_4: raise AssertionError("compiler.file: 'when' clause 4 failed") else: context.end_save_all_undo() context.undo_to_mark(mark3) else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def rule_decl(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "This_rule_base = engine.get_create(%r)" % context.lookup_data('rb_name')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def rule_decl_with_parent(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "This_rule_base = engine.get_create(%r, %r, %s)" % \ (context.lookup_data('rb_name'), context.lookup_data('parent'), tuple(repr(sym) for sym in context.lookup_data('excluded_symbols')))): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_rules(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 fc_funs = [] fc_init = [] forall91_worked = True for python_ans in \ context.lookup_data('fc_rules'): mark2 = context.mark(True) if rule.pattern(0).match_data(context, context, python_ans): context.end_save_all_undo() forall91_worked = False flag_3 = False with engine.prove(rule.rule_base.root_name, 'fc_rule', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2),)) \ as gen_3: for x_3 in gen_3: flag_3 = True assert x_3 is None, \ "compiler.fc_rules: got unexpected plan from when clause 3" fc_funs.append(context.lookup_data('fc_fun_1')) fc_init.append(context.lookup_data('fc_init_1')) forall91_worked = True if forall91_worked: break if not flag_3: raise AssertionError("compiler.fc_rules: 'when' clause 3 failed") if not forall91_worked: context.undo_to_mark(mark2) break else: context.end_save_all_undo() context.undo_to_mark(mark2) if forall91_worked: mark5 = context.mark(True) if rule.pattern(3).match_data(context, context, tuple(fc_funs)): context.end_save_all_undo() mark6 = context.mark(True) if rule.pattern(4).match_data(context, context, tuple(fc_init)): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark6) else: context.end_save_all_undo() context.undo_to_mark(mark5) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_rule_(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'fc_premises', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(1), rule.pattern(2), rule.pattern(8), rule.pattern(9), rule.pattern(10),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.fc_rule_: got unexpected plan from when clause 1" flag_2 = False with engine.prove(rule.rule_base.root_name, 'assertions', context, (rule.pattern(11), rule.pattern(12), rule.pattern(10), rule.pattern(13),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.fc_rule_: got unexpected plan from when clause 2" mark3 = context.mark(True) if rule.pattern(14).match_data(context, context, ("", "def %s(rule, context = None, index = None):" % context.lookup_data('rule_name'), ("INDENT", 2), "engine = rule.rule_base.engine", "if context is None: context = contexts.simple_context()", "try:", ("INDENT", 2), context.lookup_data('prem_fn_head'), context.lookup_data('asserts_fn_lines'), "rule.rule_base.num_fc_rules_triggered += 1", context.lookup_data('prem_fn_tail'), "POPINDENT", "finally:", ("INDENT", 2), "context.done()", "POPINDENT", "POPINDENT", )): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(15).match_data(context, context, ("", "fc_rule.fc_rule('%(name)s', This_rule_base, %(name)s," % {'name': context.lookup_data('rule_name')}, ("INDENT", 2), helpers.add_brackets(context.lookup_data('prem_decl_lines'), '(', '),'), helpers.list_format(context.lookup_data('patterns_out'), '(', '))'), "POPINDENT", )): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) if not flag_2: raise AssertionError("compiler.fc_rule_: 'when' clause 2 failed") if not flag_1: raise AssertionError("compiler.fc_rule_: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_premises0(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 rule.rule_base.num_bc_rule_successes += 1 yield rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_premises1(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'fc_premise', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.fc_premises1: got unexpected plan from when clause 1" flag_2 = False with engine.prove(rule.rule_base.root_name, 'fc_premises', context, (rule.pattern(0), rule.pattern(2), rule.pattern(13), rule.pattern(14), rule.pattern(4), rule.pattern(5), rule.pattern(15), rule.pattern(16), rule.pattern(9), rule.pattern(17), rule.pattern(18), rule.pattern(12), rule.pattern(19),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.fc_premises1: got unexpected plan from when clause 2" mark3 = context.mark(True) if rule.pattern(20).match_data(context, context, context.lookup_data('decl_lines1') + context.lookup_data('decl_lines2')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark3) if not flag_2: raise AssertionError("compiler.fc_premises1: 'when' clause 2 failed") if not flag_1: raise AssertionError("compiler.fc_premises1: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_premise(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 with engine.prove(rule.rule_base.root_name, 'gen_fc_for', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6),)) \ as gen_1: for x_1 in gen_1: assert x_1 is None, \ "compiler.fc_premise: got unexpected plan from when clause 1" mark2 = context.mark(True) if rule.pattern(7).match_data(context, context, (() if context.lookup_data('break_cond') is None else "if %s: break" % context.lookup_data('break_cond'), 'POPINDENT', 'POPINDENT',),): context.end_save_all_undo() mark3 = context.mark(True) if rule.pattern(8).match_data(context, context, context.lookup_data('clause_num') + 1): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(9).match_data(context, context, context.lookup_data('decl_num_in') + 1): context.end_save_all_undo() mark5 = context.mark(True) if rule.pattern(10).match_data(context, context, ("(%r, %r," % (context.lookup_data('kb_name'), context.lookup_data('entity_name')), ('INDENT', 1), helpers.list_format(context.lookup_data('arg_patterns'), '(', '),'), "%s)," % context.lookup_data('multi_match'), "POPINDENT", )): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark5) else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) else: context.end_save_all_undo() context.undo_to_mark(mark2) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def gen_fc_for_false(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, (('STARTING_LINENO', context.lookup_data('start_lineno')), "with knowledge_base.Gen_once if index == %d \\" % \ context.lookup_data('decl_num'), ('INDENT', 9), "else engine.lookup(%r, %r, context," % \ (context.lookup_data('kb_name'), context.lookup_data('entity_name')), ('INDENT', 19), "rule.foreach_patterns(%d)) \\" % context.lookup_data('decl_num'), 'POPINDENT', 'POPINDENT', ('INDENT', 2), "as gen_%d:" % context.lookup_data('decl_num'), "for dummy in gen_%d:" % context.lookup_data('decl_num'), ('ENDING_LINENO', context.lookup_data('end_lineno')), ('INDENT', 2), )): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def gen_fc_for_true(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, (('STARTING_LINENO', context.lookup_data('start_lineno')), "with engine.lookup(%r, %r, context, \\" % \ (context.lookup_data('kb_name'), context.lookup_data('entity_name')), ('INDENT', 19), "rule.foreach_patterns(%d)) \\" % context.lookup_data('decl_num'), 'POPINDENT', ('INDENT', 2), "as gen_%d:" % context.lookup_data('decl_num'), "for dummy in gen_%d:" % context.lookup_data('decl_num'), ('ENDING_LINENO', context.lookup_data('end_lineno')), ('INDENT', 2))): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_first(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "first%d_worked" % context.lookup_data('clause_num')): context.end_save_all_undo() flag_2 = False with engine.prove(rule.rule_base.root_name, 'fc_premises', context, (rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(0), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.fc_first: got unexpected plan from when clause 2" mark3 = context.mark(True) if rule.pattern(13).match_data(context, context, "%s = False" % context.lookup_data('break_cond')): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(14).match_data(context, context, "%s = True" % context.lookup_data('break_cond')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) if not flag_2: raise AssertionError("compiler.fc_first: 'when' clause 2 failed") else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_forall_None(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'fc_premises', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.fc_forall_None: got unexpected plan from when clause 1" mark2 = context.mark(True) if rule.pattern(13).match_data(context, context, context.lookup_data('fn_head1') + context.lookup_data('fn_tail1')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark2) if not flag_1: raise AssertionError("compiler.fc_forall_None: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_forall_require(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "forall%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, "not forall%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() flag_3 = False with engine.prove(rule.rule_base.root_name, 'fc_premises', context, (rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(1), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13),)) \ as gen_3: for x_3 in gen_3: flag_3 = True assert x_3 is None, \ "compiler.fc_forall_require: got unexpected plan from when clause 3" flag_4 = False with engine.prove(rule.rule_base.root_name, 'fc_premises', context, (rule.pattern(2), rule.pattern(4), rule.pattern(14), rule.pattern(15), rule.pattern(0), rule.pattern(6), rule.pattern(16), rule.pattern(17), rule.pattern(10), rule.pattern(18), rule.pattern(19), rule.pattern(13), rule.pattern(20),)) \ as gen_4: for x_4 in gen_4: flag_4 = True assert x_4 is None, \ "compiler.fc_forall_require: got unexpected plan from when clause 4" mark5 = context.mark(True) if rule.pattern(21).match_data(context, context, ("forall%d_worked = True" % context.lookup_data('start_lineno'), context.lookup_data('fn_head1'), "forall%d_worked = False" % context.lookup_data('start_lineno'), context.lookup_data('fn_head2'), "forall%d_worked = True" % context.lookup_data('start_lineno'), context.lookup_data('fn_tail2'), context.lookup_data('fn_tail1'), "if forall%d_worked:" % context.lookup_data('start_lineno'), ("INDENT", 2))): context.end_save_all_undo() mark6 = context.mark(True) if rule.pattern(22).match_data(context, context, context.lookup_data('decl_lines1') + context.lookup_data('decl_lines2')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark6) else: context.end_save_all_undo() context.undo_to_mark(mark5) if not flag_4: raise AssertionError("compiler.fc_forall_require: 'when' clause 4 failed") if not flag_3: raise AssertionError("compiler.fc_forall_require: 'when' clause 3 failed") else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_notany(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "notany%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, "not notany%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() flag_3 = False with engine.prove(rule.rule_base.root_name, 'fc_premises', context, (rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(1), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13),)) \ as gen_3: for x_3 in gen_3: flag_3 = True assert x_3 is None, \ "compiler.fc_notany: got unexpected plan from when clause 3" mark4 = context.mark(True) if rule.pattern(14).match_data(context, context, ("notany%d_worked = True" % context.lookup_data('start_lineno'), context.lookup_data('fn_head1'), "notany%d_worked = False" % context.lookup_data('start_lineno'), context.lookup_data('fn_tail1'), "if notany%d_worked:" % context.lookup_data('start_lineno'), ("INDENT", 2))): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) if not flag_3: raise AssertionError("compiler.fc_notany: 'when' clause 3 failed") else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def fc_python_premise(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, context.lookup_data('clause_num') + 1): context.end_save_all_undo() with engine.prove(rule.rule_base.root_name, 'python_premise', context, (rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7),)) \ as gen_2: for x_2 in gen_2: assert x_2 is None, \ "compiler.fc_python_premise: got unexpected plan from when clause 2" rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def assertions_0(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 rule.rule_base.num_bc_rule_successes += 1 yield rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def assertions_n(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'assertion', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.assertions_n: got unexpected plan from when clause 1" flag_2 = False with engine.prove(rule.rule_base.root_name, 'assertions', context, (rule.pattern(4), rule.pattern(5), rule.pattern(3), rule.pattern(6),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.assertions_n: got unexpected plan from when clause 2" rule.rule_base.num_bc_rule_successes += 1 yield if not flag_2: raise AssertionError("compiler.assertions_n: 'when' clause 2 failed") if not flag_1: raise AssertionError("compiler.assertions_n: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def assertion(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, \ helpers.merge_patterns(context.lookup_data('patterns'), context.lookup_data('patterns_in'))): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, (('STARTING_LINENO', context.lookup_data('start_lineno')), "engine.assert_(%r, %r," % (context.lookup_data('kb_name'), context.lookup_data('entity_name')), ('INDENT', 15), helpers.list_format( ("rule.pattern(%d).as_data(context)" % pat_num for pat_num in context.lookup_data('pat_nums')), '(', ')),'), ('ENDING_LINENO', context.lookup_data('end_lineno')), "POPINDENT", )): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def python_assertion(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 rule.rule_base.num_bc_rule_successes += 1 yield rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_rules(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 bc_plan_lines = [] bc_bc_funs = [] bc_bc_init = [] forall356_worked = True for python_ans in \ context.lookup_data('bc_rules'): mark2 = context.mark(True) if rule.pattern(0).match_data(context, context, python_ans): context.end_save_all_undo() forall356_worked = False flag_3 = False with engine.prove(rule.rule_base.root_name, 'bc_rule', context, (rule.pattern(1), rule.pattern(0), rule.pattern(2), rule.pattern(3), rule.pattern(4),)) \ as gen_3: for x_3 in gen_3: flag_3 = True assert x_3 is None, \ "compiler.bc_rules: got unexpected plan from when clause 3" bc_plan_lines.extend(context.lookup_data('bc_plan1')) bc_bc_funs.append(context.lookup_data('bc_bc_fun1')) bc_bc_init.append(context.lookup_data('bc_bc_init1')) forall356_worked = True if forall356_worked: break if not flag_3: raise AssertionError("compiler.bc_rules: 'when' clause 3 failed") if not forall356_worked: context.undo_to_mark(mark2) break else: context.end_save_all_undo() context.undo_to_mark(mark2) if forall356_worked: mark5 = context.mark(True) if rule.pattern(5).match_data(context, context, tuple(bc_plan_lines)): context.end_save_all_undo() mark6 = context.mark(True) if rule.pattern(6).match_data(context, context, tuple(bc_bc_funs)): context.end_save_all_undo() mark7 = context.mark(True) if rule.pattern(7).match_data(context, context, tuple(bc_bc_init)): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark7) else: context.end_save_all_undo() context.undo_to_mark(mark6) else: context.end_save_all_undo() context.undo_to_mark(mark5) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_rule_(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'bc_premises', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.bc_rule_: got unexpected plan from when clause 1" mark2 = context.mark(True) if rule.pattern(8).match_data(context, context, \ helpers.goal(context.lookup_data('rb_name'), context.lookup_data('name'), context.lookup_data('goal'), context.lookup_data('prem_plan_lines'), context.lookup_data('python_lines'))): context.end_save_all_undo() mark3 = context.mark(True) if rule.pattern(9).match_data(context, context, (context.lookup_data('goal_fn_head'), context.lookup_data('prem_fn_head'), 'rule.rule_base.num_bc_rule_successes += 1', 'yield context' if context.lookup_data('plan_lines') else 'yield', context.lookup_data('prem_fn_tail'), 'rule.rule_base.num_bc_rule_failures += 1', context.lookup_data('goal_fn_tail'), )): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(10).match_data(context, context, (context.lookup_data('goal_decl_lines'), context.lookup_data('prem_decl_lines'), "POPINDENT", )): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) else: context.end_save_all_undo() context.undo_to_mark(mark2) if not flag_1: raise AssertionError("compiler.bc_rule_: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_premises(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'bc_premises1', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.bc_premises: got unexpected plan from when clause 1" mark2 = context.mark(True) if rule.pattern(14).match_data(context, context, helpers.list_format(context.lookup_data('patterns'), '(', '))')): context.end_save_all_undo() mark3 = context.mark(True) if rule.pattern(15).match_data(context, context, ('(' + ' '.join(tuple(repr(plan_var_name) + ',' for plan_var_name in context.lookup_data('plan_var_names'))) + '),',) + context.lookup_data('pat_lines')): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(16).match_data(context, context, tuple(itertools.chain.from_iterable(itertools.chain( (lines for step, lines in context.lookup_data('plan_lines1') if step is None), (lines for step, lines in sorted(((step, lines) for step, lines in context.lookup_data('plan_lines1') if step is not None), key=lambda t: t[0])))))): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) else: context.end_save_all_undo() context.undo_to_mark(mark2) if not flag_1: raise AssertionError("compiler.bc_premises: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_premises1_0(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 rule.rule_base.num_bc_rule_successes += 1 yield rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_premises1_n(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'bc_premise', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.bc_premises1_n: got unexpected plan from when clause 1" flag_2 = False with engine.prove(rule.rule_base.root_name, 'bc_premises1', context, (rule.pattern(0), rule.pattern(1), rule.pattern(3), rule.pattern(14), rule.pattern(15), rule.pattern(5), rule.pattern(6), rule.pattern(8), rule.pattern(16), rule.pattern(10), rule.pattern(17), rule.pattern(18), rule.pattern(19), rule.pattern(20),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.bc_premises1_n: got unexpected plan from when clause 2" mark3 = context.mark(True) if rule.pattern(21).match_data(context, context, context.lookup_data('plan_lines1') + context.lookup_data('plan_lines2')): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(22).match_data(context, context, context.lookup_data('fn_head1') + context.lookup_data('fn_head2')): context.end_save_all_undo() mark5 = context.mark(True) if rule.pattern(23).match_data(context, context, context.lookup_data('fn_tail2') + context.lookup_data('fn_tail1')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark5) else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) if not flag_2: raise AssertionError("compiler.bc_premises1_n: 'when' clause 2 failed") if not flag_1: raise AssertionError("compiler.bc_premises1_n: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_premise(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, context.lookup_data('clause_num') + 1): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, context.lookup_data('kb_name') or "rule.rule_base.root_name"): context.end_save_all_undo() mark3 = context.mark(True) if rule.pattern(2).match_data(context, context, \ helpers.merge_patterns(context.lookup_data('arg_patterns'), context.lookup_data('patterns_in'))): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(3).match_data(context, context, (('STARTING_LINENO', context.lookup_data('start_lineno')), "with engine.prove(%s, %s, context," % (context.lookup_data('kb_name2'), context.lookup_data('entity_name')), ('INDENT', 2), ('INDENT', 16), helpers.list_format(('rule.pattern(%d)' % pat_num for pat_num in context.lookup_data('pat_nums')), '(', ')) \\'), 'POPINDENT', "as gen_%d:" % context.lookup_data('clause_num'), "for x_%d in gen_%d:" % (context.lookup_data('clause_num'), context.lookup_data('clause_num')), ('INDENT', 2), )): context.end_save_all_undo() flag_5 = False with engine.prove(rule.rule_base.root_name, 'add_required', context, (rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(3), rule.pattern(8), rule.pattern(9), rule.pattern(10),)) \ as gen_5: for x_5 in gen_5: flag_5 = True assert x_5 is None, \ "compiler.bc_premise: got unexpected plan from when clause 5" flag_6 = False with engine.prove(rule.rule_base.root_name, 'gen_plan_lines', context, (rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(11), rule.pattern(12), rule.pattern(13), rule.pattern(14), rule.pattern(15), rule.pattern(16), rule.pattern(17), rule.pattern(18),)) \ as gen_6: for x_6 in gen_6: flag_6 = True assert x_6 is None, \ "compiler.bc_premise: got unexpected plan from when clause 6" mark7 = context.mark(True) if rule.pattern(19).match_data(context, context, helpers.merge_patterns(context.lookup_data('plan_vars_needed'), context.lookup_data('plan_var_names_in'))): context.end_save_all_undo() mark8 = context.mark(True) if rule.pattern(20).match_data(context, context, context.lookup_data('fn_head2') + context.lookup_data('fn_head3') + (('ENDING_LINENO', context.lookup_data('end_lineno')),)): context.end_save_all_undo() mark9 = context.mark(True) if rule.pattern(21).match_data(context, context, (context.lookup_data('fn_tail3'), () if context.lookup_data('break_cond') is None else "if %s: break" % context.lookup_data('break_cond'), context.lookup_data('fn_tail2'))): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark9) else: context.end_save_all_undo() context.undo_to_mark(mark8) else: context.end_save_all_undo() context.undo_to_mark(mark7) if not flag_6: raise AssertionError("compiler.bc_premise: 'when' clause 6 failed") if not flag_5: raise AssertionError("compiler.bc_premise: 'when' clause 5 failed") else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_first(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "first%d_worked" % context.lookup_data('clause_num')): context.end_save_all_undo() flag_2 = False with engine.prove(rule.rule_base.root_name, 'bc_premises1', context, (rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(0), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.bc_first: got unexpected plan from when clause 2" flag_3 = False with engine.prove(rule.rule_base.root_name, 'add_required', context, (rule.pattern(14), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(12), rule.pattern(13), rule.pattern(15), rule.pattern(16),)) \ as gen_3: for x_3 in gen_3: flag_3 = True assert x_3 is None, \ "compiler.bc_first: got unexpected plan from when clause 3" mark4 = context.mark(True) if rule.pattern(17).match_data(context, context, "%s = False" % context.lookup_data('break_cond')): context.end_save_all_undo() mark5 = context.mark(True) if rule.pattern(18).match_data(context, context, "%s = True" % context.lookup_data('break_cond')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark5) else: context.end_save_all_undo() context.undo_to_mark(mark4) if not flag_3: raise AssertionError("compiler.bc_first: 'when' clause 3 failed") if not flag_2: raise AssertionError("compiler.bc_first: 'when' clause 2 failed") else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_forall_None(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 flag_1 = False with engine.prove(rule.rule_base.root_name, 'bc_premises1', context, (rule.pattern(0), rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13),)) \ as gen_1: for x_1 in gen_1: flag_1 = True assert x_1 is None, \ "compiler.bc_forall_None: got unexpected plan from when clause 1" mark2 = context.mark(True) if rule.pattern(14).match_data(context, context, context.lookup_data('fn_head1') + context.lookup_data('fn_tail')): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark2) if not flag_1: raise AssertionError("compiler.bc_forall_None: 'when' clause 1 failed") rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_forall_require(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "forall%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, "not forall%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() flag_3 = False with engine.prove(rule.rule_base.root_name, 'bc_premises1', context, (rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(1), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13), rule.pattern(14),)) \ as gen_3: for x_3 in gen_3: flag_3 = True assert x_3 is None, \ "compiler.bc_forall_require: got unexpected plan from when clause 3" flag_4 = False with engine.prove(rule.rule_base.root_name, 'bc_premises1', context, (rule.pattern(2), rule.pattern(3), rule.pattern(5), rule.pattern(15), rule.pattern(16), rule.pattern(0), rule.pattern(7), rule.pattern(9), rule.pattern(17), rule.pattern(11), rule.pattern(18), rule.pattern(12), rule.pattern(19), rule.pattern(20),)) \ as gen_4: for x_4 in gen_4: flag_4 = True assert x_4 is None, \ "compiler.bc_forall_require: got unexpected plan from when clause 4" mark5 = context.mark(True) if rule.pattern(21).match_data(context, context, ("forall%d_worked = True" % context.lookup_data('start_lineno'), context.lookup_data('fn_head1'), "forall%d_worked = False" % context.lookup_data('start_lineno'), context.lookup_data('fn_head2'), "forall%d_worked = True" % context.lookup_data('start_lineno'), context.lookup_data('fn_tail2'), context.lookup_data('fn_tail1'), "if forall%d_worked:" % context.lookup_data('start_lineno'), ("INDENT", 2))): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark5) if not flag_4: raise AssertionError("compiler.bc_forall_require: 'when' clause 4 failed") if not flag_3: raise AssertionError("compiler.bc_forall_require: 'when' clause 3 failed") else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_notany(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, "notany%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, "not notany%d_worked" % context.lookup_data('start_lineno')): context.end_save_all_undo() flag_3 = False with engine.prove(rule.rule_base.root_name, 'bc_premises1', context, (rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(1), rule.pattern(7), rule.pattern(8), rule.pattern(9), rule.pattern(10), rule.pattern(11), rule.pattern(12), rule.pattern(13), rule.pattern(14),)) \ as gen_3: for x_3 in gen_3: flag_3 = True assert x_3 is None, \ "compiler.bc_notany: got unexpected plan from when clause 3" mark4 = context.mark(True) if rule.pattern(15).match_data(context, context, ("notany%d_worked = True" % context.lookup_data('start_lineno'), context.lookup_data('fn_head1'), "notany%d_worked = False" % context.lookup_data('start_lineno'), context.lookup_data('fn_tail1'), "if notany%d_worked:" % context.lookup_data('start_lineno'), ("INDENT", 2)) ): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) if not flag_3: raise AssertionError("compiler.bc_notany: 'when' clause 3 failed") else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def no_plan(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, ('assert x_%d is None, \\' % context.lookup_data('clause_num'), ('INDENT', 2), '"%(rb_name)s.%(rule_name)s: got unexpected plan from ' 'when clause %(clause_num)d"' % {'clause_num': context.lookup_data('clause_num'), 'rb_name': context.lookup_data('rb_name'), 'rule_name': context.lookup_data('rule_name')}, 'POPINDENT',)): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def as_plan(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, \ helpers.merge_pattern("contexts.variable(%r)" % context.lookup_data('pat_var_name'), context.lookup_data('patterns_in'))): context.end_save_all_undo() flag_2 = False with engine.prove(rule.rule_base.root_name, 'plan_bindings', context, (rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.as_plan: got unexpected plan from when clause 2" rule.rule_base.num_bc_rule_successes += 1 yield if not flag_2: raise AssertionError("compiler.as_plan: 'when' clause 2 failed") else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def plan_spec(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, \ helpers.merge_pattern("contexts.variable(%r)" % context.lookup_data('plan_var_name'), context.lookup_data('patterns_in'))): context.end_save_all_undo() flag_2 = False with engine.prove(rule.rule_base.root_name, 'plan_bindings', context, (rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7),)) \ as gen_2: for x_2 in gen_2: flag_2 = True assert x_2 is None, \ "compiler.plan_spec: got unexpected plan from when clause 2" rule.rule_base.num_bc_rule_successes += 1 yield if not flag_2: raise AssertionError("compiler.plan_spec: 'when' clause 2 failed") else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def illegal_plan_spec(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, helpers.syntax_error("illegal plan_spec in forall", context.lookup_data('lineno'), context.lookup_data('lexpos'))): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def plan_bindings(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, ('assert x_%d is not None, \\' % context.lookup_data('clause_num'), ('INDENT', 2), '"%(rb_name)s.%(rule_name)s: expected plan from ' 'when clause %(clause_num)d"' % {'clause_num': context.lookup_data('clause_num'), 'rb_name': context.lookup_data('rb_name'), 'rule_name': context.lookup_data('rule_name')}, 'POPINDENT', "mark%d = context.mark(True)" % context.lookup_data('clause_num'), "if not rule.pattern(%d).match_data(context, context, " "x_%d):" % (context.lookup_data('pat_num'), context.lookup_data('clause_num')), ('INDENT', 2), 'raise AssertionError("%(rb_name)s.%(rule_name)s: ' 'plan match to $%(plan_var_name)s failed in ' 'when clause %(clause_num)d")' % {'clause_num': context.lookup_data('clause_num'), 'plan_var_name': context.lookup_data('plan_var_name'), 'rb_name': context.lookup_data('rb_name'), 'rule_name': context.lookup_data('rule_name')}, 'POPINDENT', "context.end_save_all_undo()")): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, ("context.undo_to_mark(mark%d)" % context.lookup_data('clause_num'),)): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def not_required(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 rule.rule_base.num_bc_rule_successes += 1 yield rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def required(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, ("flag_%d = False" % context.lookup_data('clause_num'), context.lookup_data('fn_head1'), "flag_%d = True" % context.lookup_data('clause_num'), )): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, (context.lookup_data('fn_tail1'), "if not flag_%d:" % context.lookup_data('clause_num'), ("INDENT", 2), "raise AssertionError(\"%s.%s: 'when' clause %d failed\")" % (context.lookup_data('rb_name'), context.lookup_data('rule_name'), context.lookup_data('clause_num')), "POPINDENT", )): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def bc_python_premise(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, context.lookup_data('clause_num') + 1): context.end_save_all_undo() with engine.prove(rule.rule_base.root_name, 'python_premise', context, (rule.pattern(1), rule.pattern(2), rule.pattern(3), rule.pattern(4), rule.pattern(5), rule.pattern(6), rule.pattern(7),)) \ as gen_2: for x_2 in gen_2: assert x_2 is None, \ "compiler.bc_python_premise: got unexpected plan from when clause 2" rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def python_eq(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, \ helpers.merge_pattern(context.lookup_data('pattern'), context.lookup_data('patterns_in'))): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, context.lookup_data('python_code')[:-1] + (context.lookup_data('python_code')[-1] + '):',)): context.end_save_all_undo() mark3 = context.mark(True) if rule.pattern(2).match_data(context, context, ("mark%d = context.mark(True)" % context.lookup_data('clause_num'), "if rule.pattern(%d).match_data(context, context," % context.lookup_data('pat_num'), ('INDENT', 2), ('INDENT', 5), ('STARTING_LINENO', context.lookup_data('start_lineno')), context.lookup_data('python_code2'), ('ENDING_LINENO', context.lookup_data('end_lineno')), "POPINDENT", "context.end_save_all_undo()", )): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(3).match_data(context, context, ('POPINDENT', "else: context.end_save_all_undo()", "context.undo_to_mark(mark%d)" % context.lookup_data('clause_num'),)): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def python_in(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, \ helpers.merge_pattern(context.lookup_data('pattern'), context.lookup_data('patterns_in'))): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, context.lookup_data('python_code')[:-1] + (context.lookup_data('python_code')[-1] + ':',)): context.end_save_all_undo() mark3 = context.mark(True) if rule.pattern(2).match_data(context, context, ("for python_ans in \\", ('INDENT', 2), ('INDENT', 2), ('STARTING_LINENO', context.lookup_data('start_lineno')), context.lookup_data('python_code2'), ('ENDING_LINENO', context.lookup_data('end_lineno')), 'POPINDENT', "mark%d = context.mark(True)" % context.lookup_data('clause_num'), "if rule.pattern(%d).match_data(context, context, " "python_ans):" % context.lookup_data('pat_num'), ('INDENT', 2), "context.end_save_all_undo()", )): context.end_save_all_undo() mark4 = context.mark(True) if rule.pattern(3).match_data(context, context, ( () if context.lookup_data('break_cond') is None else ("if %s:" % context.lookup_data('break_cond'), ('INDENT', 2), "context.undo_to_mark(mark%d)" % context.lookup_data('clause_num'), "break", 'POPINDENT',), 'POPINDENT', "else: context.end_save_all_undo()", "context.undo_to_mark(mark%d)" % context.lookup_data('clause_num'), 'POPINDENT',)): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark4) else: context.end_save_all_undo() context.undo_to_mark(mark3) else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def python_check(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 mark1 = context.mark(True) if rule.pattern(0).match_data(context, context, context.lookup_data('python_code')[:-1] + (context.lookup_data('python_code')[-1] + ':',)): context.end_save_all_undo() mark2 = context.mark(True) if rule.pattern(1).match_data(context, context, (('STARTING_LINENO', context.lookup_data('start_lineno')), "if " + context.lookup_data('python_code2')[0].strip(), ('INDENT', 3), context.lookup_data('python_code2')[1:], 'POPINDENT', ('ENDING_LINENO', context.lookup_data('end_lineno')), ('INDENT', 2), )): context.end_save_all_undo() rule.rule_base.num_bc_rule_successes += 1 yield else: context.end_save_all_undo() context.undo_to_mark(mark2) else: context.end_save_all_undo() context.undo_to_mark(mark1) rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def python_block(rule, arg_patterns, arg_context): engine = rule.rule_base.engine patterns = rule.goal_arg_patterns() if len(arg_patterns) == len(patterns): context = contexts.bc_context(rule) try: if all(itertools.imap(lambda pat, arg: pat.match_pattern(context, context, arg, arg_context), patterns, arg_patterns)): rule.rule_base.num_bc_rules_matched += 1 rule.rule_base.num_bc_rule_successes += 1 yield rule.rule_base.num_bc_rule_failures += 1 finally: context.done() def populate(engine): This_rule_base = engine.get_create('compiler') bc_rule.bc_rule('file', This_rule_base, 'compile', file, None, (contexts.variable('generated_root_pkg'), contexts.variable('rb_name'), pattern.pattern_tuple((pattern.pattern_literal('file'), contexts.variable('parent'), pattern.pattern_tuple((contexts.variable('fc_rules'), contexts.variable('fc_extra_lines'),), None), pattern.pattern_tuple((contexts.variable('bc_rules'), contexts.variable('bc_extra_lines'), contexts.variable('plan_extra_lines'),), None),), None), contexts.variable('fc_lines'), contexts.variable('bc_lines'), contexts.variable('plan_lines'),), (), (contexts.variable('fc_head'), contexts.variable('bc_head'), contexts.variable('plan_head'), contexts.variable('rb_name'), contexts.variable('parent'), contexts.variable('decl_line'), contexts.variable('fc_rules'), contexts.variable('fc_fun_lines'), contexts.variable('fc_init_lines'), contexts.variable('bc_rules'), contexts.variable('bc_plan_lines'), contexts.variable('bc_bc_fun_lines'), contexts.variable('bc_bc_init_lines'), contexts.variable('fc_lines'), contexts.variable('plan_lines'), contexts.variable('bc_lines'),)) bc_rule.bc_rule('rule_decl', This_rule_base, 'rule_decl', rule_decl, None, (contexts.variable('rb_name'), pattern.pattern_literal(None), contexts.variable('decl_line'),), (), (contexts.variable('decl_line'),)) bc_rule.bc_rule('rule_decl_with_parent', This_rule_base, 'rule_decl', rule_decl_with_parent, None, (contexts.variable('rb_name'), pattern.pattern_tuple((pattern.pattern_literal('parent'), contexts.variable('parent'), contexts.variable('excluded_symbols'),), None), contexts.variable('decl_line'),), (), (contexts.variable('decl_line'),)) bc_rule.bc_rule('fc_rules', This_rule_base, 'fc_rules', fc_rules, None, (contexts.variable('fc_rules'), contexts.variable('fc_funs'), contexts.variable('fc_init'),), (), (contexts.variable('fc_rule'), contexts.variable('fc_fun_1'), contexts.variable('fc_init_1'), contexts.variable('fc_funs'), contexts.variable('fc_init'),)) bc_rule.bc_rule('fc_rule_', This_rule_base, 'fc_rule', fc_rule_, None, (pattern.pattern_tuple((pattern.pattern_literal('fc_rule'), contexts.variable('rule_name'), contexts.variable('fc_premises'), contexts.variable('assertions'),), None), contexts.variable('fc_fun'), contexts.variable('fc_init'),), (), (contexts.variable('rule_name'), pattern.pattern_literal(0), contexts.anonymous('_'), contexts.variable('fc_premises'), pattern.pattern_literal(None), pattern.pattern_literal(False), contexts.variable('prem_fn_head'), contexts.variable('prem_fn_tail'), contexts.variable('prem_decl_lines'), pattern.pattern_literal(()), contexts.variable('patterns_out1'), contexts.variable('assertions'), contexts.variable('asserts_fn_lines'), contexts.variable('patterns_out'), contexts.variable('fc_fun'), contexts.variable('fc_init'),)) bc_rule.bc_rule('fc_premises0', This_rule_base, 'fc_premises', fc_premises0, None, (contexts.anonymous('_'), contexts.variable('clause_num'), contexts.variable('clause_num'), pattern.pattern_literal(()), contexts.anonymous('_'), contexts.anonymous('_'), pattern.pattern_literal(()), pattern.pattern_literal(()), contexts.variable('decl_num_in'), contexts.variable('decl_num_in'), pattern.pattern_literal(()), contexts.variable('patterns_in'), contexts.variable('patterns_in'),), (), ()) bc_rule.bc_rule('fc_premises1', This_rule_base, 'fc_premises', fc_premises1, None, (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((contexts.variable('first_prem'),), contexts.variable('rest_prems')), contexts.variable('break_cond'), contexts.variable('multi_match'), pattern.pattern_tuple((contexts.variable('fn_head1'),), contexts.variable('fn_head2')), pattern.pattern_tuple((contexts.variable('fn_tail2'),), contexts.variable('fn_tail1')), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num1'), contexts.variable('first_prem'), contexts.variable('break_cond'), contexts.variable('multi_match'), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('decl_num_in'), contexts.variable('decl_num_out1'), contexts.variable('decl_lines1'), contexts.variable('patterns_in'), contexts.variable('patterns_out1'), contexts.variable('next_clause_num'), contexts.variable('rest_prems'), contexts.variable('fn_head2'), contexts.variable('fn_tail2'), contexts.variable('decl_num_out'), contexts.variable('decl_lines2'), contexts.variable('patterns_out'), contexts.variable('decl_lines'),)) bc_rule.bc_rule('fc_premise', This_rule_base, 'fc_premise', fc_premise, None, (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('fc_premise'), contexts.variable('kb_name'), contexts.variable('entity_name'), contexts.variable('arg_patterns'), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), contexts.variable('break_cond'), contexts.variable('multi_match'), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_in'),), (), (contexts.variable('kb_name'), contexts.variable('entity_name'), contexts.variable('start_lineno'), contexts.variable('end_lineno'), contexts.variable('multi_match'), contexts.variable('decl_num_in'), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('next_clause_num'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'),)) bc_rule.bc_rule('gen_fc_for_false', This_rule_base, 'gen_fc_for', gen_fc_for_false, None, (contexts.variable('kb_name'), contexts.variable('entity_name'), contexts.variable('start_lineno'), contexts.variable('end_lineno'), pattern.pattern_literal(False), contexts.variable('decl_num'), contexts.variable('fn_head'),), (), (contexts.variable('fn_head'),)) bc_rule.bc_rule('gen_fc_for_true', This_rule_base, 'gen_fc_for', gen_fc_for_true, None, (contexts.variable('kb_name'), contexts.variable('entity_name'), contexts.variable('start_lineno'), contexts.variable('end_lineno'), pattern.pattern_literal(True), contexts.variable('decl_num'), contexts.variable('fn_head'),), (), (contexts.variable('fn_head'),)) bc_rule.bc_rule('fc_first', This_rule_base, 'fc_premise', fc_first, None, (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('fc_first'), contexts.variable('premises1'), contexts.anonymous('_'),), None), contexts.anonymous('_'), contexts.anonymous('_'), pattern.pattern_tuple((contexts.variable('init_worked'), contexts.variable('fn_head'), contexts.variable('set_worked'),), None), contexts.variable('fn_tail'), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (contexts.variable('break_cond'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('premises1'), pattern.pattern_literal(True), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('init_worked'), contexts.variable('set_worked'),)) bc_rule.bc_rule('fc_forall_None', This_rule_base, 'fc_premise', fc_forall_None, None, (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('fc_forall'), contexts.variable('premises1'), pattern.pattern_literal(None), contexts.anonymous('_'), contexts.anonymous('_'),), None), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('fn_head'), pattern.pattern_literal(()), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('premises1'), pattern.pattern_literal(None), pattern.pattern_literal(True), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'),)) bc_rule.bc_rule('fc_forall_require', This_rule_base, 'fc_premise', fc_forall_require, None, (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('fc_forall'), contexts.variable('premises1'), contexts.variable('require'), contexts.variable('start_lineno'), contexts.anonymous('_'),), None), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('fn_head'), pattern.pattern_literal(("POPINDENT",)), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (contexts.variable('break_true'), contexts.variable('break_false'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num1'), contexts.variable('premises1'), pattern.pattern_literal(True), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('decl_num_in'), contexts.variable('decl_num_out1'), contexts.variable('decl_lines1'), contexts.variable('patterns_in'), contexts.variable('patterns_out1'), contexts.variable('next_clause_num'), contexts.variable('require'), contexts.variable('fn_head2'), contexts.variable('fn_tail2'), contexts.variable('decl_num_out'), contexts.variable('decl_lines2'), contexts.variable('patterns_out'), contexts.variable('fn_head'), contexts.variable('decl_lines'),)) bc_rule.bc_rule('fc_notany', This_rule_base, 'fc_premise', fc_notany, None, (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('fc_notany'), contexts.variable('premises'), contexts.variable('start_lineno'),), None), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('fn_head'), pattern.pattern_literal(("POPINDENT",)), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (contexts.variable('break_true'), contexts.variable('break_false'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('premises'), pattern.pattern_literal(True), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('decl_num_in'), contexts.variable('decl_num_out'), contexts.variable('decl_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'),)) bc_rule.bc_rule('fc_python_premise', This_rule_base, 'fc_premise', fc_python_premise, None, (contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('python_premise'), contexts.variable('break_cond'), contexts.anonymous('_'), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('decl_num_in'), contexts.variable('decl_num_in'), pattern.pattern_literal(()), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (contexts.variable('next_clause_num'), contexts.variable('clause_num'), contexts.variable('python_premise'), contexts.variable('break_cond'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('assertions_0', This_rule_base, 'assertions', assertions_0, None, (pattern.pattern_literal(()), pattern.pattern_literal(()), contexts.variable('patterns_in'), contexts.variable('patterns_in'),), (), ()) bc_rule.bc_rule('assertions_n', This_rule_base, 'assertions', assertions_n, None, (pattern.pattern_tuple((contexts.variable('first_assertion'),), contexts.variable('rest_assertions')), pattern.pattern_tuple((contexts.variable('fn_lines1'),), contexts.variable('fn_lines2')), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (contexts.variable('first_assertion'), contexts.variable('fn_lines1'), contexts.variable('patterns_in'), contexts.variable('patterns_out1'), contexts.variable('rest_assertions'), contexts.variable('fn_lines2'), contexts.variable('patterns_out'),)) bc_rule.bc_rule('assertion', This_rule_base, 'assertion', assertion, None, (pattern.pattern_tuple((pattern.pattern_literal('assert'), contexts.variable('kb_name'), contexts.variable('entity_name'), contexts.variable('patterns'), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), contexts.variable('fn_lines'), contexts.variable('patterns_in'), contexts.variable('patterns_out'),), (), (pattern.pattern_tuple((contexts.variable('pat_nums'), contexts.variable('patterns_out'),), None), contexts.variable('fn_lines'),)) bc_rule.bc_rule('python_assertion', This_rule_base, 'assertion', python_assertion, None, (pattern.pattern_tuple((pattern.pattern_literal('python_assertion'), pattern.pattern_tuple((contexts.variable('python_code'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'),), None), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), pattern.pattern_tuple((pattern.pattern_tuple((pattern.pattern_literal('STARTING_LINENO'), contexts.variable('start_lineno'),), None), contexts.variable('python_code'), pattern.pattern_tuple((pattern.pattern_literal('ENDING_LINENO'), contexts.variable('end_lineno'),), None),), None), contexts.variable('patterns_in'), contexts.variable('patterns_in'),), (), ()) bc_rule.bc_rule('bc_rules', This_rule_base, 'bc_rules', bc_rules, None, (contexts.variable('rb_name'), contexts.variable('bc_rules'), contexts.variable('bc_plan_lines'), contexts.variable('bc_bc_funs'), contexts.variable('bc_bc_init'),), (), (contexts.variable('bc_rule'), contexts.variable('rb_name'), contexts.variable('bc_plan1'), contexts.variable('bc_bc_fun1'), contexts.variable('bc_bc_init1'), contexts.variable('bc_plan_lines'), contexts.variable('bc_bc_funs'), contexts.variable('bc_bc_init'),)) bc_rule.bc_rule('bc_rule_', This_rule_base, 'bc_rule', bc_rule_, None, (contexts.variable('rb_name'), pattern.pattern_tuple((pattern.pattern_literal('bc_rule'), contexts.variable('name'), contexts.variable('goal'), contexts.variable('bc_premises'), contexts.variable('python_lines'), contexts.variable('plan_vars_needed'),), None), contexts.variable('plan_lines'), contexts.variable('bc_fun_lines'), contexts.variable('bc_init_lines'),), (), (contexts.variable('rb_name'), contexts.variable('name'), contexts.variable('bc_premises'), contexts.variable('plan_vars_needed'), contexts.variable('prem_plan_lines'), contexts.variable('prem_fn_head'), contexts.variable('prem_fn_tail'), contexts.variable('prem_decl_lines'), pattern.pattern_tuple((contexts.variable('plan_lines'), contexts.variable('goal_fn_head'), contexts.variable('goal_fn_tail'), contexts.variable('goal_decl_lines'),), None), contexts.variable('bc_fun_lines'), contexts.variable('bc_init_lines'),)) bc_rule.bc_rule('bc_premises', This_rule_base, 'bc_premises', bc_premises, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('bc_premises'), contexts.variable('plan_vars_needed'), contexts.variable('plan_lines'), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('decl_lines'),), (), (contexts.variable('rb_name'), contexts.variable('rule_name'), pattern.pattern_literal(1), contexts.anonymous('_'), contexts.variable('bc_premises'), pattern.pattern_literal(None), pattern.pattern_literal(True), pattern.pattern_literal(()), contexts.variable('patterns'), contexts.variable('plan_vars_needed'), contexts.variable('plan_var_names'), contexts.variable('plan_lines1'), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('pat_lines'), contexts.variable('decl_lines'), contexts.variable('plan_lines'),)) bc_rule.bc_rule('bc_premises1_0', This_rule_base, 'bc_premises1', bc_premises1_0, None, (contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('clause_num'), contexts.variable('clause_num'), pattern.pattern_literal(()), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('patterns'), contexts.variable('patterns'), contexts.variable('plan_var_names'), contexts.variable('plan_var_names'), pattern.pattern_literal(()), pattern.pattern_literal(()), pattern.pattern_literal(()),), (), ()) bc_rule.bc_rule('bc_premises1_n', This_rule_base, 'bc_premises1', bc_premises1_n, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((contexts.variable('first_prem'),), contexts.variable('rest_prems')), contexts.variable('break_cond'), contexts.variable('allow_plan'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out'), contexts.variable('plan_lines'), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num1'), contexts.variable('first_prem'), contexts.variable('break_cond'), contexts.variable('allow_plan'), contexts.variable('patterns_in'), contexts.variable('patterns_out1'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out1'), contexts.variable('plan_lines1'), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('next_clause_num'), contexts.variable('rest_prems'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_out'), contexts.variable('plan_lines2'), contexts.variable('fn_head2'), contexts.variable('fn_tail2'), contexts.variable('plan_lines'), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('bc_premise', This_rule_base, 'bc_premise', bc_premise, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('bc_premise'), contexts.variable('required'), contexts.variable('kb_name'), contexts.variable('entity_name'), contexts.variable('arg_patterns'), contexts.variable('plan_spec'), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), contexts.variable('break_cond'), contexts.variable('allow_plan'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out'), contexts.variable('plan_lines'), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), (contexts.variable('next_clause_num'), contexts.variable('kb_name2'), pattern.pattern_tuple((contexts.variable('pat_nums'), contexts.variable('patterns_out1'),), None), contexts.variable('fn_head1'), contexts.variable('required'), contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), pattern.pattern_literal(('POPINDENT', 'POPINDENT',)), contexts.variable('fn_head2'), contexts.variable('fn_tail2'), contexts.variable('plan_spec'), contexts.variable('allow_plan'), contexts.variable('patterns_out1'), contexts.variable('patterns_out'), contexts.variable('fn_head3'), contexts.variable('fn_tail3'), contexts.variable('plan_lines'), contexts.variable('plan_vars_needed'), pattern.pattern_tuple((contexts.anonymous('_'), contexts.variable('plan_var_names_out'),), None), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('bc_first', This_rule_base, 'bc_premise', bc_first, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('bc_first'), contexts.variable('required'), contexts.variable('bc_premises'), contexts.anonymous('_'),), None), contexts.anonymous('_'), contexts.variable('allow_plan'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out'), contexts.variable('plan_lines'), pattern.pattern_tuple((contexts.variable('init_worked'), contexts.variable('fn_head'), contexts.variable('set_worked'),), None), contexts.variable('fn_tail'),), (), (contexts.variable('break_cond'), contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('bc_premises'), contexts.variable('allow_plan'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out'), contexts.variable('plan_lines'), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('required'), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('init_worked'), contexts.variable('set_worked'),)) bc_rule.bc_rule('bc_forall_None', This_rule_base, 'bc_premise', bc_forall_None, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('bc_forall'), contexts.variable('bc_premises'), pattern.pattern_literal(None), contexts.anonymous('_'), contexts.anonymous('_'),), None), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out'), contexts.variable('plan_lines'), contexts.variable('fn_head'), pattern.pattern_literal(()),), (), (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('bc_premises'), pattern.pattern_literal(None), pattern.pattern_literal(False), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out'), contexts.variable('plan_lines'), contexts.variable('fn_head1'), contexts.variable('fn_tail'), contexts.variable('fn_head'),)) bc_rule.bc_rule('bc_forall_require', This_rule_base, 'bc_premise', bc_forall_require, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('bc_forall'), contexts.variable('premises1'), contexts.variable('require'), contexts.variable('start_lineno'), contexts.anonymous('_'),), None), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out'), pattern.pattern_literal(()), contexts.variable('fn_head'), pattern.pattern_literal(("POPINDENT",)),), (), (contexts.variable('break_true'), contexts.variable('break_false'), contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num1'), contexts.variable('premises1'), pattern.pattern_literal(False), contexts.variable('patterns_in'), contexts.variable('patterns_out1'), contexts.variable('plan_var_names_in'), contexts.variable('plan_var_names_out1'), pattern.pattern_literal(()), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('next_clause_num'), contexts.variable('require'), contexts.variable('patterns_out'), contexts.variable('plan_var_names_out'), contexts.variable('fn_head2'), contexts.variable('fn_tail2'), contexts.variable('fn_head'),)) bc_rule.bc_rule('bc_notany', This_rule_base, 'bc_premise', bc_notany, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), pattern.pattern_tuple((pattern.pattern_literal('bc_notany'), contexts.variable('bc_premises'), contexts.variable('start_lineno'),), None), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_in'), contexts.variable('plan_var_out'), pattern.pattern_literal(()), contexts.variable('fn_head'), pattern.pattern_literal(("POPINDENT",)),), (), (contexts.variable('break_true'), contexts.variable('break_false'), contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('bc_premises'), pattern.pattern_literal(False), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_in'), contexts.variable('plan_var_out'), pattern.pattern_literal(()), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('fn_head'),)) bc_rule.bc_rule('no_plan', This_rule_base, 'gen_plan_lines', no_plan, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), pattern.pattern_literal(None), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_in'), contexts.variable('fn_head'), pattern.pattern_literal(()), pattern.pattern_literal(()), pattern.pattern_literal(()),), (), (contexts.variable('fn_head'),)) bc_rule.bc_rule('as_plan', This_rule_base, 'gen_plan_lines', as_plan, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), pattern.pattern_tuple((pattern.pattern_literal('as'), contexts.variable('pat_var_name'),), None), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'), contexts.variable('fn_tail'), pattern.pattern_literal(()), pattern.pattern_literal(()),), (), (pattern.pattern_tuple((contexts.variable('pat_num'), contexts.variable('patterns_out'),), None), contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('pat_var_name'), contexts.variable('pat_num'), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('plan_spec', This_rule_base, 'gen_plan_lines', plan_spec, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), pattern.pattern_tuple((pattern.pattern_literal('plan_spec'), contexts.variable('step_num'), contexts.variable('plan_var_name'), contexts.variable('python_code'), contexts.variable('plan_vars_needed'), contexts.anonymous('_'), contexts.anonymous('_'),), None), pattern.pattern_literal(True), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'), contexts.variable('fn_tail'), pattern.pattern_tuple((pattern.pattern_tuple((contexts.variable('step_num'), contexts.variable('python_code'),), None),), None), contexts.variable('plan_vars_needed'),), (), (pattern.pattern_tuple((contexts.variable('pat_num'), contexts.variable('patterns_out'),), None), contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('plan_var_name'), contexts.variable('pat_num'), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('illegal_plan_spec', This_rule_base, 'gen_plan_lines', illegal_plan_spec, None, (contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'), pattern.pattern_tuple((pattern.pattern_literal('plan_spec'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('lineno'), contexts.variable('lexpos'),), None), pattern.pattern_literal(False), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'),), (), (contexts.anonymous('_'),)) bc_rule.bc_rule('plan_bindings', This_rule_base, 'plan_bindings', plan_bindings, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('plan_var_name'), contexts.variable('pat_num'), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), (contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('not_required', This_rule_base, 'add_required', not_required, None, (pattern.pattern_literal(False), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.variable('fn_head'), contexts.variable('fn_tail'), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), ()) bc_rule.bc_rule('required', This_rule_base, 'add_required', required, None, (pattern.pattern_literal(True), contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('fn_head1'), contexts.variable('fn_tail1'), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), (contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('bc_python_premise', This_rule_base, 'bc_premise', bc_python_premise, None, (contexts.variable('rb_name'), contexts.variable('rule_name'), contexts.variable('clause_num'), contexts.variable('next_clause_num'), contexts.variable('python_premise'), contexts.variable('break_cond'), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('plan_var_names'), contexts.variable('plan_var_names'), pattern.pattern_literal(()), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), (contexts.variable('next_clause_num'), contexts.variable('clause_num'), contexts.variable('python_premise'), contexts.variable('break_cond'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('python_eq', This_rule_base, 'python_premise', python_eq, None, (contexts.variable('clause_num'), pattern.pattern_tuple((pattern.pattern_literal('python_eq'), contexts.variable('pattern'), pattern.pattern_tuple((contexts.variable('python_code'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'),), None), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), (pattern.pattern_tuple((contexts.variable('pat_num'), contexts.variable('patterns_out'),), None), contexts.variable('python_code2'), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('python_in', This_rule_base, 'python_premise', python_in, None, (contexts.variable('clause_num'), pattern.pattern_tuple((pattern.pattern_literal('python_in'), contexts.variable('pattern'), pattern.pattern_tuple((contexts.variable('python_code'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'),), None), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), contexts.variable('break_cond'), contexts.variable('patterns_in'), contexts.variable('patterns_out'), contexts.variable('fn_head'), contexts.variable('fn_tail'),), (), (pattern.pattern_tuple((contexts.variable('pat_num'), contexts.variable('patterns_out'),), None), contexts.variable('python_code2'), contexts.variable('fn_head'), contexts.variable('fn_tail'),)) bc_rule.bc_rule('python_check', This_rule_base, 'python_premise', python_check, None, (contexts.variable('clause_num'), pattern.pattern_tuple((pattern.pattern_literal('python_check'), pattern.pattern_tuple((contexts.variable('python_code'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'),), None), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_in'), contexts.variable('fn_head'), pattern.pattern_literal(('POPINDENT',)),), (), (contexts.variable('python_code2'), contexts.variable('fn_head'),)) bc_rule.bc_rule('python_block', This_rule_base, 'python_premise', python_block, None, (contexts.variable('clause_num'), pattern.pattern_tuple((pattern.pattern_literal('python_block'), pattern.pattern_tuple((contexts.variable('python_code'), contexts.anonymous('_'), contexts.anonymous('_'), contexts.anonymous('_'),), None), contexts.variable('start_lineno'), contexts.variable('end_lineno'),), None), contexts.anonymous('_'), contexts.variable('patterns_in'), contexts.variable('patterns_in'), pattern.pattern_tuple((pattern.pattern_tuple((pattern.pattern_literal('STARTING_LINENO'), contexts.variable('start_lineno'),), None), contexts.variable('python_code'), pattern.pattern_tuple((pattern.pattern_literal('ENDING_LINENO'), contexts.variable('end_lineno'),), None),), None), pattern.pattern_literal(()),), (), ()) from pyke.krb_compiler import helpers Krb_filename = '../compiler.krb' Krb_lineno_map = ( ((16, 20), (24, 28)), ((24, 24), (30, 30)), ((28, 28), (31, 31)), ((32, 32), (32, 32)), ((35, 43), (33, 33)), ((45, 53), (34, 34)), ((55, 65), (35, 36)), ((68, 80), (37, 49)), ((84, 89), (50, 55)), ((93, 108), (56, 71)), ((140, 144), (74, 74)), ((148, 148), (76, 76)), ((164, 168), (79, 79)), ((172, 174), (81, 83)), ((190, 194), (86, 86)), ((196, 197), (88, 90)), ((200, 200), (92, 92)), ((206, 214), (94, 94)), ((215, 216), (95, 97)), ((229, 229), (98, 98)), ((233, 233), (99, 99)), ((251, 255), (102, 103)), ((258, 276), (105, 107)), ((278, 287), (108, 109)), ((290, 307), (110, 127)), ((311, 318), (128, 135)), ((340, 344), (138, 139)), ((358, 362), (142, 146)), ((365, 383), (148, 152)), ((385, 403), (153, 157)), ((406, 406), (158, 158)), ((426, 430), (161, 167)), ((432, 443), (169, 170)), ((446, 449), (171, 174)), ((453, 453), (175, 175)), ((457, 457), (176, 176)), ((461, 466), (177, 182)), ((488, 492), (185, 186)), ((496, 511), (188, 203)), ((527, 531), (207, 208)), ((535, 545), (210, 220)), ((561, 565), (223, 227)), ((569, 569), (229, 229)), ((572, 590), (230, 234)), ((593, 593), (235, 235)), ((597, 597), (236, 236)), ((619, 623), (239, 242)), ((626, 644), (244, 248)), ((647, 647), (249, 249)), ((665, 669), (252, 256)), ((673, 673), (258, 258)), ((677, 677), (259, 259)), ((680, 698), (260, 264)), ((700, 718), (265, 269)), ((721, 729), (270, 278)), ((733, 733), (279, 279)), ((759, 763), (282, 286)), ((767, 767), (288, 288)), ((771, 771), (289, 289)), ((774, 792), (290, 294)), ((795, 800), (295, 300)), ((822, 826), (303, 306)), ((830, 830), (308, 308)), ((832, 843), (309, 311)), ((858, 862), (314, 314)), ((876, 880), (317, 318)), ((883, 892), (320, 320)), ((894, 903), (321, 321)), ((920, 924), (324, 326)), ((928, 929), (328, 329)), ((933, 942), (330, 339)), ((960, 964), (342, 347)), ((978, 982), (350, 350)), ((984, 986), (352, 355)), ((989, 989), (357, 357)), ((995, 1005), (359, 359)), ((1006, 1008), (360, 363)), ((1021, 1021), (364, 364)), ((1025, 1025), (365, 365)), ((1029, 1029), (366, 366)), ((1049, 1053), (369, 371)), ((1056, 1069), (373, 375)), ((1072, 1074), (376, 378)), ((1078, 1085), (379, 386)), ((1089, 1092), (387, 390)), ((1114, 1118), (393, 395)), ((1121, 1140), (397, 400)), ((1143, 1143), (401, 401)), ((1147, 1150), (402, 405)), ((1154, 1159), (406, 411)), ((1181, 1185), (414, 416)), ((1199, 1203), (419, 423)), ((1206, 1225), (425, 429)), ((1227, 1246), (430, 434)), ((1249, 1249), (435, 435)), ((1253, 1253), (436, 436)), ((1257, 1257), (437, 437)), ((1281, 1285), (440, 446)), ((1289, 1289), (448, 448)), ((1293, 1293), (449, 449)), ((1297, 1298), (450, 451)), ((1302, 1314), (452, 464)), ((1317, 1330), (465, 466)), ((1332, 1348), (467, 470)), ((1351, 1352), (471, 472)), ((1356, 1356), (473, 473)), ((1360, 1363), (474, 477)), ((1395, 1399), (480, 484)), ((1403, 1403), (486, 486)), ((1406, 1425), (487, 491)), ((1427, 1440), (492, 493)), ((1443, 1443), (494, 494)), ((1447, 1447), (495, 495)), ((1471, 1475), (498, 502)), ((1478, 1497), (504, 508)), ((1500, 1500), (509, 509)), ((1518, 1522), (512, 516)), ((1526, 1526), (518, 518)), ((1530, 1530), (519, 519)), ((1533, 1552), (520, 524)), ((1554, 1573), (525, 529)), ((1576, 1584), (530, 538)), ((1608, 1612), (541, 545)), ((1616, 1616), (548, 548)), ((1620, 1620), (549, 549)), ((1623, 1642), (550, 554)), ((1645, 1650), (555, 560)), ((1672, 1676), (563, 565)), ((1680, 1687), (567, 574)), ((1703, 1707), (577, 581)), ((1711, 1713), (583, 585)), ((1716, 1728), (586, 587)), ((1745, 1749), (590, 595)), ((1753, 1755), (597, 599)), ((1758, 1770), (600, 601)), ((1787, 1791), (604, 606)), ((1795, 1796), (608, 609)), ((1812, 1816), (612, 613)), ((1820, 1840), (615, 635)), ((1844, 1844), (636, 636)), ((1862, 1866), (639, 640)), ((1880, 1884), (643, 644)), ((1888, 1891), (646, 649)), ((1895, 1901), (650, 656)), ((1919, 1923), (659, 663)), ((1927, 1927), (665, 665)), ((1929, 1940), (666, 668)), ((1955, 1959), (671, 675)), ((1963, 1964), (677, 678)), ((1968, 1968), (679, 679)), ((1972, 1982), (680, 690)), ((1986, 1988), (691, 693)), ((2010, 2014), (696, 700)), ((2018, 2019), (702, 703)), ((2023, 2023), (704, 704)), ((2027, 2039), (705, 717)), ((2043, 2052), (718, 727)), ((2074, 2078), (730, 735)), ((2082, 2082), (737, 737)), ((2086, 2093), (738, 745)), ((2111, 2115), (748, 756)), ) ./pyke-1.1.1/pyke/krb_compiler/helpers.py0000644000175000017500000002161311346504630017252 0ustar lambylamby# $Id: helpers.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import pyke def fc_head(rb_name): return ( "# %s_fc.py" % rb_name, "", "from __future__ import with_statement", "from pyke import contexts, pattern, fc_rule, knowledge_base", "", "pyke_version = %r" % pyke.version, "compiler_version = %r" % pyke.compiler_version, ) def bc_head(rb_name): return ( "# %s_bc.py" % rb_name, "", "from __future__ import with_statement", "import itertools", "from pyke import contexts, pattern, bc_rule", "", "pyke_version = %r" % pyke.version, "compiler_version = %r" % pyke.compiler_version, ) def plan_head(rb_name): return ( "# %s_plans.py" % rb_name, "", "pyke_version = %r" % pyke.version, "compiler_version = %r" % pyke.compiler_version, ) def goal(rb_name, rule_name, goal_info, pred_plan_lines, python_lines): # returns plan_lines, goal_fn_head, goal_fn_tail, goal_decl_lines goal, goal_name, pattern_args, taking, start_lineno, end_lineno = goal_info assert goal == 'goal' goal_fn_head = ( "", "def %s(rule, arg_patterns, arg_context):" % rule_name, ("INDENT", 2), "engine = rule.rule_base.engine", "patterns = rule.goal_arg_patterns()", "if len(arg_patterns) == len(patterns):", ("INDENT", 2), "context = contexts.bc_context(rule)", "try:", ("INDENT", 2), ("STARTING_LINENO", start_lineno), "if all(itertools.imap(lambda pat, arg:", ("INDENT", 2), ("INDENT", 20), ("INDENT", 2), "pat.match_pattern(context, context,", ("INDENT", 18), "arg, arg_context),", "POPINDENT", "POPINDENT", "patterns,", "arg_patterns)):", ("ENDING_LINENO", end_lineno), "POPINDENT", "rule.rule_base.num_bc_rules_matched += 1", ) goal_fn_tail = ( "POPINDENT", "POPINDENT", "finally:", ("INDENT", 2), "context.done()", "POPINDENT", "POPINDENT", "POPINDENT", ) if not taking and not pred_plan_lines and not python_lines: plan_fn_name = "None" plan_lines = () else: plan_fn_name = "%s_plans.%s" % (rb_name, rule_name) def_start = "def %s" % rule_name taking = [line.strip() for line in taking if line.strip()] if not taking: def_head = def_start + '(context):' else: if taking[0][0] != '(' or taking[-1][-1] != ')': from pyke.krb_compiler import scanner end = scanner.lexer.lexpos taking_start = scanner.lexer.lexdata.rfind('taking', 0, end) if taking_start < 0: raise SyntaxError("'taking' clause: missing parenthesis", scanner.syntaxerror_params()) taking_start += len('taking') while taking_start < len(scanner.lexdata) and \ scanner.lexdata[taking_start].isspace(): taking_start += 1 lineno = scanner.lexer.lineno - \ scanner.lexer.lexdata.count('\n', taking_start, scanner.lexer.lexpos) raise SyntaxError("'taking' clause: missing parenthesis", scanner.syntaxerror_params(taking_start, lineno)) taking[0] = def_start + "(context, " + taking[0][1:] taking[-1] += ':' if len(taking) == 1: def_head = taking[0] else: def_head = (taking[0], ('INDENT', 4), tuple(taking[1:]), "POPINDENT", ) plan_lines = ("", def_head, ('INDENT', 2), pred_plan_lines, python_lines, "POPINDENT", ) goal_decl_lines = ( "", "bc_rule.bc_rule(%r, This_rule_base, %r," % (rule_name, goal_name), ("INDENT", 16), "%s, %s," % (rule_name, plan_fn_name), ) + list_format(pattern_args, "(", "),") return plan_lines, goal_fn_head, goal_fn_tail, goal_decl_lines def add_start(l, start): ''' >>> add_start(('a', 'b', 'c'), '^') (0, ['^a', 'b', 'c']) >>> add_start(('POPINDENT', ('INDENT', 2), ((('b',), 'c'),),), '^') (2, ['POPINDENT', ('INDENT', 2), ((('^b',), 'c'),)]) >>> add_start((('POPINDENT', ('INDENT', 2)), ((('b',), 'c'),),), '^') (1, [('POPINDENT', ('INDENT', 2)), ((('^b',), 'c'),)]) >>> add_start(('POPINDENT', ('INDENT', 2)), '^') (0, ['^', 'POPINDENT', ('INDENT', 2)]) ''' ans = list(l) for first, x in enumerate(ans): if x != 'POPINDENT' and \ not (isinstance(x, (tuple, list)) and x[0] == 'INDENT'): if not isinstance(x, (tuple, list)): ans[first] = start + ans[first] return first, ans f, x2 = add_start(x, start) if len(x) == len(x2): ans[first] = tuple(x2) return first, ans first = 0 ans.insert(first, start) return first, ans def add_end(l, end): ''' >>> add_end(('a', 'b', 'c'), '^') (2, ['a', 'b', 'c^']) >>> add_end(((((('b',), 'c'),),), 'POPINDENT', ('INDENT', 2)), '^') (0, [(((('b',), 'c^'),),), 'POPINDENT', ('INDENT', 2)]) >>> add_end((((('b',), 'c'),), ('POPINDENT', ('INDENT', 2))), '^') (0, [((('b',), 'c^'),), ('POPINDENT', ('INDENT', 2))]) >>> add_end(('POPINDENT', ('INDENT', 2)), '^') (2, ['POPINDENT', ('INDENT', 2), '^']) ''' ans = list(l) for last in range(len(ans) - 1, -1, -1): x = ans[last] if x != 'POPINDENT' and \ not (isinstance(x, (tuple, list)) and x[0] == 'INDENT'): if not isinstance(x, (tuple, list)): ans[last] += end return last, ans e, x2 = add_end(x, end) if len(x) == len(x2): ans[last] = tuple(x2) return last, ans last = len(ans) ans.insert(last, end) return last, ans def add_brackets(l, start = '(', end = ')'): ''' >>> add_brackets(('a', 'b', 'c')) ('(a', ('INDENT', 1), 'b', 'c)', 'POPINDENT') >>> add_brackets(('(a', ('INDENT', 1), 'b', 'c)', 'POPINDENT')) ('((a', ('INDENT', 1), ('INDENT', 1), 'b', 'c))', 'POPINDENT', 'POPINDENT') ''' if not l: return start + end first, ans = add_start(l, start) last, ans = add_end(ans, end) if last > first: ans.insert(last + 1, "POPINDENT") ans.insert(first + 1, ("INDENT", 1)) return tuple(ans) def list_format(l, start, end, separator = ','): ans = [element + separator for element in l] if not ans: return (start + end,) ans[0] = start + ans[0] ans[-1] += end if len(ans) > 1: ans.insert(1, ("INDENT", 1)) ans.append("POPINDENT") return tuple(ans) def merge_pattern(pattern, pattern_list): # returns pat_num, new_pattern_list if pattern in pattern_list: return list(pattern_list).index(pattern), pattern_list return len(pattern_list), pattern_list + (pattern,) def merge_patterns(patterns, pattern_list): # returns pat_nums, new_pattern_list pat_nums = [] for pat in patterns: pat_num, pattern_list = merge_pattern(pat, pattern_list) pat_nums.append(pat_num) return tuple(pat_nums), pattern_list def syntax_error(msg, lineno, pos): raise SyntaxError(msg, scanner.syntaxerror_params(pos, lineno)) ./pyke-1.1.1/pyke/krb_compiler/krbparser_tables.py0000644000175000017500000010755511365364034021152 0ustar lambylamby # /home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser_tables.py # This file is automatically generated. Do not edit. _tabversion = '3.2' _lr_method = 'LALR' _lr_signature = '`\xa3O\x17\xd6C\xd4E2\xb5\xf60wIM\xd9' _lr_action_items = {'TAKING_TOK':([142,161,187,216,],[-66,188,-66,240,]),'LP_TOK':([18,32,43,65,73,85,88,95,111,112,124,125,128,132,144,150,158,162,165,170,171,179,180,181,182,183,184,185,189,192,193,196,197,199,204,205,206,207,210,212,214,218,219,220,224,225,226,231,233,234,235,236,238,239,244,245,251,253,254,255,257,258,260,266,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[32,43,65,65,43,65,43,111,43,43,-94,43,-79,43,-111,-32,43,43,192,43,43,-91,-51,-76,-50,-11,212,43,43,43,-38,43,43,226,231,43,-51,-50,43,43,-57,-33,-37,-36,-31,-21,43,43,-59,43,-108,255,43,43,-40,-34,266,43,-11,43,-11,-11,-30,43,-11,-60,-52,-25,-56,-16,-39,43,-53,-58,-61,43,-54,-63,-35,-55,43,-11,-65,-62,-64,]),'FOREACH_TOK':([61,],[79,]),'AS_TOK':([256,267,285,294,],[271,271,271,271,]),'ANONYMOUS_VAR_TOK':([32,43,65,66,73,85,88,100,106,111,112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,192,193,196,197,205,210,212,214,218,219,220,224,225,226,231,233,234,235,238,239,244,245,253,254,255,257,258,260,266,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[46,46,46,46,46,46,46,46,46,46,46,-94,46,-79,46,-111,-32,46,46,46,46,-91,-76,-11,46,46,46,-38,46,46,46,46,46,-57,-33,-37,-36,-31,-21,46,46,-59,46,-108,46,46,-40,-34,46,-11,46,-11,-11,-30,46,-11,-60,-52,-25,-56,-16,-39,46,-53,-58,-61,46,-54,-63,-35,-55,46,-11,-65,-62,-64,]),'NUMBER_TOK':([32,43,65,73,85,88,111,112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,192,193,196,197,205,210,212,214,218,219,220,224,225,226,231,233,234,235,238,239,244,245,253,254,255,257,258,260,266,268,269,272,273,274,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[44,44,44,44,44,44,44,44,-94,44,-79,44,-111,-32,44,44,44,44,-91,-76,-11,44,44,44,-38,44,44,44,44,44,-57,-33,-37,-36,-31,-21,44,44,-59,44,-108,44,44,-40,-34,44,-11,44,-11,-11,-30,44,-11,-60,-52,-25,288,-56,-16,-39,44,-53,-58,-61,44,-54,-63,-35,-55,44,-11,-65,-62,-64,]),'DEINDENT_TOK':([94,107,109,114,119,121,124,125,128,144,150,153,154,159,160,172,173,179,181,183,189,193,196,197,209,214,218,219,220,224,225,229,233,235,241,244,245,250,254,257,258,260,261,265,268,269,272,273,275,276,277,278,280,281,283,284,289,291,292,293,295,296,300,301,305,307,308,310,311,312,],[-104,118,-106,135,138,140,-94,143,-79,-111,-32,-90,173,-49,-48,-107,198,-91,-76,209,218,-38,224,225,-9,-57,-33,-37,-36,-31,-21,250,-59,-108,-85,-40,-34,-15,269,275,276,-30,278,-43,284,-60,-52,-25,-56,-16,291,-39,-41,293,-53,-58,-61,-86,300,-42,-54,-63,-35,-55,308,310,-65,-62,312,-64,]),'STEP_TOK':([256,267,285,294,],[274,274,274,274,]),'EXTENDING_TOK':([0,3,6,],[-22,-23,9,]),'ASSERT_TOK':([61,80,143,],[-101,97,-29,]),'INDENT_TOK':([33,37,39,58,59,60,62,77,96,113,122,139,141,145,151,152,157,160,168,194,200,208,213,215,227,232,262,273,287,298,299,303,],[-69,61,-69,75,76,-69,81,93,112,134,-10,-68,158,162,170,171,176,187,-70,222,-70,234,238,239,248,253,279,-67,297,-67,304,306,]),'.':([7,129,155,180,182,184,204,206,207,],[10,147,174,-51,-50,211,230,-51,-50,]),'!':([158,179,181,183,185,193,205,210,214,219,220,233,234,235,238,239,244,253,254,257,258,268,269,272,273,275,276,278,283,284,289,295,296,301,304,307,308,310,312,],[177,-91,-76,-11,177,-38,177,177,-57,-37,-36,-59,177,-108,177,177,-40,177,-11,-11,-11,-11,-60,-52,-25,-56,-16,-39,-53,-58,-61,-54,-63,-55,177,-11,-65,-62,-64,]),'IN_TOK':([44,46,48,49,50,52,53,54,55,56,67,99,103,104,127,129,136,137,146,180,182,],[-78,-75,-74,-89,-80,-88,-81,-116,-20,-84,-100,-117,-121,-120,-66,-87,-118,-119,163,-74,-87,]),'NOTANY_TOK':([112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,193,196,197,205,210,214,218,219,220,224,225,233,234,235,238,239,244,245,253,254,257,258,260,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[126,-94,126,-79,126,-111,-32,178,126,126,126,-91,-76,-11,178,126,-38,126,126,178,178,-57,-33,-37,-36,-31,-21,-59,178,-108,178,178,-40,-34,178,-11,-11,-11,-30,-11,-60,-52,-25,-56,-16,-39,126,-53,-58,-61,126,-54,-63,-35,-55,178,-11,-65,-62,-64,]),'WITHOUT_TOK':([17,],[30,]),'*':([43,65,85,88,],[66,66,100,106,]),',':([40,41,44,45,46,47,48,49,50,52,53,54,55,56,57,67,68,69,70,71,82,83,90,99,101,102,103,104,105,136,137,],[-98,63,-78,-87,-75,-96,-74,-89,-80,-88,-81,-116,-20,-84,73,-100,85,-97,-93,88,-115,85,-113,-117,-110,88,-121,-120,-114,-118,-119,]),'BC_EXTRAS_TOK':([11,16,21,140,],[19,-92,-109,-45,]),'CODE_TOK':([75,81,93,148,163,164,176,188,195,222,228,240,248,297,306,],[91,91,91,166,166,166,202,166,166,166,166,166,166,202,202,]),'REQUIRE_TOK':([225,276,],[246,290,]),'PATTERN_VAR_TOK':([32,43,65,66,73,85,88,100,106,111,112,124,125,128,132,144,150,158,162,170,171,177,179,181,183,185,189,192,193,196,197,205,210,211,212,214,218,219,220,224,225,226,230,231,233,234,235,238,239,244,245,253,254,255,257,258,260,266,268,269,271,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[48,48,48,48,48,48,48,48,48,48,48,-94,48,-79,48,-111,-32,180,48,48,48,206,-91,-76,-11,180,48,48,-38,48,48,180,180,206,48,-57,-33,-37,-36,-31,-21,48,206,48,-59,180,-108,180,180,-40,-34,180,-11,48,-11,-11,-30,48,-11,-60,286,-52,-25,-56,-16,-39,48,-53,-58,-61,48,-54,-63,-35,-55,180,-11,-65,-62,-64,]),'TRUE_TOK':([32,43,65,73,85,88,111,112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,192,193,196,197,205,210,212,214,218,219,220,224,225,226,231,233,234,235,238,239,244,245,253,254,255,257,258,260,266,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[49,49,49,49,49,49,49,49,-94,49,-79,49,-111,-32,49,49,49,49,-91,-76,-11,49,49,49,-38,49,49,49,49,49,-57,-33,-37,-36,-31,-21,49,49,-59,49,-108,49,49,-40,-34,49,-11,49,-11,-11,-30,49,-11,-60,-52,-25,-56,-16,-39,49,-53,-58,-61,49,-54,-63,-35,-55,49,-11,-65,-62,-64,]),'PLAN_EXTRAS_TOK':([11,16,21,22,118,140,],[-99,-92,-109,36,-12,-45,]),':':([12,20,],[23,23,]),'=':([44,46,48,49,50,52,53,54,55,56,67,99,103,104,127,129,136,137,146,180,182,],[-78,-75,-74,-89,-80,-88,-81,-116,-20,-84,-100,-117,-121,-120,-66,-87,-118,-119,164,-74,-87,]),'NOT_NL_TOK':([149,169,175,201,],[-66,195,-66,228,]),'$end':([2,4,5,11,13,14,15,16,21,22,25,26,27,29,35,38,72,118,135,138,140,198,],[0,-1,-2,-99,-95,-46,-6,-92,-109,-103,-4,-46,-112,-77,-47,-5,-3,-12,-13,-14,-45,-28,]),'PYTHON_TOK':([112,124,125,128,131,132,134,144,150,153,154,156,158,162,170,171,172,179,181,183,185,189,193,196,197,205,210,214,218,219,220,224,225,233,234,235,238,239,244,245,253,254,257,258,260,265,268,269,272,273,275,276,278,279,280,283,284,289,292,293,295,296,300,301,304,307,308,310,312,],[-44,-94,-44,-79,149,-44,-44,-111,-32,-90,-44,175,-44,-44,-44,-44,-107,-91,-76,-11,-44,-44,-38,-44,-44,-44,-44,-57,-33,-37,-36,-31,-21,-59,-44,-108,-44,-44,-40,-34,-44,-11,-11,-11,-30,-43,-11,-60,-52,-25,-56,-16,-39,-44,-41,-53,-58,-61,-44,-42,-54,-63,-35,-55,-44,-11,-65,-62,-64,]),'USE_TOK':([61,76,],[78,78,]),'WITH_TOK':([94,109,159,160,209,241,291,],[-104,120,-49,-48,-9,-85,-86,]),'FALSE_TOK':([32,43,65,73,85,88,111,112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,192,193,196,197,205,210,212,214,218,219,220,224,225,226,231,233,234,235,238,239,244,245,253,254,255,257,258,260,266,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[52,52,52,52,52,52,52,52,-94,52,-79,52,-111,-32,52,52,52,52,-91,-76,-11,52,52,52,-38,52,52,52,52,52,-57,-33,-37,-36,-31,-21,52,52,-59,52,-108,52,52,-40,-34,52,-11,52,-11,-11,-30,52,-11,-60,-52,-25,-56,-16,-39,52,-53,-58,-61,52,-54,-63,-35,-55,52,-11,-65,-62,-64,]),'CHECK_TOK':([0,112,124,125,128,130,132,144,150,158,162,170,171,179,181,183,185,189,193,196,197,205,210,214,218,219,220,224,225,233,234,235,238,239,244,245,253,254,257,258,260,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[1,-66,-94,-66,-79,148,-66,-111,-32,-66,-66,-66,-66,-91,-76,-11,-66,-66,-38,-66,-66,-66,-66,-57,-33,-37,-36,-31,-21,-59,-66,-108,-66,-66,-40,-34,-66,-11,-11,-11,-30,-11,-60,-52,-25,-56,-16,-39,-66,-53,-58,-61,-66,-54,-63,-35,-55,-66,-11,-65,-62,-64,]),'IDENTIFIER_TOK':([0,1,3,6,8,9,10,11,13,14,16,21,26,27,30,32,42,43,63,65,73,78,85,88,111,112,124,125,128,132,134,135,140,144,147,150,153,154,158,162,170,171,172,174,177,179,181,183,185,189,192,193,196,197,198,205,210,211,212,214,218,219,220,224,225,226,230,231,233,234,235,238,239,244,245,253,254,255,257,258,260,265,266,268,269,272,273,275,276,278,279,280,283,284,289,292,293,295,296,300,301,304,307,308,310,312,],[-22,7,-23,-24,12,17,18,20,-95,12,-92,-109,20,-112,40,45,-7,45,82,45,45,95,45,45,45,129,-94,129,-79,129,155,-13,-45,-111,165,-32,-90,155,182,129,129,129,-107,199,207,-91,-76,-11,182,129,45,-38,129,129,-28,182,182,207,45,-57,-33,-37,-36,-31,-21,45,207,45,-59,182,-108,182,182,-40,-34,182,-11,45,-11,-11,-30,-43,45,-11,-60,-52,-25,-56,-16,-39,129,-41,-53,-58,-61,129,-42,-54,-63,-35,-55,182,-11,-65,-62,-64,]),'NONE_TOK':([32,43,65,73,85,88,111,112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,192,193,196,197,205,210,212,214,218,219,220,224,225,226,231,233,234,235,238,239,244,245,253,254,255,257,258,260,266,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[55,55,55,55,55,55,55,55,-94,55,-79,55,-111,-32,55,55,55,55,-91,-76,-11,55,55,55,-38,55,55,55,55,55,-57,-33,-37,-36,-31,-21,55,55,-59,55,-108,55,55,-40,-34,55,-11,55,-11,-11,-30,55,-11,-60,-52,-25,-56,-16,-39,55,-53,-58,-61,55,-54,-63,-35,-55,55,-11,-65,-62,-64,]),'FORALL_TOK':([112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,193,196,197,205,210,214,218,219,220,224,225,233,234,235,238,239,244,245,253,254,257,258,260,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[133,-94,133,-79,133,-111,-32,186,133,133,133,-91,-76,-11,186,133,-38,133,133,186,186,-57,-33,-37,-36,-31,-21,-59,186,-108,186,186,-40,-34,186,-11,-11,-11,-30,-11,-60,-52,-25,-56,-16,-39,133,-53,-58,-61,133,-54,-63,-35,-55,186,-11,-65,-62,-64,]),'STRING_TOK':([32,43,65,73,85,88,111,112,124,125,128,132,144,150,158,162,170,171,179,181,183,185,189,192,193,196,197,205,210,212,214,218,219,220,224,225,226,231,233,234,235,238,239,244,245,253,254,255,257,258,260,266,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[56,56,56,56,56,56,56,56,-94,56,-79,56,-111,-32,56,56,56,56,-91,-76,-11,56,56,56,-38,56,56,56,56,56,-57,-33,-37,-36,-31,-21,56,56,-59,56,-108,56,56,-40,-34,56,-11,56,-11,-11,-30,56,-11,-60,-52,-25,-56,-16,-39,56,-53,-58,-61,56,-54,-63,-35,-55,56,-11,-65,-62,-64,]),'WHEN_TOK':([94,159,160,241,291,],[110,-49,-48,-85,-86,]),'FIRST_TOK':([112,124,125,128,132,144,150,158,162,170,171,177,179,181,183,185,189,193,196,197,205,210,214,218,219,220,224,225,233,234,235,238,239,244,245,253,254,257,258,260,268,269,272,273,275,276,278,279,283,284,289,292,295,296,300,301,304,307,308,310,312,],[132,-94,132,-79,132,-111,-32,185,132,132,132,205,-91,-76,-11,185,132,-38,132,132,185,185,-57,-33,-37,-36,-31,-21,-59,185,-108,185,185,-40,-34,185,-11,-11,-11,-30,-11,-60,-52,-25,-56,-16,-39,132,-53,-58,-61,132,-54,-63,-35,-55,185,-11,-65,-62,-64,]),'RP_TOK':([32,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,65,67,68,69,70,71,73,74,83,84,85,86,87,88,89,90,99,101,102,103,104,105,111,115,116,117,123,136,137,192,212,221,226,231,237,247,252,255,266,270,282,],[-102,67,-78,-87,-75,-96,-74,-89,-80,72,-88,-81,-116,-20,-84,-17,67,-100,-17,-97,-93,-17,-18,-82,-17,99,-18,103,104,-18,-26,-113,-117,-110,-17,-121,-120,-114,-102,136,137,-83,142,-118,-119,-102,-102,242,-102,-102,256,263,267,-102,-102,285,294,]),'FC_EXTRAS_TOK':([13,14,27,198,],[-95,28,-112,-28,]),'NL_TOK':([0,12,17,19,20,23,24,28,31,34,36,40,41,63,64,79,82,91,92,97,98,108,110,120,126,132,133,142,149,166,167,175,178,185,186,190,191,202,203,205,217,223,242,243,246,249,256,259,263,264,267,285,286,288,290,294,302,309,],[3,-19,-105,33,-19,-27,37,39,42,59,60,-98,-17,-18,-8,96,-115,-73,107,113,114,119,122,139,145,151,152,160,168,-71,193,200,208,213,215,219,220,-72,229,232,241,244,260,261,262,265,273,277,280,281,273,273,296,298,299,273,305,311,]),} _lr_action = { } for _k, _v in _lr_action_items.items(): for _x,_y in zip(_v[0],_v[1]): if not _x in _lr_action: _lr_action[_x] = { } _lr_action[_x][_k] = _y del _lr_action_items _lr_goto_items = {'inc_plan_vars':([183,254,257,258,268,307,],[210,210,210,210,210,210,]),'when_opt':([94,],[109,]),'bc_rules_opt':([14,26,],[25,38,]),'parent_opt':([6,],[8,]),'fc_extras':([14,],[26,]),'start_extra_statements':([33,39,60,],[58,62,77,]),'bc_rules':([8,14,26,],[11,11,11,]),'file':([0,],[4,]),'fc_premise':([112,125,132,162,170,171,189,196,197,279,292,],[124,144,150,124,124,124,144,144,144,124,144,]),'python_plan_code':([176,297,306,],[203,302,309,]),'bc_require_opt':([276,],[289,]),'plan_spec':([256,267,285,294,],[272,283,295,301,]),'goal':([78,],[94,]),'plan_extras_opt':([22,],[35,]),'pattern':([32,73,88,111,112,125,132,158,162,170,171,185,189,192,196,197,205,210,212,226,231,234,238,239,253,255,266,279,292,304,],[47,90,105,47,127,127,127,127,127,127,127,127,127,47,127,127,127,127,47,47,47,127,127,127,127,47,47,127,127,127,]),'top':([0,],[2,]),'bc_premise':([158,185,205,210,234,238,239,253,304,],[179,214,233,235,179,179,179,179,179,]),'assertion':([134,154,],[153,172,]),'name':([158,177,185,205,210,211,230,234,238,239,253,304,],[184,204,184,184,184,236,251,184,184,184,184,184,]),'data_list':([43,65,],[68,83,]),'start_python_plan_call':([273,298,],[287,303,]),'pattern_proper':([32,43,65,73,85,88,111,112,125,132,158,162,170,171,185,189,192,196,197,205,210,212,226,231,234,238,239,253,255,266,279,292,304,],[50,69,69,50,69,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,]),'python_goal':([0,],[5,]),'without_names':([30,],[41,]),'bc_extras_opt':([11,],[22,]),'start_python_statements':([139,],[157,]),'patterns_opt':([32,111,192,212,226,231,255,266,],[51,123,221,237,247,252,270,282,]),'fc_require_opt':([225,],[245,]),'python_premise':([112,125,132,158,162,170,171,185,189,196,197,205,210,234,238,239,253,279,292,304,],[128,128,128,181,128,128,128,181,128,128,128,181,181,181,181,181,181,128,128,181,]),'with_opt':([109,],[121,]),'variable':([32,43,65,66,73,85,88,100,106,111,112,125,132,158,162,170,171,185,189,192,196,197,205,210,212,226,231,234,238,239,253,255,266,279,292,304,],[53,53,53,84,53,53,53,115,117,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,]),'fc_rule':([8,14,],[13,27,]),'start_python_code':([112,125,127,132,142,149,158,162,170,171,175,185,187,189,196,197,205,210,234,238,239,253,279,292,304,],[130,130,146,130,161,169,130,130,130,130,201,130,216,130,130,130,130,130,130,130,130,130,130,130,130,]),'bc_premises':([158,234,238,239,253,304,],[183,254,257,258,268,307,]),'data':([32,43,65,73,85,88,111,112,125,132,158,162,170,171,185,189,192,196,197,205,210,212,226,231,234,238,239,253,255,266,279,292,304,],[54,70,70,54,101,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,]),'patterns_proper':([43,65,85,],[71,71,102,]),'check_nl':([112,125,132,134,154,158,162,170,171,185,189,196,197,205,210,234,238,239,253,279,292,304,],[131,131,131,156,156,131,131,131,131,131,131,131,131,131,131,131,131,131,131,131,131,131,]),'rest_opt':([71,102,],[87,116,]),'fc_rules':([8,],[14,]),'bc_rules_section':([8,14,26,],[15,29,29,]),'python_extras_code':([75,81,93,],[92,98,108,]),'nl_opt':([0,],[6,]),'python_rule_code':([148,163,164,188,195,222,228,240,248,],[167,190,191,217,223,243,249,259,264,]),'colon_opt':([12,20,],[24,34,]),'fc_premises':([112,162,170,171,279,],[125,189,196,197,292,]),'patterns':([32,111,192,212,226,231,255,266,],[57,57,57,57,57,57,57,57,]),'comma_opt':([41,57,68,71,83,102,],[64,74,86,89,86,89,]),'reset_plan_vars':([122,],[141,]),'taking':([142,],[159,]),'without_opt':([17,],[31,]),'foreach_opt':([61,],[80,]),'bc_rule':([8,11,14,26,],[16,21,16,16,]),'start_python_assertion':([168,200,],[194,227,]),'assertions':([134,],[154,]),} _lr_goto = { } for _k, _v in _lr_goto_items.items(): for _x,_y in zip(_v[0],_v[1]): if not _x in _lr_goto: _lr_goto[_x] = { } _lr_goto[_x][_k] = _y del _lr_goto_items _lr_productions = [ ("S' -> top","S'",1,None,None,None), ('top -> file','top',1,'p_top','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',40), ('top -> python_goal','top',1,'p_top','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',41), ('python_goal -> CHECK_TOK IDENTIFIER_TOK . IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK','python_goal',7,'p_goal','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',46), ('file -> nl_opt parent_opt fc_rules bc_rules_opt','file',4,'p_file','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',51), ('file -> nl_opt parent_opt fc_rules fc_extras bc_rules_opt','file',5,'p_file_fc_extras','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',56), ('file -> nl_opt parent_opt bc_rules_section','file',3,'p_file_bc','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',61), ('parent_opt -> EXTENDING_TOK IDENTIFIER_TOK without_opt NL_TOK','parent_opt',4,'p_parent','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',72), ('without_opt -> WITHOUT_TOK without_names comma_opt','without_opt',3,'p_second','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',77), ('when_opt -> WHEN_TOK NL_TOK reset_plan_vars INDENT_TOK bc_premises DEINDENT_TOK','when_opt',6,'p_fourth','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',82), ('reset_plan_vars -> ','reset_plan_vars',0,'p_reset_plan_vars','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',87), ('inc_plan_vars -> ','inc_plan_vars',0,'p_inc_plan_vars','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',95), ('bc_extras_opt -> BC_EXTRAS_TOK NL_TOK start_extra_statements INDENT_TOK python_extras_code NL_TOK DEINDENT_TOK','bc_extras_opt',7,'p_fifth','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',103), ('fc_extras -> FC_EXTRAS_TOK NL_TOK start_extra_statements INDENT_TOK python_extras_code NL_TOK DEINDENT_TOK','fc_extras',7,'p_fifth','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',104), ('plan_extras_opt -> PLAN_EXTRAS_TOK NL_TOK start_extra_statements INDENT_TOK python_extras_code NL_TOK DEINDENT_TOK','plan_extras_opt',7,'p_fifth','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',105), ('with_opt -> WITH_TOK NL_TOK start_python_statements INDENT_TOK python_plan_code NL_TOK DEINDENT_TOK','with_opt',7,'p_fifth','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',106), ('bc_require_opt -> ','bc_require_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',111), ('comma_opt -> ','comma_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',112), ('comma_opt -> ,','comma_opt',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',113), ('colon_opt -> ','colon_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',114), ('data -> NONE_TOK','data',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',115), ('fc_require_opt -> ','fc_require_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',116), ('nl_opt -> ','nl_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',117), ('nl_opt -> NL_TOK','nl_opt',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',118), ('parent_opt -> ','parent_opt',0,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',119), ('plan_spec -> NL_TOK','plan_spec',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',120), ('rest_opt -> comma_opt','rest_opt',1,'p_none','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',121), ('colon_opt -> :','colon_opt',1,'p_colon_deprication','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',126), ('fc_rule -> IDENTIFIER_TOK colon_opt NL_TOK INDENT_TOK foreach_opt ASSERT_TOK NL_TOK INDENT_TOK assertions DEINDENT_TOK DEINDENT_TOK','fc_rule',11,'p_fc_rule','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',134), ('foreach_opt -> FOREACH_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK','foreach_opt',5,'p_foreach','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',139), ('fc_premise -> IDENTIFIER_TOK . IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK NL_TOK','fc_premise',7,'p_fc_premise','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',144), ('fc_premise -> FIRST_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK','fc_premise',5,'p_fc_first_1','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',149), ('fc_premise -> FIRST_TOK fc_premise','fc_premise',2,'p_fc_first_n','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',154), ('fc_premise -> NOTANY_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK','fc_premise',5,'p_fc_notany','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',159), ('fc_premise -> FORALL_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK fc_require_opt','fc_premise',6,'p_fc_forall','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',164), ('fc_require_opt -> REQUIRE_TOK NL_TOK INDENT_TOK fc_premises DEINDENT_TOK','fc_require_opt',5,'p_fc_require_opt','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',169), ('python_premise -> pattern start_python_code = python_rule_code NL_TOK','python_premise',5,'p_python_eq','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',174), ('python_premise -> pattern start_python_code IN_TOK python_rule_code NL_TOK','python_premise',5,'p_python_in','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',179), ('python_premise -> start_python_code CHECK_TOK python_rule_code NL_TOK','python_premise',4,'p_python_check','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',184), ('python_premise -> check_nl PYTHON_TOK NL_TOK start_python_assertion INDENT_TOK python_rule_code NL_TOK DEINDENT_TOK','python_premise',8,'p_python_block_n','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',189), ('python_premise -> check_nl PYTHON_TOK start_python_code NOT_NL_TOK python_rule_code NL_TOK','python_premise',6,'p_python_block_1','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',194), ('assertion -> IDENTIFIER_TOK . IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK NL_TOK','assertion',7,'p_assertion','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',199), ('assertion -> check_nl PYTHON_TOK NL_TOK start_python_assertion INDENT_TOK python_rule_code NL_TOK DEINDENT_TOK','assertion',8,'p_python_assertion_n','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',204), ('assertion -> check_nl PYTHON_TOK start_python_code NOT_NL_TOK python_rule_code NL_TOK','assertion',6,'p_python_assertion_1','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',209), ('check_nl -> ','check_nl',0,'p_check_nl','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',214), ('bc_rule -> IDENTIFIER_TOK colon_opt NL_TOK INDENT_TOK USE_TOK goal when_opt with_opt DEINDENT_TOK','bc_rule',9,'p_bc_rule','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',220), ('bc_rules_opt -> ','bc_rules_opt',0,'p_empty_bc_rules_opt','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',225), ('bc_rules_section -> bc_rules bc_extras_opt plan_extras_opt','bc_rules_section',3,'p_bc_rules_section','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',230), ('goal -> IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK NL_TOK','goal',5,'p_goal_no_taking','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',235), ('goal -> IDENTIFIER_TOK LP_TOK patterns_opt RP_TOK taking','goal',5,'p_goal_taking','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',240), ('name -> IDENTIFIER_TOK','name',1,'p_name_sym','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',245), ('name -> PATTERN_VAR_TOK','name',1,'p_name_pat_var','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',250), ('bc_premise -> name LP_TOK patterns_opt RP_TOK plan_spec','bc_premise',5,'p_bc_premise1','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',255), ('bc_premise -> ! name LP_TOK patterns_opt RP_TOK plan_spec','bc_premise',6,'p_bc_premise2','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',261), ('bc_premise -> name . name LP_TOK patterns_opt RP_TOK plan_spec','bc_premise',7,'p_bc_premise3','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',267), ('bc_premise -> ! name . name LP_TOK patterns_opt RP_TOK plan_spec','bc_premise',8,'p_bc_premise4','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',273), ('bc_premise -> FIRST_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK','bc_premise',5,'p_bc_first_1f','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',279), ('bc_premise -> FIRST_TOK bc_premise','bc_premise',2,'p_bc_first_nf','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',284), ('bc_premise -> ! FIRST_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK','bc_premise',6,'p_bc_first_1t','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',289), ('bc_premise -> ! FIRST_TOK bc_premise','bc_premise',3,'p_bc_first_nt','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',294), ('bc_premise -> NOTANY_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK','bc_premise',5,'p_bc_notany','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',299), ('bc_premise -> FORALL_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK bc_require_opt','bc_premise',6,'p_bc_forall','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',304), ('bc_require_opt -> REQUIRE_TOK NL_TOK INDENT_TOK bc_premises DEINDENT_TOK','bc_require_opt',5,'p_bc_require_opt','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',309), ('plan_spec -> AS_TOK PATTERN_VAR_TOK NL_TOK','plan_spec',3,'p_as','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',314), ('plan_spec -> STEP_TOK NUMBER_TOK NL_TOK start_python_plan_call INDENT_TOK python_plan_code NL_TOK DEINDENT_TOK','plan_spec',8,'p_step_code','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',319), ('plan_spec -> NL_TOK start_python_plan_call INDENT_TOK python_plan_code NL_TOK DEINDENT_TOK','plan_spec',6,'p_code','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',325), ('start_python_code -> ','start_python_code',0,'p_start_python_code','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',330), ('start_python_plan_call -> ','start_python_plan_call',0,'p_start_python_plan_call','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',336), ('start_python_statements -> ','start_python_statements',0,'p_start_python_statements','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',342), ('start_extra_statements -> ','start_extra_statements',0,'p_start_extra_statements','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',348), ('start_python_assertion -> ','start_python_assertion',0,'p_start_python_assertion','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',354), ('python_rule_code -> CODE_TOK','python_rule_code',1,'p_python_rule_code','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',361), ('python_plan_code -> CODE_TOK','python_plan_code',1,'p_python_plan_code','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',366), ('python_extras_code -> CODE_TOK','python_extras_code',1,'p_python_extras_code','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',371), ('variable -> PATTERN_VAR_TOK','variable',1,'p_pattern_var','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',376), ('variable -> ANONYMOUS_VAR_TOK','variable',1,'p_anonymous_var','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',386), ('bc_premise -> python_premise','bc_premise',1,'p_first','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',394), ('bc_rules_opt -> bc_rules_section','bc_rules_opt',1,'p_first','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',395), ('data -> NUMBER_TOK','data',1,'p_first','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',396), ('fc_premise -> python_premise','fc_premise',1,'p_first','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',397), ('pattern -> pattern_proper','pattern',1,'p_first','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',398), ('pattern_proper -> variable','pattern_proper',1,'p_first','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',399), ('patterns_opt -> patterns comma_opt','patterns_opt',2,'p_first','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',400), ('rest_opt -> , * variable','rest_opt',3,'p_last','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',405), ('data -> STRING_TOK','data',1,'p_data_string','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',410), ('taking -> start_python_code TAKING_TOK python_rule_code NL_TOK','taking',4,'p_taking','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',421), ('taking -> NL_TOK INDENT_TOK start_python_code TAKING_TOK python_rule_code NL_TOK DEINDENT_TOK','taking',7,'p_taking2','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',426), ('data -> IDENTIFIER_TOK','data',1,'p_quoted_last','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',431), ('data -> FALSE_TOK','data',1,'p_false','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',439), ('data -> TRUE_TOK','data',1,'p_true','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',444), ('assertions -> assertion','assertions',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',449), ('bc_premises -> bc_premise','bc_premises',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',450), ('bc_rules -> bc_rule','bc_rules',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',451), ('data_list -> data','data_list',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',452), ('fc_premises -> fc_premise','fc_premises',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',453), ('fc_rules -> fc_rule','fc_rules',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',454), ('patterns -> pattern','patterns',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',455), ('patterns_proper -> pattern_proper','patterns_proper',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',456), ('without_names -> IDENTIFIER_TOK','without_names',1,'p_start_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',457), ('bc_extras_opt -> ','bc_extras_opt',0,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',462), ('data -> LP_TOK RP_TOK','data',2,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',463), ('foreach_opt -> ','foreach_opt',0,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',464), ('patterns_opt -> ','patterns_opt',0,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',465), ('plan_extras_opt -> ','plan_extras_opt',0,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',466), ('when_opt -> ','when_opt',0,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',467), ('without_opt -> ','without_opt',0,'p_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',468), ('with_opt -> ','with_opt',0,'p_double_empty_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',473), ('assertions -> assertions assertion','assertions',2,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',478), ('bc_premises -> bc_premises inc_plan_vars bc_premise','bc_premises',3,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',479), ('bc_rules -> bc_rules bc_rule','bc_rules',2,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',480), ('data_list -> data_list , data','data_list',3,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',481), ('fc_premises -> fc_premises fc_premise','fc_premises',2,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',482), ('fc_rules -> fc_rules fc_rule','fc_rules',2,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',483), ('patterns -> patterns , pattern','patterns',3,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',484), ('patterns_proper -> patterns_proper , pattern','patterns_proper',3,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',485), ('without_names -> without_names , IDENTIFIER_TOK','without_names',3,'p_append_list','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',486), ('pattern -> data','pattern',1,'p_pattern','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',492), ('pattern_proper -> LP_TOK * variable RP_TOK','pattern_proper',4,'p_pattern_tuple1','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',499), ('pattern_proper -> LP_TOK data_list , * variable RP_TOK','pattern_proper',6,'p_pattern_tuple2','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',506), ('pattern_proper -> LP_TOK data_list , patterns_proper rest_opt RP_TOK','pattern_proper',6,'p_pattern_tuple3','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',518), ('pattern_proper -> LP_TOK patterns_proper rest_opt RP_TOK','pattern_proper',4,'p_pattern_tuple4','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',534), ('data -> LP_TOK data_list comma_opt RP_TOK','data',4,'p_tuple','/home/bruce/python/workareas/pyke-hg/r1_working/pyke/krb_compiler/krbparser.py',543), ] ./pyke-1.1.1/pyke/krb_compiler/TEST/0000755000175000017500000000000011425360453016013 5ustar lambylamby./pyke-1.1.1/pyke/krb_compiler/TEST/krbparse_test.krb0000644000175000017500000000100111346504630021352 0ustar lambylamby# This is a comment # line 2 of comment # comment after blank line name1 foreach a.b(x, $b) assert c.d($b) name2 use x(1, $c, (1, b), (1, b, $c)) taking (a, b, c) when a.b(x, $c) line1 line2 $d end2 x(1,2,3) some $plan(stuff) \ and more stuff fail here with a $plan b name3 use x(1, $c, (1, b), (1, b, $c)) when a.b(x, $c) line1 line2 $d end2 x(1,2,3) as $foo_fn ./pyke-1.1.1/pyke/krb_compiler/TEST/kfbparse_test.kfb0000644000175000017500000000025211346504630021331 0ustar lambylamby# This is a comment # line 2 of comment # comment after blank line fact1() fact1(a, b) fact1(1, 2.0) fact1((a, b)) fact2((1, 2.0, 3e3, (1e-3, -4), -4.5), -5e+2, -5e-1) ./pyke-1.1.1/pyke/krb_compiler/TEST/scan_test0000644000175000017500000000051511346504630017721 0ustar lambylamby# This is a comment # line 2 of comment # comment after blank line name1: foreach (100 0100 $_ $foo 0x100 0) {3.14 0.99 3. .3 3e6 3e-6} assert ['this is a string' "so is this" ''' and this \t too''' 'should be\ able to do this too' True] !can \ I do this too ./pyke-1.1.1/pyke/krb_compiler/compiler.krb0000644000175000017500000010204211365357242017552 0ustar lambylamby# $Id: compiler.krb d38305cf599c 2010-04-26 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. file use compile($generated_root_pkg, $rb_name, (file, $parent, ($fc_rules, $fc_extra_lines), ($bc_rules, $bc_extra_lines, $plan_extra_lines)), $fc_lines, $bc_lines, $plan_lines) when $fc_head = helpers.fc_head($rb_name) $bc_head = helpers.bc_head($rb_name) $plan_head = helpers.plan_head($rb_name) !rule_decl($rb_name, $parent, $decl_line) !fc_rules($fc_rules, $fc_fun_lines, $fc_init_lines) !bc_rules($rb_name, $bc_rules, $bc_plan_lines, $bc_bc_fun_lines, $bc_bc_init_lines) $fc_lines = ($fc_head, $fc_fun_lines, "", "def populate(engine):", ('INDENT', 2), $decl_line, $fc_init_lines, 'POPINDENT', "", $fc_extra_lines, ) \ if $fc_fun_lines \ else () $plan_lines = ($plan_head, $bc_plan_lines, "", $plan_extra_lines) \ if $bc_plan_lines \ else () $bc_lines = ($bc_head, ("from %s import %s_plans" % ($generated_root_pkg, $rb_name) if $bc_plan_lines else ()), $bc_bc_fun_lines, "", "def populate(engine):", ('INDENT', 2), $decl_line, $bc_bc_init_lines, 'POPINDENT', "", $bc_extra_lines) \ if $bc_bc_fun_lines \ else () rule_decl use rule_decl($rb_name, None, $decl_line) when $decl_line = "This_rule_base = engine.get_create(%r)" % $rb_name rule_decl_with_parent use rule_decl($rb_name, (parent, $parent, $excluded_symbols), $decl_line) when $decl_line = "This_rule_base = engine.get_create(%r, %r, %s)" % \ ($rb_name, $parent, tuple(repr(sym) for sym in $excluded_symbols)) fc_rules use fc_rules($fc_rules, $fc_funs, $fc_init) when python fc_funs = [] fc_init = [] forall $fc_rule in $fc_rules require !fc_rule($fc_rule, $fc_fun_1, $fc_init_1) python fc_funs.append($fc_fun_1) fc_init.append($fc_init_1) $fc_funs = tuple(fc_funs) $fc_init = tuple(fc_init) fc_rule_ use fc_rule((fc_rule, $rule_name, $fc_premises, $assertions), $fc_fun, $fc_init) when !fc_premises($rule_name, 0, $_, $fc_premises, None, False, $prem_fn_head, $prem_fn_tail, 0, $_, $prem_decl_lines, (), $patterns_out1) !assertions($assertions, $asserts_fn_lines, $patterns_out1, $patterns_out) $fc_fun = ("", "def %s(rule, context = None, index = None):" % $rule_name, ("INDENT", 2), "engine = rule.rule_base.engine", "if context is None: context = contexts.simple_context()", "try:", ("INDENT", 2), $prem_fn_head, $asserts_fn_lines, "rule.rule_base.num_fc_rules_triggered += 1", $prem_fn_tail, "POPINDENT", "finally:", ("INDENT", 2), "context.done()", "POPINDENT", "POPINDENT", ) $fc_init = ("", "fc_rule.fc_rule('%(name)s', This_rule_base, %(name)s," % {'name': $rule_name}, ("INDENT", 2), helpers.add_brackets($prem_decl_lines, '(', '),'), helpers.list_format($patterns_out, '(', '))'), "POPINDENT", ) fc_premises0 use fc_premises($_, $clause_num, $clause_num, (), $_, $_, (), (), $decl_num_in, $decl_num_in, (), $patterns_in, $patterns_in) fc_premises1 use fc_premises($rule_name, $clause_num, $next_clause_num, ($first_prem, *$rest_prems), $break_cond, $multi_match, ($fn_head1, *$fn_head2), ($fn_tail2, *$fn_tail1), $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) when !fc_premise($rule_name, $clause_num, $next_clause_num1, $first_prem, $break_cond, $multi_match, $fn_head1, $fn_tail1, $decl_num_in, $decl_num_out1, $decl_lines1, $patterns_in, $patterns_out1) !fc_premises($rule_name, $next_clause_num1, $next_clause_num, $rest_prems, $break_cond, $multi_match, $fn_head2, $fn_tail2, $decl_num_out1, $decl_num_out, $decl_lines2, $patterns_out1, $patterns_out) $decl_lines = $decl_lines1 + $decl_lines2 fc_premise use fc_premise($rule_name, $clause_num, $next_clause_num, (fc_premise, $kb_name, $entity_name, $arg_patterns, $start_lineno, $end_lineno), $break_cond, $multi_match, $fn_head, $fn_tail, $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_in) when gen_fc_for($kb_name, $entity_name, $start_lineno, $end_lineno, $multi_match, $decl_num_in, $fn_head) $fn_tail = (() if $break_cond is None else "if %s: break" % $break_cond, 'POPINDENT', 'POPINDENT',), $next_clause_num = $clause_num + 1 $decl_num_out = $decl_num_in + 1 $decl_lines = ("(%r, %r," % ($kb_name, $entity_name), ('INDENT', 1), helpers.list_format($arg_patterns, '(', '),'), "%s)," % $multi_match, "POPINDENT", ) gen_fc_for_false use gen_fc_for($kb_name, $entity_name, $start_lineno, $end_lineno, False, $decl_num, $fn_head) when $fn_head = (('STARTING_LINENO', $start_lineno), "with knowledge_base.Gen_once if index == %d \\" % \ $decl_num, ('INDENT', 9), "else engine.lookup(%r, %r, context," % \ ($kb_name, $entity_name), ('INDENT', 19), "rule.foreach_patterns(%d)) \\" % $decl_num, 'POPINDENT', 'POPINDENT', ('INDENT', 2), "as gen_%d:" % $decl_num, "for dummy in gen_%d:" % $decl_num, ('ENDING_LINENO', $end_lineno), ('INDENT', 2), ) gen_fc_for_true use gen_fc_for($kb_name, $entity_name, $start_lineno, $end_lineno, True, $decl_num, $fn_head) when $fn_head = (('STARTING_LINENO', $start_lineno), "with engine.lookup(%r, %r, context, \\" % \ ($kb_name, $entity_name), ('INDENT', 19), "rule.foreach_patterns(%d)) \\" % $decl_num, 'POPINDENT', ('INDENT', 2), "as gen_%d:" % $decl_num, "for dummy in gen_%d:" % $decl_num, ('ENDING_LINENO', $end_lineno), ('INDENT', 2)) fc_first use fc_premise($rule_name, $clause_num, $next_clause_num, (fc_first, $premises1, $_), $_, $_, ($init_worked, $fn_head, $set_worked), $fn_tail, $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) when $break_cond = "first%d_worked" % $clause_num !fc_premises($rule_name, $clause_num, $next_clause_num, $premises1, $break_cond, True, $fn_head, $fn_tail, $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) $init_worked = "%s = False" % $break_cond $set_worked = "%s = True" % $break_cond fc_forall_None use fc_premise($rule_name, $clause_num, $next_clause_num, (fc_forall, $premises1, None, $_, $_), $_, $_, $fn_head, (), $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) when !fc_premises($rule_name, $clause_num, $next_clause_num, $premises1, None, True, $fn_head1, $fn_tail1, $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) $fn_head = $fn_head1 + $fn_tail1 fc_forall_require use fc_premise($rule_name, $clause_num, $next_clause_num, (fc_forall, $premises1, $require, $start_lineno, $_), $_, $_, $fn_head, ("POPINDENT",), $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) when $break_true = "forall%d_worked" % $start_lineno $break_false = "not forall%d_worked" % $start_lineno !fc_premises($rule_name, $clause_num, $next_clause_num1, $premises1, $break_false, True, $fn_head1, $fn_tail1, $decl_num_in, $decl_num_out1, $decl_lines1, $patterns_in, $patterns_out1) !fc_premises($rule_name, $next_clause_num1, $next_clause_num, $require, $break_true, True, $fn_head2, $fn_tail2, $decl_num_out1, $decl_num_out, $decl_lines2, $patterns_out1, $patterns_out) $fn_head = ("forall%d_worked = True" % $start_lineno, $fn_head1, "forall%d_worked = False" % $start_lineno, $fn_head2, "forall%d_worked = True" % $start_lineno, $fn_tail2, $fn_tail1, "if forall%d_worked:" % $start_lineno, ("INDENT", 2)) $decl_lines = $decl_lines1 + $decl_lines2 fc_notany use fc_premise($rule_name, $clause_num, $next_clause_num, (fc_notany, $premises, $start_lineno), $_, $_, $fn_head, ("POPINDENT",), $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) when $break_true = "notany%d_worked" % $start_lineno $break_false = "not notany%d_worked" % $start_lineno !fc_premises($rule_name, $clause_num, $next_clause_num, $premises, $break_false, True, $fn_head1, $fn_tail1, $decl_num_in, $decl_num_out, $decl_lines, $patterns_in, $patterns_out) $fn_head = ("notany%d_worked = True" % $start_lineno, $fn_head1, "notany%d_worked = False" % $start_lineno, $fn_tail1, "if notany%d_worked:" % $start_lineno, ("INDENT", 2)) fc_python_premise use fc_premise($rule_name, $clause_num, $next_clause_num, $python_premise, $break_cond, $_, $fn_head, $fn_tail, $decl_num_in, $decl_num_in, (), $patterns_in, $patterns_out) when $next_clause_num = $clause_num + 1 python_premise($clause_num, $python_premise, $break_cond, $patterns_in, $patterns_out, $fn_head, $fn_tail) assertions_0 use assertions((), (), $patterns_in, $patterns_in) assertions_n use assertions(($first_assertion, *$rest_assertions), ($fn_lines1, *$fn_lines2), $patterns_in, $patterns_out) when !assertion($first_assertion, $fn_lines1, $patterns_in, $patterns_out1) !assertions($rest_assertions, $fn_lines2, $patterns_out1, $patterns_out) assertion use assertion(('assert', $kb_name, $entity_name, $patterns, $start_lineno, $end_lineno), $fn_lines, $patterns_in, $patterns_out) when ($pat_nums, $patterns_out) = \ helpers.merge_patterns($patterns, $patterns_in) $fn_lines = (('STARTING_LINENO', $start_lineno), "engine.assert_(%r, %r," % ($kb_name, $entity_name), ('INDENT', 15), helpers.list_format( ("rule.pattern(%d).as_data(context)" % pat_num for pat_num in $pat_nums), '(', ')),'), ('ENDING_LINENO', $end_lineno), "POPINDENT", ) python_assertion use assertion((python_assertion, ($python_code, $_, $_, $_), $start_lineno, $end_lineno), (('STARTING_LINENO', $start_lineno), $python_code, ('ENDING_LINENO', $end_lineno)), $patterns_in, $patterns_in) bc_rules use bc_rules($rb_name, $bc_rules, $bc_plan_lines, $bc_bc_funs, $bc_bc_init) when python bc_plan_lines = [] bc_bc_funs = [] bc_bc_init = [] forall $bc_rule in $bc_rules require !bc_rule($rb_name, $bc_rule, $bc_plan1, $bc_bc_fun1, $bc_bc_init1) python bc_plan_lines.extend($bc_plan1) bc_bc_funs.append($bc_bc_fun1) bc_bc_init.append($bc_bc_init1) $bc_plan_lines = tuple(bc_plan_lines) $bc_bc_funs = tuple(bc_bc_funs) $bc_bc_init = tuple(bc_bc_init) bc_rule_ use bc_rule($rb_name, (bc_rule, $name, $goal, $bc_premises, $python_lines, $plan_vars_needed), $plan_lines, $bc_fun_lines, $bc_init_lines) when !bc_premises($rb_name, $name, $bc_premises, $plan_vars_needed, $prem_plan_lines, $prem_fn_head, $prem_fn_tail, $prem_decl_lines) ($plan_lines, $goal_fn_head, $goal_fn_tail, $goal_decl_lines) = \ helpers.goal($rb_name, $name, $goal, $prem_plan_lines, $python_lines) $bc_fun_lines = ($goal_fn_head, $prem_fn_head, 'rule.rule_base.num_bc_rule_successes += 1', 'yield context' if $plan_lines else 'yield', $prem_fn_tail, 'rule.rule_base.num_bc_rule_failures += 1', $goal_fn_tail, ) $bc_init_lines = ($goal_decl_lines, $prem_decl_lines, "POPINDENT", ) bc_premises use bc_premises($rb_name, $rule_name, $bc_premises, $plan_vars_needed, $plan_lines, $fn_head, $fn_tail, $decl_lines) when !bc_premises1($rb_name, $rule_name, 1, $_, $bc_premises, None, True, (), $patterns, $plan_vars_needed, $plan_var_names, $plan_lines1, $fn_head, $fn_tail) $pat_lines = helpers.list_format($patterns, '(', '))') $decl_lines = ('(' + ' '.join(tuple(repr(plan_var_name) + ',' for plan_var_name in $plan_var_names)) + '),',) + $pat_lines $plan_lines = tuple(itertools.chain.from_iterable(itertools.chain( (lines for step, lines in $plan_lines1 if step is None), (lines for step, lines in sorted(((step, lines) for step, lines in $plan_lines1 if step is not None), key=lambda t: t[0]))))) bc_premises1_0 use bc_premises1($_, $_, $clause_num, $clause_num, (), $_, $_, $patterns, $patterns, $plan_var_names, $plan_var_names, (), (), ()) bc_premises1_n use bc_premises1($rb_name, $rule_name, $clause_num, $next_clause_num, ($first_prem, *$rest_prems), $break_cond, $allow_plan, $patterns_in, $patterns_out, $plan_var_names_in, $plan_var_names_out, $plan_lines, $fn_head, $fn_tail) when !bc_premise($rb_name, $rule_name, $clause_num, $next_clause_num1, $first_prem, $break_cond, $allow_plan, $patterns_in, $patterns_out1, $plan_var_names_in, $plan_var_names_out1, $plan_lines1, $fn_head1, $fn_tail1) !bc_premises1($rb_name, $rule_name, $next_clause_num1, $next_clause_num, $rest_prems, $break_cond, $allow_plan, $patterns_out1, $patterns_out, $plan_var_names_out1, $plan_var_names_out, $plan_lines2, $fn_head2, $fn_tail2) $plan_lines = $plan_lines1 + $plan_lines2 $fn_head = $fn_head1 + $fn_head2 $fn_tail = $fn_tail2 + $fn_tail1 bc_premise use bc_premise($rb_name, $rule_name, $clause_num, $next_clause_num, (bc_premise, $required, $kb_name, $entity_name, $arg_patterns, $plan_spec, $start_lineno, $end_lineno), $break_cond, $allow_plan, $patterns_in, $patterns_out, $plan_var_names_in, $plan_var_names_out, $plan_lines, $fn_head, $fn_tail) when $next_clause_num = $clause_num + 1 $kb_name2 = $kb_name or "rule.rule_base.root_name" ($pat_nums, $patterns_out1) = \ helpers.merge_patterns($arg_patterns, $patterns_in) $fn_head1 = (('STARTING_LINENO', $start_lineno), "with engine.prove(%s, %s, context," % ($kb_name2, $entity_name), ('INDENT', 2), ('INDENT', 16), helpers.list_format(('rule.pattern(%d)' % pat_num for pat_num in $pat_nums), '(', ')) \\'), 'POPINDENT', "as gen_%d:" % $clause_num, "for x_%d in gen_%d:" % ($clause_num, $clause_num), ('INDENT', 2), ) !add_required($required, $rb_name, $rule_name, $clause_num, $fn_head1, (POPINDENT, POPINDENT), $fn_head2, $fn_tail2) !gen_plan_lines($rb_name, $rule_name, $clause_num, $plan_spec, $allow_plan, $patterns_out1, $patterns_out, $fn_head3, $fn_tail3, $plan_lines, $plan_vars_needed) ($_, $plan_var_names_out) = helpers.merge_patterns($plan_vars_needed, $plan_var_names_in) $fn_head = $fn_head2 + $fn_head3 + (('ENDING_LINENO', $end_lineno),) $fn_tail = ($fn_tail3, () if $break_cond is None else "if %s: break" % $break_cond, $fn_tail2) bc_first use bc_premise($rb_name, $rule_name, $clause_num, $next_clause_num, (bc_first, $required, $bc_premises, $_), $_, $allow_plan, $patterns_in, $patterns_out, $plan_var_names_in, $plan_var_names_out, $plan_lines, ($init_worked, $fn_head, $set_worked), $fn_tail) when $break_cond = "first%d_worked" % $clause_num !bc_premises1($rb_name, $rule_name, $clause_num, $next_clause_num, $bc_premises, $break_cond, $allow_plan, $patterns_in, $patterns_out, $plan_var_names_in, $plan_var_names_out, $plan_lines, $fn_head1, $fn_tail1) !add_required($required, $rb_name, $rule_name, $clause_num, $fn_head1, $fn_tail1, $fn_head, $fn_tail) $init_worked = "%s = False" % $break_cond $set_worked = "%s = True" % $break_cond bc_forall_None use bc_premise($rb_name, $rule_name, $clause_num, $next_clause_num, (bc_forall, $bc_premises, None, $_, $_), $_, $_, $patterns_in, $patterns_out, $plan_var_names_in, $plan_var_names_out, $plan_lines, $fn_head, ()) when !bc_premises1($rb_name, $rule_name, $clause_num, $next_clause_num, $bc_premises, None, False, $patterns_in, $patterns_out, $plan_var_names_in, $plan_var_names_out, $plan_lines, $fn_head1, $fn_tail) $fn_head = $fn_head1 + $fn_tail bc_forall_require use bc_premise($rb_name, $rule_name, $clause_num, $next_clause_num, (bc_forall, $premises1, $require, $start_lineno, $_), $_, $_, $patterns_in, $patterns_out, $plan_var_names_in, $plan_var_names_out, (), $fn_head, ("POPINDENT",)) when $break_true = "forall%d_worked" % $start_lineno $break_false = "not forall%d_worked" % $start_lineno !bc_premises1($rb_name, $rule_name, $clause_num, $next_clause_num1, $premises1, $break_false, False, $patterns_in, $patterns_out1, $plan_var_names_in, $plan_var_names_out1, (), $fn_head1, $fn_tail1) !bc_premises1($rb_name, $rule_name, $next_clause_num1, $next_clause_num, $require, $break_true, False, $patterns_out1, $patterns_out, $plan_var_names_out1, $plan_var_names_out, (), $fn_head2, $fn_tail2) $fn_head = ("forall%d_worked = True" % $start_lineno, $fn_head1, "forall%d_worked = False" % $start_lineno, $fn_head2, "forall%d_worked = True" % $start_lineno, $fn_tail2, $fn_tail1, "if forall%d_worked:" % $start_lineno, ("INDENT", 2)) bc_notany use bc_premise($rb_name, $rule_name, $clause_num, $next_clause_num, (bc_notany, $bc_premises, $start_lineno), $_, $_, $patterns_in, $patterns_out, $plan_var_in, $plan_var_out, (), $fn_head, ("POPINDENT",)) when $break_true = "notany%d_worked" % $start_lineno $break_false = "not notany%d_worked" % $start_lineno !bc_premises1($rb_name, $rule_name, $clause_num, $next_clause_num, $bc_premises, $break_false, False, $patterns_in, $patterns_out, $plan_var_in, $plan_var_out, (), $fn_head1, $fn_tail1) $fn_head = ("notany%d_worked = True" % $start_lineno, $fn_head1, "notany%d_worked = False" % $start_lineno, $fn_tail1, "if notany%d_worked:" % $start_lineno, ("INDENT", 2)) no_plan use gen_plan_lines($rb_name, $rule_name, $clause_num, None, $_, $patterns_in, $patterns_in, $fn_head, (), (), ()) when $fn_head = ('assert x_%d is None, \\' % $clause_num, ('INDENT', 2), '"%(rb_name)s.%(rule_name)s: got unexpected plan from ' 'when clause %(clause_num)d"' % {'clause_num': $clause_num, 'rb_name': $rb_name, 'rule_name': $rule_name}, 'POPINDENT',) as_plan use gen_plan_lines($rb_name, $rule_name, $clause_num, ('as', $pat_var_name), $_, $patterns_in, $patterns_out, $fn_head, $fn_tail, (), ()) when ($pat_num, $patterns_out) = \ helpers.merge_pattern("contexts.variable(%r)" % $pat_var_name, $patterns_in) !plan_bindings($rb_name, $rule_name, $clause_num, $pat_var_name, $pat_num, $fn_head, $fn_tail) plan_spec use gen_plan_lines($rb_name, $rule_name, $clause_num, (plan_spec, $step_num, $plan_var_name, $python_code, $plan_vars_needed, $_, $_), True, $patterns_in, $patterns_out, $fn_head, $fn_tail, (($step_num, $python_code),), $plan_vars_needed) when ($pat_num, $patterns_out) = \ helpers.merge_pattern("contexts.variable(%r)" % $plan_var_name, $patterns_in) !plan_bindings($rb_name, $rule_name, $clause_num, $plan_var_name, $pat_num, $fn_head, $fn_tail) illegal_plan_spec use gen_plan_lines($_, $_, $_, (plan_spec, $_, $_, $_, $_, $lineno, $lexpos), False, $_, $_, $_, $_, $_, $_) when $_ = helpers.syntax_error("illegal plan_spec in forall", $lineno, $lexpos) plan_bindings use plan_bindings($rb_name, $rule_name, $clause_num, $plan_var_name, $pat_num, $fn_head, $fn_tail) when $fn_head = ('assert x_%d is not None, \\' % $clause_num, ('INDENT', 2), '"%(rb_name)s.%(rule_name)s: expected plan from ' 'when clause %(clause_num)d"' % {'clause_num': $clause_num, 'rb_name': $rb_name, 'rule_name': $rule_name}, 'POPINDENT', "mark%d = context.mark(True)" % $clause_num, "if not rule.pattern(%d).match_data(context, context, " "x_%d):" % ($pat_num, $clause_num), ('INDENT', 2), 'raise AssertionError("%(rb_name)s.%(rule_name)s: ' 'plan match to $%(plan_var_name)s failed in ' 'when clause %(clause_num)d")' % {'clause_num': $clause_num, 'plan_var_name': $plan_var_name, 'rb_name': $rb_name, 'rule_name': $rule_name}, 'POPINDENT', "context.end_save_all_undo()") $fn_tail = ("context.undo_to_mark(mark%d)" % $clause_num,) not_required use add_required(False, $_, $_, $_, $fn_head, $fn_tail, $fn_head, $fn_tail) required use add_required(True, $rb_name, $rule_name, $clause_num, $fn_head1, $fn_tail1, $fn_head, $fn_tail) when $fn_head = ("flag_%d = False" % $clause_num, $fn_head1, "flag_%d = True" % $clause_num, ) $fn_tail = ($fn_tail1, "if not flag_%d:" % $clause_num, ("INDENT", 2), "raise AssertionError(\"%s.%s: 'when' clause %d failed\")" % ($rb_name, $rule_name, $clause_num), "POPINDENT", ) bc_python_premise use bc_premise($rb_name, $rule_name, $clause_num, $next_clause_num, $python_premise, $break_cond, $_, $patterns_in, $patterns_out, $plan_var_names, $plan_var_names, (), $fn_head, $fn_tail) when $next_clause_num = $clause_num + 1 python_premise($clause_num, $python_premise, $break_cond, $patterns_in, $patterns_out, $fn_head, $fn_tail) python_eq use python_premise($clause_num, (python_eq, $pattern, ($python_code, $_, $_, $_), $start_lineno, $end_lineno), $_, $patterns_in, $patterns_out, $fn_head, $fn_tail) when ($pat_num, $patterns_out) = \ helpers.merge_pattern($pattern, $patterns_in) $python_code2 = $python_code[:-1] + ($python_code[-1] + '):',) $fn_head = ("mark%d = context.mark(True)" % $clause_num, "if rule.pattern(%d).match_data(context, context," % $pat_num, ('INDENT', 2), ('INDENT', 5), ('STARTING_LINENO', $start_lineno), $python_code2, ('ENDING_LINENO', $end_lineno), "POPINDENT", "context.end_save_all_undo()", ) $fn_tail = ('POPINDENT', "else: context.end_save_all_undo()", "context.undo_to_mark(mark%d)" % $clause_num,) python_in use python_premise($clause_num, (python_in, $pattern, ($python_code, $_, $_, $_), $start_lineno, $end_lineno), $break_cond, $patterns_in, $patterns_out, $fn_head, $fn_tail) when ($pat_num, $patterns_out) = \ helpers.merge_pattern($pattern, $patterns_in) $python_code2 = $python_code[:-1] + ($python_code[-1] + ':',) $fn_head = ("for python_ans in \\", ('INDENT', 2), ('INDENT', 2), ('STARTING_LINENO', $start_lineno), $python_code2, ('ENDING_LINENO', $end_lineno), 'POPINDENT', "mark%d = context.mark(True)" % $clause_num, "if rule.pattern(%d).match_data(context, context, " "python_ans):" % $pat_num, ('INDENT', 2), "context.end_save_all_undo()", ) $fn_tail = ( () if $break_cond is None else ("if %s:" % $break_cond, ('INDENT', 2), "context.undo_to_mark(mark%d)" % $clause_num, "break", 'POPINDENT',), 'POPINDENT', "else: context.end_save_all_undo()", "context.undo_to_mark(mark%d)" % $clause_num, 'POPINDENT',) python_check use python_premise($clause_num, (python_check, ($python_code, $_, $_, $_), $start_lineno, $end_lineno), $_, $patterns_in, $patterns_in, $fn_head, ('POPINDENT',)) when $python_code2 = $python_code[:-1] + ($python_code[-1] + ':',) $fn_head = (('STARTING_LINENO', $start_lineno), "if " + $python_code2[0].strip(), ('INDENT', 3), $python_code2[1:], 'POPINDENT', ('ENDING_LINENO', $end_lineno), ('INDENT', 2), ) python_block use python_premise($clause_num, (python_block, ($python_code, $_, $_, $_), $start_lineno, $end_lineno), $_, $patterns_in, $patterns_in, (('STARTING_LINENO', $start_lineno), $python_code, ('ENDING_LINENO', $end_lineno)), ()) bc_extras from pyke.krb_compiler import helpers ./pyke-1.1.1/pyke/krb_compiler/kfbparser.py0000644000175000017500000001236311353376030017570 0ustar lambylamby# $Id: kfbparser.py 49507964ae64 2010-03-27 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ See http://www.dabeaz.com/ply/ply.html for syntax of grammer definitions. """ from __future__ import with_statement import os, os.path from pyke.krb_compiler.ply import yacc from pyke.krb_compiler import scanner from pyke import fact_base tokens = scanner.kfb_tokens def p_file(p): ''' file : nl_opt facts_opt facts_opt : facts_opt : facts nl_opt facts : fact facts : facts NL_TOK fact ''' pass def p_fact0(p): ''' fact : IDENTIFIER_TOK LP_TOK RP_TOK ''' Fact_base.add_universal_fact(p[1], ()) def p_fact1(p): ''' fact : IDENTIFIER_TOK LP_TOK data_list RP_TOK ''' Fact_base.add_universal_fact(p[1], tuple(p[3])) def p_none(p): ''' data : NONE_TOK comma_opt : comma_opt : ',' nl_opt : nl_opt : NL_TOK ''' p[0] = None def p_number(p): ''' data : NUMBER_TOK ''' p[0] = p[1] def p_string(p): ''' data : STRING_TOK ''' p[0] = eval(p[1]) def p_quoted_last(p): ''' data : IDENTIFIER_TOK ''' p[0] = p[1] def p_false(p): ''' data : FALSE_TOK ''' p[0] = False def p_true(p): ''' data : TRUE_TOK ''' p[0] = True def p_empty_tuple(p): ''' data : LP_TOK RP_TOK ''' p[0] = () def p_start_list(p): ''' data_list : data ''' p[0] = [p[1]] def p_append_list(p): ''' data_list : data_list ',' data ''' p[1].append(p[len(p)-1]) p[0] = p[1] def p_tuple(p): ''' data : LP_TOK data_list comma_opt RP_TOK ''' p[0] = tuple(p[2]) def p_error(t): if t is None: raise SyntaxError("invalid syntax", scanner.syntaxerror_params()) else: raise SyntaxError("invalid syntax", scanner.syntaxerror_params(t.lexpos, t.lineno)) parser = None def init(this_module, check_tables = False, debug = 0): global parser if parser is None: outputdir = os.path.dirname(this_module.__file__) if debug: parser = yacc.yacc(module=this_module, write_tables=0, debug=debug, debugfile='kfbparser.yacc.out', outputdir=outputdir) else: if check_tables: kfbparser_mtime = os.path.getmtime(this_module.__file__) tables_name = os.path.join(outputdir, 'kfbparser_tables.py') try: ok = os.path.getmtime(tables_name) >= kfbparser_mtime except OSError: ok = False if not ok: #print "regenerating kfbparser_tables" try: os.remove(tables_name) except OSError: pass try: os.remove(tables_name + 'c') except OSError: pass try: os.remove(tables_name + 'o') except OSError: pass parser = yacc.yacc(module=this_module, debug=0, optimize=1, write_tables=1, tabmodule='pyke.krb_compiler.kfbparser_tables', outputdir=outputdir) # Use the first line for normal use, the second for testing changes in the # grammer (the first line does not report grammer errors!). def parse(this_module, filename, check_tables = False, debug = 0): #def parse(this_module, filename, check_tables = False, debug = 1): ''' >>> from pyke.krb_compiler import kfbparser >>> kfbparser.parse(kfbparser, ... os.path.join(os.path.dirname(__file__), ... 'TEST/kfbparse_test.kfb'), ... True) ''' global Fact_base init(this_module, check_tables, debug) name = os.path.basename(filename)[:-4] Fact_base = fact_base.fact_base(None, name, False) with open(filename) as f: scanner.init(scanner, debug, check_tables, True) scanner.lexer.lineno = 1 scanner.lexer.filename = filename #parser.restart() parser.parse(f.read(), lexer=scanner.lexer, tracking=True, debug=debug) ans = Fact_base Fact_base = None return ans ./pyke-1.1.1/pyke/krb_compiler/testall.config0000644000175000017500000000002511346504630020067 0ustar lambylambyexclude compiled_krb ./pyke-1.1.1/pyke/rule_base.py0000644000175000017500000002206411346504630015102 0ustar lambylamby# $Id: rule_base.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import itertools from pyke import knowledge_base class StopProof(Exception): pass class stopIteratorContext(object): def __init__(self, rule_base, iterator_context): self.rule_base = rule_base self.context = iterator_context def __enter__(self): return stopIterator(self.rule_base, self.context.__enter__()) def __exit__(self, type, value, tb): self.context.__exit__(type, value, tb) class stopIterator(object): def __init__(self, rule_base, iterator): self.rule_base = rule_base self.iterator = iter(iterator) def __iter__(self): return self def next(self): if self.iterator: try: return self.iterator.next() except StopProof: self.iterator = None self.rule_base.num_bc_rule_failures += 1 raise StopIteration class chain_context(object): def __init__(self, outer_it): self.outer_it = outer_iterable(outer_it) def __enter__(self): return itertools.chain.from_iterable(self.outer_it) def __exit__(self, type, value, tb): self.outer_it.close() class outer_iterable(object): def __init__(self, outer_it): self.outer_it = iter(outer_it) self.inner_it = None def __iter__(self): return self def close(self): if hasattr(self.inner_it, '__exit__'): self.inner_it.__exit__(None, None, None) elif hasattr(self.inner_it, 'close'): self.inner_it.close() if hasattr(self.outer_it, 'close'): self.outer_it.close() def next(self): ans = self.outer_it.next() if hasattr(ans, '__enter__'): self.inner_it = ans return ans.__enter__() ans = iter(ans) self.inner_it = ans return ans class rule_base(knowledge_base.knowledge_base): def __init__(self, engine, name, parent = None, exclude_list = ()): super(rule_base, self).__init__(engine, name, rule_list, False) if name in engine.rule_bases: raise AssertionError("rule_base %s already exists" % name) if name in engine.knowledge_bases: raise AssertionError("name clash between rule_base '%s' and " "fact_base '%s'" % (name, name)) engine.rule_bases[name] = self self.fc_rules = [] self.parent = parent self.exclude_set = frozenset(exclude_list) self.rules = {} # {name: rule} def add_fc_rule(self, fc_rule): if fc_rule.name in self.rules: raise AssertionError("%s rule_base: duplicate rule name: %s" % (self.name, fc_rule.name)) self.rules[fc_rule.name] = fc_rule self.fc_rules.append(fc_rule) def add_bc_rule(self, bc_rule): if bc_rule.name in self.rules: raise AssertionError("%s rule_base: duplicate rule name: %s" % (self.name, bc_rule.name)) self.rules[bc_rule.name] = bc_rule self.get_entity_list(bc_rule.goal_name).add_bc_rule(bc_rule) def init2(self): if not self.initialized: self.initialized = True if self.parent: parent = self.engine.rule_bases.get(self.parent) if parent is None: raise KeyError("rule_base %s: parent %s not found" % \ (self.name, self.parent)) self.parent = parent self.parent.init2() self.root_name = self.parent.root_name else: self.root_name = self.name self.reset() def derived_from(self, rb): parent = self.parent while parent: if parent == rb: return True parent = parent.parent return False def register_fc_rules(self, stop_at_rb): rb = self while rb is not stop_at_rb: for fc_rule in rb.fc_rules: fc_rule.register_rule() if not rb.parent: break rb = rb.parent def run_fc_rules(self, stop_at_rb): rb = self while rb is not stop_at_rb: for fc_rule in rb.fc_rules: fc_rule.run() if not rb.parent: break rb = rb.parent def activate(self): current_rb = self.engine.knowledge_bases.get(self.root_name) if current_rb: assert self.derived_from(current_rb), \ "%s.activate(): not derived from current rule_base, %s" % \ (self.name, current_rb.name) self.engine.knowledge_bases[self.root_name] = self self.register_fc_rules(current_rb) self.run_fc_rules(current_rb) def reset(self): if self.root_name in self.engine.knowledge_bases: del self.engine.knowledge_bases[self.root_name] for fc_rule in self.fc_rules: fc_rule.reset() self.num_fc_rules_triggered = 0 self.num_fc_rules_rerun = 0 self.num_prove_calls = 0 self.num_bc_rules_matched = 0 self.num_bc_rule_successes = 0 self.num_bc_rule_failures = 0 def gen_rule_lists_for(self, goal_name): rule_base = self while True: rl = rule_base.entity_lists.get(goal_name) if rl: yield rl if rule_base.parent and goal_name not in rule_base.exclude_set: rule_base = rule_base.parent else: break def prove(self, bindings, pat_context, goal_name, patterns): self.num_prove_calls += 1 return stopIteratorContext(self, chain_context( rl.prove(bindings, pat_context, patterns) for rl in self.gen_rule_lists_for(goal_name))) def print_stats(self, f): f.write("%s: %d fc_rules, %d triggered, %d rerun\n" % (self.name, len(self.fc_rules), self.num_fc_rules_triggered, self.num_fc_rules_rerun)) num_bc_rules = sum(rule_list.num_bc_rules() for rule_list in self.entity_lists.itervalues()) f.write("%s: %d bc_rules, %d goals, %d rules matched\n" % (self.name, num_bc_rules, self.num_prove_calls, self.num_bc_rules_matched)) f.write("%s %d successes, %d failures\n" % (' ' * len(self.name), self.num_bc_rule_successes, self.num_bc_rule_failures)) if self.parent: self.parent.print_stats(f) def trace(self, rule_name): for rule_list in self.entity_lists.itervalues(): if rule_list.trace(rule_name): return raise KeyError("trace: rule %s not found" % rule_name) def untrace(self, rule_name): for rule_list in self.entity_lists.itervalues(): if rule_list.untrace(rule_name): return raise KeyError("untrace: rule %s not found" % rule_name) class rule_list(knowledge_base.knowledge_entity_list): def __init__(self, name): self.name = name self.bc_rules = [] def add_bc_rule(self, bc_rule): self.bc_rules.append(bc_rule) def prove(self, bindings, pat_context, patterns): """ Returns a context manager for a generator that binds patterns to successively proven goals, yielding the plan (or None, if no plan) for each successful match. Undoes bindings upon continuation, so that no bindings remain at StopIteration. """ return chain_context( bc_rule.bc_fn(bc_rule, patterns, pat_context) for bc_rule in self.bc_rules) def num_bc_rules(self): return len(self.bc_rules) def trace(self, rule_name): for bc_rule in self.bc_rules: if bc_rule.name == rule_name: bc_rule.trace() return True return False def untrace(self, rule_name): for bc_rule in self.bc_rules: if bc_rule.name == rule_name: bc_rule.untrace() return True return False ./pyke-1.1.1/pyke/immutable_dict.py0000644000175000017500000000636311346504630016127 0ustar lambylamby# $Id: immutable_dict.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. class immutable_dict(dict): ''' >>> im = immutable_dict((('a', 1), ('b', 2))) >>> len(im) 2 >>> im['a'] 1 >>> im['b'] 2 >>> tuple(sorted(im.keys())) ('a', 'b') >>> tuple(sorted(im.values())) (1, 2) >>> 'a' in im True >>> 'c' in im False >>> del im['a'] Traceback (most recent call last): ... TypeError: del (a) not allowed on plan context >>> im['a'] = 3 Traceback (most recent call last): ... TypeError: not allowed to change pattern variables (a) in plan >>> im.clear() Traceback (most recent call last): ... TypeError: clear not allowed on plan context >>> im.pop('a') Traceback (most recent call last): ... TypeError: pop (a) not allowed on plan context >>> im.popitem() Traceback (most recent call last): ... TypeError: popitem not allowed on plan context >>> im.setdefault('a', []) Traceback (most recent call last): ... TypeError: setdefault (a) not allowed on plan context >>> im.update({'c': 3}) Traceback (most recent call last): ... TypeError: update not allowed on plan context ''' def __delitem__(self, key): raise TypeError("del (%s) not allowed on plan context" % key) def __setitem__(self, key, value): raise TypeError("not allowed to change pattern variables (%s) in plan" % key) def clear(self): raise TypeError("clear not allowed on plan context") def pop(self, key, default = None): raise TypeError("pop (%s) not allowed on plan context" % key) def popitem(self): raise TypeError("popitem not allowed on plan context") def setdefault(self, key, default = None): raise TypeError("setdefault (%s) not allowed on plan context" % key) def update(self, dict2 = None, **kwargs): raise TypeError("update not allowed on plan context") ./pyke-1.1.1/pyke/fc_rule.py0000644000175000017500000001060711363610730014556 0ustar lambylamby# $Id: fc_rule.py 38124415f0b5 2010-04-21 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ''' Forward chaining rules (fc_rule) are one of two types of rules in a rule_base (the other being backward chaining rules -- bc_rule). All forward chaining is done automatically as each rule_base is activated. This is done in two steps: 1. All fc_rules are registered with the fact_lists referenced in their 'foreach' clause by calling fc_rule.register_rule() on each fc_rule (including the parent rule_base's fc_rules). This will cause the fact_list to invoke fc_rule.new_fact each time a new fact for that fact_list (by that name) is asserted (by the same or another fc_rule). 2. The fc_rule.run() function is called on each fc_rule (including the parent rule_base's fc_rules). The forward chaining rule is compiled into a python function which does the actual inferencing work for both the 'run' case and the 'new_fact' case, depending on the arguments passed to it. Each fc_rule object remembers its associated compiled python function. The fc_rule object tracks the progress of the forward chaining for that rule. If the rule has not been run yet, it ignores new_facts since it will see the new fact when it is later run. ''' from pyke import contexts, fact_base import itertools class rule(object): ''' Common to both fc_rules and bc_rules. ''' def __init__(self, name, rule_base, patterns): self.name = name self.rule_base = rule_base self.patterns = patterns def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.name) def pattern(self, pattern_index): return self.patterns[pattern_index] class fc_rule(rule): def __init__(self, name, rule_base, rule_fn, foreach_facts, patterns): super(fc_rule, self).__init__(name, rule_base, patterns) rule_base.add_fc_rule(self) self.rule_fn = rule_fn self.foreach_facts = foreach_facts # (kb_name, fact_name, arg_pats, # multi_match?)... self.ran = False def register_rule(self): for i, (kb_name, fact_name, arg_patterns, multi_match) \ in enumerate(self.foreach_facts): self.rule_base.engine.get_kb(kb_name, fact_base.fact_base) \ .add_fc_rule_ref(fact_name, self, i) def reset(self): self.ran = False def run(self): self.ran = True self.rule_fn(self) def new_fact(self, fact_args, n): if self.ran: arg_patterns = self.foreach_facts[n][2] if len(fact_args) == len(arg_patterns): context = contexts.simple_context() if all(itertools.imap(lambda pat, arg: pat.match_data(context, context, arg), arg_patterns, fact_args)): self.rule_base.num_fc_rules_rerun += 1 if self.foreach_facts[n][3]: self.rule_fn(self) else: self.rule_fn(self, context, n) context.done() def foreach_patterns(self, foreach_index): return self.foreach_facts[foreach_index][2] ./pyke-1.1.1/pyke/question_base.py0000644000175000017500000001113311346504630015775 0ustar lambylamby# $Id: question_base.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import contextlib from pyke import unique from pyke import knowledge_base class question_base(knowledge_base.knowledge_base): r''' Each instance keeps track of a related set of questions. ''' def __init__(self, name): r''' This is only used by the compiler, so only creates an instance suitable for pickling. Specifically, this means that the self.engine is just set to None and the instance is not registered with any engine. ''' super(question_base, self).__init__(None, name, register=False) def add_question(self, question): name = question.name if name in self.entity_lists: raise AssertionError("question_base %s: duplicate question, %s" % (self.name, name)) self.entity_lists[name] = question question.set_knowledge_base(self) def get_ask_module(self): if hasattr(self, 'ask_module'): return self.ask_module return self.engine.get_ask_module() class question(knowledge_base.knowledge_entity_list): r''' This represents one question in a question_base. It takes care of lookup parameters and caching and delegates the work of actually asking the user a question to the user_question object by calling its 'ask' method passing the format parameters. ''' not_found = unique.unique('question.not_found') def __init__(self, name, params, answer_param, user_question): super(question, self).__init__(name) self.params = tuple(params) self.answer_param = answer_param try: self.answer_param_position = list(params).index(answer_param) except ValueError: raise ValueError("question %s: answer parameter, %s, " "not in params list: %s" % (answer_param, params)) self.input_param_positions = \ tuple(filter(lambda i: i != self.answer_param_position, range(len(self.params)))) self.user_question = user_question self.cache = {} def __repr__(self): return "" % \ (self.name, ', '.join('$' + p for p in self.params), self.answer_param, repr(self.user_question)) def set_knowledge_base(self, question_base): self.knowledge_base = question_base self.user_question.set_question_base(question_base) def lookup(self, bindings, pat_context, patterns): input_params = tuple((self.params[i], unicode(patterns[i].as_data(pat_context))) for i in self.input_param_positions) format_params = dict(input_params) ans = self.cache.get(input_params, self.not_found) if ans is self.not_found: ans = self.cache[input_params] = \ self.user_question.ask(format_params) def gen(): mark = bindings.mark(True) end_done = False try: if patterns[self.answer_param_position] \ .match_data(bindings, pat_context, ans): bindings.end_save_all_undo() end_done = True yield finally: if not end_done: bindings.end_save_all_undo() bindings.undo_to_mark(mark) return contextlib.closing(gen()) def reset(self): self.cache.clear() ./pyke-1.1.1/pyke/qa_helpers.py0000644000175000017500000002473711346504630015275 0ustar lambylamby# $Id: qa_helpers.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import types import re import sys class regexp(object): r''' >>> m = regexp(ur'(hi)\s*there', u'the msg', u'the prompt') >>> m >>> m.msg u'the msg' >>> m.prompt u'the prompt' >>> m.match(u'hithere') u'hi' ''' def __init__(self, regexp, msg=None, prompt=None): self.re = re.compile(regexp, re.UNICODE | re.VERBOSE) self.pattern = regexp self.msg = msg self.prompt = prompt def __repr__(self): if self.msg: if self.prompt: return '' % \ (self.msg, str(self.prompt), self.pattern) return '' % (self.msg, self.pattern) if self.prompt: return '' % (self.prompt, self.pattern) return '' % self.pattern def __getstate__(self): return self.pattern, self.msg, self.prompt def __setstate__(self, args): self.pattern, self.msg, self.prompt = args self.re = re.compile(self.pattern, re.UNICODE | re.VERBOSE) def match(self, str): m = self.re.match(str) if m and m.end() == len(str): if m.lastindex > 1: return m.groups() if m.lastindex == 1: return m.group(1) return str class qmap(object): r''' >>> m = qmap(u'y', True) >>> m >>> m.test u'y' >>> m.value True ''' def __init__(self, test, value): self.test = test self.value = value def __repr__(self): return "" % (repr(self.value), repr(self.test)) if sys.version_info[0] < 3: def urepr(x): r''' >>> urepr(44) u'44' >>> tuple(urepr('hi\n')) (u"'", u'h', u'i', u'\\', u'n', u"'") ''' if isinstance(x, types.StringTypes): return repr(x.encode('utf-8')).decode('utf-8') return unicode(x) else: urepr = repr def to_int(str): r''' >>> to_int(u' -34') -34 >>> to_int(u' +43') 43 >>> to_int(u'43x') Traceback (most recent call last): ... ValueError: illegal integer: '43x' ''' try: return int(str) except ValueError: raise ValueError(u"illegal integer: %s" % urepr(str)) def to_float(str): r''' >>> str(to_float(u' -34.444')) '-34.444' >>> str(to_float(u' +43')) '43.0' >>> to_float(u'43.3.3') Traceback (most recent call last): ... ValueError: illegal floating point number: '43.3.3' ''' try: return float(str) except ValueError: raise ValueError(u"illegal floating point number: %s" % urepr(str)) def to_number(str): r''' >>> str(to_number(u' -34.444')) '-34.444' >>> to_number(u' +43') 43 >>> to_number(u'43.3.3') Traceback (most recent call last): ... ValueError: illegal number: '43.3.3' ''' try: return to_int(str) except ValueError: try: return to_float(str) except ValueError: raise ValueError(u"illegal number: %s" % urepr(str)) def to_tuple(str, conv_fn=None, test=None, separator=','): r''' >>> to_tuple(u'1, 2,3', to_int) (1, 2, 3) >>> to_tuple(u'1, 2.5, -7e3', to_number) (1, 2.5, -7000.0) >>> to_tuple(u'43', to_number) (43,) >>> to_tuple(u'1,43.3.3', to_number) Traceback (most recent call last): ... ValueError: illegal number: '43.3.3' ''' def conv_element(elem): elem = elem.strip() if conv_fn: elem = conv_fn(elem) if test: elem = match(elem, test) return elem return tuple(conv_element(elem) for elem in str.split(separator)) def msg_for(test, type): r''' >>> msg_for(None, int) >>> msg_for(regexp(u'', u'the msg'), int) u'the msg' >>> msg_for(qmap(44, True), int) u'44' >>> msg_for(slice(3, 55), int) u'between 3 and 55' >>> msg_for(slice(None, 55), int) u'<= 55' >>> msg_for(slice(3, None), int) u'>= 3' >>> msg_for(slice(None, None), int) u'' >>> msg_for(slice(3, 55), str) u'between 3 and 55 characters' >>> msg_for(slice(None, 55), str) u'<= 55 characters' >>> msg_for(slice(3, None), str) u'>= 3 characters' >>> msg_for(slice(None, None), str) u'' >>> msg_for((slice(3, 5), True), str) u'between 3 and 5 characters or True' >>> msg_for(True, str) u'True' ''' if test is None: return None if isinstance(test, regexp): return test.msg if isinstance(test, qmap): return msg_for(test.test, type) if isinstance(test, slice): if test.start is None: if test.stop is not None: ans = u"<= %d" % test.stop else: ans = u"" elif test.stop is None: ans = u">= %d" % test.start else: ans = u"between %d and %d" % (test.start, test.stop) if (type == str or type == unicode) and ans: ans += u' characters' return ans if isinstance(test, (tuple, list)): return u' or '.join(filter(None, (msg_for(test_i, type) for test_i in test))) return urepr(test) def match_prompt(test, type, format, default=u''): r''' >>> match_prompt(None, int, u' [%s] ') u'' >>> match_prompt(regexp(u'', u'', u'the prompt'), int, u' [%s] ') u' [the prompt] ' >>> match_prompt(qmap(44, True), int, u' [%s] ') u' [44] ' >>> match_prompt(slice(3, 55), int, u' [%s] ') u' [3-55] ' >>> match_prompt(slice(None, 55), int, u' [%s] ') u' [max 55] ' >>> match_prompt(slice(3, None), int, u' [%s] ') u' [min 3] ' >>> match_prompt(slice(None, None), int, u' [%s] ', u'foo') u'foo' >>> match_prompt(slice(3, 55), str, u' [%s] ') u' [len: 3-55] ' >>> match_prompt(slice(None, 55), str, u' [%s] ') u' [len <= 55] ' >>> match_prompt(slice(3, None), str, u' [%s] ') u' [len >= 3] ' >>> match_prompt(slice(None, None), str, u' [%s] ') u'' >>> match_prompt((slice(3, 5), True), str, u' [%s] ') u' [len: 3-5 or True] ' >>> match_prompt(True, str, u' [%s] ') u' [True] ' ''' def prompt_body(test, type): if test is None: return None if isinstance(test, regexp): return test.prompt if isinstance(test, qmap): return prompt_body(test.test, type) if isinstance(test, slice): if test.start is None: if test.stop is not None: if issubclass(type, types.StringTypes): return u"len <= %d" % test.stop else: return u"max %d" % test.stop else: return u"" elif test.stop is None: if issubclass(type, types.StringTypes): return u"len >= %d" % test.start else: return u"min %d" % test.start else: if issubclass(type, types.StringTypes): return u"len: %d-%d" % (test.start, test.stop) else: return u"%d-%d" % (test.start, test.stop) if isinstance(test, (tuple, list)): return u' or '.join(filter(None, (prompt_body(test_i, type) for test_i in test))) return urepr(test) body = prompt_body(test, type) if body: return format % body return default def match(ans, test): r''' >>> match(u'foobar', None) u'foobar' >>> match(u'hithere', regexp(ur'(hi)\s*there', u'hi there')) u'hi' >>> match(u'hi mom', regexp(ur'(hi)\s*there', u'hi there')) Traceback (most recent call last): ... ValueError: hi there >>> match(u'y', qmap(u'y', True)) True >>> match(2, qmap(slice(3, 5), True)) Traceback (most recent call last): ... ValueError: between 3 and 5 >>> match(3, slice(3,5)) 3 >>> match(2, slice(3,5)) Traceback (most recent call last): ... ValueError: between 3 and 5 >>> match(2, (slice(3,5), slice(5,10), 2)) 2 >>> match(2, 2) 2 ''' if test is None: return ans if isinstance(test, regexp): ans = test.match(ans) if ans is not None: return ans if isinstance(test, qmap): match(ans, test.test) # raises ValueError if it doesn't match return test.value elif isinstance(test, slice): if isinstance(ans, types.StringTypes): value = len(ans) else: value = ans if (test.start is None or value >= test.start) and \ (test.stop is None or value <= test.stop): return ans elif isinstance(test, (tuple, list)): for test_i in test: try: return match(ans, test_i) except ValueError: pass elif test == ans: return ans raise ValueError(msg_for(test, type(ans))) ./pyke-1.1.1/pyke/bc_rule.py0000644000175000017500000000553011346504626014560 0ustar lambylamby# $Id: bc_rule.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import functools from pyke import fc_rule, immutable_dict class bc_rule(fc_rule.rule): ''' This represents a single backward-chaining rule. Most of its behavior is inherited. ''' def __init__(self, name, rule_base, goal_name, bc_fn, plan_fn, goal_arg_patterns, plan_vars, patterns): super(bc_rule, self).__init__(name, rule_base, patterns) self.goal_name = goal_name self.orig_bc_fn = bc_fn self.bc_fn = bc_fn self.plan_fn = plan_fn self.goal_arg_pats = goal_arg_patterns self.plan_vars = plan_vars rule_base.add_bc_rule(self) def goal_arg_patterns(self): return self.goal_arg_pats def make_plan(self, context, final): return functools.partial(self.plan_fn, immutable_dict.immutable_dict( (var_name, context.lookup_data(var_name, final=final)) for var_name in self.plan_vars)) def trace(self): self.bc_fn = self.surrogate def surrogate(self, rule, arg_patterns, arg_context): print "%s.%s%s" % (rule.rule_base.root_name, rule.name, tuple(arg.as_data(arg_context, True) for arg in arg_patterns)) for prototype_plan in self.orig_bc_fn(rule, arg_patterns, arg_context): print "%s.%s succeeded with %s" % \ (rule.rule_base.root_name, rule.name, tuple(arg.as_data(arg_context, True) for arg in arg_patterns)) yield prototype_plan print "%s.%s failed" % (rule.rule_base.root_name, rule.name) def untrace(self): self.bc_fn = self.orig_bc_fn ./pyke-1.1.1/pyke/fact_base.py0000644000175000017500000002353211346504630015051 0ustar lambylamby# $Id: fact_base.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ''' A fact_base is one of the kinds of knowledge_bases (see also, rule_base and special). >>> from pyke import knowledge_engine >>> engine = knowledge_engine.engine() >>> fb = fact_base(engine, 'fb_name') >>> fb >>> fb.dump_universal_facts() >>> fb.dump_specific_facts() A fact_base is nothing more than a list of facts. Each fact has a name and a tuple of arguments. These arguments are python data (not patterns). Fact_bases support two kinds of facts: universal facts (universally true) and case specific facts (only true in a specific situation). >>> fb.add_universal_fact('some_universal_fact', ('a', 2)) >>> fb.add_case_specific_fact('some_specific_fact', ('b', ('hi', 32))) >>> fb.dump_universal_facts() some_universal_fact('a', 2) >>> fb.dump_specific_facts() some_specific_fact('b', ('hi', 32)) The 'reset' method deletes all case specific facts, but leaves the universal facts. >>> fb.reset() >>> fb.dump_universal_facts() some_universal_fact('a', 2) >>> fb.dump_specific_facts() Normally, universal facts are established once at program initialization time and case specific facts are established both just prior to each invocation of the expert system as well as by assertions in forward chaining rules. >>> fb.assert_('some_fact', ('a', 2, ('hi', 'mom'))) >>> fb.dump_universal_facts() some_universal_fact('a', 2) >>> fb.dump_specific_facts() some_fact('a', 2, ('hi', 'mom')) >>> fb.assert_('some_fact', ('a', 3, ('hi', 'mom'))) >>> fb.dump_specific_facts() some_fact('a', 2, ('hi', 'mom')) some_fact('a', 3, ('hi', 'mom')) >>> fb.assert_('some_other_fact', ()) >>> fb.dump_specific_facts() some_fact('a', 2, ('hi', 'mom')) some_fact('a', 3, ('hi', 'mom')) some_other_fact() Duplicate facts are not allowed and trying to assert a duplicate fact is silently ignored. >>> fb.assert_('some_fact', ('a', 2, ('hi', 'mom'))) >>> fb.dump_specific_facts() some_fact('a', 2, ('hi', 'mom')) some_fact('a', 3, ('hi', 'mom')) some_other_fact() ''' import itertools import contextlib from pyke import knowledge_base, contexts class fact_base(knowledge_base.knowledge_base): ''' Not much to fact_bases. The real work is done in fact_list! ''' def __init__(self, engine, name, register = True): super(fact_base, self).__init__(engine, name, fact_list, register) def dump_universal_facts(self): for fl_name in sorted(self.entity_lists.iterkeys()): self.entity_lists[fl_name].dump_universal_facts() def dump_specific_facts(self): for fl_name in sorted(self.entity_lists.iterkeys()): self.entity_lists[fl_name].dump_specific_facts() def add_universal_fact(self, fact_name, args): self.get_entity_list(fact_name).add_universal_fact(args) def add_case_specific_fact(self, fact_name, args): self.get_entity_list(fact_name).add_case_specific_fact(args) def assert_(self, fact_name, args): self.add_case_specific_fact(fact_name, args) def get_stats(self): num_fact_lists = num_universal = num_case_specific = 0 for fact_list in self.entity_lists.itervalues(): universal, case_specific = fact_list.get_stats() num_universal += universal num_case_specific += case_specific num_fact_lists += 1 return num_fact_lists, num_universal, num_case_specific def print_stats(self, f): num_fact_lists, num_universal, num_case_specific = self.get_stats() f.write("%s: %d fact names, %d universal facts, " "%d case_specific facts\n" % (self.name, num_fact_lists, num_universal, num_case_specific)) class fact_list(knowledge_base.knowledge_entity_list): def __init__(self, name): super(fact_list, self).__init__(name) self.universal_facts = [] # [(arg...)...] self.case_specific_facts = [] # [(arg...)...] self.hashes = {} # (len, (index...)): (other_indices, # {(arg...): [other_args_from_factn...]}) self.fc_rule_refs = [] # (fc_rule, foreach_index) def reset(self): self.case_specific_facts = [] self.hashes.clear() self.fc_rule_refs = [] def dump_universal_facts(self): for args in self.universal_facts: print '%s%s' % (self.name, args) def dump_specific_facts(self): for args in self.case_specific_facts: print '%s%s' % (self.name, args) def add_fc_rule_ref(self, fc_rule, foreach_index): self.fc_rule_refs.append((fc_rule, foreach_index)) def get_affected_fc_rules(self): return (fc_rule for fc_rule, foreach_index in self.fc_rule_refs) def lookup(self, bindings, pat_context, patterns): """ Returns a context manager for a generator that binds patterns to successive facts, yielding None for each successful match. Undoes bindings upon continuation, so that no bindings remain at StopIteration. """ indices = tuple(enum for enum in enumerate(patterns) if enum[1].is_data(pat_context)) other_indices, other_arg_lists = \ self._get_hashed(len(patterns), tuple(index[0] for index in indices), tuple(index[1].as_data(pat_context) for index in indices)) def gen(): if other_arg_lists: for args in other_arg_lists: mark = bindings.mark(True) end_done = False try: if all(itertools.imap( lambda i, arg: patterns[i].match_data(bindings, pat_context, arg), other_indices, args)): bindings.end_save_all_undo() end_done = True yield finally: if not end_done: bindings.end_save_all_undo() bindings.undo_to_mark(mark) return contextlib.closing(gen()) def _get_hashed(self, len, indices, args): ans = self.hashes.get((len, indices)) if ans is None: ans = self._hash(len, indices) other_indices, arg_map = ans return other_indices, arg_map.get(args, ()) def _hash(self, length, indices): args_hash = {} new_entry = (tuple(i for i in range(length) if i not in indices), args_hash) self.hashes[length, indices] = new_entry for args in itertools.chain(self.universal_facts, self.case_specific_facts): if len(args) == length: selected_args = tuple(arg for i, arg in enumerate(args) if i in indices) args_hash.setdefault(selected_args, []) \ .append(tuple(arg for i, arg in enumerate(args) if i not in indices)) return new_entry def add_universal_fact(self, args): assert args not in self.case_specific_facts, \ "add_universal_fact: fact already present as specific fact" if args not in self.universal_facts: self.universal_facts.append(args) self.add_args(args) def add_case_specific_fact(self, args): if args not in self.universal_facts and \ args not in self.case_specific_facts: self.case_specific_facts.append(args) self.add_args(args) for fc_rule, foreach_index in self.fc_rule_refs: fc_rule.new_fact(args, foreach_index) def add_args(self, args): for (length, indices), (other_indices, arg_map) \ in self.hashes.iteritems(): if length == len(args): selected_args = tuple(arg for i, arg in enumerate(args) if i in indices) arg_map.setdefault(selected_args, []) \ .append(tuple(arg for i, arg in enumerate(args) if i not in indices)) def get_stats(self): return len(self.universal_facts), len(self.case_specific_facts) ./pyke-1.1.1/pyke/ask_tty.py0000644000175000017500000002265411346504626014631 0ustar lambylamby# $Id: ask_tty.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. r''' A "match" here is one of: - an instance of qa_helpers.regexp - msg (for error message) - prompt (without the []) - match(str) returns converted value (None if no match) - an instance of qa_helpers.qmap - test (a match) - value (value to use) - an instance of slice (step must be None) - a tuple of matches (implied "or") - some other python value (which stands for itself) A "review" here is a tuple of (match, review_string) "Alternatives" here is a tuple of (tag, label_string) ''' import sys import itertools from pyke import qa_helpers encoding = 'UTF-8' # The answer has been converted to lowercase before these matches: yes_match = ('y', 'yes', 't', 'true') no_match = ('n', 'no', 'f', 'false') def get_answer(question, match_prompt, conv_fn=None, test=None, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('4\n') >>> get_answer(u'enter number?', '[0-10]', qa_helpers.to_int, ... slice(3,5)) ______________________________________________________________________________ enter number? [0-10] 4 >>> sys.stdin = StringIO('2\n4\n') >>> get_answer(u'enter number?', '\n[0-10]', qa_helpers.to_int, ... slice(3,5)) ______________________________________________________________________________ enter number? [0-10] answer should be between 3 and 5, got 2 Try Again: ______________________________________________________________________________ enter number? [0-10] 4 >>> sys.stdin = StringIO('4\n') >>> get_answer(u'enter number?\n', '[0-10]', qa_helpers.to_int, slice(3,5), ... ((3, u'not enough'), (4, u'hurray!'), (5, u'too much'))) ______________________________________________________________________________ enter number? [0-10] hurray! 4 ''' if not question[-1].isspace() and \ (not match_prompt or not match_prompt[0].isspace()): question += ' ' question += match_prompt if match_prompt and not match_prompt[-1].isspace(): question += ' ' if encoding: question = question.encode(encoding) while True: print "_" * 78 ans = raw_input(question) try: if encoding and sys.version_info[0] < 3: ans = ans.decode(encoding) if conv_fn: ans = conv_fn(ans) if test: ans = qa_helpers.match(ans, test) break except ValueError, e: print "answer should be %s, got %s" % (str(e), repr(ans)) print print "Try Again:" if review: def matches2(ans, test): try: qa_helpers.match(ans, test) return True except ValueError: return False def matches(ans, test): if isinstance(ans, (tuple, list)): return any(itertools.imap(lambda elem: matches2(elem, test), ans)) return matches2(ans, test) for review_test, review_str in review: if matches(ans, review_test): print review_str return ans def ask_yn(question, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('yes\n') >>> ask_yn(u'got it?') ______________________________________________________________________________ got it? (y/n) True >>> sys.stdin = StringIO('N\n') >>> ask_yn(u'got it?') ______________________________________________________________________________ got it? (y/n) False ''' return get_answer(question, u"(y/n)", conv_fn=lambda str: str.lower(), test=(qa_helpers.qmap(yes_match, True), qa_helpers.qmap(no_match, False)), review=review) def ask_integer(question, match=None, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('4\n') >>> ask_integer(u'enter number?') ______________________________________________________________________________ enter number? (int) 4 ''' return get_answer(question, qa_helpers.match_prompt(match, int, u"[%s]", u'(int)'), conv_fn=qa_helpers.to_int, test=match, review=review) def ask_float(question, match=None, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('4\n') >>> ask_float(u'enter number?') ______________________________________________________________________________ enter number? (float) 4.0 ''' return get_answer(question, qa_helpers.match_prompt(match, float, u"[%s]", u'(float)'), conv_fn=qa_helpers.to_float, test=match, review=review) def ask_number(question, match=None, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('4\n') >>> ask_number(u'enter number?') ______________________________________________________________________________ enter number? (number) 4 ''' return get_answer(question, qa_helpers.match_prompt(match, int, u"[%s]", u'(number)'), conv_fn=qa_helpers.to_number, test=match, review=review) def ask_string(question, match=None, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('yes\n') >>> ask_string(u'enter string?') ______________________________________________________________________________ enter string? u'yes' ''' return get_answer(question, qa_helpers.match_prompt(match, str, u"[%s]", u''), test=match, review=review) def ask_select_1(question, alternatives, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('2\n') >>> ask_select_1(u'which one?', ... (('a', u'first one'), ('b', u'second one'), ... ('c', u'third one'))) ______________________________________________________________________________ which one? 1. first one 2. second one 3. third one ? [1-3] 'b' ''' match = slice(1, len(alternatives)) question += u''.join(u'\n%3d. %s' % (i + 1, u'\n '.join(text.split(u'\n'))) for i, (tag, text) in enumerate(alternatives)) i = get_answer(question, qa_helpers.match_prompt(match, int, u"\n? [%s]"), conv_fn=qa_helpers.to_int, test=match, review=review) return alternatives[i-1][0] def ask_select_n(question, alternatives, review=None): r''' >>> from StringIO import StringIO >>> sys.stdin = StringIO('1,3\n') >>> ask_select_n(u'which one?', ... (('a', u'first one'), ('b', u'second one'), ... ('c', u'third one'))) ______________________________________________________________________________ which one? 1. first one 2. second one 3. third one ? [1-3, ...] ('a', 'c') ''' match = slice(1, len(alternatives)) question += u''.join(u'\n%3d. %s' % (i + 1, u'\n '.join(text.split('\n'))) for i, (tag, text) in enumerate(alternatives)) i_tuple = get_answer(question, qa_helpers.match_prompt(match, int, u"\n? [%s, ...]"), conv_fn=lambda str: qa_helpers.to_tuple(str, conv_fn=qa_helpers.to_int, test=match), review=review) return tuple(alternatives[i-1][0] for i in i_tuple) ./pyke-1.1.1/pyke/contexts.py0000644000175000017500000003405211346504626015015 0ustar lambylamby# $Id: contexts.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ A context is used to store all of a rule's pattern variable bindings for a specific rule invocation during inferencing (both forward-chaining and backward-chaining rules). >>> from pyke import pattern >>> c = simple_context() >>> var = variable('foo') >>> var $foo >>> var.name 'foo' >>> c.bind(var.name, c, 123) True >>> c.lookup(var) (123, None) >>> c.lookup_data(var.name) 123 This is a shallow binding scheme which must "unbind" variables when a rule is done. Each time a rule is invoked, a new context object is created to store the bindings for the variables in that rule. When the rule is done, this context is abandoned; so the variables bound there do not need to be individually unbound. But bindings done to variables in other contexts _do_ need to be individually unbound. These bindings happen when a variable in the calling rule, which must be bound in that rule's context, is matched to a non-variable pattern (i.e., pattern_literal or pattern_tuple) in the called rule's context. For example, a caller, rule A, has its own context: >>> A_context = simple_context() and a pattern of $foo within a subgoal in its 'when' clause: >>> A_pattern = variable('foo') In proving this subgoal, rule B is being tried. A new context is created for rule B: >>> B_context = simple_context() Rule B has a literal 123 in its 'proven' clause: >>> B_pattern = pattern.pattern_literal(123) Now B's 'proven' clause is pattern matched to A's subgoal, which results in: >>> B_pattern.match_pattern(B_context, B_context, A_pattern, A_context) True The pattern matches! But the $foo variable belongs to A, so it must be bound in A's context, not B's. >>> A_context.lookup(A_pattern) (123, None) >>> B_context.lookup(A_pattern) Traceback (most recent call last): ... KeyError: '$foo not bound' This is done by using the current rule's context as a controlling context for all the bindings that occur within that rule. If the binding is for a variable in another context, the controlling context remembers the binding in its undo_list. When the rule is finished, it executes a 'done' method on its context which undoes all of the bindings in its undo_list. >>> B_context.done() >>> A_context.lookup(A_pattern) Traceback (most recent call last): ... KeyError: '$foo not bound' Thus, in binding a variable to a value, there are three contexts involved: controlling_context.bind(variable, variable_context, value [, value_context]) The value_context must be omitted if value is any python data, and must be included if value is a pattern (including another variable). Variables are bound to as just strings so that they can be accessed easily from python code without needing the variable objects. >>> A_context.bind('foo', A_context, 123) True >>> A_context.lookup_data('foo') 123 But to differentiate variables from string literals in the patterns, variables are special objects. When a variable is bound as data (to another variable), it is bound as a variable object (again, to differentiate it from plain python strings). >>> a_var = variable('a_var') >>> a_var $a_var >>> b_var = variable('b_var') >>> b_var $b_var >>> B_context.bind(b_var.name, B_context, a_var, A_context) True >>> B_context.lookup(b_var) Traceback (most recent call last): ... KeyError: '$a_var not bound' >>> ans = B_context.lookup(b_var, True) >>> ans # doctest: +ELLIPSIS ($a_var, ) >>> ans[1] is A_context True But to differentiate variables from string literals in the patterns, variables are special objects. When a variable is bound as data (to another variable), it is bound as a variable object (again, to differentiate it from plain python strings). >>> type(ans[0]) The anonymous variables have names starting with '_'. Binding requests on anonymous variables are silently ignored. >>> anonymous('_ignored') $_ignored >>> A_context.bind('_bogus', A_context, 567) False >>> A_context.lookup_data('_bogus') Traceback (most recent call last): ... KeyError: '$_bogus not bound' >>> A_context.lookup_data('_bogus', True) '$_bogus' """ import sys from pyke import pattern, unique _Not_found = unique.unique('Not_found') # Set to a sequence (or frozenset) of variable names to trace their bindings: debug = () class simple_context(object): def __init__(self): self.bindings = {} self.undo_list = [] self.save_all_undo_count = 0 def dump(self): for var_name in sorted(self.bindings.iterkeys()): print "%s: %s" % (var_name, repr(self.lookup_data(var_name, True))) def bind(self, var_name, var_context, val, val_context = None): """ val_context must be None iff val is not a pattern. Returns True if a new binding was created. """ assert not isinstance(val, pattern.pattern) \ if val_context is None \ else isinstance(val, pattern.pattern) if var_name[0] == '_': return False if var_context is self: assert var_name not in self.bindings if val_context is not None: val, val_context = val_context.lookup(val, True) if val_context == var_context and isinstance(val, variable) and \ val.name == var_name: # binding $x to $x; no binding necessary! return False if var_name in debug: if val_context: sys.stderr.write("binding %s in %s to %s in %s\n" % (var_name, var_context, val, val_context)) else: sys.stderr.write("binding %s in %s to %s\n" % (var_name, var_context, val)) self.bindings[var_name] = (val, val_context) if self.save_all_undo_count: self.undo_list.append((var_name, self)) return True ans = var_context.bind(var_name, var_context, val, val_context) if ans: self.undo_list.append((var_name, var_context)) return ans def is_bound(self, var): val, where = var, self while where is not None and isinstance(val, variable): ans = where.bindings.get(val.name) if ans is None: return False val, where = ans # where is None or not isinstance(val, variable) return where is None or val.is_data(where) def lookup_data(self, var_name, allow_vars = False, final = None): """ Converts the answer into data only (without any patterns in it). If there are unbound variables anywhere in the data, a KeyError is generated. """ if final is not None: val = final.get((var_name, self), _Not_found) if val is not _Not_found: return val binding = self.bindings.get(var_name) if binding is None: if allow_vars: return "$" + var_name raise KeyError("$%s not bound" % var_name) val, context = binding if context is not None: val = val.as_data(context, allow_vars, final) if isinstance(val, bc_context): val = val.create_plan(final) if final is not None: final[var_name, self] = val return val def lookup(self, var, allow_variable_in_ans = False): """ Returns value, val_context. Returns (var, self) if not bound and allow_variable_in_ans, else raises KeyError. """ val, where = var, self while where is not None and isinstance(val, variable): ans = where.bindings.get(val.name) if ans is None: break val, where = ans else: # where is None or not isinstance(val, variable) return val, where # where is not None and isinstance(val, variable) if allow_variable_in_ans: return val, where raise KeyError("%s not bound" % str(val)) def mark(self, save_all_undo = False): if save_all_undo: self.save_all_undo_count += 1 return len(self.undo_list) def end_save_all_undo(self): assert self.save_all_undo_count > 0 self.save_all_undo_count -= 1 def undo_to_mark(self, mark, *var_names_to_undo): for var_name, var_context in self.undo_list[mark:]: var_context._unbind(var_name) del self.undo_list[mark:] for var_name in var_names_to_undo: self._unbind(var_name) def done(self): """ Unbinds all variables bound through 'self's 'bind' method. The assumption here is that 'self' is being abandoned, so we don't need to worry about self.bindings. """ for var_name, var_context in self.undo_list: var_context._unbind(var_name) def _unbind(self, var_name): del self.bindings[var_name] class bc_context(simple_context): def __init__(self, rule): super(bc_context, self).__init__() self.rule = rule def name(self): return self.rule.name def __repr__(self): return "" % (self.name(), id(self)) def create_plan(self, final = None): if final is None: final = {} return self.rule.make_plan(self, final) class variable(pattern.pattern): """ The code to force variables of the same name to be the same object is probably not needed anymore... """ Variables = {} def __new__(cls, name): var = cls.Variables.get(name) if var is None: var = super(variable, cls).__new__(cls) cls.Variables[name] = var return var def __init__(self, name): self.name = name def __repr__(self): return '$' + self.name def lookup(self, my_context, allow_variable_in_ans = False): return my_context.lookup(self, allow_variable_in_ans) def match_data(self, bindings, my_context, data): if self.name in debug: sys.stderr.write("%s.match_data(%s, %s, %s)\n" % (self, bindings, my_context, data)) var, var_context = my_context.lookup(self, True) if isinstance(var, variable): bindings.bind(var.name, var_context, data) return True if self.name in debug: sys.stderr.write("%s.match_data: lookup got %s in %s\n" % (self, var, var_context)) if var_context is None: return var == data return var.match_data(bindings, var_context, data) def simple_match_pattern(self, bindings, my_context, pattern_b, b_context): var, var_context = my_context.lookup(self, True) if isinstance(var, variable): bindings.bind(var.name, var_context, pattern_b, b_context) return True if var_context is None: return pattern_b.match_data(bindings, b_context, var) return var.simple_match_pattern(bindings, var_context, pattern_b, b_context) def match_pattern(self, bindings, my_context, pattern_b, b_context): var, var_context = my_context.lookup(self, True) if isinstance(var, variable): bindings.bind(var.name, var_context, pattern_b, b_context) return True if var_context is None: return pattern_b.match_data(bindings, b_context, var) return var.match_pattern(bindings, var_context, pattern_b, b_context) def as_data(self, my_context, allow_vars = False, final = None): return my_context.lookup_data(self.name, allow_vars, final) def is_data(self, my_context): return my_context.is_bound(self) class anonymous(variable): def __init__(self, name): assert name[0] == '_', \ "anonymous variables must start with '_', not %s" % name super(anonymous, self).__init__(name) def lookup(self, my_context, allow_variable_in_ans = False): if allow_variable_in_ans: return self, my_context raise KeyError("$%s not bound" % self.name) def match_data(self, bindings, my_context, data): return True def match_pattern(self, bindings, my_context, pattern_b, b_context): return True def as_data(self, my_context, allow_vars = False, final = None): if allow_vars: return "$%s" % self.name raise KeyError("$%s not bound" % self.name) def is_data(self, my_context): return False ./pyke-1.1.1/pyke/unique.py0000644000175000017500000000242011346504630014441 0ustar lambylamby# $Id: unique.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. class unique(object): def __init__(self, name): self.name = name def __repr__(self): return "" % self.name ./pyke-1.1.1/pyke/test.py0000644000175000017500000002052211346504630014115 0ustar lambylamby# $Id: test.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import with_statement import types from pyke import knowledge_engine, krb_traceback, pattern, contexts def parse(str): str = str.strip() if str[0] == '(': return parse_tuple(str[1:]) if str[0] in "0123456789.-+": return parse_number(str) if str[0] in "\"'": return parse_string(str) if str[0].isalpha() or str[0] in "_$*": return parse_identifier(str) else: return parse_symbol(str) def parse_number(str): ''' >>> parse_number('123abc') (123, 'abc') >>> parse_number('123e17abc') (1.23e+19, 'abc') >>> parse_number('-123abc') (-123, 'abc') >>> parse_number('-1.23e-7abc') (-1.23e-07, 'abc') ''' for i in range(1, len(str)): if str[i] not in "0123456789.-+e": break return eval(str[:i]), str[i:] def parse_string(str): r''' >>> parse_string("'hello' mom") ('hello', ' mom') >>> parse_string(r'"hello\" mom"') ('hello" mom', '') ''' quote = str[0] end = str.index(quote, 1) while str[end - 1] == '\\': end = str.index(quote, end + 1) return eval(str[:end + 1]), str[end + 1:] def parse_identifier(str): ''' >>> parse_identifier("abc, def") ('abc', ', def') >>> parse_identifier("$abc, def") ('$abc', ', def') >>> parse_identifier("*$abc, def") ('*$abc', ', def') >>> parse_identifier("*") ('*', '') >>> parse_identifier("True, 0") (True, ', 0') >>> parse_identifier("False, 0") (False, ', 0') >>> parse_identifier("None, 0") (None, ', 0') ''' if len(str) == 1: return str, '' start = 2 if str.startswith('*$') else 1 for i in range(start, len(str)): if not str[i].isalnum() and str[i] != '_': break if str[0] == '*' and (i < 3 or str[1] != '$'): return parse_symbol(str) if str[0] == '$' and i < 2: return parse_symbol(str) if str[:i] == 'None': return None, str[i:] if str[:i] == 'True': return True, str[i:] if str[:i] == 'False': return False, str[i:] return str[:i], str[i:] def parse_symbol(str): ''' >>> parse_symbol(", def") (',', ' def') >>> parse_symbol("$+, def") ('$+', ', def') >>> parse_symbol("+)") ('+', ')') >>> parse_symbol("+]") ('+', ']') ''' if len(str) == 1: return str, '' for i in range(1, len(str)): if str[i].isspace() or str[i].isalnum() or str[i] in "\"'()[]{},;_`": break return str[:i], str[i:] def parse_tuple(str): ''' >>> parse_tuple("))") ((), ')') >>> parse_tuple("a, b), c)") (('a', 'b'), ', c)') >>> parse_tuple("a, (b), c)") (('a', ('b',), 'c'), '') ''' ans = [] str = str.lstrip() while str[0] != ')': element, str = parse(str) ans.append(element) str = str.lstrip() if str[0] == ',': str = str[1:].lstrip() return tuple(ans), str[1:] def is_pattern(data): ''' >>> is_pattern('abc') False >>> is_pattern(123) False >>> is_pattern(()) False >>> is_pattern((1,2,3)) False >>> is_pattern((1,2,'*$_')) True ''' if isinstance(data, tuple): if data and is_rest_var(data[-1]): return True return any(is_pattern(element) for element in data) if isinstance(data, types.StringTypes) and len(data) > 1 and \ data[0] == '$' and (data[1].isalpha() or data[1] == '_'): return True return False def is_rest_var(data): r''' >>> is_rest_var('foo') False >>> is_rest_var('$foo') False >>> is_rest_var('*foo') False >>> is_rest_var('$*foo') False >>> is_rest_var('*$foo') True >>> is_rest_var('*$') False >>> is_rest_var('*') False >>> is_rest_var('') False ''' return isinstance(data, types.StringTypes) and len(data) > 2 and \ data.startswith('*$') and (data[2].isalpha() or data[2] == '_') def as_pattern(data): if isinstance(data, tuple) and is_pattern(data): if is_rest_var(data[-1]): name = data[-1][2:] if name[0] == '_': rest_var = contexts.anonymous(name) else: rest_var = contexts.variable(name) return pattern.pattern_tuple(tuple(as_pattern(element) for element in data[:-1]), rest_var) return pattern.pattern_tuple(tuple(as_pattern(element) for element in data)) if isinstance(data, types.StringTypes) and is_pattern(data): name = data[1:] if name[0] == '_': return contexts.anonymous(name) return contexts.variable(name) return pattern.pattern_literal(data) Did_init = False def init(*args, **kws): global Engine, Did_init Engine = knowledge_engine.engine(*args, **kws) Did_init = True def eval_plan(globals, locals): while True: print expr = raw_input("run plan: ").strip() if not expr: break ans = eval(expr, globals.copy(), locals.copy()) print "plan returned:", ans def run(rule_bases_to_activate, default_rb = None, init_fn = None, fn_to_run_plan = eval_plan, plan_globals = {}): if not Did_init: init() if not isinstance(rule_bases_to_activate, (tuple, list)): rule_bases_to_activate = (rule_bases_to_activate,) if default_rb is None: default_rb = rule_bases_to_activate[0] while True: print goal_str = raw_input("goal: ") if not goal_str: break goal, args_str = parse(goal_str) if goal == "trace": args = args_str.split() if len(args) == 1: Engine.trace(default_rb, args[0]) else: Engine.trace(*args) continue if goal == "untrace": args = args_str.split() if len(args) == 1: Engine.untrace(default_rb, args[0]) else: Engine.untrace(*args) continue args_str = args_str.strip() rb_name = default_rb if args_str[0] == '.': rb_name = goal goal, args_str = parse(args_str[1:]) args = parse(args_str)[0] print "proving: %s.%s%s" % (rb_name, goal, args) goal_args = tuple(as_pattern(arg) for arg in args) Engine.reset() if init_fn: init_fn(Engine) context = contexts.simple_context() try: Engine.activate(*rule_bases_to_activate) with Engine.prove(rb_name, goal, context, goal_args) as it: for prototype_plan in it: final = {} print "got: %s%s" % \ (goal, tuple(arg.as_data(context, True, final) for arg in goal_args)) if not prototype_plan: print "no plan returned" else: plan = prototype_plan.create_plan(final) fn_to_run_plan(plan_globals, locals()) except: krb_traceback.print_exc(100) ./pyke-1.1.1/pyke/knowledge_base.py0000644000175000017500000001215611346504630016113 0ustar lambylamby# $Id: knowledge_base.py 081917d30609 2010-03-05 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. class gen_tuple(object): def __init__(self, tup): self.tup = tup def __enter__(self): return self.tup def __exit__(self, type, value, tb): pass Gen_empty = gen_tuple(()) Gen_once = gen_tuple((None,)) class knowledge_base(object): ''' This object is a master repository for knowledge entities of different names. These knowledge entities could be facts or rules. The cumulative information maintained in a knowledge_base represents all knowledge within a specific domain. In the syntax: "name1.name2(arg_pattern...)", the knowledge_base name is "name1". ''' def __init__(self, engine, name, entity_list_type = None, register = True): self.name = name self.entity_lists = {} # {name: entity_list} self.entity_list_type = entity_list_type self.initialized = False # used by self.init2 if register: self.register(engine) else: self.engine = engine def register(self, engine): r''' Called at most once either from __init__ or after loading from a pickle. ''' self.engine = engine name = self.name if name in engine.knowledge_bases: raise AssertionError("knowledge_base %s already exists" % name) if name in engine.rule_bases: raise AssertionError("name clash between %s '%s' and " "rule_base '%s'" % (self.__class__.__name__, name, name)) engine.knowledge_bases[name] = self def __getstate__(self): r''' User must call 'register' on the new instance after loading it from the pickle. We do this so that we don't end up pickling the whole engine! ''' ans = vars(self).copy() del ans['engine'] return ans def init2(self): ''' overridden by subclasses. ''' pass def reset(self): for entity in self.entity_lists.itervalues(): entity.reset() def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.name) def get_entity_list(self, entity_name): ans = self.entity_lists.get(entity_name) if ans is None: if self.entity_list_type: ans = self.entity_lists[entity_name] \ = self.entity_list_type(entity_name) else: raise KeyError("%s not found in knowledge_base %s" % (entity_name, self.name)) return ans def lookup(self, bindings, pat_context, entity_name, patterns): entity = self.entity_lists.get(entity_name) if entity is None: return Gen_empty return entity.lookup(bindings, pat_context, patterns) def prove(self, bindings, pat_context, entity_name, patterns): entity = self.entity_lists.get(entity_name) if entity is None: return Gen_empty return entity.prove(bindings, pat_context, patterns) def add_fc_rule_ref(self, entity_name, fc_rule, foreach_index): self.get_entity_list(entity_name) \ .add_fc_rule_ref(fc_rule, foreach_index) class knowledge_entity_list(object): ''' This object keeps track of all of the knowledge entities sharing the same name. For example, these knowledge entities could be all the facts of the same name or all of the rules of the same name. Generally, all of the entities in this list may come to bear on looking up or proving a single fact or goal. In the syntax: "name1.name2(arg_pattern...)", the knowledge entity name is "name2". ''' def __init__(self, name): self.name = name def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.name) def reset(self): pass def prove(self, bindings, pat_context, patterns): return self.lookup(bindings, pat_context, patterns) def add_fc_rule_ref(self, fc_rule, foreach_index): pass ./pyke-1.1.1/pyke/target_pkg.py0000644000175000017500000005220411361137402015263 0ustar lambylamby# $Id: target_pkg.py b034e6155de4 2010-04-13 mtnyogi $ # coding=utf-8 # # Copyright © 2009 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Each target_pkg object keeps track of all of the compiled files within one compiled_krb package. """ from __future__ import with_statement import os, os.path import time import sys import re import pyke debug = False Name_test = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*$') class target_pkg(object): r'''This manages all of the target files in a compiled_krb directory. There is one instance per compiled_krb directory. It keeps track of everything in that directory and manages recompiling the sources when the compiled targets are missing or out of date. This instance is stored permanently in the "targets" variable of the compiled_pyke_files.py module in the compiled_krb directory. This maintains the following information for each compiled target file: source_package, source_filepath, compile_time, target_filename. ''' def __init__(self, module_name, filename = None, pyke_version = pyke.version, loader = None, sources = None, compiler_version = 0): r''' The parameters are: module_name: the complete dotted name of the compiled_pyke_files module for this object. filename: the absolute path to the compiled_pyke_files.py/c/o file. pyke_version: the version of pyke used to compile the target files. loader: the __loader__ attribute of the compiled_pyke_files module (only set if the compiled_krb directory has been zipped, otherwise None). sources: {(source_package_name, path_from_package, source_filepath): [compile_time, target_file...]} compiler_version: the version of the pyke compiler used to compile all of the targets in this compiled_krb directory. This class is instantiated in two different circumstances: 1. From compiled_krb/compiled_pyke_files.py with a list of all of the compiled files in that compiled_krb directory. In this case, all of the parameters are passed to __init__. 2. From knowledge_engine.engine.__init__ (actually _create_target_pkg). In this case, only the first parameter is passed to __init__. Either way, after importing compiled_pyke_files or creating a new instance directly, reset is called by knowledge_engine.engine._create_target_pkg. ''' # compiled_krb package name self.package_name = module_name.rsplit('.', 1)[0] if sources is None: # compiled_pyke_files.py does not exist. # Creating a new target_pkg object from scratch. try: # See if the self.package_name (e.g., compiled_krb) exists. target_package_dir = \ os.path.dirname(import_(self.package_name).__file__) except ImportError: if debug: print >> sys.stderr, "target_pkg: no target package", \ self.package_name # Create the target_package. last_dot = self.package_name.rfind('.') if last_dot < 0: assert filename is not None package_parent_dir = \ os.path.dirname(os.path.dirname(filename)) else: package_parent_dir = \ os.path.dirname( # This import better work! import_(self.package_name[:last_dot]).__file__) if filename is not None: assert os.path.normpath( os.path.abspath(package_parent_dir)) == \ os.path.normpath( os.path.dirname(os.path.dirname(filename))), \ "Internal error: %r != %r" % ( os.path.normpath( os.path.abspath(package_parent_dir)), os.path.normpath( os.path.dirname(os.path.dirname(filename)))) if debug: print >> sys.stderr, "target_pkg package_parent_dir:", \ package_parent_dir target_package_dir = \ os.path.join(package_parent_dir, self.package_name[last_dot + 1:]) if debug: print >> sys.stderr, "target_pkg target_package_dir:", \ target_package_dir if not os.path.lexists(target_package_dir): if debug: print >> sys.stderr, "target_pkg: mkdir", \ target_package_dir os.mkdir(target_package_dir) # Does __init__.py file exist? init_filepath = \ os.path.join(target_package_dir, '__init__.py') if debug: print >> sys.stderr, "target_pkg init_filepath:", \ init_filepath if not os.path.lexists(init_filepath): # Create empty __init__.py file. if debug: print >> sys.stderr, "target_pkg: creating", \ init_filepath open(init_filepath, 'w').close() filename = os.path.join(target_package_dir, 'compiled_pyke_files.py') if filename.endswith('.py'): self.filename = filename else: self.filename = filename[:-1] self.directory = os.path.dirname(self.filename) if debug: print >> sys.stderr, "target_pkg:", self.package_name, self.filename self.loader = loader if compiler_version == pyke.compiler_version: # {(source_package_name, source_filepath): # [compile_time, target_filename, ...]} self.sources = sources if sources is not None else {} elif self.loader is None: # Force recompile of everything self.sources = {} else: # Loading incorrect pyke.compiler_version from zip file. # Can't recompile to zip file... raise AssertionError("%s: wrong version of pyke, " "running %s, compiled for %s" % (module_name, pyke.version, pyke_version)) def reset(self, check_sources = True): ''' This should be called once by engine.__init__ prior to calling add_source_package. ''' if debug: print >> sys.stderr, "target_pkg.reset" self.dirty = False self.check_sources = check_sources # {(source_package_name, path_from_package): source_package_dir} self.source_packages = {} self.compiled_targets = set() # set of target_filename self.rb_names = set() def add_source_package(self, source_package_name, path_from_package, source_package_dir): if debug: print >> sys.stderr, "target_pkg.add_source_package " \ "source_package_name:", \ repr(source_package_name) print >> sys.stderr, " path_from_package:", \ repr(path_from_package) print >> sys.stderr, " source_package_dir:", \ repr(source_package_dir) if not self.loader: assert (source_package_name, path_from_package) not in \ self.source_packages, \ "duplicate source package: %s" % path_from_package source_dir = os.path.normpath(os.path.join(source_package_dir, path_from_package)) self.source_packages[source_package_name, path_from_package] = \ source_dir sources = set([]) for dirpath, dirnames, filenames \ in os.walk(source_dir, onerror=_raise_exc): for filename in filenames: if filename.endswith(('.krb', '.kfb', '.kqb')): source_abspath = os.path.join(dirpath, filename) assert dirpath.startswith(source_dir) source_relpath = \ os.path.join(dirpath[len(source_dir)+1:], filename) self.add_source(source_package_name, path_from_package, source_relpath, os.path.getmtime(source_abspath)) sources.add(source_relpath) # Delete old source file info for files that are no longer present for deleted_filepath \ in [src_filepath for src_pkg_name, src_path_from_pkg, src_filepath in self.sources.iterkeys() if src_pkg_name == source_package_name and src_path_from_pkg == path_from_package and src_filepath not in sources]: if debug: print >> sys.stderr, "del:", source_package_name, filepath del self.sources[source_package_name, path_from_package, deleted_filepath] def add_source(self, source_package_name, path_from_package, source_filepath, source_mtime): if debug: print >> sys.stderr, "target_pkg.add_source:", \ source_package_name, path_from_package, \ source_filepath rb_name = os.path.splitext(os.path.basename(source_filepath))[0] if debug: print >> sys.stderr, "rb_name:", rb_name if not Name_test.match(rb_name): raise ValueError("%s: %s illegal as python identifier" % (source_filepath, rb_name)) if rb_name in self.rb_names: raise ValueError("%s: duplicate knowledge base name" % rb_name) self.rb_names.add(rb_name) key = source_package_name, path_from_package, source_filepath if debug: print >> sys.stderr, "key:", key if self.sources.get(key, (0,))[0] < source_mtime: if debug: print >> sys.stderr, source_filepath, "needs to be compiled" self.sources[key] = [] self.dirty = True def do_by_ext(self, prefix, filename, *args): ext = os.path.splitext(filename)[1][1:] return getattr(self, "%s_%s" % (prefix, ext))(filename, *args) def compile(self, engine): if debug: print >> sys.stderr, "%s.compile:" % self.package_name global krb_compiler if self.check_sources and not self.loader: initialized = False for (source_package_name, path_from_package, source_filename), \ value \ in self.sources.iteritems(): if not value and \ (source_package_name, path_from_package) in \ self.source_packages: if not initialized: try: krb_compiler except NameError: from pyke import krb_compiler initialized = True target_files = \ self.do_by_ext('compile', os.path.join( self.source_packages[source_package_name, path_from_package], source_filename)) if debug: print >> sys.stderr, "target_files:", target_files value.append(time.time()) value.extend(target_files) self.compiled_targets.update(target_files) def compile_krb(self, source_filename): if debug: print >> sys.stderr, "compile_krb:", source_filename rb_name = os.path.basename(source_filename)[:-4] return krb_compiler.compile_krb(rb_name, self.package_name, self.directory, source_filename) def compile_kfb(self, source_filename): if debug: print >> sys.stderr, "compile_kfb:", source_filename try: fbc_name = os.path.basename(source_filename)[:-4] + '.fbc' fbc_path = os.path.join(self.directory, fbc_name) self.pickle_it(krb_compiler.compile_kfb(source_filename), fbc_path) return (fbc_name,) except: if os.path.lexists(fbc_path): os.remove(fbc_path) raise def compile_kqb(self, source_filename): if debug: print >> sys.stderr, "compile_kqb:", source_filename try: qbc_name = os.path.basename(source_filename)[:-4] + '.qbc' qbc_path = os.path.join(self.directory, qbc_name) self.pickle_it(krb_compiler.compile_kqb(source_filename), qbc_path) return (qbc_name,) except: if os.path.lexists(qbc_path): os.remove(qbc_path) raise def write(self): if debug: print >> sys.stderr, "target_pkg.write" if self.dirty: sys.stderr.write('writing [%s]/%s\n' % (self.package_name, os.path.basename(self.filename))) with open(self.filename, 'w') as f: f.write("# compiled_pyke_files.py\n\n") f.write("from pyke import target_pkg\n\n") f.write("pyke_version = %r\n" % pyke.version) f.write("compiler_version = %r\n" % pyke.compiler_version) f.write("target_pkg_version = %r\n\n" % pyke.target_pkg_version) f.write("try:\n") f.write(" loader = __loader__\n") f.write("except NameError:\n") f.write(" loader = None\n\n") f.write("def get_target_pkg():\n") f.write(" return target_pkg.target_pkg(__name__, __file__, " "pyke_version, loader, {\n") for key, value in self.sources.iteritems(): if debug: print >> sys.stderr, "write got:", key, value if (key[0], key[1]) in self.source_packages: if debug: print >> sys.stderr, "writing:", key, value f.write(" %r:\n" % (key,)) f.write(" %r,\n" % (value,)) f.write(" },\n") f.write(" compiler_version)\n\n") if os.path.exists(self.filename + 'c'): os.remove(self.filename + 'c') if os.path.exists(self.filename + 'o'): os.remove(self.filename + 'o') def load(self, engine, load_fc = True, load_bc = True, load_fb = True, load_qb = True): load_flags = {'load_fc': load_fc, 'load_bc': load_bc, 'load_fb': load_fb, 'load_qb': load_qb} if debug: print >> sys.stderr, "target_pkg.load:", load_flags for (source_package_name, path_from_package, source_filename), value \ in self.sources.iteritems(): if not self.check_sources or self.loader or \ (source_package_name, path_from_package) in self.source_packages: for target_filename in value[1:]: if debug: print >> sys.stderr, "load:", target_filename self.do_by_ext('load', target_filename, engine, load_flags) def load_py(self, target_filename, engine, flags): if debug: print >> sys.stderr, "load_py:", target_filename target_module = target_filename[:-3] # strip '.py' extension. module_path = self.package_name + '.' + target_module if target_module.endswith('_fc'): if flags['load_fc']: self.load_module(module_path, target_filename, engine) elif target_module.endswith('_bc'): if flags['load_bc']: self.load_module(module_path, target_filename, engine) elif target_module.endswith('_plans'): if flags['load_bc']: self.load_module(module_path, target_filename, engine, False) else: raise AssertionError("target_pkg.load_py: " "unknown target file type: %s" % target_filename) def load_fbc(self, target_filename, engine, flags): if debug: print >> sys.stderr, "load_fbc:", target_filename if flags['load_fb']: self.load_pickle(target_filename, engine) def load_qbc(self, target_filename, engine, flags): if debug: print >> sys.stderr, "load_qbc:", target_filename if flags['load_qb']: self.load_pickle(target_filename, engine) def load_module(self, module_path, filename, engine, do_import = True): if debug: print >> sys.stderr, "load_module:", module_path, filename module = None if module_path in sys.modules: if debug: print >> sys.stderr, "load_module: already imported" module = sys.modules[module_path] if filename in self.compiled_targets: if debug: print >> sys.stderr, "load_module: reloading" module = reload(module) elif do_import: if debug: print >> sys.stderr, "load_module: importing" module = import_(module_path) if module is not None and \ getattr(module, 'compiler_version', 0) != pyke.compiler_version: raise AssertionError("%s: incorrect pyke version: running " "%s, expected %s" % (filename, pyke.version, module.pyke_version)) if do_import: module.populate(engine) def load_pickle(self, filename, engine): global pickle if debug: print >> sys.stderr, "load_pickle:", filename try: pickle except NameError: import cPickle as pickle full_path = os.path.join(self.directory, filename) if self.loader: import contextlib import StringIO ctx_lib = \ contextlib.closing( StringIO.StringIO(self.loader.get_data(full_path))) else: ctx_lib = open(full_path, 'rb') with ctx_lib as f: versions = pickle.load(f) if isinstance(versions, tuple): pyke_version, compiler_version = versions else: pyke_version, compiler_version = versions, 0 if compiler_version != pyke.compiler_version: raise AssertionError("%s: incorrect pyke version: running " "%s, expected %s" % (filename, pyke.version, pyke_version)) pickle.load(f).register(engine) def pickle_it(self, obj, path): global pickle try: pickle except NameError: import cPickle as pickle import copy_reg copy_reg.pickle(slice, lambda s: (slice, (s.start, s.stop, s.step))) sys.stderr.write("writing [%s]/%s\n" % (self.package_name, os.path.basename(path))) with open(path, 'wb') as f: pickle.dump((pyke.version, pyke.compiler_version), f) pickle.dump(obj, f) def _raise_exc(exc): raise exc def import_(modulename): ''' modulepath does not include .py ''' if debug: print >> sys.stderr, "import_:", modulename mod = __import__(modulename) for comp in modulename.split('.')[1:]: mod = getattr(mod, comp) return mod ./pyke-1.1.1/pyke/knowledge_engine.py0000644000175000017500000005353611354270146016456 0ustar lambylamby# $Id: knowledge_engine.py a8fb0b81fd85 2010-03-29 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import with_statement import sys import types import os, os.path import re import contextlib if sys.version_info[0] < 3: import itertools class chain(object): old_chain = itertools.chain def __new__(cls, *args): return cls.old_chain(*args) @staticmethod def from_iterable(i): for iterable in i: for x in iterable: yield x itertools.chain = chain import pyke from pyke import contexts debug = False Sys_path = tuple(os.getcwd() if p == '' else os.path.normpath(os.path.abspath(p)) for p in sys.path) class CanNotProve(StandardError): pass class engine(object): _Variables = tuple(contexts.variable('ans_%d' % i) for i in range(100)) def __init__(self, *search_paths, **kws): r'''All search_paths are relative to reference_path. Each search_path may be: path -- a path relative to reference_path to search for source files, placing the compiled knowledge bases in '.compiled_krb'. module -- the module's __file__ is taken as the path. (None|path|module, target_package) -- use target_package rather than '.compiled_krb'. This is a package name in Python dotted name notation relative to path. Use None to use the compiled knowledge bases in the target_package without scanning for source files. kws can be: load_fc, load_bc, load_fb and load_qb. They are all boolean valued and default to True. ''' # import this stuff here to avoid import cycles... global condensedPrint, pattern, fact_base, goal, rule_base, special, \ target_pkg from pyke import (condensedPrint, pattern, fact_base, goal, rule_base, special, target_pkg) for keyword in kws.iterkeys(): if keyword not in ('load_fc', 'load_bc', 'load_fb', 'load_qb'): raise TypeError("engine.__init__() got an unexpected keyword " "argument %r" % keyword) self.knowledge_bases = {} self.rule_bases = {} special.create_for(self) if len(search_paths) == 1 and isinstance(search_paths[0], tuple) and \ search_paths[0][0] == '*direct*' and \ isinstance(search_paths[0][1], types.ModuleType): # secret hook for the compiler to initialize itself (so the # compiled python module can be in an egg). search_paths[0][1].populate(self) else: target_pkgs = {} # {target_package_name: target_pkg} for path in search_paths: self._create_target_pkg(path, target_pkgs) for target_package in target_pkgs.itervalues(): if debug: print >>sys.stderr, "target_package:", target_package target_package.compile(self) target_package.write() target_package.load(self, **kws) for kb in self.knowledge_bases.itervalues(): kb.init2() for rb in self.rule_bases.itervalues(): rb.init2() def _create_target_pkg(self, path, target_pkgs): # Does target_pkg.add_source_package. if debug: print >> sys.stderr, "engine._create_target_pkg:", path # First, figure out source_package_name, source_package_dir # and target_package_name: target_package_name = '.compiled_krb' # default if isinstance(path, (tuple, list)): path, target_package_name = path if isinstance(path, types.ModuleType): path = path.__file__ if not isinstance(path, (types.StringTypes, types.NoneType)): raise ValueError("illegal path argument: string expected, got " + \ str(type(path))) if debug: print >> sys.stderr, "_create_target_pkg path:", \ repr(path) print >> sys.stderr, "_create_target_pkg target_package_name:", \ repr(target_package_name) # Handle the case where there are no source files (for a distributed # app that wants to hide its knowledge bases): if path is None: assert target_package_name[0] != '.', \ "engine: relative target, %s, illegal " \ "with no source package" % \ target_package_name if target_package_name not in target_pkgs: # This import must succeed! tp = _get_target_pkg(target_package_name + '.compiled_pyke_files') if tp is None: raise AssertionError("%s: compiled with different version " "of Pyke" % target_package_name) tp.reset(check_sources=False) target_pkgs[target_package_name] = tp return path = os.path.normpath(os.path.abspath(path)) path_to_package, source_package_name, remainder_path, zip_file_flag = \ _pythonify_path(path) if debug: print >> sys.stderr, "_create_target_pkg path to " \ "_pythonify_path:", \ repr(path) print >> sys.stderr, " path_to_package:", repr(path_to_package) print >> sys.stderr, " source_package_name:", \ repr(source_package_name) print >> sys.stderr, " remainder_path:", repr(remainder_path) print >> sys.stderr, " zip_file_flag:", zip_file_flag target_filename = None # Convert relative target_package_name (if specified) to absolute form: if target_package_name[0] == '.': num_dots = \ len(target_package_name) - len(target_package_name.lstrip('.')) if debug: print >> sys.stderr, "_create_target_pkg num_dots:", num_dots if num_dots == 1: base_package = source_package_name else: base_package = \ '.'.join(source_package_name.split('.')[:-(num_dots - 1)]) if base_package: target_package_name = \ base_package + '.' + target_package_name[num_dots:] else: target_package_name = target_package_name[num_dots:] target_filename = \ os.path.join(path_to_package, os.path.join(*target_package_name.split('.')), 'compiled_pyke_files.py') if debug: print >> sys.stderr, "_create_target_pkg " \ "absolute target_package_name:", \ target_package_name if target_package_name in target_pkgs: tp = target_pkgs[target_package_name] else: target_name = target_package_name + '.compiled_pyke_files' if debug: print >> sys.stderr, "_create_target_pkg target_name:", \ target_name tp = None try: # See if compiled_pyke_files already exists. tp = _get_target_pkg(target_name) except ImportError: pass if tp is None: if debug: print >> sys.stderr, "_create_target_pkg: no target module" tp = target_pkg.target_pkg(target_name, target_filename) tp.reset() target_pkgs[target_package_name] = tp source_package_dir = \ os.path.join(path_to_package, os.path.join(*source_package_name.split('.'))) if not os.path.isdir(source_package_dir): source_package_dir = os.path.dirname(source_package_dir) remainder_path = os.path.dirname(remainder_path) tp.add_source_package(source_package_name, remainder_path, source_package_dir) def get_ask_module(self): if not hasattr(self, 'ask_module'): from pyke import ask_tty self.ask_module = ask_tty return self.ask_module def reset(self): r'''Erases all case-specific facts and deactivates all rule bases. ''' for rb in self.rule_bases.itervalues(): rb.reset() for kb in self.knowledge_bases.itervalues(): kb.reset() def get_kb(self, kb_name, _new_class = None): ans = self.knowledge_bases.get(kb_name) if ans is None: if _new_class: ans = _new_class(self, kb_name) else: raise KeyError("knowledge_base %s not found" % kb_name) return ans def get_rb(self, rb_name): ans = self.rule_bases.get(rb_name) if ans is None: raise KeyError("rule_base %s not found" % rb_name) return ans def get_create(self, rb_name, parent = None, exclude_list = ()): ans = self.rule_bases.get(rb_name) if ans is None: ans = rule_base.rule_base(self, rb_name, parent, exclude_list) elif ans.parent != parent or ans.exclude_set != frozenset(exclude_list): raise AssertionError("duplicate rule_base: %s" % rb_name) return ans def get_ke(self, kb_name, entity_name): return self.get_kb(kb_name).get_entity_list(entity_name) def add_universal_fact(self, kb_name, fact_name, args): r'''Universal facts are not deleted by engine.reset. ''' if isinstance(args, types.StringTypes): raise TypeError("engine.add_universal_fact: " "illegal args type, %s" % type(args)) args = tuple(args) return self.get_kb(kb_name, fact_base.fact_base) \ .add_universal_fact(fact_name, args) def add_case_specific_fact(self, kb_name, fact_name, args): r'''Case specific facts are deleted by engine.reset. ''' if isinstance(args, types.StringTypes): raise TypeError("engine.add_case_specific_fact: " "illegal args type, %s" % type(args)) args = tuple(args) return self.get_kb(kb_name, fact_base.fact_base) \ .add_case_specific_fact(fact_name, args) def assert_(self, kb_name, entity_name, args): if isinstance(args, types.StringTypes): raise TypeError("engine.assert_: " "illegal args type, %s" % type(args)) args = tuple(args) return self.get_kb(kb_name, fact_base.fact_base) \ .assert_(entity_name, args) def activate(self, *rb_names): r'''Activate rule bases. This runs all forward-chaining rules in the activated rule bases, so add your facts before doing this! ''' for rb_name in rb_names: self.get_rb(rb_name).activate() def lookup(self, kb_name, entity_name, pat_context, patterns): return self.get_kb(kb_name).lookup(pat_context, pat_context, entity_name, patterns) def prove_goal(self, goal_str, **args): r'''Proves goal_str with logic variables set to args. This returns a context manager that you use in a with statement: Ugly setup to use the family_relations example. You can ignore this... :-( >>> source_dir = os.path.dirname(os.path.dirname(__file__)) >>> family_relations_dir = \ ... os.path.join(source_dir, 'examples/family_relations') >>> sys.path.insert(0, family_relations_dir) >>> from pyke import knowledge_engine >>> my_engine = knowledge_engine.engine(family_relations_dir) >>> my_engine.activate('bc_example') OK, here's the example! >>> with my_engine.prove_goal( ... 'family.how_related($person1, $person2, $how_related)', ... person1='bruce') as it: ... for vars, plan in it: ... print "bruce is related to", vars['person2'], "as", \ ... vars['how_related'] vars is a dictionary of all of the logic variables in the goal (without the '$') and their values. The plan is a callable python function. If you only want the first answer, see engine.prove_1_goal. ''' return goal.compile(goal_str).prove(self, **args) def prove_1_goal(self, goal_str, **args): r'''Proves goal_str with logic variables set to args. Returns the vars and plan for the first solution found. Raises knowledge_engine.CanNotProve if no solutions are found. Ugly setup to use the family_relations example. You can ignore this... :-( >>> source_dir = os.path.dirname(os.path.dirname(__file__)) >>> family_relations_dir = \ ... os.path.join(source_dir, 'examples/family_relations') >>> sys.path.insert(0, family_relations_dir) >>> from pyke import knowledge_engine >>> my_engine = knowledge_engine.engine(family_relations_dir) >>> my_engine.activate('bc_example') OK, here's the example! >>> vars, plan = \ ... my_engine.prove_1_goal( ... 'bc_example.how_related($person1, $person2, $how_related)', ... person1='bruce', ... person2='m_thomas') >>> print "bruce is related to m_thomas as", vars['how_related'] bruce is related to m_thomas as ('father', 'son') If you want more than one answer, see engine.prove_goal. ''' return goal.compile(goal_str).prove_1(self, **args) def prove(self, kb_name, entity_name, pat_context, patterns): r'''Deprecated. Use engine.prove_goal. ''' return self.get_kb(kb_name).prove(pat_context, pat_context, entity_name, patterns) def prove_n(self, kb_name, entity_name, fixed_args = (), num_returns = 0): '''Returns a context manager for a generator of: a tuple of len == num_returns, and a plan (or None). Deprecated. Use engine.prove_goal. ''' if isinstance(fixed_args, types.StringTypes): raise TypeError("engine.prove_n: fixed_args must not be a string, " "did you forget a , (%(arg)s) => (%(arg)s,)?" % {'arg': repr(fixed_args)}) def gen(): context = contexts.simple_context() vars = self._Variables[:num_returns] try: with self.prove(kb_name, entity_name, context, tuple(pattern.pattern_literal(arg) for arg in fixed_args) + vars) \ as it: for plan in it: final = {} ans = tuple(context.lookup_data(var.name, final = final) for var in vars) if plan: plan = plan.create_plan(final) yield ans, plan finally: context.done() return contextlib.closing(gen()) def prove_1(self, kb_name, entity_name, fixed_args = (), num_returns = 0): r'''Returns a tuple of len == num_returns, and a plan (or None). Deprecated. Use engine.prove_1_goal. ''' try: # All we need is the first one! with self.prove_n(kb_name, entity_name, fixed_args, num_returns) \ as it: return iter(it).next() except StopIteration: raise CanNotProve("Can not prove %s.%s%s" % (kb_name, entity_name, condensedPrint.cprint( fixed_args + self._Variables[:num_returns]))) def print_stats(self, f = sys.stdout): for kb \ in sorted(self.knowledge_bases.itervalues(), key=lambda kb: kb.name): kb.print_stats(f) def trace(self, rb_name, rule_name): self.get_rb(rb_name).trace(rule_name) def untrace(self, rb_name, rule_name): self.get_rb(rb_name).untrace(rule_name) Compiled_suffix = None def _get_target_pkg(target_name): global Compiled_suffix if debug: print >> sys.stderr, "_get_target_pkg", target_name module = target_pkg.import_(target_name) path = module.__file__ if debug: print >> sys.stderr, "_get_target_pkg __file__ is", path do_reload = False if path.endswith('.py'): if Compiled_suffix is None: # We don't know whether this compiles to .pyc or .pyo yet, so do a # reload just to be sure... do_reload = True else: source_path = path path = path[:-3] + Compiled_suffix else: assert path.endswith(('.pyc', '.pyo')), \ 'unknown file extension: %r' % (path,) Compiled_suffix = path[-4:] source_path = path[:-1] if not do_reload: if debug: print >> sys.stderr, "source path is", source_path if os.path.exists(source_path): print >> sys.stderr, "source path exists" print >> sys.stderr, "source path mtime", \ os.path.getmtime(source_path) else: print >> sys.stderr, "source path does not exist" print >> sys.stderr, "compiled path is", path if os.path.exists(path): print >> sys.stderr, "compiled path exists" print >> sys.stderr, "compiled path mtime", \ os.path.getmtime(path) else: print >> sys.stderr, "compiled path does not exist" if not os.path.exists(path) or \ os.path.exists(source_path) and \ os.path.getmtime(source_path) > os.path.getmtime(path): do_reload = True if do_reload: if debug: print >> sys.stderr, "_get_target_pkg doing reload for", target_name module = reload(module) suffix = module.__file__[-4:] if suffix in ('.pyc', '.pyo'): Compiled_suffix = suffix if getattr(module, 'target_pkg_version', None) != pyke.target_pkg_version: if debug: print >> sys.stderr, "_get_target_pkg doing invalid version for", \ target_name return None return getattr(module, 'get_target_pkg')() def _pythonify_path(path): r'''Returns path_to_package, package_name, remainder_path, zip_file_flag. If zip_file_flag is set, remainder_path is ''. ''' path = os.path.normpath(os.path.abspath(path)) if path.endswith(('.py', '.pyw', '.pyc', '.pyo')): path = os.path.dirname(path) package_name = '' remainder_path = '' remainder_package_name = '' ans = '', '', path, False while path: if in_sys_path(path): if len(remainder_path) < len(ans[2]) or \ len(remainder_path) == len(ans[2]) and \ len(package_name) > len(ans[1]): if os.path.isdir(path): ans = path, package_name, remainder_path, False else: ans = path, remainder_package_name, '', True parent_path, dir = os.path.split(path) if parent_path == '' or parent_path == path: break if _is_package_dir(path): if package_name: package_name = dir + '.' + package_name else: package_name = dir else: package_path = os.path.join(*package_name.split('.')) package_name = '' if remainder_path: remainder_path = os.path.join(dir, package_path, remainder_path) else: remainder_path = os.path.join(dir, package_path) if remainder_package_name: remainder_package_name = dir + '.' + remainder_package_name else: remainder_package_name = dir path = parent_path return ans def _is_package_dir(path): if not os.path.isdir(path): return False return os.path.exists(os.path.join(path, '__init__.py')) or \ os.path.exists(os.path.join(path, '__init__.pyw')) or \ os.path.exists(os.path.join(path, '__init__.pyc')) or \ os.path.exists(os.path.join(path, '__init__.pyo')) def in_sys_path(path): r'''Assumes path is a normalized abspath. ''' return path in Sys_path ./pyke-1.1.1/doc/0000755000175000017500000000000011425360453012361 5ustar lambylamby./pyke-1.1.1/doc/syntax0000644000175000017500000000721111346504626013637 0ustar lambylamby'' -- required punctuation or keyword | -- alternation [] -- optional {} -- repeated at least once -- if it ends in a comma, the last comma is optional NL_TOK -- means one or more newlines file ::= [NL_TOK] ['extending' IDENTIFIER_TOK ['without' {IDENTIFIER_TOK,}] NL_TOK] [{fc_rule} ['fc_extras' NL_TOK INDENT_TOK { NL_TOK} DEINDENT_TOK]] [{bc_rule} ['bc_extras' NL_TOK INDENT_TOK { NL_TOK} DEINDENT_TOK] ['plan_extras' NL_TOK INDENT_TOK { NL_TOK} DEINDENT_TOK]] fc_rule ::= IDENTIFIER_TOK ':' NL_TOK INDENT_TOK [fc_foreach] fc_assert DEINDENT_TOK fc_foreach ::= 'foreach' NL_TOK INDENT_TOK {fc_premise NL_TOK} DEINDENT_TOK fc_premise ::= fact_pattern | 'first' fc_premise | 'first' NL_TOK INDENT_TOK {fc_premise NL_TOK} DEINDENT_TOK | 'forall' NL_TOK INDENT_TOK {fc_premise NL_TOK} DEINDENT_TOK [ 'require' NL_TOK INDENT_TOK {fc_premise NL_TOK} DEINDENT_TOK ] | 'notany' NL_TOK INDENT_TOK {fc_premise NL_TOK} DEINDENT_TOK | python_premise fact_pattern ::= IDENTIFIER_TOK '.' IDENTIFIER_TOK '(' [{pattern,}] ')' pattern ::= NONE_TOK | TRUE_TOK | FALSE_TOK | NUMBER_TOK | IDENTIFIER_TOK | STRING_TOK | variable | '(' [{pattern,}] ['*' variable] ')' variable ::= PATTERN_VAR_TOK | ANONYMOUS_VAR_TOK python_premise ::= pattern '=' python_exp | pattern 'in' python_exp | 'check' python_exp | python_statements python_statements ::= 'python' | 'python' NL_TOK INDENT_TOK { NL_TOK} DEINDENT_TOK fc_assert ::= 'assert' NL_TOK INDENT_TOK {assertion NL_TOK} DEINDENT_TOK assertion ::= fact_pattern | python_statements bc_rule ::= IDENTIFIER_TOK ':' NL_TOK INDENT_TOK use [when] [with] DEINDENT_TOK use ::= 'use' IDENTIFIER_TOK '(' {pattern,} ')' NL_TOK | 'use' IDENTIFIER_TOK '(' {pattern,} ')' 'taking' '(' ')' NL_TOK | 'use' IDENTIFIER_TOK '(' {pattern,} ')' NL_TOK INDENT_TOK 'taking' '(' ')' NL_TOK DEINDENT_TOK when ::= 'when' NL_TOK INDENT_TOK {bc_premise NL_TOK} DEINDENT_TOK bc_premise ::= ['!'] [ name '.' ] name '(' {pattern,} ')' plan_spec | ['!'] 'first' bc_premise | ['!'] 'first' NL_TOK INDENT_TOK {bc_premise NL_TOK} DEINDENT_TOK | 'forall' NL_TOK INDENT_TOK {bc_premise NL_TOK} DEINDENT_TOK [ 'require' NL_TOK INDENT_TOK {bc_premise NL_TOK} DEINDENT_TOK ] | 'notany' NL_TOK INDENT_TOK {fc_premise NL_TOK} DEINDENT_TOK | python_premise name ::= IDENTIFIER_TOK | PATTERN_VAR_TOK plan_spec ::= step_opt NL_TOK | 'as' PATTERN_VAR_TOK NL_TOK | step_opt NL_TOK INDENT_TOK { NL_TOK} DEINDENT_TOK (with '$$' for returned fn) step_opt ::= | 'step' NUMBER_TOK with ::= 'with' NL_TOK INDENT_TOK { NL_TOK} DEINDENT_TOK ./pyke-1.1.1/doc/__init__.py0000644000175000017500000000000011346504626014464 0ustar lambylamby./pyke-1.1.1/doc/cheatsheets/0000755000175000017500000000000011425360453014661 5ustar lambylamby./pyke-1.1.1/doc/cheatsheets/css_help0000644000175000017500000000105011346504626016404 0ustar lambylambyBox model: margin | border | padding | content | padding | border | margin margin is always transparent table: "border-collapse: collapse" names must be: [a-zA-Z][a-zA-Z0-9-]* -- hyphens; not underscores! .class #id -- pseudo classes: :active -- between button-down and button-up :first-child :focus :hover :lang(X) :link :visited -- pseudo elements: :after -- to insert content after an element's content (content: xxx) :before -- to insert content before an element's content (content: xxx) :first-letter :first-line ./pyke-1.1.1/doc/cheatsheets/rest2web0000644000175000017500000000145611346504626016353 0ustar lambylambyInstalling rest2web (May 14, 2009). There is a 0.5.1 version on http://www.voidspace.org.uk/python/rest2web/ but not on https://sourceforge.net/projects/rest2web. Their subversion respository is https://svn.rest2web.python-hosting.com/trunk/. It doesn't work with easy_install either :-(. It seems that 0.5.1 has bug fixes for Python 2.5, so I'm assuming that it doesn't work on Python 2.6 (but haven't tried it). Installation steps: 1. sudo easy_install-2.5 docutils 2. Download the 0.5.1.zip file. 3. cd /storage/downloads 4. unzip rest2web-0.5.1.zip 5. cd rest2web-0.5.1 5. sudo mv rest2web /usr/lib/python2.5/site-packages 6. sudo chown -R root:root /usr/lib/python2.5/site-packages/rest2web 7. sed '1s,python,python2.5,' r2w.py > r2w 8. chmod +x r2w 9. sudo chown root:root r2w 10. sudo mv r2w /usr/bin ./pyke-1.1.1/doc/cheatsheets/sf_hg_access0000644000175000017500000000011311346504626017212 0ustar lambylambyssh -t mtnyogi,pyke@shell.sourceforge.net create cd /home/scm_hg/p/py/pyke ./pyke-1.1.1/doc/cheatsheets/from_svn/0000755000175000017500000000000011425360453016512 5ustar lambylamby./pyke-1.1.1/doc/cheatsheets/from_svn/shamap.hg_order0000644000175000017500000005327311346504626021514 0ustar lambylambysvn:280a1989-90ef-4448-b869-2704929e1777/trunk@193 004c0c044772247fe96f414050f9679d1755ec65 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@142 00a03bbc9eeacd589b6f269a0980927e0ccbc100 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@238 020dc443c6b44b6576ad6b441f7a627042214dac svn:280a1989-90ef-4448-b869-2704929e1777/trunk@32 04c623cfafb17de5c359f4796e83461376fdb80d svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@246 057d79259b209c0cb1c6784aa872b57036e5c167 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@45 05a128b38e5dd6bb213485e9a8a711bf2c5d7979 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@203 08db709f2e4c9f1381a6dae523169ed5921d4fd7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@55 09e5d4f578d09fbbde77e608074456eb68e92f78 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@122 09e7aeede8896d538952faf86e6f1b83bd7b4ca6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@76 0bd686a1ee59d36cb46d009f462e69d28f9cb783 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@136 0d9d268ff45702e94e89d58d6ea2792a7cf744e3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@138 0ee160488d06bdf10277e79cc8ab788627e64131 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@66 0f171275ef53b3fd0ea9bb73347f85ac5fb4fbfe svn:280a1989-90ef-4448-b869-2704929e1777/trunk@14 0f22938ecc3a94e41ccd32c32b611543030ec654 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@233 1037341283d980cb53b9b566b830dbfb03a5e02d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@116 1053a76196fb9242f2e0f4edef15bebcfc5dc8bc svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@221 124b5d832e2fb17ff29e89c375c0896a2e0a1974 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@250 1259c0704d77b8748d0e0fb2af594fda4b046fee svn:280a1989-90ef-4448-b869-2704929e1777/trunk@46 141a36f34199de920c668416e06478b6d0d1f3bf svn:280a1989-90ef-4448-b869-2704929e1777/trunk@4 14453f2f69810754eca9a0b4517c36cee6a58ee3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@139 1649988e3ac410b8cc6723cddb976adc896af46a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@78 170e42db52492632ff3de5c31ec83381c81f9641 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@155 1b9ce94d58154f031cecad45ceaa8e655182a310 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@12 1c69a760ffe8b7232f393bd3769d439b8332c064 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@227 1da0455c55371bd2ca4c8bd2779d61b7b1bc05b2 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@125 1ecb05f1b2f29ef475df03e0e10b0a8e624e2ad0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@24 238d7743b0c0bed7061c273856470ae540e15d3f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@140 23a00cec9d32f5037c50bed79181229cedb2b802 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@220 252a2849b88ac58a04344031e1e717282acd40c4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@89 27144980246b3f13fb6b63f45d3d1dba486f6372 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@43 27797b9ee361453fddac5d4966e55bbeaab3419a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@119 27a6fd2a410d9b59cb872f1f4522ae08a32a2563 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@218 27ea017a37c4989068fde92f50b072e9387727e7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@97 27ec571c66c0b4bff4c64d7ed0d182c72ee99b33 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@162 2825a90e94455947736ce2fb17dea06964f13cd9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@194 28aa4e16f2282636acb2d82f2e7be355c98c8907 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@49 2a30e889415d2c4cbad2b91ab0b657ad2bd16627 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@150 2bb500de12683b80e5ed87e17ff422565c06ae8d svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@237 2f5b42c29587bf0d3486d43c1f5f9725fdf9d051 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@130 2f6d13d2eb84790f2f1ab3ee157e8519205d2b39 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@129 300377fc33e64d7daefc48b4786d3dd064e47356 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@199 30a9bc80c33f7233a54c7c558501c61b0955dea4 svn:280a1989-90ef-4448-b869-2704929e1777/branches/zope@173 31b2a650f313c9c0a6a2aa64f0cf8cb0d0a23ad3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@172 31b2a650f313c9c0a6a2aa64f0cf8cb0d0a23ad3 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@230 32c3f5b90395efeedad7a7239a7f9934670ad6d1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@190 3315cbe4f46e6760509979ee632fe9f838e9d5d7 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@234 361e09ac6aa526410043ca40ec6f891d4396ba3c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@34 36a9494259eb8df748c80b946b7f1a26dfa979c7 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@235 371c4cc61f4756f0eb4c84c53107b473e0b6eb2c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@174 37d4a79f1c003036f7ac777c39174e63d9b0f20d svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@245 39689d7cbce3b67ffe7a35024973b97f3372d434 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@228 39a916143e0c6fa954a4af196cd443760bf97d11 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@209 39d43afba7dd9b0294d52c615974544df857d409 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@204 3e69271bcf55b932991b2a43927cac83e2758cc5 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@229 3f628a6057f4285698c41e508d54f6fb8241f3d6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@103 4095a6364ce46d59afe5815f8b1675029b5560f9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@68 41aea150cfdc5d8e9c84b3f050d3545e21b1b979 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@25 41ff1e431b8c96d05bfcf8754aa8ce16c8d0d9f3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@160 4368e111e2a0a3188d3903b2dea7eeddbfaf269c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@9 447832834fdfa1b6462d0f566831204b736a37cf svn:280a1989-90ef-4448-b869-2704929e1777/trunk@39 45e4bbd974d3245a54da4b2e0be1fdda3b889f31 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@215 461e189c3e9dfca1b8ab44db8f7121a47c9978bb svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@207 46f2beb1f589c9658c776d928f13172e677f0294 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@248 48d3fea1f5735419619e7141e65a4ac148eb9e4f svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@217 4aaf2b51da0314561691c4e1085e3922757a9d3a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@106 4ca57fd63b60bd0a198b4415d11f346558e462b1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@108 4dd0959ccca49354f804d88df7a2c855f3228740 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@104 4f21c6821e9ba3b222420170b952b1120efb18c2 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@179 500e14bdb9bbd434676b993086f2240e34419efa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@96 50e9b4593447a1d2472bc545c038fc362bf560b1 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@213 5210290788a1181621f7ce13a46e125eb1e18bd8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@95 526e905c2d5c635eba8b4f5619969130903cebc0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@151 53737d0387c8485848d32f4ea331d273edf50400 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@223 53cf0ee16fe22cb238f071ea8bcd32314d25aaf8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@98 543a06e3ce7f66f34765cc4c03943f5831dac122 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@240 54a3fb8413c1e3d6e1220e31f80fcb4b7c30b946 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@67 55084c68a04f02ff493699dc3d56d22b4575c81e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@36 5514c1b427fd22e6300a2f240928573a441ac0a5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@1 55c3bd1fa5ad6dda66319d1e4347f1d44998f245 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@16 565184b9daf8266473b813960ac3ba293b83fe9d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@92 5846e05fb82734fd1fc55e47730ea67256ddf0fe svn:280a1989-90ef-4448-b869-2704929e1777/trunk@154 58b2e2212483ba90db840e799933d2645087f454 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@73 5a44bd4649a1f9a2bce9836e8c3867d31c3ee332 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@19 5a6a9744d2364733b8634dfb8c847e1a28a63746 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@93 5b9ac524b7ebb1b25704ffae17926c24513f9b3d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@62 5c92b3a7491c19d4042427dd04331e74249bdde5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@166 5d0d34bc649ed37f40bc1db0d119b6941cb3b51a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@168 5df5fc23bf48f3f0e4561e02ec11bc8d053e377e svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@198 5e9289d4182c4269a925c70aecbf70eafd799a91 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@196 5e9289d4182c4269a925c70aecbf70eafd799a91 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@212 5ea7f12087f93a0d10622e7a0a3f94217f637fc0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@20 5ed680eb1b046107ec93422777aa97617e2fe04e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@145 603d2923e657db5068d77f0045d3effa6ecb2038 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@127 6212a61d7abc8f3cd8e7842294f947721a2c7709 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@170 62dbd4b5fb78d2c3c710d23771f37aa6403c07ff svn:280a1989-90ef-4448-b869-2704929e1777/trunk@163 632068ca36ebfc02fec9e5901bedafd9c1d9ec1a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@202 63f44d74b7c6273d81d62f7a2d4e4c942e9371ba svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@201 649a41aa774c0d10894a1c8f0fe4685fa72c595e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@200 649a41aa774c0d10894a1c8f0fe4685fa72c595e svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@211 64b6699c61b86b5a732252c773aefaba8ac47690 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@31 6617b7aa7824b2d0064f53caaa8208603b891108 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@44 664ff001a2cad5f29ca5a6c4c614b0a9df04ae44 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@22 66814e5404ef5ab956c91a281d74eef5b3edb421 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@63 66bbffda843586635e0232de2fca8df18534eb37 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@26 69046670a719a3e812c7e24bf5416f379400f4ee svn:280a1989-90ef-4448-b869-2704929e1777/trunk@18 698a37b6814053826e6cfd9a25ff3c82a9fa63a3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@178 6ba6c58dda449209ce25b4bebb723cfe365f8d77 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@50 6c2fcb432dbd3207e211547c158fc73508c0be46 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@57 6cba15d129f605c06bf8298884253bafaaf37f66 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@182 6e8bf0d535b31388eae7cb2534a3af9972b14f2e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@85 734c698b4deb4b99abec1ad6828d895e093c0735 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@71 74b0ca757ad3dfbf2fb30f21f2e7be8d2e0d694e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@120 765c02db8903eea08b54e2921078d923572dd42f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@5 7996a88b9e2c4fc671500131d18657070bda0253 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@115 7a5f9c2f420f2189c965796d0640b550c98a55a7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@118 7c29e9a21affe6044cde6b04252a8085428267a9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@102 7ccc7be2f550b9af1155fabbdc78a51badd802b7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@90 7cd0dd5bff3dfc0b8da903b2c3498dc047d51274 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@65 7de8b453d3d1f265ca1d96d2fac713a99cc47aaa svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@241 80220df5730f41ffb21989e71dc32f365e5f2f76 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@153 80a7e618855ab9032ae79879fff72dda1cf2e2d9 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@239 81d7676b2cf4ddd3b92c08d031da377f38acdc0f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@128 837894519c8360e52d3440829ab28ebc53fcb4e3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@52 839f6db3ae43ad5894d5f8da09f407138acd311c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@137 8420439b4bae699fe7679367acd3a225883243d4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@6 869429fb7fc87f77f938e808b2a714834e77c4b3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@47 86ef6c004956c6c0930752f212ba5c5a631ec9aa svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@236 8719392536ca0c970d947f13a99680a133d46593 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@15 879ae09d9df83e5e66185261b420c3eb05527d88 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@205 88492f227bdc6a44aa295a19116977b6acd5582a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@124 88c936e441f5d553cc16ca6bed4e1501361bea80 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@183 8be709dbb806c0a412b0c68657943e6a42dad627 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@146 8c02baa2d95c8f955d737d1c539af8ae2b47849a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@86 8d35aeda76201ee457fbb76dbb2d17b5077a9c49 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@206 913c96b68af3d10d3793b73de1e470b898df7cd7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@59 916d24c6b8a68e42c27256c7b2d95b27c36fb7c5 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@210 91bdc9b5e454579488092f59fbd92ca43bc0e92c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@11 91d91cce3a545d8d560aee5c3e4e8a1d9eb1afcb svn:280a1989-90ef-4448-b869-2704929e1777/trunk@244 92073671619ecf887e79a009d0cfa6f4bb243894 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@253 93041c73c46b16b64b5be1c440abb69b90caa9c9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@79 93aa5effed85049ffe88f3720db56dd663d20cbe svn:280a1989-90ef-4448-b869-2704929e1777/trunk@149 95098790c54220a87fe296a60958f8a1970730ec svn:280a1989-90ef-4448-b869-2704929e1777/trunk@82 965ee7ad97617cc2dda44e359b640c27029bc198 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@141 97f0531f7c21a847e3dc79f7f76c511cec34d32c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@180 99a8d3c03fcc646659bb89ff0044b4eceaf94323 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@232 9a26aa02b491f345321147a487df5075ef86f0e6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@8 9ad3713536c3e0bb858a97463ea967be5a0e4615 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@161 9b9741998898ef5024cdf06e07d11505d7c5ae15 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@184 9c1b571b39ac852850d9afcd907aced721fb330b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@13 9c6ab0d8e084237c7ae5faa330f759520431d2bc svn:280a1989-90ef-4448-b869-2704929e1777/trunk@2 9cc939fda63a88b023985336a5685a1207b7510b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@99 9cf235538b1aa63a1e531a97780aa1cdfbc0ebff svn:280a1989-90ef-4448-b869-2704929e1777/trunk@164 9db75244a52c92653b9479c5730a5d29410b5fc4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@169 a022d942097994b0c675052bb159355fc0d165a0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@10 a12f244a02fe79e53a025a2b48cc47507a34d04c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@147 a1da97f54693e0e162ef65c1e42c2afc5906be20 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@152 a2119c07028f680f7e8979988ee8cd4b60828d85 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@77 a32a78d01ec4ef0ef71b8d73fe9a2fbfa3f9d6e1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@3 a45ab1bd32dc654d3cda97368036f15ab542e10d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@42 a74e3f598ed84ff942bd7fc12d0844b16ade8ce6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@75 a7d38b206efba29e2b7b92b7bf9b39287040d84a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@51 a8c5eb1fe22da0043f3668f9b329c788c0c39e00 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@216 abb78effaba940bc5717c718f043942f0b5b888f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@37 adcc3e4ec64ccbbecdcd4945942e477dab5e1ade svn:280a1989-90ef-4448-b869-2704929e1777/trunk@80 b0f66d9f5c763558b37e44e92caef316c0feb060 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@117 b1ccc80ff75ac1a5abdca68410fd1ba85c82c2fa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@156 b3a32d7320ae50fbbf57f9b14db6a81d3c79cd99 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@131 b4780f4ee2120d9efb89fbbe978851181ebd90ba svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@243 b4cfb5de45c897c2f71a805b39e49f77dc9c2f52 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@226 b788b2d027eb00b3ae064e77debd1c9ad3e31bc4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@112 bc17181582956570621303a42460fac3db781173 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@135 bfcaf9a66359ec2c4d74033a12aeb3fc30d162f7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@87 c031ad5d2ecec3c62984aea66a2741400f0ccca8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@56 c062a2031c16e90b3335133ec7b581c917e8f96d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@70 c0848aaeaad797772a48c497f8b3377fabd04d76 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@105 c203b84fe600cc962e76e08f9b95fbe5a948a412 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@225 c30276c539f7453f5f593a513b7ed6e40164593f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@88 c593ff2fa99723b0552722796717fb33ed3089ae svn:280a1989-90ef-4448-b869-2704929e1777/trunk@58 c599d2bc8363a839cb3f05e52e603a717a979ad6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@148 c5e55da127125e04b80b15933fd0ccd94a441ef1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@81 c65e43e284ee6213464aead5e99f3590110d55f4 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@224 c668061b6f76bc39ffaedbd33d8ab91ef26527a7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@35 c7198481a02f4f9cc835bb5ba97936971cdaca82 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@83 c8540c4f91fea38fc04ce39d58c88cc77c89d751 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@38 c9bc1d60a78aa60049e0364fb28625844a003502 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@159 cab589ae6cfb1976f338873f8d89afe622ff96a5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@30 cb745723955141d5f8608b54ea1b621ffebe8873 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@185 cc52adb6773fb10c347a3fac4dfa46471a342bc7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@28 cd27300ec152f459e428f98ccf0be428be249b50 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@186 cf0d0598d1dbde25444ed7f2452f4e4b5237521b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@23 d1f1b064481370c38253f229d7d0eb8b2d753e36 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@61 d43b9376fcf5ceff5448f0c8968981f574d94d1d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@126 d64633875e2b54ff7f5b993b3e30af2cb4206ec7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@17 d79e3e48581ac3a9fd82690ebba1763ff9cec5bb svn:280a1989-90ef-4448-b869-2704929e1777/trunk@134 d7e01c9e5bdca67a26dd304d62e3bdaafe2d481b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@113 d90801e3bfa502b54da0bf084027df6f32e52f85 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@214 d941520d2b708b5d583c785c0c281ca85b2832d9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@133 daaa92dd4b6e6d42dd071d8b10513c904c05c3b7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@251 dc355c4fda996cf9353f95ccbc779006407f3a60 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@187 dce03638f358cf42354fd8e5ce7e1a271d3092ca svn:280a1989-90ef-4448-b869-2704929e1777/trunk@27 dcf73021d37d6ba524fd78a341456fc24442bd1e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@53 dd3b68327689007cce230b220e7483ee4138c712 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@208 dee8ced42ea53bf3c6f66c3cea096cbfe1d93f9c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@132 e16031e560d42f002f9ff7e4f9b46b10879a6206 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@121 e3ae1837197035f30d39fadca496bb817c02c39c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@175 e547f5e92454fc830f0d98969a8e7acac0033e80 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@72 e67fdb5714ce5be9655dc2d0b507e78abee049bc svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@252 e6e990a053fdeb7e4d13421c0638af5cdfd0c929 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@54 e7aa2fa52cf6847126df737cbfd6b117103ffeb7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@41 e7b7ba729069d98d973e132a7d728fb6513d55d6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@109 e7ee5f0197b0b2d8d2e45f2fb7e243a42764325a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@60 e9291bc4ad2e61f094bfd4e18ac19d9fc926f656 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@249 e92c5345020b3ac49413e96aa9c47ab133f653f5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@111 eb84e07b204d8b96b2a7cb3161d813d530d4fcb5 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@247 ec934e11cb61be9feeeb943c5a44a719e2cc8c29 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@94 ed186b5a10e8028e67b91a6b581f41332a7cd9ea svn:280a1989-90ef-4448-b869-2704929e1777/trunk@167 eece38b0472ad0320901a43294c6681f834446e8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@69 eef52e9492485afdc59ff0d00da143179ddfdc8a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@188 ef7132bc7df21d9374dd91121731bfe0d8fa0bb9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@7 efa2626ca56a4aebf7b6a1258f8a5310defb04aa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@123 f048390d1f3455f34d6c2e291d516ddffefbf536 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@157 f17c13400c68987a9978fc80a59a292c6be6514b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@101 f1a37747ea1b827a40d495506bca5a6e40796bd6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@33 f1c3a49912ade54d31402fa32955f3ae585ed37c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@40 f4f20645ef3549c0a775eeda063a6cedb00754c8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@222 f5130a902ee9b8abf1e408ad0ff0e5ec78f5f8e3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@144 f61484680d6d821b82b96a2a35c28a36b1638fea svn:280a1989-90ef-4448-b869-2704929e1777/trunk@100 f66b44a75bfaf36436c7a838759f0397dc11964d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@219 f6f592a2e7b6177de4fc36261d4d2c603a66caa0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@114 f891112d51fa1875cf78eb45ba3ad8ca6ca49f59 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@84 f906c30c77fe0f1a6e5ff7a34eed7d7c6b125947 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@29 fa6daebedbf53d017e5e4f3cffe8529525949939 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@110 fa777b3b351892d60518fb2a5e26acff5e9fb2a3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@91 fc35ed8f9ae8ba5cb005c275787f4be2dcd08020 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@165 fe1a2365d4bf54f4d5280ab974be52068cf788d8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@21 ffca543be171a570f089e22b030f78471a19506e ./pyke-1.1.1/doc/cheatsheets/from_svn/pyke.filemap0000644000175000017500000000002211346504626021017 0ustar lambylambyexclude old_stuff ./pyke-1.1.1/doc/cheatsheets/from_svn/README.txt0000644000175000017500000000556411346504626020226 0ustar lambylambyHere's how I got there (all directories relative to ~/python/workareas/pyke-hg-convert): 1. Copied svn repo from sourceforge to the "pykemirror" directory: 2. Ran "hg convert" using pyke.filemap and pyke.splicemap (both in ~/python/workareas/pyke-hg-convert) to produce sf.3 hg repo. 3. Do "hg rollback" in sf.3 to undo the commit of .hgtags done by "hg convert". 4. hg clone sf.3 trial.3 (make sure you got updated to the tip, the last changeset for the "release_1.0" branch, and not the last "default" branch). 5. Add username to trial.3/.hg/hgrc 6. mv sf.3/.hgtags trial.3 7. Copy pyke.filemap, pyke.splicemap and sf.3/.hg/shamap to trial.3/doc/cheatsheets/from_svn 8. Set up hg keywords, create trial.3/hgrc_keywords, add to trial.3/.hg/hgrc 9. Create trial.3/.hgignore 10. Change test scripts: Test/testTest doc/testdocs examples/testexamples pyke/testpyke to automatically set PYTHONPATH to be able to run from any clone. 11. Extraneous change to examples/family_relations/family.kfb for keyword expansion problem. 12. Remove .svn directories from: Test/testTest examples/testexamples make_doc_tarball make_examples_tarball 13. Commit all changes to trial.3 14. hg convert --config convert.hg.clonebranches=True \ --config convert.hg.usebranchnames=0 trial.3 split 15. Go into split/release_1.0: A. "hg rollback" the .hgtags commit B. Move .hgtags somewhere else C. hg update tip D. Move .hgtags back E. join split/release_1.0/doc/cheatsheets/from_svn/shamap and split/.hg/shamap to update the hg ids (since these have been reassigned in the split) F. Add username to .hg/hgrc G. hg commit 16. Remove the named branches from split/release_1.0 and split/pre_2to3: mkdir no_branches_1 hg convert --branchmap branchmap split/release_1.0 no_branches_1/release_1 hg convert --branchmap branchmap split/pre_2to3 no_branches_1/pre_2to3 We don't need: split/default -- because it's the same as release_1.0 split/zope -- the person can do his own repo to work on this now This reassigns the hg ids (again), but assigns the same ids to shared changesets between no_branches_1/release_1 and no_branches_1/pre_2to3. 17. Go into no_branches_1/release_1 and update doc/cheatsheets/from_svn/shamap using both no_branches_1/{release_1,pre_2to3}/.hg/shamap to convert. The .hgtags file gets converted automatically by "hg convert" without it doing a commit this time... Make sure you update the username in no_branches_1/release_1/.hg/hgrc prior to committing. 18. Create a release_1 and pre_2to3 repository on sourceforge (shell interface) and push the two no_branches_1 repos to sourceforge. 19. Using the shell interface, clone release_1 on sourceforge to create the pyke repo on sourceforge. ./pyke-1.1.1/doc/cheatsheets/from_svn/shamap.svn_order0000644000175000017500000005327311346504626021724 0ustar lambylambysvn:280a1989-90ef-4448-b869-2704929e1777/trunk@1 55c3bd1fa5ad6dda66319d1e4347f1d44998f245 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@2 9cc939fda63a88b023985336a5685a1207b7510b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@3 a45ab1bd32dc654d3cda97368036f15ab542e10d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@4 14453f2f69810754eca9a0b4517c36cee6a58ee3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@5 7996a88b9e2c4fc671500131d18657070bda0253 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@6 869429fb7fc87f77f938e808b2a714834e77c4b3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@7 efa2626ca56a4aebf7b6a1258f8a5310defb04aa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@8 9ad3713536c3e0bb858a97463ea967be5a0e4615 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@9 447832834fdfa1b6462d0f566831204b736a37cf svn:280a1989-90ef-4448-b869-2704929e1777/trunk@10 a12f244a02fe79e53a025a2b48cc47507a34d04c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@11 91d91cce3a545d8d560aee5c3e4e8a1d9eb1afcb svn:280a1989-90ef-4448-b869-2704929e1777/trunk@12 1c69a760ffe8b7232f393bd3769d439b8332c064 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@13 9c6ab0d8e084237c7ae5faa330f759520431d2bc svn:280a1989-90ef-4448-b869-2704929e1777/trunk@14 0f22938ecc3a94e41ccd32c32b611543030ec654 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@15 879ae09d9df83e5e66185261b420c3eb05527d88 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@16 565184b9daf8266473b813960ac3ba293b83fe9d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@17 d79e3e48581ac3a9fd82690ebba1763ff9cec5bb svn:280a1989-90ef-4448-b869-2704929e1777/trunk@18 698a37b6814053826e6cfd9a25ff3c82a9fa63a3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@19 5a6a9744d2364733b8634dfb8c847e1a28a63746 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@20 5ed680eb1b046107ec93422777aa97617e2fe04e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@21 ffca543be171a570f089e22b030f78471a19506e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@22 66814e5404ef5ab956c91a281d74eef5b3edb421 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@23 d1f1b064481370c38253f229d7d0eb8b2d753e36 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@24 238d7743b0c0bed7061c273856470ae540e15d3f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@25 41ff1e431b8c96d05bfcf8754aa8ce16c8d0d9f3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@26 69046670a719a3e812c7e24bf5416f379400f4ee svn:280a1989-90ef-4448-b869-2704929e1777/trunk@27 dcf73021d37d6ba524fd78a341456fc24442bd1e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@28 cd27300ec152f459e428f98ccf0be428be249b50 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@29 fa6daebedbf53d017e5e4f3cffe8529525949939 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@30 cb745723955141d5f8608b54ea1b621ffebe8873 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@31 6617b7aa7824b2d0064f53caaa8208603b891108 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@32 04c623cfafb17de5c359f4796e83461376fdb80d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@33 f1c3a49912ade54d31402fa32955f3ae585ed37c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@34 36a9494259eb8df748c80b946b7f1a26dfa979c7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@35 c7198481a02f4f9cc835bb5ba97936971cdaca82 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@36 5514c1b427fd22e6300a2f240928573a441ac0a5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@37 adcc3e4ec64ccbbecdcd4945942e477dab5e1ade svn:280a1989-90ef-4448-b869-2704929e1777/trunk@38 c9bc1d60a78aa60049e0364fb28625844a003502 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@39 45e4bbd974d3245a54da4b2e0be1fdda3b889f31 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@40 f4f20645ef3549c0a775eeda063a6cedb00754c8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@41 e7b7ba729069d98d973e132a7d728fb6513d55d6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@42 a74e3f598ed84ff942bd7fc12d0844b16ade8ce6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@43 27797b9ee361453fddac5d4966e55bbeaab3419a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@44 664ff001a2cad5f29ca5a6c4c614b0a9df04ae44 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@45 05a128b38e5dd6bb213485e9a8a711bf2c5d7979 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@46 141a36f34199de920c668416e06478b6d0d1f3bf svn:280a1989-90ef-4448-b869-2704929e1777/trunk@47 86ef6c004956c6c0930752f212ba5c5a631ec9aa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@49 2a30e889415d2c4cbad2b91ab0b657ad2bd16627 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@50 6c2fcb432dbd3207e211547c158fc73508c0be46 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@51 a8c5eb1fe22da0043f3668f9b329c788c0c39e00 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@52 839f6db3ae43ad5894d5f8da09f407138acd311c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@53 dd3b68327689007cce230b220e7483ee4138c712 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@54 e7aa2fa52cf6847126df737cbfd6b117103ffeb7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@55 09e5d4f578d09fbbde77e608074456eb68e92f78 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@56 c062a2031c16e90b3335133ec7b581c917e8f96d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@57 6cba15d129f605c06bf8298884253bafaaf37f66 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@58 c599d2bc8363a839cb3f05e52e603a717a979ad6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@59 916d24c6b8a68e42c27256c7b2d95b27c36fb7c5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@60 e9291bc4ad2e61f094bfd4e18ac19d9fc926f656 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@61 d43b9376fcf5ceff5448f0c8968981f574d94d1d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@62 5c92b3a7491c19d4042427dd04331e74249bdde5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@63 66bbffda843586635e0232de2fca8df18534eb37 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@65 7de8b453d3d1f265ca1d96d2fac713a99cc47aaa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@66 0f171275ef53b3fd0ea9bb73347f85ac5fb4fbfe svn:280a1989-90ef-4448-b869-2704929e1777/trunk@67 55084c68a04f02ff493699dc3d56d22b4575c81e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@68 41aea150cfdc5d8e9c84b3f050d3545e21b1b979 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@69 eef52e9492485afdc59ff0d00da143179ddfdc8a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@70 c0848aaeaad797772a48c497f8b3377fabd04d76 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@71 74b0ca757ad3dfbf2fb30f21f2e7be8d2e0d694e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@72 e67fdb5714ce5be9655dc2d0b507e78abee049bc svn:280a1989-90ef-4448-b869-2704929e1777/trunk@73 5a44bd4649a1f9a2bce9836e8c3867d31c3ee332 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@75 a7d38b206efba29e2b7b92b7bf9b39287040d84a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@76 0bd686a1ee59d36cb46d009f462e69d28f9cb783 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@77 a32a78d01ec4ef0ef71b8d73fe9a2fbfa3f9d6e1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@78 170e42db52492632ff3de5c31ec83381c81f9641 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@79 93aa5effed85049ffe88f3720db56dd663d20cbe svn:280a1989-90ef-4448-b869-2704929e1777/trunk@80 b0f66d9f5c763558b37e44e92caef316c0feb060 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@81 c65e43e284ee6213464aead5e99f3590110d55f4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@82 965ee7ad97617cc2dda44e359b640c27029bc198 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@83 c8540c4f91fea38fc04ce39d58c88cc77c89d751 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@84 f906c30c77fe0f1a6e5ff7a34eed7d7c6b125947 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@85 734c698b4deb4b99abec1ad6828d895e093c0735 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@86 8d35aeda76201ee457fbb76dbb2d17b5077a9c49 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@87 c031ad5d2ecec3c62984aea66a2741400f0ccca8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@88 c593ff2fa99723b0552722796717fb33ed3089ae svn:280a1989-90ef-4448-b869-2704929e1777/trunk@89 27144980246b3f13fb6b63f45d3d1dba486f6372 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@90 7cd0dd5bff3dfc0b8da903b2c3498dc047d51274 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@91 fc35ed8f9ae8ba5cb005c275787f4be2dcd08020 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@92 5846e05fb82734fd1fc55e47730ea67256ddf0fe svn:280a1989-90ef-4448-b869-2704929e1777/trunk@93 5b9ac524b7ebb1b25704ffae17926c24513f9b3d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@94 ed186b5a10e8028e67b91a6b581f41332a7cd9ea svn:280a1989-90ef-4448-b869-2704929e1777/trunk@95 526e905c2d5c635eba8b4f5619969130903cebc0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@96 50e9b4593447a1d2472bc545c038fc362bf560b1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@97 27ec571c66c0b4bff4c64d7ed0d182c72ee99b33 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@98 543a06e3ce7f66f34765cc4c03943f5831dac122 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@99 9cf235538b1aa63a1e531a97780aa1cdfbc0ebff svn:280a1989-90ef-4448-b869-2704929e1777/trunk@100 f66b44a75bfaf36436c7a838759f0397dc11964d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@101 f1a37747ea1b827a40d495506bca5a6e40796bd6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@102 7ccc7be2f550b9af1155fabbdc78a51badd802b7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@103 4095a6364ce46d59afe5815f8b1675029b5560f9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@104 4f21c6821e9ba3b222420170b952b1120efb18c2 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@105 c203b84fe600cc962e76e08f9b95fbe5a948a412 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@106 4ca57fd63b60bd0a198b4415d11f346558e462b1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@108 4dd0959ccca49354f804d88df7a2c855f3228740 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@109 e7ee5f0197b0b2d8d2e45f2fb7e243a42764325a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@110 fa777b3b351892d60518fb2a5e26acff5e9fb2a3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@111 eb84e07b204d8b96b2a7cb3161d813d530d4fcb5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@112 bc17181582956570621303a42460fac3db781173 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@113 d90801e3bfa502b54da0bf084027df6f32e52f85 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@114 f891112d51fa1875cf78eb45ba3ad8ca6ca49f59 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@115 7a5f9c2f420f2189c965796d0640b550c98a55a7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@116 1053a76196fb9242f2e0f4edef15bebcfc5dc8bc svn:280a1989-90ef-4448-b869-2704929e1777/trunk@117 b1ccc80ff75ac1a5abdca68410fd1ba85c82c2fa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@118 7c29e9a21affe6044cde6b04252a8085428267a9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@119 27a6fd2a410d9b59cb872f1f4522ae08a32a2563 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@120 765c02db8903eea08b54e2921078d923572dd42f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@121 e3ae1837197035f30d39fadca496bb817c02c39c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@122 09e7aeede8896d538952faf86e6f1b83bd7b4ca6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@123 f048390d1f3455f34d6c2e291d516ddffefbf536 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@124 88c936e441f5d553cc16ca6bed4e1501361bea80 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@125 1ecb05f1b2f29ef475df03e0e10b0a8e624e2ad0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@126 d64633875e2b54ff7f5b993b3e30af2cb4206ec7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@127 6212a61d7abc8f3cd8e7842294f947721a2c7709 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@128 837894519c8360e52d3440829ab28ebc53fcb4e3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@129 300377fc33e64d7daefc48b4786d3dd064e47356 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@130 2f6d13d2eb84790f2f1ab3ee157e8519205d2b39 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@131 b4780f4ee2120d9efb89fbbe978851181ebd90ba svn:280a1989-90ef-4448-b869-2704929e1777/trunk@132 e16031e560d42f002f9ff7e4f9b46b10879a6206 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@133 daaa92dd4b6e6d42dd071d8b10513c904c05c3b7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@134 d7e01c9e5bdca67a26dd304d62e3bdaafe2d481b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@135 bfcaf9a66359ec2c4d74033a12aeb3fc30d162f7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@136 0d9d268ff45702e94e89d58d6ea2792a7cf744e3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@137 8420439b4bae699fe7679367acd3a225883243d4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@138 0ee160488d06bdf10277e79cc8ab788627e64131 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@139 1649988e3ac410b8cc6723cddb976adc896af46a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@140 23a00cec9d32f5037c50bed79181229cedb2b802 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@141 97f0531f7c21a847e3dc79f7f76c511cec34d32c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@142 00a03bbc9eeacd589b6f269a0980927e0ccbc100 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@144 f61484680d6d821b82b96a2a35c28a36b1638fea svn:280a1989-90ef-4448-b869-2704929e1777/trunk@145 603d2923e657db5068d77f0045d3effa6ecb2038 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@146 8c02baa2d95c8f955d737d1c539af8ae2b47849a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@147 a1da97f54693e0e162ef65c1e42c2afc5906be20 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@148 c5e55da127125e04b80b15933fd0ccd94a441ef1 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@149 95098790c54220a87fe296a60958f8a1970730ec svn:280a1989-90ef-4448-b869-2704929e1777/trunk@150 2bb500de12683b80e5ed87e17ff422565c06ae8d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@151 53737d0387c8485848d32f4ea331d273edf50400 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@152 a2119c07028f680f7e8979988ee8cd4b60828d85 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@153 80a7e618855ab9032ae79879fff72dda1cf2e2d9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@154 58b2e2212483ba90db840e799933d2645087f454 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@155 1b9ce94d58154f031cecad45ceaa8e655182a310 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@156 b3a32d7320ae50fbbf57f9b14db6a81d3c79cd99 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@157 f17c13400c68987a9978fc80a59a292c6be6514b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@159 cab589ae6cfb1976f338873f8d89afe622ff96a5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@160 4368e111e2a0a3188d3903b2dea7eeddbfaf269c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@161 9b9741998898ef5024cdf06e07d11505d7c5ae15 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@162 2825a90e94455947736ce2fb17dea06964f13cd9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@163 632068ca36ebfc02fec9e5901bedafd9c1d9ec1a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@164 9db75244a52c92653b9479c5730a5d29410b5fc4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@165 fe1a2365d4bf54f4d5280ab974be52068cf788d8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@166 5d0d34bc649ed37f40bc1db0d119b6941cb3b51a svn:280a1989-90ef-4448-b869-2704929e1777/trunk@167 eece38b0472ad0320901a43294c6681f834446e8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@168 5df5fc23bf48f3f0e4561e02ec11bc8d053e377e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@169 a022d942097994b0c675052bb159355fc0d165a0 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@170 62dbd4b5fb78d2c3c710d23771f37aa6403c07ff svn:280a1989-90ef-4448-b869-2704929e1777/trunk@172 31b2a650f313c9c0a6a2aa64f0cf8cb0d0a23ad3 svn:280a1989-90ef-4448-b869-2704929e1777/branches/zope@173 31b2a650f313c9c0a6a2aa64f0cf8cb0d0a23ad3 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@174 37d4a79f1c003036f7ac777c39174e63d9b0f20d svn:280a1989-90ef-4448-b869-2704929e1777/trunk@175 e547f5e92454fc830f0d98969a8e7acac0033e80 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@178 6ba6c58dda449209ce25b4bebb723cfe365f8d77 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@179 500e14bdb9bbd434676b993086f2240e34419efa svn:280a1989-90ef-4448-b869-2704929e1777/trunk@180 99a8d3c03fcc646659bb89ff0044b4eceaf94323 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@182 6e8bf0d535b31388eae7cb2534a3af9972b14f2e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@183 8be709dbb806c0a412b0c68657943e6a42dad627 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@184 9c1b571b39ac852850d9afcd907aced721fb330b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@185 cc52adb6773fb10c347a3fac4dfa46471a342bc7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@186 cf0d0598d1dbde25444ed7f2452f4e4b5237521b svn:280a1989-90ef-4448-b869-2704929e1777/trunk@187 dce03638f358cf42354fd8e5ce7e1a271d3092ca svn:280a1989-90ef-4448-b869-2704929e1777/trunk@188 ef7132bc7df21d9374dd91121731bfe0d8fa0bb9 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@190 3315cbe4f46e6760509979ee632fe9f838e9d5d7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@193 004c0c044772247fe96f414050f9679d1755ec65 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@194 28aa4e16f2282636acb2d82f2e7be355c98c8907 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@196 5e9289d4182c4269a925c70aecbf70eafd799a91 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@198 5e9289d4182c4269a925c70aecbf70eafd799a91 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@199 30a9bc80c33f7233a54c7c558501c61b0955dea4 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@200 649a41aa774c0d10894a1c8f0fe4685fa72c595e svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@201 649a41aa774c0d10894a1c8f0fe4685fa72c595e svn:280a1989-90ef-4448-b869-2704929e1777/trunk@202 63f44d74b7c6273d81d62f7a2d4e4c942e9371ba svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@203 08db709f2e4c9f1381a6dae523169ed5921d4fd7 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@204 3e69271bcf55b932991b2a43927cac83e2758cc5 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@205 88492f227bdc6a44aa295a19116977b6acd5582a svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@206 913c96b68af3d10d3793b73de1e470b898df7cd7 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@207 46f2beb1f589c9658c776d928f13172e677f0294 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@208 dee8ced42ea53bf3c6f66c3cea096cbfe1d93f9c svn:280a1989-90ef-4448-b869-2704929e1777/trunk@209 39d43afba7dd9b0294d52c615974544df857d409 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@210 91bdc9b5e454579488092f59fbd92ca43bc0e92c svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@211 64b6699c61b86b5a732252c773aefaba8ac47690 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@212 5ea7f12087f93a0d10622e7a0a3f94217f637fc0 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@213 5210290788a1181621f7ce13a46e125eb1e18bd8 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@214 d941520d2b708b5d583c785c0c281ca85b2832d9 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@215 461e189c3e9dfca1b8ab44db8f7121a47c9978bb svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@216 abb78effaba940bc5717c718f043942f0b5b888f svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@217 4aaf2b51da0314561691c4e1085e3922757a9d3a svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@218 27ea017a37c4989068fde92f50b072e9387727e7 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@219 f6f592a2e7b6177de4fc36261d4d2c603a66caa0 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@220 252a2849b88ac58a04344031e1e717282acd40c4 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@221 124b5d832e2fb17ff29e89c375c0896a2e0a1974 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@222 f5130a902ee9b8abf1e408ad0ff0e5ec78f5f8e3 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@223 53cf0ee16fe22cb238f071ea8bcd32314d25aaf8 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@224 c668061b6f76bc39ffaedbd33d8ab91ef26527a7 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@225 c30276c539f7453f5f593a513b7ed6e40164593f svn:280a1989-90ef-4448-b869-2704929e1777/trunk@226 b788b2d027eb00b3ae064e77debd1c9ad3e31bc4 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@227 1da0455c55371bd2ca4c8bd2779d61b7b1bc05b2 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@228 39a916143e0c6fa954a4af196cd443760bf97d11 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@229 3f628a6057f4285698c41e508d54f6fb8241f3d6 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@230 32c3f5b90395efeedad7a7239a7f9934670ad6d1 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@232 9a26aa02b491f345321147a487df5075ef86f0e6 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@233 1037341283d980cb53b9b566b830dbfb03a5e02d svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@234 361e09ac6aa526410043ca40ec6f891d4396ba3c svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@235 371c4cc61f4756f0eb4c84c53107b473e0b6eb2c svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@236 8719392536ca0c970d947f13a99680a133d46593 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@237 2f5b42c29587bf0d3486d43c1f5f9725fdf9d051 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@238 020dc443c6b44b6576ad6b441f7a627042214dac svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@239 81d7676b2cf4ddd3b92c08d031da377f38acdc0f svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@240 54a3fb8413c1e3d6e1220e31f80fcb4b7c30b946 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@241 80220df5730f41ffb21989e71dc32f365e5f2f76 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@243 b4cfb5de45c897c2f71a805b39e49f77dc9c2f52 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@244 92073671619ecf887e79a009d0cfa6f4bb243894 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@245 39689d7cbce3b67ffe7a35024973b97f3372d434 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@246 057d79259b209c0cb1c6784aa872b57036e5c167 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@247 ec934e11cb61be9feeeb943c5a44a719e2cc8c29 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@248 48d3fea1f5735419619e7141e65a4ac148eb9e4f svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@249 e92c5345020b3ac49413e96aa9c47ab133f653f5 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@250 1259c0704d77b8748d0e0fb2af594fda4b046fee svn:280a1989-90ef-4448-b869-2704929e1777/trunk@251 dc355c4fda996cf9353f95ccbc779006407f3a60 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@252 e6e990a053fdeb7e4d13421c0638af5cdfd0c929 svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@253 93041c73c46b16b64b5be1c440abb69b90caa9c9 ./pyke-1.1.1/doc/cheatsheets/from_svn/pyke.splicemap0000644000175000017500000000704311346504626021371 0ustar lambylambysvn:280a1989-90ef-4448-b869-2704929e1777/branches/zope@176 svn:280a1989-90ef-4448-b869-2704929e1777/branches/zope@173,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@175 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@203 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@201,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@202 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@207 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@206,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@205 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@211 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@210,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@209 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@215 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@212,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@214 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@220 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@218,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@219 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@223 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@220,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@222 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@227 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@225,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@226 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@234 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@228,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@233 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@245 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@235,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@244 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@252 svn:280a1989-90ef-4448-b869-2704929e1777/branches/pre_2to3@245,svn:280a1989-90ef-4448-b869-2704929e1777/trunk@251 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@200 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@196,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@199 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@205 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@202,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@204 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@209 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@205,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@208 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@214 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@209,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@213 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@219 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@214,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@217 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@222 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@219,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@221 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@226 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@222,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@224 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@233 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@226,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@232 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@244 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@233,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@243 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@251 svn:280a1989-90ef-4448-b869-2704929e1777/trunk@244,svn:280a1989-90ef-4448-b869-2704929e1777/branches/release_1.0@250 ./pyke-1.1.1/doc/cheatsheets/web.upload0000644000175000017500000000051211346504626016646 0ustar lambylamby./make_doc_tarball /tmp/pyke.html.tar scp /tmp/pyke.html.tar mtnyogi@web.sourceforge.net: ssh -t mtnyogi,pyke@shell.sourceforge.net create cd /home/groups/p/py/pyke tar xzf ~/pyke.html.tar chmod g+w html mv htdocs htdocs.old; mv html htdocs rm -rf htdocs.old cd rm pyke.html.tar shutdown now rm /tmp/pyke.html.tar ./pyke-1.1.1/doc/cheatsheets/r2w_restindex0000644000175000017500000000311011346504626017402 0ustar lambylambyinclude: yes # include this page in index? format: rest # or html file-extension: html # setting in index.txt applies to all files in dir template: filepath # in index.txt, applies to whole dir and subdirs template-encoding: ?? # guessed if not specified index-file: alt_file # only for index.txt to specify alt main page for dir section: None # for index.txt what section to appear in one level up link-title: text # text in link from index page, page title is default page-title: text # needed for index.txt and html pages page-description:\n # rest format, default '' text /description crumb: name # default page title, or filename if no page title # in index page target: filename # default is filename minus .txt plus file_extension build: yes # build this file? initialheaderlevel: 1 # starting header level for rest headers encoding: ?? # default is to leave 'subtools' (docutils) to guess it output-encoding: ?? # setting in index applies to whole dir and subdirs sectionlist: name... # only in index, default is single 'default' section-title: # only in index, default is section name (in Title-Case) section-pages: # only in index, order of pages, default is arbitrary section-description:\n # only in index, default is '' tags: # list of keywords that applies to the page, default () file: filename # additional file to copy to from source to target plugins: # list of plugins to apply to page ./pyke-1.1.1/doc/cheatsheets/making_a_release0000644000175000017500000000437011365357370020065 0ustar lambylambyAll code/doc changes should be committed in the release_1 branch. To get the hg revision of the previous release: $ hg tags Do "hg log" to see what's changed: $ hg log -v -r .:48 Prepend notes to: RELEASE_NOTES-1.txt FILES TO CHANGE: - README.txt Read through to see if anything has changed. The following lines will always change: - line 3: Version: 1.0.2 - line 41: ... pyke-1.0.3.zip - line 44: ... pyke3-1.0.3.zip - line 59: Add any new examples - setup.py - line 7: version = "1.0.2", - line 37: "http://downloads.sourceforge.net/pyke/pyke-1.0.2.zip", - pyke/__init__.py - line 3: version = '1.0.2' - line 5: compiler_version = 1 (does this need to be incremented?) - line 7: target_pkg_version = 1 (does this need to be incremented?) - doc/source/index.txt - line 43: Release 1.0 RUN MAKE_RELEASE This can be re-run if errors are encountered. $ ./make_release 1.0.3 > /tmp/make_release.out 2>&1 & $ tail -f /tmp/make_release.out CHECK OUT WEB SITE with the browser, go to http://pyke.sourceforge.net UPDATE RELEASE FILES ON SOURCEFORGE: In the browser go to: Project Admin -> File Manager Go into the release directory left click on RELEASE_NOTES-1.txt check the "Release Note" checkbox click Save left click on pyke-1.0.2.tar.gz set the "Release Notes for this file" (leave the "Label" blank) click "Select All" under Platform click Save left click on pyke3-1.0.2.tar.gz set the "Release Notes for this file" (leave the "Label" blank) click Save ADD NEW RELEASE TO SOURCEFORGE TRACKERS: Project Admin -> Feature Settings -> Manage (on Tracker line) Do: Bugs, Support Requests, and Patches Add release through "Add/Update Groups" UPDATE PYPI LINK: $ python setup.py register CREATE NEWS ANNOUNCEMENTS On Sourceforge: Project Admin -> Feature Settings -> Submit (on Project News line) On Google Groups: http://groups.google.com/group/pyke TO TEST: $ cd ~/python $ rm -rf pyketest $ virtualenv --no-site-packages pyketest $ cd pyketest $ bash $ unset PYTHONPATH $ source bin/activate ./pyke-1.1.1/doc/cheatsheets/link_and_spell_checks0000644000175000017500000000072511346504626021112 0ustar lambylambyLink checks: In the doc directory: $ checklink -sr file:///home/bruce/python/workareas/pyke-hg/r1_working/doc/html/index.html > checklink.out 2>&1 & $ tail -f checklink.out Then check the checklink.out file. checklink is in the w3c-linkchecker ubuntu package. Spell checks: Options: --dont-backup --suggest --ignore-case In the doc/source directory: $ find . -name '*.txt' -execdir aspell --warn -c {} \; ./pyke-1.1.1/doc/cheatsheets/rest0000644000175000017500000001163711346504626015575 0ustar lambylambySee: http://docutils.sourceforge.net/rst.html Paragraphs: Separated by blank lines. Indented paragraphs are treated as quoted paragraphs. Characters: *italics* **bold** ``fixed-space literal`` -- text not checked for further markup -- spaces preserved, but not line breaks To turn off special meaning, either \* or ``*``. Lists: Blank line required before first item and after last item, optional between items. Numbered: 1. foobar or 1) foobar or (1) foobar or A. foobar or a. foobar or I. foobar or i. foobar or #. foobar -- to auto number use indenting for nested lists (must match first char in outer list) - need blank line before and after nested list can have indented paragraphs (must match first char in outer list) first number is starting number for list Bullet: * foobar or + foobar or - foobar Definition Lists: term1 definition1 term2 definition2 blank lines not allowed between term and definition Preformatting: End prior paragraph with :: (shows as single :, unless preceeded by a space) Or use :: on a line by itself. All indented text after that is preformatted (left alone). OR Use > (per-line quoting) at the start of each line (> is seen in output). These don't need to be indented (but are rendered indented slightly). Use >>> for doctests. These go until the next blank line. Line Blocks: | blah blah -- the | causes a line break | blah blah blah blah -- must use space instead of | for line continuation that will wrap with first line. Field Lists: Creates a two-column table to line up field name with descriptions. :Authors: blah blah blah blah more blah -- blank line causes a line break Option Lists: -a blah blah -b file blah blah blah blah must be at least two spaces between option and description Tables: +---------+---------+ | Header1 | Header2 | +=========+=========+ | blah | blah | +---------+---------+ Can omit boundary lines (vertical or horizontal) colspan and rowspan. OR ===== ====== ====== colspan blah ------------ ------ A B C ===== ====== ====== blah blah blah blah blah blah blah blah blah ===== ====== ====== Sections: Underline headers with -, = or ~. Underline only is distinct from overline/underline. Underlines and overlines must be at least as long as the header text. The text for overline/underline style (but not underline only) may be inset for aesthetics. Document Title/Subtitle: A unique adornment style at the beginning of the document. Use a second unique adornment style immediately after that for the subtitle (if any). Admonitions: attention, caution, danger, error, hint, important, note, tip, warning .. :: -- this blank line is optional body1 body2 or .. admonition:: title -- this blank line seems to be required, I guess to separate the body from a continuation of the title? body1 body2 Images: .. image:: images/foobar.png Can immediately follow with attributes: .. image:: images/foobar.png :height: 100 :width: 200 :scale: 50 :align: top|middle|bottom|left|center|right :target: url (to turn the image into a link) :class: text :alt: alternate text .. figure:: images/foobar.png :figwidth: integer or "image" :figclass: text :align: left|center|right caption (simple paragraph) legend (all remaining elements) Horizontal Rule: ---------------- -- 4 or more repeated punctuation chars Linking: Name_ .. _Name: link -- external link .. _Name: -- internal link target `blah blah`__ -- indirect link __ Name_ -- link __ to what Name_ links to `section title`_ -- section titles as links don't require a link definition or `Name `_ [1]_ -- footnote reference .. [1] blah blah -- footnote [#]_ -- autonumbered footnotes .. [#] blah blah [*]_ -- auto-symbol footnotes .. [*] blah blah [CITATION]_ -- citation reference .. [CITATION] blah blah Substitution References: The |name| blah... .. |name| image:: foobar.jpg Comments: .. blah blah -- that doesn't match any format above blah blah ./pyke-1.1.1/doc/cheatsheets/r2w_values0000644000175000017500000000525711346504626016712 0ustar lambylambytitle - title of the page body - full body of the page breadcrumbs - list of (crumb_link, crumb) sections - {section_name: { title: ..., description: ..., pages: [{target:, section:, link-title:, page-description:, crumb:, namespace:, subdir:}] }} .sortpages(sortorder, section=True) where sortorder is either 'link-title' or a python function. NOTE: The default section_name is keyed by None. default_section - exactly the same as sections[None] pagename - filename of the page (not the full path). pagepath - full path from top level to current document. encoding - the orginal encoding for the document. output_encoding - may be 'none' or 'unicode' final_encoding - may be None path_to_root - path from current document to top level site directory (ends with '/', e.g., '../../') sectionlist - list of subsections in this directory rest_dict - full dict of parts returned by docutils html_parts function doc - everything that has been printed so far (StringIO instance) stdout - real sys.stdout modified - time the page was last modified (secs since epoch) modtime - string representing last modified time, created using time.ctime(modified) template_file - path to file template_encoding indexpage - information dict for the index page for this section, not included in 'sections' value indextree - pages that have been rendered so far (above the one currently rendered) thispage - reference into indextree for this page sidebar - standard function minibar - standard function print_crumbs - standard function print_details - standard function for printing indexes section_contents- standard function Processor - the actual processor doing the work, be careful with it! page_description - string include - standard function for including files within templates globalValues - dict, initially empty, can store stuff here from one page to the next... current_dir - relative to top level directory source_file - source filepath target_dir - absolute path to target directory, BUT not if 'target' specifies a different directory, then use os.path.dirname(target_file). full_page_url - full url, starting with '/', won't work for relative references target_file - full output filepath of the page being rendered ./pyke-1.1.1/doc/cheatsheets/backups0000644000175000017500000000040711346504626016241 0ustar lambylambyhttps://sourceforge.net/apps/trac/sourceforge/wiki/Mercurial#Backups from emachine: #rsync -av --delete pyke.svn.sourceforge.net::svn/pyke/* /backups/sourceforge/pyke rsync -av --delete pyke.hg.sourceforge.net::hgroot/pyke/* /backups/sourceforge/pyke-hg ./pyke-1.1.1/doc/cheatsheets/r2w_std_funs0000644000175000017500000000152311346504626017230 0ustar lambylambyUse <# ... #> for all of these! http://www.voidspace.org.uk/python/rest2web/functions.html print_details -- prints index for all pages within a section section_contents -- returns list of (url, link title, description) for an individual section print_crumbs -- prints breadcrumbs minibar -- prints simple sidebar that shows links to all pages in the current directory (excluding index page) sidebar -- generator to print indexes from indextree include(filename) -- filename may use all normal template values formattime(t, format="%a, %d %b %Y %H:%M:%S") -- default format: 'Fri, 27 Jan 2006 09:53:52'. the time is always understood as a local time. ./pyke-1.1.1/doc/source/0000755000175000017500000000000011425360453013661 5ustar lambylamby./pyke-1.1.1/doc/source/logic_programming/0000755000175000017500000000000011425360453017360 5ustar lambylamby./pyke-1.1.1/doc/source/logic_programming/rules/0000755000175000017500000000000011425360453020512 5ustar lambylamby./pyke-1.1.1/doc/source/logic_programming/rules/backward_chaining.txt0000644000175000017500000002560711346504626024707 0ustar lambylamby.. $Id: backward_chaining.txt 9f7068449a4b 2010-03-08 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Backward Chaining page-description: Explanation of *backward-chaining* rules, including how *backward-chaining* and *backtracking* works. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: backward_chaining.txt 9f7068449a4b 2010-03-08 mtnyogi $ /uservalues ============================================= Backward Chaining ============================================= Backward chaining rules_ are processed when your program asks Pyke a question_ (i.e., asks Pyke to prove_ a specific *goal*). Pyke will only use activated_ `rule bases`_ to do the proof. Overview of "Backward-Chaining" =============================== To do backward-chaining, Pyke finds rules whose *then* part matches the *goal* (i.e., the question). Once it finds such a rule, it tries to (recursively) prove all of the subgoals in the *if* part of that rule. Some of these subgoals are matched against facts, and others are subgoals for other backward-chaining rules. If all of the subgoals can be proven, the rule succeeds and the original goal is proven. Otherwise, the rule fails, and Pyke tries to find another rule whose *then* part matches the goal, and so on. So Pyke ends up linking (or *chaining*) the *if* part of the first rule to the *then* part of the next rule. Reviewing: #. Pyke starts by finding a rule whose *then* part matches the goal. #. Pyke then proceeds to process the *if* part of that rule. #. Which may link (or chain) to the *then* part of another rule. Since Pyke processes these rules from *then* to *if* to *then* to *if* in the reverse manner that we normally think of using rules, it's called *backward* chaining. To make this more clear, Pyke has you write your backward-chaining rules upside down by writing the *then* part first (since that's how it is processed). "Use", "When" Rather than "Then", "If" ====================================== But *then-if* rules sound confusing, so Pyke uses the words **use** and **when** rather than **then** and **if**. You can then read the rule as "use" this statement "when" these other statements can be proven. .. note:: Unlike the *assert* clause in forward-chaining_ rules, Pyke only allows one statement in the *use* clause. Example ================= Consider this example:: 1 direct_father_son 2 use father_son($father, $son, ()) 3 when 4 family2.son_of($son, $father) 5 grand_father_son 6 use father_son($grand_father, $grand_son, (grand)) 7 when 8 father_son($father, $grand_son, ()) 9 father_son($grand_father, $father, ()) 10 great_grand_father_son 11 use father_son($gg_father, $gg_son, (great, $prefix1, *$rest_prefixes)) 12 when 13 father_son($father, $gg_son, ()) 14 father_son($gg_father, $father, ($prefix1, *$rest_prefixes)) 15 brothers 16 use brothers($brother1, $brother2) 17 when 18 father_son($father, $brother1, ()) 19 father_son($father, $brother2, ()) 20 check $brother1 != $brother2 21 uncle_nephew 22 use uncle_nephew($uncle, $nephew, $prefix) 23 when 24 brothers($uncle, $father) 25 father_son($father, $nephew, $prefix1) 26 $prefix = ('great',) * len($prefix1) 27 cousins 28 use cousins($cousin1, $cousin2, $distance, $removed) 29 when 30 uncle_nephew($uncle, $cousin1, $prefix1) 31 father_son($uncle, $cousin2, $prefix2) 32 $distance = min(len($prefixes1), len($prefixes2)) + 1 33 $removed = abs(len($prefixes1) - len($prefixes2)) .. note:: These rules_ draw the same conclusions as the forward-chaining_ example_, with the addition of the *brothers*, *uncle_nephew* and *cousins* rules. We can draw something similar to a function call graph with these rules: .. figure:: ../../images/bc_rules.png :width: 509 :height: 583 :scale: 100 :align: center Example Rules These rules_ are not used until you ask Pyke to prove_ a goal. The easiest way to do this is with *some_engine.prove_1_goal* or *some_engine.prove_goal*. Prove_1_goal_ only returns the first proof found and then stops (or raises ``pyke.knowledge_engine.CanNotProve``). Prove_goal_ returns a context manager for a generator that generates all possible proofs (which, in some cases, might be infinite). Both functions return the `pattern variable`_ variable bindings, along with the plan_. Backtracking with Backward-Chaining Rules ========================================= For this example, these are the starting set of ``family2`` facts:: 1 son_of(tim, thomas) 2 son_of(fred, thomas) 3 son_of(bruce, thomas) 4 son_of(david, bruce) And we want to know who fred's nephews are. So we'd ask ``uncle_nephew(fred, $nephew, $prefix)``. Here are the steps (in parenthesis) in the inferencing up until the first failure is encountered (with the line number from the example preceding each line):: (1) 22 use uncle_nephew(fred, $nephew, $prefix) 24 brothers(fred, $father) (2) 16 use brothers(fred, $brother2) 18 father_son($father, fred, ()) (3) 2 use father_son($father, fred, ()) 4 family2.son_of(fred, $father) matches fact 2: son_of(fred, thomas) 19 father_son(thomas, $brother2, ()) (4) 2 use father_son(thomas, $son, ()) 4 family2.son_of($son, thomas) matches fact 1: son_of(tim, thomas) 20 check fred != tim 25 father_son(tim, $nephew, $prefix1) (5.1) 2 use father_son(tim, $son, ()) 4 family2.son_of($son, tim) => FAILS (5.2) 6 use father_son(tim, $grand_son, (grand)) 8 father_son(tim, $grand_son, ()) 2 use father_son(tim, $son, ()) 4 family2.son_of($son, tim) => FAILS (5.3) 11 use father_son(tim, $gg_son, (great, $prefix1, *$rest_prefixes)) 13 father_son(tim, $gg_son, ()) 2 use father_son(tim, $son, ()) 4 family2.son_of($son, tim) => FAILS Each rule invocation is numbered (in parenthesis) as a step number. Step 5 has tried 3 different rules and they have all failed (5.1, 5.2 and 5.3). If you think of the rules as functions, the situation now looks like this (the step numbers that succeeded circled in black, and steps that failed circled in red): .. figure:: ../../images/bc_backtracking.png :width: 590 :height: 465 :scale: 100 :align: center We Need to Backtrack! At this point, Pyke has hit a dead end and must backtrack. The way that backtracking proceeds is to go back up the list of steps executed, combining the steps from all rules into one list. Thus, when step 5 fails, Pyke backs up to step 4 and tries to find another solution to that step. If another solution is found, Pyke proceeds forward again from that point. If no other solutions are found, Pyke backs up another step. When Pyke goes back to step 4, the next solution binds ``$son`` to ``fred``. This fails the subsequent check in the ``brothers`` rule:: 20 check $brother1 != $brother2 And so Pyke goes back to step 4 once again. The next solution binds ``$son`` to ``bruce``. This succeeds for ``brother`` and is passed down to ``father_son`` which returns ``david`` as ``fred's`` nephew. Further backtracking reveals no other solutions. Backtracking Summary -------------------- Thus we see: #. The backtracking_ algorithm: "**fail** goes *up* (or *back*) while **success** goes *down* (or *forward*)" is not limited to the steps within a *single* rule's ``when`` clause; but includes the *entire* chain of inferencing from the very start of trying to prove the top level goal. #. This execution model is not available within traditional programming languages like Python. #. The ability to go back to *any* point in the computation to try an alternate solution is where backward-chaining systems get their power! .. This code is hidden. It will add '' to sys.path, change to the doc.examples directory and store the directory path in __file__ for the code section following: >>> import sys >>> if '' not in sys.path: sys.path.insert(0, '') >>> import os >>> os.chdir("../../../examples") >>> __file__ = os.getcwd() Running the Example ======================== >>> from pyke import knowledge_engine >>> engine = knowledge_engine.engine(__file__) >>> engine.activate('bc_related') Nothing happens this time when we activate the rule base, because there are no forward-chaining rules here. We want to ask the question: "Who are Fred's nephews?". This translates into the Pyke statement: ``bc_related.uncle_nephew(fred, $v1, $v2)``. .. note:: Note that we're using the name of the rule base, ``bc_related`` rather than the fact base, ``family2`` here; because we expect this answer to come from the ``bc_related`` rule base. This is 'bc_related', 'uncle_nephew', with ('fred',) followed by 2 pattern variables as arguments: >>> from __future__ import with_statement >>> with engine.prove_goal('bc_related.uncle_nephew(fred, $nephew, $distance)') as gen: ... for vars, no_plan in gen: ... print vars['nephew'], vars['distance'] david () .. _example: forward_chaining.html#example ./pyke-1.1.1/doc/source/logic_programming/rules/forward_chaining.txt0000644000175000017500000003625311346504626024574 0ustar lambylamby.. $Id: forward_chaining.txt 9f7068449a4b 2010-03-08 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Forward Chaining page-description: Explanation of *forward-chaining rules* and how *forward-chaining* works. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: forward_chaining.txt 9f7068449a4b 2010-03-08 mtnyogi $ /uservalues ================== Forward Chaining ================== Forward chaining rules_ are processed automatically as each `rule base`_ is activated_. When a rule base is activated_, all of its forward-chaining rules_ are run in the order that they appear in the `.krb file`_ for that rule base. Overview of Forward-Chaining ============================= To do forward-chaining, Pyke finds rules whose *if* clause matches Pyke's list of already known facts (the *if* clause may match, or *succeed*, multiple time; see backtracking_). Each time a rule succeeds, it *fires* this rule, which adds the facts in the *then* clause of that rule to the list of already known facts. These new facts may fire other forward-chaining rules by matching their *if* clause. This can go on to any depth. So Pyke ends up linking (or *chaining*) the *then* clause of the first rule to the *if* clause of the next rule. .. note:: Forward-chaining continues until no more rules_ can be fired. Reviewing ---------- #. Pyke starts with the *if* clause of the first rule and checks to see if it matches the known facts. #. If so, it proceeds to the *then* clause of that rule (*firing* the rule). #. Which may link (or *chain*) to the *if* clause of another rule. Since Pyke processes these rules from *if* to *then* to *if* to *then* in the manner that we normally think of using rules, it's called *forward* chaining. "Foreach", "Assert" Rather than "If", "Then" ============================================ Finally, since the statements within the *if* clause of the rule contain patterns_; they may each match several facts. In this case, the rule will succeed and be fired multiple times. The statements in the *then* clause of the rule also contain patterns. Each time the rule is fired, the pattern variables within the *then* statements are bound to different values so that different facts are asserted. To avoid confusion, Pyke uses the words **foreach** and **assert** rather than **if** and **then** for forward-chaining rules. This is to suggest that "for each" combination of facts matching the first list of statements, the rule is fired to "assert" the facts in the second list of statements. .. note:: The use of **foreach** and **assert** identifies the rule as a forward-chaining rule. Example ======= This example will figure out the paternal ancestry of individuals given a list of starting statements about who the sons of each father are. (Daughters and mothers are omitted to keep the example brief). These facts are stored in a `fact base`_ called ``family1`` as ``son_of(son, father)``:: 1 son_of(david, bruce) 2 son_of(bruce, thomas) 3 son_of(thomas, frederik) 4 son_of(frederik, hiram) We want to derive ``father_son`` relationships of the following form:: father_son($father, $son, $prefix) where :$son: is the name of the son (e.g., david) :$father: is the name of the father (e.g., bruce) :$prefix: is a tuple of prefixes before the 'father' and 'son' titles to indicate the number of generations (e.g., ``()`` for direct father_son relationships, ``(grand)``, ``(great, grand)``, etc). This is done using three forward-chaining rules. Each rule is presented as a separate step: - `Step 1: Direct_father_son`_ - Step 1 demonstrates the use of `pattern matching`_ to transfer values from one statement within the rule to another statement. - `Step 2: Grand_father_son`_ - Step 2 demonstrates backtracking_ within the premises_ of a forward-chaining rule. Understanding this will help you to understand `backward-chaining rules`_. - `Step 3: Great_grand_father_son`_ - Step 3 demonstrates a recursive forward-chaining rule. Finally, you will be shown how to `Run the Example`_ yourself. Step 1: Direct_father_son ========================= First we need to establish the direct father_son relationships (those whose ``$prefix`` is ``()``):: 1 direct_father_son 2 foreach 3 family1.son_of($son, $father) 4 assert 5 family1.father_son($father, $son, ()) The Use of Pattern Variables ---------------------------- This demonstrates how `pattern variables`_ are used to transfer values from one statement within a rule into another statement within the rule. This rule has a single statement in its ``foreach`` clause (on line 3). This statement matches all four ``son_of`` facts, so the rule succeeds four times; but with different bindings for the ``$son`` and ``$father`` pattern variables. This causes different facts to be asserted when the same ``assert`` clause (on line 5) is run four times; because each time line 5 is run, the values for ``$son`` and ``$father`` are transferred from the statement on line 3 to the statement on line 5. When the rule fires matching line 3 to:: 1 son_of(david, bruce) It runs line 5 to assert:: 5 father_son(bruce, david, ()) And when the rule fires a second time matching line 3 to:: 2 son_of(bruce, thomas) It runs line 5 a second time to assert:: 6 father_son(thomas, bruce, ()) The rule fires twice more for the remaining ``son_of`` facts, asserting two more ``father_son`` relationships. So this rule adds a total of four new facts:: 5 father_son(bruce, david, ()) 6 father_son(thomas, bruce, ()) 7 father_son(frederik, thomas, ()) 8 father_son(hiram, frederik, ()) Step 2: Grand_father_son ======================== Now we want to add grand son-father relationships. We have a new rule for this:: 6 grand_father_son 7 foreach 8 family1.father_son($father, $grand_son, ()) 9 family1.father_son($grand_father, $father, ()) 10 assert 11 family1.father_son($grand_father, $grand_son, (grand)) The Use of Backtracking ----------------------- The ``grand_father_son`` rule_ is run for all combinations of ``father_son`` facts_ that satisfy the two ``foreach`` statements_ (on lines 8 and 9) and asserts_ a ``(grand)`` ``father_son`` statement (on line 11) for each combination. This rule is a good example for backtracking_ and will help later in your understanding of backtracking with backward-chaining_. So let's follow the backtracking in the execution of this rule. The ``foreach`` clause has two statements (on lines 8 and 9) in it that are both looking for ``father_son`` facts with a prefix of ``()``:: 8 family1.father_son($father, $grand_son, ()) 9 family1.father_son($grand_father, $father, ()) These will be matched to the following ``family1`` facts (facts 5 through 8):: 5 father_son(bruce, david, ()) 6 father_son(thomas, bruce, ()) 7 father_son(frederik, thomas, ()) 8 father_son(hiram, frederik, ()) Pyke starts at the top of the list of premises and looks for a match for the first premise (on line 8). This matches fact 5, so the first premise succeeds, binding ``$father`` to ``bruce``:: 8 family1.father_son($father, $grand_son, ()) => fact 5, SUCCESS 9 family1.father_son($grand_father, $father, ()) *Success* means go *down*, so Pyke goes to the next premise on line 9. This succeeds with fact 6 (because ``$father`` is bound to ``bruce``):: 8 family1.father_son($father, $grand_son, ()) => fact 5 9 family1.father_son($grand_father, $father, ()) => fact 6, SUCCESS *Success* means go *down*, but Pyke is at the end of the list of premises, so the *rule* succeeds and Pyke fires the rule to assert:: 9 father_son(thomas, david, (grand)) Since this is a forward-chaining rule, Pyke wants to get *all* of the answers from it that it can, so it continues as if it had a failure (i.e., as if it's not happy with this answer). .. note:: You'll see later that Pyke doesn't do this automatically with backward-chaining_ rules. So Pyke *fails* back *up* to the second premise and looks for another ``father_son`` after fact 6 with ``bruce`` as the first argument. This fails:: 8 family1.father_son($father, $grand_son, ()) => fact 5 9 family1.father_son($grand_father, $father, ()) => FAILS *Fail* means go *up*, so Pyke goes up to the first premise and looks for another ``father_son`` after fact 5, which succeeds for fact 6, binding ``$father`` to ``thomas``:: 8 family1.father_son($father, $grand_son, ()) => fact 6, SUCCESS 9 family1.father_son($grand_father, $father, ()) *Success* means go *down*, so Pyke goes down to the second premise which succeeds for fact 7:: 8 family1.father_son($father, $grand_son, ()) => fact 6 9 family1.father_son($grand_father, $father, ()) => fact 7, SUCCESS *Success* means go *down*, but Pyke is at the end of the list of premises, so the *rule* succeeds and Pyke fires the rule to assert:: 10 father_son(frederik, bruce, (grand)) Then Pyke *fails* back *up* to the second premise, and continues looking for another match after fact 7. This fails:: 8 family1.father_son($father, $grand_son, ()) => fact 6 9 family1.father_son($grand_father, $father, ()) => FAILS *Fail* means go *up*, so Pyke goes back to the first premise and continues looking for another match after fact 6. (Since fact 7 is just like the last case, we'll skip matching fact 7 and go straight to the last fact, fact 8). The match to fact 8 succeeds, binding ``$father`` to ``hiram``:: 8 family1.father_son($father, $grand_son, ()) => fact 8, SUCCESS 9 family1.father_son($grand_father, $father, ()) *Success* means go *down*, so Pyke goes to the second premise and looks for a ``father_son`` for ``hiram``. This fails:: 8 family1.father_son($father, $grand_son, ()) => fact 8 9 family1.father_son($grand_father, $father, ()) => FAILS *Fail* means go *up*, so Pyke goes back up to the first premise and looks for another match after fact 8. There are no more facts, so this fails:: 8 family1.father_son($father, $grand_son, ()) => FAILS 9 family1.father_son($grand_father, $father, ()) *Fail* means go *up*, but Pyke is at the top of the list of premises, so the *rule* fails and Pyke is done processing it. .. important:: Note that the *last* statement in the ``foreach`` clause may *succeed* multiple times (which fires the ``assert`` clause multiple times). But the *first* statement in the ``foreach`` clause may only *fail* once. When that happens, the whole rule fails and the show's over for this rule! So running the ``grand_father_son`` rule results in addition of these three facts:: 9 father_son(thomas, david, (grand)) 10 father_son(frederik, bruce, (grand)) 11 father_son(hiram, thomas, (grand)) (this is the one we skipped) Step 3: Great_grand_father_son ============================== Finally, we want to add great(...) grand son-father relationships. We have a final rule for this:: 12 great_grand_father_son 13 foreach 14 family1.father_son($father, $gg_son, ()) 15 family1.father_son($gg_father, $father, ($prefix1, *$rest_prefixes)) 16 assert 17 family1.father_son($gg_father, $gg_son, (great, $prefix1, *$rest_prefixes)) .. note:: Note how the $prefixes for the statement on line 15 are specified as ``($prefix1, *$rest_prefixes)``, rather than just ``$prefix``. This is done so that it does *not* match ``()``. (But it will still match ``(grand)`` by binding ``$rest_prefixes`` to ``()``). This is the only rule that can be recursive. As this rule asserts_ new facts_, those facts may be used by the same rule (by matching the statement on line 15) to produce even more great, great, ... ``father_son`` relationships. Recursive Rules --------------- Running this rule normally will assert the following two facts:: 12 father_son(frederik, david, (great, grand)) 13 father_son(hiram, bruce, (great, grand)) But, since these facts may also be used by the same rule (on line 15), Pyke checks each one by trying to run the rule again just for that new fact. Trying this for the first new fact: ``father_son(frederik, david, (great, grand))`` fails to find anything because ``david`` is not a father. Trying this for the second new fact: ``father_son(hiram, bruce, (great, grand))`` results in one more new fact:: 14 father_son(hiram, david, (great, great, grand)) Now this last new fact is tried again with this rule, which fails again because ``david`` is not a father. So at this point Pyke is finished with this rule. The rule ended up firing three times, asserting:: 12 father_son(frederik, david, (great, grand)) 13 father_son(hiram, bruce, (great, grand)) 14 father_son(hiram, david, (great, great, grand)) Running the Example =========================== .. This code is hidden. It will add '' to sys.path, change to the doc.examples directory and store the directory path in __file__ for the code section following: >>> import sys >>> if '' not in sys.path: sys.path.insert(0, '') >>> import os >>> os.chdir("../../../examples") >>> __file__ = os.getcwd() These rules could be run as follows: >>> from pyke import knowledge_engine >>> engine = knowledge_engine.engine(__file__) >>> engine.activate('fc_related') # This is where the rules are run! >>> engine.get_kb('family1').dump_specific_facts() father_son('bruce', 'david', ()) father_son('thomas', 'bruce', ()) father_son('frederik', 'thomas', ()) father_son('hiram', 'frederik', ()) father_son('thomas', 'david', ('grand',)) father_son('frederik', 'bruce', ('grand',)) father_son('hiram', 'thomas', ('grand',)) father_son('frederik', 'david', ('great', 'grand')) father_son('hiram', 'bruce', ('great', 'grand')) father_son('hiram', 'david', ('great', 'great', 'grand')) .. _Run the Example: `Running the Example`_ ./pyke-1.1.1/doc/source/logic_programming/rules/index.txt0000644000175000017500000001232511346504626022371 0ustar lambylamby.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Rules page-description: Explanation of *rules*, *forward-chaining* and *backward-chaining*. /description section-pages: , forward_chaining, backward_chaining format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ======= Rules ======= Conceptually, a rule is very simple:: if A B C then D E Meaning, "if A, B *and* C are true, then D and E are also true". These are often called *if-then* rules. .. admonition:: And what are A, B, C, D and E? They are simply statements_. Specifically, they are generalized statements containing patterns_ as arguments. You'll see more about this later. Premises and Conclusions ================================= Rules have two parts to them: an **if** part (containing a list of statements called the *premises*), and a **then** part (containing a list of statements called the *conclusions*). .. note:: Pyke doesn't use the words **if** and **then**, as you'll see shortly. Each of these **if** and **then** parts contain one or more facts_ or goals_ which are just generalized statements_ (meaning statements with patterns_ as arguments). Processing the Premises Within the *If* Clause ============================================== Because each premise with the *if* clause is a generalized statement, the premise is pattern matched to known facts. This means that it may match more than one fact. Pyke tries all combinations of matching facts through a process called *backtracking*. This will cause the same rule to potentially succeed multiple times, once for each unique combination of matching facts. Backtracking ------------ Note that while processing the list of premises within a rule's ``if`` clause: * If Pyke succeeds at proving a premise: * Pyke will proceed *down* to the next premise in the list. * Trying to proceed *down* from the *last* premise in the list (when it succeeds) causes the rule to *succeed*. * If Pyke fails at proving a premise: * Pyke will back *up* to the prior premise in the list and try to find another solution for it. The process starts over at the prior premise, going back down or further up the list depending on whether another solution can be found to the prior premise or not. * Trying to back *up* from the *first* premise in the list (when it fails) causes the rule to *fail*. .. figure:: ../../images/backtracking.png :alt: Backtracking Flow Diagram :align: center Backtracking Flow Diagram Thus, execution within each rule's ``if`` clause proceeds both backwards and forwards, up and down the list of premises, depending on whether each premise succeeds or fails. The process of backing up within the ``if`` clause to try alternate solutions is called *backtracking*. Inferencing =========== Rules are specified individually within a `rule base`_. They are not nested or explicitly linked to each other. So Pyke must automatically figure out how to combine these rules to accomplish some larger task. This is called *inferencing* and there are two different approaches that Pyke uses: - All forward-chaining_ rules are processed when a `rule base`_ is activated_. - Forward-chaining rules may assert_ new facts, and activate_ more specific `rule bases`_. - Backward-chaining_ rules are processed when your program asks Pyke to prove_ a specific *goal* (i.e., asks Pyke a question_). - These rules are designed to answer a question rather than assert new facts or activate more specific rule bases. - They also have the ability to assemble Python functions into a customized call graph or program, called a plan_, to meet a specific need. .. note:: Forward-chaining and backward-chaining rules may both be included within the same `rule base`_. Pyke knows the difference between forward-chaining and backward-chaining rules because they have different syntax__. .. __: ../../pyke_syntax/krb_syntax/index.html ./pyke-1.1.1/doc/source/logic_programming/plans.txt0000644000175000017500000002247211346504626021251 0ustar lambylamby.. $Id: plans.txt 4670da845e46 2010-03-05 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Plans page-description: Explanation of *plans* and automatic program generation. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: plans.txt 4670da845e46 2010-03-05 mtnyogi $ /uservalues ============================================= Plans and Automatic Program Generation ============================================= Once you understand how backward-chaining_ works, it is relatively easy to do automatic program generation. Adding Plans to Backward-Chaining Rules ============================================ The way this is done is by attaching Python functions to backward-chaining_ rules_. These functions are written in the *with* clause at the end of each rule_ in the `.krb file`_. They don't affect how the rules_ run to prove a goal, but are gathered up to form a call graph that is returned along with the `pattern variable`_ bindings that prove_ the top-level goal. Example =============== Consider a small `rule base`_ to construct programs to transfer money between bank accounts. Each *from_acct* and *to_acct* takes one of two forms: #. (name, account_type) - This is a local account with this bank. - Example: ('bruce', 'checking'). #. (bank, name, account_type) - This is a foreign account with another bank. - Example: ('my_other_bank', 'bruce', 'checking'). At least one of the bank accounts must be a local account. Here's the example rule base:: 1 transfer1 2 use transfer($from_acct, $to_acct) taking (amount) 3 when 4 withdraw($from_acct) 5 $$(amount) 6 deposit($to_acct) 7 $$(amount) 8 transfer2 9 use transfer($from_acct, $to_acct) taking (amount) 10 when 11 transfer_ach($from_acct, $to_acct) 12 $$(amount) 13 withdraw 14 use withdraw(($who, $acct_type)) taking (amount) 15 with 16 print "withdraw", amount, "from", $who, $acct_type 17 deposit 18 use deposit(($who, $acct_type)) taking (amount) 19 with 20 print "deposit", amount, "to", $who, $acct_type 21 transfer_ach1 22 use transfer_ach($from_acct, ($bank, $who, $acct_type)) taking (amount) 23 when 24 withdraw($from_acct) 25 $$(amount) 26 deposit((central_accts, ach_send_acct)) 27 $$(amount) 28 with 29 print "send", amount, "to bank", $bank, "acct", $who, $acct_type 30 transfer_ach2 31 use transfer_ach($from_acct, $to_acct) taking (amount) 32 when 33 get_ach($from_acct) 34 $$(amount) 35 withdraw((central_accts, ach_recv_acct)) 36 $$(amount) 37 deposit($to_acct) 38 $$(amount) 39 get_ach 40 use get_ach(($bank, $who, $acct_type)) taking (amount) 41 with 42 print "get", amount, "from bank", $bank, "acct", $who, $acct_type How the Plan Functions are Generated for This Example ------------------------------------------------------- Each of these rules_ will have a plan function generated for it. These plan functions are generated with the same name as the rule_ name. Thus, the name of the generated Python plan function for the first rule would be "transfer1". The plan function generated for the first rule consists of two lines taken from lines 5 and 7 of this example. The $$ in each of these lines will be expanded to the subordinate plan function returned from the proof of "withdraw($from_acct)" and "deposit($to_acct)" respectfully. The generated plan function will be defined to take an "amount" parameter because of the *taking* clause on line 2. This parameter is passed on to each of the subordinate plan functions in lines 5 and 7. The plan function generated for the "withdraw" rule on line 13 will have the single line taken from line 16 in the *with* clause. The "$who" and "$acct_type" `pattern variables`_ will be expanded to constant values taken from the values bound to these `pattern variables`_ after the top-level (transfer) goal has been proven. Finally, the plan function generated for the "transfer_ach1" rule on line 21 will have three lines: two from the *when* clause (lines 25 and 27) followed by one from the *with* clause (line 29). These lines will be generated at the same indent level in the plan function even though they are at different indent levels in the `.krb file`_. For more detailed information about the options available for plans in the `.krb file`_, see `Bc_rule Syntax`_. Running the Example ======================== .. This code is hidden. It will add '' to sys.path, change to the doc.examples directory and store the directory path in __file__ for the code section following: >>> import sys >>> if '' not in sys.path: sys.path.insert(0, '') >>> import os >>> os.chdir("../../examples") >>> __file__ = os.getcwd() The plan is created as a byproduct of proving_ the goal: >>> from pyke import knowledge_engine >>> engine = knowledge_engine.engine(__file__) >>> engine.activate('plan_example') >>> no_vars, plan1 = \ ... engine.prove_1_goal( ... 'plan_example.transfer((bruce, checking), (bruce, savings))') ``plan1`` is now a program to transfer X amount from 'bruce', 'checking' to 'bruce', 'savings'. Using the above rule names as function names, plan1 looks like this: .. figure:: ../images/plan1.png :width: 187 :height: 118 :scale: 100 :align: center Plan1 And can be called like a standard function, passing the parameters specified in the *taking* clause of the rules for the top-level goal (transfer): >>> plan1(100) withdraw 100 from bruce checking deposit 100 to bruce savings The program may be used multiple times: >>> plan1(50) withdraw 50 from bruce checking deposit 50 to bruce savings Notice the strings: ``bruce``, ``checking`` and ``savings`` in the output. These were specified as `pattern variables`_ in the code and are cooked_ into the plan along with the function call graph. Let's create a second program: >>> no_vars, plan2 = \ ... engine.prove_1_goal( ... 'plan_example.transfer((bruce, checking), ' ... '(my_other_bank, bruce, savings))') ``plan2`` is now a program to transfer X amount from 'my_other_bank', 'bruce', 'checking' to 'bruce', 'savings'. Plan2 looks like this: .. figure:: ../images/plan2.png :width: 187 :height: 195 :scale: 100 :align: center Plan2 And is run just like plan1, but produces different results: >>> plan2(200) withdraw 200 from bruce checking deposit 200 to central_accts ach_send_acct send 200 to bank my_other_bank acct bruce savings And the final use case: >>> no_vars, plan3 = \ ... engine.prove_1_goal( ... 'plan_example.transfer((my_other_bank, bruce, checking), ' ... '(bruce, savings))') >>> plan3(150) get 150 from bank my_other_bank acct bruce checking withdraw 150 from central_accts ach_recv_acct deposit 150 to bruce savings Plan3 looks like this: .. figure:: ../images/plan3.png :width: 264 :height: 198 :scale: 100 :align: center Plan3 Note that the same *transfer2* function is calling two different functions (*transfer_ach1* and *transfer_ach2*) in plan2 and plan3. This shows how different functions may be chosen based on the rule_ inferencing. Also note that after the generation of plan3, plan2 is still valid; both may still be called successfully, resulting in different calls from the initial *transfer2* function. Conclusion ============== So you can see that it quite easy to use Pyke to automatically combine Python functions into programs! It also allows data within each Python function to be specified using a `pattern variable`_ so that Pyke can customize these values to match the specific situation. If you would like to know more about how Pyke *cooks* (or customizes) your Python functions, see `Cooking Functions`_. ./pyke-1.1.1/doc/source/logic_programming/index.txt0000644000175000017500000000516711346504626021245 0ustar lambylamby.. $Id: index.txt 057d79259b20 2009-05-14 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Logic Programming page-description: A tutorial on logic programming in Pyke, including *statements*, *pattern matching* and *rules*. /description section-pages: , statements, pattern_matching/index, rules/index, plans format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt 057d79259b20 2009-05-14 mtnyogi $ /uservalues ========================== Logic Programming Tutorial ========================== Pyke is an inference engine that applies rules_ to facts_ to establish additional facts (through forward-chaining_ rules), and/or to prove *goals* and optionally assemble Python functions into customized call graphs, called plans_ (through backward-chaining_ rules). Pyke may then be reset_, deleting the last set of facts, so that the cycle may be repeated. For each cycle a different rule base may be activated_. The plan_ capability allows the postponement of code execution until the top-level goal has been completely proven. This shields the code from blind alleys and the backtracking_ that occurs within the rules. Once a plan_ has been created, it may be executed multiple times with different arguments. It may also be pickled_, and later run again only requiring one small Pyke module. Pyke also provides an end user question_ capability, as well as the capability to run commands_ on the local system to guide the inferencing. ./pyke-1.1.1/doc/source/logic_programming/pattern_matching/0000755000175000017500000000000011425360453022707 5ustar lambylamby./pyke-1.1.1/doc/source/logic_programming/pattern_matching/literal_patterns.txt0000644000175000017500000000460111346504626027031 0ustar lambylamby.. $Id: literal_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Literal Patterns page-description: Explanation of *literal patterns*. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: literal_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ================ Literal Patterns ================ You want to ask Pyke a question. The simplest questions are just asking "Is statement X true?". Going back to Pyke's family_relations_ example, your Python program might want to know whether it's really true that Bruce's parents are Thomas and Norma? So it would ask whether the following statement is true:: family.son_of(Bruce, Thomas, Norma) Pyke would search the facts that's it's been told about and answer "thumbs up" because you've told it before that this statement is true and it has remembered that. In this case, all of the statement's arguments are *literal patterns*. You might consider literal patterns to be *input* to Pyke. You're passing Bruce, Thomas and Norma *into* Pyke. And Pyke just answers "thumbs up" or "thumbs down". Literal patterns look exactly like data. Thus, your question would look exactly like you see above. ./pyke-1.1.1/doc/source/logic_programming/pattern_matching/tuple_patterns.txt0000644000175000017500000000554411346504626026535 0ustar lambylamby.. $Id: tuple_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Tuple Patterns page-description: Explanation of *tuple patterns*. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: tuple_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ================ Tuple Patterns ================ Tuple patterns only match tuples. They are written as simply a comma separated list of patterns within parenthesis:: (1, $x, "buckle my shoe") You can also write a *rest* pattern variable at the end using an asterisk (``*``):: ($a, fie, $b, *$c) This will match the rest of the items in the data value that the tuple pattern is matched to. Note that the *rest* pattern variable is *always* bound to a tuple. Examples: - matching ``(1, $x, "buckle my shoe")`` to ``(1, 2, "buckle my shoe")`` matches, binding ``$x`` to 2. - matching ``(1, $x, "buckle my shoe")`` to ``(1, 2, "buckle my belt")`` does not match because the third pattern within the tuple pattern fails to match the third value in the matched tuple. - matching ``($a, fie, $b, *$c)`` to ``(fee, fie, foe, fum)`` matches, binding ``$a`` to ``fee``, ``$b`` to ``foe`` and ``$c`` to ``(fum)``. - matching ``($a, fie, $b, *$c)`` to ``(fee, fie, foe)`` matches, binding ``$a`` to ``fee``, ``$b`` to ``foe`` and ``$c`` to ``()``. - matching ``($a, fie, $b, *$c)`` to ``(fee, fie)`` does not match because the data value has to have a length of at least three. .. hint:: You can use ``(*$foo)`` to only match a tuple. It will bind ``$foo`` to the entire tuple, but will fail to match any other data type. ./pyke-1.1.1/doc/source/logic_programming/pattern_matching/matching_patterns.txt0000644000175000017500000001117611346504626027174 0ustar lambylamby.. $Id: matching_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Matching 2 Patterns page-description: Explanation of matching two patterns together, vs matching a pattern to data. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: matching_patterns.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ===================== Matching Two Patterns ===================== You've now seen all of the different kinds of patterns and how they are matched to data. Not too bad so far... Patterns are used to generalize statements. One situation where you need to generalize a statement is when you want to ask a question. That's been covered above. The other situation where you need to generalize a statement is when you write rules_, which are explained later. But rules also need to be able to match one pattern to another *pattern*, and not just match patterns to *data* as we have discussed so far. So before we can move on to rules, we need to examine how one pattern is matched to another pattern. The short answer is that it all comes down to pattern variables and that pattern variables may not only be bound to *data*, but may also be bound to other *patterns*. Binding to a Literal Pattern ============================ Binding a pattern variable to a literal pattern is just like binding it to the data within that literal pattern. Nothing fancy here! Binding to Another Pattern Variable ===================================== When pattern variable A is bound to pattern variable B, they essentially become the *same pattern variable*. Basically, pattern variable A *becomes* pattern variable B (or, you might say, *defers* to pattern variable B). Let's say that pattern variable A has been bound to pattern variable B and that pattern variable B is still unbound. #. Prior to binding pattern variable B to a value, it doesn't matter whether you ask if pattern variable A is bound or pattern variable B is bound, the answer is False for both. #. It doesn't matter whether you match pattern variable A to a value or pattern variable B to a value. In both cases, it is pattern variable B that gets bound to this value. #. And once pattern variable B is bound to a value, it doesn't matter whether you ask for the bound value of pattern variable A or pattern variable B, you will get the same value. So for all intents and purposes pattern variable A and pattern variable B become the same pattern variable. Binding to a Tuple Pattern =========================== Because pattern variables may be bound to tuple patterns, the term *fully bound* is introduced. Asking whether the pattern variable is *fully bound* means that not only is it bound to a value (the tuple pattern), but that all of the subordinate patterns (recursively) within the tuple pattern are also bound to values. Being *fully bound* means that the bound value of the pattern variable can be converted into standard Python data without any pattern variables in it. This is important when Pyke wants to talk to Python because Python has no concept of storing *variables* within its data structures. Pathological Question ===================== What is the bound value of pattern variable ``$y`` after matching the following two tuple patterns: Tuple pattern A: ``((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b))`` Tuple pattern B: ``($x, $x, $y)`` The answer is here_ (but no peeking!). .. _here: pathological_answer.html ./pyke-1.1.1/doc/source/logic_programming/pattern_matching/index.txt0000644000175000017500000000560111346504626024565 0ustar lambylamby.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Pattern Matching page-description: Explanation of *pattern matching* and *pattern variables*. /description section-pages: , literal_patterns, pattern_variables, tuple_patterns, matching_patterns format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ================ Pattern Matching ================ Pattern matching is a little strange at first, so we're going to ease into it slowly... There are two aspects to pattern matching: *patterns*, which you write, and *matching*, which is what Pyke does with your patterns. In a nutshell, patterns are made up of two fundamental building blocks: #. `Literal patterns`_. - Theses are just data values that only match themselves. #. `Pattern variables`_. - These will match anything. (Including other pattern variables, as we'll see later)! And one compound pattern: #. `Tuple patterns`_. - These match -- you guessed it! -- tuples! We'll examine all of the above looking at how Pyke matches a pattern to data. Then we'll expand this to cover `matching two patterns together`_. And finally, a `pathological question`_ to see if you've been paying attention. Sound like fun? Good! Let's get started! OK, so why do we need patterns? The simple answer is that we need patterns to generalize statements_. One example is to turn statements into questions. .. important:: - When you want a *direct* statement, such as to state a fact, you just use *data* for its arguments. - But when you want a *generalized* statement, such as to ask a question, you use *patterns* for its arguments. ./pyke-1.1.1/doc/source/logic_programming/pattern_matching/pathological_answer.txt0000644000175000017500000000573211346504626027510 0ustar lambylamby.. $Id: pathological_answer.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Pathological Answer page-description: The answer to the *pathological question* in Matching Two Patterns. /description format: rest encoding: utf8 output-encoding: utf8 include: no initialheaderlevel: 2 /restindex uservalues filedate: $Id: pathological_answer.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ==================== Pathological Answer ==================== This is the answer to the following question: Pathological Question ===================== What is the bound value of pattern variable ``$y`` after matching the following two tuple patterns: Tuple pattern A: ``((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b))`` Tuple pattern B: ``($x, $x, $y)`` Answer ====== Let's take this step by step, matching each element of the two tuple patterns in turn. #. Match ``(ho, $_, ($a, $a))`` to ``$x``. This succeeds with the following binding: ``$x``: ``(ho, $_, ($a, $a))`` #. Match ``($a, $a, $b)`` to ``$x``. Because ``$x`` is bound to a value, this becomes the same as matching: - ``($a, $a, $b)`` to - ``(ho, $_, ($a, $a))`` Which succeeds, binding: ``$a``: ``ho`` ``$b``: ``($a, $a)`` ``$_`` is an anonymous variable, so it is not bound (or bound to). #. Match ``($a, *$b)`` to ``$y``. Because both ``$a`` and ``$b`` have bound values, this becomes the same as matching: - ``(ho, ho, ho)`` to - ``$y`` Which succeeds, binding: ``$y``: ``(ho, ho, ho)`` So the overall match succeeds with the following bindings: ``$x``: ``(ho, $_, ($a, $a))`` ``$a``: ``ho`` ``$b``: ``($a, $a)`` ``$y``: ``(ho, ho, ho)`` And so ``$y`` is ``(ho, ho, ho)``! .. note:: If you got this right, you should really be using Pyke! ./pyke-1.1.1/doc/source/logic_programming/pattern_matching/pattern_variables.txt0000644000175000017500000001305511346504626027165 0ustar lambylamby.. $Id: pattern_variables.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Pattern Variables page-description: Explanation of *pattern variables*. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: pattern_variables.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ================= Pattern Variables ================= To take this to the next level, you might want to ask "Who are the sons of Thomas and Norma?". In this case, you are passing ``Thomas`` and ``Norma`` *into* Pyke, and you'd like Pyke to pass something back *out* to you as part of the answer (in addition to the thumbs up). Pattern variables serve as output parameters. They start with a ``$``:: family.son_of($son, Thomas, Norma) You can use whatever name you like after the ``$``. Pyke will answer with a thumbs up *binding* ``$son`` to ``Bruce``. If you don't like that answer, you can reject that answer and ask Pyke for another answer ("nope, try again!"). Each time Pyke finds another son for Thomas and Norma, it answers with another thumbs up and ``$son`` *bound* to a different value. If you reject the last son of ``Thomas`` and ``Norma`` (or if ``Thomas`` and ``Norma`` have no sons in the first place), Pyke will answer with a thumbs down. We say that Pyke *binds* ``Bruce`` to the pattern variable ``$son`` when it comes back with its first thumbs up. When we tell Pyke "nope, try again!", Pyke must first *unbind* ``$son`` before it can go on and *bind* it to the next value. The "nope" part does the *unbinding*, and the "try again" part does the *binding* again to a new value. So at any point in time, a pattern variable is either *bound* to a value or *unbound*. If we follow a particular pattern variable through time, we might see that it is alternately bound and unbound many times as Pyke tries to find a suitable answer to your question. Specifically, when Pyke comes back with the final thumbs down, ``$son`` is unbound. Anonymous Pattern Variables =========================== Suppose we want to know who Norma's sons are? In this case we don't care about the father. We use *anonymous variables* as "don't care" placeholders. An anonymous variable is any pattern variable who's name starts with an underscore (``_``). The rest of the name doesn't matter and just serves as documentation (and so ``$_`` is all that's strictly needed). So "Who are Norma's sons?" looks like:: family.son_of($son, $_father, Norma) We're giving Norma as input to Pyke, and asking for the ``$son`` as output and telling Pyke that we don't care about the ``$_father``. Anonymous variables are never bound to values. (So you could say that they are always unbound). Pattern Variable Identity =========================== Now this is very important, so pay attention! The same pattern variable *name* means the same *pattern variable*. Thus, if you say ``$son`` twice, it's the *same* pattern variable. And, a pattern variable can only be bound to one value (at a time), so you mean the *same* data value must appear in both places. .. note:: This does *not* apply to `anonymous pattern variables`_. Since they are never bound to a value, each use of the same anonymous variable can match different data values. So if you wanted to see all of the sons with the same name as their fathers, you would ask:: family.son_of($father, $father, $_mother) .. note:: The Pyke family_relations_ example never uses the same name for both father and son because that would cause confusion about which of them was the father to both of their sons and daughters. In these cases, it modifies the name of the son to make it unique. Thus, this question would always fail within the family_relations example... And so here is the complete explanation of how pattern variables are matched to a data value. First, the pattern variable is checked to see if it is already bound to a value. If it is bound to a value, then this bound value has to match the data for the match to succeed. If it is unbound, then the pattern variable is bound to the data value as a by-product of doing the match, and the match always succeeds. And so pattern variables only "match any value" when they are unbound. And in matching that value, they become bound to it as a by-product of doing the match. Once bound to a value, a pattern variable will only match that value (much like a literal pattern). ./pyke-1.1.1/doc/source/logic_programming/statements.txt0000644000175000017500000001470311346504626022321 0ustar lambylamby.. $Id: statements.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Statements page-description: What is a *statement* in Pyke? /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: statements.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues =========== Statements =========== A *statement* is a statement of fact, also just called a *fact*. They are the bread and butter of Pyke. Statements are the data values that Pyke acts upon. You might also think of a statement as a spoken sentence. For example, the Pyke family_relations_ example deals with sentences like:: "Bruce is the son of Thomas (his father) and Norma (his mother)." But we condense the sentence down to it's essence. In this case, the sentence revolves around three things: Bruce, Thomas and Norma. All of the rest of the words can be condensed into a single identifier that identifies the relationship between these three things:: son_of(Bruce, Thomas, Norma) We can give these condensed sentence structures any names that we want. In this case, I chose ``son_of``. I might have also chosen "parents_of", which might conjure the following English sentence:: "The parents of Bruce are Thomas (his father) and Norma (his mother)." Or:: "Bruce's parents are Thomas (his father) and Norma (his mother)." But the ``son_of`` form carries the additional information that Bruce is a son rather than a daughter. So this is the form used in the family_relations example. .. caution:: Statements are not functions! When we wear our Python hats, ``son_of(Bruce, Thomas, Norma)`` looks like a function call! We might expect that it can be executed to *do* something and possibly return a value. But when we wear our Pyke hats, this is just a statement, or a piece of data. It doesn't *do* anything and it **never** returns a value! Note that it makes perfect sense to have several statements defining the same relationship between their arguments:: son_of(Bruce, Thomas, Norma) son_of(Michael, Bruce, Marilyn) son_of(David, Bruce, Marilyn) But this only makes sense if they have different arguments. There is never a need to state the same fact twice. Thus we can never establish two facts (two statements) that are identical. If we try to do this, the second one is silently ignored. So:: son_of(Bruce, Thomas, Norma) son_of(Bruce, Thomas, Norma) son_of(Bruce, Thomas, Norma) is exactly the same as:: son_of(Bruce, Thomas, Norma) Finally, we see that the position of each argument is important. In our ``son_of`` example, the meaning of each argument is:: son_of(son, father, mother) Thus, changing the order of the arguments changes the meaning of the statement. So:: son_of(Bruce, Thomas, Norma) and:: son_of(Bruce, Norma, Thomas) mean different things! The first statement says that Thomas is the father of Bruce, but the second statement says that Norma is the father! Syntactic Structure of Statements ================================= So we see that statements in Pyke are very structured. Pyke categorizes statements into `knowledge bases`_. You create knowledge bases to help you organize your statements. A *knowledge base* in Pyke roughly corresponds to a *module* in Python. .. note:: Pyke does not allow knowledge bases to contain other knowledge bases, only information about statements. Thus, there is only one level of knowledge bases; and beneath each knowledge base, one level of statements. So statements have three components: #. The name of a knowledge base. For example, ``family``. #. The name of a *knowledge entity*. For example, ``son_of``. #. The statement arguments. These are just Python data. Currently in Pyke, there is a push for these arguments to be immutable. The syntax for a statement looks like this:: statement ::= IDENTIFIER '.' IDENTIFIER '(' {argument,} ')' Knowledge Base -------------- The first IDENTIFIER is the name of the knowledge base. In our family_relations example, this is ``family``. .. note:: You'll see that within `backward-chaining rules`_, the name of the knowledge base may be omitted. It defaults to the currently selected `rule base`_ for this `rule base category`_. You'll learn more about this later. Knowledge Entity ---------------- The second IDENTIFIER is the name of the *knowledge entity*. This is the relationship between the arguments. You could also think of this as the statement *type* or *topic*. For example, ``son_of`` is a *type* of statement with three arguments: (son, father, mother). Or the (son, father, mother) arguments are about the *topic* ``son_of``. Arguments --------- The arguments can be any simple Python data value (numbers, strings, None, True or False) or tuples of these values (including nested tuples). Currently, statements are supposed to be immutable, so all of the arguments are immutable. The arguments relate to the topic, above, to make a complete statement. .. note:: Prolog_ allows arguments to be other statements (functors). But Pyke needs to integrate into Python and Python has no concept of a "statement". So we just use tuples in Pyke because Python is very happy with tuples! So the complete statement for our family_relations example is:: family.son_of(Bruce, Thomas, Norma) ./pyke-1.1.1/doc/source/images/0000755000175000017500000000000011425360453015126 5ustar lambylamby./pyke-1.1.1/doc/source/images/backtracking.png0000644000175000017500000006541111346504626020272 0ustar lambylambyPNG  IHDR,bMr>sBITO pHYse IDATxwXǿET@c/F #ŞXhb45jTlwb J{2?v9N8~ofwfB\tX#daaY5BaeMv2nىTg鯷Mc|[OOզ596?9zاT/)ɱATYL$+BX+oD,w?E׍*w8&HuvwO^^ }B6x˄7] 48Sr(UJ^@ʊk>L$ps:,BTVn|r S`pLFW\h!0Utڵƛ7..DXEj{'G?y =?3,co-86|{ڙ8hG̴ǻ\~@\C 5e/Х[~|O8ׇӄkjAyO$&,I` ]XZ ʊ.ڒrl,17/*_͛8NnN<)}+"""55Uq `ݶu߿/~7nhB ݻ, sZHsqjdFCu W9bG^Z(raya{׍sm>(=p}CIEԽkkr_xD$Baq|p# )bƍKHH ܺuybzkڴiׯWp,rёWn߾}fu=w\nnnxxiJ+C}(vbԣz4>:PggG=] ܇FyQggdBHtfOz} ٺՉgn_nM AFXW#ܑ00p$&_GC1h۟8qdeeXebDVVIK'_t3eɘ% WW,\c<Wւt?9ӧ#$"|}1v,]6WXfDjժ%Kzx:>>\.O\1Y mNJMqG\U6BYۻϞǼ{ȻMnF}퍬D2 kٝ9s}Lkn`od=@}fJ^;OO[Zk2軆l.cQ9VCf>\}<kCwRC=z|8_Zp{YwtPz|J\a)~~eIcEw#_ /hexژ5WX6š1BKB݋|!SudqMPb:S>B!rMJ CԿo$J+\ބC8ڪ!޽Ý;x{[4B)OO\?A:gRᮍ㾗w=Hk6o&VV!ݺA,,S!!C&}_l2a#dKj/+`nbkKҺ5)p MIH$)`15꣝>Q_ڒFGH;p*HVȋb8@!nIS6s!-ZHv6IM%摱cEȰa$!ő RR{Y#=DFLz5)p #UDB<<+O\\ݻ_abZG SdMp_'9L0Td2N=}{r!++R̩+HݺtҒ #^:B D֬!&&EmԔ̘A]+t}#y~IE Hl2SSS]]]WWtEAcI |wޮ6RS}|; *'t4mJbdv9{zn݈~Q_">nKj/k%iذCHNEmΎ,]JJą.lFf͚>}DFFO4i֬YODg/yRS[sEyLK*%H-#vv$Ow`eEN"D"!_ Kj_#$|H!_ٞYySO oYcAXsQqrEyʚ]}8[LĤ06 uS\DrsIH3 $!Ǔ!C Kj%r=rJmO[Cnݒ{ϩKz>+PYœu^uݡ>/XS,ruBQgIFD(+A{{kWYYdxKڵD (#L ͏# ҏ-E,͛7[nnnrP,] jx`F!EZ^;(;vspMBAxoUp`g3gP>;f 1|8~ܽ%8GX]c`3t)LέKmJ oݴYHe _^ %^]7M,XTfl_*CRТ'|yI46Z-1o^^e,Yя)|So_wsEy"4'#_f sS2<^?^&H|^ hzl샢w#J5@zEt#$5>:Pwgkc! ͩH, FIKܜ;-/)=wYYX-[Up0kbbIWhZP"xݸ[aR$K$xQy 7F5 @B$}OyEǩ0+$^koȫW KR5<phw{jp9? Xp!4COLːIz^faNGIN\ĴFsFh 8qs)^`۲k6*rMX/eVQѼ<̘ɓi 3ތY`q|dge&8dc_?W.رb&|G?r8S)]ßjK=,,ai#5/$۪M 5x&}#F 'p^5Z&&ȀKaѻ7mWLN_^ :+_,rz}A Z 9vMST?"ش 'C(q"Sy-Э11D۶eȲŐ{"yˆl*E:En=煦}mWuzlQFBSr{7OgF 05E@LL:Y}ۙ$a'06ÇFN P6f͵xZJD6PK۰~h:CRpPjx8=SL*'LO C6UA}E Ol.-p@; !=oo5iQG´4 OH5>668} >}„ v0۫XߐȤ7 c!Q[8nMoG<݈QqliǯZpq-ϟI*Pv<<`cVՊlj1={;tesjfM܏|Ɇ+@}g~C>^#cZrTG} xWٕel^ woJo^+V޵{pu]ny *w7syLR2¼<//hL3bPio*|vr´i;A=0 UMhms {#+b8%.GLdg_?.Mܽڵ+Dl^ƍCC|\KṫXZv=]GV$iqIz @Zw40gZTTH=i tvaT!4C::_5+͛hZZ–33+dd`͚ZgjQS[3RfOj^#u (5 >G1- T8қB4!{:"{eJM,[[;GrsIx86M[[:j`96r۝Fy!K3t 9=iEPF/ؐ=j)In`ٲ5پ]ARdr@HLVIwg뗱>Gs :CCSuT y;y3o:up{Ԉ/н; @_DgBq.ڶ .)?K9f u…B'%Ww*Bh_׏:;a=Deƹ\,6R,pptP+,)5U07r޸A+ϩ<Ν+jڽߨ`ڽ݉F/1P+*g$|c@"T)g2rr ?χDRj_| FOO: FrkC ?^Q~4&*hbDFLS3%ullaMT {eJM,[vw'>hAn*&M*3bq%O?lj6URcf4U/^֝ ީ0-Fτy+,8lZnyZzbCxG0C%#6N]@Qz,^:u`F$'s̈Yji:<CPU<>´"aN CVlڄ5 G`l 57V?:α2`ۻFmQ7L+TfF/Osбv(ܳ<mݗ? &70kװ{7`R_@v6"= uOk\ίjdM;'0³prB\\xays G/Kɓgz{{PUvWhF@lmˬ(AN -M&ޤk,, ?l cLJE/hWjR.667WU]PYb,}Z^ vU Z|C.X ''Cp08k3j(8ƒ/v=7rt1"τtZ/--Wc T={jJ dbT,:djB^'h -}$#2*`!5V-ڪܹs+ WxãtGX] G΅gDWjnKS?,mk(޹CnЀ^bܱcGfʹ*0`*rt _,\jwE8mןͅɹiU,\τB!4AX\<}5t;\\nK*x8:<ѸQ%Dw\އ񗩁zj[UarizA ,C\3r: yW6g7;szD,7ʏh޼܉4RgG6P@9' A6x\.ˑ'6'GH qAoR5/OIMr7eݩjΫHd }bj~kV'pV@L$jFR2wj8"t* H鉮]AZw١.#!B __ ȚHZ NxTI6oy{A+9.\HǙߚc+@(Ć ~qZO-wTv;dPE #FT$&F#!BФ ЊJDz%䤘t&La;|J=]GB+Wu54T.Xq8t2fOK5";Y* 6T$_wZz}*uՃR1Ǽ;Ҍp6: 'cx Pl(U8x⺪X-!'E+4/_p"tVT{bp!*M.u+oVPSkEsӖ=WqnFkb?_geQ%-Z`@ޕ-`DR|dBqq^~sЬc}žK6|<cpE,ZD*p?Qp!UMaxWfU-5G'jdu*)/]B\L $b ZSԡ9٢\ܽ~;*O#\dC8oUN?FTyTþxIUݖl0য়Tu2JdB cR&.[JD|.oCB"~tc],Zt5k v_[0K%}`_=ۗ] ƌ}}P&|<p#C?N}z Ő{ opXQA= [ 6;q`GS2'4,f$?^ƃ4}Y-mC!}.NutZlX3])'WmXxLVB;,eKV-$$5dXY!'-Z׷ⲓ?zU*eío &b3~?\EʑfPX }|#GIԮM/gW`MZb2Y­"3Bj(@Vc8W違ְAjkH10X]_ 9qj9wM@~060`UM6v2ܵi)՜8u޺:13èQWVS<6»w8$SnpfF Z6ֲ=|H *G2FHSSda]OYrPabÇWvqҾK{F P38zTqÔ˗ ;]z599RU3z[wncۑʹ+#|.ՃIɓ' Í8qRU?VBh"cO]UI0v,3 m۶-RRFF5keP! 89V-c)66JC邏OIM_~ @KK̙3?|y)nNo5@z~% 0"Хjűd o/%:hE /5I/2QteYJ:H:%3tPdjX¤# ?N9#"QXbРRT$u?ѱWk)"*xO4 7nwwwFGvRc-*vP`ү5#7!iBΘ1IItXЫW/kkkqqq7oiCcX*n_%6 ۷`dFT/] С04ClW͊p&ZJ42BHQF"Ii10&Lx\ [An?u>1BoWG;bdҗBhMﻣ *Ds[={IF\HIy37KVAػXup󓈲U!֭xbg!7E &d<2v6rݺ9Kɴi-Ҹ1;// $@RKK+11QӣYe໹WB;eH_O1ɹiWP׫=/M̈M˜ro圇*_lЫsⳓ㲓\Ms!.#;x!$"~E%:X#/y=!deu崑$<'%E试%f"oN[ǎե`~n۶my|Gaߞ/)b?{xz$#;; 9r+)9*f%ZVq^ J X\X -|hsP,*!\g[R&^@00}RΟݻh66THǃ˅H$xAzIM")rGcCFi}rԲg[-"=;vu8r-cؔ[}Idu%>;Yq}lvU3TVXp;l3e X";YYSHj*HHzz[rGB)7nz":LJx< @x%GzTHjĠ3-^DgƗ !Dgg,!=2'Ȏ)%P/qOQRH^d( S iNXP\͛+o,"'::AXM+@~>$l `f88:f1xL:H e_m`l}m-ݖ'Km B!I"X'm~uꐝ;PH k.~ĥKKKKHX,.Ȁp%9 $VSjnWCci4yw8~sRsHl6l̓\G~> 0{6_е+&N,~teĉ`q\Ăc.r9.4H\o-񼢟`e0Ю;>ѥ&O毿V/ɸq b$+ѳ2?.>>^Pq' @h?q@PRcPa=>TSU.ܧ.:}[ra`/i &Lcת;wr%ֳgfφsC݅B!]6nXI6lصkWBݽf?4a7ZVܒ/_"k!=mG^ѫD`qmoa[~kb~\Yl5m>T~!6ѷ4U3LT 诬n]ǴKZ"8<&ꕻְnK~y7rG߶7 ҉rA20`Sj  L[p7SU15K%\K512Y,]z2qclpq뱒rlnm66L)+<wtþb0;Q/˥Y#T[Μ#yNI ndMwqaفxewtˊ~fṰ|>FdZrtN~k&-j78ʫ\P>M)!):uҸe$}n+Ӧ'0yYP<$\.qYг'RC:d%$%Qe;$Mt8J,j/]lYuhavnEW~6Oy%|I@QƢ=3i$#?*HKk86@%QLl6i-ꅝ%5M(xnq\.bbpMKWFغ5y}fF!qF̙l"(11ӧ[ӦM^d72#e zH,h9fAk KpjiH-b<XNvڅ}U$:z>?|:Q=OflVC5D-E}DBgfQ-q ʂ▭[C:bh_C!///00pڵ[<| @7(-bY4ZՐj7'%HujЅ׫ZTM`l^TJM@x@` :wƦM5jԨwIs}/M=G?ȳw]qNʌF^ĚW>${&p_;z&j_{x31'%oƝZYaNAȏ\qWc4S9ﲔȻڄ,R6Ҳ5پ+<9ّ&K,!ffWdI1-PvǏ=Oboۛ(oq{-pm }iww@rXV68Sr(UJۓ]J IF'-Z[J9˗]!iSr<-4%uF54il.Oxo|~\BN\ozlVNo?#w:;[CF <^on{wLAX"^ﵯ~;[:1L-9)VngyQg,.*Yl+u28;n)A;th B!ȦãK)X ) --VZ,,,erW>rʽTA8a]'279 |7M 8tT7DGGtU:84iӄk3]9m}{Gj7Kzw!-njW%X*̹sh#%= r8psWQzeܼ}O{GwF짜w_99: 4KY/POޑVwbcѣogdD{Zc IDATt^n!/_\`… me{th ,K3zeEX0$K|.'O陾}Y|>Mjצa2w.%FF $&FAe[111TpQ9JWX3*r:5i~] M\͑HpuEr24l؁k,wk֠^7iÇV҄i4r@m怣* s< W0eѠ>}BӦxٳ4jh̘1'Nu#(8ԡ'/CVƑ#S!pv/F'fa|-7ocG ѪɓrL~jy41XleP4V'G%;m=l"ptpˬ,lRXJi Ç@=,-n3@LVª;Ѡm9r2憯T)?@_A%S~.4rXyqe3Bmm,_X( =C,ww4h uH~gCyEP QQ$Eh cZ"ujЇP)(5FE \xzb~hYZkY~&ZԎ@HS&d?QI۷ƢŋHvAGi5Ǧs:\ Ge`M;P9Ҕ1B6DV ?#7M4@8tMҝ}zʎCnΞU4?9R`LTc0EΊ ͠ػ#`A6K<<` nͰe02hw)7f3Os-}5}v _2 Y/ªUS6oFu,D,;XZW/(Mok:]eψ`mH{k2_| *V4Vн$,ӣ>I8f 5v"*Kͩ8i 03dHBŒ@j`h0c)eɎYPF/|fZZ ՠ ֖vlRQ# ZZL)'M J#0k=bTODrr妮NmˮR@`gа Ur88~l|}+KB&&T!1'Y%j§0|.H%#![[lb1ƎeUIp0]d#4ҧ T~v0FVZ<:wz?l,*!`[QBLѼ%#f AUa=ߏU'KF]f5i5BF䋅Zn(Tz5yLvj̤ .)[K:B.Δ@viVGPa3BFuTl:(TwSJ!_~WWv尢HBCcҀr  kJ+Uj_ʕ*! >j ai_kw:yBS%b:9kt!o_c лWsK=ͫ+o>Ry;[*V%?+kio^Wӧ4|NIM45, yMoSʟ24͛h?KY_ݻ͛HԤ\yCX桌P'h^;Jf{1xFêj&L|:욆p&6(~ߺ-&'Qڙ"RUK7x WbU)Gӱ-lєlQ:} z3]<~,.V[맖xၾ}Wug\/cǘVS&?5_^R޾E頚,YzrE{: LbZZp/._KQT˗tN__jUā$Ì5kV2j* 4RUd}}xx+` ܙ_Z:A޴ L)o>z ŪÔ#x|yEyFnm:tb"\\p͞ccgZ(w1P+&+aE?r zGÐ:'TMW .̟B!lZ Es2\xfŐ{TMhZ뿈$b8zYOrc k4q䕅Q.[b.p8 ѣ%DE=J:tˠA{tҴ,am&lЇAmjob8 Ee)!Erm+ڕaڣeX*We풻!r#TDDwM|~|Jsj;|w>~k~S%AXЀ!^ - |4zDb"Ӛ4SSE_'KMOO߾}155ٳ{XXٲe\n> ߱=ˎew!!Ӳj@PQ^a (-Ŷm)Mtyʕ*?zAO!@mr_gsj Q:*T?#&Dfa!fȑ2 'uU6ѣѨ1b>5$ZOи1BppYB̝/HI)ҥKď?E6(Y :@J$9o)j! r4<3}v|D"a+!+WJ]ݵ+ 'ń#ɓeĔO'@@يVQ\L<<*w[ W9Or`Sx‚ cgC]S1_Gvw8,X".]3c)hIĿF{{M4_X] κ^' A[Qk n>܈ Rő+͛LkRywEELFLt۶d.LwEYYãu]9А2aNN2P\Agn{pS_p%H+k|9"79=4nۂ|a˫\]qF(I/Fia=|e¨j ! ٴW^'$eѣȀȨ|%S>MF"ffm꟯#GɄ %ÇWFSť_GLPTʧτ %%]:τ/xU~ʅ9NxqS2kQ郘xz 6ms'U/1 +K#1j|y]"׮]Sjƫ)Gv3V$.JD$7-ƿX$xW1'GϿGJaz^IᲛ*h$uIyoEį..S>f[L~--/xoTKVɦMԴتORXȴ2ˋwEA_iοloߌ<#:ۡc/&$d<n+?=V~:g}>{XAX"w{8ؼ͚al@>*/l޼Yo/ƏXhȉmL;p8B໮i:hI˖0:Eg\Dɓ4YQK.;wOh&[hjJ, qqLS=t # R?{-CkԮ:ݤzwhخB5!DB._&Ǘ E/sp [oNAbqqE- R[Tݺ;tCP;~r ݄}K֭#XQ[ @v"))L _sHBBbjj֭[]]]۵kr4iңG~t^Mn׉:}T!SyKzs>;9CyQӧ'>xGGgg œ !G! nPEˈ|j@,\{`@kGNӘaN}ct̄D8|AA dcƠo_L^e!<OEE8بzϺaܴF-TF !Wo(_$6|U6>^SZ"8XFZkKKꅡC1dWSܺkꑳޮ{?$ܫvn `5v !aar/cuuб#uCϞP "de{ddT==8:W/88`yHн;R ;c/w]'׮NK:+oo43̐_&T7o'OxĨj'[[ؠS'8866Ж1L fW>D${։rikP f aMXk22-Ґ"'JX˅!ZLLо=ZF˖ J\ŰógE||Y>1t/;82b #TM0y‚ڜȨʢ-ZE 8-"JJ Td^pBO07;Iǎ39)aH1μR؜)%cr5v 3a$9# OxXia,soJTOX`?ʨyP:i(CXiHIaցwtƝì vRW9P5aԔi̴ [HfZN]2S:e|aMBd(2cY//1nY~sH"<*ym`MȢBt4i  J Ǽk@քtaOk5ds^aZK@@FnKd YTKVeR/WwQVuf>Y+~:(&`5jo߾}q2ʜwD<˗/rW\ H+Z4Ԅ} @&?*{esEn`ɜEKK+<Ѥ~U\pnSq&L [^*84djdMXAF*,%_s;Z41e, *MחV&0D*'`MH,j9LAYz-(󑩆{8;K_&mgؘZRmeMH###߫Sرc}||ݻw&0PxEjbRjz3Nt705<=]5!o IDAT]̤W^СC_H߿_,+W>}UEB$hdXjF]1KZq"ou_93NCYHG^tIĉ²D"Q~~Ç}||ƌ`PXNpR8c84qS$Jk/5!]|<QBg*RH1L}bnHfswʁON/_{9J)Stڞ4_7mڤ5#]} *fr '45 pS.L e˖EEE 4NcbbT, ,&CNݙVT&JD-86퐜X$0݃V3CKoƜS*iki?gZgBp?#WT{/9RH Z>CjfhkB6}xtf%ۨ!s1J>]-0jFX !]mNos; !1Ϩc]s&jy[Cyj{OȢ4b"1߇/Vi76h4Mamv6G5:'Ohfi"R#RfON|CB$Zxgք,ݽt#UԐ|C7Nɴ"Z(X"Yiϣt9.5?b9 Y="xE! r U`MRƟG /67 wK;E)cA׻ؿ]UEL$v>..+([Tmd x'Q46>ps X~Tψ;D܇4=x4͊pWڮRyra7 pvOף:aM%f&1pE㖍6[tEuڔσ~ٸY pASE#Bټһ;(οÚ#S,PVp2l?F-+M9<Ojsq8ӢjK=` f2D.3n%IOL"q-[^x8 ?Z ^̧߀>pq֧{ >a+~2T"! b.8 IG{ʔƢ g|8~秋ܢ"Ζ4HnL[ !Gݬ1-aMȢ.&uk=}ք,ȫ~K}ۤpU"Xhvۋom-.SleaMȢAexu]ǡm5lCkMzQ W,u.Jqyqu1 Y4/,NL}tjaצ6X0H*6g9ݧhH?U1~לbIj'\HadឞR ->FyCx %^ɾmyd#sQ{}*bG@&|s5}p;|աY?57 cvP/ ðgBaM0 YX5! ð&daaք,, ÚaX0 kB?}]SPIENDB`./pyke-1.1.1/doc/source/images/plan3.png0000644000175000017500000001644111346504626016663 0ustar lambylambyPNG  IHDRsBITO pHYsIDATxy@g'GQ!@jQ ^uqV(ZEAU ڊ-o(Uŭnaz"R ( $`11Ow^! kg~w$ \zuĉtWu׮] ]ӱX,6MwZAB ``ĉyyytWǏ B P`@ P`@|Ew!z{FGG={V_@_pa<o"HK x FU]v[ګm``.X,oX$@r+++͍.b:X,3444r\<-)[vvv...uuu|>_Ų U}Mo/PAC7nx뭷X,iCC~|xĄٟ9s&''g̘1ܹsJm>yۛr8;;;Ě\ҥKki\}1iGGFB܌~Et 222TSSzA:88ܾ}DzPz *++D"144xUUU+VPN``{::: \.Wʕ+/_^QQ!JJJ{ѣGНLRLj733CNq\WWd8};RS.'$$8;;Q6Ct3iҤ= C   (@0  (@0  (@0X,B+ F =薙رcux_BGe#pL6ŋ'O}' #2Ap0l(m  z m p 9`0c0l9`0c0A` ۀA` ` '`8` RۀA ۀA9  =s6`8f =s6`8`@ 1bq[[V1>}J[YzA#GXZZ=zի`۶mt֧ t[n@16o,i.Q_A04gcǎN/_4hPDD%+X,֦MB[nJdyfPTT%%+͛UYYy ˗/2dڵtW 4cٸHLLGEGG\cOPxzzm>CSSSK_cЏfoܸ!D1B(&&RA/1A.9!doo_QQ.JAgÆ x:66RA;1B&Kr###w5k]j \\\.ӁBw1ݣ~ã*tG}A  (@0  (@0|>_c0UgϞ.\>}:U#G5  ^| <\.?;COw;>\~A;/%g }ŋ 'N?)]@0NPdffΞ=0a©SەT ɓ6,))ŋ*_OA0qϞ=Çilٲ &,--V`hEQQG}D}ϟ~``r_ה)Sp\.wѢEׯ_h %J~meX^zz\.W=tΝ### __Js`hF]]ݗ_~@DDD( #={lӦMC uqqٵkWSS+(@0ڵkAAAgG^^^~Z[[:4rH\'|ѣ^H^jmm=z(9+]`Kf)iӦq8ʕԣ ѣG|>ӦM]G,TTTl2ccc\ޛoyi8xU u)9sp8ɓ]5ۼy-.U l߾Q#FϚK~.]-W^I$#Gxzz֮][QQE T U?c|3϶mțwiƃ#O?͛ $ R̙3'O&:mڴkwJZ 8,,cر;:: ;B0>>ɉ:bڵ=bFS L$mٲ ֭[Wm/`wɞɽ:t ѣ555]ztt`H$cǒ_G̟??77WP]S(3gl6{Μ9}6f``TUUĐݰaǏi,I ݿ>211kcԨQ}?w.x9w\ǟ8q V__?tPfk} dz}1oucc㐐[n]=X{{}||211Yr>`FIIɪUvvvNLL.e )77_xfg͚uEґ T?L2:bԩgϞeeL VVVj*+oo&yj H$JHH/׬Ys}3=<11b`)-7o\x1uȑ#8_`r0'O3؊+ݻGw]7@!H?>n8ρ.^H`Ν;~X,?? .ZFuuull Mllluu5uF? V^^fGo7DBw]_P(.^8o<7|z``m8PHw]}ŋ8-Y͛t/^hnnvFsssDDD9;"ԩSѬ__~z?޽{k֬ JKHHDt{-B?;#66!h"륟~i'gXӧOw4CPxŌL&;{ԩSɯ#L? X,9sssuÇ䗪nmm.u7'NP>11\ssUVH'; 2jjjڹs'mll>gϞD.t#%%!d``r#F߿ ; ](JG044ݻ3|rSLBڂq%}{)e``KKyD"ӟw^r###7n݅L6a6044\d Ȇ p`>|t BJ)>>QwdEW7nɓ'?~ܹ!OOO`lڴ !DlҤIgΜ_Gh4誺zVVV8>={֖-[Nܹsܹsbx}.Bf^ٳGطo_ppƛe'O.^X|W}V[[T*eDVd:<€5jUFqqo3A0  (@0 ;{*5I#wywf D" 155۱c~1r<66$((H,cx~~~"!$JlllwލzxΜ+W455kӁÇ"?~f>}*-->|8S/(3޾tRrC$e]w+=֠Wp8=^PQQRuuu|>s0!mllptt,++/yPXrU7׫ttݍ!^ccCvZ<괛 Z`AMMX,޲e˸q~YD"Q]]݌3MH޻377922s5tĉW]3KCjbboߎ_L&lllx<^``˗/ @mݺ,]D" X|9S&##P]/YᐷWir9!/&'MDp.Vjfi FFF|@̙3gϞMĢE233 2xܮ2JnCW0(_N:ŋW&!4s̺:P8cƌhBdd̙3Ba]]ݴiӢ2dHZZZ[[[uuuXXW7c899(c OcRU,[[[7664H,bu΍R}f[鱆 wwyN |̙1csHuW_YZZ // &k?쳷zkԩ&&&B׏;V)))~ٻo7o=11ϯl=/BAAA|I}}H$n@[QСCd2&GP;igg|~|>Bhܸq5P:RݿJ۷ s:x|:C)DlmmɳRܸ\krr2CסTtt4>g%|3f(//JŁA f͚% HQQQd x%%%':䪳*--ޜcgϞ͞={ݺuaW%dիWV~H~~+`.8 P`@ P`@ P" EwAieݽ{"W< T;wܹs'U'?ߩ&4qnnnj{ٚj|||4`?2Y2hJTT(_suuݻwՅg>IENDB`./pyke-1.1.1/doc/source/images/bc_backtracking.png0000644000175000017500000011646311346504626020742 0ustar lambylambyPNG  IHDRN㡩sBITO pHYs IDATxg\SW' !쩀q2d(PUkݭZGm}k{ZGVPPDA@@D{&!$pR~;yB{=B RλAQRJK"B5 a# P!0աf]p)(DL9J㔭@*Bޅ!TBHaC!$0!sB9Lu!:BbSB!1!T~XkDB9Lu!:ҏLfpn13˫^{tvDAͿNX.XeuT]%{:W-/n0yux3I tm6!;WS%ͻGJ6j_FeJrK=%uԔY̤ʬvO?XF\T_}Gյ[rK6J&7Tf 9ԭ" wyZMĜwv򆔱*%ii1!aY J)?HH5yυz __lv >dvKtw)hʻUWEqv~JCYɫοu@ POHPi&ULu4 AŽRλa?1殎JUlH).B !6`~|}'P "BlD!$0!sB9Lu!:BbSB!1!TBHaC!$0!s81jk,eU ; SũvaCL"(AmS*_KPŽ!$J0ա1hj*ŽCr/e̞s ;)K) ;P;piV$2V'eͳMŽ!$J[ B!1!TBHaC!$0!sB9Lu!:BbSB!1!TBHaC!$0!sB9Lu!:BbSB!1!vH`oߞZ^^^^^bƎ;qD[[[KKKaGBBNܽ{waaaA~ƍ7nqyf=jRfAyV{cHf{`IN4iҜ9sZƍsqq>|ph42ωpzTG)*2 /:T'om λ}p!n΍yK}4{"Sy򥼼CN4hPYYYnnc7vdj~YtnT'z>}JY Æ {677veUup;2!D99`hhEEE75U@F~9!SLjJh4 H5!SRy<]7v,oB݅NXXX@xxxcccJ ґ͘Cu4afzzzέ6#h;C i|zP&go?;Z7#\ر "9(BLu"#>>j;Zn۶mzj p_g%f%fV4T׳T[6vRe) 2Hp~P DRNNY,̙3~/eeey )1=sFnm=`Yi zSGَї>6`]ǎ[jrRRzWNIMM)..0Nx2%hÆdjbjPUUY%{S^]_Z:(1uFqAY5KAB0ՉƵk?~.\J"##=z㓕3gvI)N'>\e-hTe#āΝ;>}:̡g:`o߇A8ܯ<ϢѨ̵lvDWaYYY۶mKII)///-+RVlÚEzv >8|-,6/$g엋3 q#B {f/O vP O~Q4DC٘3WS(G!N܊H~ +ut'rtѳw}]4uey<÷<5.LubOnFEt5EfႏY65 Cb B SX N,(4eHaǂa8v,!Lubj cGj$;zk/]V7듍gRrJȽM<6K_||y| YnGյ[r/hۥ,>;ܷɣ%LYGf~9}2˧C@o^; , { T'VRsJ@XD'du~u[NSE//,iyӋXqkI:mŰnϓro"Ҫ#"U)&)!< &،;_ 8X@ Lu⣲UY˒a tS[K1ܭMH~Ԧ37,~IҒE'wNߴp̦n?LV c,lUZUӺVt U*Z8aRt`q:F46Tղ:?x\nIvr@%[*߳0::=JGCrK(v=M^bjJdj\ʠ B0Ň E|~π3li'lv{agz[<瓁9E<?-l!n;ݯnnq4Q-Ɣu`ޟ7[:RfAivE}R|H3 e5%UC4{_m9~o@%/<E{.zn `Ė1`oWJ*j \:s|tg|w͝<~O[xc=zA'v!907oEr5 ;={GMŽ{8M.Wןy..냐aXYnィk; aqF0!Sh3ҍ~uן ;j;_ݥu X:scAHAlIȦѨsl#BQVU큛ϓrT޵3vD3Lub']1Ao˧(;(w';N]E~73j ;(:1,c842 B Ee )FY3?_IEk7_J:qgY|S @]E~,;#*LûCW"?M&JvFVtMG+*.nTRDl:3jۓG#ZTq*Hx_ZMnP` QzFC5dde处j[@~w'6qj5ʚČEi4x#)GN2n}0} nE$&$fJY%yuy2MRrmFiaR PamGJcCHh0}|"%ufQrvIU-]SϮgԳʭ+WȪmғƧE^]FOZlE>@PTee R rR* 2cidja Lu)aUde>afQzLG}%;Y>iA9Q&Pba?IWAK#)Lu_Ȩ }\#@Pix03[_my=-nvd=Q?@ @ S%I~a F)iLizj*- ʉt'+h<`$/ʽ*BTZQ ܨPpӵ`:LdT5x?OZr^BH`C} yt3#$0;vV˂NPF7,OZЭ2v@IKSLm@jALuw*oEI`уh6ɑ>iA~ F]-OI^.=PoTzEfM_F_ƿL8 cӡm7^#Q/|ӃoԿ]O[Ncx *AHaCC[p-8tk.*IqOZ98iliNÜ8T>TY/#e7%=fҫ I MQKnTVt2pӲԃCP/3NVDs7-9zә"-bKTe[T{8;iхBH0ա)eU e7zNͤ%O,O#(1y8;L1B0ա.ɬ}TXuc}G=G1$27ozplr<]MuD[!9LuCq~!~aSȍ X4={w3mչ}҂&rbmk#O4.TZkK idLG!?¯B`ߴOh g ^Su!Luz.~!Y&єUt`:P7^UP_r#=7=8ِ+I;i2pg*$BT+cWd>t(fҫ2B}҂" b<S%-=32Q5B`HeK 4ߝL{JC(cWg=Fh{8`: (B>2o3C2˒-d7Liz2H||?1T uL}'m9 a Sk _ege%&`:;Yӂpt Lub}?͌;Y-{0,pF.vnvd-,f0Չ2v՝!L TOZp@փҍT =GR.Lub"\Re7+u#tDoJI jT$ÄB Sh+KkAۊ\4[\~Shߴ`ЖK{Ow6o LufڪkIf"2ȥ}ӂ2BȍRVFpDFC;$͌;dL,HG/}҂.AK+JMճ6p:a5q> |Ъt^NTeƶZ*UZ. _Yi<#EH05_FhLs`:ZƎv[ȥ}Ӄc[.堌fYlR}i:~2@5rtT&~Ӣ3O8Lu4vWTeuŖid:qv3A.g1%5R rtmFs(!dU2RE_1 񀩮3 ^x^UXoei2`ح-X`:Nֵn&HϹ6l ; $pF{YSe(g@Cf4={v3AUg4]Je'4Ae2rtYOHn '2. +Yu/J_yqoR(<Đ70Q{He} nfE " 9e Q{.PYwҐѪCg͞g6?/(TMٗUڈ/#!UE#L[-(S"Hl.>A756ֿǭml4ddy?#T"ꌈӓ~Sf(v>ϩ-|]23"}2+\X_k DF/NkexjI\o\LV I_ǭyaGҖSfز$8>r򒲿_LkRJU'C./)n(ב/p-5pF5iuՃF/~~'Gɍ-/N^x ʷu.B}IQB?ҏ+*c%#!@~}e/ڧJ*줺n{C^< z^VEG5"ԗ0ݨ.&vrcˋӎ.<[ٕo\̧w]aiʗ>"≃d}~**:qu\WβP}c%~˨f{3kԥU{B,1Lu o/*Y-/N;lgWms3&$.-Ȯ)XܨĐOĥg-%:{] hcWoCREz#'< UYªXc{A .͚U5y2@^x6 A2#k˩wr y w~ŃPOr߷w͹^[Nc٢kw1 ^ػ )pdf/R;;p]Fu0es`k·?? \~J@ "]:mu;Nm{yyq+D'ykIACRǜ<ﲳ1mrNu.Beg7MϮF_L]_*|F%ܮC':)_Z5ݷ[#B}I SKu[Ʈh{@m{BH(¥qʶ]UCec VGᄥTŽ;\sj 3>7X~cho^9G3[W x]BH41쁉B!TBHaC!$0!sB9Lu!:BbSB!1!TBH_S09A@ey#JQR}D$b\MYFW!FFbB.AKpY ZuE4@A]"{`+I Vz.:2pjϪq4ZZSLw+kJ dFR}X+!3"^݆=\VTKxLlg5%"ڕ^x=$.zV#JM9:ʋ^I+4Q uԄ5B#2\3,6/$X-5쀇C⒳KBჽL\gHJT~u\[Mѯ.DLXҙfion@RWB5`I: M!#xs=4.I2 Te{-u$؄>xy=$&4Tfy:kP,ۻ:~]Ғs'jqNWW^\ ~1XoDS&T6`"PXVA|aY HШNN&M}FRk'Ց}1c#22 R7HVZR/5I ]YM<~XLOhܣLa eOG;J],DF}֘Z?-q];7,ҽم >t%b:ZLumguHdV'W.1i0o':mS$ox5`K^4S,e<^x:̙lIwz uK:Oճ^Hn1d:yp)tyoP^2c"\yw֓sO, Y,P LuIJH{`'.%zhܽo؍('5fPB*'|FQ]w?<1{IO$Uf6qB`V>5PޑŽ!Z֭Dش2Pr$+c6b/m {c JyuW_U'NQS`-SrOJ224es!!0յv9vKA}'"cӯEL'h T`q-!> 7n1JJ0c̜ 3pxR>yM.d2vpck.^'!6g:N77XQ y :B`LhLDDpnN}xď{`VUU]xԩSmHTˎ;Lf,7o?*+WF`H4ΆW #1@o/{+"R ^&ciKGK*jCOVS8_~:UUKKEEii˃,Ȁ@x طll:*ҽ羡y U9f^&J} TwsjSSӽ{𼼼vޭ7QM@u=WIdʕp6:|%|)IIp"9&&ZZ}cs\{Gnd2zLZe+ P$7o`J1$;b3o?B~ ;vmpVDŻ!)1z|7 %DMh۶m#믿sU]]}ر~AVV6,,ҲbsX~_u}&x PY p̝6IΟPU˗ٹcy3݉z]9Ɣ#M탨_+wxZx={PY pvt?رx<03PRpWYc"_-VtL332Z[x3g$$$vڵnݺN?~@@Jxxѣ{;6Kbb*+J$$}(E^]Dbr˘aN&kOfV\61|qq`cWtr[֙G 9E>{-5y-f{b'x{{EGGꤹ7k,___ee億Mg:mɬK Rʚ*:.]zOs -?!&tt#xw/ eyiwNL-UaG%ߞJ+e;`dii0{6\A<|q!0Lr z,&25B[Blo,[gIJJ>}zȑgΜ駟{c^06#y0 HV2IYťuf])N͛7`6#((EZZZUUu%%%-z{{s8={6鶣W]ƻȫC(c`kk׀ `"6._[mWV+tDq>Qn_XjP>A8X =ѕ_Ͷ<W l6L&gnX ;Oݵv-PpRikU;j T.v:iѝBșZ*~%m"שyyy:::cƌiw޽֭+++KJJRTT3gN֮] wlTT*e;8ptpxW^4'0 u[0L.2.dB:hW'|;:>K]Uǎ\/S+%=.D6LCCaLPRyyX^w`h\.8`YV\i9RsγkXzWWSev];w{deewޭ*$؉T,jŦlk\%X%%@;  † ]С@Av6p=oWOM=;*.ޜJ8:Ӝz;Z">ܝ,D^\ 0rd;tS_G67ŚJ8Y:Y&g\ zyڃiOf=Ros ~4o } }7?Fiiggggee6us?'# 2.xHXH^߶MHvaZ()Z$(+CUw>@+_Jyu)sWrM>{CULmFo'B]jjJe-N4lSs~?JЋ_N遗ɧf~9燿 ~<kqLAmkϟ6Tjjj566 Rm#^U@Bn^kՁL{#(h406K`Ĉn:(/:YCԳW'`ofK݊Lcގ&&?55p ,^ nOK>d wbbSO?9b|7t>QSKu`FJLuaaa-i-;;u:/_OceFvQT4i:X 퍊iXh(_ tkJx )1M|_g rl'A_O- ?dr?`">m>NNpʕGA֯mRY II@^sQ'1yČ wb>N ~$ycjcX/& iիW,Xknnvommmaavog6zTNJC?>g$$@So;u#fďg Tf+ˏl9q'>@AVjϮW~csnn@PX,XIIyZ|p80}zOfQL]JIYgo?R ~<VЫ#!ToYn߾}6l6`x֓'O:t(mCvKQSf\DϬY`3gœ9 - vv0|8>D 7Wd|$$૯z9^|h@k]BA_zH\bΎa'Vy1Wsz~~OٳAZƌrr·T^Ա8<>ekg,>v$%I46}ۧc@B!R 6&* j:G|IaI3DkR|bsY6pڄ)=mg55pYͨHz=Jtĝ_O:⤛7a\`@BV~.<|l~<)m--5E5ey)]!)#EHeb5#m l.y466mF4@PPt!jzu [r%;^͛7{⅞Ç{{/~/9SbFص ~ 8Pkkhw u _ g_xϪӨqRrJRsKSsJtvWϲQU}_jtN/),(33 |HMTwo'%v} jB"O5y%UY婹eO9ܦtS#ɊHhΝ;|r6d2E7%"44ŋgϞ%BKKǃV}s֓8!TGk%'Îp4̧aYY!=T*̞ ?΄Ѩ46=M"T$a ݭ`@v)3L֎'O1v|Ͳ + $%a 7ѱHdťL΋M*,'d%͇N8b *@P 555UUUϏ$N IDATXv? /߷}7fŎRsJ%S?1+#b*Fmiippwwpw S y;2GK ' _N޽R7o'Tnx+:y)Fy̚b9.>*L1LM,,:^*Mׯ ܥ$/}?r>|)V} :tҤI[n0` NcӦ#$+I] M ؉ČR(0EGnHe( သꂦ&~CʺK_ ~Y]ǦKЖyNX2c|<~+JЋvngY=?{iU- FO6a44@]]!eՁQI&"s\:JxOu Ko8S0LK~>A{#Yʥ>Sŷ?r-4O VWPqW^jS_Ë_s\ۙDpӣD8\kqoOMHIeT˶~嗾F=I.9ufcFȡP(_fU4TMxi>ԧՔf: )-L)6eJJ7zH,J9#czJШ7_F<A' -X?{#un_2vX]gW =U )x֦I3:j]z{;s)QOw?IԹ_`>hk<&Q[^rZ^Y;Pzuc3f6`on|dPG{^y0]GgU|q|V~J*bRus?4A'fl)ɇ%Ղ-9(MP9-sG25[xW;Ѩ;rw4l`7t<#{V6((pȟ'mFߺ@sX_ a{%\13rVSgS&+*;wHua1&zl9iƤ Xi0np]avM?}v* &K.jInoz﫦l -5&CqlO7%h;ngfPUpȟ\s_<4 H?dix' t\_R=\W}}/W?~StBA_;n.=de(բ6Z5`V5PSNcӯ z,+%pދa W7cNn?+'] o`7:YMA/y.$:!dz5lw=}&R]ےUeFޱe)蒊ڡn[5PGzoڭJׂ_ }Zߪ،B *T俜1In遼=~'ؼh9eI/PP`ʩϼxwѪOE{cYsg>u?%tZh_=ӕKkS&q)|J~`\{Aho{oxw2↶ouWM|bϧYZ}St gf=3˟$fۙ7v:jKسvp`QS> wJ+g"eyi8yQbzdbUy.wzԮCȤWWk[ :!zD??|-! >Ap,FՕ*V?r%C˫OD}2Еʺ;@l:r ;Tw`˅/9ac}}*pԬ+_++_սȍi wȊʫ˪vy!1YFQY x쟇7%Kwñۿum_8TPZ(/N 9\OwilH]B*˪~|[-v|ە²:guRi˽$!6w{{o'^ҮCFoTˍGھBhИQ}_ Gr*ɟ +?Q%8:[J\VUgV؍K uJ|9a$K. 7fwT{'QyuOĤG<+(Xv8]w^ym8=j¿+5eAΆi$rrM '-zy+g~(?,rYRIK[*K"wQl'nw+K1%u(M HMY+5'u*_y lZ8iړmi~'}#il|Rwg $eٻﰦ6oLp8{VjsT{]-*  Ce F >}097!3{u? 5}רLқ-)r.%9)k:Y~gYeJia !iHTbӡ>fK'+WHu5٠Q~qA?|"W jxdm42ow|6s%+UvNrR UJ +U?@l窿Z~aUU)qWdǀՏK*[ov;)+*SK:^n.TTLqY5v3gf䱥oiF!ʦ3'H/S*-vymt9|A:ljJ-idiŊݑC CZY^<}7MUc+z%xOuUr_"꟏o~H7oK3HW|߶ܷԐ[IZ_9y,_^CPM6_jbÉ{gx2~ڭ8/KSY#62HP.jQ4jf^s%uڟ2 mR:g՟ `{ jxjkю? idHHig'4~eKHWcŹEeux'ltq^@ +*(#I?.YqI.%Iw^^fюBkm9XIJn@H+ }wkhpNAi)IUXy+{'AO?S/Tl|¹sGue}~3xᶎVr'?Lѵ5]'l^뺏#I[>޼be"=\Gu1wȢ\(/r}]Owy[&nv`h/c}/;`n;ׯaX, mU?,E"!ڏ2j]WP''t']p7Bڭ]~p[&m0ieORYh fm3fG<^Vh=fÐG}W[k5zX,IeԺWiuuBE˅~wTʟBxSuvsiI_L;w_1zw+Su{'AOn4bQQ=_(Q몎HsZw+%Gkաp Q?HZl0iřXуȤ9/8wvO]F&Ծ{K)Wc![տlSάڕ*;ȮBxŁ&'z7wIΚH,޳xW{;߫o0U1ž.U>ӕo#ӂ%7 ~]9q)W q ASfr66 _ f?gTU N\Z:WzY(Ods2ʅ?bmҸ|~ʃA&sORd[j_߄ 7=2 8坧[߳ņ//qcoeMg?叙-,N5E5Y̠C&bR^ ?Q//Gm-6mnžx:k{J0bAĽkb(?TaQ0XT MZy&)='V۩Fkn<]u6O 4Pi5Y~׃L"r!jyEeo<'`?DKG),S)Eu^ROk zIj^yA/e mjիbή }:bz#LQTַ%g ;ޛ0w.^Zr Kc?dG}x6A͋]!%K^{xC=FuhYtal73U΢wG缢Ĵ7 goGqV&LZ,삒–}D"y!ZV/R*Oɝ4_>[fgv 퀐(m:q.J7ѫ䕇nR(=[Laomz+ߞ"5DmYSxǙk/tilQ_=ܽB_&Iͭ};7nCH͹툸ӥ x8j"Ǧ?}/*'LL_5os3ryi9ܪ NcK۾6JMSԃȤ&IĀifkjbs+jhz/-,+*gs^K>xޘn=gdRzȤG$fT]D[ c ==6Kҡth PerOP+/dV63jgծcK[7~)HE'fd[Gӫ'xةEo2^OJLJϓ;AԦkӵt*CMҡS:t1&!W |'B.\y 2`mRPwPT"=O"AQnAջRtVQcl;f%?6;wnjw-D&%%%*TConknombomjcnة0&9k'vU2 %(#8=[,!3]ۉhTJcS}+3KS}+iTQ]4QTW 4i3(=^w ~,OPQ:r/S lef`j23dwV W AyW0Jv_"HLŠthLFEKDy3rɱMY_gI)GPT ^~Db2h :?m6bu;AEc:I$puVN]e1_rui:G~B{XyLs1Y:GgDjflQ3H9+EYw?KcR2`Пjd9t7$YH3mqH25w6PA'_$mJfFmxN񿿧ڙ6xs!&ɹ 9B S|ԧfs 3[ҧ"L?=;dǛSzz^PCN5w`EF:=`K]Y0M ?, d{gܝ!ϤS؁LurTk\S=՝~0n`mD}Lw>5`C,N^]P܁5N6`hiȔM{\;yҝf7kNvsO~ Fhaoj0^],b2}f„[nߛL"_:}V>ۓRU Tp 9j@p9d82˦S/N׏$Łu/Xw2|mw{GdP`Lurnjaݤ&e:|75njh. lRt3BKA lL:p\FxRUH Ya!̙σoOO==(*7nn_-<ߴ8f i6LuxEt3ceL}Hbce.aU[Ap,\l6h0d̘=z%ذJK 3gRZ[W|#͆dUB)ю =nE$1w=w";O6dpksyyu+µk II %VΥ9A8i(Lux~w7Y׏ַIDx.)-NX@j ={@K v۷a|xZrr-V9wGO`7NVžu7PZC켯ܛ81?YZ$.xA句Kp1:n9y;;x vt5 : Ql+S$AOsswT'K/:OkֵXsĠn$pmIe:@˖?zЩ =]$:lٺ+mؑ?uRs#A^80 i$Lu*Zuj($r&].aݵ7I+^}]}CP"Ru£|}aԨ/'wwo`0Ə\ssؿ`i{h/8~ IDATy O@,ApP$$4gi^֝:kcw=2ڱL Jy<͎֯dvT?WZ%)l!?@_ƌx_?hRS+ά\[wnݴ?vtwL#}=ۨ:::My1'W#0?߫[lOsc UH@n~{[.q1o<^O%m"OQ#psc__`2 6mI$=˺ UEyHp&b s#`R]4:Sh^\oYe{Ͻ+LI*RaFsی{3&0liCTƃ.=k  gXwM~1hց_zp0t?H-:p%e|Ncjn~^y;0X0ʡo[Sg:4j})XfR/ua]/M8w#:@;y x< k<<2~ ##;T5u%H%\SuYHalѶE~+'b _Q;UuOʜ2ɨs/G3hs__=DT{%$4@QQ(hΞ Aicqt1Y"!J8|&6#B`?l{s+'ńdfQz1qTviܾ{(RNjcΏ|TQbs{Nn8ٮcGRS`k+F"ܭ.\  0\]x\zU#JiϪݬo*d4$EN n~zGc.sC>i@o]Yn)0*/69W2i)z4VI9g?d*캤 ¥KO11 Ar2L#Gnŋ!8ڷ8{dou+ ׯ R ?f[^pxUH4P$..jbd2IdY KbY--Z6ӴTRLuNfqt"4n &긔!4hl+\Q2U2\*xNyax{Ch(_Y.]QLxQRz~bZ.L YvV&ζf[vje+E꾰#KctGĄvJKSKWY: 0;ͷI;jк5ph} κv8t>F"S3=6AgX :Aq\$. 81[G&OlQ=T'kIO\:Gձt#}%%St؋н;p@ҥh+X g_ArL=vNdnhd҃Ȥr<#vl6Hje<wYpEWm:08/iSO_;s)""kczcab8tdz=E A(l6Íp de@C5PT=չ \p5_=_s[sUDžڥ:0HUGzY4W<wUVXW/HD<ɗ4nokQ(nUwùJ/^mpҿ{ꂥ%0EERuֹi3V1@Q#"Շo]y֢ 4sXgr5Mu,[ Uu,1D"UGbjkbݜǃ3x>~yRB"%5an1y,k' Mp|M9 Pu13dauiC[$_ .gmGcXz4]CtJf.Z20~υ \n'WD408/~^87ӦSLo%\rʱM}jڌu_Q8UedFF/>[Jiduoo4S;'&\{tUglX#z@8B4ops-}<("kΚU< 6mQ휬2:!i(LuQ\q!N~m9s Kk*s[uLF;ؘf^Pu8GaCH=p]6EձԻ’6 aEjޗD7g:C^B2_Q(Ez*^Ahաwgj&r&4Җ~L>qEnQi%N"=\yNK˧9|ina߳;ژ@P۷KO(Risai{YYw1Lu Àh.qL_*|tȧB3#C:i+ $vn߇Nժַ};5O)FTOUgoS$D=/*i~n/vV&Oޗyrjtw\?pw 4YlJ+R/S^Mξ_hT3ՃN;?+*c32wT T:=)0!$+>%Z){v%| tuL||фI\<å b]1o>ƺt@O9o[:POXtB t~ի ^2XɠՃNN-,+`s,LVwK&3}+]-/P Y0Ԭv JQLdO&"*v*ܫt軏<g9hөbVf Z)`MQa !knۂie>l J@% H6щ )n-lV/[.W0gf䱥_ylelh|O6MY:mԐ[lrE]3'*X^#wDY5fc}7>1:Mr6<%bÉ{EnM8rUBQ3W] {?q[?~O_)z[q0(5H,$/yM/ !ϳƓ+cBԫ|p6Uu ivxHw W$/s5wi,{/!n|xkjv~d9"˨u޳Eu~r}H=gjG}2j݄T!ݪK/hFQhe[/.]SlVաUg[삒7eZ;.s6vwPrHF)Gcws%w9X/6rk!=9A<F:43d%Oį_/\FHQ @{[k5z}Uǂ~fw`V5rw+]ZqNpZt\GK{Aoػ[.bҘk;,-sv7uLNnF@ϕp11hk%f #s^.$ܪF%hs? 1gnqSfN}nQ&R! ??#bp3/E>1B[Q1 E/m=~.4dر91'8yqn=$s5ըZ+ml>%wښť033 yD"QE${N[sb G>C:i_RBA `s̙2e|:mhY "HH37Vq5\-!d2:ROOo…Ϊ޿4S&;ѦaiT! تCir-'L'Ν;ޥޱt]6kig!rKS=Z:"uWѪa)!FìnРA~~~ ]vϟ)phӦM}3w'U t_!]S[u!5K=zaii)ȑ#eUSSӱcsJҽ9'RձhB8!'NxÇ)A'L&SP.J|.qx\~WPrUXHb'T7 H:tCgY:t=S6sR ΫCi-زeKppg֮]|rE#bڴiL^rUϟviUeUB01`:ژ9ؘ[ jEQh̭!@OOѣ>>>+V(,,A,Xѣd-yq2BIDATEqi֨}c}6KbY:t]Sp?F@ _H[e\!+&9+_1"#9zs=&BH#T={9sf;v숊 444{Ǐ͛km休975w6kl[|vBJ^73B#C#SkC;c&BH#^?}tĈ :v8k,;;;cccwޅݺu t 85|]2)e³)ҙm-ԭ]};{to\!H2OѣG\ttK.Yp!PJrgD6 mbaY{,?BL6I_pqǾIE|Ʒw7ަ9@u _vjm{snj-$,dͭolގ]Ɵ"E"~u!ME+*\Na{ ݋F`c?Eپ`XM9W/!؁_r[[^^;0B"l= h s$:SXAv$ˤ< l;IDi^'o?F'e&sWrB_o~ԦM!}loI'n-*}sf BrowU3}tH,voEa' ɤ5]lFnƤb&k5cg/>|ina߳;ژI$D`F}nI͇-C>eNiO[Ph'зse>tVK˧SrF&psv`"4BE.:1wqa/r{V)bSysK^Mξ_hT3ljyl맬b.,:yvK=>ˣCYlƺLmwkb~ҫS{$gŕ<\iZ,}[>6?#SNMTV.m:5ͱ0[5-ccÿw5ըLCRZms줭:1BꭢS`ˬ ?[A!KCNaEGt s^QYM*"*1swy!|]MŒ\ ~ 0N.vwgs?%ܚjT&GS}:9n]}#05d426`Y=XW iŶ߾ZԂm٦)KGtMU-,v/|߾&̪>[_B}c12ʅ 4BE1$+?.O+*߫_v+fŒ;n )(-}>.SSf"ET- F+^y / !b@?%]MprFh9ؘ=MMJҶ#=w|fe2h:>kꐎp9[Xjom:ch'yDͧC->B0kQˎs_fV '-~ޔnG)x`-i9o;Z{4Ч\%*!*6t?j.m_6J!!{jߠOʌ !IEfO7&ëtA/TZa&BH#Tt`gBOژV%{)um=J0Qmn!]ybw_I8w'rL $6bR t#Ci{ѦwqBBtiʃA|Z]&:eąBM|z9,-!? ydueW_-k`"4.E,vy@@*!G>)@(OaΊ 4WgBā?zn& RxN"BH#j|Ψ[6}A Mqtj%G\(>x9'"HwͬZ*~8v`"4B˭M)k_{srĹn?2Cwv/˟vni}߶*v`"4Bm 5}{{)YLG=ѽ *@K ~bkXͭMJ؁_tmn}c2]~ 2ܝsw"uy;ziMSBGˆ!/D&Jq$x>S;'>t";0BMoZ4CFDZ!/n<8L"Y72t1ulb`cDQUrQ~1'!57!5}Jnbj^Jvaf=tiao`!m#MY2^yg>{UUxb$L]W=J82+(.UO=$owG'Vba:BՋTTI\~\z;tshlBS]>r}yTT޽{k:I&drߜd} y4&ylﹷ9sV !T70)_=rBL/((-,yչ;2>4idݱRZrkƭj?z"R_Kmig1}hnDHఔ Æ |r@@aê?[}KD__Fn=j5O 6ӇtlӴbFQ\~}6L6̶:! *Hw*,QTsd!O Ҷٹ'_1\ҡOYNma?tё#WJw/B?lUҥ4iՂDmܸqɒ%v/]ޮD*&(m=͛Z4B} bqƹߺ%oڴivv׆XO"$nZ\5Bj ;0+P(c3o(vl777sZuV~ɴ5Bj SݿV^ݬYȵk*~ԓ'O.] GUgɈd:[ѰenOT/u12zM6)ҵs8S!Ai~<~-Cw.sai u;P!Q)E!$:YӧOz---gΜY,A۷o\`͞HB4Udճ̳N"=\y׌2[|ZW:X=DBTjT|r:Sf!FEky};7_Cjɭ-*7_ {:"P޽5uq?7`$]>j-:Na]GYvGWDVFDlEŪcG-[0D$da6!{wqϽt& Ϗ6g%o=}G.u֨Vm=-.q)Z\n ='fo9ڡLM ˫qu?{j 梃5G oǾw[-ɲp]gg{xh':T:VZq5࠻0Ӻޮkv/p~nu]Jc.^㰨9kqNwau݆.1t,U ј@:')*I}Ij)}/ܳGH$r!ެT4,CPJc79ï ٭_WB8r@O7rrcKs:wU/*F Luo} Dc*1wxA]:}I,u  'f 0J!o\3 M`PB3D W[V8_Mru;O-OJx&jXgg40Cz1* i+?6oS9?F861 p]oc4zp0o IǺFw;Ymj:Sִ`LYYЛ@Mg^oŠw{j%crR=Ge(Ad]y-ΠcnİZȥqNKpJ4YlC[(\R|vݮ8앋.|]RRlD.Fq5+C-'iΧtB>:F#l-'#56oEY=5jD `z#MtvG,xTL&ć:hޘK3R+kR3~H/2~juGp)kiDİk+$-sgD/#:5n`βD^iM{*%-  sqnA^wSbl=ܒo4ZasW4| `>G;BxnX?x\ֿ܍@o+U1PF Gj`G"4xr?:s¨IENDB`./pyke-1.1.1/doc/source/images/plan2.png0000644000175000017500000001267411346504626016666 0ustar lambylambyPNG  IHDRUsBITO pHYs_IDATx{Pǟ$K DIbEֱN^*(-V-ziXZKPG tS⨃whbF(.ABi!B6 f999盳={v9g4MJkNccL&ZKP\ x-B@ ZE鸖`~;FTq_1bD}}=*L Z31;1;1;1;1;1;1;1;1;1Hff\.O0{4!!Ӗ?rʜ9s$g}1GmmԩS-ϿgϞZR=|P*FEE'$jDRg@jjH$*//ΝH2:tnĉ.\x7|}}322Jj6mrwwH$QQQmmmFu S'U6@ch VS*Jrܸq$Cnn[ZZZ"##cbbH%K[[[ܹcTrRRRAABhll߸qQ]F®^ʪ-kbICWߺj;V)">>>iiiUUU&K>nhhf*((xy6 :0۷oߞ>}9rÇfL6D"H$ =Ev횯oAAA?ǼcFS^3x,f4M_p#ooU4u] 6똁<Ri4X,H*++WXatvvV}V-..^޽{^:iҤW-ז}E,cv T쀀<f'NRte:.))\agee,Ulw'*j`G~VBt t t t t t t t t t t t ~jVTT$JV/tuuq-4v?% :fUTUU%|||V)P C`ݺu\ Z^#1AC& r ,i@:Ba&t 6Eȗc8a&<+1`X:"pÀc ,G } 6E8c0`Xc ,lq :"a&0`X1 a&2c,1 =ɶYw 1Q(...+W$:c֬Yo5ܾ}^>|Օc\1T*]nl߾ F s 鑸8Tz%ŐB0==~ʔ)Z 7cz;v>+%%~qJ>*Y ̛7>S6m׺sY颢"8< pJrԨQjzСϟ?1cƍ7%L&#we?]: f靆QFi43gp-cXl9~xVV*Lŋ;44k!IOO1b*R͈oJ?{`QVVf}d{,[l\+WfpUggg+׈#_蘗̔V$ytL5wBBӧi -?jkkNU0W[CAcQ PUQQ9d0RI2I&0oxbPPX,;|@ iZ(u:͛e2cttZ6ˤ<6lw^Ӥ$ۇ!GᘟP(ZmDDD\\RT*ƍ'rssoݺC,YښLccJNJJ*((P(7n4ˤ<߿P(˫HVıQl>lMV *I(lUUU,dA.$#Gi6)3Ϙ1cUcLI6w}LKsrrRT$͍l8::2ɓ'sssCBB|||Μ9cT'O&L@QH$ úLbR@}}Qrd 0,X`͚5zٳgفdhhhvvvSSSFFի>j:i2xzz2K^:ť54X,H$+V0_NTTԃ:;;zN3tժU?jіh3)`ҥk׮miij?KpsNH5EWJNN޿?*?  츖Vs@MƵmmmֶsǏ44ycǎi)S;w@*i ch333`Ȑ!|e…dVh4쎩${}Μ9lKHOOH$0a„HbP;_~!o9wqq-ZSXXH 9;;=zoE1UUUs%]|ɓ,mҥKNd9F:thС0|~ ?z(;f̘>,v\y{GzE566y%%%&LDs`qNKMMuttOOSN_]Y[d…---WyҥKPiVVT*QF[F0Օ.\f'OXwW)cݻB\ee:::Sf;20ѱm6p5n={v᤟y&b^  ׯ[#555ӧOvE#1 "?V]]][n 0{솆 17o $Ml`NNxxx\zk9imm]v-9vz뭂BCCٳys˖-]]]\+bO`vEsO^SSõ"KcZZZbccɅkhhhQQ׊Xa:ܹs#G~u.pQT9?mڴ. cN<)駟~]%p7ihhOH2sr=c~WnT10ۊc鵵']xx8B_±c8ֶ}}ʡ*[0p8%%,CaR9FN8܎#78>/1 ?~<L4ɚ g>|xʕEiZPz;v455UWWTWW;$O:%Hƍ7~Ƿ677D"Nwif~j4ӧP&9;;d2#a6lD 7ߐ #Yi`„ VfuuuV#G)iӦ1^777rㄎ,Ç>,)))))1,ȑ#{*z ׯ_!hWr}CffKzISSSGG*Ȭ%>dhHTSSõb֬Y\1cƥKVDGG8q[1;1;1;1;1;1;1;1;1;1$ꧯ Nlq1_wY8ד"ʻ"nj=B! *))=z4@|el''MTTTD;::/_Aݐoe***"## T*{0008:n21::ZV@ HIINNN_|ŋXT*JWX xbPPX,;|0z"DŽݸqΝ;goo9~zxxa6b[CDRrssoݺCk׮1_W(Z"""JR7n\||IJJ*((P(7n$yyyEEE۶m#[l{QYYYee֭[`ɒ%yyywa,B$!p¢EhOHH?>M .dW| )jj(ż]WWgvZޫ9s&F kN{} iÜ^^^d{ĈeeeddĈ4MUUU1efLV]]]>}:l0Z,pғc aD;^N}ӝAz_18ED"HDb9 J Ç>}4$H8o߾cǒ7Dӳl̳`5kz>`lOOJVKznL\nN njj`&^+%'' DsINN Ņf^~}SSR3GшbDRYYb VxYjUllǏZmqqqtt4I_~=-^$FEE3%!**z^h.3׳Meeep]ݻ'QÞmΝdYFYlD"d?E0-BA2"IENDB`./pyke-1.1.1/doc/source/images/rule_base_categories.png0000644000175000017500000005610111346504626022011 0ustar lambylambyPNG  IHDRZr.sBITO pHYs IDATxw\SWAKY(ā*TAZ-*.תuUE:Zh]HqEWr$܄Ν;#Fׯ߹sDil$&44Ąb:s _ {{5k66OŹkiiiii]~]A($e`x:1`ϟlݺ)2@c4i$g޽ ҥK!ww\WWWn߾f͚5 !tqI&Aq###?.SZzulllEEEEEEDDBhMHڵkL&sذayyyL&ڵksɓjjjݻw?~piQWWڵիkjj騑(Ǐ޽;ñ=qp2bΞ0a^Q'&&|Y1@X,EUUU?333)c0M b?ݻ'<֐!CBO>!v*$H2nZZB&&&MHOΣG=BI0_3{=<NKKKqqDKKO>-z=/y d»k111-掋q8Puu5S C3 OÇ<@v%IhF&钬<+ʚ4i )%~=B?i###PFFa/0WAAA^^^&Xwp8]tIIIx999'N*5w\ ;vTUU_C֟'"SLCŇц V ӌzkB.ʓEv8ԩS!#@)4޳g4uSAN>|{ѣG۷g NB޿?n8uu>}% Yڵknnn&5TAcĝb||&Z񺱠 b?#bbb>~rccc<-K=݉Oǎ\ׄTC+Cc:w,|qO'O&F)yeܸq=ro޼FpAb4… 7oNMM۷E|}}N (( F#A_Q <F|ۓ63v6EQ?nYdsPXx]]]II nX,|~ii?fKWSSmWVVֲ8m.[^^޲n⛠%TVVⶆ$qUUUۄUƵ544p\,I?$ !Oi,^^^rq[WW_'a\OOOMM x<^vRrusR%%%B ]CCC&ٲϟɾrvȁI ֱ%o(,,$m  qC8Yx^xQQ 9G$5~ĻtB/^ qOKK#^zxrr2;;;8sqq!$www'x$8"q|UC$K.\ q????s 'H<00ď9Bf"xHHݻ,X@⑑9sTTTPrPYYxbћ6m"l2߶m sN_`ٳΝK ?rO6g ӧO?ǍG$_xll,ׯ8"}6ݻwI|РA$NBQT~HܠEQT޽IKMQTI$ޥK"qkkkwؑHԔċH\T{p0I^z ݿKq;N 7Bf m$TAQQѧOF |ɧc I!Dַ!AdF$N6BL&ąKp\x~I'8l6ɸpjjj ƅ'"Kp8$N6QB ƅ^n$d\{ oxjjjjkk8p@KK ?&F6o?6IIN_:::$.Ӓ$.68p\xx==&!"ÓMK~ Dž700hR"<$qCCC.ڵk,Nٴq7 '[ڷoO}d oddDfddDE'V8.7Xʌ)И;w"իB7nG700SL  ;HBKKD 蘘4t.011ׇF@*SR2 Ra#(r--- dqKGGGN^9  CtC:tn:ytu:ڵK eee:t֭TTp(2gaa!|* dZXXu ة@srrBtg>PFFF]yyyIIeL___6<(!R$rssyB=(/33(bbb6 ?(oÆ t@- Ay PF@*lkkknTMM‚,ZUaaaUUY4LOOZe^2bhhhmm-R@$ɴCt^%|lk׮tھ+2vl >6vZQ]䭷HSR2 Ray!Թsg3ӧO/ʖvqI,۷oL&Ύ|8z/#""lmm+r[[['''ة@QP 쌌y 9d2p;ӧO.CCCCCC9򠫫'TT/_Dӝl㼔,ތ"îچBCCCwO>Tْ킡-[ 33b1u@w>`)9~xnݶl"wխ[ˣѶ=u-S^^ޭ[|T|;T ֮d0,CeeedI =k֬Z1߾}{ZZZZZZ׮]CQaggp:uuVX`Xݻ7h1hhh899YYYl /J)\.788,ޤƖ+Hn0$O@ ؼyMd ݻx4Ay˗/GWUU-^kٲeO<:x BhJH'b CXB說mۆ5kVeeڵkB۷o39Ǐ;88lٲE޽aIzPl̙ϟGR -Z8޽{7n[ZZ g\,-"7\5Ⴢ***ȇJxbKٴidP啕988 8P7rss\(y<BȈjj?d^^cǎbe/^| !MQϟB;w39\F۰SrfX--- BNxH $~[nn.EQ߿'7\5q٩S'KCuIL2b(W%X,P]]EQ|>|#TCK!{fb߽{7yg.]~7lѣ,K@E h?/Z5x7\5Sp$1JZF(ޕ.,,$b'NsΙ3g\.~-AIC;t f |[n̜7oߛsrr'?"Ekr%pU?gϞѝFM2!QSScKnmm->נ㧬۾}{uuBSN3RSSCY˗//)))//,h->}JOOϗG煅^(B;w^\I>?$/'9{fOKOO9B zIvkjjfΜI1\boo/r~<ؐjjjΘ1F/_6l{pٳ'=zL"ԫ{[S_jn>|!F5\5SpGr˖-`Xׯ;birԓ#e2*Λ)~qQ?6 @[YY+;# ݫW/ HȨW^̙Kw+?CBvss{Exx4bKWW_ѝ PSL'a޼yE5rB|ƌ3fhd$tttpz Q ;%%!ԧO3(77XW𡠠C2yKKKcXL''}ҝPNrrr\O?ɣs䡢iȐ!SR2 Ra;;;u@&&&m2gaalff&쬫~1 CBBBBB9򠣣'TT؏=BߟLPSSS1655/ܼ< 9ݳ/ĉ!cc㌌ ZT())IoݺL:ouu5~_MKz %///)));;[{.)))''G1Bo 76/B_n UOJJzB vhkv~YvjMnTsO<?puu'BVQ2׍˗/Ws=t𬰵=ra+' -[W_>~khhDGG4(;;{ĈQ2z,Zh޽/7n\hh(˝0aϟ[-I/b۶ml6{IjNllӋ/坪 bѝF:w܄ \nxxxdd$nݺuРA޽6mȋp< 77Ν;ˣs+++777KKKytn޼ٷoĎ;޾}{ɒ%ͺڵk)))G_*f 4Hԟ$ʕ+B2 Ç8LLLdmFF1Bh„ -NC >?l0Um)ɉʒG޽GE DDD!޿/>}OO͘1C ȶs޽ \7c\C#}o>|o^+}oJm%%%Ǐǿpd޽{ ,G*Bݲe >yr4~aÆ8;{ofhhxE|y<>hРAQQQ8F/}݊+X,֑#GfϞ-n=<<;@0y\Yu T١C\]]_~ݯ_'O;Vy955͛7o޼Yyxxxzzҽu,x5fw^WW7rH=<<+Ώ9q yt***Nsέi>s v%**>ͨ3fL=SdڹsEEE\jxx>y=z@c}ߤÇ3GmOo` ,@_xQu=555-mҩS=z<4vڅbXgϞ+h]]ݜ9sB/_nOܱcBPNOmRMMܹsԩS?0}!Л2Bqqq7nܠ; ٨1cBHKK`ܸqQݻ/_ʣ󬬬7oȣsEggg?x c!MM[nѝrxqqqF|)S1۷oG㻕ϟߚQx"~`ӧON_:::ߧ;%֮x'Oӻr Ѩ;wNCCcϞ=gϞm͏J燇7x?ӧIݻO^QQ1z蔔Qmr܉'={2ֹs\QtJ\F+**NiiiZZZcǎѝ YaaB?A"w.UѲ2wwwŋ/N_;JKK;@wv!djjzMiϟ?!T\\Lw: ^r4]ԩSff&4`֬YnݺӝdggfW^aL l߾?0tȐ!JS\PP~x|ᑖonnNwFM0akkk'LOѹ<:)S̛7Ν; bݻwv횾3g̙CQяfxyy)Ǚ.] ,WaH/>Mz^9stݻEѝQ2onׯߧONٲ(vAw.@N8JϞ=;9@br IDAT^\Ѭ,+++^`0Ԓ<teQ]]l6mZee%h|9֭[E+==m\,(&&&&&4(x򥅅BݽܤtR%9(#MLr_ cϟ?y<:o1>f͚*xVVV߾}B?- ` /44Zˍr Bqر#)Ϟ=ïhx<|ȑ#4"tBwjRF9rssS'9СC d?~G߿ܹ3n'8zxx{yy]|~Rjl6&&&W^ݴiB[n_.//;;>)dɒǗ|׏?7Y@0k֬ .  `ff͛tkU QFӎBɞ>>>ms'Od2֭}Z=,̙3YYY'ND#""BMM-22ܹszzztH+W0L|KNu7m7o"vJQԃym__Zz!33<|={NJ"111GNU7S|Cz5cccPN"B!j 'OV3AAAk޽{\.yLzzȑ#?~LQT]]BÆ ۽{7Bht'DL555k׮ǚFGSS!q]]݄---&zkMv!~gɓ':ӛLw! ]XSNEߟ9::"v܉y,W[>}wܙ_WWv= јO;>}:OOO_~2^r/ǏV"҆fL&ljKΟ?൝y@ x!)|(Bf5ɓ_}B/cǎ;Ə/cM 8}4]vx"y+ZBB"FrőwIt͛7x{AwMJMMի;xEIQշoߔ&) |򜜜O>](UV[[{'Nr\~nn.8(**Fmǻt?3驫LLLfϞ2_Y,Bɓt4n$a0FFF0kkkxO>㏮"/nj;5RF/]TYY*++ MHH;xQ8ʜ5k޾}i&###1x'ذaBڏ?X^zFDDܺu)ڵkn:!J=ILٌ y|;B pqqa0Fsuum^~Si>tsׯ\~>. *ȣ--- MMM @,Mσ :x`Ɲ1cǏ֤޹s璒'N믿1_u]Vn E &Cwt:|?OϜ9wIV;2F3tPW-,K沈oڴil|$z!GPRRR^^^Gsrra>߿dH>{ĉf!%''+1?_-Ǐ;;;K={J8xQĉ׬YӒZ/>E2#Ga>_ߣt/܇dҏ_ΡA R{@yWPs zӧO#222zxBhӦM?NNN.((PWW_lUVeffz۷W޺uE|>oU͛7޽[RR'2]'(|yq~N8N``I$z3(׋ !hѢ~I[[)Z` W^/qܹs555޽ (*<<ǧ0??ȑPworHѣ FtR/^<''lkϴIfŁ\]]-~02Jyyy$nee"#z _xannNQɓwܹcǎ&CxlsBBǏ]YYfEp`?Mα58V!/_CTxHY$/ex[qNo޼rE͙3_cu)22ݻw eK54uSaaaۙ6Lp`fX,_]腇aX|>y<.%Ν޽ϛ̖2d oOIF3TQ/$m͠A~W@?vX_WWGQTfgg7oLMMB/omH.(<̄CVḅG;vLڵ޽{nnn)**:pܹsB:tS[vO0SSӷo6ihpeedd=zÇӧGm{d!$$dYYY|>ٳg"/^pѢE7oΝӴVι `=K8ZXXgɓuḢ>"۩)((CG9G66u/^x1ceb̘1މ#GfffxtmL|^qS/ÝM6YYYܹs"TWWUWW2̮۷o{{{OCqv7nܨlr{nP[sӧO2Q?OI&MLHz..]$ri{{{6x5OibbӘ)Nx2e ͛7՟ δ6\FDѶAz''QIݻwʕYuhs;mlld՛IB7(͢e=k֬V04**͛qJa…֭rιt#Y(Νg͚%\N*ZHHʢD5WSSO՘Eg޼y͓a40>tBhtg]zz{"?~~999ɼs?~0̙Cw>@ ܸq#88ٳ<:::88866V SR2 Ra(=bիWHH<:0`@HHyX=ᄄhjjGSN3?Ue'TTC}7tg_Իw˼>~x2yr?:{޼y C2{Br .n4şFzC޾};,,,,,LeҥK7nܸqeR)Ύr\W_ɤ+ 300h=/_|rR tg$B^4"cـ8pNE[$׳gO;J~2 Rcڵ`,\LTѧO&&&:99 2D߿Ç 0`;@jkk߯ ;,e4!!!44ŋ<66644DPSSb Ee)O~b (()vXXRQ׆)h߾}†*6p@yt>>{?#55ǎϞ=m__ݻs?~|np… /_m??.]ٳgp{ĉgΜ!'o _޾}SLԩn8q"'';vǏӧѣG?|3g433Çٳq?~9sogܞ;w!n۷ϟQQQ帽pB޵kWee%njiiΝ;q{ɒ%긽}v.dm۶m|>-[bp{˖-'Zb^(ڼy32L;^WWuVfK.m?6oAօ76?|]EQrqq;ܹ!&LM6Zlo۶/^L;w$qplϞ=$>w\?p#GH|ڴi$?x@@>}H$.>h/I<66ą_1}u$۷o%hw%qyd2VVV$Hގ%\MF*/E͜9Tj&&&$>gΜB&s}&)?~qq1n/!`2&PRZx1. EXl%+VMbժU!|vbpUSS#q]#Q$%%:ujÆ Zr۝;w )Ȇ6,..pBH l߾!~zS4l)>>COOnݢ;/@3X,Vbb^ijuVff&B\ 8qU4<8zhmm<!`0< ZA\\n{yy]v|Ĉ ;و#ȅy QY!4zhxʈn:sPG;vw1pQFɤ+uA@o`Ph ڵ,hfСCHHY4tg@e֮]KwM۳gQZsڵJQ߿vګWҝaÆ7ҝP<ذaN<TTyy4~'!Ć1 0j(N=H(Hb دHJJZb^ bnٲe˖-t8/|і-[n޼Iw"(ة~ Cq֗͢Q s֭[n;f555!٩S'l___]]] QFWx%KWG]]݊+LLL*++[sܶn:f̘Psҥt֬Z*///33իWo߾]z5?|099#ƌhѢݻ/^!nݺ7o"nڴ˖-k͉ׯҥK=</yOrĞ>}rrrjrHssW^/)B58|ee1EQ;v| GVVV鸝ojj*&(… N177B䗫_޾}۾}{?p4V;҃$T}E2b|>nx<6M Ru}wwwmmm 2 <"Lx,6bX,$4ʨ_f͚ZbEvv6#~ѣӧOG 0 --妦_O\RUUgceeEz3.3WWW(E%K|t5/_ю;9sX 5jii#a2/K,U_:t6. !FFIKKS|'}2<^&Y,O gϞA(cǎ;v48A/^pѢEp8o߾ iӦ-X%%%-%$$dYYY|>ٳgr!O<ٱcG|||k~(@5///Ǎ?"uF޾  Dں!c.]p[OrqFSSS;;.]tqÆ 9zhxx_|1tPлwΝ;؈|!C >\KKkʔ)Ǐ,IDAT`bbή066FeffQ^|NB,_ ZS۪N!6@HSdSsNPXX:%6sAx;v0c 322x<^ZZy4HpڴiŸF sNP*^F#""e! [PP`kk+fw5y]|'7o m[prr #@111y444CBBO<@N|||555x CNdcc4zh)gϞ*%2QQȼ._\ZZ󳳳ϟrtt;/-~K[Y,"u0-Z;@ -N4˞jjj'NLJJ;v,yݻ#3(O޽b7\ѣG=,T\7 Rc@\x֭[tg&rvTT<H#**O>/B@Z^F4w޽{;7/;  J4xyѝh |eN1T̓:ĭ[͛w@0?ҝPKJJ; msҝENeKl!Ć 9KKK]/ؿ^d!m?H}|Gt'baϜ9rѣ'Nv``СCqرcw3fÇݻAAA >x̙3`VI(('Nlڴŋρ2z#Gඛ)?3n{xx2/඗.?G~Eex"00!JKx t$F 1bnM۷o;wnPFU {ƌ hO>Ow.-:}t'^?>tP% رcǎ; 222;#@N=P7y_Ne {*MND ?@w"(ԩS(7UqرS޺uKNٳgԩ>S@aAÇ'O̔S'O?PX)S~=իWLr)@B  L'69z(n֒555ɳJfϞ:::߷on̛7۵k۟>}Zp!nGFFvaaaXXnر?|ⶅŶmp˖-N:m޼߽{rJܶٸq#n~zڵݥKuW;֭ۚ5kpd=z>gՋ_ܜHdZGѣGdHɟI敫+IIId޺yHaÆ֭[4Ç1cnő눽2pիW?yd@o\X,\.khhxee%xII xQQx^^[XX>5~ĻtBix^H<99ĝI|xԩLydEQԝ;wN:;9!Z; ` i X,yP9٩s8%q 700 qMMMo߾=kkk xH\OO---IĭI]v$nkkK۷']1;88);::ӇIٙ;vH'qKKKwqq!q+++wss#q:t(wܙ===IΎGA]v%#G/}Eʩr6ܕT (s÷o߾}%KНKa0*/|>_MMMMMMxYL=`322dۭ[jjLvU'''uuu{{{aPF8|Inܸ!w5i$MG޿?l0;6 @Vb̙3lR^^{mUɓ_U\l%%%999Q__?<LNN(8x֭ nmR\' Z|ܹsBGVE!Ν;'?CsslL8heebiJccc:u)|VX,>/~H"`YYY81(իW8 sss[^^yҒR5gΜ_U|JbHQdZ\F|8f###KK]۷o_XXXr_dZ\.V?f6a„'NWWW7:eQE#\޽{סCrlvUU[ZZVTTvǎϜ9S\\,JKKEJRll$;رc /) vnnn(Q˗=zԯ_;wWҗQ86 :u4h |C޽ᇪ\rS/2{쬬,>31'⫫8@p8o߾ A .+$ٲe+V~:}5 ^|)>I&}ʼn-^pѢE _~˖- TqZ_(իd'''6miigͺM6YYYӧXfΏ=[[[6mcc@Qӧlk$韢˗/ݛّ-߸q+++LibbyfUX]]׫K.YYY }2z?XܻwONϟH>޽{W\)N=JIIpBvv rsspϟ?燇J8ViiiTTyhfooХK==K2 h߾… Cx3(fkF%Hj2 TŜ9sz-  ‚2 TE޽WCg @@b_}իWm۾{ɩU;9㘘w5ee<U78::ʩ˗O6My_@ Z (PٳhZ`7nڵ+..CCCnovذam6ҋe˖de˗/0RSSI믿&~ӧOI<00P9Ee;{I⡡pW^$dɒI&$ל"2 TٻѳgOJ4vi>}$|'hL=H(H(H(H(H(H(H(H(H(Hnmݻ[C׭[|RPFAKV7ߴ'N=H(Hv #O.>>^NEe]Wܾ}3PpSR2 R2 R2 R2 R2 ?$3?+yz⩝L@"F0pb 53!&12e H2!+;LBʻһ||'M=N~z |P )(HF(†Dѧ#11q߾}CCCoFݹsnP(.hi޻woyyOc={vQ:|{Ɨ/_VVV&'''''WUU|rIVT=\a3 SRSSM&SYYLXTSS322NlR\\xW^ˋ>7jXG[ZZ 7|\ovIM?+L^^ss۷&''Ý::: kkk=ٹsg]]СCϟ?yXUƠʉ'gΜYGGGmd~U]bq:by~[,p%/8_OfeeݻwO?0&$$/^zfff۶mYֶ3?MBf{>999}vf\mUUIKK[r A|>_SS~)۳gOBB(O<Btww_~n[+W0FBQ .9rŋBt:W=zd6M6YUU߯:55tᒍFcEEE__>,))9vBӚ ! {zz^oggѣGW!PbZw}E!Diii]]x<&n'緶|gVWWWVV/A윿;FrUUU铥CCCP( *cDf}6nk+*****~ɦvڥjfff{{`0ܜe0 ._8Vnw8|OOfSU5''GUM.]cUU]H6Jbٔ5(#&2jP )(HF@ 5 RQBjP )(HF@ sgϮQ^ds(!6jw}w/E(69ټ o@ 5 RQBjP )(HF@ 5 RQBj O"bGGGqqq$WDtQ>}:::gff";/XUjjj~^ 5woZzɴK`#&BjP )(HF@ 5 RQBjx)7j2EQE1 }_hK$&&۷ohhݹsF5 Di^r8EEE~a]-iv[^^vc={v](.{O`0xԩTTVV(mmmVUUU}888;ߧl6^oɄW^}Wiii*99ɓw | \kI&fddD:΃&&&nٲ!^}UG5 /|ɟ777߾}{```rr2..^u@ ЇIII>qFJJ|.\7n,閖dDNN~ۗ܆iPhxxfݽ{w%;|&S׷gϞEQY]]]YY922߿GۇÇO.PVV_{^S[[ƬVݻ/^(omm|ccc \n~gg5.J,-- B`p%`MlT駟ο  CAA,z@ O_~hLMMmiiY fz{{vivRU533]k- !TUCl;wNtҎ;TU˻vJߌQjQD1@Q )(HF@ 5 RQBjP )(HF@ P[[;]kG kQ8j܏??DrEu(69UUs1jP )(HF@ 5 RQBjP )(HF@ 5 RQBj ؐlw  J&Pؐ룽 @jqqq6s B +&IENDB`./pyke-1.1.1/doc/source/images/PyCon2008/0000755000175000017500000000000011425360453016470 5ustar lambylamby./pyke-1.1.1/doc/source/images/PyCon2008/bc_rules9.png0000644000175000017500000013316011346504626021075 0ustar lambylambyPNG  IHDR@ sBITO pHYs IDATxw|l/MM#AR t(E^S>˳D~ҋti{0LffM6Ι3wns0@נ9\p)R`K;.v\ XaDF6+HrR;v̢zJ<==bqRRRjj d@I[C6 >B&***F#qǂ0GŀAڄF )SL2dv EPRRgmٲE.whɘ1c-[裏t[֭[ !-[h1Rٳ}^l`0| ;'B89sfɒ% :rH7XؗY F?//, -,,S bob{]4%RٳOΖJk֬4]h?BdF#9?ƍS?3! 2 vA/O3mnn6Q*ڈ#jjj*++SSSomlrR'HSRg;f0mS?XcXw  2|M6u7K /V+s6l0!o ljjj*} o\x1ŋ .}] 6ao:ʕ+|Mrr2Y]7%Y] p(RXR`{G(/ZrV oߏeɓ'Ng{p6aÛ8p~wWņBܥEXLtU\\.śoi٤3g%$y G.344|h4:?nb ξc-Yva:)+uzP$ဧk楘0aeZsm2,X`ubҥL_!$9 Ƿn[o j{NwVz:]-+mDB9{e=tPk榤.rϛ=m=ㄫqҥ۷o .d={,]"4ͺufϞ ^EPr: kN5g)Xp8D[PΖmr6@ X)Sƍ#/p ''k qss 5k͛MW'ۇ@ УpMš|,hjj"\ᇶFr%r9K.%۶mÞ u>k8>vCC{k]\ؓpa>o_?N$ s̱q;+t:___۷wt6[b z+..&]7ŀhF1--?;;k|A;m+HBaȑ555jUUUmQ ;VӃ t\rDu3\ 6#LtyTjB_e &Ryr9H޸qC&Ik#uѢEvSZ\҆yٳglM.K,!uvsszjmp=`(vD2n8r瞞=֭[ZB߽{ʕ+xp),>|ؚ\cǎdEx]]$:x Z3̺:یq1`@7mooӧOw#kz]wP$׮]K_$>>ȑ#5.E׮]#OQ/Fs+ qqqD?_}0m;Zb-YYq EPXX3Xðqm߾ r\kD۶YCgbh<|Ν;+++z_PPPZZ#<d$&&׭[o B?7m6i[? B '11qD]kCюT#G={633^Vs\OOOCN4 p(R@LF{'ryii)YaXLL Yb4o߾Mh4r`:B`0ܹs,tz>//Y Ftt4Y޽K0LrCV'KX,9!h 6ABᄇ%*,r/J,xdB())!K|>Q#DŽ͍:(@@kJdP( $K$IEEYޯ_?!ANi{5u:ݚ5k&Mrr8N\N FH$TWWSt(:TPhh(EOJ W8p Edy]aÆQtL3c^wĉ#GPtMF1O;k,Aљ?>EǼE(:DM'xq,EFFҥKsrrL׮]C>W_}ՆxqsΟ?ጌaÆ_\!T__O J$!>o4C (zL!f)幕J7Y"jkkɒfۛ2?TWWGg̍UTTP؂)s~EEEdIdd7Ȓ;w$&&%dɍ7(y/^Hddd5,IMMTt%ѨQ?N={vʔ)dɸq:DӧO'KLw^СC?0B(--m׮]455;wڵkbUUU䳻w&G͞=,ٷoYFT"WGM2EP%Ǐu0aF!KN>`x3f y#(9 KLSF3a;v,C!PH655QYRWW7{lϏ,TYY9|$00\!TRRc%aaa۶m#K q$::zӦMd۷/_NdINNO?JHH "雴-[<@صk׿?7nJȖT*}WgB*JVSsԡm(:ǥMspAuF :Z+:SgΜ3f5پ} /`g:thmm-e]rnhnvzz:˗/K$`0Pt.^RpBϓ%ɬ Ey^Ey#t:Ey#4 E R 8BHRQt(nBHPPt(!\N1cIRqGskCC>ӔH`g}!$J/_/5dddOD"77wԩ 7X}}-ZDۚ*TFdO>׆3fp%@/g%?qF:dsmhh R`x'ju^^Mq|ŔJa#'x76o/:ۖnD;v7oުU@b(hx<=*N1 X,0LyŢOle!r;xJBSt(a#!777yi`@@ѡ BaݝcBAѡ <==M:h;vP;_{'|!Zl|?|{;v<#ҥKreh'Nlذ޼yy(M\^y湹ݸEݴVR}G\s_V.S.4j):4Z,,ߝ ͦP6!EMѩSt(Q%"!EͥQtLIII+WPtJ1_T=z4E20aEѣ|~ө3f8~M0C,YB1!TRRBn?/544Pt):2ckdd>GT*<Ήbao h4&$$ܼy,ܰaʕ+m0''gҥ׮]{bbb(ހHoFq|/X:Q\Uh4fL^xoܹs(9pSO=UYY/Qʗ$%%Qk׮QcPdW^S^ڕ+W(sC 5CR6`O 6|81cðC%F3OӇ B ʯ/t8#&BhZJ!NJJ"K4 eá4T*r D(l|^YPPHd2ӷ@ {?cԚǏO80J :oghӧ=F;.\hG#F4|﫯bL&{뭷 2hLGnOV^,ŹF`Sh,*****:x`sssW\ )Luz-aQQQ)))E |/+p[WsM|||؃/%j}}2Xh4 0 +,,,,,&MyQ/wmC,|N'rH^IPRgGQNNj||yqN\.Gw= 9;z0R:,w j|6iӦe˖%+VA<3yL)BHm@BnG@455yxx87o߾Yf%sٽ{=̚5g``}G7:˞\ nݺEje K噙dIߩhYSRY b:ېaUCǪ@/tVi{&eDk9n1y:+=نlw)Z삜bf;8?~B`Q~=LȨ4v/8,0}cl&LC :g>-`{T+R9"n k.V5~>u=ku'aÆT ݐ3BCLRGUHeU5<3,n1 X, pǚH>6SRQm;ME8O?Z=Qm,{qF\rE=Q4R!?ve0.^k:kwMBH)>WR(huxN\ӧO>+wd2Lf+T?'Brp)\bSCo4,? рV (pv1Q 74R(usת>vhOOƖ+&Ytav5 p&tV(3BoMq:*sZcR}!ü+7r :%!8,Gmse?fY,Kgu!ɠwׁa4cfoO_P%|>;'n-㾠QJG>ځ΢5?;UP#p%=ХȬ% }qkv=7L UuF=16v:]q?[qA& WCkM޻|UK+\ >],:=qE<"6PZL. N(awGF`99;IcU5Տ/3GMiPIa{sr'\\£4*7%dTG-tg +Mmaz"`8^+WU\&=9Q\sc?_\cV]ξT*aOlZؐB^cjî=MlčҊbiEF*~-0Ο?O>0lĈβQ>o%0]x vwfzELQ0,-5 ƨQPvƇZHlA``0th4͕ؖ E8ӦM+))!K9iNd$55uβ;|JʴtRuYW6v\ c̘1c+k|???@} ƙ3gm6mڴi]*(At:,c3AiiMz=iiir+=9r=2x۷o%\nf hv ˵Xk;'R`N"c6vX'V(k}vo?UwÇXC&8N:NSTd xβ=A:t5>&MT\\LNqՅ [>:t9˞\ JJJtJEE2ѨeUd\],-W5jテ /w.. k{/v b_㓽yl"0K ں粻^ 1yB?L/龢S}-`;0LOOOBu{R|NOpJI#9"ΨVe+Gf̤y m3}d=FYBM6*5 How=Pv3ΥͯcQ/ %oݗUߒTycJ{mliӦ566RR"jK]Ct J={t?rr7M<E'?)dXtf{p{숉_~ccήب8V 2,;YS(L\.sm0i o7c=YƦ/~m:u쾢S396O6k ]"nep EGW/  |y1qi+Owʹ s vN Ɔ lVzŗ\zB \ ao,[<`:!he5_8N_vE@lu-zC7גpu@KуȮjP9`OnK`̬lhҞ7KR^ə뙕ζ3O/ ce$L6Ծِ?e^*;( $Z;%׏{`0{CW4*G%c6ydgYc3M2^V:A4a y^Vbj ڦ2\)uz.dB<|b}7b3Ĭ 'Ͽ*مr\g0 FV_-Sݩk[qbIѻ2M|y ^WɔJk:b[i&q(F R6j'n9zh5>{yXYTp=-&dD/1khhuAY9\רusnce-V,H ?+?P~ n L﫰鷄Eg: V+J% BR1u)Ai[oxK HP/l{!oϞܠG'zs P .SD'(T˔/ݺT&ʮpE qB"'KH贷yfГǍ.[w߿5wnweFZC~Go5Lk[vT@C?:ÇSFO_V1q%U'~{ԮE57Bcǎ iMiii.ӫafmnԎyߐ)h_V%#OghIfa /v9]oĕZ}LY,ˬlQ ^ȩdgaXJ?QRwPEMEU R,:-^Ŕϊ8L !Ņ-^W]C|=Fgg'G uGҷ _01hx{1h5 wTd+V1v yC:F{ܨ)I/{Fz*_V\EqS'pN=y%V2/rʿ.JQg~*ƨ]V&hֶBqCҕsh4Qޞы]sEJ]˧=НjX,yb|d@=63+:#([hm Dғ}9EZ!ԬK{S4i׍[cCwJW?浽ﲷkIc~` M U2߻}{]={⫼gOڷIH}y޳#&Ύ߻ܒ 3H t k_enݚEg>0jVW j}#AL 1i+|d[okzʠ֦Kan-abzTAxNhs?.͚&=+.O%xvxlnT7^h F0 kofbi BHS`Pַ&13ߛø7f(:ť]ś1  C4 +L~mFh40 0.ܥ0Q)U~}>gLX_~Un~j_';dj Yn'lpnS!+KUflmhuNQ()T%//Gq<|E#zJdkE>@~LDᇍ2³_Г0h'DӦE5>Lt1DA%ўbyؐU *!t~FlϢ+c !j 1E!5PL7zd]&؉Qd'Io8~jf]+]1҉"tk/Un?#_ݝ̔xGޡOGavH+鰄Aw_:. c7a"}y0pKom t3ٿ櫡#9RDQԒ^f JàBg0TS@feގ/;,$Rt?Ug0s*o_G~~~~~~ȻEH V{ru|?hilBc[MElm`[<`ٟ>tF\mXT lMқ)1n@_/:㭂b]d`rGjBC${=6bcWPUUe-:!oOK kMX愠aDbum nٖ!n ާI/u<(83zBYtƠ5:;fsΡc2*H҂F)N Fx #nRuYnb k 2'/pPq½^d 4 `&boyUf18#GH;M >(h[Q"& vscBfWʊy_i:Ptf 0Ѹ 608KplNwL^rc>ʁziګ>LRH4^RokRٶt>qaإrw׭bPk0ީkS׌bi^~Do&m߂CE}s!Cp?S !rqtj1Ljp)VMxLgđǍ8Ȁ86ޛiPO~BNOܧ#_yB^} !.=: i"pm;M>NFS^QmdanP>8A2m"qs~C 7 7;5&ي=>ի[~ՒK6sK>7հx_}mg[MZq;&-:@bPcb SpY#[>6_Y\O o5]:#&T*#ab[mܩonVx=ۂ TH;u !F[.QIͪ{YNL&n,&9'ҬVk!!! !x>1=&$5$ƈiVO+RHWO (*Xc޹#_[NE^WR>7GJXBZn}oy,}XDUy|4U !(;bk ZC..Z2yIn9%yxeIZ;é-AFCͥg_d2q1c k|04"``[=1g3ǀUۜYYW/!QkeޭW'GQAXtyE(zÝ^VۡK -$w5>B m`l­2[}c=`:eL$eDG1S;ƒvxLZ Ko9 zDU҂3ȝbJ >I)CdzT:.Ġ4)u46W sxʏASSG>Sgd U7NStۜf9bAO;=l,BvS?Uf]z;Ɠۼv?!cX``rk8zYȒ9sRغh`#G,,,$]t)$ĞY`xRSvN19ٛO0X߈A>!LN/^QT޹UFZqi [\{sJ>R_7wx/Fɉu-s8Bg'Ja)j{mV٠7ھn7Uԏ #d< w@wi҂\(i]ԍdL4"ķ;7xq<*c.[[,@r?cZGggqslϮm:Vzt GwY۷N[t/[Q,'9!iyμQ!9Sq8L4aVN`YL"0Uk*>[quLܟ/N&ĚQ膗Aq#gڭSoAU[gvKouvWQ7UyJ2x Uuܦ`vvߑ]@*2$}?Aԍ.9cZIQVȯ8p^u]s(0R3bBlUN 0E( X#"B%v>U0 Lj6ʤr;W+\ $9`E|w"Mg yA–=oMgjG]$AM< y}g"޻I{yZTxo8ɥp*~yrU!'1C&F?aR:Ztzeen: s2x4T:}潷_{vDcN'B#4)/:M$}xk m^3zBA. O[[&K87wSI { o8;Ud#I'ЀAhgK-M~ɝ&da&?oCDk  ң7x1̀zZ8CR}%aC;oy(i_304skb|s?4>sjmwt0p;ݼ7MaY}}jF^|yBw`OoIسgCa%Li.8+1MjDir?;0!#Df0&Gnh)[Vˣ=H6gFo" ¥Ȩ} d&L0aB mu2{ϊ |~VQg~T7:?X~`5gNO-~+yDCCZx5^kO/Unȹixz8'r"!ty0-rA(F^|h:Ф;GJqm~rmum&BH 1 Xg᠈_ܨno /rxL!V_-S7_,%; #[()˒Q%F|>ˤ34XPߩk7̸`PdRc Qkx3J sl]k0t:R[/ u!ycfR̞ђϗ\V5ǖ pi~tW3W}xbаXH/;$ﲋ?MiQg\~gO8.0u|QhPOuʂegFl[ͮ6NxPR~6!Tq  Y#\r!TyBW5e+<1җnLޏt(C$p(0-C/.OG)tlXۃ'H q=9:ZQUXǺ9{,&ݔ`Ҕ߲ 4zBHӟ*'9̉/oNjȾ_,\"`숉{GJ9)w_wZ18'lZqݝw$Ӊu]'vExseOq=`ZFr;2uW&ϵI#_ځQ|6m}O_O? Ò^С?a 򞯌NXznfiKOwp̌:ý}">nOe6 p#upqZݭBiyQݬԫQ⋸`΍ps'/Z.iP7+t*e8~<0a`GH({plw(0ÑEV\́A!~ߍg^?ZϹK&^epGvL͊e}P0Qih_c@q":[PN<^+)--) qD`ʁW~t IDAT<]"6pDэnFTԐ%|>ǧsU 2س' wN4z`$''wC9~֐ZPЪRIFFFhh]\ 5z}=p)[ q@67 !B{V(rؤ\;vp @gܸquCXXg:Н_|>?2U*g=+W83qĻwv ~Ldt7•)pU`E{_w͘oF>0lβ^|,HMMu=@xGt:\ >}ڼG\|J(i:\. KA@@O).zÆ @9qDk|$%%%aaaFUe,u0[RS6j&q E> Q)! uB!ǝe 7֕&ZrJkUri]yu[ŎH8v<#K7.?k]<_v?ð6c:R0,1[+zNa|dj<&#AWDZFQܠnv9q<^zqܽ9=hElziiZҨ4ke|-BEg"uPZl'd%$7aI|Bo˨˛oe}~F!1i֗7:;h"q4A&մHoJDQP#BxXJAHÆ sm&큢Ӧ6aFH`G Ҥf7i :8՜Vl?Waj\{^svGVL!Mj؜XV_t??]X҄4j5+`w $ɿU/~{Miz An~ej-O|}AktWLmP賿;ǁg_l1ݖYHoP?Iva4\vm; 0›ӇsJn! NvRz> qkG-} 9qabCM΄gW\Vu-FsYhY%04D[Xk휦BHdĠi|_$W*f62z \ g8svلۧǴiԛAzD8`/x !8Hl5,Ͷ~F<~Fَo{7c[@'ҴwBqw.c:JڠQHo]P4ך$:70fMA37\~׋Cgٱs!, ON%㹋*\1[f.=;fテ(Xj,Iz ac› eϮq<(%i{v [l!cd+/R|BC}oZ/0{a)_=Awײ 7O8no4?q"BY*)]̨95)HjKw?VPej3Aw=ū_TxK-Ӈ5).޽Kܾ}j|3DK'4 JyCg@q2x"(_(e5V,nI>]jFF< v hC "j HY{)ӧm\\[qn{.F!u9ZlWF@UF'Zb/'@/PWDK"vU^-71 [|>BHDY'Zb/+W$tp)'++R#11Vpa" SƊe7ōKꚂ¬ v= L0j|N'::R㣠 <1׋{,.tF 2ĢN{;N2bCc: H/dq11$/Xpo gv5M;괰Ѡ(eg6f3h7vnnTWWw;1bq{(J(F>bu;F t;ۻ1ݎ vLlllc3tPo_Pm) ]m)4TTd_$$0)4?z9kv~3N^;N`lǁ?p̏<$Ҩ'/:r"0@JtUޥ-?>iow9zk&0\ Wp7Rfns=&>1aic;4qBȑ#G?& n7 AW+-; w7&/%Sh^'̏nR;vHD)TؠSpýYL(B(yʣXOս%DNSe q*{>i`Hh_xP+ؘIX td{yϐAw4lW)ܙɔ}}|Sۖ`L&Ӎ>%QR 7V}QR_nncŎGcU5U@e4rq][ǾIy6w=  pÍem3!:YܘkyǰΎ'._=cǎEbOl6áoJ\ ;0K(R^;QJlblcܹP"'-W ѡn|Kt- Ig1 4y<6^U߻pЩ1rߚ!xd4t?*o;)vRDJMiZB%ij(.v,zڠ\=<H"Cz!JZk]m; pQ%MY57 t&G=/Zwhɋ?Kll-opX Nv,"nļa<d2ඣH01/ic9}mpkǏoȃs{,zv9YsMgbF͵28v?|EV"-uwF yֺ%wߑ5UۡCYX陊R@J~/J?7̏VR]+z>Db|#8nF^}΢'r1 =gcb($x `R Zunir2dN |#SR}#S"Sw=-h2M3;d;H)L6n;9z?lyՃ gvRv=rv1p ܂+-5CKb\PeWՕ09?c]ٕdܿ zEUUl^U͇>ѣ҈ ^LڷUYkFR[* 6͏5GO C!n(S+% W='`әJ";p?aS-5E^!:ͱ-8hn}GܸΙBcď[]|:C H[9_p#^!qCf=}Ê}:?z('sd-j&Fپqp؄ImH)܃J\t$vlu$M} K)oRIY|}* 7~'D@M|bsɭ?uCFU}' v4hhL>î;BJrhh[! ދG kC ܿ~:Y{FSLdFT0t&g | 3WИEpemM׌i7t}= ){:NyhGISKvC9:) oز<+>RI,.F':( OλN"-jE:){xrEgļF{.D"ܰI+*5JZJtPNyzDa3U z&: K`Rp VL>}j"~GL_t)7>`pH H)@;xkpvW)K9s&"R ~papv)R ̥7CՕ6LFb[ R ;fQ#-- }~ YklF  MII7n\dd3#R "/_5>t:\.<~?&&f'Hx<ĉ}qxxĉ߸qF-^8((ȡ?BOyyy:th̘1j!tC=D?p3$ M6EDDlܸҥKz577??~#Elo֭۹s^G]p6pJ( مW6f!X]VTq8TEE@Aߗg%+זt$e򖺚ѪR?}S֬Yܾ}D"xJ|r,3fLnno|~JJƍsrrF1nܸA`$>dԗay@QJxee'D-oQz`'}3w {#m<:xzQ6ږ"1>>>eee,oڴie˾{z>$$Kщ'fΜi%ڣGΙ3JKHH0JKKg:,y&4K)i\kvŀ ;KFR]Tw+$^\_IQ!z)+Bhʔ)XJqGO< PZZZ\\\^^^ou<3/0^wP%_vFQ]Φhw1G9#0+jĬy}⋰a@8[Idʈ_z+8ԩSm`(~oEK,9:'*H1H)+_?b<A9Ox2B' >w ':+fUnƎyD QDG@ `3]0Ot)Fk̥pAW[pKhU2cqQP((ƖoKLZ@,##cG{n:o(LLq#}ӹjnݞ3""yPQQmaaaijjj(|~ [@\ !Sh6%Ђ Ƈ#B჈Δ찄 |O6kۘ2XE"*8R*VSTm^P(ݞ`0`m >=a„!\2 R) q˗wILL$0~ R boX")LAOAJkS\m{ϛ$ۭ5@@JwzR+_wv؁M2e/W0=[TL^x1,=RYY ןtn7=رciiivBB-W {@֭[ƎV͇Giؗo6o<|k,+:rܹs=MMMÆ +//7N<ٳ֟z n|J>/heeKKK/䔸wU|>! kbC ЍQQmu,5k+Yf|>|';=@7X,֞={Lbtҥė_~ 3Leeeڱcye~f2d2D"immͽqƥK,8p` i%\ w0/b'OΛ7b&uG&M:~8Q#t:}͚57oƗFW)KXt)}O{JP,̙3\zꌌNP(~֭766vɒ%<󌿿?ѱ p}R'N߿555z/888--mXҾ]gB0 >:nܸ>()H)`v)R .~X㏻¤qFO|OXXԩSgRs})R ^O&''GZb_~i1fڵcvi1fݺucon1^[ٰaŘ-[XٲeŘ 6Xy-Ƽkcon1fݺucvi1fڵcK1Vg1˗/O?YYtŘ,X`1ȑ#c̙c1ԩScf̘a~̙3hйsέ\+UZZj4:86L0ƘtR@'6n8uT|QͲ2gÇ/y*p O%|7j߳rJ zɓ3f$?e=OqDqJ\+bQcҥR|=iJ]Z@J;vs)LC$1*jLap [neXDQffEԕ+W `jʫ ID+':`7 B BFhnUUicV)4V3E fk}#SPiw z(kxtjaX=z zc/<_4^LVh$:"`7C}n7M`l5 W!L&^Rŵ{.XR=:`#(t*ԕ>@'B&׋3NpooZG`0o-cC}rA{(\weS۶k N& Z˾ޏ9-TC{Q#f'QHT~:OzO2R_Xxg FW͚Q" JqHzFʨjTdz]GDEE==z}{Xz:"pBɏC$9ů[/C"Jʌ߼|BRwףY)p oE˗wRTIםrYύJ$ ֬ d&PRٴN޻_~e|{RB(Ie%-j~9@~ 5sC' |,4eҨ&QD_RWj|'+F6ʦQ:C)QWm>Kl0qYBJK7䩏+;K)Z U9v'G*͍iс+X8J0zDjT2w0;$' `td#H)I~rm$!zHdV\B$T*{͋n .a͚5J㠧"##ׯ_4hPOOxŋ/(H)Kزe !G ;v)R@;Ng1SNX,@J\§~jQ_EG:y衇zt R 6olQcժURyW=iJ5K;v)R@;:y_U[CT0H)Kx饗,V1Ln*::_$%%$ .\p` 6p{ .QtY{13N{[ Gx1A\a q#ΌX)@vs J|vGDrNxzQ/)kY'/=BE\0Clglb2! Vk[vGZDth`@+{f;) hpK PNYv}a *p:a4pmY1!Thd,8!JYmϳ^rv߬F/Awv &ZghVK[e7* sa[F97FW-)F*.x  tE?Y$fB(hW־sPa}6%h$DRm6J?XR($Nй! gUmhͮkI~G+((_=~ )-% JPÍ$vNz9NV.A]_/RBtt6:˔LQit:x@(䠄Q~ΌvR;XXv-.ԨzFUFU#)Ž{|M|GּFpALAh_FĜUHnH_ѠtZBZXSr3 *n+"b#)Ɔa)EDAl0]sfh:W\Q`\Ȉ 0j݃Ej-Kr^àT[H8":թK'/ܬiTh$|Ǘ5wPZa($g{1=1j!t3"C]޿3Lnn9YYnmsDѤPtzH%2A0\v %x55kWJ2N3>*&z&x;Џ2Hu,B!dݾŗN #69֍l~^k{-Vh؝sWw nRs53 4¾?E/zǭJzӻ??ۥwl2iT2BQH*ުλ^[a~h:e^KAJUK3K*r~3Z}L(1wZh2])?UXeQZ}Ly^% P.şF! 메?=8kN|N`lp^P5}?T+_x|٪kևmThYS5/ܪV4f$g;;,1~#{wF!$oݷa74p!N $o ƾJ7|_$>Nu5wQrb4d{{Bqag 3Lݪnʮk89"Find2OKzQW|"li#|:Wcжh$9E2):ZjT2b' ̇شGg dZ5˵?{:!d49Ee.ѺP^aXt Ht+$~ے&/ÕJ^>޲% 66H)KxWܺƇ\s'LPXoɤRFR)Ų5 rSiO/gH~dihQ˦Q:}i|imq!d23@/G]*^$$$ /lFk0ȯZPBhܹ` 8'`,u[&:;;T2Ccsf8˵7^թ֦o@ϡO{cEGN{8r;7gA\_`=1?K)n4, v$sVUN|ܣ$6*H)Kx׈O.)uzs;HynTN!Yt=cfLpix~eWڟU&qAJ<m ?ӅU'>ɁߙMURs|H!ƅYm޼Ӈ&wL)ZUooD'ݻXcNOm>{f]-گr"뻾ӌOڹEFh,Z5B«GGAN}+֞': CYu-m&D.vS$ViT#T:%)6FI74+e U%'۸]*hznVw3!>wS5rțk ܬ:_V߻TJ2sǠ I { `̮k AޫG;9p4N8ܱ}Jn|ѻxOSf35x־Rwwk/B\JҫcÒ >4nbx$zڲ,3WrCQsU㧯a<ښ _f2KΨ =[PzV'bW ־ِӣ;FrKM^Ф𖕑7>Kx,j|tEe>TrjU"@>kWHRtgFok5REv}fL?keʧ *OV܇cɍj|OǍkfjn^ɦ2 0t|I J7x eX;#Ԗfޤ"87gQ=/jG`$RZXn"n4bC-)BGz#En~q9 _ɠRTHMF~Lʩo=YP9;=4u%J_um/ I|W.~TT\2BEe8f¨,j[Lu9 LȄ2L!m;۞Ip;Qi>X[\ceAJ s~-wj Fq~!D#=CREeO.gcotZ< 9gFƿ]г5C|N*\1h4 F!SdL#r^nk5zƠ굚Fm}]]Wshбa~οb46.?XrQDBe^s|[keX[kqYv dퟩ\6;~T%s* RdB_[opNYSg[:1Q(>W17>ӝT:6M' c0sG FӍAcZfIX^#U6)*B&q`wp(ϳKN tFu0gXDyWVyWD'iJ5Ţ2 .:nf2]vp/駅O:D'V^kPuSh|k% _mʒj:/Ǿ +kՐ[)3[ue{TR 0(dlKεaOB(W ]T$wv'B~IKzCǧl:jad1ԢԴ(5wkC<ˇRA.x4 ؐ N"dUx:ER m^ /Sɔ9OB嵖\uW,ҋMvN =7)bz`)Eênd GCqBb0)MRMJ Zs[AJDJ& BrUIY!8_aN^?3k #x*Ϯ2a`2ݮi rMS5p8!!M DǓQhK)J$Q,g #O'>b4Wg|p鳻?~7= o[fH*$BLZ}P' f>PRDNAs,0_XሹlUnK)j;X7)گ%mZїFLEr&_ 󥵏Ə`ұJ5kIkOTMb3cB?-d1X4`,6emUi,o~m|O\\B(A,J&ϊ =y͛؝Rⲛ ̦|j"g!Rb{dyRЈIA#VMK-z榹}{A#|ٖ?{wCE pI IDAT=`@aE ]~+у$E#jgM!f<.|!4+lulNftT"ʔ?*/ [o|nNOԷJ$U jECOM$fhQbP\\pkfbB&V!inKIF}cBx?hO #m C'pFQZQo_,֛،qa$%YB+9^!^aBIcP)ZQ7*5ReaYs&WZQUKYu-k_eP)S"{tK!<7Єpb=III=XNo-[ #REOnJ?W\s *ԨA~ݬևy0OjW :vt⾻Ek& H;z:u`5! $?g,޻o4pӒEo]uhm3*0jƿG<aB*$DZΘubgl}8r7\uaYw6zo]fHa3G&!AJQsNMd)5@43&:^֑='>:ĐAͧ W] L57>ۓE7W8|;~ knmoH[R2)^ `Kgn5f6Hj-jR6<:Nj%Lퟲ(zV 7T]=_Q%oV:LcQL?(M,($2Bӹ^,a0/F7.`p8ۑ[M?|Jض\NbCBR˛||ptZ\ȽqqFTj[lMّɃZ)VH UR-UZ`4!ĠRt. yvg(_T,-h5K%j\ N!T>fzs|N.Lf(\:-P REzC_eDz K0_huҢ>ǟ gaHMfvʭkm8GSEޭ;,~{>ė hBcLP[uϠ'{[$"E|3e{hGDV]w 'B=FuUB&V%(j2Vսk{=wѽ̎ 8ꦽw̷gG%^cřx<^TT׷'6Ѿ*=uSvnRi&,e='.n#ăxĭ[{bbbzzsΝ;~A`x+~3AJ\?OtQ*=[fn y'BQQQ# !Hd Fl>(0aoѡuR QX}B&IZ$r7"[pj YyRVkBn Ψu'亙KSӶAVNCkLcp5R!T*( $:L q{Y#=idxw?$RFCƠBt$.7$P$%qP.|Xl,b#yEsNNLɄ42yaZO3fncioTj`ŕPd)~{Н4LjVdqo~:LFF`Hj},$-Eewϕ= K2s'|γcǎL<.Bd.Q3*.a5>lb:!r_%m&RNj/QXK)n5Yvk,kp8{?3|ř[j:3pL*G juAJ޼$_s+%rӵ&BHR_NT0ΧܷàݾO08{w} YmZ .ͤ#!߾<R.AJ(d$ViG˩8v}H).%btQJBzzl3Lqu\e2Jn9ؼo4Q)`#6g>:}Ph呏?R zZfYppC'#H)%L\{~?V:@gkc? )GDqY34)TrpqYzɗa.x#_a^۶/$6Bgx( !`4,<[\c4T Bj8 *!tMmUf2Bt*iL41 .aǎ5>!fΜ9s̞>h^yzIhd_h-D"M gEBd2b@hHCJ\Ų=HXWi-`2 ptX^E'a>$,'qH)OU6`)Neҕ~Ok0T喩`Y@t qH)Ot_G-_X0!Og0S"!N@J}"Qkyl3y00L:LQ5LЮe) r&f[,:$nnlJ$|Obb"wTpR y;wEN:5>@߽ gϞO8xR gQcRktH)`pc[yX@"i, K <%|UR LFRUd59o9fg=Mێq=111DWRRV{BS@@T0Nk.ѿd-gv^tKgqKӧO>}:Q .aѢEDz⎝FNJgPe/<䣕 ٹ1gLxCY߈=Tqqc8#F-X8y ֑R 3 4֮+M`$gamHu5Nݽ{W,oQJJG7%<3r_¢Dj&J'}~z1!FpKp;'!& A*L:|Dj H)g4*&zٽw'ܓf:3B`>鶄+$~"ph<}7e|OBBQ7LZ&/7 Ci ,ڱjNs܇#dH1&"d>{HBVXVwh2!$jO],"8F|pV6#ɋ?Kll&G0&Qb{t7M 5 LRNp N>6VO„?*zOw i`gz yp zmcJtP'%%O6WJM JT2N!4eQ"'㽆{ J\)uz&gCD_2䄟:rXKz!T"˪kI$*7o{ & 9 .aŊ5>r3CS PEyL)Ξ=5>%bE` No;ona4ן*it~Vkeʫ ~<(s1Kk͇%*Gea@$n'0~IF`z7Md]Ws%G Ѥeu橚%-RZRU{Rn_ڧQtAzAY&}l_dR)zIʔbʩ4zçkJ!D58@$iTN_*;_Z[,ELb:l^A !%ciн+$:'(eXJDYPQu ,G\W)T*[58nQi[J:$<7*N!Yt=cfLpix~eWڟU&qAx ?A~ N}٥"W„<,G!H)R T`mY :IC"oC-TE.kK cYG2ܓvt{gյܪn2T8ј$ViT#T:%)tpau2% @R 3Gϭ-q[ 3g d븬fef ^Lq ֞U>a67!NmJGݪiJRRX%*Ç]$l@J\ž={C~FXyWaHt Lj[=*b޶衳!CSy! qtJ zª8I!nG'1!T*'6`e ЩJiSCIfmL?%l޺D؛ܴ.ғQhn_.&FxP=(E~Bߖ{WH,p2M\ >:,׿LK|fez8MYӈ}H[t 9ܓɏB<ݦťVgQ`8tVܿ)ak^B4RsYRg0*B.&~|/25iRQpţRBǍ?-lwOPcnp~_ pƇ[F1BF!Gzz$r9H&dBgntitBt͡;3Ut!H$Q`hIDeeYSu#[=O!8dlOqcޝ=]' w8L&d{} %L05);)ũ3ycysC;ݙ l0O%zB`v_wsRL&@ k67 :_m*~wN`{@J\²ed2g޽ezXIgHHasu˥;BƦ j -4{zח}> 5MMygw"$j A)dލsX=9;E^﫸}2j:!%32$}%YXa Q"V}ъUDm*TbDl ņ-N},ev?n 7;O.m2ӻ4?Uv .BHhb:NLjmG{qIDAT&إ+"%x$Ɂ^4M 5.U=Drzq\eR,4jvJCoe{ .HFmWAEvv |%9(dq8*O}^9/X=nj0s'u5BcO[.!˧xwSK k;-Wh~ejҖ6HbeRIZō0ˍ O܉j%'*]xݗ ϔ֖4u43]v\|Zo1Kq~3 3gCh)^~N߾Yԛ{z36rXBay*G4Z.RWHM嚶cBHSGױǣzlo-ݧ a/QxQEt[CCfHPRؽ{7BH_l5M!> N$C:_ |ULKMsheRTaM~|ۢYŘyݗ _:,.;TԺbI~:@ aPwŮy9U +5w-), \9bQ=().̘1{w줧~kzf#6v}mq\x͹FSNuéWtAMdIٲ: !|X^R(Bf dJ siB7O~G'g44<9VvJEu֎URco.M {C҅]emMYUx_UB$uFSPYբWk4}FMHة7j6mYs[qckiGJDK"t {삹 Txa w_[%5&/M}~* ĆMvf]ͺ~ڑ)uB>䘯/0s|Rx@r#y9U >m(*ш7Dǣzﱴ$GLi1rI|'yg)ptN 7=˳-&$$ y8Dž?xENP+ɣ=]_aFBys{UAҩoFMH .1.q~*WwOq/jhSk Z4]m:hS8qbU"59># c1^VI.{ylz,’J34@PR8~8+hh-E뼘`3PR([߼=;٫&_%FWkw]g'K%:*)`6(d?=S^PR ?I&q 3׺*e'E XBIv!##S&âk@JJJJh?dұeU"`ҸNCe8MH"0Z BHQcG5l# fX V/>z={NpppRRWuq{@Iva޼y3>ۇM`{N}J @IvԩS3> W8 !ԥm:iB1'jPR88W7ڢ+4m6s:ۛ NBONF w8 /3xJ1pPPR88R.E&BHK}ySuOIqM3qSuOm>b2uFzхnaSLmaTßȔӉ|:ѺgΠD!̞ pW;ۚNp| sV<3uyTʯL)=8d2V_μ曑S*`0"Y1ZR;0677b|}}Y1uuu`VLee%+&""S\\̊abX1W^e$%%b.\IIIadeeb~aVLff&+f֬YGbx VT*UZZi/ƏQ2T&i'N0W.]XSR0=*8`.]cm߾Yt~v0dqJ$ufD $kӻroo簾Εeee=ܧ~* T*ZmӦM C9Y 6mgƫVJKK41J%MUUUC'ӧOgy饗|]wZr%+r۶m^zuVVիY17ofż묘>ob{=VL]k׮elذfV̊Yz5+f˖-%Y1۶mcŬ\{eղeX1_5+geݻOb87oѕ+W<\ \RP*{yG=<<}ӁwI~?`nb2X%^g $h4!r 11 L Mj))$.nfB0Kv%99&##ٙ\`Đ_ᅅ>>C!i&f,K}xaRH$\g# Ǜ2eT*_paClٲׯ]vܸq@aRyy 0_c#{[TUrmmm~S  cRi1JOO~c 7&22ߘdzbz/MMHH7&99ߘ~cf̘o̜9sx{ݺu3f轫T:gD$r AIo s1E9d}JMM'Mmi35&g( MӬä(5}K17m>Mۆy=_Ȼ/>#LGKw.3p|Jhbӿ72M]Y}>s_ݡ zVD 0TsuZK}9s))gxuݿծzbe.]&c('5`M9(nj- I]gM~T!lGuCI`hZ֥mlkVߪ)Xq\u^evdz~tgXwYqSo\~wBI,w/Z>u")PTʳo2%EK}`3J /<*婉KeCqW+jġ;/p rE5f_Jyl]uf0o()1V`8wc)p?_3 )E%>՟^9q//3aک]Ƿ`-#+9w3LO()V7.g'|"zs@(BInd9*{wX܂w %:(/~M|7Vdroo0c߱^p%fɯ1co}3;PR8s=A"}) PRCid R+@IV%XJ `() PR+@IV3>6 .C4MKnX PCI?gP !m \ñ}>ÖG jܹsi999OXꊏϹsZقz=Q10rf@~SPΝ{;O>-H Ü?Zu퇿o鸽fs_|u+##MD nΝ;?KM+M7Zx~SUTTp6ol\9M6%P5gx/-<:::7ˣ{zz(#{7ӦMkMoW]]iӦ W,{zz󩧞ڶm[{2IJJ2dP(T(=zxg9rBh~ ?/x{{|QGڨjpHԞqNQ<]ߺ>y1NrJX\3fhrOs_Ml?dg@uu5-D(]Ql!ꊋi!FkϜ9C mOMԊߨR5jTk3[F}i7?uVu:_%0=\߾}QezKzzz 5rMX`Ak*E-^;kkkW_}eܷh<Ŝg|QgQW]]}կxXXX^^^;K69///DVVV;=h'SD _|E5*JL&+--o555-rJz9sm´Y-~~<ZD"5az5D"y*+++Z wv(j澾澕 # p+]lY:tܳgO#E[1miV uA >[򫯾j-DDjB矫T*5sGZ!←=y^5=Ǐ},,,EEE-h%~ȊW^ahhhRRj:dQG(//g֮]B Ji?:wpj65Us_ѳgϖopuuRl@kC~k׮顣.S;@!:K.[nСjII /0k,q4:;;S:7nܠvHHHk>FCP|w/7Fᇬ26mP,o޼;Cu øEDD?9rȦM۲eUܗRSSM2˥5|'*%X,"H$ BP 09::XoY-~V~UBp˖-,Ӫ¨k@ |E5r$.\0ߨj5Vjuuuuuu:Nz:AqJevgm>}zԩT?ԩS-i >oCJ#?Bx~Nj/kZJu?c…j'EJ߿?Α#G_:NT޾}{߾}|C=$_ink_޾}>x[@Ï R"""$`@Եɣ>~EEEuuuYYY;w\dINU5(ZpN]=?l'E XRqQ]L[Q}9 /%--5> uFw1 hѢqy֭gyѱ51bÆ Ih=7wxW/]VgZ]곍gm tQgmg>t`fȑGixjyseeed2B5`1c ݜ{ ?tPNNNIIJ2788844w7I_lٲeǎ/^ͭkK_[!~F3.iiimzRuFևN;zϟ?j\ݧOɓ'# 7`t`i{Fhi{pHLLl|͛ .p"^߂]\\l`_xkx{{smNg`uuuiiieee^^^ZK\t9ӓkJV՗/_nή𨩩iPTW\iήvwwJ2^ʵkjj V*F,0XBp̘1!P1j!X1EI$ZFd2JB9::bBNNNJ Ƹ!1.++C) qii)Bc\\\"1!6]1999N:aB,bo߾ gff":w1&;vc|MPn07n@`_ _z!Խ{w˗B=z_t !ԳgOŋB{?!ԧOsBg"1gΜAc|)PLL ĉРA0ǎC 2c|<@&" U1@9c#F1޷oBh̘1{"ƍ1?BǏWTBp˖-,a zŋ͛Dz1>pxܹ,p˲?8fY'۷ec/e|IɲSO=jsԩQ!$ dMMMfffDDD@@NKMM .t:O=_XXH{JJJ2jWTTP[TRJEϓ6ڵkԩ!u-ZWq555?Sxxxvv!;;!볳Be|uu5=_SSCmu&QDl?X__OM~ bt:]vv6>'H$D"6!\.GB;;;dDbωNmOOOV}||y???jսh4dybQ;88dI좢"zdJ|k쒒ZnἁvPʒJB9sF !`W={1,,'\|PﯿBBJ փRLOO'_}O?=i$B]'>^ztgJ$2$`!~mIJJzץKҹ`&22tXAddX,I'888DFF 9O:/<H)#I'HI:2<Ȑ^^^!XI:5쳃"zHd\u֭/_&`''iӦ' zW_}E:L۶mKOO߶m[FFFNNѢJJJZX\qqvQQQNNFi]XXC1ήmtvth[csss[im۶a'M$ /_NwG޽÷n"^!v}}}NNN^^6Qvu:v]]]NNljsrr hi%h[V5ӍKKK+**N$RXXH"pBjjzko~ꩧf͚e<`oKR2S__-2;vxW8`.`ol߾}̘1d3//O&1SLa&***::zĈtg裏^}UoQ˾}.^=`͛7Ϟ={ڵ#G7M˻ճgO@ l:tj'O~'%%%4vÉdׯ__|9yo@1 O?!Ak65'Nl۶M3 O޺+Wܹ!$t:'}osȴLTOOONvP(D"ѱcM;Hќ8qLJ.OOOB!RSS#P|}}i`Z,??<{1:"= ˉ]^^r2K!Nf1y&[] !0 Z4d2Ub& T*y!777^6QYvMرZϟOVpE}ƍ`Y,}1H@d22ڵ멧"vll0&&P\]]i`wm5'!TXXȫHFhh`TTTX "Ȳ)6c5Ba$h4DT*eNLbKKK0 kZ=Zc04QWݝ hhsΥZ? Z#@k\k  L&6as'xؓ'O<9X FymPxiԁPZ- *Q]]M[DgggHdY+-cLcFP|FjѣG!CX+(//r  E=,9hhsٽ{77l0a>jl:Z\\lrߟ2,77ׄˎ;͛G)SJ`^#@Q(40K`[4`^RWWGFbT*ttt mBnnn-cT*+QT*E"sazWuQVVvebGDDXsSPo IDATZ#{]v_ xyyRkĖyyy&,]o{ʔ)aÆ; RFJE@k(Jxh`Xֈ*VgVFw{# \ȯ putR555\Q(ZJ%WWWO ø|!H$:t0 CPSBFZa`]޲@7'K!=P`<,--={Xh+>[D>Xq)S3y5r"dW]ヷnV2lj'̠5Ξ=5N8Zӓ'\k!L&+((0a}s{ʔ)|Xj U=?޸_fp>Re,E~ř}xwwwz⩖ ]N*++yU!t:jZw7 E7"N*} A/ lFVgL& TUUQOgNS 5:j6k^j$ "aM|d&/y9J #r0kN_ZZKKkI˯/MI2I57Gg3cyfڹs'7;P0uWdEEE&,]m6gbO:U8br`yJ]$h-\=t+tNNbAw<բV.0j^Fuz}Kw]Q__O֒Z2fa!$rԵ:.=ܫ-X[[K*RivKY"m|tty赑#Gӕle9fZnB*s6.\ 'OT*XỹhP( (5j̯5q2պ5B<]KM3':>Wʭ)jMW8YfNN6J}}= Fc1Q][tsWBwǘsDʼn+] vw^<,F-D" j\c5AdF&3x `SQ( Oű| ZQPRRryb{yy8J$>l+Xh70cƌ!PN:eGw+P2NlLVZZje֭hG&ZCB<==i`ۗZZ-] eEV5ҨMj^jCci(.}<#L@KRʔ)Sh`8884U)))9w===###E$D[ j888pa±c Fb%oڴ)66ZٙT*knnwWϐee֭[}QbO>iawI!TWWGߖ,FUk_-mi` kY+$&& i&"f(SN!Fah9ũ򊊊h&,x h@ phfoN͛7逪Z6Bl\^QQae˖-3g$3F RFNO@J]]Z#-ZcLAYNbQ~ܮ^s4r z3mCgFƘn.{! DsϜ9Clooh>j1HglZ1 pC8qDbP̣5Θ15N4ZC.ĮZ3e2wٲe#)((8vG-:5BO(Xls^GkRknAENC.A'8fv 40@kZ#`fnzH:dcD߯A4zK E3YA_[חE"Dr!0Dk<Ш"Hر%wOB޾-MpTj+{5wsFT Z>e2C`|W"UUU.Jy]QN {F:aNg/s--ji\ld& S__݂B( gΜIAGj+zZ;U*tV\q)=Lus9<53gh+Z#Ak(Z#}v$Q=[117mOhoh`DEETT*98O@A"Z!>ZTQ%:uo{*"aM!TZT0VLddC~***+\*B4fʔ)hh}'Sж sB Brx2ޚƼÇSN<RQ*BO(:iCNGk5k9iTiu a[C M+*QlGϟ?O#66GhBUktrr"6heh>(90Z$ R8QH!Ta~Һ(C 6[(y*z*@qpp!Je29'/2nzاi4C`tLBYXT2en`414fɰ&)gDW]MR͑w!b?|ԢVi|/'1>,a(vNS ٳg@1F?Fll,OMY@kBlHFkڏ? Hj!)#@./SBn`7l`Y+o߾f_~*[¥R)k6ɓ42 C;x ;u4|p>jɁ#$ja(vN]]]\W$ ̙C4祝[sAfqĜ?Fll,OMٳAkɫvwrʎ;FOo߾jQT\G}BOS1maD"?N4… 40bccyj{1x{{5j4n5CqܹFҩS'ktrr)H wyq'k("C_63:݊$gN,5GӚxpOs`Gz@;'77&v@@ȑ#ZGP^^Nmۇ`nZ0 M;GVWW[, ͛G4… 40bccyj̙Z#^FbPvyH5N:o^3}Hbjlwww% ШbΠ5Z 5X X DsrrrOleGG-UUU\GPZZJmڑ7cV&/QZ-S, ϟO4ŋ40bccyjΝ Z#???^FOOObP FӧOЇ8OF"i`h-`%[b1 C믿8zh>jQ*Tktuu p2mcU204QFSYYIlD"|'Ɂ@  \xFll,OMy@kөS'^FooobPvuָ`rZ#@ FRZaFhE"puuZJ~7􀎡YM}]07_k+TkdYvر|RQQh#@(**]v1.**"67FC7 J ތ R=W1.єjKjjKTrF?vѬ?.e[ K=E*v ͙ݻw?CĎݱcL:F1b?P6o|SO3k s&bAis9/|:P5چQ[ϯɯ!=ȜM#˲40zS- חؐ2PeFhE"pssֈĕ%JKnU0}#G?k60 MC4@ki4;l Ob7Z˩֨P( qԦyS1.,,|F !t⫓WY,U5P xk\lXc5.\^c[/_;8 l+؜"BeYzwww???bKRjl&au5TJCPXRkDUiV:6!$j]~rҍ^n t̆𫯾"04Q0?7,Q ;zR>].޹sgϞ= ?~<GGm1'67ZF?.e0i\c~ݜ U^=.̋ ts2?5Fj-"\Cu22J+Iiq\i0hXgf)40ѩS'bCPˋahԁPR) www k\0B3n*3J+e]u輥2nNC:MOG)49'\/04Q8U5ގ%@FM[Gvʸ?>m:0cAZ&EurΝݻw;((h„ |RZZV #@ͥ]1ͽp3HQեĖdV5Z ?KOͽܨ(=NO8~Yf\2$$|h0?Z3k1 MV6!Ν;Ο7|QH*>˖-3>sۻv"vΝ'NG-%%%\GMmڑ7㜜b3 cr-Qjjj@kl7M\bp_d/ h0?Zs=G@kl+ <ӧO_QTW/^|ŗ_~Ղ~APP ={TW`{!e(k#h`xzz@[F.:~HII}%wwW^yeNNXh+ڵkM4V_tkҥ>MtTo߾sNbwܙ⚚{{{/;w.{PPi ߹s sjjj-AklM#AV嗫V***2bŊzʷ 5Pk|hB&K .O{=;B(//w͛',g Ok3Ar9O6GPPi@@40AklTY#Rf͚>HT\ڵʕ+gϞmN1NO>04QOh eee~Z]]mp{qqqe6h,:ڹsI&QKaa!}|| qYYYl1YYYfRZc5UV}t;O>III'Oam Z#`~ ŋ+y(w`?W_}믵Z-t…S?99ytйsg<ۥKbCPyiE.F.oNII? 2bjZYf]ah`i$ܼy3))_m<=rȔCdeeѡ`: Z#6ǭ[M;cIlaLe6J*,,$6h`pʕM65'NmO(5Fj/"9Rcƍϟ_r; ݻ3uԤ$MlLΝi`уZvJlHJ׮]y]CFk(40|}}AklRY#SN%$$ݻ@ xG]|G& 54GT IDAT?̙`rƀL8e>jϧY~~~8 e֭i gddAvNuuuAA@klzFq"howyXc5KFkcԨQFڵkWBBBjj*=_WWnݺ~a…˗/3y40BlHJnxH5GGG~~~56`mY#ce˖+W^t\.g.]i-:|pb('V4zotuK΋/~W Iܲe t2uTk@^^]Tzb-qzz:ɵLF#3h XyHt?srr2Lqss{WٹXc5+F@(>=ط~oKksϵgU`kԩ)#@ UkQZ#@qrrZc65r]n;C9._|…V{04QOli$|gnII7xc"Me޺ukڵiLRSN8jrƍf&44ԄKUUhMcM#?*** .'$$̙3G(4 Z#`~ %Kmgg7x>䓪*z)33sVZr̙3}Kҥ ݻs@@@xx8!e(ἮJP(40:uZc65r)))YzN|3))iڴihwߥ4QO:LHw֭[h .EEE%''!ܺu? v׮]O·9994 hcp5e7x5bs3HΩ/xi$dgg}]A%''=@k,h1_{5r#leꫥK&''nj3|𔔔Cr?եKi,I2ݻ:FLm(40@klCf\_qF^opiܸq111q `VZub('i$\r֭"a&MԷoߌM6]vÓJbB\BG&\Bl@ߠ`[TVVfggi$={6!!a9r3䭷nV?׿ {ܹs^ )#@ٳ'Z#: @K}}/ap^z]]]-0o= F͛77n܈999{졣ww%K ՒE5vGFz2֘Fl@@_JRymb45T7m_tOe˞~i#hc55nݺH$=܂ UV;^~>`ŊO=X,n}-}!6޽{5Ҩܹ3h @?>RPPP||yBE|0 LJJ]ah`@Hy_b̜9RYnݺ\r֬YT* \pvc-^Eb ޽{pvQ*vssh)ܯ[vmcGqqq-<@kFjFҭ[7aaajժ^ziժU_~emm-tʕGyo߾ɱMFW.]"## )#@۷/Z#:+ `=[|V52vX8VD>믿;44GmRRR~ƽx %%gnݺE.]@Ο?O}5HADΩiiRkl_U\=ztJJh@3CqʕuBBBBh`y剉w~衇vEλsɫHFFK.56Y#;w.!!aΝ6mZRR$&&; i/ 5kV; |QF_>88$6 h1iJHH #6lءCI;,,loF@@jlhF@Я_?bPh`t k4';wLHHC[ThѢe˖Z1&!!ܣ;!4Rnܸab>c|Ԓyu!~\.,Yi v˙3gh_~&Ϟ=Kl@mۥ͛vwwh)jIOO///'vHHB vmm_~jժB-_|…>}vor̙3澭 9eee56 4kPUTk׮}JKK >Ȳo1|Hćce0?ZoiY+$44FneĦ)#qҥ<̚5k>CGB(;;{ѢEWNHH3giʼnUkQBZc5Z-'|R]]mp)<<<11 ; i\~5Ν;Z8q4x՟Z6ԫW)S'O.L5:uܷU;ƍiXo5~%''O0ĎFjo96@ ߼"bS~~~~%K|>CBgΜ8qSRRFiz0p@^AFN#44 k92227lxcĈ)))C c4Z CFb?|rڵ2ZKG^i&^opioy9~84Z'@_JׯiX/^LHHؾ}@0'ONNNݻw{h1yrZ#@ ߼ݻ2Rzu3gtPkǎ#6mU)))z*===AkDlVo0!5?@G{7 .yxx,YwtQtFj.9y]=\ZhycN?1""a".\8o޼uֽ;Riie>e˖= GxWqذaĆyӓF=]kD-ʍ 4555[\\lp) `ŊO>X,oXW_k1 MFBW^oݽ{ Q˗j8={4sy|Lgϝ3a%b&%Nڊ}Z^[+Uv-JHlE"D&d'#[Ν=O8w>'q2g~Yn={y^ߤFtt4 һl\ ѣ .0jժ5,7JSh h%` >[SLڵLFBZ#axjgdQB#ԭ[vꚚ*UBŊM6bĈy-Y$##ߺrJg͚_wQ/B$hՐfH UTpqq!Fs9sX"++[^^^ӦM /F7nܬY;!p}8(HbF pҥUV1~}bjF2q,۷gΜf͚"oL>y͛vڪU~|_ك5>|ٺU CUQB#HXk,7nL6~ ((hl Օڵkug}?Hk$ OQq:d$*7JVhza` 4۳gϘmSF~ڵǏ:u͛q$>|888y-Ztqqq 6ܵkD !!!52FZ*+iZȍ$4%q)Sl۶MoGPT^=))IΊ+n۶ } LnZ[q"rFxد_?!F9wVٻı'O޵klҤ Vbfff͚nݺ;&22v]kēy,--Ik,DWn55Ǐꫯ+W|b8n̙Ǐ7o i)5~wtF) P^= Ҙm%6h`_ԩSKGL0͛K,~:oCXXZcӦMMZ#TZ͍BPn$x{233tD֭WsssxE5j8É4qT@ۊϋ/^ҿ!F9s j&:q&/]BCCwiee%W&MDDnڴ޵ƈfrܔHH={ժU#%(qHk|krrrjԨ2 vvvwWp^:5ָh"vAZ#%.4@10k&2*7n?~<&&&&&ĉoMLLlܸmۨKI4mTPYf̦8 ݸƬONMyWEG؁ʚa'ԬP.vI+[[h.o*&Hnnn|||LL̑#GbbbKl]t1{A%õDA> /;~QD MEsj-J,.^|rf0@QQk4щkz*ˑ111W\)5T&͙3/=j֬޵ƃ2[w*!qdȐNЅF/^,CiٹsΤy3i4˼.']|2,~vvv啙lKKKFy'2229rFsu!LNNwݻwWZhBP1\PPSvmcsмysA-Z0 CdQ+R_}frɎ5-),ehblO8NFFƄ /^\PP N8!S"44422׮],G!2xc7͋i`gD$'v_NoArJw]qBf{yy8qkӦ͝;weAAٰѣ+Vի۷hBZ-qS"!q-֘3;2.'Ĭ ,(SX[nfu.Ii)))vvvc ޽?kt STk\d 01ERgr₁!흝lF͋666ZXX.gϞ=|0::ԩSΝ;'oe˖5sI&jՊ٤5Hj00<==z#F7*2.uvaJg6 ΝswwΝ;JRDbcǎm'N1c.XCeq8Q0 Jx"A^VJ!S ~f}]3f}0nܸgUXɓ'rHZ#N 7xYLh 4VXeҳb<8L&8s짙?0[``` 4VWVMQ^S6m>|CN4k׮maaQjժU/_njJPwH ժUMk͛dIS*f5?#Kz׾RG"EGG3;::q#:VVVʝl$(#qё|1.$.]l77!C1ӧuF&W?PZ={-[lLJcccmeeceL&3HKRRFxx@qkҮ];ƜIk$G5' A 666닒M 䣏>TkHZ#XYYa`#LƤI0Eo.ÕJ_% %00P70A1e˖a\?\ܰU:u#ؽ{75nZZݻ-EG;}4WίX]d2J#)) #<<\8c Wi߾Zc6mMZ#;v%?j$!.5JQu~DS#ibee0@\#1qD Lm۶ pR |D qNsܹŋ3}ذaBr{1ߟ&㯿6m]kr7%޽{'OjԨQ_r%d ƭ[00J3g$x; 5k׎٤5HQ? qssД@ڴi#h]#~ #@WO\#1a L[ pRk4lP70 S#q4$Ν[h=<<.('Nƀ8?ڶm߉c~~LJݻw_W^.d2Fq- pRٳIk$^EV 5oߞ٤5_ 777 kkkFiРNW.(Ѯ];AFh< IDATL5H10>}aX[[ 0ƍ')eT*$L ߷o_fqj*"gj#FbǏc]c hH0v؁u۷׻ֈr ݻwO85kk֬a2R#}6Fxx@qΜ95ҹsgAƎ;2F)5' P%www Ɔ M }PԨT*ptQF@@߻woq{ fry^^YUFUZVӗԪU1vX7~d2ss *Ԯ]ݽe˖-[T(q߄{Rn0Q4h8(z4OIx,:~9%©Vc;+%Hg~w5j;vݻ ıٳgϞ=}Ç,YRrɓ'2ym۶رcGkxT248w9~8kժů[]d2WIv!|Ӿux}j۷10Js%#Gرc˗ҥZZf6ir Z]K+߅7||&+7qF֍Ozx W5F;FZ%00'I5jhq.>{,!!ѣׯgGDD|'ثEtQF\-@jԨѠA?]7aɒ5W*}jŦF]4u9 _[ժ{~LI[sw+jH$BPPPtt47nߟiƿ/F3w+۷oq*99ɖbyB"ՋYǕdpMk&erCçF̙3 ,`ѣѣZci8ƍwҥ 6W6l ql֭SNz[.wA'L;w;SժU?HL&hdgV;w``LQ9rf7nXNj/mgg'#Fu;wֻֈݺU sG2VZO?.d2S㑛XNU,DsӧOJ#l2D֭j:uһإKfT*JPՅQZ ݓKkl'=<<007n\Lf׬YSQF`T* "GӹsgzrkUF@@ߣGq&+/qFVӴn=%}reDYc̘1Ba=aFaj8' 7GW׳og.(111ofv&MJ'=siݺj]tѻֈG%<s혘f[[[M&6p~TǦbYZ`{.Fjj@qZcLoq={1cp=z@sz?fT*JPռkqwԮak:|CդIe =h6lذy>lH.]kڵ+@jժwM\yi9ywO~wqn~A^Akw=[GٱF &FEI=T>|x̙;vl޼9333;;{ϟ߶mijW _~8I x/2s̱S{r:6b;(&| /ZcPP&۷0aBNΝ;;w2eʌ3vPL6oތZc׮]5be\.ptuKZ#IL&ԨKreԛu&37"6 ޽sR~KZ#899ٳ%%%͛7`~9ٳ'j޵FT*JɓjǞLkdXQ6w6hN~Iz>CT\9f W=iԨQcذal~JlDk׮5' 55F@@{rYjRkԨaɎҺuk\GrjCH@PHvyx ~~~Q9S\2_Yvt߭ԴOK69tFGEE,񉣓ں-U%/Zcn5be78n:|0mllxL&XFQ(i0ѱS6;br= 4Rw}5J<5VPm&:J^zصkWkDRQj$'OPՅQZ#d棭9$Hxyya`w FHHH-Ϟ=C=InFB,7SzC3ur,Y[ײ(7@DV^1j(0!._vjՊyĉg&$$<}TPXZZvҥC Bl/5Faj8N]w'?Aus#&qqqsaرcСCnbvhh''E޵/Jnnnbbbbbo7n܈Zc=5rВl[[[_e2Sl#]I{00222J .$qM#۵kNɓ'CBB+VoDwި5vMZ#߮R(5SNa`(ee>ovVިYUeq]/// ի 4Jhh(=rg˗/wӳCEqpphѢE@@EffSfϞ~yٳΝkhGPٳ'*``;w!/Hν,'qwX1]<$'z#G =ђø۷oYYYussUV^vڵ"/_>444$$k׮{Q:R5k{Bn0Q|}}u05rW*go^#S}X[ 66v7nDFFjrNʕ+oݺ$mf̘r?FgϞz[.w]'LC1֖ū_T%\yרҶyq{S10pBw-Z5(ʮ]Λ7]_}Sj6ݻ]kիU*Fq) ZRke RQӢ=W.vXNk m#RˋeʔlذaΝT~:{,320={?@lll00Ryuf` #FzQ(F~|(W BкID qN;k,f?^Q"""Pkڴ&z᧟~bB#qf[ӆ Pkի޵Fy̶͛n.$5ܿ#;;[Q{ gСCKͬ_~5SZc޽R(5ӧOc`(5x )XM6T|rqСm۲/.[HoKP?Ik$[[[ ___cǎ:D!VVVÇ =Die߾}۷-[|DrJP($L(LD8O9s&}}}'L (y&5kFǷd]veŀԱn:{wqy{'111""miYxHmظq㧟~ݻ*5 0W^zlJE`b`Hy``XYY 4Jf*W6XbȐ!lFo> ?{j>}MZ#b`۷!VcذaBQ>3f7h`Ϟ=2/+{Bn0Q|||t05d?C$ӧOgĉ56oޜ&a̙&Mbv&MvUQkעاOkX<6@!$NbbmooϣCZ)+oIrr2n;K,!-xWnݺU 8p j~޵~1[RQj$j߾};FNkl޼yժUmkk+(ѧOAF RQZjEvvvBTk8p Ik${{{ ooou:D! F,]T+ A& K70 SL&۹sH^ɓ'Nl)S1ʞ={tF8Kw+y(Ĺ~KZݻiCuFJcذa5_ZR(5x Zڵ]P]#a`5~5j`6E8pZ#~H G}$CRjUŋ{Bn0Qt05r {43'@ 㴆/ɓ'Q_GQ7n`G}$O2Ьu5}`^,[ gCջֈaBA{FBBf@GG˜dQF֜C-I2ԬޭRWw- ݻ1<<\^۷۶m+(odɒ%Ç},((H"׮]]@ ;pX]JƼ|xƌ8q{{{{{{3"BZ#axjk\DiJ2D'u9 5ʫGjYcj*RNk k׮-( qT7 [YY5oÆ Tk1bIk$ OOO"d(VJ͔}Ί3999T*~Wh q Lr¼SͨXE,be˖?]t_ $O9ÉXT, x 9z7mM6}'%z2DD@DDEGo~3BX@eV/׫8qBWk>}_ nӦL qYx1jÇoM^^<6@!$εkװv!*.P.ԭ C@A= óg \+Vǁ+Ah(H=*Ǐ10ʔ)#(VBޞR#Qk"//oԨQVT ٳg10juaj <=F<8}Z&j4p;\^^aaи13hQJFiӦ JH #FTkO@J ƥ5;,l8q"" 2;hQ( @ڠ6Q# ¨6m,p@W (`TȀ#Gdl,{(`n.@L LeB`6M{8qĄ ݠA3g 1Ν;LvhH0{59RZ#P(`K(׮]*5ggg>""](*4olSj*>]t=w 6:̄AI6 7z1J%TիQktppH0ƌZ#yyy}U*FqY Z] C`am@6C4yr?wPkdo,/``T\YQڶmAQcȑjGf65#i/O]{W###E7aeEWJ*iaT𡡡̒d"axjԀݡ{w7 ݻ> v*pǏgvÆ g͚aǎXخ];8 b]Q5~y^ r՗C {{>}^CJ-uNNjx#O<077缞kעH`|嗨59RϙR(5sa`j>22]P]1[9m<|RSUzJ{o ;::2"=Z8yFqrrpww'1w"/bc %Ѕ`iqclH75Q08(F σ?øqǏk' ;)8u dÆ%8~qݰaٳg sl߾;t@G`?3k .dB-a\zgg7d2Ft 6m۝w'=]ofxM@5olYMXf ͑#'a]v(;vh~_E.]vGdt9L|J e֭jK%GgpV<{lU^` 4NkС~;Cё >35 5~&@00Hk,dwc8tH&/]zK 5Q18AN*WNS'{92"O۶mm Eѣ!4=۶mիWݱcG8ow3FZ P( !q\}vfשSfO IDAT M^k|WnЬ^CSFrNĄ66^4k2>iiHZctt.aA>G`XT`zXI޽:vhH cƌ/d6P%ggg _jFgjW\_[8pq`xY q̒dB|8<`9ܯp<|=MMZcNhH0͛;T k~-u;#G J O> <v8qB{ > O`%ذa֫WR#8q"P3f~,T*Fq Zİ J 6TR M@&0e ddѣ4y\8r3L ԦI??xɢSNc~.$/ Ɨ }`6YRo]͡IZHFߨQ#fqB;v 5j7ߔxk /z 4y2+- +wvm!N} 7qرz͛lB[Df׭[?z( @>}]P}_/w\/N-[j$QL4 FO/&LlJE`\pCVF@6lcTݺ뢐X&[./lؼ6o֮9dرPO@ N:5BZ!_m|4 ժ 8HF߰aCfqG3HqQިQu@:0hh4/Xp-޸9+-׮kl*IJ5sYq5v8/_~Ik<~8 @RRR00T"stCΟL^^4bɨ5;V˛8q"U*FqE Z]  X@tՕέ[Cݺ0v[t-@APv50i1~xAFSi/AZё GhdIt)S6ԦI0> ¨%Ɏ;&7pQ<ǮqsRlٲej]cٲЬ4kDGk9S|o,Hآ'B̚5 &]k3g l ˿;ի?살FIII|3ƍ{1Q ukhcҦK[>i'q3uTǏ߇}WVT ŋ10j5 vA=T $00C8K.~C^,Bб#t[t-<} ۷koiYx$5n5J&N(ֈ5Hݺu10\\\Hk,FMT2"B@X[I+ ¨qNs]qByfvnb޽wo+W " n݂ `G4Yz{ 3gYI5Κ5 B rҥ-[0~ɓ'iQMK&5jaaa!(|򉗗 =e| xj-gP"kIWW-~L0].^yff5"ߕ,_c囅IͦM{5BZ#axjzHƍ10uu 4j[fSɨ{ $$@BBao6LK3F͔)Sk6mIk$c`ԫWBHk$F~>idtttqKұ:_` 0<|8-Z$(6mtwnW|}/!7NjǏCVV1~Cݶ d`~{ SN_޵Ư bz|8a\x_~aK5ֈ(4'j6((@*]t2e4!!i4њl75K.7nQBF޽KՔ5- MBӦϟCtv5.ɬ,8t(W7Vxy\nH U!3f߇K10֭KZc!5beڏav7W5l  }{xx0K&ʼn a$DGG6AAA/b7أGLܼnv6YRoرؑ^K=K$&OiӦ]k톃-… 5iiĺƛ75kj?MZoU4YgOڵD~ i)5={V/)(M4i`ߟh>>U0 OOکdh(XY'oP]#``5bn=TYoV Az*k(8̙3zC CevPPВ%Kx"{IǷ `mo&cb画ŋp",Y2xxhdP$_ꫯp8c kX˨P(Ƒ8.\ظq#]]] FF99ʹiPM2o&y^Ӱf́ @  STk|xРA eXثW/8 NNF.hsdT<}ZׯjJC7q8{lk'NdBsX)5bg $-- AQmۆZcF(5-Mo-tKMmߢ8k,(H^^7|lJE`\rCVF@00u+~iFMyؑ||/ 7GzdesAA\,X<EFl5gA{' 5+i/AZ#!ƥ5YYp6M[@33h@&-@%yP!$NTTָl2!FYnݻ7MM2eӧCZDGk]%!* `(W5:ƍƹs]k?~< l ܹs6l`;evAZ#c`8:: 4QklҤ F>>SҦ J-o`aL\`jsѯyyygJH0^V S#P%` ޽{7i҄/M>ڷ!{ bo냣#8: 5' 53iH kʭ[کdDDtY@f;;#ԩ,p@H0;44tBvZ,ӧM%5z\M| *;wd? eڅi}{ 7J[ $ٳgׯ_lʕ+살FIOOp[;Pk (9ѱJFE-|~{?-z5pԭ͑!![ ??VT Ƶk00juaj$$88\rҧO`fS^4[`p((7vl+p*\egsF.K4<' i憁;99a+WѶm[ LlyZ;7N.=ZboxVJ<Ϗ=Z0It05dW$СCgvhh?((k֬Ao߾4q$cƌϗ¤Iǎi?[ /N'`lmoM;v, @HgϮ[e2Y~%;v0?fvڵ+0/f޽u߹so;vHmcfvZf{{{bTptP*Xט' 4V[@[ 4²\\ʾGRTz0MN:1[Vstʌk$m[wޯU%c ##޽{VTՅ9ihΝ n۶mڵhvW̙Sxb>bĈ7dz gaV-vvСr0i]_1Iu7nx' E۷_s $"^}U@Hv OݺG 96T갳yfI:tɒ%8tP~rJ!FYzsݯ_?WWW!F1fzqƒ-_'O$?Y ޳֭\/^ yFF$ș3gP(|C&i$ :Ųe]vo <<\QڵkرCQhժYQyyyzxիtV-S7o_?k)uV Zͣ#!!!aᝤ P^//,,2@HH{[Y`PO+[hsMWµk|FF߸W-P!wwE1QZ#Vn$ȍ$4 +d+g؊[ L^kqNFIr4F۷/VZ%(V:{,  ͉cIrdF=z4j .o?JR{Bn.:9v 23_ШQ̙3:g5%ˍ55F !F2uҞJ*MD櫯4iH4AAS5QQlzK"Z#w 꾬bYL~qJb]van0$EIՍҬhd5:T -[l @d$ŽJiIooDIHH￙@Z#@ r# a(Vn$HI(m<ڤF&gkk,LvuqWF M~Yr&(ϫrF5jj-һg1[Pa ƃpV|,MRo$>>bxUnШ!Ѱ*7JYhԈ5+ni֯|$F Mի5ׯatBOk|M#IQQP e 47olNEV42-ZF g^^$$hW\##{6nN`oJBzpx_<<<00Hk,HB#aHȍ$4< \TOaBC!$m{ :kCJYU8q:#2rF׬Y#(+V@qРAR8ʍ`Ĉ5.^XZQT*;=>xXo&߲6MEw0q"uǣUĵFrF iGWnШ19=1CӴl7 <׌ٷO^i,,43gskLIommiՍRhddee=|e˖"={`]~(ٲZxQ+V8h 5yz|Ǐk7ܯTBZm2 <k~ZjքSO=~޽{MZKHB#axPn$Qk{ DDUou~*z`lY!VZZ{i)M(q"""pyYfx~Y|3g=x`www!F1HB# 6 ƥK]k1bJ%`DL|MoFȑҸ1̝ Xoo7HkԼ%.4jHk&7Ш)Z㻒ټY3hٹ7J:h.]g0 5t 4 ٰa+aaaeʔh̖^2JtRA/_l[ZܾJFFMIms'SBºjxyya`Ԯ]ƗqF6mHh$D'x3spr0j|%35kdL&u떸ވ}ITIuh֬n =l2]CQLpC?]k>|8 ŋpn̙0i J0n0qqq+V`whLmQ£hN>q\TTՕ: kԨQ <޽u߹so~ǎޱcG۶m]h4qqq999O>5ժU3 ?t(\fױ,}RB~~~BB۞'߭HVVЫ-UTyooo7GNVw{^rJ޽= AAAqΝknn.?O^233$L'}a-[!0b;~* //cǎh۶Ν;Ν+z\jj{]#|>} ///ܵkו+Wڷo}vq1M49qD~~~ӦMΝabpƍ3v ֭[gffVXQ_ԯ__ɓbH~~KjuWق3g qqq(?HHp7hРgϞR3Eܹ#o j'ֶ(VVVϟ\(}gݺu+r­ߏ9R5kh;;7}B~+7o\޽{y͢ӧ}ݶmr~̚5kpԷo_k݉O?T{-[:uj*IH.] M2eEٌxfجY3ob#& .ۣRO:](?;w푘Ro^lwJ3vډ페$%%b{h4<9h4x)1qywޣGfCr=z0y??tҸ8f6Lӧm410DqԻ8x`f+~A{tO.Fvy.]ލX ___>99]+dddjѣG>LMMupp vUVRX;rrr00R޻|M߿ƶmۊ۵kpʕzͭL2VVV999J1j׮]Z#Va J%JjLOOo۶mBBµkgf 1|0lݺ5## ҁ#}q5.! %==~ zذab8OƦk׮UUU555tmذaFիБ L{{;$Fcc#O$$$8m45Rq޽ЮB(,,hiiwPZQPPiӦ{>z[ <صk0Ç={{ァ˞ƌpBjS0̳ӓJFFFvvvwuuݻ*#Tsǫ-6}iֽ! 333ooodI999EGGs IMM|}}UNvRR'eײ_~+V(**$7*//:>d;;;N謋c$ eiɓ'?t-$$$88̬4+++66ȑ#!dgg9M'66w!:$$ȑ#|w]r/ÃZ4p߈?=T8HKRh4딹|Oד7Xso0tP(믿{ί<~޽;$0jƍS-'NZNDpYgggbN1ȹs@ 5;vZ6oޜNEi xk]R)ЬKKKZ5ŽaÆ P( 1ZZZx%99Z4___hu:vx+wttkt<늋!1 ͣ"77h.y--`$p!^Fhud]KY7l0H ;;;q!SSS#\D" Ϗ*4fEEEF"0"W/Ss񬳳SN']#˲L8J#jaaa[(qȦM._LŋPRyO aƌÇ9dTEdz.--mDa՚xaC+P( 1?Onx"x3gMDRSG8~8x;::2{>M+..V~P߂nH$ȣGLLLG Q=ŋYAOOOjDiC=O[xaw'Q:///H ;;;T*6 122v˲*H PjkkAk/dʔ).N_grb<YՎA]UUƣDGGO4谰>jٰak\x18'd.kjY~=k\t)8f&$$hIiӦ4c8ONT*ugepҥ7#9}O/_N^?kc DpΥKkyPP%Ѻ~'Nkʨ<+tH Hk&>-[g?~ԩ &F9WoN k\t)t#xիWw /,X `0BqQ[@n Ph!!1lllZk׮k׮7oy)]\\z%`/^i&C ->όa$ϧjʔ)ȑ#ǎ Nd>D"^LYuZ=== C'O֚ x5,MOΝ;̙3r__XggYfEGGw^RR}jItt;v,L{r˺u҈u|xҥӧ)ݺu۽{NM:sqڴiDKÇsX¬ٳg_ikk?v옧#G^iԡnذh___1/k0̡CFyPii… -ZdddjllܱcGxx:Cy(nIKKN'O?۶m۵k'NЎ58u^#d.{d]zz#_9Bb{BptrܹI&#?~m۶!Cfff=zT;EBhh 믿5k:2d744޺u+==ŋ0t&888qǏJ;鯼I{̘1^^^Ύ&&&MMMW\Yn*((Xn4H F333ooo_zL&{O9&&9%Orpjj*mNID!$HϟWG˹-BP>SΎ2UuBy>}adB z8A 7e,%D+WΜ9sӦM{ᙔ Ì1?>},hyUΜ93n8Ǎw)>j ˖-ӑҿӧϙ3ZXgʔ)0i'JR8]yco%ރxNդ_h___1.={lxڵf}}nݺYXX{yy=Hĸ|2tgB$IRcccsss{{2߿W^BFDFF5BSց+BbH$vןѣZmrbᾉz ::Z^~ \R;e(oĉk<}4^#<.Jw nN81}t2^򊌌:(HIIYv-OF5RQg⩊+W9s9sT E㈎k^s>ܲ :4669Qk"eeeʉ!pxYwk0$Ɗ+r9trN:uuQQQDS%ٱcǼy?3..D8Qkf)oV:yaZ>h:!!!β,^uօ }"BJ91zzzDQk Wq?ڢHνF8V*eڵ˗/': **JQ/>5BHSU/ 0M:eddysAw/66Wz/EPRRXaarH CCCqkkQԐP$7|=;Q4H^FhH 1pwEQ(^#O&R41cW8˲z R91(5RxWqժUkƍFEEq;kmm0aRɓ}4~rr~Kt@@S]pQQ a 1{nڵkDϛ7Z(G\\l( :5R H ###1ڀBQ&44Tj**uUhxAbXZZ>5JR nYɄ2dR4ѣGW8˲z R912(5RxVJII!z>>>|B87ck7n2 v8k֬!zOF, sׯ_?xq={WzDLP QAb^O?%NRh^5M5RH KK˧Fm:a&tttHOr+R4QFW8˲z B910@F oq͚5kBBB-kܖJp;EIJJzk"}, s˙gϞ%xq?Wzk׮Fa!!!!*H5k@H'W)@LL ^#İxkJw6%z/<U$4ptqqX_,eR4 %hdd4w{%%%bO>}jmjƕ+W^xp___>jy>ݻw(//W}j\{`XǹJT*=s $3g$YРԍ޽{[XXXXX VZEß HOO/..?$/_%H$j͛7srrrrrz)H "8rNVV牞?>O<{yyyĠ>7k׮{$x^F:~cc'N@XXXhoo/H$jB^^߿$JHݻ=z$Hxxxrqqq:@II᥇ a 1áGtvv滺cD޽?iFk.B} xjje7m$tD4i$۷ cP1!!!L=ôv1NNNt? ϙ3gT5X"99k 1̞=![[`GGǮ] LNNv\qㆉ !=Ƴgr5-`wS[[\\\ W,,, I<|0???66.;vObb7|Ct`` 2vхBT{銿GGPHwyZ&Nɓ'y<|ή/˫0 CO:_=GGzǏHN`Y/3k֬K.†2afa9slܸHب͛UUUD;;; Iff o޼)T<* 2lΝ3g0DC500ۻdBHH$ WeϞ=s΅"KKKyL533^:::FFF>svdSSt+V=b1kV+",boȘ ]xu 99WN}FTWWCbXZZR"o߆qРAݻw6pEhP()))D,+LuD1555^n^zzz]t6 BQ%k .] 3ׯ R#F1!!s8dp;EIHHXl#G,,6zzz o߾ G+^Pt^F:5RjH kkk5R#''ƁRBHMM=T}}}9SSSDN\srr633c`[aahD!-H~s BQzA5^%Kkܰa)@9a#@T St .<5\t]#Ӄ`՜xz 55WzƆzs k(K.A͹טF>Fy555w!Ԕ^*{ ) E ^#oP׸xb8nƍžNQkLNNk $Z&sX8EspҥK kXܹs%kkkyq5BQ@bQ"#׏z˗]6l^czz:,zzzrX8Es{.ݻwg= pT*yR(zA5^W_}@͛H!ט¹& ):N||%K5jҒ{RD7rNnn˗^#HOOk^#ppp^#E0޽5S4th<==9\B4˲CpR[[XZZJ^0 T*P(zA5^EÇQ E155svd/^QF?VD"l|uo߾fff#F੖ttt߻wv­[bX,pBNw"Rz}^ff&]CA[YYݛZ<==:ڊ(:Nqqq}}=Ƙe٩Sr;dDIRr$kSѧOcOGGSbb"8''GOOXmmmҥS-Ʀ5+++##rKee%x0sKq̙fdd6m233㏅ ",[n?C^+illC555LJZ#E),, ޺ukpp0ܹsO>d T[[{At>xXUUI0_-**0`@EEE']\\ܿC߿.--ׯ_iioVVV֯_No߾Ň۷oQQQ']YYY\\qFLm۶G&%%7_L1c߿ IOOO,=_tD$WR5tUUU'-X}]YYР_CWTTt ü.//葲644|mw圜{{-[ܹhĈR333cYeʕӦMϟ6mZTT~sss.]0? E'hhhBY755noo亂 ^UUVV*떖Эtkk薖*eֳgԬ[_~tssC!qCC4ӧǏ_NH,c$H0!T1njj"!CCCRB9cLFv1&u c\]]255I:",,,0eee!+++qII Bc\TT"1!GGGq^^BG?!ԫW/{ȟ1EcL߿?bB ߼y!4x`qVVBcL>:WWWkBnnn㌌ Ƙ:t(Ƙ06lƘl1NMME`/^'%%!0ɺÇc/\1bƘZ1>wBhԨQ?!4zhqll,B(88c Yr%Bh~!Գgφ3k֬!O?a ңGPdd$Ƙ#0ơh1Ubnj[gbBϟ11%)) cL\LNN"RRR00Ę'<<<BcwwwիWɟ~:!1vvvFݸqc8!tmB999c2᜛1&ǽ{0 >Ƙ }0Ǝ1{O>ݥKի˲F3gNsssvv6ݲUpuue&++}ʔ)3f O‚܅l޼ի?CQ!}v++ݻw[xiƌC222`Hk,{19a7nѮQ011ӧݻwoݺsNIѷoߜ^+]?zrzm˲r1Dr)& b\.'ƀ\.'$D#$\.'KR\NX&L&<\.'^Ѥ400$ZCCC\Nj!tE."r9 "rr؈\.'7 D[YYD.nݺr2E-B{rSr{{{dL.9.ɜ\.'`rW^\\.'shVVVro߾<\.߿?BF.8ݱ\.4hBN.y<lkk|r2 uɧjoowqqqtt!djjjccCҩ{D}u33N?DussN?Dҳ[XXGnll$_O &B[ZZnooP(&INjdD"rJOOD"MO\744FFF$zSS\oiiafرQ\a޼yDۏ=4J ؐ0X! N"r4>DƇhMHdccCrhDFYbquIϺgzhXxe{oinnްaêU5pJG̙3g/32;6Hzj-R?MQ5"(11޽{ |С7Ay޽E744")^P(}ٔ)SȒ۷0`d6l@^5vZxxutt|… @ST֚5kȪ̙fme=IJeD"tܹsSSZf???2gϞaQQQ҇Vc fȗl6ӧ5x\#PJ&;V?MQYcǎ%ڼy ,h/+atH_MMM{UUUIl!%=6%%|u„ [a:2(o߾c:1Nsq4BͽlΝ;Kʏ=:dM}b="Ӵe!y6,H.Ns6m4r{ǎpn*SN'O$o/k+* ֬} }:TX333prRKKdcƌ!(--=}fڧP...ϗln۶M)vԉ~k>qۀ^+k.(hjj"V@C#T R [yS eD m@/WTd`RGQ0mtU<~XySDZo8q@>zP+ѣGL`---*ڵKy#T%'?:t#""Uz6#ё2hZzYxժU N!Xz5.ltUV__OnK HPf͚E\6WSe"#رcKԆN!Wr&F Tںo>02U#:322|oyy5ʠm;{dLLLσ,++ џVVV!r312UlmO}`Ub˗/ YU%`0^|Z2F =… '`7ReH"J ,_yΝ;㠈~"VpԠQDiii{uVii@ prrrwwOLL7nj H 'W ]zg}B%A|wQy:ݻZ= ,bYYY988GGG3N7hrO0L%&&&LhiiEKX,:Ռ .%Ϟ=CKlZԔa  rn  KKKD]]+++Dmm-] kkkiAԔ%666hIuuuii)Ab4dAQ/2!!bIaXذ+IVVVXLUUcooHQnnnX -`]2AAAAXLFFŤc1QQQXիW8,XL~'Ob1C bWMhc1ƍbO,fʔ)X͛Yfa1y(9۷ON.]eXbŊؓNsNBBŠ+B!*CwB!FKL&sZ[[%hImm-&A9 ʁ3sU///`^^v5?33-yq׮]ђΝ;c?߿>څ k׮%n݊CKz|r~В>}:u -9{!CВb?O81rHdذaCK:4n8dԨQ%3edĉ3رc̙3 :uNt/^\tΝ;۷orϟZ{^Zr"b8&& h/_FKZ[[郖X,칰MMMGK,--ﵵؿ9z(Z#F%NNN@KJKKGٳ-)((8q"Zə:u*Z>~XoJ q-&UvX,b| V#z4#TGoH=1ԣf ҩSHG,_~MMMA :֭[Xػnܸf{@]~ݥ[{"u* [[[,c1NNNXLkk+"~r+Xi X:,][V]]``:yGT ?~vضmTzuuuhaEEo.;}tP#JF®ޗak׮Z -pv=f̘y_p4Rd2Ji4Zxx8Zbnn.7FD``1X bAA`1ύ255bAm` 033b[wAc1؀g ,,,l3AXcmm-7^VN#e˖'N~aJJ zJeeeX9|-Y_qѣG0@J )`ڃ~%^:))Brrr^ϟĉ#G`djj*v;;;1rc<==ɍ &7{rcz)7wrc'7fȐ!H i#FFRH=z4:͛X@MMMVV62H.//̯B/@ q5terX}n߾=%%E U*Xnђ O!c@6H}}h4􂕕rkk+ e˖3g%6lPz밮2Pʉ'>3dذa_}=.?~ *5bO &Kn+&%/7Y+ 3G6G~>J˹UCp3wr3wHCbB\XQX!&`֥బ"QZO~P}N=4' =:fNqe+{[Tf ۑ}TMiྜ Y鴭W?ܲE;ݬx ّYaש4s|111辂 ~! *觪Z V,3i#~Se5 ggg!$pʼn ?/-o\c*WLy~=`п}7'^ng <<%Ą?~8Ϲ-&4uү\Qv$-AeM/U;6[-|$#>99999Y#X,dwKDys:i=S[(|qo~O 6#t)ZwɧK[.MS`pjy LTYqůܵ`X3تeJ^~UȗRJV1+wf%%cKEݻ7*% ~}<*5m߾]uuPʱc>cdĈk֬!Rnn.H̀r볳XBpkqmǿHUh+֐u 'ٳgu0xIIIIII`iidj`=x1Lllet FNP^^KIIIIIQ;w`C`2Pְað ,,^=. X ܵz\@gΜA)J~z`=sϟ?BK<<+^~#*_>_|kŇnm8qĉ²:9VL9333a]ek784΋G]6 |'޵|IcZyZy}ܧ<߫[ڪRrE,7?aYsX֑M/r}ґý669'&poάc+L JL蘘O>nٜ .60- K!zep3 .JMm.!%99N]yg[_bfUBo>5 Z]j"&۟+r<&OJP}`/{/>ԧtVڸ_*;BPVpiyNM3ǎC)СCun?؟{ǹu\τ`MW3@[p>Q.rjsO]M+|Knu5ΒahfñKw!^:n9zIyrp>9p׷'_AVSAAÇoo/Q\5';_3v1dCnW4W0MT.9'_ۻMcYͭ#h#\䓐䓠Z=4 15uTf=vC+q'y<.)=zM n|o߰JameK͏?qKbmrwjjٽ"_+Z}Ǐos]屝}|c.Yee ód4QA Q&MKr677ck4l+++ X?{:Xв-ONy.R  Ga 8#`W>/Emq9_JOyn~|@#bgv"=֥޸*emb1GQյ6waew徾.o2_~qF=#=--uݻwcO?DP$ eO}`YA {43Fxœ†Rv'kޮשZ… %/Xܚ>!ϟ\t=ݒiVU,;TvMZ쟖YTKP'$4}U Zٽ_2vIJĢ疙YhH w%SG=:{2FZɻcKDEE]ǢE[^rޏ4bٍ_ZAwe /jK` h]eCoo}zSnqR\ۀ~I^jt:i*jj0u7wǼke\^U]XI-W3?wd#3ҷH~_}W\*%Q#b6,YBs7Gk۵[~}1V"-sN,!D'i1FE o ƘҦV!+q--BQhN=DFevߓ m30Skl@$l7={~'|B6nq'k=:*n_@$ y^S?t{ kdgJhqfCoO\>1ݧR e%on&W@Фob=-YNlkz~d˲R MsϐSF`SF=gZ.q//2(zI&Qy"5 ŢQ>*?5{f(irj@ }^9ʛ+;~ mihwIH 6UۚjzMlU.pF9o4jt7W;^垒\4&ҥ>^[׿ާFàm])hZ(%%pajO@,uJ0FX,cmbr=`h9noP^}wEf8$Cn_/qQ[SGHRmY6bYq͝˸RTT`~⌦5*NLwd ڑ fx"Y6b~^_ja!ڬ^1F*挶WI4oWw >nKWwg 'eL]-%Bt'_g'Z=6L /)KL  [V73kl<t13?k}s,qFR{4͕ܚGs+kl|ToOzPRs/XֺNDi4O*n7kޙX,eU }?l'G^.sΊqjBcʮ{S[r[toږu0ޖn..y'+LL55e`3^#|1kLڇr8qu+yP dLXtS:EcYtKLX4SGչ紻ڨ>_͏A-lǭ5n+!kb>whaLѵ3E0ĹD2o)_y5ݎ>R,qdsNg ;UJ[Ӗ P(#GʹU2X " }>sѕoH\tѵEז\iMKԶEPP_-1tpVQ}.fL,D*:El-ׁͱQ~dMIcVFٲ~'ڃ|rFev R A5juM&(M|L76mW{M+7'm`)mzrzGb/mA۟\hl{rHnEuu6U&bgֱ~Siu< An~S*CkԸ!ȥߒqMXY! i+zŕ4ԫׯ}:k<@#lO7`q*2H͓T־ e6&\Kv=I慌=7N 3̻AY5Ϯ޽\z矢XS[vs]?(h!eM6YeTI4B ^ 淇{ cΜ.7~j|2FC=Γ7ݞ={f|qjj F풣$M,W[[kv24#+;/jg~رcׯ_Ohav-2 ɫ+jm|fwϝI8'`'ITzۓւ̟SO׿´~W%QspYK%鏪rSMVnZơ룡$MFed'P]]]&ҋC0<5ں2m~x<sp@gTf+8ܜ ʯEPEqJ=l˿ϵ<Rk9rm.K+y?g)olOmO^̶H,'T uV txQ+mBq6f!X_O]#Wm`}!ɺz<4PʌQ_DuzvT:2sK2"1G/ tk?PϑLS nn˘6qOթaIC-xAꕲjBKJ)FCA@ߢg%eD.Z]E=M#"ޭfp0x+@?GV[j,';ʃz1tH@Ĺ7=߳_]gP5"IVEfZjKq:Sn=͝|CSEW%۲H)kH]v_$1\`Vh}x FC{8*/s/fw'C#beVIv_ޅ*m\[DB߳-j0 ɘX4=T(=U8:?3*-BP=ǒYg6fWG;_OUvHޔd+Щ4M槼 Hi)A#V D hVPGdEK (/7az4BRCǤ\>^p 檸)'{+f]rJ:mv,C;Ƹ$STk%sڌ|gmOɞUkXxkrwo~v$ٮh/E{#hމs y1NJiܝ>䅗*l^Y5%)a"-A>}! 5jJvM#savI>Z3fZ !nk*C#BP\2ve7סC5 Z~}8zh۫5 支Ƅ>~vbwGs'gkbEU-uOy~}ӣhۘK.w^[sɑg9M7g_7deA#͜>Ǩ\(ED 3v3.ԆòM/Tf.?\d2mU}v?m7QkX{o{[ełi)ae [O^H/=MgM<{sNJJ"hVeK14+;gZp/XRk F>.Ak9w\>lL#~_Mv5]wePN5z0MMָ=˽H&5vܶS<d+#MgA߅p? KM`-cD;q"nZ!@Ak&8,c! x{@#F7rNCCǚ1LSJiz{ntB/k::Y{N^RzAev e]@K^[4ݗ3*>)xV_\Y",fv6vm߀Q~/9[|RIzqcEUKmˤ2L&,k'6ҭgg;fmEVv8\FlSF,V&\rrr߸ЭZ X4+s{SpwF +F(/}m'\.s2E3lL,ٶ]z8vNpa߸M:ǕpN :V~LfCz-L Aڽ~F봏NHU;}J999"kwpe[QZ^ҽ#Օ%֎.P.SIOL?0- ۾}d7ZZZÕۻw3В &@jll$  h~ @ h}U:ެJ\!3XCTN}F߱cG ׯZ3y7֬@e ~c!gg}.*0r 腻w:`𒓓թ*ej*Pƍ9r$ZBZz\cf3,&@!Fk? 8)@۶mC)ʔ)St 0PYYY7oDKt!i}=.PٳgUV}!6b%44+% =-@/DEE@MPs]eGGZUJ믿NL4i@h %V$z&:As\:6KnMUKm ^$Q)mJk/5eñ핃t+.`wߪʖj]: faYsXL3]NK^VԾV|.J?k; eEŧӱ]WIc\(]R{v[Վ ܹOk Z gY ;ӡI4䟗lИbBL!`z\dۄ6`nAzIvJo:͊ڢ\ j7nܨ{~!<-\lL,U衺ֆso^,Mtstº_gQ̡^)&~svm'ɒΜNGr8pݽc#nd;YnNv,>.КƇU9;%!UEQa\һ E?kn.Gm탤c\"w񑎡=ţ+wXW:I}"[nQnNEܚ ŷxE0׈=o(7ʩ߇lA 2k,oG]z- ٳ=w4\[Nz]~I޽ ZuR*++ {{{OOOk^u>-f@ v4iZ}vVyz\@>w\t_`BCCd@?ׯ_ݻw޽թ!22Ru@988ƃUN0B`Fz h\Cz\@?T*U6@edd\x- ի=* P]7n`*GGGpe_~AKLLL͛h ͞={6Z ̄%M6%ӧOGKjjjn݊p8)S%;v@KvBK&L_hرcђ-5jZRXXx;)) -yÇ??aÆ(z IDAT%999ǎCK dee%DK=ztisCK222Ξ=+OL,KX,+++, bJKK777,b ,&## bӱ(,իXL\\sy,_~Xɓ'!C`1GbF`];AƍbO,fʔ)X͛Yfa1O(#}З/_0L+W$={V,7J~bD" 1MMM*Bb;(F `1\.b|1\64iRUUX mڵd Ylt@=VgϞ0$ ]\ !M|6 llllX,JZZZGGG,b*++ɥXLpp0SPPńc1XLTTHHOObׯc1b.\Ō19u]$رcXLJJ ~KnnnɹAܹ3$$6IHf Noٲf|I e˖-:IOzD8[[[t<)))%%E<|܃.\pőZ2uwwOKKz@YG'W"PJ0Lɫ5l?hB ɓ'[V| ptt\t+ Co2VyRt``2 #P\Uz\@z\@`2  u݇tYW *Wmqm DZuTEPTTt}3,,LW@/@}2sph6@ h6Xe*5yyy9-ڵ@U*=. =. 0V`*5 UJOOOGK|||"""t  *=6@ h6@ hUza̘1غ0P(ghIdd@UN#mqmS``( cq "I-腿 [Wy„ 0\(%//ƍh_=tO{ЮHqoy%`0LLL,,,bcc}}}"`2 2P~kMR?qfϞ5!O\{555N +Lי/#jkk }˗uUH㒒M6>}:++ٹw'NUHw۷o|gggwwqƹI:nݻwgϞ---9R|T}{SX@tR&^_0jԨw'2h hc*eXp!8Q VH h 3f̑#GdW;\pĤ'O&%%x<ɓl6[nSVXX'$: ͛7Ov}֭\.ԩS nܸbŊ*vr8+Wfddݿٲe666A\t)55?r<=={M\bbb``Jv =f*=./W%#ƎǏpvZؓʴΈ2?344Tv]WWzMr[ 9~JD"`ӻ6X,r[zmQQ &}w8za鰮2PS@@YВ8]%Wd,jjs4}֒G'9SU_sJȈ=@@& '))ĉClŋj8 ܬ[;,]v ID^a'W_=.Y<<1FOȩǏ'&&J9 V*ӓݻ111;F2q^`*0 CWCiӦ3g%6lU>1W xzzJvO81x`U=zth{ݻK@D߾}ϝ;f2*s9s搻 ,10z…ZK7nݭ͛u&&&J/LDyyQ}=;wn\\ݚ:tKYS055ݶm[||d ȫW|#F\>WZ|X̗_~|WXŋoX̼y 6`13fblقŤ`1vbƎ۷1bsQ,FDNb㱘 .`1Xױuܹty!(y);;[>8ڵkUUUݒ Kqq1ptt$K`d D"QrrI b X,9sێ;B8za2PSΝ?#$::ZV^}Az\M6:u 3ft֍u EEEEEEi*///33&*-ZHO~m& =.&M3cƌsΑh FɒH?iGzX,zc'ݙʍ`=l6[n=`133cnnH_#Rbx֬Y?E`>.M,믋/vtt7;\!!!ģGB 555:`0v9ydٹz\x㵴%&&&,KWC,--[[[ ;{BHa%[68D[Ukjkjm}ZZnEP@PQ#d\o09y?O'-ޓ\:. [l!z</# md֭%>QҲ )((S@+ZҥKjZhp8` @3  )>d2*^FFF_$* sVXf a7|# r8q 8q"Y@TyS›bFYX" -h(8LXu A䛥l˄dT)!EPoSWky%D+94FFN(ghgm:.r랾seUZUBhKԉWȮRݠĒ;T #Y ^O#. {JT tIwzUtLP'jv`2 BJCE[ ˩G%ĝ|KbǼfxX,ϿU*0Wb$yAh%nxtzxa2l6[[vd}e2h`cg(-wifoĹ[άV$#qVs.*g]/M^Nt9_$RK n^C5 ϙ޿ٳH``رc={]W916>h0zF~o9ǥ̚G;!Cq, r+̜tX"nⷔT&{ُEn<(. iO MG̒yMd#|>(aiҦϮՀ"NH(dWD8$A&~k#ߜS4AJ/_.H<{?E4kO]yKa,eUZu.t\T?Ǝ]e'Gvf-I>tٵ||&vsss37/-~ mX}gDq<טS"hlqQKĿ<rڹ`rpEj4Q{Ŵ>+'7gyۑu!<:D%*Fzظ3OZx tmD#݋3:yPe3vjݪ[uŭ>'Bfm?e/_Dw>yS*2ۯ:.WNb%}H'A?*gQ/lNrL+`ݣrsssss++̤a6Axσ'[.RlͲN7(YnBK$ؘXL Ye-[FX{ͽhrMg=>+X0;qCGў#ݫnp3k~v蓋\!'oJߛSt IDATؘڰPY6چ{Ѻ;<<,=,Ic  h飧CظMq ^U >}}_6fXWh;vUzQ#G:.t2E¸ K>A}v}jGO.DyAޛB?]vmEɗB40c\i]81($AI$6~_'6 w,@<ҹ9Dԭ/ꠑ%bE1i e1hzw $H ԉS[AƠR'Ȋisqd nH{o<n#?샍G Ʈ}r铀ޑݫ~1sds%h"^j=!'@G:l}rIh9H,GLAnrS\vBH`_cnBu/$t%wc]*-1L3l|Lݤ㻕 'SM{Crv!Zt\ЇNEYvw5f)l”Z8IB<1sv},in/&$}]"93GsCVM6ƣ ~„5EjHw*Q}Az{#ב[R>tU\A#r9+v?kVL5~<_ӖYMK(=§ۚ;k~SlPC:{ί gXS\ސ͙: RV~l΍# W;̐{\Ur~pm}ĉXeUqA߲:dgWsG#yD^<ҽ;}k4KWU[?O.t.6XΟ+ˏߙwʴCrTq)?fqyNtTh ?fXiL5[΍ߥЬR|ֶ{…w7/|*}y zi|)QL]M8 mo|XJqo9xuפҫr$v!v<܍ iAӆ;:ھ7M$Bj#BfboͲ0Zm ' ٵyWK^-z&$i6[xMuiݭ8X9BwÇyBdº۷oWte8D[íWtA'53.ȯBgӦ(j'''8p {Mžu:tO>GԴd#seU !kro{ŀm7\o'_,y4;l]f?e -LX#Zrj$Bϴ}͟'ф `]eKXWVӺpBG:S? d\-8qt }J{]Xhդ.Qn</dҨ:qkB4 0t)W4'DOݕSt >'KqA_G:Nw.3u$^:Gd8o\mnFFmv' ned<5շ7!zflv E2sj4V׵7 eoc6hn_՞:jI %oTֶ7 t>Mτad4ghfk[t(T=!mol϶r7v7fN};U-踠#:z=I`k)]rQKOL'D"i|p;,c* Qm'PͬoL踠O#FGS.-~ =UQ {'a~ &YXXήלLJyi vXWh۷D/]OE]׿lsanuL(6lدxzz*3gΜ4rĉA@+̟?l?Hǃ΍ߥ7<<<<<^5j1mn 0^-n;.@x|Ͳ _tݟ ^u9! *** kkkv|$ :. /&믿t׀&4HJJڿ?>2|RG̺ʀu? *ڵ :.PH^^޾}h@}:. qM h\CGHb/=WOO:. {uA6mڴi=}G BZa֬Ydz=777%Z,*v$\f~+%5]'C4=PIku)oAٱ-G9 /B-@0NÝ (Rɭ%."N 4?#ւiJvE/SR.!, ɮ^yσz%6gA/&=.wߙ,- X",iݫ~XVä1.%#_/MO>%GnU@غiU9K r*'U^,WOB"ޠf 0&]PXT$cv!jciiӧO;;;ggg9oY֡H_%r|͠1}i~ٞ~%BuBBB(D||?t9޻{oYX`B obuy={#Whw[nUb]ڼԵ7"8tv :d̀eqC迱i.N2R&|߻qxO}T}tf:k)&!Ĥ1v?x >b5uOnl/Jv8$k`@c8zT uu[yMRz;qAO&eOKA/e~;"V08dJLȵ!WRNv9@غLc=>AwĽ$/~r**Os7r" Q,M,M.::ٍ1qqq!{ 4a00WhGU6m +ܽ{qqq8p Y@ht\@sCQQQ^^>NV=JNNnkkGBCCa f^zݝU$&&ڵ ZhBQn]e`]eN٘!DD|%6zNE5\ D?SXeǶ2apT]NVc%:<{,77qvv߿Vax27koڛ'_v1bt\?~FO+Wl۶ ~ӧa]es-u:wa]eEUXXxi|ʊb'@M ht\@:.tpvv?ZHw& h'ND/=kG5jԨg#ndܸqƍS]Qڵkl:. 111dz={{{{{{:. qM h\O>G\]]}}}ɪRuGh &x<|ٳ2PȥK~'|d̘1K,Qh#gϞuA}ᇰ2R/_uA\t qtt$du\@g@tacYr2U^$JӡYf c;0@gNY:.tpssG^& BX"h+oήͻP$9NsYg˲ B|bAZٳb@QG|2>bkkFƌ3fICᷩOmԵ?43$"epPZ!22@gcccccCv@e%Hom57sjU=H)2EXKGolOF]  Ypʩߙ}x_qX*mcG "N\e뎈UW'w4xqz+ŝ_:@6Xmⷼse5%u *@ |ӓ0W]yte@Oȅ dU]7{WץV> ĹBWȫmoxXJq4(^X7]mx!6_ /_D؅Osυ>Sezwҥ2R7n܀uA_zqss#$ߞt#ujW-q&ܑ[4Ԣ_F5;}X:fYFikVݤ ^7/-ڭ |(!.{C:'sjTdͿM5[#]'}ChDSkJ.=Ѝ4X"~7k}c;ɷ2;؄c oӂ؞uQ:4?qSUo϶qՠ}ugǫ6ovk˲8-м?SK$I.QRvipolؖ)buqSrJ{vo[oZ^*H;tlHg'Lއox_\鸁׼NeV!skID|fG @V1zTZ(2aC?q8I> fБ~j(s4ƥd1~ |fCZ˰2衱c޸qRt#Ѫ+J SMʨʽSbٳ<: yg]U#ܺʵc<´y)8Jv ׳RžNfE񷩻wF( ~rrlnoi?XqB)v\ v+q5f<8yb޿ "Yֱ~rcvrb܍D!Y^;XMRYzRY:ff4GO.#y ϱcwkok ׭R&lOcX,鲛/]Al_.J\ҧMwOt#k'RE$Ei$>GF\:<|055 V1ۋc_^$-Wg7/h,)Wȋ/J/J^uS4;Vݬ *vNϛ˰='O$_ox Iߎm쿕4 ,#m44M: J¦kwTfg4t4 IwkDj} t\Ш3/7f<8!]3EKP#B"B%~{ iV=BZmPRYtT޻:̿D]ab4%$CMeϛʎ_9cw4*&{~CN&gpQ~/bcƒoB#cO.)ͻWpcˏ(#OΗX:xyy͟?8pjw1)\J.UX|tK8)]5#fc&>}2BaKcj/Ŗ .̱~H5nWlJJq BlSލC)STUU#qV~: zhoG,,^X7x7TWT'lYb>>I_]/$>5Ue=QV|wϥ/&I;{<S5 23s"tV]p..? !tiB;.aڼԀVP+߇4iuϮJ'ߞ~ӽ#yx Vc٫.4s?3>5]7?{9ťS]GtEPe[m9.V-R\!X~άSruOwd/P,FXnUZ,Q Hq[U_(B:.h ao_TTA^S{q(Tr,Ugӫr$v!v<܍ iAӆ;fJ;ھ7M$Bj#BfboͲ0Zm ' ٵyWK^-d&$i6[xMuiݭ8X9B°&q]ΕU-6Py˽[:뷑Lre›ؖcuIc$L޷0a.Bj]BfnWnZ>ӾVnwlbL t\ d(_SuwwˏNvFՉs^򾫑wQf{nԊi&'Ic~K]9=߯;Mw{um#~]:d+?SiqÃRow IDATRR__*܌O.&,Ȭy\kooB3 < ޝ%%dTf?/h*)okoh%b:Lm4vlnV8u7JJn(M+imohpT]} Ȋi]7Pz:tC:L؞mno9&]73ҋDv7xȑ0c @+||$%%+:ujȤIK6 :"i|p;,c* QmϜRY ՒJ ,{իWU# ݃uAԤ#(L#N:OGq ?aw4 |}}/~iE!CU P=~x[骙>&V.W9s& TJJ aLT8qbPKO35%>K+ h|~x߹TnBk׮ A hK-)M:f3t L@W`!t,/<{r >9ht\@2tLJJGCCCɪV믿z :. ^zfFFLW 9yW_}L:u͚5 mڵk*CZna]c hXWP]]]vK'[iV&@4*@0tT E"%b !*n.BERqC@@t<<%]Q}DP?C(Ν[]]R`~ =H) Yި 166V~ǟBFlѠ;9"HEsbX,~ybb_|abbcǎS:.7ovpp׬YCn=03d'ܓ0tuu~Bl۶em޼qFBƍ 9[l!]m6BΗ_~I_ 9˖-#%Krd1,^#{bӧOrf͚Eȹt!gڴiDB΄ 9n"3F9r$!'333l0BA-[L(VUU͘1K ɓ'̓/_Nv9055%DBBBd\m۶ zʔ)3fPzk/;r䈻;ve:." (--=rOgjj}555=tН;wӵ?Hfff;wTAo ̣D|sssk@:;;#&&&(rB Ci1!J*cddD2Frd'inG6Gv;t$nۡ]n;9zzz]rLLLF4ydKK嗽żk֬پ}2O}7~">z%t\*++'௔tJ__ey9]ׯ.s 2gС]DDDt3jԨ.sbcc̙zj6z UVVVw,**r2 -KL&ÃGFF0`Ǐwqq}vEwƍml%O? X,[bMϳa6nyl_ڵk{轜 vB k3H$O +ZHT;\.aBBS9ؠo&K޽koo;dԨQ{N %$Ɍ3?N &?>%%GPTTT>|xΝd z5=rÇ?~<33odDm B";wn۶m999S'O޷o pJܜ}^|_w͛7mlpm㷳1-[_p…=vN;aOצV7l@dϞ=`W4Z2e UիW|meeɓwz֝;wR_~ex׮]MMMhB oh"dvlr27o;w.99矯ZX8vH$>}lذ!44T6#dG+WYH,n޼? _~f'q~Çx{{'$$\2Zŋ{yyGGGgff~ᇥ=xҖ-[E'[nM3#>>:cK F3f̘a^=oe\팩T*׮]pg͚d^:hР*aXWnkkʏٝbrBAAFs}S'~drqmp>MV¨N8aav#L&6mZw݂#h;Gwڃ[KÇ7|7ٳdDN^^D"999֭ g|I+0Z&J}7S}URN;a4K.^zj|+ߧzg-;sZnZ֍|77Fݘh*lСUUUaomޏ%r7SQ9rĊdj|@p8nݲ ΝKU[X}9 /tFؼyn 9-::t3Aΐ]zpB[} vB3MP_|>8p _Z]T*l k/ͫcϯUO֮]K?䓴g E`` `0iF?~p1" vBK}Yʌ_y}}}JJJ,;s}77ԩSTpbb"Y$$$3ϊ=Ru^RT̿okNhSc cѢE߷U?1e2wEqo?pGKWWWӞ=s uLB7u& .YD73z stBi4۷7TTm (˩c+l{q"`$&&ԱX,={ExƼc,TXXxY1Xxqa/F .X%N;-555ݽ{_dd 6̘1]+\Flp7\${+d2ڳߧ:zf533zL$ipNhGDDӷlْۿmIDvۻ"9G\ci@{yy}TjLO.sԵd~s GT}=tڍ7^z{W X9k!r+rmѣG$|>̙z̙C5ȑ#m =GfӧϪUr|۶mmݻb߾}\+l{8vAKW] /K$wwwF<==[ZZxrڈ-{WLMM4] /WThllbckdò{'4yFr ;Yݻ"9׺ ֳ`G׭؀d~ɊAgNh6>O;ȗd wT*+֭[ý] g3k 6P˽;$ u]/z5wE|rnmOsKXv`߷oT||1K-_L|JKK̎P4`'4yۛzvE3444Yn危]+>}P8p@U{Όi@w^ycǚ &P}-iNhj{רW+/*/..6jW_Vc̆#no6-P(T=?I/_NP(nNh3j+))!o7`W4Z2e Ug```^^8qc6q9lذ.~C@]mmm䵗{' vM2}rrJNٚҨj׬Y#H壏>"?mٳg\->>>n~~~SSD"[~=|'L+V0r3f' \\\{E=<<,oh XIV. x۷UTT򖖖;wڵkŊٯ3/$H{Gi !5ϛQ 9KťƼ8f0D'ON15.۱+JɯwF߿CSk ʕ+u zjLfFt}'T 3F@']3NhQhѢ./9"-Vq{Θt #33t{ [AS潝15.ǫܹҥKJ2 $$dʔ) 16lx7̨>΄X,~Y"{*ZԞZ^^^nPVVF  d4!!Q)zk\.Ww˥M"w*:Z/-FM І%Іdbi1YYYtZٳgi1#FŜ8q3fZ̡Ch1&MfM]h믿b͛GCYh-￧,_W_iZr!q:q/_tR*+W!Sۯ1+ :qÇGoT*YLF>TT<,p8Rˋ,%K h_iWi_zaرc&M"KƏO:x3f%SNȒ={̛7,5km;v,Z,Y`˖-ŋO>ݯ_۷KR'Nz{FFR$K/rF3l02b9s,>(Yri¶;,H$?ɒ{, ؽ{7YR^^>{l$$$dǎdIIIɂ ȒHWŋ%7SJbb"폛KM6%YYY/Y2x?,9/L >ȒӧON>333{=رck֬cǮ_jqnOj \RLuֵkn߾=;;[ ]O}} /?j( ڧ.\?@b| PPG7ZX+FVQܘztcS?~w% ~9s\cXAAA Cb%''%ctO]\\h1~~~CMWWWZQnnnڤ.chݝCuڄg&<ϧńb BZn*ooogm˖-[8@K .$o_C" Z9~!#Y_Ξ={ܸqVRRRB[ELPwȏ 6L>q a̙V:p޽{ifv@ 0c0&00`LXXh1c Ƥ:t#G3fI&fRzh3)t͞=IqEZ@CCCAAmfA׿{=R Ν#3k4˗]TTr]vwmUZZzP(̙smHY߿8Y(STo& o߾pB*]dI^^ކ &Mԯ_?ڇ&m*1 CLݡb4 m$hz'hƍ۱cǪUjkk l2HDњEk~^4,Qմk,+55,QT[l6{dRMнR(srrWWפ$D&Q޵\TJiɒk׮%---z-w&Z>On A .gb PH@MY__ .\@ ݙ) jժM61̨{lȐ!fWR3k,+698Z~|}omqEeee\\5EԮ-bXWb DV +J-qY,$aTY&$ ੧裏 F\\5 ,Owid2բh_~u.;tPk5jm!ɓ'ل!P(D<ݝ1|qCЦ@w+P ݤrrrLYWWGf8&A!-B61Ԭ#3a!d9s̙3ǒL&~C]kj zT0B)??erG"">a0`rP\NI<d2<x95 (wg1I8: 1!%қ 9Ԋa`h~[¢EfC~0mW K /&;1/7ZlV6I9.En܊:bEc0եU,Sܭv9'w\o kSPO  \]aB3`>}Va˜scE|yy;wȒhTo-ꍘ:)i)s'#G$c^ed¼ʹ{LP(dWk|P"d9???̫,}v **gee?d}+dc=!Ds-a$HnݺETTThB!d 8"B5j@vDH11=!z)cM>}9ڃ8--2B3C4^ .\hV^Jvv6mNTF:u*-?G\ja&^ӓbi-B!d 8"B>z(`3^Aԃ ȒPs6>C!7n*#3ݻ¼E"۪98LFKrhprWY?yb1YӕI_|,9s~8"WY,BȱIp> WBq1ւT < ))GCdj@/;O{%\Y,.%qy^X (Do܅}m/ul۶MMp>dg÷Ӱ`Xha &Yп˥5?% 1!kagƠ@RզT5ɋ%gJ*9q}0qEVR {fTFNSAc)oi<:qHKý{ԡ*Mkm+nhLҢ-/oR\e^>=7J(תL O(***hu0zۚF"@,X<O-5mDEAQ ̝ u+'y.]K B]ܸ6'*PUթjk5[HH?L+\ox Vʷkşg6jś]+U 7.z~%.P}?B>o~\HMԱϴ^uK}xq9^Κu/8b5D&Ӈ[a[++@YGBzիOrŶ+qr,Z*WV4klZ^G|)UV.i`2A>)}D U,nh:]\QT'vqX́A>Yi.!~ızriĘ]PZZaQd1Q}#i3F㝽Y/] oO۝9iiʊrjY'550oP;+Wر |>dP_׮ s't^ F,<$̝ 744ٳgjXx<]ec:G*Jd g+Ļt8ryM )o:pAwU̿K} e :vtu= Hav8R-ɢ:q]a˙}:6KV,WQ*]+u6B/qZ鋌jWkO %8ul-,&c81ʦmm^nn\G 믡M11pr7Y z/!:nǎWdzl6CHLٳfMU=lp{̙0s&nG%?l~;ͯǁzU8]ޞ+O¸l֪! +7Q*? [O2]"L q=lր@QåJ2WWked0 &Q1oijZV4j5_=7J`0@q%umdD 356҃Rbr_0X3F/d2J9 `'Nh>Xi `?pkذNH{O?p$DGwܿ~֮3ٔ۷;̯ǁ{11ᎆ2$XQԸ0SŇTIJR^;->2*c_Z@z/5Wַ)U\gk)SteҳK %Fsn=>24Q@Oޔx?U ߲. [U,~c+zؒstp6nosuDb={ػ fRWZJ֜<kh{ 1f)J}>K Z^'xϛ34GsoiJY0 zӒ 'w ouph<@ZP/7]Vpl7?p#!Ձy…x)|>,\ހ?Ky_H:ZVߵ9TߗYJ0#'>$s۳o޾d\.yնoW+޲elUͪ*OOOuyJKӦLjSct7Hޜz̙U}ڧz}(o~VYs^UUtdd`۔ 4`DR-ii+o?AGnj?54U7KNÛj;ZaR,WKZU_-SJw*ZJA:KϟOϝܴIcߖȀxfy^|&N)Fe!dHr7#2 c+٢zIQbFxQ"P/w: #?9^-!^=2LFz~"_zP3%Ωvc1} zuYl?{Lt[Mpb{O*\;a0.ksgM0wnO=ej'np> OÇCFdd@z:H@N_ר%j,1Žwգ!iq(! *[5bFy$\'3v?AA,qΜ)Rz/5fLb GG5jFQFuF hTj(ϭg`VbD>kwh*_w3?u|H`zx>3[>dn1 { 4ظ^~Sak+9GAF̟sv#w1j5tL7Yp0>\_W[wM>f03NeiFcB&5cn =s' qiqa]*:6Q5? f"L"7}Yr\HI1*]pJxn\Ϟ56Uva1D:I<\]ƅ K2eVu"dB~~ouS7tUGEE&sS%F8Xj8u nJ> wv4/-7\j*ofp:ձ!5?+G&uG ;[.i6j#1-D;Uu8(Qࢺ&[&%g)O רpNپk?jI -׽Jπأ7Z]q٥[v<=a2mϗ17dߗɄL̄e:r1=,#Nn?>}ڦ#.˗w\sk_q̈́=k5kџDw|,$MϩlRۺ1.ՕK}P<7 D&ϯh#zy4vn{c W4yvq~lA&Ą$ Ke*3ѐ˗s7;Lu%?;uw#YYIC:]~d_oY{ngVϫ'd1* kŵm>< k[ nb0 NC])zֽ q6K晊'OxQ'/=y,>3Qtt4#\_h\ؓ wM7Zݵ"aBGMt8H$iQ7Z[TFn/fG|>?MPRyyafyӸT*xY8tXz'T3Ϸh-Owg0`͚4 O=.tigUU0t(> ڪ*XZ}SBo׊f3BPn;}aY.5.~ɿZDBZ~I]L7HQAdO^B\Z_úseF!5IY֫mp ׮]#ן7ZKuoRK9shwXᶸɫvmfφ76|x뭮# -[ mlgw3g̛~;!Ԗ\QC[ou>a={6>ej>IQ[~sϯS'y$/G㹰 h++k߯&?'Gww_c|}~nn.,SR״ݪi*v7VƪM}} =\W6KRKʚrIkaN 9g*Owf2Ss}n+ErY(W_%&ۻY} , [tv{ɓl p(|Mx˖nBgeSOsQ0z4$%Al,p9_|1(":/p.\ PV> F0t(p6p5c`[Zw/`$?D"P(rsaW]Yt$[F,U(O(7y̕Z=RYh<ϋY6(! ޹Z>VLnbE-r%9gGw7\cQ#ҝ n:?.sKScC='4k,Yu#^ [̙jAçzo_[xp$<Pêp(lM L]6nvYE/00MG&10 ~C!?F ԗb.85OβjT y7$#a;UMvc7Zd#w:B̚0>fÂmx5/ǃ_~;!1 oj z n܀w#1 ?99yZ`4x{|ݹ SNz^R.im+y.l݇ϋ ~lƖrIkuMҦh+*fX=7j \ t+'c[ (羳1*dFɌrLpݼ&bbbzb2߼7z"ѱJ gg@2LF idfps3vٱ~BRR *#o˫lPZΝ;~id_5Bf**#gT*iJ6`S!!-|I*#;nU%)34h D!n&o"6B\cƌCn9b~d2 kkLj!9•+W͜9se9"iStwAHy輐p qDȖ\û7^ɆsY,Vw_pE ڇI!B޶m`,Z^ATAAŋɒtlB΂d1رcyMq]FӕIJKKoo {!Bzz:UFڽ{y;mo\[[yI~ŋ%O<8"EPr*#SjZ/R*9Bق,#5ȧYl+ZJYk9lw;Lat?Eh+ {7 6ww9.lCXʠA&*+o_06m?yYLFaD"k?eS'#0q]+nB @MhGѶiUz1?TZ~d>mtr"ܸ~|̞m֠s"Ba!@Byu[S_~}R#~[ ֭sر#)).ArSN]\IHH6lxtF[BC[ehh^^^Jh1&ZLhh(-4O)))$''bnݺEIOObt'deebFM9<-fĉ'Ob{1ZÇi1o>Z… i1v>QaX* |M6avyxxڵkܸqk׮wsP! i%IIIM[Гd+B`Cr.KxxxbhbtBZ-חӧOZ?-FIPP-&""Bѝ9Nׯ-&**HkŤbi1$%%ic faFSNx=8],ӧOll$''ddZt)CbmܸQp81<`Ph0`L@@`1aaac 73`1))))))c d2ڐ!C ?T'NļB02%&&,,,F( f̠N0 Z -B1=d2 ưX,Zn= u1/fqqq1{֭ͭz|رcRSSg͚ :M]ላ,wYkE'N!-B!-!dffb^ed Z8{5!]8s !B!-B!-\e=2~xLRZZz5$$$$!!^A*#Y0#rpxU!qB![!qB![!L0*# L<,߿.B^UF!lG\BpE!lG\Bp2rU2e  ߿C%%%٫=\e02UF!B![!qB![!´i02Pxx3Ȓ{5!]8W!B!-B!-\evM˫JjwyrB2eʔykzn]zuСmmmڇw1c1BYO`cj(ʵkr8ƂYf555|Wّb^*??W^>bc}L&_OFFơCxbŊL6rHC{\#ߓԠin:ZsQ1nnn1TСCt{b5kP5<37 8W9m۶*/Y+#ܾ}"Kbcc f8^xaڵ]>8p@;W*رC uƍԺ{yyVxb6 J_~>8gr{7 2~-Xލ|1~~~zDO>$Yld*?ݸq!<<&!B=S_jIFsrrh:tB{0ao7eʔ~ǬUWWg6BNnbccjڳdʈs?ѭrĥTf3x!Fcu,i^x:6r.OٗZ0rxBN<˥u޿:7 %r>{"tR̫,|rdĈjCѓXe]xkllE"]ڀ9BȦlsʘwf{JbMRQ;gL=f0qBC+tR5| u+zԨQviΜBoU^bNWF&)((8y$YG z̩O2E{ok~nݺeRnAW\6l#})T6#.r<F.ث=7-[FXbjOwz݈T*˵80qD=UӦM#K8֦iiǏ[9B!}l>K=|L_zMe؅ V(~vl! xWqܹ3ePYY9qDGت=܈#}Ϟ=vlB!docLfdWeD,"I BKѾ֪hk-EV-UJvX BUd';1LN~9ss:3seQQQ5 oܸ;wx^xqرY~Є /&jjjRRRnܸAt]&w@/u\zp5[Q%ܹsCU\x 22̙3l6[TOO-t:O?]|9i dc\f̘*V5k'<|r*,,$1%оdS:88ݻc\̙*V?>G zcǎ:oϟGM2׷K.*.k֬!;ie(Ѐ@E_|-&MBӡçLux<| |P1OQe&817ݱXc!LTVq68&*pZr3p8T*U&M6{7##cӦM_|܏ ͓dEw ;@|}} 'HN< ihh#;n۷o„ [[b@ hjj0 &IV>=<O("\\\/^  6V/ PKFFƹs=Wk7V/~G-:kkR|Onn.̜zaɒ%u͛%%%Tխh1"X .@tL&fJ_&&&/J@/,[0WyʕDjjѣG=aaaC%+v-:w]*^+Nʤ-a6q";2h~5*.xȐ,!4{[dSxǞdN{}s{_,0qέ)@}D;vTp !ԉ3zEPƁ ڽgS..I-D w9"mrXxJ>Xxǟ)oS)T BPq'!Vi6BM{m\eо?rrZYʤufn\r{a/2)ǼguX[*NAW.0Wc2$rqi)i 1^ϿCns6r|dK-א=/#g#;SOG@R+lxP5g^\l9 D177 š5ks*gg_-J^u)ʘiDm7U{~׻*{N: 4hZ[ܹs*g?c pPkyD޾_XyѣӧO=2+V hѢ+lj{hOm D^{_̊TlHmfߕtR|խ6u`*j"zs,^nƎ& 1Ȕ9 Q >pQ#h"h\})b02U6X[yqM ųyیi ރM)XΎ/nR ~.1.plmm=FFFd%/KKK¤w@/,^0WGեKo SN:urՍ7;xbS^pppp0L #] T\@:.Q___UU122211!+ND"|-T\VXAv~-^ZZڿ Wk~MuPԻwoXW5kU^lT\{Z 3}tu+.m.u\x㹸{LMMIc٠4 *. ˗/' ӦBCC{|OPPL4iҤIK tP/_V섊 ‚ N{dg2X[4+Cgm@[TÀnDX0M֞.^l]uak@PR\C6sԳS!!@GDRH*5l({P}6/Ia<#v({fNT"IkӶf=lQK%d:yߥl#vd >hz^/ш!;fȐ(dg4D8$CZaCFXY,a{w^=x,wӋ PqD͞6Iŵܚ{>:qr mm*|[Ls/?RSSS^^111177vj@mD1s\l=z@eVl}p`w~N7nsC7r'Uvrr ŋ s{W 4s9Ӥv/N(̋k'_ҩ/>|c`48k#x2Ȟ={`]v}K䒉ރ'_WW̻3怛@OOuWZWoQeaȔU{$7##~LdKOyI o$l1KVzt 5@cEVX?+# ;/" UܴLriEF %ɩgOFFo?s!,+:FX lˊmnϱ6s sٵ8T>ټWeJA ϐcͱf)rKo=`sMB˻^Mv^o u=]gSWLϰw Bo{xeׅǟ^^!/łG.$;&Jve^߷\R]TY;먏_E:iO Ki~#7'^:ڞH70$+ ܏;{eNomF$.vz6_,hPQg%VU]6gx7JM,E zu.T\U=F-evKdRGd?ʗ]&}e2211RwN /rnƎ<:W V j2+rnd9깒 G"[ҀB7ңeg3q˻3_+JEIeOeј<Q9g#vؿHBug!T\֬YӮU{mh IDAT}bn֯_Gu9u:.I70ζ.~~~~~~;yDŊ+WX_2­oXʧ-ls_#⿄z_ٷ&}Fѹ~ם6_w_#jA;[wF-0{-aY9$f*.ZtbNԐ[Sp-{!bsn̋kFt_,A!MM!_$/Kb@,RR܉e/8ǝWvcn]-]u黱sCGrugo4xӭ猻6_LA@ ޳=]O5c@Zٮ>-ʤg;Y\0%7*j44^u>:Sϣs{ ޒ! N>qlW[L<]ߙ'=AMT{:lO:fv(2{[u\nZaэ~-.efuTGcw\r_j)M$f*.Zt2$dqKPȿV<~8++++++:3D]PI^j7zqz*`fv7kHe2Ms,rrr&2 , ~v4]9.dߓS·W ܋iP}ӽ^zQc9~/zz/$µi2+Qj%[堺m#FvgΓӒ4f]~ A_`m/[̰v9J >/8p Ԗ-[*[Ul}kW!F)BooY}v['Ц{;ىh.wm}(nBMƈ 9p{U"G? B2 !' 5ȝå*.Zt&Nl ?T>uk=S}G:d NLɤYL!e`)7oɐH*CGY=ƤRWdöw`2 IU_YKs>r7v5ł 7&o\w:-k[jK/ꊱ#׆LAZώc93`);BBh~Bɫ-Rtʳsk T,w{Ph EȅgO][[LخƎ㹉*7|T\ЁNM1f>>69u!kR$BP-KpkCbll|fҌiR{[S]j)oSWoSש#2鵢uoߒ!~5!V*nH-J*J))L<==̷~t@/lذA*ÞNxϊc\?pfoddkL7⋛J3צ&R^]ڴ]{k}2=zشi!t$'A bfL㍽'L0a„'D|B(BاBA^nJF:E:MO/"Cv=lvZ_0l9],}X߹yO: 3f`+:ipVsB=xcDD:q:C9BǞ]Jifad- >]f!q@vY/}< n͟u4{`hؕCO H ݲG^~4Q1&%fD@uvXkJ" 5ފ.ៃdrD&*poE0vǬ7n!4!Nbf)ʭ)\xG6b#4N78s>>C޾< yTT=(]ҵ0ǣ'V-n(ƌtKyXkMGzXz\[*}veUяr1dQ5qADz4lƉs{]+5QZP7RtޟczצU9ߞ~kzۇ*ł9 [3N_I=z>\lwP(F1NlGjFXz6f}{,--훹5܌vEsvīE)}F8v1w7cS(Q峋ɿe]+ɥfN+:E9t 4u1eX4fYu[3gkv+rk SK"JM6s`@[ j={P}حwmDdu†a}627Û/ydd{pP+͟ߥ-= œ9s*o޼-+Þ5Dr~G'r=Mq ;U}E_ONʧMQevv߼ys޽?TksѺb0zQ#BZP w([an1GH˻<́k}"n3אl.-C].]^ ՟~7nϓ(>>Uzj׮]u6ZWt-RkOg4g.VL~˅wf$.? K/Yc=ڶmG*[qui[NJ;[$%4(foQ&x}sW4? sY-"m#.c<LIA鴂n`8#vMH(q(ed^ka%x8s%]/N_JP[T2eL@ ^v]VeI˲2ʳ^l(ln7IeR#:ǂeg6pjt7KֿhnTCa4aw9x8[x-m*ÀΣs-X\OS@ ^vxmQ킊 :(ii0fp-YfAmGSw9 7G\~<.@zT|md :4at;8Bmz%]KKC|oW}]k] }'C9i 􇟟[kú@_l޼Y"yzj;z=h·f]N~[sm1W^;vx{{;ȸqƍk~9;zb'T\&OLv ln6lk7^5mhuNrvH.1. -y׋vh6.>^sBM$c\@()):9iaСܿ 3fUޱcLW~rA={{G=PYWBg_QF@O矄um%;;{nu\@ oBR. |y *. ;wuA+?xxx;ѣGn8xb'T\ƏOv Р :.xO N!DrI>&Ic JN!u?C:Ej0T\Ў Ǖ@AW mE"7Uѩ{Y-K$o B IdgZP!PaeMU gA;&IN*o}3q]|M hT&"i-{TXΦ1N-юݯǞ]dd4`"!+4{IA~N=Ŀ T\ЎY3 !$tgl΍byIE/j$2)!MXTT3| hӏr/2iAK|Π3k~ ަʾ:zaaaLPɓB!?Peß^^ _D`?BL{a[4y՝;w{&O6nܨ*cMlAӹF^v]Lwu:pJŝ;ymiWӵa[GPq+^.!6?vݻ-B(''g߾}nՌ]{muBMc4l1ÈJ ;~0ǁ ڽz7Zdw )3UχtČiLv:Ħ1wD-Głdk6eh6B|!Y)jad2o-YEcxGPq(,3kى@3} hua20WCU=z4 +5޹sֵkW*. p T\@sl|'Yv͛p9E#m۶{bbbO  ױc*۷*.P:155%+u\@g@;μvE!$L@;ϩݲq"? gi<T\xٹ//?U-uu!{uIC<}SSVV6uOMG"""\]]=, *pq=T||< Ԓqqq G{<7{i@B}Yz+{j Ɓ r/P))Z#.<EK#CN[`P8 ?4v7q/SĔv $4:QÙy@VT&E6ʗ쌀;>x:u'BF5H ZvI-R$ $sX1cөT'xyVVյsJ>RPP)0e2l+j_rӷo_@/5J(|,͑#Gh4~5\xqӦMϜ9SAN8*֛>}: ӧ *,z'N{lllJEp.@tC/_ EfpQg聺w)| |}MA}p@>zqzQC^ݤ e/ʭrnƎ<:W V j2+rnd9깒 G"[ҀB7ңeg3q˻3_+JEIeOeј-f[);Y9wv!\k6)zUqɿ @+N?G,ė[E=m/ [s>#oKw-]O!oW ٪IZE3QǸ=]t!+u-]4g]n\7c'%?q$'ZP:}fmfLc7zFXYi 2rpXWҠA]ﱱQw_{I_fTESfKnlö_aytx[2#b?<utҿ$)t ijmN뤸 )Bb&}F*{}&8-8Mcu j0+$0qVe,3]ίR;obmIb&{"wmߺ(nBMƈ 9p{ UkG? `m$q5ȝå*.ѣ|OhhhmN*RU^;xUbnG_\!@]X3!kÿ?Zf5`Dbn堶k;rI1ºÇ BTT@զWteSN^pBIHHP\WYEc`B5Ft5bmϗul^?O&.W*7MLX[Su鄷4V^ŔP{X̍4-ZDXW9""h@/ܺu Uի7o{4'2x|q]+jP 4N;E"W^/NO*NL#i4*nBr}f@b&N1M^Wܚ?e݌i⧼M]M]ʤ׊RץNDd|+iX!}((Uަ J'myW~AyvnMA֐JbXrݦO :dƌ'*ށ> RwT 5!,!lzⷿgwxyaT&o'_vuf%/j\;* ܏ :nO$+A4_{iX[PCw}y{wzx{yDuGH-}}l|ʶǸɓ=]v&6DR1B(VnMcʭ)\xG6b*ȡIDAT#4Nc3Oyl^ҀVlKgʛG-6Хb=l=lF|qSZiڴ.$#צZs^+1bDii)*. W^uA+ 2֭[++>% {h}ON!$2++O٦D& pUS]X[SeqOƝRrڥe7Gw=@.~a2xV4diBǡs G{v0m^vj@/h0t@!]Z6Ǘ n9;Ww=\ZP7Rlcf#3S1#s Ytkc/G5b{c<=zX?m)Xp8'akɣ+ Ӻg[2ޝuD,{8#6{-(+ϖW܂O!tiW1g_MZ2gDcwsw31*jU>X[]+u:4se]Y!,ʡ[)Ǣ1EϪ/ܚ76Sڅg?3`\C^[SZ!TP}lr_{rQciuރ˅w.n2n#2$6Y|yΫ'#ۃևZirWVn2T\ p6L^Ԉmg=J>bDr[Ṽ+sZ5d7.Ƽ4|KG˅w ɺaYigtf{vv[) o9 IfѨ/ n* {γa[( cӘ0-fUʶ̊ox0wm+m#~L]f*. ]vzfzz:LWj9v7|9reʕ+*CZba]A LXWReeo=nz8 4S)L&IeJ C*2jR=AAA9'""BGxqr=0?@r~@"Bu98uW{x<}cTܹ3B!+UWW{LMMmmmzCN~v6 E^#[xR|{+7_wjA嶾L PqU:mDZRw9qǷnC rm| - DA=|O҆mAn>@L$ګݻwU2e T\=2PÇ/^3f̊+dƍ*Zt$usssaBIIJRD"Hb1T\>_RR響!+)-ZԥKPX__BǏN 3'NyƍN 7p…+WO,o߾|@O:qt1p8L&377ÂJheH.$$ܹs۷' =-dZ|Bxm#J=T*HDR__100rm#=4#Ս144dB%ŋ,X?a„7!MgРAiiii.LPEzzz32@JJ "d2\gb1!Nb!dddD"XXXb Bvvv|BLN1)!///BÇ 1աdBLxx8!ի(BLBB!&66sIBL||>>L&wQ5`X,|OSSa=d2|@aX CݘFPn ͦ444D"|144T73I$uc ԍJRŋ_}ٖ-[F@__I^l¢55{aLL ~P(6m2}Gׯ'Ĭ^zjB̆ 1˗/'lڴ_bvAY`!? ̙3xaƌs'O&Ĝ8q3~xB 1G&$&&b 17n 8J&ܿӫW/Bnݺ,X@,5jرXT\/x<ޱc,--CCC.\Hv:077'􄅅)9mڴ zĈcǎx4Б|b>䓲e˖m޼JĀ?W:t]*.w^R```R,bq1-۷өScZ h1&44Ř={bL~Zk1fÇW3v6=ٻdɒ&_q}v[[[]33(t,}*@W@ PtuT:?|pjjjFFݻw+**,,,lmm{1p@//'}0n F.@12v?<66)?a7cƌKjOuܹC;.[O>||Æ {(z ]N@KGGG'&&wlmm 6t`ꜜg۷oӦMEEEo3}IZv_oI)//?もtui]=OO&|PfuuQv܉_:;;ݻ7((1͝1cƮ]PS24AG3 A_׎'OlDQ\`AWǚUկuLadaaA;]]zY:Քppp uAAACR 욾U']|СCE)&43 #t2呍Ç)&4}j9s搳Mqqq&裏a6m?Biu:$@sU~K9##>yG342^Ët:AvzlMMX^zMiihVtF"mٳ۷F)KWEQ1wvvjdg]^z… zԣ $444/KK˅ 7𑲲/RzYL2uuQ'MDi׮?<66{ϴ _}Uc9?&S3M6=F|v,9g ˳f$C{gff޽{ɩ[n={J?7h-KhtuT:]U*@W@ 磢O󼽽=BH<'Ny!TZZ<Ԥyje? y?99955O:KAk"t:݀v*++v0o/B:ZUV;vXv-˲*HIIC{ի,6`DQ\pkkseddxzz2 gS///g?Eke̞TtʖLMMTjږ9311yR]SSSUU̚eL++mY&00rĉo֭[]]]ftݻwȐ!,t3gtQ>J1/ ٳgcn5jbŊؔ;vHgwuYYYE/_NJJbVvݺu!166611Ȑ>^y7•s>'''2 t>z1#u/@jkkES!####h@+N M(Պ+Hx뭷h2H ta=yЪ1҃.~t:9Ʋ,lk~7|Ci2Z((QIÀ ٴgl\JXU`(@,_4 <h8o'T;9xT#a(d ƒ֫10PHϫ更pbXؤeYh cuTt6eD1~u].0$'Y`*pi$MMM)FFFFaWx6B~-io68@9GYqu (P}x'16 Z>w}G;C;Gl kjjҢ"Ya__Ν;4Ƥay ƺuz eB:txkjjh21%%%YYYQioߞ5kΝ;;/_FX555/CBB֮]O7Uuu56²,V^|vvv ä{zzMeJKKɤ<Æ]xxx֮]K9BHXy իIXx18@y gӚ5kd>>M6sz 0_2XQQaÆW_}U Q6mHÀg(5MKoCŏ]F:#jXXp@yK.BSKKK#&a]u]ֱcGɋ/ \aa!y-l{ߟvI.'I׿E;G` tkג O4mڴÇzݺu|o-imUSy} 6 999Cᗻw>|Iy 99ߤ.^H^tܙbyIË---իW70[~AAAnn.`î(J:^XXhff_iAAIj~l9Օ  ]}jׯ3gck"kiggG^YY)}A||S?"opgϞ%KҽI&?MMM'OwᗫW=zZw޺u aWW366&_xĉ&|ɩS75/ո k—t= _~%&&FΔip@iݪ։ZlY?'2& gIIIma}:c-Dn޼k'''ذ+׭[7\,[[[K7 06l7ocbb+y@=Fё4 777a,8 1yɓ'NPW$jv2冓S/. khƍ0VW:(p899ADZ5.))v`p&M4i$)8r(0=Fµ lٳ'`M6X]atL;0@52 cp'O0dET!'= "\^pcu@l޼ PW0<==:h9ɋ޽{S GNNNFF]]]aî\>}pcu@lٲ ÅIwqj۶-i^^^tYX݀Xȑ#bȁݺu+==ڵ pcuyӍ GvHp"/O1 0W^uv:vH7h>.""WT'NqFvvvvvʷnnnto0VW p%555??????%%LNi:uȆCDQK<f1k,qpm1 3`=h 0 ^r%**jӦMzA]ee嫯gR7o޼r ۷ovyy-_ swwy^a/??ѣPTy0VWnu^}ٲew&f8p}"ƲN?HۓC1 XzzI!;ƲV/=<<6l3_vڵkN͜9:))Ν;T~[9sΝ; a"7n$%%QSScooO;h.cz/\PRRr]'Nlذ׽{MڵG cu+[]nذa2:FFFھ};桮N7 E_;i;wȂ^Z_OTTTTT@Nu]a"W؁'++… p#F_O||<桮N7 E_itYpv>؁gѣG顮>rHQ(z233ϟ?kOOOذ+7j(\X:Bhǎ0VWν;xzzѥKa,xzFEv2uuaT{$vh4 p&5a;wX]atP;xyy!0@ܶmhgv9rjϼ*C]}رP;\vٳ pѸRXvٵk7n:[u7itY<ԝ>}:88!!!<Ȏ#BevIOO?}4;vصkWy@q&L_ݻa0uuQ(mtؑ4 a,`B/_&wܙ̵bp7o0dE6_իWSSSqݩS'ذ+sJcu؁gϞ=0VW:(z:uD9 Z5#Ѝ7:tk yG@0>6lP`^OZZZrr2}}}aîܔ)SpŲ,tucuB{ա+@]Wk1!0uF7 Bݻvrrͥqׯ0dE6_ϕ+W=?!!!tiWy{ivvvtP|r80 /+8v}'KFhh(0@0VG2+++\<_\\L7~gbԩPGt]`BUUU633:+K/J|=~~~atޝn t4hIDAT#(sW]]M7bccq0 Y 0 sQŋO:kذ+8u:3JM6 Wj>222vu1yܹ3i={WVVZ՚իWa"W!8/u…?aWgmm]RR벲2 y肱p3fDeRa dc q]TTdccC7VZ+aȊ\2xϟ8q]t X>gg;w://n`<ܬYpcuRV= 4^z dcܲqB7~\1 CV*GΝ;wq\wҥwt^}U\, ]J࡮N7 ]p^K.aӇn שSW:==ˋn}wb^"؁h4Gu`` l={6XUsWx?C:Vu]Ϋ @Fxx80@0V/00Pܹst /nŊbUvΞ={\w6 ͝;W,˪ԡCcu PW.8/HF߾}}aaad$xtP믿˲dEB/u̙C:((_~tbbbp]԰a0bbb+@]WWyu8/յkW0O7 `/կ_?ؒ+L]WgUvN>:886 書bYV]vॎ9BFLL tuxӍBK{}嗴3؁۷/\90u]a"W!؁JMM=x uA5[oeY5wu؁:z(i111{qI [`0^e>s եá+L]WgUvRRRpI7h>w˲j/u10bbb+C |tُ?eN銋Z((Ut~)((vR};vX||U߯7a„011),,[$Zga*l롡uhhrbYVT~'?hLXȀ[`-.1HO84444 xރ2zڳgψ#XoӦ#O>~04WZZjggW]] j`lqݽ{α(OAN;50VWQ^yرGW@!Խ{w0zM7 C=έE0K._1 YV+//seZKJJڷo{1dyT;VEN3f̠&+pB^105B=z W^tYX!z!}ڵk ;fŸbYզtssqHt:ujϞ= :t(<@Nj8f2e 8X]y8UWyu,22rV@!ԳgO0zI5 ;|.]p}ܹyhѢS7nW!Q۶m{m{{<矻wuXXrRX])S ƌC;}0VWnɒ%/B(22_:B(,,4 +dddxyy:u{ 3>_1 YV```JJ qPv]vW^O"u;x:dQ ƍ'MD;}˗/7ocbb+y@qYhFAgϞG^~2--M,;|nݺ=FիW/0`8 JT,˪̶;vMOn>9gN֬Y3c ^^^׮]G }]2??'Nرc3rHa(WOMM566ՕA{Mh9D8Dܲe_Xjڴi9[̙iiiI%?~7ٯ3gNDݻ7iT2^׈۶mٶmD$%%In>ډr=݅a"קԓ'OL6 .$w%$''Jrq$>}XX&cJÑB"ƍS$VZseddVVVVVVQ`8)ӧi0VWMYjjjPPaFAv '^˲dMZb&/Zhtn H7nuddĉ-<{$SSSe&"##e4UUU$0p_Tcuq=8==㱱O'&&N2eΝhp @+uMo՟N+ύ3:PW_ a"Wa,Xpŵkw֮]G!0YMO9sf7n$=z֭z y饗pŲ,Y.6~xF_qc4tСCy*1@PHvvv6mGLLLdI 6_]y8rmΝ;o̫bcc.\H;5& stYw'O @NNN7nxT{q0 YSNH-$h۶дuzСSN4G,nmmMꢢ"I|2i|*..& HkNjȐ!a d 3K.nݺ84NT!+ru'uϞ=)&n߾}d47l0r@^eY5w,-*Fʕ+a8::BWWL6KKJJˠh:t(ipK2p`,///55u۶m6m[YYZJ:՜ @Wβ,gSg[ i<޽{ɴ<Ç嗩2Sdɟ,]]꺺fff666NNNAAAz8qPbذaa2p|p [N:I/ P!+rr{!1bڴiTp7oƕʏ9W|۲e >4; d?v`p:v=\a:˲d ݻLFzWoS=|dp?==4 _꺺Ϋ1 tYp*<7p Ð-n׮]: 233IÀ[ܔXQQQt,@܈#hg.SnbYlǓZ;W_4w^\XYYYa#Y@3L{ :v`p:t@nwPYݳg"t:]qqQ9cYaZ1o:::^ȅۿ?hǎ{͛7o"ܹC%zjMM Btԩb\~4Ν;ӊdTiW/((!0a*1 DBB.ZmVVcǎ5+C}7J|={ҍD׻tRRR_.Z裏> (3hРÐ->\~8O>P?Q>7---5 {Nm۶b \GGGϞ=J "X)9p*{m(~$ILL $@.R4p%K,\P9㸂: rrrH޽;0@u]Ϋbĉ&7؂V ClS8M ͛zoQn*¢EDQaŢ(n޼Y%KqFA>ٳg#STTϮYZZjZ({mt:̙3t59BHi4KKKR<h4mڴ!B555ٙ@uuF),,${BUUU!VhZ-IG*++5 ^_ TQQhIG+‚VVVAi4[[[R;88 JKK5]\\B%%%? Jr\{xx 5;uwF%uΝBEEEK. Dj4|YEh4<},o߾fffaÆ\rĈn@8ӧϟݓ蘗G;+v 6,>>~ԨQCܿM6?\_|.SaAY ޽0(.\`Ys:ŋOZ^tIZs[SSseimll#\"MLL:uTUU&MMM;vj^ۻ2==]Z{yyUTT\vMZ[XXxzzJ imiiQVV)ݥuiiiVVyC%%%ׯ_۷7nܐ666nnnݻynӦMvuQQQvvm۶maa[kAAANNwJU.B#xIENDB`./pyke-1.1.1/doc/source/images/PyCon2008/client3h.png0000644000175000017500000015254211346504626020724 0ustar lambylambyPNG  IHDR^fjsBITO pHYs IDATxwx\Ź잭7[n `cZBHM @~ynrS.4@ccp{U/mW3:^K=[);3y{f1F|( Fk[nFXgX>ӣG?b(JJPRR2}E Q04lp h4kq%^|ɓ' C@X,-[ВZ'nwyy9mJRRֻk4*xMK Q@~g!۞yiDBJf… nlP=}SɿkٶmhJq/2mDBBBMM͈>vz)Pin FMlb0Yw]ZOQ43ՍAK :uhq:?>ccCi 'M+ZxYY?: 7mnnǏ(/]v4-MvFq<ȤIRi|| {]v)sVm۶9sű,RƏO;v춟;@~O}^QVuv.HFS7sݙM h0~BŇiÜN/~ T:Xׯpj維7 i@~((h,u9A:::h!^Ϝ9C/((Mi0G^hѢgDW_}u lVm3gδZCs[|@~((+Wh!^$P(?u]^o4h8N4&nݺgϞ̛7_[o5̆=$:u믿^WWgX߿tR>P%>V Nyu9A.$Tǿ'x.{5{エ~6H%?='--ٳ_hS)s|'9 By-oK Z@~)?>nn:ߖ@uA:3f\MLB[0O?6B%;w'DGGWUU Q#݄Gyd8"vj;wbD` .8Ug4_+/,,lnne4.!!6~-MP̙3 /5zBmfy2_Г7oݷ--h(qqqdL.xTG&}{[j3 5MV 3ONNl{`&J~ UxԘxb?~B߶@`P?N7 mڅTXuoۙ_A7]#G^ ;Fs#j`L0abbbh`8P/G+J.(:4r;i&kqp^+++i?PTCfNCA)۷?Rtǎ! -..7qر۷7O?Jbֹs|2g8xzT*d2T*J%D"aYeYX,D"0/a#:P~U,ܹse T]>,D"իW.ʛKrE/Jvfvp8t:.v :L&7. `k֬nH$z7֬YVP]H0[vo(oƲ2:u뺅clW._o üꫛ6m l|.Tؼy3OoFsJn-tww{MЀKvww=y6I @unhh/FAEE.KN㚚9 e7׿OKB$ݣ@t uvѣl s@~@wwҥKkkk鞟89!3fx66b1=azѼ󽖟`1c|Q@u>9߷l߾?o<K/$%%Սn'Jnĉ {Qۼԋ3dFҀFwZUTB7nFo/zY|9-wx u))).]g[[[WX17z`{=ٶ!JO_v i@~? 9uK^lne@u?۸q`K^ SҦDDD?zs /%h/T1޸q#='--?_ӦMM#:Æ _|q8e1ohӧ3g? UJII<6헿%pz,X9T*<o\tnL>fhTb2M?s…իW{zzN^ohh8pu] b|kbbb544x}>@~Cs@JqqwBP\udZ_zj{{߽{O>ƯA1Zh>Lp:۶m㯞7L&N' &cl2Qd|ٜEcF#H˗/͡V0?}v}'""" [Y9{kaʕ6femQ@~ޝ?" +guPwHugϞַEߺ< ,\رcimabO=zNժP(T*Ubbbii3,Y17`9|0G=x#GZmggdJ "...999;;`ҤISNMMMI>[VX{իŽqqqfy`0řLk׮ FZ͏FGl2}:ĉ3gЬY0_~%BhΜ9cǎ!N2!4w\ϟ1>tBh…> !h"BK,߿!l2'|Z|dbYvΝ, n[rwA1:1޲e qo޼#G`~=1޴iq_|xƍ}{w㸯 caN81^~=q'O[ӧOc׬Yqܙ3g0ƫW8ܹsロc9x"xʕǑ0X+W`-[qիW1K.8ŋsw E8/X㸪*9ϝ;Zwq\}}=xΜ9Ǒ7 ̞=F̙39j3fpԄ1>}:q---Srڊ1.++8c\RRq\GGxʔ)ǑWL48nĉ9tbz=x qQQqdBf38??8ł18fG?_"ի[lq\QQ`0pG{{{9+..t:~OOq8qܔ)S0Ǖb8#VȢǑ@SSq"h8#FE;c\WWqpGPX]]q܂ 0ƕǑ BEEq/_~8rAvqp/_1|2q+W_x㸻 c|1Ν8nժUgrzj3g8[v-ԩSǭ[c|I6l؀1>~8qs8nƍ/c|1~#G8{0Ɵ9q[l:t|cgqCa8q?1O9{G1Ɵ| q=nOnjd2YII Y!'75d?q!\^RRBnp,(rC$.,,qQQBHTvDDDII IIlTTm`wFciiX,~ꩧ.ߋV}Q7,++{w;>A鲲z !D9 B/۷!aÆ &PYr9rc׿;$2&''WCnl\vdv_yrôk׮]vhZEuvvvVkFiZ*bÁ1nmm:niijNs1t:v㦦ae׮]ロegyGww>JMM޻wѣGkkk#vZ-].Vmnn:&ή:v8ZI&Z똸6m1qVױb!k{wuut'OzB{n{{{ӟO" "/^xܹ3fL8Q{M6yD7r9q\dC >'x.n|GK,! Yz50eee ,/}™O~Bx Qˁ.]TVV6c Euu/˳g޸qc…j.!!t„ " vȑ#W_eO 9dh]]]]]X,:w\||t:prssU*X,H$_~ "vĉ$bBU*D"ΝcNJ $''SaZqqq|-c: = zzzH2K!V,f SյB P@ZkT( Y !0 M0GדB>%l D"ݻ///㏅&+"^e˖ Q rpGCiZ{dAB *={{1ӧO'5* С-$ k(6 CӁ NG{XY6>cLְF1 CFɓ'1(aN{%Axk|衇7xFNT}5  E}} B={ w}7;c ^#@ /NGk(zTu5Fk^'EĐy-"!KbL?~9sfZ:ƍ$VT%Axk|o._\Z#55kZW!Be?0WZΚ5lPbcc0mёUk$Fb۩0z{{k`ODGGu^ Ƹ n5~da(aNOOIVK׸e5x!99s1== Bp tٽ{C=DUVg&"(` JE1n8j1 th F-HU^#@TF `F#FEEI$iV}[8ƘaJnFKR/̙VEOOϵkHRƏ׸y{Zb!GRRPטJbB?޺u+W^qd5JE!\h4ЖZ:X8* ^#0L&FDDkB-c+Qd"D"a=Jyj\tww_zj8%Z IDAT'5سgyBBBNטLbR壏>zIzjvܹdFV0KM&zAt:0fsyv,-~6*8IkNRf5,ABoo/.|zd&::0$H$#G04Qf݋5ۺq6\:2b#B;! 37;|\(py|ww˗I7a!jHBD;|k<:I4՘\MSrSy8q^W\)D-@O}5&%%XPp t裏lBիW#ZCՍU^jk H{V8zi oZ˜8q@f4 8N* xqoEなȋ{kr`YQiX,zmR(5@(ߋ0LddBiXH̲,saz Ww !5;.)Rf x(!B_s.(\r^~CxyRz,Y5/'e,?6O7mڴ{n5ZMTu:ϽƄ&Bhooa@k׮͛7x͚5 Ȇ5o~`w8zeq^ZQaL4IZ, } >h4 5RչCa°Z?RE=<B1dqE cmGJ*ro tKY""""B| q:tyX:t[p?Y]03tL.^HxG\.D@#tw!eO-" Ij5΁5cJY&ݤ75z'F.MHRI'Ox}ђ{=Z#^{qqq$V*>,]vuxڵ}]Fm9c"b",0dw8ݘAӋM '5XH)%F$QRT,B,v6&.. cbZL=@ (P(. fk8?=Q9+lOԿ|`GQߟ-jmfQ&, . 2 Pu<Z\.Ye}Q* T-(Wŷttps:;;ϟ?O!jH$B'„Z?b.]D5xs-yBQQQt@d2kzR||w׭[5°;@(eD94f Pl6^#=0jB Mp8A@2}t?cG53kW@Y C$u Ds:::Ν;G℄R!jɂgD"0˗5ӧOkܰaG}D;v"""耪bMbR|X8ܹsƍ$^~}_^#@IHH(eD9Nz8AFZƘtRɯNg{N;:&Pu\|nbz-acBuH۷l0 C 8s ʄc" i0 rJP5_5}BJ&vV^cTT  ٹsK׳+V *@IHH˜:UmEG05!N CI@\.0n7H$ {,!܅.E0aؽ{ctps^@# T"tw!anYCZJ53gk\nݮ]Hr9vϽƈ+Jeǎs7l5p@ILL˜6m@n:^#@ C=@ `! F - 0 |aw&04Qm?)emTp~)'%% 8U / <5vSeim.p- :Μ9C!׸v?ĻvZj!L&$v:> Jda@cǎדxÆ }]cIҴ{9XVTy K#  &nK&!SN0v)t@("r9<gݺux 0L8<=J2Nj:kB,\zeڵ|au DaqΤ89oOJ;y)m;tǏ'qrrYr}e!qNf+ ?Q$H$b׭[G7ƪhs.Mg%x0==v[N<'8;w C8q5Q*5~kW1|~"JYnUocJYنr:gg&DRRFYY@,KsaP,_芀aep{MOO 4-=a;¼k,++0o.t@("h&'k=vZ0Fah00Y $&]MKORKCBfw'_~%SRRfϞ-D-N5BW(Trkܰa^#Tdtut (ϝ;G!׸aDFF?n{v}!48|s k<Ա]uh0(Hdw*cR~wd2A@UGDT~OENg9x"gœ8yrJ_Op[qkk&O7,7krA9R? 1 .]Q֬Y-]#C33Z;9϶5Q,iii{sAR)$}b{-n?~p*%8#Tysx@k̮]npk<H/p:JhLFDp11SIdrr2pR)HjEPH UE$QaHR llŒ%igMo4~]B`]%Bu@ii1^E. F+Y/a(Z F 3tLMOSHVp-sJRF----ǎ#qjjw!D-6d2Hbk*W@ah9.f eٍ7 H4F 81N썑K cotnX=> nΟ?OQ^^.P׸qF5FDDF5+X21=q3K]٦eǔ*Rj4'|b1ՃL&cABp]gӒ}|bJ)a'B`s\o'| FNp\"ПիW52 CPNIjD< sHTeC {|BwX\.@zj0nZHI>)gAnTr4779rĩB_&(@BEEy338NBbe￟lD5… Tu6mO\\^ctt4k(^#=T%%%բT*E RF_^\I^#@aY/ l dʔ)~o-t@()\r\5ZeժU|au Ds>Lⴴ QlADD$a(a᠋J$vdC$A8|~pO,4\p \O||^clWP>[FzRaL2EZ"""hN H9ˋ T> ` U²,_oAȔ)S I"]QVZF_0 M#äСC$NOO_`L&#@_৯y@p8D"a|A!k/^(//k|k(hTo xz[l!54* 5FFFҋ@5DBЇ|vFJ%Z:՟ 8 :_\h:'=a o :9MMM}.\(D-Fn8** GCoꑽ`{zna(an7$J֭[ɆH$ \x \q5IJJk#1xeݷx ^#@IKKn^cTTHIRjBZM_"Tu5TJ^c^#@$b& @ja:\.PTBG.X bBJ>tAQB7g+a((d݄7U? Q"I"_Ί7Nظo>gff._\ZV+j5$Ʃ-cBb~ 9V3^#夦+u[;%yi)bZ)ZZ5>d#ks}L3'=&v f,HȠn^c\\\ZZ!e(}0Tu5\NV5it]M#(?9'+#\L䜬$N0/͈ahpZ EEΟ8$~'͛4Y6u6VGū>`hD,OK+VQKWWb!q||<$޲pqSMAab"B 1Hx~mۛ6mz{++-P045~Y1.5FxzO<ـ5A*kzVhj Q٦]}ATk8tAP P(0k 8Cd|*:t{+4X"f&$?=F./0  zUhzFc\neCIMṳ̈̀EyiQ~VCCÞ={HrJ!j{8FCcz#+0Z B ^ 3k`.tд-bY s" m^#<|;dƑ 49%nb|sJMJ]C5_5NY*g}2330&L P- $ddd:TGPPaǃHF>nOk;Wj{-n rSJZ _M"b\ԴUMz2;{n4mY:'3YnFDCCݻIEW}KGG|sQD=33ӷcI 0l6wttXThF>5M~`KHdEAA5~%5 X4/'eVfұC5fG -C5Kӧq `3330HT*9233HUETRa$&&؇F>VHmVgF/+H/M?D DrV 5[ml}B䔸 jVVw-D-mmmkLJJ [l1'1?N∈"kc94}hB8I$#x@?^}^D$kg-I=PtBr e7_;]+J `# YYYTՒCbHJvv^#U PJ%FRRx}5WjOk;~luŒ8x_#@``?a(BR8/J.~Ym1+ ,UT@W__O:lmm{B47c\WWGba|e!djkk#1xϬOѲBs>.1veaFz ^#<d@xIj[ޮѮ^QȥDύʢ?~@RF+Tu0DDDPa$''GF>: ͍~ASR'F3/"ݠ(2b#1۰oU =N)uuuu|qBBRRR q555=//ϷckjjH 0h4_ IDAT822>!kSթ[1x3t.qi~zB*P5Fxz?яxF~|ch}.7vH\-}M!ܼ|CPHUJDDFJJ x}[H]nBb0{Es2EHـ ` $b& @P 4)Y=1I}sh.f'/IUH|Aܹ999k֬I477FFFF Q rTUU[|haH,|e!hlnn&qTTx}mǍnB.I"cG̀5?&5"P@VgCsoH]ˢܴ9YRoKΦn^cZZZaa!!e(zTu5H*T>~ZkyΏILdEw ?D D X_ַ~VdhlW4-O(@mm;HvZ_kט#@qAAϽJ3 SPP`080!59]GZ׶X#QjlY75-^4K x@?^O>I6k idxI~Yɇkֵڜ}w9f;?nZV^?眜*q "CP]CP(QQQTiii5Y#Yuӗ vPJry!71Y A4QB)j|ܔUM۝`癊D`%~$]njeӣrJ ظqe/**x 3H 1 Z5˜,O4~Y\A|LO5>Sd1IBz卵Ey+g:ݼkyq+ 3rԷdl999TyI2q :5(*tY#vJ텖E + XxoI0 M1Ib%}+FCWZ^ȥEGl߾ݰah47I#@ve?~Ͻk׮X$ 7(51F>ZioEv~I)qmu޵35k|xaEzL71Tu~Ŗ.Eﱍ$#|&L 1XPFMqx}Uȧw_Ecmca',+HW)di@@`ߐaAaE^\fOѡWi~7'54m3SȥkȈm@rkz*)..a@Hbo!lF Fj[ MsD,87-R&Uu5^#ydhK.X$M4ɇ^#qll,x}@HMv硚/[.ǡHŊBnRJW5?^s^#@ˣ(,,){9U7}tumFg+Ӣ#VrTj+''Đ2)S5RՁPbbb0kѠTiOh]nOEeF, b0$04Qœwywm?#n0/Jimwը3uB.7obmm-srr q.\SLxED"P"t:F00YWj5w(YYD5^#<_mSa %>^jU+ R?6[5RՁPbcc0rrrkF!hқVjxg^^ȥD)0`mF7 UUU.`4w i1ߘVԠ3Xѩ1BZ/vg*D"%y8s[R{ϟ'H$%aNOOOmm-U*x}@H85ݽnhj{=]Tf2T45k|k(T^xN :#/[ׇH\+F )++kDSN%1x%66 #77> k'zVjH4;3iq^ZL=0 M0"qAA<*'Kѭwݟ9u-'J^!e}^59s޲O:^ٳgI,|X8TWWXVdFȼF]'?7#OKIƊeIQH #k_ 'p*|jϟ>}:BYtb<+wl'؜O[NDA˴io! nR0kƀr㓚UZq(R*Y:'3Y*my& @SYYopBRUUss)|J%13s _6}Vd9Fk ״,OȊ>}޲O6^3gHZsk)I UE}롚&sl-V!]>K 0?^?AHAAF^^p̘1*T,Z:;3h]fMgweei" ӧOkyERQadA\l]JQBB.#6Wm-Y|58qb>9]]]$)5Fsy_j[G T^QȥF+GS>7klP 08nܸN{2z|tZFo{CsCGcu_mKT"@hf͚%mH$3ga UV0 k1C]aoEcuW~Ô/+HW Oa(a΍7^u7n֭Bru57n#%[YU|S4.ai~JIC0W_[ٳgk<~8OaNWW׍7H ^-@H Eq0Uh4z~V$$?-Z&^kk6 ,/kFbÑKm۶ѵ|/@Ra?qqq2.1(1Jk ML;/[Ojd&/KJj0R̙#;0QaG^zd2YOOBхO*5F!+3+yanBmSO=E70鉏w[r2w#GI7xܸq=\vfǏ.qlSJmqHΊ;0w{{BK5!C"E4; ((JRH^/wrw5{w;yOtd9<./^W+\ktg"<Ν;611A 7|wQf<@Hgw3 k[{}-f;[v dn@Q>>\v]cTTCJcTT2Q<<< 1 崌ؙژ^/?QTS eG.b=ޜ`T6C aUk$6kDH ///kCF@{gץҘ^_hGDؚ <}qD~B\E(ȍ)sΗ_~IlOOy%%%vQr؋6Y3-#WS_GV̅ 122RZ - aR"s*++SRRmjjZ#E-7Shx5EKlvɹ6YSmͱnV&XFD?Cr[[nHQ$K^|||mbb’BKČst*[3PM|̢qnFkaHVP:t(QkDH ooo)Fq*r^vk}n^憽>令?MáWQk_~E(ʍ)s޽{sϱ%99Y^kTƱ'3 wv~7anWK05x6MG[Ν;QQQ ϟ?Ol٪ϩ@/7Vhx5Eusۉ]8u6|.lc0wgwֈ(Zm۸ HE_nHQ'$#K^|}}-#`$xeJU]w}wS؂iax~c3tPV56ب5")$jw%7ЈEIC񴂤j!MikZ%b慸)9BA#K/tע[r ,4RumyqlxIJJ 7(k:Vpn>NF wV49s 6LZٳg- <8SQQDl333{,4R5>2y5GL]kX 4Eb[k裏5FKzzzBbϯ æ,ya{C^' n+5m#.xΪ8bbֈ5r# ȣٵ!&^~23 W\ԁ /ihɐ!CAWWWxx8۷Cloo %11F5m/>J](vy+yj؎JĈ cbb- aR"soݺElsssȍ<)ήNhj?#0Os^{=[kjF EyyyAb1 Lj2 Ãrksjj]} m\Jl:̈#XGIlMMM j$jpKrkNQS+jS+ؘCQMQf3UCO:#GTxibVExNyyybb"Qk|]hɯm"(p1ˬNVplٲM'\5vؓ'O'mmm'ᒦ?ՄBJJָh"6Ǘ;00Pɍc{gׇ+lll,'ސiAAA00䖱]HQ?.O M7*#Θ1c@kTsI ;ب5"9$F@@Jh/;}}D{{*Wtt4\н! $v;H(HQE 3ʏ-D5a.]J,QP2pEQ;vm](j„ O=O?D.8))))v"ŋWVVF`e6u0u(+s Cuر O8AlPSZZ s!-,,x NJcwwwGGM_-cǎ%Q(rAbDGGT}]F+=qa]1Ѳ7б qBiCsUs۝,X(p0u75PrH'NdOkloo?~88,yy :bT_֥.klhlhuRd9NpKWwGuuwm1!t" F@STuskSgi;UqܸqF!1mbb;W0tK>U|=*q˺}0:T[̣=2Q@xNrrΝ;d6ܼyS^kTrxx0JCǎWq)\k<~8B1cxsD})--Y/ L XB~~>$Ftt4Kq*jL4UֈFx"aggǒTeD8gjPQkD H`=N 믳QG>b5e<Ģi>(*DHNN1zJ#M({ ϟ?駟K,a֭[}_|E6\rOM:[eYrKhW,u!LpJf6CxNQQիWmcc|B 4OUUՊ+~ÇrBbDGGT7oތZ#]v(%7w FҚ:2}tVFTJlڵkR4W`[ y:::볲._yLLO? e???H  8zt+++pG5!9@\Ƃ0ƨrL2uBf[FĈX9Iuu1MLL***=?{k֬O>0իW <8pK唬]1ߏk,tSTLfџs!>F㭓7vPm1 `̙C,}@h^z;w8@>9pm۶; ॗ^b˗8>FX5})-P~iT)~Wx:uµFX- |IQ_\Rkcc@=,%K?ō7 s_H`O?ͪ8m4bֈ׮]ĐJ5zyy]VVa$?${ZA`a?[FBG 솶#QR)Z#<QkDkkkH HE`B`kkk%$ƫʶ uA~I(g"u.=ihx۷vpp. 11q֭xrҥ"b<ύciC3FZ|oe~X8m4k[~*s /_Llo!ϥ>{F Ab԰TlقZ#R^>3gqԩ ONlD!\~C*Fj۶m)Hbxɒ%F=i$5K^Tn:][PN.z!qδiCU(gf͚m@ \_W۷owss06UZ rJ]&]ݭΪܚrSRv406Uc"d&=ihiӦ &Z;00W^aŋ =dȐ~8V〥$̐xԂ~ ӧ+\kSXXxEb2&ei|h={ ;I(..Ĩc4~Zc,MQ!6 p|(j֬Y5N6MZO?MlD!\~C*2] 8x/~z)#U!ChiiE馨Š⪡Nmӧqƌu`cc=T+**'Nx!#8CM.+oJ./;~mn8/]PEy3gi^u::::::'O^vԩS(:r[oa䒄>AAA,͗phj5#4hZtFZIC3EQ)e5 ƻq H^kyi(jDt-sqƌ aD!BbHҞsގBbB0tP]ݻk$x^)j[$wD9s&Z#<QkD[[[H0O!55lss`ii,G?QiiixvF jz}[ܖYUWX\\. h NQ(gHm*=i~oڴAAAVbٳgA6ljp`}L\O:;۪K tIzYf)\kap"rsޞկϥ1//OI&q 甔@b477ToߎZ#MFd<+)FiÞ5sq̙ vD!ܸqC*F>O644ˀ'|ې%00Ғ%/Æ O|ؽeߍ6]uZO¸oL$C- -TfQZEEQ5-m2&z+;z5k{Ξ=ظ*Ab1ӦM6 hjj?|O?J>ٻw ,--+V`ۅQTߔTZ_\%#|9X7<'XX@\zBqEQJGi/ٻH$bu$'FOii_vill믿Z;88xlx9s挼/G?(m137V_'4Ew#SI[gϞpVv gyF7Gԗgޞ_$Xcy_GD(L׹on?o$y3(\k3g% Fp H T|[רoff0`iӦp8|pb.2H#Yk;XS뛉OV͞=uu`ggCmJB |r]pB_g4DLV1 u=E"&5%$$D>1zJ#M( <'..nƍ^f ^bbb=b5 FaitdW2qΜ9 ae70 sΜ9Cl_%<yJKK!1ؚ'_Ȩ;x+؃, I8|gϞpqܹĖH$X͛7!1RiOiֈ< H #FG;2ƦU^fHa̙3U5"=$FHH3enBT %$ /JZEi]E9=oR_wsHIDM OH44 Ϲy{G쐐kײyyy9r$6HRi /, _8w\k'607srsscbb:t\ֈeee(;vkXE'dvuwSe|H)NXp!dݜ9s5>sĖH$XB\\$T*)?kґ`H 9fXye$g3y>SEΝ#6j`ooL<ۀ\ l2]'eKl{CZ)ٻH$bu$'FOiGQ!͛7ׯ_OuֱԩS55 LJp2h5NFz <8*W_8oZ#<QkDH fĉ fffJH%KQGvE"&5%((H>1zJ#M( <ƍCCC|M68qƱcb ]… 5ݻ `￉F(//`> F~,YZ5>ĖH$XB||<$T*eDԎH 333;M:88Q;,Xָh"bֈAAĀ  QA̔ ܹD"VM"jJ``|bF@pBTط~aaao^?.5bK-ZpVv3  <';;ѣFPBb޽[^k҈-[Z 5.^K#BHHHĐJ_E.p]#Bb57ʊX`ѢEjD!1s*!1@FyvE"&5%00P>1zJ#M((a(LMMP($/ˊJztbccA_ Q;v,''ǏWƱMwq ae70 s?Nlgggرc+hĈgϞmkk144$&988<䓜PQQ^yϹ'z7jZ*Z+Z+Z*d{׶46w `R&ښښ&ښ&MPc/_ZE5‘/K#BHHHĐJ=C3gL2HTbpChh(${ZmllȒSpN^WgSI;]l%giiii˨((OsCSexbVFxD"Q_ 1iU8~#EJJ!qťK"##a4СC'O6~ɥ_V? UDIQ Rnjsעih̍7"""=`֭C a]]]111k׮GGdDx_~ѣwӄ 86~t1!?`m$qpgJK`ҥ ?3bD" @ tT>W޼y3W9ںH@p̙HN9z(^'Ox 6<YW}E!jhPZ<0OO3be2bV---bK$&Q_~S[*2'N6 O<D"hkkdÆ \E 1LMMY2a[[[b+Ykd~g FȟHST_cǾFJE-YUqٲeu *Z#۸q#>>>\$mii9J`_lZrY.K?a,Ѩjnsm1**@W 5j͛P뎎xWD\SRWW>x^CCCi_ubGDDlx믿@kVrHQTC[s MWӊk=ܚ%>e AV&ʏgΝ0mٲe ae70 s`wLUgy'Os{(ofZES:4M4v;PP u ak,ֈ(Z#5{"@XX${Zctt4LBvrrbñғ"0&9f4(+{fAw6lbٲej˗/'6j BZ#`gk/544Os_ΟqͰMgĶ:NFQNVFQ@xεk׳?"ĉj v:v.EuwS]]Vz ϝ[aFE{ud UIe/211u6Sw#-CŌBٱch/bdpT20 s233aqWWWԩSkD*H MMMݻFGGGnK#@ӔX(QĮXb;¨tVѥ2YwFUdo +5|zE!^|EbK$,֭[R4ֈaaa&&lQ8q"JUh&y9 u:QtG)ojg3]ۜY {" $?j=ֈp?SO{?Vv"0ѝƻ*jAx3bb4 s]vZbGDD{lx9rHff&'M?b1y*]]wqvJ V8| t+VPG%D""jJff&Rsssc$ Z3GT[sVH酷JǴʺ$/snwTV\ Z#̧W2_&D"҈nݺ!J{J#,yy':;;䥿b+y6حhz{]Vsx.'GVXK/5" $jZ#5E^mʺ^4de2Ti/a F,@pinATWY ظq#^~wX8i$l5:GxeUM+ȮϻEU6cl ؾ};k|5~'fFqxYrZ#TWWCb5㫯KXiG kk7*"lGZk(kZ+{sL+["`iDIIIR9s u8ydbc]Tn)e5 w꾔Wz|gk>j꥗^bOkd\]]!1PkF+@)VIձƖ^_i0!Ü$"UAT fСĢi\zu0`φÇú'|GBSQ\Q剌Jd2.stdw4飰m6_~ek۷o'H$R-dddƜ;w\ֈՐ,ysK#h:4$š6Uy,|Npgb<V^ Z#̧W2W_%D"҈ 1RiOiD ""='tss#6i:,j~Ɍv]vN.xE~嗡kdCk' j Zc5"\]JOg7w@S<&L(Iya"##E4vʕ+V"7mĆCedd{ʔ)8*P0j3-2YEclCMZߺu+P]rµm۶[$)|QS>Llwww… FgAkҨL4(WgK唴zmnn!!tf7@K5kրL&>D!$''CbHҞ҈ @DD$K^LNl%bƹF:Z*[.wZryc7qtƹX)>\uqUpss@PkDBZc_4u,W.xw;OsZ2<fϟ6DEr L4h͛o8uTlEWC4q ;^ \`;Մv`˖-0CWRaW IDAT_UָuVb𜴴{K. А%/K*`%~iՉ›E%4zXo[])Znh+WTe2lg!H4"H T\x\0`H ƩSzzzՕ%/ȿX93e􂄒*y%ɞfmmYz^cUk' kD777H F+TAkci)eս4EXsTӒ0 "M( <ʕ+05hР> /kzz:R)6Pciy7E%V'UXu1R>UV)\kܲe E"l5%--~#serZ#@b77߀퍥Q3Y]p,-?Ӓ+KMƸK4_FO(d2ڵk-H4"H TS0`${pR7.NFKxgTM+ȭiϻc +*FӒWZD!1|||PkF+TYk;G z}Fipi ˗aRA>C6/5N6 G xZAIC3|.꺐Sz-|pgkmci&W^psqzxUrZ#BboKASQ|I Ӓ;b/E:Zu=F|7Akm-L&[n% FpmH TS8p $[ۃM6LJpp#v4de`i|DFau=%Qx!t F(7\f  Z#Cbx5"\ZZP~2W:bA"!P!N,@pnAT˗/a=='?F짞z ~P@75Wv:o.b=!%oܸƵk*\kGE"l5%55_~!'surZ#BbKc-ؙ_)9]sZr]k91YclBmLxZo Z5kL&{7-H4"۷oCbHRڵkPEBb5N>ϏXb` s%g[NKii11TfX7@+^rݺujD<<< 1QkF+ 9%%uƹY9:0aaaĢi\tI^kܶm^<Z3qHDLPGSYErOK.klf8w[osC{=_uk7n$H$R-ܹs&K.PkD:H sss5`ih&{9 s:QxgȪiol8wyFO(d2[oElD!ܹsC*F 8cƌbQ55R_.VȟW۸ke0 5;Z#<QkDH ///&$$ۀD___ 1c ] * . +0o{"///)4Mc.]b b2䣏>bˏ?(5bCL5gr>^Triii^f=lQ&{Ė9w駟ܼy\ֈuuu,yAk [,t%s 뚎.vy͝?Kq8-Y&As H4";w@bHҞ҈ A 1YO"b #qs=hwSTbIURiuX7[tZ*j q+!1Oζ D7 U9-fQe|qUhWC-wi8xzz'FOi7n(*Dxˉ}v6Ğ9s&6w!{JMC NK_~2lqo.֯_OlH+*lD}Ǔ ښ%/188K#Bx`]cLZYw,Ӓ/^+(do1JG2w%D"҈RSS!1R)G.pUd` qߧ%dwy"7dQ'n߾?ۛIHH 5"@}}=$ K^~'CCC4"7x5B3 1_~*jjw6x{3A4LabK$,!55C*F ǒgy&44XfR}ρ`!fJOgɟ[JNLv(p;3w` H$ 1<<M _U5~y*hM M߳QH$!%%֭[Fhhhİc? ZcXXFi&a?%bƻF9Z,WsZrycˁSzEcl] $D"҈ 1RiOiD`Ȑ!,y5kVXX===YׯZ[ D"Hi*$8Ӓ[e' /s`!J{J#:::,yyg DliӦ?0 <CU@f!֦W Nfյ!+5\NHg"!vH www<< QxB会Ϯv6ndF'33C*F 1:u޼yQQQƺ[laUk'j}a$xyJy*~SqvlՕquu6 DVBb<l@ԑ+Vwsa^z%6l9;ud񴂤jIidUU>1zJ@ HKK(*D8{ =lذ={/!\?PZU-Ea+^Xt4-N=%'դ[u5R_?r֭=o_xUUUvrr266& TGN0GaɓAkDVUUABBW_}E젠9s&X ZL& [ikkݲ$HBnMѴʺ^ h:dD=coM:R̄HZeΝ4]VVfjjT撒bK$KKK69r$++O<񄓓^T޴i8aǎ|r駟[$-]T7 UGsz}.P[Ѯ6Zlǀ#']]]1''Odooo< a [[‡իJaԊڣu>g;QzbNC "MF yyy}}tRxgϞ?> _e߾}IIIĞ?^TYf}w}}[]]Yj+]m5D[lQN7E%VO/(oX(do1JG@ILLQFg}Vz?,J*Zrػw/Lwc;vLFk| TBIƖ^_i0HGaNVZ"޽qN/{:H4,CĐHؚk>Ç-#EQCcm3T 01؊@T͢UͭUdFܲaNxZ2pqqH=TnDaȍ(4{:QX+m13zOKE(F~ E9s#ˆ{޺u .gؗ[^z ĝ۷+\k|W-uu]+?Ypiz⑮ȓeG쀀)oB#ZKnHw/;.喞*jj@K<6TWK srr Hx[aÆAb5.X`ȑeɋӗ[۷5D;v[rP0j%-=୬әElMMQ8yZ#E!7Ј(ʍ(4YLV6Y<3qn E | _~ebD"8lAil8Y|)iVzqF4E5ulk sGBBj~B#Zr_nHUk싺SEWe] 8ws9wuTǁJS5>d#^KnHQ԰a 19p-Z4j(be$+Yh(?fUk 5S};[QxӒkOliiNʮnia3v H {%7Ј(^r# lj=^_\H/_Yw >Kh;;Mr ˍ|)K6|5>|nF E-_ĝ;v(\k|-?#\95K+UR%nhzp{b7/>" Yt`` O.>pvȏ|D%/ǎq̘1|.C8h >Ev Z'|؛wvvڵDKֳn-?ʪmgU\NY恇^= ِR4"Py.4R5|pH ~̘1cɋZ *2vO@~^64|)yArR_ 1PkQhDȍ(4"7qEK Fl!'[[[b4>//_cbb̙C#G1fe݉^x1ǨNWW788X8fٲe5ܹSZˉ-9"EQmνS9ۚZgb10/FcX[[[!1*+/q9~8hƍyi$r# EٳFφܽ{7%Fm1svu}NuCܱV}cbVusXw[@rrr 1R)ׁ8dСHQ!1455YxquĈeEܹU*Rb$ȬzJb%6 6i66464W5uQ[dy.G"buBU2=00 {ə8q" '/(4n:UUUU]ٹ*DRzAV&$eJ\F8[hښXh846>s#ȑ#xb>FDGGHQҥKAkܵkµ^xp(ʦ4p '5vSw/ sfɔ9Tn7o4}9!aOOA*y%.++37`KggggggnU_WKohW3p9: 7|6JB=TD_ϝ;EEFF?눔DHHH|||gg'MӋ/޼yˌ1CCK,?~={lb9lxٵkWBB.]UuE]\\2339G=III\ųxbwvޭpqҥ*y;wOښ}r 2QfYb'>>>+*TZ~5&UMSGEOS\׷bQ!\d[k,))QضmE@y+Wr'ttt̙3\e# IDAT~ HFָthbs2kpvAN͹;t  yTsNkD@[ 9ϟoʴDk$`J%ZIKKZK/DR#?bj-Ij$ *++q`tuuF3i$|>[E~?|v(ɋLBB>, frZ5i9r$ ///ޞ[C`?&> x<Z&HHtss3G^ W^y111 |7Zcpp0s}vҹ8o50* ƽ{Xj̙3Xk9s&I޽{ָsN\V㣹b1Iĝ;wp`rj ɓq`k…3gD6ɋ̮]X׈{@9r$ ///֖[#0n`"lX2 Fv`0vvvȢ(#EjjY믿ƍ #k{ѹֈH o!)6mBQ|9hr<555##ŋeeeMMMr\,K$QFEGGp)ǨT*]]],YfH@۷kwj5./Ij$ ܹaZbwVGGGGGGeeeAA)7oޫj۞&O==?cSSʋE=->/ %#"" O ={5Д~ <-E1}ӧO+111AAANNNNNNJS)))_BQQQ\\aVȎݿ?|ϟGG}F+FAiiʕ+׬Y#V\i8|M\C~ҹֈH Bo1"uި:uzKoud=2MOk쌎ƝX,-Zg<$|}}}}}g̘qƭ[~f^?OV,ٳgdjh!loookk{nQQQ~~~vvsлJr˖->p1c _ֈ Zo>dbL<ZTɕ꺎ʶ򖎊HKjֵ5dP?{k.5p`(J!3g΋...#Fx_ ;vn('OƁG3̋BQW``9s[lپ};*UWWGGG9r$::kd*MIk| "'bx@j#vwvteV6h u+-~+t '3ӓ!غuѣG-H~ݩˑ#G>uֱA0qƱ݄Q0lذ7ߜ3gNEE@.Ϙ1חk8)dǪi,Y_ pUP5;_h\9.v1mmm//n1(Zv]3RRRqqq` ?c38>1cƜ?~ȑ(;ϝ;733k8_ݫs7D@ 㢟GʼcJwÇqmܸ٣GfpLS7oތӧO>gҥsʘP80re 33ks1prrJLL GyVVVbb⟎^eXk駟t{sZb3OK3o7g U vKպW7UUU80T*^hjO{q茑==?Fb`ȑ,b\͟?Wظqƽ{*2%_LxWָo>kP(9B_GҦ?j`]k̭Kzܹs_5\!dF\0j(=1^4 ZZkR+Hxx8N<ƟY7WոX,&;maRcuu5 FðW<+JKK=|p=1^bbbp`5~'8#!6.**NطoZ#d_Sߡw]'2j(jl;::rcǎe #UUUz x2 x<Zbo!npv`FMcL|%HNN #:F+ׯCE]CCO8aΜ9꯿sW :bhPPq ';wnÆ 3f w֨P>jh480OrY5,bhFj,Zj/X{j pBuuzC \!ʑN[[3- gEᵂ,b` kkkHj40%Okk+Mo51c{7x3gdKw3Iħ];0zS#MӦ1uԄf///n1F^|Ed'&&ڵkE#FT3\J+}QP`Q |s"Wm)0tnn}Z#VLFk8pK}堠 n1F 80F\(//Ƕ'wpCbb"{ZFʈ֨MW\lAQ"  Syyy_9u(Ł駟HdggcߟCO8aj$)k=p`3|>[tOㅦi="݄G͡''ـ㱪e/e-BID*j#QC(J铐oooTaQQQ:smmRPPpt]s>4m4d>|V֬Y5~gd(++@6EQ65k?xεF|bP(9]=[N0Xbv;ssscƌahEK,A/[]FT>BxAZ#|7;8q '>̪ֈQVW=ZhjOM?񏯾 ?t /XbٲeÁgH #.\m6r…: Q͍{epEXX TʘW;;UV;L& ׯBhѢkF77-L=f=3fb &L2e ~|OcU4:)T 篫vso1mw;kڻ |O[ cm*%)Rv`FMi ?~Q@}}}xxxJJJ@@m}}ܹs\ѽIRR?)SiOݲzs!_sssgϞOᶷOHH̙3GZY- Wh}FU&b>3g<}ϟհrm_9MaGNNڵk`U|7a„jkknj~'֏\|յu֕+Wj#1C 80sV ͛7o߾KKÇ?<)￳53g+.KPgsw;7}h7Z|>aoXYY:u_LKKܻwoʕ7o1bD"qttT*/_NII9p'ED\\_ܹsm&CFaGGG[[[kkkqqq~~ٳguAs^{!V5&)'=GVήʻe-w;?co!k /18E~[o{k7kЂS5w:OެhYo`3T*eLfaC9qĪUV^$ֽ{ݻ~z}4D(CUksH5 +V0¿x2 x<Z!hGSc]uTdO:gxxbsȔows3Ł}1cHLLԹֈO `w3"Hb#>^dGvscP4%Qolnߞ_ uѿpnnnFݫ@Mq$Pٻ9MӬjƻ;_W56 dSځ&50IIIz˜?&/]hĴiӰxkxP(Û6WZ\ƋvVoh*;;{ժUȎMDk$hRϟ?~8ϟR+#))UGꦟ/DW=m >zRWWIVaG==nL&C6\}"~;;l5>% oZ0zC580KfRlDOޠ($$$XnN4Z~pq9M2/\]]"Z#qʕZcXXEGչֈO `wC9qJd8F 6yTٳgqj$Z1(M:,XR+&_JJ Z#:5>[?M{ǖ=b'2rq80,--BC$..Nl2n`,=zUDk$`ƎՕOg

V͛7q鲲2N|0/nݜƪֈNϹ_lhTiW ZTkjڻ*v*uJՍ^[=m-8ӥFLgg>(!!!dɒT?PSSe2$$7bbb+W1 J{IDATHHHBB{=v۷ovpp0W?TVFy)Sv‰3kBZ72 tMdd$]ꫯݻ<׭[fmٻ9M&L`1Ѭ˫zGD;0z 9!ŕ pvvz 0++a6mz8z?,dZx޼yxpww+566fee]x_z-'.M4 k'OԹlHvgis*d8##cٲeȎb 0)ڱc{{;x[nq咞#""8,+p`W#իgΜAvSSK<:55 NHH߿JE\E@zz:Z#:3!9s^5@<^'xx7}E@SS '''pK̝;777%pUdS5udȕ'/_~ܹ 6+􌓓D۶m{78'##PSF@67no4n؜S~Zx@Đڈky@FJpcccQQ% ֆ߳ѧ+h,YrرWN2|j8ĬZ* oxe z@./YdӦMjsq픞8q"ZsN___=2q߮} 'Ҧ$[:|B)üY1A4MYYYyy9׎ [[[Ho Ks5.YD[k;v,<999|BBB{95:uJZ#., Dk }ףF=4e$mںM-5gtRdGEE1xhP5Ti0`vLa`oXXXfZy&D"ф 8\Hddd5j:(٨!yUJ.5xZjF/;WGw? D"1 `nϡH0(233q4n8kx鈈YQrZC+.ؘuCGGUnH9,T\\lDBݽ;pMPTa3BC p#XrkwX4XbW)\  ܢqř^z!hC`ĉXkLKKӹֈ+iD>۩ fKzz3x_Aina`oXTTShC ++UG9,&<%80\\\Hk׮8tPn!gϞرcu5fgg#i k׮!ɉjii9`Q*.窸.@ p A?Zg}g0֬YZA0~x5\kG%D"|;IOO_x1'LY,lN0|> ׮]Gk^̜lVFuDk$`q`%%%Xk2d \C5<<\ZcNNy<^XXoN0^KJJDEP*@` A5~O?kk׮%Z#ƌ kP(̜3gܧ5\d_#q`wKII ^FOF&''UG Rhθ~:Έ&{\/\kǍ4=zhޜ`\~َTcc#~ɉ#B9?ȓ@   A?ZEy֭ydܸqXkҹlHÛ3g|gȞ8q"#qh$ @X_VZ!ΝcUkQGF QZZ3AH@~iԨQ:MthhoN0^ZZZnܸl̑WBP( fNOONZB,!f A?Z'|5FFF ZcNNεF\W$᝵3'--mѢEȎf^FB!` 8q,XYYVU*͛7Ij$ aC+++*ILii7xLNNέ[{/_&|OOOWWs)Jhh} F0s!4MϜ9SCFP(HLLDGBVVVzyy v P&**+888$$חkܣV322 %%%|>hV(VVV bq!J ŕ+W Z!2dHpp0%9srr  лBUV_魷E\rƍ|͛ojC---]]]\jΝ;1117naׯhUUR߿?{ ~!11q۶m;/_(..~3iҤEEEׯ_h4۷o|rPPZޱc}ʕ JcǎW1BT&$$hۅ#FP( EEEÇٹs]\\<|;w^v-00P.ڵK.)) ڵk{޽4 s7n6lѱgϞ7o6}Ϟ=n׶~۷o߽{]VV6t֟~|С---}슊!C477ݻrȐ!MMM};w <0Lkkk[[햖>@ xj777kBOMMM}lHDb766vtthnhhcbծԶ---JJJ<==m۶UUU?^(xNNN4M,]t֬Yf:v옅Eww޽{}lF겴|V[V744hrU*U[,?T*힞?a+>B_잞&m[T  ɓ|>e;wtqq aGG4wޥKP1 !DR@ vwwB!P. Bx=%:BF >{{{ass3BNȒH$(º:+ J!UUUwww;wŠ 7 Я_?۷>>>›7o20p@!:Bx5!C EEECB Æ ^r!D_!/^A !!!BjȑBtԨQBT6,, B3f ٳcB333ƍu3gƏ!DO8Bx)@tt4ĉI&ASRR111d@llҥK-nڴ п2_|B8bĈ~= !DcǎAIIIB4HIIN< !|'ON8pi!:=-- B^HOO(Bp1++ BΆ";w h^HHB p.]! \rBz*PTT!2dڵkJJJ h¹B7oB|-!ABUVVBCqUUP*jjj nnnZ!U__!DU $: Bhii BTS Bhmm @!dii .H$BwѣGC:a֭º:ؔ_;.WCC;'v\O>w\O._+"">0a|R5ydqA?=z0e!ZZC7i _|q7B_~eX,(~'KK˄X|%: .KGb]ϟ]TTDJÇ(ʕ+*jƌs̉k=lذ… :_C0|-`˖- ѳf͚y:Q?D"#uٹ _vWWy:;;r|]wqqQ*VTh4FAjuqq( _x身+Ƕ@ 6>nnnb_׭Q,uuur_(jʔ) \wAIQDQT*En4-JQG]GQl u8<O*N]GudNQ#|>gloyu@3^:jPOc_$=z; `xGJFIENDB`./pyke-1.1.1/doc/source/images/PyCon2008/client3g.png0000644000175000017500000014254511346504626020725 0ustar lambylambyPNG  IHDR^fjsBITO pHYs IDATxw[ŹHGuڮ붻p,` 6%!& <ܔKH..ccc"moJZuqfŻZy{fFwϜwg0cZ@7]#tp5M@7]#Vg}vsuvvZVRRKJJfΜhѢa aCM9@ :n'OtR]]]OOXR̜9sk׮dnf M[[/]TUUUSSnXNgttJ***1cNZ}7oor-j$+#.]zHM@~?ϊn#'%%PTnRfϞ}iT:"\./K\>/@(..޳g m-jkkgӦMCµ9hh9s洴-g|b/8v2؋޵k8pQT˗/_lYIIFh4Nŋ{} ׯUf--MFo,[x oڴi6lXhQFFF9}[o{nrΔ)S:ߖPX[0 SZZtɓ''&&Z,K555#߿cWM&iQJg1|b jzy ŷoTff֭[n|&#7FF- [>|8))uV8ՍԩS_xG V-[h111z~L6%99ܹs#lkk]w5|p쥅) 3oڻw-;^ΝJ8q?~ȑ[~FQBG~^r]w\ko~C79o~;ezX,޺u]ZZZv{kk~8o<~Qo#`鯿z]]j޻wҥKg>3ÔGE[/4~;ZH^^P[x'3g z/KzZ||NIg} <( 9--dx|1i*{)))v~ߥj_SO=5|'O4ɇo1Z[Z M\.%?WAT:CCR^z%[^*cܑ%|4^j0S/^Lcǎ[>VXXH[ aG?/fAAs=څTBXuo}[_AG/]#G^ 9B;s$UᅥbҤIßGc86#BU~_?ͨ_TvAաQM<OOO˗~hggwM6׿/]cLL C_YYI|%JvH I}T*ݶm"AXC:a㋋9r>otwz1 ,age`H79J2L*JRD"HXeYV,D"H0׋袢|nب, y˲۷oc!.U7D7=Ht%kߒ\p~p8Nr\.xdAl<0< СnҥtLJ~8MP]|.ǬzPQQ"hZ%---/!WxT.'qkdПgˇbK .]Z[[K'?y駃$AՅ 4\cwǎ1Ϛ5˷ְQ/ c\ ׇ/ ?L&ӬY?c#x >ϛ7r|Kuuuy'Ʈ3ydz®]F6*| Y5 l6ƍƅ ߋ@/_Nos9_C=Oyy9mDjjŋG+V ]%{/ↆmW^ pA~.kݺu-[p8F5P6n8yhr|,&i)QQQ?φ_b qA`7nHIOO/:}}}?g̘1JGu  /HKu !?OgϞ-č(!?yuv/~ :!45h4.XsTx7޸xbKKX,~7K &?b̘1… _}+W\.аo߾뮻 +okhh|#b& ;aj?yʕv[__s'x"==_5Yb$ ޱ|r{9y#dɟ~ mJ05BX1F#oso_ϥKFpTBa{l~\>QQQ#i z뭡g{w5^Ͽ|rAAh326) ?F2κFηG3g|ߤo]a.\xȑ0xlj~駇>w\}}}ggfS(**))t֬YK,x>Ç߿СCzbHRB]PP0eʔӧo۷o'\xl6;?4u7.]4+u^\.ח_~_vl6+ؤSZjyTkpǗp`]#tp5M@7]#܄g-%%c|~ޞx;::b}E~ٙď].W;K. www'&&:a➞D~bhfFWlZ\2Tۛ7Ll2,իWfZf+X,^dv?STh4xk cǎ,d˲cÁJc͆dcՊR(cłL&Ptt4Ƹ!16 xqww7BHRaB ㎎Pbb"Ƹ !1nmmEU<-- cBcN8cЀ!0ͫ999jP^^Ƹ!1@b]0aʕ+'b/_4iŋ)S`ϟ?:u*ܹs3gBeeeӧO#O1>y$Bh̙Ǐ#fϞMBs=z!4w\#GBwq̋;_6|B .EaۇZd x޽e˖a?S- ˲۷oexmݺcI|-[8;x x:tc|swaM8/17n8ѣ{w㸯 ca?1^~=q'N[SNa׬Yqӧ1ƫW8ٳロsa9pxʕǑ0X˗/c-[qܕ+W0K.8ڵkŋswuE8/X㸪*9yb1s8an8c<{lz=x֬Y555agΜq\KK xǵb8koopс16mqSL8:yd Ƹ8ш18q"q& c\TTqY㸾>q~~>qVcqnE(m ˕+Wl㸢"d8rW\\16 Ǒ?~WWqiӦa9+--qG.ǑE9#&E@qG.Ǒ@}}=q~;Ƹ8r8 `+++9# /^1vqpU-_c|%V\1pqwuǕcϞ=qܪU0gΜ8nӧOsvZɓ'9[nĉmذc|1W_}qƍ1_~%qwȑ#Cq` lق1>pq> ?8k_x߾}=C>Gyc駟r裏:SCӑN$j?v0q{{N#$뜁ILVjkk9r ~=LܬixHBMMMNFƤDzB>l]vY!1c 6}}}J7,**tѤ :.66!xt:Y7t__HLTDb?vɯAb˥#~HK$d2+J߅쏉IL>'ZFA9ND'''4&{X"vN#Ӑ8334&Kl6NNLIىZ/]teBr<%%_Bfhƍv]&''L(Μ9UXXh6y䑧~ @N~~>H(++C F+7n,B]+W~[BMƘ86j5} É'FBHKK۹s'UH⽽?O?XS.\8{Y&O,h >M^"DUUUrZ2!|ɏx4>%KfB^aҲ З>1?۷ŋeeef"Q ^TWW8s.\H/oKKK'M$@xBh?:d2^}U6??ĉӟ:;;[ZZkпdggV***~ik)#b7DD?c0LKK yp^sND"q\{r@$VLuZgϞh4.r;@UTbX"=zvwn?~xrr21h_FRI$X|Yv\JJJ yU[ZZH P-@w1#B(11===@!Bjb8%>Ba*6v !k!FxB(>>^ЧBATHD1ݻw|Jrv̙dFGA:E֜P[[^#UxnSa a0h^#@ ˦Ob!a2`r9q=F 1 |Q x_7xFjjss߽F"Bc@k.:fg͚E6k(T B`0x]PFb۩0F#x@0}83 %Tc>[0Uw`{{{I,ɘcǎcgR`0\~*J x> |˗ Q vVz6 !P(X8ܹ"U9s J||<FQQ@ҡ-: y|PFoo/x@0LXBWW. wuuaX 8ƣG ahD8===׮]#Z.qAk!0^-[5j[[߽FVKbBX8ܹk_WZvmdC$Q@R0&L P-&mPk( d2 L{ĘD!Bww7j 1 R[>8LbT|ܹs* z*U*ĉ@^#07o޼{n[+V HNN~HP(X8|'[n%իolFR0Kf3[‹A@U+Ia6kbW(}mB(>>޿cFpaX,$H$Ç;#HB+WXs IDATV=F`5>v";^#@HLL ]]]~SRRHT*X8|> W^ydFV0K- FAr0Bk5]e隽`7'@dUKo6.*N)KFe Dץ8O | q:}}}$H$C04Q }N׻j.u? ..`mudG#]C ݗ.]"qBB¤I"EJw$ _I\\s燗,B n6Wwn~׸rJ!jFC5&''XPp |㏷lBիW#ZCՃ?V^lk J{xͿ?|qܙ7FVSaL0~/G\.j%1˲1:0tF_^% )tъ6uCQ'*4{%!H05<\j1E8(*:UrRL `s4qӦM;w$ ^#@Pt@`0kLLaR(v?/;vؼy3׬Y.Xl~^. gj[GKZBBƔ)SjҷPf^#U3Qn* Lu_a!$͚P8{ltB.79%B(***E豅 ):9Յ2!Ȯ… $h4%r\bpG C` ,d-ZD{,#j0] kkJY&d4$N\JQI'Nx}ђ{=Z#^{ $V*~,_vqxڵ]cFc;qQqQVlwNiq8] ElZ]AXqIeD*! VG @1uTjlte&XW| Maۃ5Zgꨜz@'Tv_>~u&jmAnQ&V+. 2 Pt<Yn7YeO6BЉ| cCc<<Je2, D,.YDFrx{/耪dרVIP(X8|G7y/&ZCA4 ƴipЕ XVAF:xI-@x\Z#&&Z,{7VP*ޯ">#uFv( C!t:Ұ%@z/<d!̙3 >FBWyFx 5kPaDa(NGGٳgIXZZ*D-"3Y0H|rzFpԩx6lIm6BTTPZ~cccIT*  —۷oܸׯk(T!E5)H  S=\]C5Z*lOi0_PGn5@XBL3 cL_.={ ahD8O&qRRRYYxM$г]a6-0a`W\I"J`ƻ[ZCTfk!B௼D2۷o{I~zvŊdP(Tӧ 5vFrQaT> (U ^c^D5~%ĐƘ>0 {nzN{{;k(q `P;0MkRK Frx֭۱c?#\.^EbRi2X8l۶{! F(IIIT3fCG0k(|aTWfa*Dܙ/ vΝ$b& g Ӆ JYPZ";VZ%D-@!].߽FBAbRiXX8l۶m$ްaC9wR'u^{N;pG@RRp^#@hH"nS浳^eK JBӧ@۷o }rnݺFzd&?N 't5RN8A└3g Q <  <իW(FsnI[6Lʖ0!Μ9C!H5Pd2@5kףHXbKuK'd\hB\s쌤60THNNkDTupP|`m{$pBAD1&~V'{Nۡk$@۶m ټ#r|>n:0nڸ_9>3D5nXYĉƯ:<)))TkH$2ٍ;q)?7d2zD"DŽo`<)) Ü<55Op4 ICkwOg47jC Liii ]L& 傮 )k֬ 㦮m.Ogۇ̘Zڴƹs QcRGB_Xpva|Xs=dcyVRkt)%8#ܹsTysx@bcck̎;npk<>x6uޞ2XEqqڸg$2%% CyR^`5KP(]RF"0R){A6ueaƒ|ꦝ]BP]sK0Bck F'\.tV LYf _]#04Q3fMUHVp-@ YZZZ9Bⴴo]Zv;×d8JPU*G Dpn~,˲7n$"h !d;UoeSRRaKc`0W\.LիB$M#n+%mh#'t5677>|wql65rH E?è| q\7y6m""h|tJ[vƑ0K]ն% ϟ(//kkRI ^#@o>1n3M?ޖTPHF|tJԇrc|T2IMMkT("/[(*x*@Tr}w sBǗ~k $>$BwX\.@zj0nZXĝSR΂ܴi~)*hnn>t͛'D-V MP0eg3 C q\V,˲?Dk07m^#0AXPFz7^#0vҨ0JJJETҋ@F²,_[o!ȴi ~[*pSrkaʪU477DEEAz{{qqq~/^8ND"a7oL6D"t#;d9~Tu?x@4^c|WP>䓛FzQaL6MZhN H;ˋ T> ` U²,_o!ȴi AI"]SVZF0 M[##$j ,!q+c~/SN']H$>H6D"t… Tu<x@vJum5oq˖-dFN!ܼhz|F"H0t:]=5hK=A`~KCV|Il@¾tNSSNbVpB!j11118zˮV=csu Dpl&T*enJ6D"t… Tu7oHrr^cBBk(;wkW@Jzz:pcbbEJ* T vjJ!HR*CB_'04Q"^~sh"!j1L|GEcz#/0W%| Lq8ER}衇ȆH$ \x \q˖-5IMMkh4$x{ ^#@jTSNXz|PR)Fll,xx!D Dpzg}F⌌ŋ Qh^c\\$xbb W%3 C ±$d#?kt=-S1̪ 9˜2e@TCP[Ca:L&ˆ׈`R0-#gߑD2쫯J"ah5b5h6.*`AtO?˖-5T*HBkk+鍼 W>lB; C4c8HfHŢF oƢ)Kݷ>u#k09;"B8*ɓ' TZNMM%\. //wr* JL!d;s17)((C7^%cX ` BIM~lCi1%bT,r=^x{!qff˅fXVCi1---$g@c<5RN?\缹cX)( <"1wсox@<^c=F6,.)7!_jjzδ؀>tqQkdtgVʢ[ * 5&$$RF_^0 CU^#@Tj:^#.cMWyj޴:t>؍ sRuZ%f%r?yoD CbKRTtY~C_|A'|b޼yNLSGu2P:,]Ra6ebH?illܽ{7333WX!D-]]]VGDoٵZ 75pj$X֮.+Cx&oi&zǼr J4 ĨquY"5k|q2UяϞXջBWob4ukM\ZU# 2220רh8#1>E *U TBAB^c&kSa]8*1%8T؆B¾K$b& 0&$_mSkq{цɋcdaF]v8++kʕB!q:F_`z= )}}}5F FbKמ ]uH*ݞ07=J V"5kM6k- BSS&5wuXl9PUC۝٩ r?@ff&ƤI%111##Đ2 A5ꀁ( * F^c?x0>[R.M#+En$@d4QFafqIU5mzuPmˢ) -p3*vI⬬,:_:::n,޲gffpqcc#$uttXTXF>N竆5M`KHbE!A5~;!5 X4/'uNf򑺖5}ZFujkgpP3330HT*;233HUETRa$%%؏F>6Pm6wFQʗhK5!8@`?a(BΊhN9X| ;lo_ݴRc}}=Dʺzɐ8zz˞1$g@cXI^c?Bd|vڜ^Y\Q'C"5kG6khdĬ9iܞ޾NUdG(HBVVFqq@RF-HUkRI ^c?Bg|zSπ?[0#/?$b& Ш.KRwAuzJ&ne! JPgvv@C|1**JZF_`H0߽L LX,mmm$&5i3[T.t '$ů,"5k~@6k ъ4Z>]iw R+ ԘqYYYT'N\CPrss]C5(* Vȧ`Sa0hZfy6)}M`ج ݦ=]t?\s煖tͲmR'^իgBjjj-{^^ ԐA1 IDATlnmm%qtt4xBȧӸBWc/f\|mB*P5AF x{?xF&={*tzccms2AfggSa71---??Đ2ҹRnC`mWmwf,ISHAm߾999k֬K^477fEGG Q vTUU[|haH,ealnnn&qLL xlǃν>!]zgv7^#Dklf6,]s}_`sCVkwP]ˢY)RoKΦn^czzzaa!!e(zTu5h*4 9оZo{ϏIid&";HȄO"ahHtGvʬ4Y7&sە5MK2ģݶmsss׮]v&5C***h\PPw p |1L5Ne|.ẖ-VHZ)[VMO׈F|"5k|'xa/ޞrp]g|M %?PaL0A6kڢ"CP]CP(111T5Y#yuцVu(5F ` (aM]51s~nc.Obڸ$P%~$]n&ӍeZmLLp^N ׯ_EEE~_Nb~ D8&Iדƛ7Y#Y=`x K+ x@<^O>I6k+NYR {L/0#G}SƖC!q4 )#@0a`^#@j53.F>f맕-ϋWr, bߐa(㒤hŃKL{*t[CE8YKȡ 6N{㝔8W['kz*E"p"@x۫H ^c?>k7ZvW4^k7xg mk x@z)^cD =N#ݏet?UR _FFƤIH )#@)..kRap^c?5SXm/beZB׿5$Q%~I; {*t3BTys NܴiI~z\zƌX)/_qqq߽+Wab?/$ey!kd 㯴u՟n<s޶ाŹ2j^#0RF>ydPHF`.NR`lG,'BHD9ݞC-JYc.++^ e(&MkRadddOzlSJ}g된I*gy;"կd#&>]Xcӣ6JẖiwdJ>-kʂ \t޲O<^KH,C@c4Hⵆ;7n2^gF$soLyF xkOJ6`^#@ˣ(**bE۳RfqIG>n2;ߥg;?ZyI~l.Y,NJ⸸AV9")S5RՁP0kƑ`wԷisz_T ٲ mngD Dp8??ƍ/K=+`MjׅM [vuuuF㍅x!q.\SNx"E"є)SX8ƺ:ǃdpixJV(䦤& sU5g?#5<*¡NwOȜyuц6l3[_?ST[999%%%$L6MPFG ^c?5ͱJ\x+*#>ze!WTUU;$.((n8t䜅yUO;<[Fo'cWr kkkט#@8i$ٳ^sH,C@S[[KbJ^c?5R5EMw뺚KOjN!gZ)KE@X^#xg}lFO8V\is`K&CVϯ$_/F )++kDӧO'1x%>> #77~ k $WzvWꚌHt[f$( Ba(Nee[oE₂xULLVIL]_5u8YT4Bk9v{V()}~Ϝ9CbHTVV𥧧j~ k5z׸?h:AƊeIUH k_ 'xB*|jϟ9s&~G<;woNOܟUԷ.I3;E ,3fk/* #//~ k :n>kW7^yis3SbQP@pӛ7hD8o& 7o,D-UUU==7WTbs[fL.hCM&lv8?EM9I:q˩S-35>}Uxq5MZB4_;K 0<^?!HAAF^^p̚uJuT*-M-3p]f`uweڲt:̙3`^#@QTT5Ycbu4o6 i ?/݀  TTT-[T  &(c^NGZn=nqr_rE!7)E 8ĉ}֬Y~Oԟi q^BŒluLP@X>3$b& @SQQEEE>\~2đQ(/J݅.~X.%Ư(2f ;v޲ϙ3^I,C@UQQA℄^P4]i+ XXx@_ Jaa!p'LIbRF/b(ͻw~+SSpъ1V͜9sMDs%1 Pj5Faa!x@8n6h/btͲF _0$SO=E"ahD8ׯ_II7"0h4KQwQO߽;@QM^TWTԳFc55j&hh,QW *`AE@T?É%&>w|g&dS;͐tk׮Y={ʕ+5zzzk5)U3՛r_rQNVoɓ'ᑽN:kN"lUD<}ի633C JJJط'O.PRV/^H5ph)i€Z#}kf"]`twwwH zZlEO+,+EWհMm NRԭ[El\׈fffbOʍ(4SzYT\F.v5>8"8aGbYhD'Fq \zuݻ7 /iiirJ}tN mDoV̉'1**wĉĖJ0)9O>MKK#9j qQB##c.V݇ F\Ͱ>FD̙C.D52)42 QfMJ^sssmffF˿kfd;dw o~MyVfsW;+S'**#6j`ffZ#|DnDQyc{/mLZyZT+a\_"^Î5Ų(ʍzʕ+٧O^._5HXo8IIiHLgnWj#+ǎGh޵Ǐ[s*"rQkˍk7\xZ[g*MqRV;6&FCqwj)5Λ7O؀tF xxx@b899Q㓗(lSAZj Sxyinf&nUa ܂'/u'kիGu]cZ#Cbx{{rr# xj=ˑlCyB͟$zժ^E"9r[eΝ+l4RNn0̕+W4ƾ}rҥlbxX܂}\yv7<8s> Α#G~kG%T*!DK.F5)7YhdPkl{Fr.MX ,ôpoTF˱/5"ڧ8|r!uMQϒ5|asssJ^PxPϨks^vEC j 6$6j`nnZc 7Ј|>%57(5}肋:DFA,eQ'u֕H$aaa"W\_կ_?^RSSAkԽGS$gə+B+hGǃ߰aC޵xbKR'O\x5E.425~6%). Ƣ~45"ڧָpBrZ#?QB#05ÍIO[FA`wɿyS{9⢎ӰaCZcFmhhH wԨQw?;vBhy0EuEړgV3EŹo }LJXX'n-2kVlUEanذabYRn݈W\YlOKJJ h6jW1Sײyۚq.0,xԨYX5СCިQ#޵Ç[s*"rE P9߿cǎ$kk͛7 5_~ZvmR? HK@@o7QCO^Sn$A{yX)vg}CyڸqcZc&MF~~~:5_~e̘1 HNN&5kÅ I yX31A @Un={z5x:h?vСo-駟YfO ?O*7n܀Ke/_Ni5琖50'O; @ˍcQIkcYY(xɹ?W^*k9ٛ4i»T chtHk9s.84&Ν; H$FLL 8m45jՊب5"3g( Ao]cHH4 zG˖-Z#F#88߿!:cĉ] Ȏ; .Oz#zJddfbFrF@Q!ťK-ZDl___8ԓ_>|{Ehh(6aϞ=:UVk0B*Ç ĶVXA.$ FHJq5"Ӯ];Zc6mZ# $u ЪU+! F +l@bggĘ0am>}vzrxW @DDfbFeD.]Zp!+^Ν;°qD'Nhݺ5cII֨yr8"fWZE.$ FHJDyRIOk,..%6jpwF"aggGKxx84VVV zG6mjPQkD+++HPw vvvZHqv#;r9u\>}Ųʕ+ -.^>+ IDAT56gºpl®]`]cll,Z#J!dee;w666իɅD"҈222 1bbb(ƙ3gֈOjڵ#6jP^k; ƈaˈTP(_!1¸^z  I"WRf͚M6mڴL&Nxcر]=g>,Hкͼ{R|6[n7\.nS54, B4//////##/^ɓq ŋϟOl??#Fr̙,bGFFqT77on?;H5qh)0;v]vkpT2qi"r233Ϟ=Kl[[[nڵB"4ϳgφk׮;wV\Yp&###&&RiCPPڑ~qN`7OiMԩUQTF8w$R,+VC⼼۷o>}zݺu))).]f֖Hѭ)y1XV).yݜٙyo7O/]1ڵQ*l \׈֐+6yyՉmffSju\\ᓝ;w ٳo六N* i}ᵽ7ZK&j}Oߘr01cNkb$G\=Ųl ?˲ƍz'ׯ:oi%2۶mu:t]kݚUqibr+H$b.K.AF dffBbP*gFp^;|3#v Zcy;uDlB!?CTFjjz޼y0I.m4z0YYYJ9s4 Y?am[WauZcxtBlB!?CTrT] jz[l5jԩSSG!1UuVTG0 YմjfotԉΝ;5"-$FXXxP}evvvjj]lkyVv!`u=T{J^ .?~C/jݴ_qUфڵ+XUilllll;a„_taݻwO2eڴiB($)))f"v`` 2'N1**JT'5>jٮpa+a񠅛 ɖ-[@kܹ3Z#J;vqpDy;ZMȅD"Uie^^^/^`f)++ #??Ri;w.j Ø;X0L{j"^߽{w;vȻB҈ 1J%{2v]#zСY,**Znݷ~+tPAbEEEIP^ Ô2ҹsg#,,99H˖-=j||K&;zg42 siK#BL&c' LȲ,]}u5T>|?|*$''kj?vh"o͌x򵀑ΦM@kڵ+Z#86@AD΃?Nl{{{D"4VRl":C<_?|˗J5FFCYFDGңG;w̻( ,!!!CTFk v$ Az*W~FBua7%`4H!]v5F e\v l *H '%% *L}̾q@WSEYauƻ|+Jq5r^jm63ic|+*ž5zk׮kp~B҈.\T*JvZ۶mɥ۶m IX 1(y_>L}O^^uzꭺfSײz1S8TlUYQA%AC2g2 ЭVڎݺuj݉{"=$Fhh(סCa;wnݺ͛7scc+WJ=J 1|p.tҗřy=OzCNr0A@O 8i&M'Hz0G+Fi\z5e2uee Aիo߾=((^I.wy5j:y$ӼpBMBD*aZ{8777fj޽Ak/x{AlB!\pCTrp5ԨQ?""CfffB+Bb4hPjUb.rI%X.11rVߪ>)+1TUzݻw!1BBB yu۹MXZZj!1 Fۅ |FJ$C5cmǪU .ɨD`(+,B3f;((h4߿ 6[ 22L^ˬ_=z5n`Dܻwȑ#vttoN.D5"}P(4"$H RYVcO>KG 1,--)yiԨQo7 ź9x;$ 0pua^zQCwF 1XaBt -$СCiHptb;T\Q"0˗/7L&nS44J$?C"11qԩ 8q" /q8~73^i=*f{ͻ+9 P~ab;99q T7Ǐ!1`=,^"0w?6QPUCI8`{ɻطo_b+ ,!)) CTr;w$ 1i7677'CE8M ޽{S]w@#((qBb 2 DYle2ueeYhsMqҤI4?\&Q]7)QVYF9.\2yd^8ZcfͰqD+Wk+W$6q7"r޽f͚ܞ={jH z,]F} Zc߾}yHlB!$''Cb(JN w@DĨQ%/͚5E zG~j 6jȵlR؀FZH xFCM,YBopLFu$h&FYiH$w(*DHHH:e^߯5b~X24`޵FXql;w5ݻ\ֈZHe˖ijXСCAkׯZAP(4"H R_5"@HH$=yƺ 5Fprr Zh!l@bnnA4Yhe2ueeYh q èj8[HR﫤'!!Pe߾}w%v-tq}S$t捊ce˖8h ޵FXql;w߿ܾ}ȅPZiÆ =ZXXSZ5A"={F&9::mV!1˗ 5txUySMvw;~Z\eQqQ LP&jaÆ8`޵F8EP`iD)))J4 5>y򤰰aZ}ȑv .OAb Ġ5h֖NNN|w_Ww_wNV JRI ޼*.{[V39 s^|0 0ͫj3AQ(_j֬ }]j---LMM I(N:ov+lHSot?: : *lӦMZ, pBxx8<'VZuܹua/_0a._P(a԰0O_{ԲeKm6_'~& [yh/qҥW0d޵ƥK[&\0pmj*9D}ƍ'wD"9r$**Jx {AP "66VSklݺ5 /DUZ:@BqGl /,Z}v#s̡7L&?>=W31J#˲( "ܹs&L vxxixٽ{[ݦMlO?]ydL [DOuRsuu$ٳg7_jhXѣAk|RFElB!\xCTF 1WNK֭:;;SÇ59ظjժկ__؀zZH޽{v#gϦ7L&7o=W31JD"9|@Q!ٳgǏO숈3fk.XئMl‚ `]#x'bs [DOy;ZѣGjsH ccZ+֬YZcZ4"oÇ;J믉P(4"K.Ab(Jȑ#5"@XX$=166VZƺ#G5rw@\׈...\zEWٳ'm>Gop㨮D(+,Bgώ7?# /;wum۶!̛7QF5.X2rMw\]]cǎ +Weڵ!7FO*j̘1V(X¥K 1JeYiD <<ضm[WWWb Fw@LJ6 D155Bbу D9s&95k=G31J#˲A"3gΌ;ؑnU;vy&۵k#B;w.P=z4Z#dE7nܹnnn܉'jabBuց!?FO*  FpeH RYV055]vnnnƺGo!6>>>\:u ALMM_~%m>BicBqTM"zfbFDrqBt3gdڵkS5oG0{l:f޵ƹs[sgD\~ԩSFɁĨV%/ׯK#B8q"hGwpJY( ,!-- CTr'O$*DDD@b۷oAlJ^o55"+$WvmaBtjժi!1wNL6Qk54, "rΜ9/j׮MioܸAlR#B5ktcǎ]ke2/l=ݹӧO  '' _F///,aҤI5|zPT&L B҈ 1JeYiD ""4REl0vX{Fpss""" AU֭m>2uTzs7}tz#zfbFeDOIkצtm@kС6a̙57ws͝qsڵwƳgϒ ŋfffo5z{{ciD'O J5qDb+ ,ʕ+J4" QjUJ^:t\6 IDATMl8AƏOu]#QkD777H ooo.,,L؀jժZH/ D qu奙eQ"9sFpXO:u`I~߯_N;bf̘Z xQL [DOvڶmۈ?\ֈ/^ĨQ%/6lK#B@k?~}:t&M]ke2/l=ի;===rZ#BbXXXPi&4"FO* RB!\zCTFj׮ AOkܹ?(yAI&QC5";$',l@bbbܹ3m>2ydzswS<==54,$B8uÉ]nyfMG0uT[޵FEZsgD\zu֭j aeeE͛Ak ҈N ZI\RAsP(4"WBb(ʲ҈ @ڵ!1LLL(yҥK@@."ɓk; j h!1:uD)j9 A(+… E'O6l,X@˦M]F]b{L»G%d2e#\eb{zzrF˃İe˖-5aiDӧO٪* 6=Q(Xµk 1J%D.pUS$=k׮AAAvww;LBu]#QkD͍<<<8 @*U;vG^A)+,B'O~WĎZp! /7nz* lw}3T{޵F8*Y&Q^#\i&b{yyq)))F˃İe֭5`iD3fkT*P(4"k׮Ab(ʲ҈ @:u 1TB_|Bl9Cw@_<==!19???aBt*Uh!1:t@P= 7C>fbFDsĉC;**jѢE4lذnݺa&O ]?֨.Giiih/^$5"@~~>$=%/;hX̙3Ak)3|B҈_T*J#uօĨ\2%/ݺu %%/1uT{5"$;#l@Rre-$RGƎKop&L@o|DOqssL חX,˦ [8qbȐ!ĎZx1 /ە+Wݽ{wl·~ ]ixaL&-mذܥKj@˶m@k ҈@kBRV(X!1JeYi=Tnݺݻ"L6Fֈ^^^!:Hʕ۷Gzs7n8z#zfbFeQ@DL%K믿G8"'8c ޵F8J&S._oLJ{jKH '''J^oZcdd$F0{la>=_T8b+ ,ƍJ4"AOkѣGdd$===)yA3fP{"7$$}m۶GFMopjꪙeQ"\|Y$vz~g^֭[{Oia5㏼kL&s_>>>Z#|ٙ;vXvm,aܹ5Θ1U*լYP(4"7n@b(ʲ҈ @TT$1%/={]6."̙3 w@\׈ސnnnw#66 D5j93f =U3181ϲ,?9v옦ָtR^֮] `{ꅍ#B7ntqqqkpTL&-\ti]F.PkDHZjQsN֭!̛7ƙ3g;J3g FpMH RYVqUĠ5իnݺ;⨮k; kDH WWWM؀H ѦM.}dȑ8믿7>h&FYidYD;vĮ_ehxYf ,ݻ76oqj=Ű\.suϏq@ 1\]])yٵkhX‚ @k|QRR2oQ2=E31JD"~@Q!ѣGG/4^>}`F ]9sxǎKlL- "ŋk׮%_ Dҷxصkɓ'}ΝgϞf͚իW'v~m޽{[lI옘ݻw Z]D>{ RRR֬YC=zRsr9h*J*87o*UDlBQPPoߞJu|Go#ieڵz,csss zÇV(VVV4޽nݺu͚5ixYnݺogΜ -xXh<6p/^LlL6dG[nٳ...Z6 a{^^^xVB ;;Oٳg´ cY͛F}ݻW=ڷo_bׯ_Ŋ4ZҥK۷7 /Ltt >mʕ_C8o<޵FEZ&͞=%55D ??/$ZA~w ~111hk.J^tO?s4kLA(J_ #!1J%bhPWlPeaᝨW$Bo߾ 4 [FI0̓oEq… wpDĨUصFr# m>!7Ј BAU`YQ QGӧ4hrJ^V\yEb_F Ì9 5.r[@DNjjU_hFFq jF j!(5޽{_ F ԯ_د_FLJco&DƂa e2٢EZ#Ab8;;0QhDFD@8GGGbI$;w`|Pnc#G` +x~Yb(}QB#0#Fq…kF"L&Zx_nШFQ/7YhTֈAyrrFaׯ{1΀7nLlѶ̇O0 .55"$jeQhDI9FX,˦ E5^˗8p@17rȅFa ZExGAl\>|G6ʾ֨~WnШFQhʍ"ը5"BP^k>/%rȅFa4hAOk8p`ӦMKɋ^Bo,YhU&6jZ;܈B#}@nDA#˲" AnxxܨQ#8ƌ_-[JAq$r# :%K56r[@DNrr/B/PkTOnШFQ܈BFDk;GԫWo"iР$!%/ j޼9E220K,5.[ب5"@@@$F͚5Qk|wj FDmۆB#gccC,Da''P%*>_QFK K.hx#bbbPhdfȐ!5ϼk_}5O@DNrrˉQ@j۷Y=v(..֔:#qk׮\޽{[lI옘ݻwk.bڵu4[jurrrQQQNN]?~BNqvvvvvJwAryqq1U*y#)PRRrm'ojjq\`` ˲?ڷoOlReee &7nիӧ:~i䒒e gll,`< 6000e-Z .IIIǏ?|L& 8si֭[޽5&&&..N?S9QR}7׍7bccw)tDZnݺΝ+))iذa\\;]A@@$F͚5givZͻpժU 楥eBBCDDnnn߾}5^uTk 8aTdLUBs˲H)o9p@bi/_^ >|{nԨixSRR=dQiiittɓ'5?U֭[G;񌌌.]$T< qgٲekC !\.ϭ[^zH$h+<7n^zB_MJJAAA:5hN [ !i߿_s];VkJk駟CAA'O]YyAk,--:u|²B$$-SZZzrޒ%K,H%%%q={qgҥ5}+WDHJJԩ144LOOd2ZzzCZcF k.~_`Up~WmWV-33S舄^0 +t8_u6mGH߿vpBGVt,N5juRR\[1kȑR7M:ԭ[7b7nX~Ȓ%K=tPAzD dmmt~h/54r[2SN$nf":u~III ǏɅP«W5k\gggAj֬lnnnff&Ԍ"H/^PrA[n-HiUV6mn߾} SZZZ֪UٹH.k?5kրdժUV(Ƃ֭[߾}֭[O<)7A]ԪU}!1rssY2 SF sifftgFDD;22RSEEEP (M:tҨQ#{{{^>MTTԉ'}ɓ'~ kׂثW/޵FI* UVVlCfccY>|d >|X1ё$G.k!145f]@ .lMBujT*)F~Ⴃ 54J$G [:tk׮nҤoFŋAkꫯD8/ qŊk$\._t)#Kbb;Z#JkDt"H\J^:Zc6m4"kׂEIIիP(4"{Abq!ӨQ#H z1+8"XpWș r-$FÆ i@ScRi^荏)eYjj[h6lX@@ /ѷo_WZŻؿbr zJbb"l=}\ֈŐyyy>|ƶmbiDׯqʕ^RRvZb+ ,޽{/_,+4n8|m"Zw@ ((#DdZHv#p  Z8QVY!}[.8S!@RAbPrQJ%Fa׬Y%%%p4B҈@*R2=^31UeٜBtcǎnڴ-[hx?~bb"G#Bѣhׯ]k]r9C=%!!a… 4>1h_ᅬ?|ݧO~ZPTV< 44aÆ5k:RQT^رc5vK#Bشih֭w._P`iD<(,,TSܻwo֭{e߿rJeӵkW.{jҤ $==fԨQЛVȲǾd+W677ussS6#q֯_OUk;`EǬaooϨFsňTSߔ׾}x9s DEݺu} ^~mFF/?~,t8L%2^gݑ#G  ZVsUT!I$7[5//m۶Ij՚5kִiӀ33333^x[n_BZZZ)tЁ͚5ۺu+ /ͻp렠 ^7nL:u̙:u7/a~wv600}qY,X@찰/$ ř^ט + &~̛7|&5_CLL %/mڴ/v}UZZŋ{nܸqȑ2 0666O%.t{T*~=?]ct?tkܾ};ĠT*9zG Zҥ 4%R(#Gڵk>}4˃hҤ $==믿=D2,kbbbbbbooۥKa._dɒ+WV)33aÆwnذ ïM]OY9@bUXqҥ0۷?/--ovA]'8}*Uܼyx5Vx5~:]$I^^W]'77wĉp|eٸ8###C8gb4oeܹ֏=Z$LjpBpp{ի׉'Ku7l_[.8DΟ??|bqCfEZ׸xb8Sm۶m۶oL<􉒒H7oPr XإKFavI~'Oܱc^e֭~w._P42u###CRq6 ZyԩS5vޝ}D&Hؾ};hj/))ieo~g]ff|Cނn0s˗&&&ƣ4kL Lcǎ!!!#1o߾6mE;we̙q8޽{qb,۬Y3b':vmƻعsgb"cY'Ξ=;k,bGDDpNh,oҤIrѢE|Fkkk^}j5$Lgςֈa AOܹPk>u!++ C*ƊĐ!CΝK"o߾?lݺ￟2e ͛7Ġ5?*"nOHJJ_rȑ#;TDZ:a!1 jժӧOόeY-$Ovf oٲ%|+4JTLY~ȑs]x9992F:uj۶myL(H*lՁݻwϞ= <~822>>>o?~ܫW}<++@u}jՊ-[מ?;w&LyxΝ;)ժU[z'J%hZcǎm``e;1뜜m۶lݺ_u:˙3g &,iӦ_paFDD5jwAQyVD9D$"xr+qkI^\Uڨ+޺&*-]W-z!r"p5mg$1;4c3߷##Ç޽[ucHbn^rssɠ?VC=zΝ=o… z$tkTK充LHbbwYիWWXݽ{C#""-,,̆ SRRN>"&**J׿ZGn4//ƍ3uT|=WsN5''K^'Νaooobb}]|!}˼yHb O^Ν;e2٫~R/S䐚[9e TKwBH"lذǯԊZb&>Ң)3emmmRf0::J楝^3x|Y`k֦D䢌aPm۶k8pɓ/=R }k֬0 Z^cbb.\E+ȥ-[t񙸸Y&66ښX7$gϞUHNJ`wuN ٳz> :Mc ]n0}oJNNȸ{n]]][[ɓrܹs/^Lw$G*]bܺu >GhBP*Xٹx{{988HNGF!զ&D4G1\_OyH-[q"2o Ξ=˝(HH81t泮11o<666:BS]~҄Tթa$@U UCD:af?Jy%HsE.Zٻw͛7?0rJ5Z kTO?rԩSh6l;5o>2@b=B~!GPz$tkT;׮]#?؏>hb\677Đd"tS/H83gQ ΝPH$H^Ƹ~e1MK,9x O :V2k@э#)$::˜ؤX!r mjbu)K.ikܳg`۶mcZ,_x/^THQJXܹsk֬sMHH;(w^~HQE&n߾ull,GP.]k$YG_??_̙3'%%QGUC4!w]=)ڢ5[neYk:Rt'.\D+9|痚jbbwPj& $SCㄙ5&r@ Zx'22˜x}:/1Bd)**v6lਕS]]M6%'Mt ^HKKk$Yoll|92(P[[[SSSSS%BAT^Pח RSSuvo% $ݻɈus7>>O>*ィG055S|0 흙_=ztΝD ?~ &aaazTsqW0TK$$+yA3ܻwO5VZ500wP_qqq|G4ٴi9pC+Teee| PzZd' z&zs_'ORRƭ[feeaw^<8"Laoo?i$^0]]] "++ݻ޽{&&&xb5^rE^cDD2a:;;=<<HO``\.766%$ 㺺FRȲÇy'##cX"all]__?&!tjNaLIPP__Grr2I~V/_Nϟ稕c[[1?_.6  PݶwxxX_ m}ww7__Ǚ3gH$ofDyy{wM1<<<̞@ ݿQqMqqq[[fff|ERXXtzR^\\W<ܜ|2ȑ#k׮1 jhhzGGG322 jU9~ut<&UXXH^'$$B())upMټy@ ~~~"hɒ%|044Dp4A__ߧ~ѩǤ: * ݢX -/ 4M``  }=Ӡ^/; abb"ܴdY3^c\\E+@NNNqqs;Zh^v,J`w~gNxGFF[!ضm֡"i 8:::::"ܭ*..NOOǺV^ L6NFuE6mڴiB+W;$y]nnn CPt 5Rfƿ/^cHHPkQHvdde-EIKK۸q# . BTJ$~hfffaaaP(>|8222<<\]]MF T$D".*"D!TVVVUUD"PP(D999555ot!dgggeeT*9jT*!r8Eihhaz-2"RiPPٳg>:uzS5D' :u߱Qgdd-##b1G 1 T* CBBf̘Q+񅍍R,**'kՋBWWW2sKqbcc}ݜ|GGǟP9p|o~ٽ{紡>[[[PZZZtt4-RFDDpJEE}ӹk<{#Gpwر!TZZlٲ'O:88,^OSIIIEEѣG gϞ=22wߩꢢٳgwWJ2>>kppرck``رceeeǏW垞}}}ǏPս'Nxɉ'UuOOɓ'ݻO|pU… ŋ@JJ B(""B۶mCmܸk{zzcvڅx{{O6 !>211B.]|WTXhBʕ+`еk잖tdff~p1++ Bw >/771"!tA'B<<<B777PII "`̙r yOIDATΕjNpMM [ZG=xCŏ=Pcc#X[[#/jii<ćֵ)BN{ vuuBBɓ'/@&!4w\8s ·Çsp܌?cc㺺:KiDžW4jmm}iҎ^⎫Waa: xY5|xY*$%%! !!!d/]Ο?Z|9={!bŊXF777700׿swEk;wBU.^hhhu ,\066v``n٪kxyy 7|w]tt~]r_|wQ>\"++x|iG^:<<_绹{G@F`槟~L󇆆{Шk899UUUzzz9rk2@8??ٹF)چhaaQSSCWmyL|0 ˲xP(dYSbAD"e1 Y$X!$ ˲aTʲ,cd2˲xFOOeYEa{L}}}eq,Z<,˚!XsAX9"cccea#&&&,g²,kR rP˭b1hl|"I)722¹dmmGI@ XdB*0ׯnݸS6668 alllpGqGq5|ָ 666Xc;畋D1e9ָ畋1e9rD2=,T*}Ad/(=<<Ȋj*>&w<\IENDB`./pyke-1.1.1/doc/source/images/PyCon2008/client2b.png0000644000175000017500000007053411346504626020715 0ustar lambylambyPNG  IHDRAssBITO pHYs IDATxg@GgT"ENPEz {Wb-F451Qc FB4E$"" ^0ɸr{ӳ{ۛ{vٙg0 QXL `00=a0 `O`{" <a00kkk^zZ---]]]WW#Fc5v^ X H>ጌ[Z̙3#""?]___UUr%KhjjA@y~ [痕@WXɯOĐ!C"###Sm$&&nذ|"mٲWz}+++2??v?T+[P255?Ҥ| %%O=t_m X^+HQnii;v,/yyycƌՑ_@T#zmwBGߥ~yyyϟ9s&5ۯ_?&HQ޳gڡ[ZZ[hRyp[DDcǎ}NJݾ}[OO?H">K_ȯI*~~}p[tDfffW\dXyyyX3AϞ=^G7qDi%UUU'NX`A~ u֧OE9sF[VVV``tttlͲeܹc'j~ٹsVVV Xʕ+Q}";)q >I 'mllܽ{>8ׯ_喝M6)((|ɓ'GmGI[>Jk%nZ/{}Tɓ'VVVBÚp+//1b x.Zwwv$^7IW棬 U't=B*KOaM]8AUTT? rss['O2ZUTT']p!4#""jkkKJJ\2rHjI??v$_.wwAPfGHӦMC߿Ч&. f͚t=..łP1MMLAN hiii[rٲerYY(_V@譍YpHdDTco5 v>YԩSzJJJ;-^^` '|W+YWWGݻWo!譍YpHd[J,--GHBÚhj%={?iyy9\\\ɓԴSMP9s-:1 w#$MU>i$RuǾh%ԾA}}'ϻjժO6}I׭[~nB#ķ(wC$M%""ZmۄJHe9ʴkVB/YD9sxOڵkW ڜn nDNNYQQ1;;[ڄH2#ܹsك;;;dSTO>@vyy 0j$B`WWW;w.kkkw`eev P={EssYўUV ]$IdKի Po…,~zA4` $ Ayюr~'򕜵5 >Y!]Y͛h*00P DȦEɡك RRRRTTTTTTPPPPPgrrrrrr,bCJKK?y rDa>wIwڵF(u 鉨ϟlhhohhhhhhlllllljjjjjjnnniiAo%WWW]2 $} _Q1 ُ?QDg 0ww̞=nǏl!=렝nݺ!D`>1%X///cX^^^T.| dHNNwT`%/_5 9s&] ڹs'MzhA}6J0ݒCZZȑ#O?'G-t !.N;sps`Ũ܄ѓ Q Y$7oޤrZn+fmU*D=#R{_8}}}oi{uhi%---}E.\!mTԷUUUb'PM<ƚiic۷YEi|# DItahh yyyGաxhT@__?##C@mq٤$1˓ppB+++ܨ/]T;oUU 6TTTs\w5J ӧOGew~MM]\\D9iO2رc ur.w^^~w]]uD8`> |ذaTZZZgώLHHmhh~ \`-~MtHuu _|YZZT^^qڵ۷3*Oͬoedd444R$I&Qkτn@O{mjj b}ξ}^|_MR]]M '|MMb:,i~w|nb1ۇΦe˖QG~ );)) /{NN+@vvvLLLrr2 ++φ333씔wbbb޼ypo촴4۷ocbb7o޽dffVWWSPy`~ouܹׯWUULUUU0.mce---쯩+vvsss;kkk?X_p2p0 ڍK;-C߶ o9?`Çs܅ ӹ\.\.wҥ7op\U_~rW}]r% ))~WD. =/\_ x9]v- >>~8.wrw8u˅ N8r7r r=z#Gp_} s}}}߼ybYG@^zeaa`E], ##6v-[̙3vlyRRR~իW9X]] md![]]ٚF.mddlcccd"ܼb,--meelkkkd O>ȶClwwjn4h aÆ!"{ȑ5jnjq! ٓ&MfS?ӧO̙3'O ٳgO:sΝ>}:g͚ ̙3ڋ/?>fXyyy;wtpp}9=͛7oݺk׮ʪ<==a<$IOOOO6L _6 <==a65CCCjyzzw { |6=z@ >0ֆ󃶅,--ݫW/OOO330^lmm 'UUU-Z 6Zihrrr.]g677?}L@T\\HKȣGtttvڅxD@@ S1ݻ6M@ á  GUUU?~{O /_DŨ6(| `oo Δ`hr{^j շn݂u-y,glmm( DDKK j͢T 8xfb$~ Ξ޽-b<#X;"ߢTUU'q" 5 SSS'`0C;\CCj0®]|||? ]Uuu76tPaD'ӧ[㡆!b Ɂia(DaڲrJLtTUUǏݻR'F ӧjfff8N`,KaΝ0+-jjj#FA Я_?6Z' @ CU999^^^q" "<<lj08D8NA5 sssDELmj$phUUU]v 0_5 0]ϟ8UCbjpID w+VkD8NA١ѳgO:e8---Qbᔯlj08˗Mg}F>Lٳgo޼[AAn8!''  GnnԩSD[ʼnpkL[/_|rZRSSCȈ:1Rj5c"C0EsssSSDa0lj08QeeKM$Z#ǧ@ё7 ~0D|7}ths802+#,[ $'q" 5 KKK}*dԄflj08硭s8qqq_3[UUnHxGUUU1Eammݻwo4~GG C7yyyf͂69x`8QUU޵kףGV$X,ÇMLLMeIxu_~嗴TE$D):_٘C899ѫW/PIcǎf0RVVFNNN/_d@444VQWWǴHLL򒗗tpp`w3Csww eG0ﵲX,Fۗ™b:::_~qYv+VP;666b% dYYYLX0$IB}L=xmX,[[[KKˁ2G̔<}i...^l٩STX$yyyś6AkQFݻwFJ/^tuuMJJ'I1Л7oQ.m'NNQZZ}Y&L`P]}7644į_~AKB=<<į*** ;ϟ"8tիWP޽)366.++ yj,_A1a2Dtzy}E1@jjjݻLz[SSbd<ӧOW_1]d~?n1(f޽t'8s ѻz*J8}]y'Nzy-WWgϞ=ǎLllW_M޽'N.;>Y޽T՞ϛ7GWZ%n"//~fvj5A5GEG *ttt]HHdɒ%KRm``@KW^cƌy۷o۾5\]] 011իJPð&JJJgZZZbV>N ##۷N-8 H"J"""Pؼy(UQ.[=S `[XX@YR':x ǃ5bРAw܁#""P IcuHjI0,6GoooԨ޽KKK&I"7nx1xDI EFFn޼YZ/BG@ )_59z"|(Nj5!EyfZZUTTNj*RfffLL'3*%y͛7455'8 amggR(P q"?jܹ|R_*Ϝ93ĉJKKQ(((`P!Ntut rQEE ի!@p$7NԫW/(K*DhQvf>tO>]^^.bTСCtʼnJKKmmmmKaD'O'Irɨ8p!ׯ_#VVVpC*;h[33C6lh^[[{رKW$TEEEk׮6Ñ>OvqVqdfu*aDm1o޼͛7͈sΥk07|m]]]Z(N8Fm{{{;5NDKDZZR=?C 8QII j *Ύ 027nDei 8'p8PʼnOb={4h۷Q[l|uÇiC[GGGܻwA|l󃂂x<Ç^B;%&&&..ڞ<s觥:׬7D lM<;hs8)Dn'4gΜAq"555?ìNڵkYYYVWW4iR;j@NNիWG¹O#..7M 3gΜ9shJKK y")ờ(p\4$QaʼnKnwSSǎ]ĉQcPt^˨fuuv WVV/^QHnʒ8QYYӧ>ٳ"fMDEE'*))ߡdIG $h- \v ʼnFɶ':zh]]Ї9rÇݻ }"E[[5 ...6ÑOyw7nrHضmG1Ngh|4cXhX,̝;wp'O$&&Ӈȑ#kjjQ[[ɓk׮222¾{\z]QFagg'~GH{:%**C;v CPya%GrDQQQa|T%yL:g5j:v͚5҂rXZZvPHN?5ZQ:tR~~>544&NءýB.]7n*%Yf͚5QUIMMMhF+/=x{{~Çlf9 `D LݥL`'''"}"%KQòeP 'NdyTXs!9}B*T":Ξ=*ШuuuWfgϞXB;_$Oʒ8QQQѹsЦp ( 8|QQt;ѣGi޽:::˗/APM>]YYC+**Θ1FO0:re'=zt+OĜ*zjll6qww[[[1**LJN]nݺ!
b4tuO~(.^Qz})L(NDD z3gΜ9'%wC AR&GR٩K~ y W%9q0T":ƉPW&t=[nE|LL",rJ+q"GGG(K D8v]q]vA[GGgʕ4c!֮]K >7o?~|}}=`̘1?-ҥK?1 #Yꢆall,JU%%%p8]Wzyy _¹ttX 1c QҪP D6-uO1c ?$? ]\\Zy"j# pzzzϘi 5bQQQMMMgg+W$ɴ(5ju9Z8N$ m~^WNxqD?3u놪Ŵ޽{/_g\zU&xÇ=vV9UfÁ(UA`O$?ZR/_Fq"6 O>}tZAlj!$$4hЅ  1f+#A5xpvvFx{{{1 ((J".Ȁ"fqssCofpΝ;iC[__Ϗ}ٳghsʔ)RRR iiiٰaʟ ߖ.]ʔ'O޾}SN%0GuU C#z!=z@kJ7, I2!!A4s]vA{ҤI˗/+++hs8}d(022r=z:F<`aaaEE_~v:88}:u]l+++UbX,<\`ffv1iJ[VVv!hkii͙3GJKK铓C);w(((׏A nJHH!C|Jlnn޶m@UUo߾ ^#qbjA,Z_;0CϞ=[ښߜ:z /2|Ԥʢ"ǎ]˟ Avvv:::6668͛V7?uuu;vhnnVPPySbČӧO^^^{E2ȉ'аiӦ===ᆄ;q$< ^5 eee(pDJJJ۶m }QrrrMM |M;ϟ?Auu5G$IN4I__3JJJ RQQ?۷Qݻ7(A’1d &L0tuuQO̢0hРAuF---QQQ6mBw&&&CA8SNE7A\7.?$SP=ߛ,ݻw%)*MM+VФK9w_x_WWw劊LQFAKBD@MaO8s ]q@hoܸ}۷}}}.C_f!Qh<s?ajjF޽E_6Ñ2O3??K.WTT\l.#$wakkn{etǏOKUzzz{쁶Gv*o޼ٸqܹsLMM%6m6p t5III633[_ҤK" ? fvfiWa?Z'Og|={8z"300]S~;whᡡ489~8J2sV9U 033C C;|yy޽{p'_~DeShh̾w{ѯ_? $ 3>ƍ7n8ZC +ƉuPPД)S$gp|bƌ}Zy1c0a/_(" %KФK?~|Æ |"ƛ6mfٌh<<<<<<&{رq"Ν;Gc000@s$/=oκuVX!Eiرcׯ_Yب.=LtƇ9j -p8ݻvv55իW]VCCaRQðg={YA~:cر,"zzzIxŋ~~~ϟۯd\bfΜ$yxxDAr3>={-XhM:onڴȑ#|>Ś={v``9SڤmєkY3>Ο?OW(??644D9%Ə,;Ȋ#Gbbb=gΜV9U ٳ'j"+**m#9|֭;vhEhaaa7;y!jNNNӧO3+HOg|3!IHv׮][l)))>44tь)f͚\qwwo剼R0錏oߢltտ tJSS7o6[ePPдid@M6ZSD錏 .'B+PR'<?ܰaC5c6lذpBܑAohϝ;=i$Ap`aakTTTDDD@0≮\SSVZ%9ٝeG"1uP'''O0G8u544x𡯯͛7|W~$֖M?~2'NA۠`ggm{{xf0Njj*Z].˗/Ϝ9w_hц  ҆i {ʔ)В8~:ŋtʼn[hmٲ}풑iӦÇg5cƌ͛7+L[:t5hϟ?'bN|XZZaoo/JUh5jө 44~C{#>ЮعVbccQpwwq"HNNF %NňNee3gNWzl>***oO?UVV}4`NZ #4vqq!A6(kyW^ж~z͛7(X֫W/EJ]]ݯv~5өM-Y3>.]Dchڵ644ܺu+ 477 Ebӧ)))vrr"P t5JJJtttݭ[Bf0ׯ4`kkk'''qܾ}{EEGaaaCxD&Ѳa;w5(77w622駟)\__wސlmm'L ai/_ سgφx<Df1kkk0E p8D͇HOO, `Μ9pƐ!CpԠMdۇTUU]t $I5;{==/]TQQQZ1b$>>͛7vtt$tX9sR0uuuhqaUUժ*f0ΫWP-j7o>|o5k$IH8N 讫T[[ˬơ7N222矡S???4|r__nݺ }R~Y^p!{޼ypCxd#5 QByAaׯ_oܸ1::/2 ''7=zrFLٳga 6 lj!gf#MTUUOCC͛7vIQ(s'rpp Pz} mPaX)EǏmcc#bPRRe˖]v}rӥqa뷴*5N:~=MP.+PLe߾}hŋpC䨞HƇ5 777*ihh l;fO4I=,r} IDAT8N/h~S}}=γ% ---Gٴi۷o>ѣG@@yl6#0BBBjvvvA6(@VIIY=̒#ht[t'$$֭eb>{bXpb*1118Ν;f-,,,,,RSS@/\%K.\7dO=bcc'? ӧOLNN.x-?5rH'mmRhihh0qcǎu$̙w > #11ʼnG mPE fhii1Y^|hݢܠ}'b„ Њ xG9q e|S~ᇟC zKBxϞ=)eѢ/_~%{plj#ֶaٹsd}Clj0D0FDbll \fHGPPPNNGVVVAAASNͬ͛VUUʼn_1/_x}! 7@A-j%$&&޿ڶ6l@ݻw߸q 0E8ѿedd@ݻw2'm;]C[[{ݺu+WD>FNN;FPhٲelMΐD}0aǏsssܐ7| hj4lj0$0ƌDҫW/ڳgOf0HbbϞ=˷_AAa UWW߾}jjj M(+Blmm_n_2'a_~ 픔KKKf0BZZڦMz,k֬Y1 ?dY[8???$$d޽ |>}_~՜h"hgϞ=߽|rOl۶m۱cGM g͚%ԠpP7nK~^xDYP[[{-[].44T5ۡݻVUU0`ubW^{ڽ{&v7@#ٳgNHH۷/z:,,,,6olkk܇Ů#[W\ -%˞HFx<މ'6lؐ -Z$//7LO_@GUa_^re+OĜ*摅yg׮]ۯ7=C C555׮]6a^B ljKѣG~~~ׯ_ۯjժԠ#Y&99ͯp8_~nX,tA,))|yy nذȨQϟ?u,-h-쉐-Og|bM6-((ћoF GO=|hBW&vڅDVzd|<4;+,, ݳgO}}=GG o;;;0 "=lj0ׯ_1i$'aÆ4:n ۷o+++> A8qH_F(1(A6(t={ѷo ^۳gϐ_vpp[2l0H+VVV9UbX쉺Ysssddd@@077 ={vGhܹs5 QQVVV```DDDSSu?ASL W0D_[[8q_LrIh:uˋY=ⰰݻw}4rP'''F Hmm'O"j1b#55gii޺u+ e |:ڱcǶm>rss >|x'ӧh('RVVY1m@oZ,K=D=544ݻ7$$$??#`//N}wj>>>x̙3gB4**}ώ;N8'8qĉJ;;;~ᇭ[߿hiii|̝;4; GGG0Dݻwmbdoߢ1k,11o߲eţF>} meeef`$\hٓزe eb==v<<j*RUUիWi<'riӦѥٶmj'{x"?A uuÃ;Kuppɓ%⻸!JUuuu({l%##5Ynݺ湹Vr)Sܿ lii9vƍSSSoڴۛf PWW h+))I2((()7@A^[[/pceevqܹ3f̛7eGp?WXGGwŊJJJ$bcc\mWWב#G2#Cl㵍.\PcQFNNŋwYYY~Z1NSLK!uV06l@s\\`|) mڴnssUPv^ҥKׯ_ON5 ׏GYADU޽{Faa!@ll,oߚst:{iQ>}199ٳgRG2E}}=JJJH(NdbbB nFsssn঱qfff;fϞr_aggCǏ ӧ;Ǐ%} q"7at)fΜNɒO>M<9!!Ali8L4.٨O$iCZϜ9vMVVѣ&۷]+pssC CQV#12KVVjEEE8N//_D#11ጤѣG]j;vltJף!iJFmllLlܸn 񌌌ttt ݻ7~+ի˗/V*=ztEh=Y=BD<[0aBۏΜ9,4haa-'^[le0;޼y3lp2' >ѣmD˖-k%%%ww!C 2]p%!z 4UFZF ljޓֳgǏSn߼yM>X^EEu$b͛QTTwHL%''ʼnwN mPfׯӧO+VݻZ@UUzWWWv.>pdr ]"cX ?~𪭭5kӧ$I0zgg.;wꫯc7nΞ=K>Lg˖-G9U.]twե_@hhhss3|UohhsѣGvIY,ѣǎj;JQà> ACC ܘ$''5 {{{'$$X,V޽_|ɴ(q ''gll066ߥyCCׯثW/f`$\>9pE\۷L'rrr|~ϔΝ;3A1قDm/\|,L)G1Ӭ=|?Z_߮b$''3cj1#Qnn.;… PyCQqg#1w Ce8pj5{P& 0JJJ1 '2.L/'R:\.֭FBIIIUUm۶"##i8v̜7n8qaÆ/=rwwV 0ڶm۶m[vI0{1chZ1#QnnѣvssKHHh7>T~6AN0 ƌ!>'b a9)--eƨȸ0ɜ-˻t"m=PVVr6m< f׏;F#BBB J.R]ʚݻ`ܿ!$f$5jnnn/^[`lڴ)66g#Dc"j5{beee555&R-BN0kjjvqqa2OsڵkGzȑ'O6+X k QQQT<#@y1ČD999#GGL7~wTo޼H91b3F~thq!BEE3Fmm-DƅDj\a(`TTTR,X68cA̹z#G9r)S JX3k T<#@Q( rrrNu=\"<ذaÁ޲egڀ`9c,+FO` uuu󼝝8 4ɜj\.YQUUr"'''nɒ%Ӵ4DO6AՌ888888 S͉^ܹs]J`z9JzW Zo:k׮eoj8S͉^^z1͞:-FbݻZ-}ꯩ̜fȉC]][wgoo_]]- 0HՖQ-F9S[[r"{{{!,,6xo+LbRSS޽t.]=z3 J&jFe G{2]6[RSS1ČDك gϞnj7kײ`۶mF"͉!;wd6YXX,[LҊߟC|NĞ#|Q[[ˌh9FQToX={,]T| a9Qee%2Iz#j[[[#mp.īuOy#x/nj3sLdZ֖5E&9 ͙3g˖-%--AHR}]{쩿n 0g>S}6mN";}17'z;w <#G^|?PTT+III?Y+vtpiFv`ee%̙368cAgϞL뿆رk,GJ:Du@@ٳ yHbM<ϛH͓tf WWW1#J0`?s+|zǎF"3ωB;X[[:tGN^cuKLW:Cuuub+L&f Z 93n޼9tP=<<_S '/t: 666 MMMjjKKK-y]͓SN160O~XYhR-biiiiiɚG<-|G fvډ̶Jr劏ϫ DBr0gjFZ. lcPٖ4빺;vU!… lyq->\.[wXXX̝;؀Oi)Yffկׯ*tVZŌ_ Osˉ۵k׿O:vYf;C|N.V1ZiDȉ^78thZ-Ղ se0sΟ?67n 8e#@Ag#GPD%FGVV3~9E BlqIGٳݻw G  dpwwӕ[ZCANd8 l8 9`hZNGL&BCCiy6!̜sα#,X`pWǵl0od2ǩ7:uɧQT"πdzzz/bLꨨg#' ƍܹ3=z{Xf1!^q+5=?ЕTs'9c#E$_gϞNuPP… 2_څx.#;;[nb3--MlqIrJ}W>+^IDATF"ȉvJ|B&o"L8*0sΜ97P=qİ01=w郩7zi,>}*ȉFNN3233پ"^^^(DpAAAo6L [J W;q3qD~ӧI-2+B[JNǖ(j ˅ڐ*' G=}T,cii9k,x3ۅ0 'Z$22򹜈NDtB]tiʔ)a$\t<ʘ4il"8Ɔj 3 Dqqq3f̠[n 4HڒѣG} 6lڴIڒ=?uR[[ۧOva˖-~~~{6JuڵիW/*JF|||tt4!!!˖-3+Nrw筭[>ͧ-4{*D".^a_/EZZp |嗬p1]eddZBM?dƈX0!!}h>ϗG1c,_\b(!!!{Zɋ_P=cƌUVܕFa fNUUU]]Վݻwir"Q\\̌־FNNDˬ_ӧ}ʌ3 ȉٰas9QrrFȌ3Զm[1] ХKaB01 '^鞞RW;vdҫW/i+f FSSիmll W>naadɒ ~ѶiiiR))) K.^^^b3!q@@BcѣG & rss1lqHHQBRg#RSSB)))!BA>u6B(99:!?Sg#Sg#ܹCuu6Bƍk׮Qg#\BJLLF]x:!@pu6Bܹs3gPg#N:EF?~:!tQlÇ/_.._m۶Ν;9sãU ϟGmڴ;))"---##JPdff?ʢ~kQ) JբΦ|A jjjZ "77]UUP(Zԏ=oQWTT(Ǐ E~~~!TVV.,,DKJJ EQQQ.4} -'O( 뢢"BQRRҢRM-&%%?G>!$>>^Ӆ$$$hZJeaaAT*B:NTj{{{VU*4+T)FTjzZV*LWUU!J%]I5᭩IT6551VtJT_ACCRL[zRiccôӎJM5mVT7fjjjJ%}jzݻS+J777{IT*oJe>}Sg+0M"TJn{F5}LT6ʊdر111ƍ[f x:wa:&''{yyݻwU:|||N>4mڴ+VߟNYB}=|GY]]]Bs}7oرc^`q\>,,l„ T/Z(88?,Y2yd.]:e:_bө^r̙3={6Yjܹs?>?Xz… ۷oooohZ|XbzEIMMy[ӥkL奯ZÇ &==]_r}V322EϞ=333{SSSVVzبR[CCCvvyu}}}NN޽{]]]nn֭]vy葾vppҥ~vttܹsUUU~~vrrԩ,((;v(,,...:t(///**rqqGZ$1BlIENDB`./pyke-1.1.1/doc/source/images/PyCon2008/bc_rules8.png0000644000175000017500000011276011346504626021077 0ustar lambylambyPNG  IHDR{Ve;psBITO pHYs IDATxw|SU4m{C[ ed^ (<tlٲN .ùݝZ޴i+ yjI<rssn]]ĉwX,޿?kahmmG@"~Ea)wcyپ};իF~7 ȑ#ofX:BvppMõ;Zj=Bwvony[hqzÆ  h>>>)))V|cǎ-^899#66v~iUUUoǎEh4fbΝ)0TBc$''cv, U񫯾^N6mڴi[PfkgLRvZ:>cƌVӧOWP(5kHR])--wC(((h4#Y.*a7^^^@U4Zr;ڑk2gZ`맭]9qℛ| "X֭[<`,}Xk݃#ܼyJ=,/끪h*x s/lqyF޽wYZZ*HO:5f|;UW\W/.qƼmf0)7Z'`?tzaaep=PMbYf_.K]EJR[۷໴ƶt[SSƦz{H]p!ܹsx\ sS(ͷn^zmfy\TESGX}9zr0(bXO֮]/XB  Ny0 .ܹsgbvu} 4P b/ !zQˏ8{Uò*++07r_ NMM%|1)))Va322RZbk@%41hѢ L)+c*;Mk9rlKm+=̙ctLd9е8l:h4w{wT*} 0^r9iŜm-%55Uƶ7n`}5戝PQQѕ+W$iŝ-^k_^PP`f?cɒ%!LiӦiӦAkTEc\ =.I+e2ӊ l?F:|pch\uQ 􌊊:u;޽ۣGm#Go9{U/{A\\5f---ض1_1XMV9Zd gp.P MwI1d ٫bhh(dŜm5[\ wúe8s 6I)Ś>}Yfa9}u l*W^ݖvoy^wZ1gۃʺb BÃ5///H['СCfF8,WBHeܿߊػwT*52_~byٽbar,a͊UXX=tG6ƍþkYR  ob]{@U4϶m۰ JKKMZ~7UAy OmVBM 쒸f0#2|bQՓ&M 3uuuǏba-n z]yfK*⟽>|쒸f05⧰OOOWT@U4[kkk޽l=<<{=P+"h˖- chqr ~__> ??U(_C.X*5 ?Ŋ+i.Jhcrkkk+--=xEzzzfgg[^U`9wޝW[[+E"QqqVX; chqş8q˜ ҆ Fn466h4,k׮ Fkll40.* +\.… %Y72&SbL"R\~=H=z8yu c*hq ߾}BYF&Q]9Cbbyh4,?|\ TB3tuD=tE:}*ZǏW\it$i?# i,^ ʘ6p`QW'Nؿ͛7kjjJe```XXؤIfϞf^tcnڴ6#'|핧377׼|\ TB3Ǭ`lϚ56V$HN|OP(IIIRYPPOhy Eaa!>NSd2YQQ> BH*S ~BH"Scbb)b&OD𶶶2|6m߂bLJ| ީh|l]-ooojj| 477 |||ߍJP#Gd0v ~b-6Mijj"boPB YBbM2B())GIOO'dggb1W\! <syB̨Q1'O$L0;k@~3{lBO?DYh!$,__k?Zjq:qoݺtRl۷o#LaÆz˘{?ԩS999#GܰaJ**dSt:rP"x{{Sx<>Dq\`CCAPPr$22p9XRRBxOyAFF>G;wvAeff^zrS @tҨQ)Æ ;u>ٳ&L;p9xiӦS&OS:4{l|ʌ3K۷oѢEyBٳgٲeŋ444\x1'''11q }Ȑ!Jre"f r%|\.6l>`օDGƧX,;'\#G)SL1Z1jڌ|tcL>>:uرce4Ͷmz-H8q͛7 |k׮wT=ZMuR1>>>BA $rB BH*b!DB}* 11Z[[ 1W[;Cxņx¯ޑ!_|P{Ʃ~WB%%% LrhEڵk'NLMM/kիWk[PNN ccc_{5!@xIVXGݵk477Ϝ9S P> H.]R߿_$JrƌuuumŊ[lTݻwYVZ(JHHIN'dJP)ctoi4!ߟC 1N!777BQBNcB«;'!!E!txFX,BLdd$!` !Nc뫍>`ٱc… 񯯌! Y,Vmm-!tH￟9s1cLͤ0CЙO6m:u*!Ӈ zǏ>|S_l1c DDD5l0&--`L޽ 0`СC ƌ5`̄ =)tM2ГB̙3=)nܸAhii)(( 2(222??>bƸz*~frF|2cċ-))YjՁomUUUꫯb b֬Y=ށt2cEEB JJNN~>q .4/ûw.Y$//oӦM&LHLL$4)n)V!Hwh-?0F B< 7޾}V^lٲ.ݻ7Hلb_K[>}Sj56BħT*«e*ګW/|R$t}R(ܹOqssKKKçd2lw-ѳgO|D"!g2X|=|aH$\x`XBB0&<0CKK af\.7&&f…0 G'L! HիWoݺL&$%%%%%M2fgR:4c +6HDfϞ=t[lٰaťuuuIIIXY;zxYDP @{V>,H,iq) 4$gOГ233u{E[`{n˖-]rGTd2F0[Bӵ/ 4߿`0 `|#F $p1p8\.dzxx0L2-.p)] ܢ5:kÝ;wZZZ)E*:: ?1֙lZ\ZBߟP`P 0ìYf͚eId2t:ݪ0)??0}%Gd0}=Xs,)p@ErB\ry";RtkS~Kjw)dRVlf}K}:2gJ/anT/͍ uIJ^7H3"×if}%-;F`OdJtFmR+Er K3zOpRX{vhPOJlF^+Su[oBWfCBByqK:z RJ&7֓Ƽ5{7A>j@ ]FrWEIS):yHUX.ŠHhbb8T.fɤ@/> ÍL;oFp0#p02ݻ-W9++0rvv6\=z_ǧL2eZ\UVa0P(,,|j"ZlZ\#F<5IUTHH"۫0gϞwӛ:uԩS-ɁbV|t:a4pz-\p…䐓C*&OLK-.wšXxk -PϜ9'HFWi,((01c020Ç-WYS\[88LF`xxxث<@)BCCa^e`~y>%??+/2>e~)002H$*++ç466j7$Jٙʫs ʄU< _2i7;'71+LjQP{Ԁnoq_9z lR}y gsw$ы˄5>UOl[:fu Sdٓd=X=|DŽ>Fv@%4Iykͧw)8ܦ>jZ墊 ~|pHC2&O?Bra8q)nU5iU0.>~%2,<|sW-c'͛7o:aO7G6KeiSSN`CԠu,77Z7*]Fr\w8w>^vpg۷ 3?%IS] eـ. ,80Ǟ:7QTƚs'8x/0L&ӏ?\SӄƶjdJ= e^=zmbUq)$Dz3~*8b&m!BA¹uϽ\s%]ons.c0&}zV:KA@jOU6>))EYRcb{{jYq^1nJP_␐Ƈ#.Z ޻%m!!ҦEѣG$iĉ*ZZ6U DFZ6+m2XdP),7z3%'ߛL>L7*ޡ~O*mʭ~]|ݻOJII_oCn?ʩf(>sA1\ o(enx'D& Ɋ`JrRNFIsLyx 9)j؄!ĉƶ}E&mli󢵊d]TBSWZ(ڟX{ZS Z$WֵJ 5kZe |Hɕ ^JU= C hq7DR_G<;;vy3)T]Qշ7ݙ?"pR[K $uM7+Xy~í{tR&Qʚ%U2ߗRV}n}Chx*>OʿTッ>1_ aY)!JJNXLdlФaǂʎ`tm;y3ƐU8o% `ӈ\s?U~|qnskR[VWt޿QQ/Ow/bWiԞ>R[W7=5rLLLӜ!t M^n>=}NUxJ,o+,i4eOJNNX.={^\F%Gݽ]:׋ʖ*D%(2NL sGTb.U%JT)nOQpiM 8I} 492g`YfȜŽ_߱ԔH|s+_;(0JWJą%k!ByrNZM\A\c<7*g7=fU]Z溫b2"$2B$vD&!2T55jFRTЃ22"HNmqj/DusC}!ܱҎ!~5 (;>:f=N1i;}VTj{eonnz3KoZժ=T^៹ ~+;[7JRR zi߽/`|<=4G}V:ŵgM0;GF2e ~Lv7x7IgK:n''ELrĝ&B]{&97L[ o! +kq뚥JI6MnNq5Bќ\3|w-) >\|Gl{Ӏ:iwkOk'0s5fpUL 7/ObNL|Oa]$7Hͣ6n=u9{%c֪Ɏz!{L \iޘJJˍ7;RYBHR߫k2rkOF0bt:B}nby"l6X36~QvB=r17zTaNF]{@iYt (eGJ/NjqCBwgjSBFL8{%;{D:/rhb!4*u%H ƶZЏ9+_$uB\_yFh2;}xky{Lfxakuw;y4HHҞI*t/25ͮ/l֡8{%:y:%)L>i_}ۉ۞AITSc݀B1\BXUB`i(6dZV>~Ui_X6IÇ\mZJC9oppυ|wﷂ//1A1aac샃3JOxlۊ>u5LM!IZ D)s\/vQŘw}ކCWB:&Sɵm e'`0:?9q;$:0c)ْfaIN!Gb|X1\poO/~Ԑ'zzEH]c>=4dDJ\ܬlӡIãGDž)Nyw]ɹTä2988O@G2lȎH~Ýy=VIczҘ)#'sk*B-{ @@B$Π1nTBw2ܩnOR7wG+:Un /;3v-+rJEcb-@-N↱=IA>QR6 !ĥuqϰWc$neg Z a7ߤ>#cBMFF74j4J6}˞w)ObL3WBT'̊NذڨhГJѴ_dۿ."?7iL4w粔6Y̟8Ԥ/9D:&03'k%<\!$i4ͦ Ey0/u6DR4  *It gJy=5 dB/mcP)=kO x20W 6 <UJA_~,]vʓNtyYRRrSobbbB2ԣ.߲)ɑv8ͩ5j=y9}Q,;O%(e+޵/6|}Nxc8 D [ [sGEU= rLYSVwu'}χ=7C%ċ]d1jEnxPVM3f>2BƦTnFk:B=OS8I5GbR͈݅&D ѶU7u[\[xyyyqs'C6C%Jؕ^ɿn߬_4EIw(uuSe&*xh^47ǼINj!T&y,,i괾' + #w=4@cZIN1^}Xes^71kFjt0ZRR0rջ+sXeUP!2d L5lr0PA5/Uqp[wcY=\7vkX~ٌ8ۋOD7g}-[s+VlkU7%>uZRvWJؕWߴq/ԥ>m6dɃSXjYA1n7=ӽxFJgk hXZ0#I,ep5MJ lUշy" dü=BalPǺltˋ)iֶ)nI}B2ûF~{,4~bJD'rTk?銫ڏ4Hݟw_||g@I  *3S<p[$MPKkLoGљWKc-S@%J/`bZ.Vgc[.ï_r/d#BP&55=5;7F!l[#{ń?SDB\v,/[vt.ij^GDžG *Ш]?ڨryM"٭}n!:$ЬaY+έd+!jگ.8E([vËןKnvHϧxɊ[~b[DĈx ٠{'FӺFv]/޷̝:U-2H"u9 WWP?fpP2y~m1A=fcsm+'H\&.;cۺ I@ _ƿKY+'E RLݍ}f|G7+$n*RFFFNNL B#Sj%B\ǂhvo=TjD#SE 6:3dr㴷27O_olN`l6vF\w_WJ/vw=,^ \;Z=eu߾}z6쫝JIjð'B|͛3Y [!3 1rץ{A=8 3Zyjniӟfc-nNup[رcǎ}jF0ɉn_GGGKO zGePq=hT =T>s[r{mgIn|]Wgȣ[kX"wrp;!bbbZ!T.ɝ{ eԋPN]:V7B^4|ʍ3Em,e4-uQ~y$zB3ѣ'31!=Z[.߬l?"VP+-^A\lemiiiӺc^ez駱ǵA/Xwc+.Rŝ==~̙3 o~dji1;G57 'LDB"UǿVрovRжnoqx_v0ݝFQrQ$-l_.zJsn]3$i_ ݏe x47*ERKFF(.  j>P`Q77*Rl$$A^:y ;gxUsWz.yƨ~X76%U^zpuY#BMDrXTFB\¯8Syu[^j$+dUϹf UڅӫE #xnxh֜ ӂ<"/k-7=:_u| l|gw)$ 7םex+:Q]^銫Ox5=t:sYucI .Ɍ{D;D<_Rs0.7*e^zlOCJa}7ORWz8AO_VȊDr%j Z-"҃sQoy)!uK-;|ŋȍbFY,l*;3𤯺ZIe/:M9_u{gƯ2?zǢ{o7gk )t ma8*~?}^̟g=h']'!ҰЬc7gocؠfݻO"̙M`,̈_,rv1&ʤSYV?ǘGgVE5BqH$ \P5Q)tj3mtǫ nZqOP$Hemr\t Ih>L#6v>Sw ;\}qH=eg?'!!W/0@wĞ1?KknȄ-R!B`qXi {b̊Kn|{챰V,動RFEuJ{6n|ά ];fFK9g_ήjoE Ls0(VhwDxیdPn~>龉fĎqUTB"Y!_ ǃ8UqRu^A'm*eTǍKcdXGfE7w Z\-ۀɻrЭ P eZfxcuFP cBnuiyMjqOݕ ½V>"YázyM:1rUJ!!Ґ޶A S,=t7SݦFs#[^fyk̫lp,555]?s~Z\aSjY}^eJ%a 8}o~)@ u}ϫ 蕣׌IlR @H}0Oһ uϞ=zO0jԨQ,)(=5g :*噎$'?5GP(s 8۷oۻM>}PSSC*nxfϞ=m4| > 0Ȗ)JWj L@?)vHE٫4Iܸqe.YOP:{&#XIMM$---UUU֢=.pYYY02-W9 jm*믋/Ƨ̟?DFPr| ̫ LV HTj7` &RH6R$WJ6W*{{ЩtjDo3cu& IDATQÛ| iwq@7r| o.ۓat7F$7Je* ]SpC\-JX2'oql #A$A"aLJJ"T~ukٴåniH.]Ѓ撇͏n1[ XNW\3ھF}^1U R1eBMNǃۛ}nL\ٹB'(nb)al}w#F1b%Gٳ*D7W8e#x8Ev?K>妧Sº|KZZ5\xKO1/+7 v0?FQGKR`.bbAo?32WՄ P+ 88"~9ss>7 xF۰j+9=}XC:VSwC)q"Xxk€mω}-BW|K!ՆvF#SYOs"H]U$3)ɑ3RܨPӼLb>iq#8 tGȄդpcBn/Ӊw8H{XI7_zpʜʘV'@_lIS}½=r=_;. a}*ЬP@{EFI˅2Z5us!~I5g0! DZ|e8߿r >%55u*8 bsMP10֭[uU/ۄN+020 +++çEDD3? ]U$ A`^e`?y\nKK >E X0 _~?>>e…wF0#`-.` ԕ+W Ajj*" 4^DݶmСCjI}1c^eO2)*JBR`DE #Xox -@ /_* pRyyy7>%--mȐ!*8 WX*_~0r20I}}}II >% &&BnnnV('QTfL&k笅~!1U:z*By{YF 444744xmժUT_SL /OD~->ˋB(ooKSZZZv܉Or-§x={S o|J``ܹs)+>%$$gŧTUUo3fS'>%66vGS&LO)((8q>%99yر>}ңGQFSΞ=>|ҥK[n9s&2 yQ*00:#..Τ/BgLviCee%ƧF6`hJaل&B!J)++#b 1IIIF(0 !p1QMD*b1pB #M!$''b1鄘BBLVV!&??#;;3rHB̵k1Ǐ'\p3eB̩S1Gb.\H9pӧ#~甔³Isύ3!DRw#t[\c˥WzS"0~nԩ .kY歷ӧM ;q 'yhoD`$BEOMb2\/BQT lݺfcƌ Xv!_"=<_P*1Nb !`b<== 1Bg."vp8B!&88@}GB"Ąbt{NFFFb 1111TBL||} >F߿ǘj02-WC),BStl3w2$CN//n!c( !FJ7 u1/JhϺw=zff3BCC.d2`%lZ\`Ez* OJJWa=[-@ -@ U̙30cǎ$UUUçث<@_e`G`988x -@ -@_eƍ* M8ңG{]Wx -@ -@_e;FWyҤId2\TTTܹsf@}Cy`^ehq[hq[!<302Pddi)* 2` Thq[hq[!}ث0肾-m` -.` s D¶/SƠ_%̫:*S-^ҿ{8gggݻ!$ .]z%{*Mٱquu?p邂@4ty 4Lj'֭B6iҤٳgjú0@*jw<8m4 c&g`cr (ʵkڂ3f?~]pͭ'ON:U&gȐ!'Od2 i< i山JR[UUeA \ܪUm޽wYZZ*HO:5f,6l*W[.qƼ]aaaRRҀF:ϘROFӧHLLt;;;qbKF,Yĝ3.]y 6v*x >c̴4<V(wO:hp|j:0qF9]tܶNIIqnyv>؇\@]e0H=gʔ)_qQ\egx厶P(xkNsd2ٱxl6H~fX;9M;w7o=uC rH\p8Vu#jz؆3@ǏwSSS}2ȸ\"av{ O`3d\DFF::.S>lo~ȸ\^bW pa@G>̜~>U.**#W\9x '99cq3B{;%%ŝפ 0dҤIwBeq_`2*0FBj<`$W\)**O|5k6Z[[/w9g,XaƕH$yyy}B7|9kUV9^^(L{e\;y$6 /‡が ?VWW:_Fϙ3+//GLH,_~ᨠ~0͛7k@;v,55'Xp}۷C˗/o36ͦR EwwK;m۶czgH}/s\0xw0gq'~׮]zseiӦܹdz4*#|q_XjUca{&MJg>qDQQљ3gz H=[oER<6gK.}br\Wk\;Onmm5!!!wygDDpH$FrYYY'OId\ qo xU~?4؞0]x|߾}؞ӧj<)`=.ܰb2pd2ns BNhq'J1 ݽ܉h$i8bzj4Np.bߺ ?`0pF׃#ƍqEEEE\̣>Op1> ./s=-[b,Yٶm.f…1ݻq1b&O9q.ƹٳgq12\LRR [ze˖իWKR˖ߎ, %883Sj.ZhٲetjnnbV\_;:իWC]e0HO=g„ ;o}v\'d\?||0555''A~7?$fX w~i{d2=T*2.-[lҤI>bh48qtzqq^t`0q>na2g`1nưX~cl6.8D"f=CJnii~iX l{O?tڴi!55w ( F끀o]|9G,BAc{h4NxHd0\hDEGGgfffee=q_xwqUo<]va{233g͚A֭[,OIpppgg'fN /*?䓐qGq<͸ ˸*`ȸ7s\AӅB!dj0`_$"s_Xf n+\Μ9?c{oV_ȸ04-Zmíg6k#D~Hzp̴YQI? FB]OVJJIA|ٚsf7/Z3Q& dQdzM'BcM_{SXx*wjjP8M/UI:Faw+Z6dQP#`dQ5xVѴxW̤RbBȸ/書?ܪTK{6 H aaX4_Cum}eرۓ=o{qF"zYgp|=0\DLzI@A!3($̡Q|=;,F{B/Q+)Q4t`P*vu3[mkM F$ x\@%=< Mz'Hq_xqsa;zੜ^z ۓAV\r!Q;v̹2. ?FT;2ȸ7@@Zݍp8|>W#T{{t]wX k~;_z~<u_~ۓpB{ZWgSNO書k֬ ;"`^F"$!e&$;d5fIۦ(ሽ?$~CiXAa`-6қ-rFw!# ٔF RUҚ3e'Οx5j=wƇȸ^o[l6"UM]-JJ$-?d$ߏߺԞtF};d\Mqd&Ʒ65Z˥zvLd^b丈a ?U-T-oP:F0Ɍ 0%J Pnw?(d fg67z*I":lGo1 v1}=@`w.IF3MV+HGa\f{(zrIVLZN&qi(;58 %OiܧUL."}h\dЮ&قRL[Kk6/J zh#-( /j5]] zQjX;Rl"/.n?O1YIݱ· QEQ,VŪ1U.^vcwU3.ifܦҞl 0oOpID?lIA=- ͆YQF,Nuf4|@)ad5~-kV,I{w{,%-NZ Q)Ql,bX(噊JuXJKKcG;]u8uMrn؆йViYGwAlhAlsS'$$`?轱N8m(+pUby\*`1 riwWt׹8ʨ$qYbhnx۹ [AYmև J;?kQЩy4}GDGGEP ojiJGѩBy"N&6ܦ6UZjC̖ZW@ e yl&5UjJ͆PIĜ0pΎ5t8"]j;7w#Lzjb oXwW6j\*;w}wF<6~+_C-;0yϭ4]20"!&8m^ڔpv` rdr_e={8wB~aվ kךc=:>sJ"PT>ğQ'ShP?\u[rlR8xCddjA|:u>:TZiI"&Gx}IKKKKKbN3~URu1#uw+xy"=W\Vp$ 0;[S. mBJo]l\-@'*b{.O4v t&󮊦{6ljM͘ɷ cRg7L?7Y50/׿$z2ջGKÃ%Erh,C^h w]'\/l}k?UT;3/oxDqĎvEA`HzG;q״:Jv RηIu&3BU %9DωAa&Gyuٟ\=0 j&>5Zs\Yz&LIM*t\se?,݂ҙ u_8PzfKL2/`0Y{&Lif.4)4 *{CN&]{|kXk:L ,OrnzK oК{m<biSBsL8no7紟# B6 !hٱW?jv3ȸ {Ar걝FK^%GQHnN(K,zOE907z} Y%ԭ5"S)5XR($"HTB$Iēǎ~Fh4 fᶅ _YH$o<׿"$#&-;1XmVE.w6u38bS)w{{y3_Ҥ {@_J&揙64Еڣ%GZk<˾/pFtlwd·b!w#q I!+,Wgd\l"tH  ҂V*+ﮊ@>n.JqYcjqzjwYWrLp]3dt&N93#$`qz ۩؅P(׆t ;t~jƭU4jFш=J$ bVaYy=oِmÅo&6>\wF)7(zeˑ36fENHCP^ *3)djƕh<"J4=7Tw lƄ` [t]޷!txlVrZx詾3ȸ$ ][%Q:ؙD^]H"}~GIkb\1t\}ǕQHN5tf @7e nXAKgуX[cYkcu҄9,JG;{Ϗ}kUm>wj/uiõuAwcZWq Gf^}zg~, יc>i. =]֋[狹ocX/::dwfKnw*M');Tw1~alOap˿^+7?877AV:u=MGl~pP$W|#ݲ(歋zy$2+!b` *%Mg+ʌkdBMrt2 =rqrtpV!ɌE)Ybai\!2+';uGzMz.†\Z õa\VFglԴQzb7[VKh"tWWWKK '000,,lhˋ8;|ZW>#b|0.#T,txvg;W_󷃯_>.C и 2]m6;LhnأwzϦU4,Ch:o_qsx҄B2Ŭ 1iUᢴ5r,2&6dS5 ZnP^՞(;vtElL$Gu;yAߩp\|`_HJJ d\V^aÆᘮ,bʌt]jr|dPa2)divR"?؉{WV& /ŋ "^(b0($ hX4.vLi!K3ZC\RNNjx9hG$Ū34VJ*Lު}aXSRs]vg0L33֣{gӦM#7Ǐ믱=SLnϮlO`{[&-BHnP{nӻ6|+p}dŸ讆~U+>I_CPWiǃdluJI~Θd޺-vƫKa 3~Cwl; QXel:*u(H d g'Dk'GK%IKT6c&*Z\G…jrcqJHϏz^Į#G[K.H* n!$s4n(qrhΜn|`IץRIeew}M%˵ffPY" - ~8sIp7[|p˙fuT/טtT"A!Lnx7/ߒ$״*TWMdIl*9#dܟ%fReDQ-U*lXH"DdR\%IdC,.1L$2($6cE 8Yb+䟟 ޟ0 H(mIQڒAG Ǻi^MC2*"L ˛7w d\pC0h'F vq.LMpn n;H};.˾*_F'd\7ml9_mc~]ko ˻b.<|ܴ#--ǻn ņ ,fA7tl+9^5.> vɟ~)'))Ӄu]w?;wB~aŊ.j~TooG 8ÔnBk ުW 'd2.`9-BV]PxqF_UaU_U5p4e& ׸У###\pAnw,ȸ/Z WWO?ʣ:9rdӦM؞n{=P}֭ީ |k6/^ uꫯpu?cȸ#_|L&{q>ȸ7@@Xd\0&EfX-Up!:!lfZ8=I߯,&i!ФnpPbSAy}|Sl qaJl垜ʖA%}= уFz|؞X_ \ƍ tv=?*/Yg.=}4'66vر8qok  =+++= ?^WsҤI0s e˖Lmw߹S|؞3fytuyZWg PW۷*qG~Gl@ `pqo xUqMF!/wʭ0EfI 7+4i!)!Al`zDEE͚5 ۓt&u%NULqGFפP7){ZEO #SL0 ¯jZ= .# %%%؞l/3eD2he5+5wgō q k~Ur^ZG!&/id 7g=*t9ځ,”`6 whFڤPh" fKyg OJ&~M<;>m*ު@0[r2. JCKZl6ez8`4&L"HB+汮 @K.a{bbb]|D7#qld;"`@@&i$FiAlz5&#bҽ9 =/^`f-Ei0*;١Q<:էٳ=, 2. ;vZ <5gΜ{b{<=ܹsΝ;tîKSռeBRcIDATT&/:>NBCCCCC}= 0dpmɬ3YUF^jZle&D 8o $:$`P.3'LjSiַll!K+&%xo/`2Ja.NTf1Y_hX ٢d8lBo+ =*++KJJ=IIII#B}BIh:dD*ȡQxtj0e yLZ2Z;/wʛj5dFSRD ?k,*VPLu]* 3|ȸ/̞=h4b{ӕGv܉+\XXSOyt?ӹʸ-Jצ9,VŪ1U.^vcwU`kfܦҞl 0oOi51*p]6f'|*?ua j0R)Ql,bXlLED:]%#A#>ZnEU+S*FZ F Ѵl#IQ!<N'VhnSisNš feJ%@ e yl&5UjJ͆PIDa0hqB㯄˝r;2.Gڵ&c=:>)A% *AMNvV4u.:-N]96)rˡQ2B2BT5y b\)GmR#?ʸ0WFnGhKc8[e%-{N&=6!nfƇO u&S׌ )Ѯp$z$%%-[ ۓxJYg8kZQ"DoLfPIdAJo,:!za`vl͆ ޽{2y>|Af͚5k֬W0'4Ԥ4t_]¡QEsWN&光Է#LkY, +l;nڼ|0i$_x!!!H F=U͋cp* |{Ew}q=$s<Q"6hopbl,ʔ=r]D:qv˰ }_zp Ԙ]S#SȔTqL76+Ddb?ZUC.:G[s} sFQHuU F{Ar걝FK^%GQHnN(K,+a3`C6F/dqYTq5˗/c{RRRt6r&;uئňY_%^61YWW\i\k lƲXm}[W&۷oU^hd\ СC0]xdǎog…>Gٳgs]͸1h$KO.}45XĿ)?*(ԽU_h3qoQID'υps=ꨤ!꙼W>kM@() i RE\ݪJzL"4J*L ֝!WXmת*%=ajb三@@qB^dcN… 5גVT126c"%&dZÅ6i$}_N;{͜Q/S[ _e2Af<.C{c6aNh0;Clwj5t6T9*\2)2c{| C#ℼX!㮝RPil7mԀ_p (2+!b` *%Mg+ʌks=݅Ǟt2if~IݻG.N X;3*s cPV)BHe0;VvOv늏&|h}] vH}N[*blb Mg\GrqD,]]G^lUK#EI!Hc4tNŻ ݤPwAźx7A 岂X D!kF_%?Z)-d&G8(W#z"^N(Z)42hL.U(6lћ,ZCmdue کБo2d\L܄.دtu&_5a6uF#Ί p}dzS~S:Oj`* __"$ B^IQ!s#noql~5ȸba(| @ NpskOr^ĦqY13u1/+ruVRYt)d\&Md4='N#뫯yt;Uk'`&FO q4jj055xr+w+TI[Kk!Qz WWyƌ¹s砮2$DrlONNFjrcBޔ w.ȸnP]TgYpM.~ug?\khAp9"r>E<^5>m 2.HOO_jgxCǶU3#U/wuU~ĉR0Qx[o0t`Ρڶ_.Qp[/ _j/dffz` HegE-Odȸnt)o^^~7@*@ .9rۓ=i$_ -Uoo{!0vX]KJJ`2_|ElwܱfrAʐq֮]Cv<`1(W" R&G 8pڶF&wthoSaS2.())9x '77npH_*?q_؞r xv1PT\.Ţ鵵?,ѰlD"ܵ~!77w׮]؞O>2eA7f)Jl@rcZU*H$r8Oc,ZH$6=1fY`{d24d2iZOc( 4h4wxX,{g/_nݺ4P7tӼyΞ=[YYi ())(..fL&4Yٌ R\@q8\Lww7.F$bp BF\̘1cp1)D\LYY.&##꼼<\̉'p1&M:tSPPٳg.fٸpB\O?Yx1.f˖-˗b6md X,luyܩt=~MR95. yyy+Wܸq%F۴i̜ mmm ,{7m4)))t:CO:qU@ Gft:iN3 AF Pws[~=N߾t ȶnݚx[d>P踜 [l9unG~m{[$}GC0JpcͣD@{׀P(111؞Cgc!@&#p1D"q1|>~jo L8Ov>sqcpӑB P~N F#& `QQQ- FwuuYfÆ AAo nXعl6 ]GG}R+o 7&00ߘ~cƌoLbbb13q~cMo̙3),,7fѢE-rta /̙3'77wNFlƝ?'|"B-- 7Mx57Q!f$rq1 nx<\-U|>1ؘq =zIENDB`./pyke-1.1.1/doc/source/images/PyCon2008/client3e.png0000644000175000017500000014021411346504626020712 0ustar lambylambyPNG  IHDR^fjsBITO pHYs IDATxw\g  PŮ1|MfM@cj4SQ%E靃;,G+~dzf;>gg# eM#F4Ph4@i:[\MM_uĉK.edd888{{{GEE>}M0 vٜs[g9rWfddabcc 2i$Xl4Mo ._|ջw殮VUUj'''ww޽{O2GFW^y52 3x~A4XTs+X#W^]nݸq\\\l7k׮GDD<88˫,99y;vjݻ?~͸P]k0[0 =rnݺy{{TUUedd=ztƍiii#[lӪD[ӮVVVՋl2LG֮]DIX_- ;ww}6~k%-丸&N8CO;w2@uĀoѣGk6=QSS3gZsvvv4:...{ҥf~6??رQIom[_k=-v:tg^pޞ0˗[Ek=}SFýkYjUkJ3|xs૯j~bJr,o߾ rq6Yv-r,Pݻ2vX5k֬տ⋭᜚5眊ݻΝ ?˔1-cǎc!Bu{~raMO>Io[S}9I%3gΤ^xinnnYYYͩrJNW̗^z#J ǸY, ?˔NI >>Mb zٳ -¸Y, ?˔%77ӓ,srrXYYP^{°bgiܪZgT*YkF?؀ %K4]]v5[f,S~F3dno 7#:Qjww7VUUVʘ;w9/d1=7ǏģƔgΜy|,<<ƅ,h& ?d{뭷j*:d#!!!6lhm&4;{r8y$ Ԝp_ѵkצOpuuL&kN@s!K߆ >so988v^!Qtk׮mٲehqqk6c 0itvve^ܹCN:5#\/;o1ݛ>.7RٜC);wDvN$j@uT0[dd'Oܹ_]dIJ7`VJJQv4'GE"X,D"^( B;;;@ {V!x-bY9W ݻwLTgϱc=@pu2Ƚ%IMM5ߨRJJRTjZVk4Fju:xUUS Ҹpĉ'~ᇉ'7+ 2w\t/ icbbh|y揶ao (_>zhj1 W_͘1üYP0{lsk)4甎=jp[(--57m䁦Y#G,--{>y1%Y \{c`zpm\ !((iiif0: ? !//!>NkK$jݣ@4 p/ '.8qŒFg 92==YxҥK͘ꬅb7ZG`@c\VV}7))ɰrV\t6}wO>fXb-:Ύp Hz|WYY٧On/B?nrq/zٹs'-c5tƍ4__ߌ}\-\*tݺu'߿EVsgrgՀZaS(UTBӧOgZq/z=z4->2ï:...&ʕf~6??'hkJ0;v̭ 7n0qzV ȯ ?F3ydnɣFRT- TuӧOol|7pDҚ_U^zT^jڵqr2J0ӧO?M[ݻ5S֭[ל2[S]4|OӾ}qoɀCMT*|PͧPL6tP>k֬~ʕ+yyy*޽{/5@NFRIUUU޽g6쫯~zYYFd?|~:vXTJ[t>v_~ oizm_]~d0i$kTFW=gݺu juEEEff}.\ȭAќ5&h4UVqWk&ݺu;x`9I%㪪*mT3ie?C$oT~-"\^ڜP:a^xrK/9::6'Cn߾19Cٱc#׌7z<ڵkaaa-Mߥ9[) ?oƚFΰ[/M0̰aN<ٜ< [x=jjjB($$cj׮Ƙy}{!:v1sBSN۷o#17oDuc|uP.]0׮]Cuc|P1Ɨ/_Fc|%PTTŋqrr2BW^#bcc1gϞEۗB>}!4`ɓ'B=̋4h߫چ 1>z(Bhذa!4|pÇB#F:t!4j(BG w6dc,X3w\e91&ѣG1saYرcٳg,{q̙3Y=qxƌ,˞:u c<}teO>1~'Y1SNeYٳ)S,{9ɓYpxĉ,&''c'LlJJ xܸq,^t cDzljj*x̘1,˒0<,^v c˲dQ\eE ''eYrbY\3Ç,b/e{9Y}U*U=Ο?__B@VWWgddDFFi4ZaIMMh4dcǎ;wn…BH$ 8p޽{NNNYYY>(++܇'qcIT* /((;~=۟wN$h4M;_6fgg877Wӑ8///(( srr9tii)i,hLHPvvvUUOH_P`?qDx˖-/_jP]]P]]m۶,'''CVV BHeee~\NWWWӘDE$&AVK $&I>&++>'~{{{b;88 j5.d3OޘHb9=<>>t/Oc7BJ2++,OC␐Ҙ,P( ~S"#jjjد_z555U("233%[Br<""bJ2((חŋr[t>kzN:L&3A ޽K7o~Ǎ\ cOnjbqTTY!'75d?˲!DEnpvG*FEEcǎt?y?'irppx~GGǨ(ikl4Bh;;Eeddxzz;bKnns=WSS۶m_;`;A阘E!39lڴ?DM:5..k׮0TfǎC!q={]~x 骫&Mdt͛7w&%%egg/fgg+VU8jcophZAWcN1if\SS17nP(\t)GiiO? (--=p'Ӊ#鲳Ijsss h juvv6/ٚXRegg7ZT2&NB08!k;wN&IҀ}Q./^QQxד"`SSSSRRӭ[7^k??c ݻw% jd̃'o=zTE1 |JRf„ DGG :e>쳷~<&{;|r+WӇ 8dhFFFFFOVJJFh4p:tngggooiVkDJٳĠv xyy٥{E QaZ4//Ğ<Xz|7.c: = H\VVV22K!Af1yF[] !0 BAFTʐB Ў`d2xB͍ק,AT@@1qqq{壖g}GG-,Y!mt,Jd]eؿ?O⸸8all,Rawm5'!TPPHU^#@Q*T5fnnn5l )Ƙab,3 J.Q)Hsc8\#^#y3B~ \{E!Tietq}!5777* >(//C[5B^F:Rd5fC&"\]]`RalB ÐWR X,fΜ9CLYEyy[H_hxO?4m6zh>j:o\QP^!RX8`۷g%#5777*jC[tdI$T** QYYIqqq ѽ@qII uJ5>}l0 C; SVVvM{xxq@k4^9s?FH,JX8`۷g!'lI;FΝymPJKKyk(* Flr":;;ۛ7B(--q 1 n+ERrD"ԩS؀̔`Yݸq]t1o>FL5Ξ=$޾}OjHỹ⣹{{{0C !yKſ_˨Ri܊js%c.ĠXz=;;8{ ƙ3gr1cQ `uxyyR{$JF,^9sx„  sxkʝ+yzs*̒ȖY2''L!6xxxPat֍Z[`rPQQ h0jjj5r)JWo0~]C=͒kT* TVV3 dkDԐX(2ǎȒםVI@9 նrO$-4xQpP{;KJJ^#G Ĝ5:ړW*mSlw5إ 1Ѽ3f۷Ŀ+xÃk~hRHB#X/IIIg&ĉC%x)EJk;:L'Fy\"yt6VP(q*C!{;>#b TH$v.erttkDGdž fTdrSVg4."ʫL4ˋD"XỹP(> (>|8L5v2rMC\=gJ4:]*'q\% WG?g)S:w ƧzcZ#`㸹Պ {$vpp(**2b4sLO4i4רJuccZ#W+UJJ0ZsY!B;'Y,r;EvPyʔxzzRaуZ ] (y*+ERa(Jy5jͲC;*}8:֔;ŲMgo4v4yA^Vk3#JbFPSSCK|&H H&,VKFP(t^o^#@puuF=<lބ $66h([*GBWyF݁H$  &Pai*`_t^^^QQQ|booooo%~k 0>L8rHPL5N6sθ8>jggg:ZUUetl={+ ѣ… L5N:? ]kt@^ ˍX8`޽{$2eJm^#@ˈh4 ^#@Qռzp^k 1ՃF5TD#{2s\Ǝ5TZ-k3mCgFƘP] O0 (6NQQQrr2}||bbbEo" i0 p̘1$k2e k7nV) {$Jܕw[fO>$L"|' P0zkd[ s F|@gpTZ-x z:O -C|X#c0ctpq ^#OG C> YCZJ5dx'ONJJ"={kz*^#*++X8`ڵkڴi$:ujmYLJ w<բx+ x z\@[ [H8UM#ܹs$󋍍xhx 0 u &FVw9niOz.L*\`x"^#^#@}^lai+y% U"WRVDK%}}̚K(pSzv[\V{y1L f= i$Ę@v Q(0"0yd04t MBAlmӘ^ZY\r'?Kٳ$۷/@_^|ĉd{$Ey9H{>|g1fJЂx"iPHR{:^#{v2nƲ>؛6 nӿD}vvvBm[~@ ί.#@a+ -K+T?,% z{B5;Ųpo[ca|WX#}P&MFm0 (=s)L {&g?<|v!4gΜ!_~E}PGhh+ 8cFCb@ 5&FkW6|~)"<zA\ZD!ҒˆPH0 Sl|+aBpA>ozyli10vw5kON"zeҤI\a6 ЎB[JQZIb6xZ d~YeMO>MbQFzq+H$kDNJ7ڤ}8m4OI{4:1(NpLIIk:u*x@}kL̞={xtM޳c5zpeiӘS4&]i0hoo/?Si C,:@ zmvqyDhϳ{{ 2v[c'W(Uٲ*/K&/yXgqs01&ow5RYY_5Z+eĉ\aiZ;Q.\K>[DӸVefqQZc":{H1ֈVvvviӦ5֨5WK jg_t ?qڴi5qqq01IIIuFzl{SrK4{4:]JNv~ uX7 WG)q6Չo^H$)XHR^.#@T"Hc&gSDŽ؜O}/g߭+q K ::hZL&{I"Hx]R&NFm0 (+kI0NgO djgXNzNNN$uƧzlbN8ICܝ ($!!bNѠPcfiSa5JRz `^#@prrbC&FYvBcGD#{2s\Ǝz8/Ք,~n14I4l0>j*qeeeãe!$㲲/cvGRrD"ܹsɆ@  Ra4Ξ=F>z$۷H5@* 5:;;ӋH$d:H$pvvRB~$bv';;ȑ#$fYv|RYYJJJhLoƘdX)*S$ }Y!iW\ˆi3gx@}yH ^#@FDѣGjqqq)2j8q"x@}D"^B5]vyabюa46Nm pZ5j)`CL4,Kѽ{wjqww#1t/k2 CU^#@TnnnfB_qXf- ybrdc~&~W$bvLC4@ij4;d <]~IMK+;\LL ruM#àADbSa7300e(ō0Tu5DBaf FXZYZIJ[2͝vs%Ӏvpa6m0bQ0;y>妬?9?$^hBi~ WBg_g  n}R0N Nx{{/^_۷ڵꇋX/[1~=HƩ.**"C[56Fuuڵk}||6lP(FLkx_}U^Jo⋏?}W?sr/EHH^ Z6 mBBBxHU ZZ?ZLnݺ>L&СÊ+fΜi Y~ÈahG6i$~'_|\.;Թs+WN:U 0K6$33]vƍ㣖5B dff[P1$1 8UUU$vt FcyQPPo48ӣG={t:Wj5fF{ , 5򍏏ڵk~믿VTPjjĉ{0zh3&Ih׮Fdd$Ooߞe(zTu*@qppTsĭ[ֿ 0 11qС4?'0MHH_dɒUVZ:}aÆ ؿDQK~~>k3047cAbaeVJUUUAAk^cc\~}ڴi h3&99dPkx_u^ҥˎ;._bŊ{bω'Z&֧]vT n;t@b2:*Uk(T~~~5bJ1Ο?|C5tL IDATO>ʕ+MM!\nݠ\vlm :_]j۷9;;/X୷rww7J]w&q'Nhbͥݻw-{N;1{.ѽLJ幹$vvv=^y V)%&&VTT3^#`zƷzlhgyf֬Y~{wM-zWZ*ihh(\20^F:F@@xXj} Ŗ-[>C1oҥχ-F C; 5}Qqqޡw}w޼y搞k.waҤIɵ.999k #@aaaFܹCbaŒX8`TVV0V56FEEEbb[?yhhw}V_x0=^… xV+|ׯ222}իWXboߞ s<Ab2^P5T3F`` xXj}׬YqF'J׮]WZ5i$h !BX5^^^onٲETC׮]2eJtttBBرc+!==/ta|䙝M{AAAu֭[=""^[HA6Nee%}:XӥK&jhӣ5Ǔ Jǎ0;soիKBAݸq'ٳgBBB\\\j߾}TTPzɫHU^#@quu Pm 9997|R&&&9,aBBƹ{?Lⰰz ܸqE]Gc%&&LcwڵgϞ#F C#@HIIF/]Db@@JltFJ+8~AӍU`Հ}qʕd5N:Qa56ƠA?~sq{wU#`5 ^zFFѡCkє۷oth"H^x%K%1'TƹsI6k,Wk׮-[!~BϿW_}u…^^^Fp-{^5^x &&ƈKYYٽ{HQ{F){l>j{nYY;uD)/rz_t~ sz޻wo{$> 856 x{PW/be7oެRxJ 0/5Gk|Zm0*;WK>}H\g B/~饗֭[vZPVV /f͚˗Ϟ=Ύ ˫HU;FNkT-O>dr\PDDʕ+M> yw膍 iܾ}5Ι3Z^GQQњ5k6n#t-!!a„ uܹsO>Fϟ?ObӪSZZzX)56Fnnc8z:pO&F{>ـmN߼ƈB׹Mk/b…V1c?11qذa0 }kׯk(Taaa5VGZZZBB۵ZޡC&&&0,`-[F"ahGqn߾uVGDD<|r֭RZK;:lݺuɒ%+WܹsN;6pѣG'$$hIszޯ_?{gϞ%}(qJJJn߾M:skb^cc60&LHMM5J΀ 0=^~H hq6Exx8;w\\\Lbzt}Ϟ=1III{:uʕ+;wʊׯ_rc!0;P<<<0k6ӧ;n̙+VСY*.YD Ў`ܺu'qΝΝG-7oޤ^cΝ[q 0ѣG~Vm۶_~gywU#`D_z޿{gΜ!1iU)))ukxk .!-_}\ F{k֬iMKk]o!ZlVkm۶-::ӣ5~Gdf5<8%%LBko0Mboo?snٲ?ˣJJJ,Yg-Y_;***;lٲ{N$%7o?;π(.vEQATzo EA]kl{Kb]c/ @FĊ%lpaܝ]>Y윽IQF#0׮][f ԩӳgO\rvñ/_Ν Yz&Mڵ+q&5bbb/Cb\+Wi4j%5~̟TRƆ!vESkȅdFv4Faj׮ ¨Q^Hޔ0([/2t9s,^͛77o3f̘:uj6my?=TׯObagg^#|nDQFFFk֬w'A{'O$\.!ϳgdJ*@^}QF/&..qFFFUTGe3f!^#{ {χ"nw:u0kttt| DoocǎM:uʔ)7Zv[x1%4lؐبQ#׈+Va888X؍h4"_۷o[,YRY!=pÇ'˲0P&~~~2,??KuիCd tt/ 8pA311AFH,aR""q>}Bʕ+FFZw}իWz؉55"׸pBr^#?QF#0 z_~Mb2*ʻwƞ;wG"--^z˧hԨUq$8RJ GGGc޽۷G6rss/]{̙،OK##wA!C ea Y233۴i-v""s˗޾o߾4ztx:p n"5266͛dYf=ZǏ{ոqc'N?[8O>MNN&1zr"qkOFDD5[T|iz)5.ZHݤGDD$&&VRe֭; ggoԭ[WV~-zzqqq{OǛ7oΜ9sV_9##Ǐ=ZjաC6mʯ:& ??ͭF0I&TƦM5"@J@NNNإ˗Q>Kڵkʼnא_6lL&cF&yyy'] |}}oܸ!Ać55U|tL ^xAb##+Vt]|=شiSFrHK.Xƙ3g~UV-BHLL!)133̬O1ApAZ1(5.^\1\reb&MkffƟPo޼yԩsjZarss{qQ;;;" D2>?^477 477/[X)gϞ\x^۷˗z5k^%LHbr ggg)S&--MDcΨ[Cڵk;9s0a3B D> ,@Ay ,ZB4􁠠o߾\&&&>+W,] Kbb",wsseximmm7m$LQq5j?3}tryY29r(xrH'O$%%]]\\@O>% a͚5䭐SN%xaaaÜ9sDAW)[l~~(i׈^# ?nffq\k111)_RT(-Fف0 rrrHޔ0Rf͖-[>{ٳgN4,--kԨQL+VXիWJR7oNbr Pccc9L̙3S!qLLLzGtLLL)wFFlŀ :eY( '55uɒ%$vpp8p ^^XGD8t|eo޼^ÇI,˛5k&`bbbnL!0)HGFFjՊF/Ѻu\k4aMɁm*?C$ٳg۶mKbZ]PaS,%2"@-zPkDzzzB׈ pܞee˖ '"WX*,/^| =<>5z{{!DFFºƐƽ{X.c|ѣsΑ̌[f dXƒ@JYkD>]vT6mڐF(5PEGGG=z8dD{BiT*6.UTaxyyM??N2dɒKQcf͚5k1p IDATP(F]Bk4h׈ paaa$bY E>}z*T4i9;;III?>NGW:G版M6{{!1qi"q>|OU@&iŋ m~ӰalArf ___Qr8pL])22R/rM@錌 Qr5}WT6.vajnݺuI6Ufgg߹sٳׯty=**SNYdqttaJP|}}aTJJ hӦ urf[FR ۛTS5?ϟ9s&W#H߆>Qc}>Dtݻ˲E~QX;vk6l@^ٰayyĈ4z9{,k,σ]vi m۶{$塡6.>a$/0`177$oCF5~-?}5׈^Ʌ5B %Xn]XX&!#Vzڵ#1z`ffu&nBzL&@G݅yfz+۷k1P((,@A\zի^FU0.](h4/_޹s'..nݺu ؈>`ffa5v lڴ^ b֭G OOO0 J#˲0P(J|N{{YfQ toFbF/< _8۷ءC;v8Z-`XwPQ]%,ۭ[tAzNp].ч=TJ~&5R}va(h 6l۶mȑ3`ϯx{P~Сu;v$1kDU0$q^^zYrrrddmd˖-#""D=yy'?<A:wL"e%5,Ydɒ%---CBBƏ߶m˗/3 o߾ɓ'O6MҥK+]]])͗_RGgضmx;vkr}6.ߗòe˒_Q*$oF]'{kDIadFB*UL_;)pvva(QVO(?>;vFV k,?\_ |0jz2jaԈ uԉ| XSfM{Z?N 'kŞ$8III|>N1 @tزe x;wk(ĹӧIlnn^{Z6''~KT~oKЇQ#z%}5׈^#|0Lvv6İN0Q~RJXCFǣsTF FV'puߝSSS7Ex1|p]_ZZZttsRRRܹP(*UޡCТQlNBQ$wD0 J#˲uw޽{yy}=zt$))i֬Y$vuu3f ^N< ~m $>p,-- sN۷7m^c.]aRL@$NzzSHlaaQ{iiipa;w~y/ߗlak%z5maaɣ׈H^#l !e_Kr*nJ055K J.M_"?++Mzyy988XXX)S۷/^9scw̙3gϞ)ХK{vڕĸ*0<==իWwݽ{wn@]aJ,yůE}5JF/?B~~>lufmmèADk׮,[$G_B vrssA8sL;F/|HiˏeiӦOnzzW秗5vUp6?#Kzzɓ'I, Q*ݺu èA7`Adſ?y}5׈^#x-uŊ+SLJ}||ڵkgll,vR+ذaCSH?KII!ڵ+uݻw'1kDsssVM"s111с0J Qб`K82DV^MqBAu$bQPY"qg̘Ab77q%** FIm(7n$F#˲Æ ;aذaxݻwk(Ĺw^tt4WVL&^V~ƻw(GFF822KէO4Ȍ{K] OnnnXXU*FPQj^#\]]AF/_mu|'Olժ4|3ݻS^#XXX0YmB $666~외|9=!3gl)LL cbbի'n>_ѣGCBBdKÇ #IFF322*U$HbXpmڴ!˲٫IHH>}:ǏO'Nܻwč7ƁgϞ;񢗗I֭1,,Lpq$8[n6.iiiQQQ$"""zGp-Z4~ 6mѣGȑ#W}u]pW^$VTXBbb"CVF?ԷBqssaPq,◰bŊѣG˔)#vRFu՞={Fau|;Ruy9r$>L.׈ p!!!$dw7DOHHH'DŽ hrq4i0}'E9*vZ{)+9 Pv [ZZr`> F/8/F 0LLL U^c={MbJ!$&&0j5gr AklҤIŊIVٳ'upDWpss r:ƠAhw"˗/׸BXr%Օ/Ȳ, sE' ǎKKK#qӦMq0aիWػwo5k֐88w=~8---{ ӧ I Β%K^#F0`{)طo_T*,!)) V J#zm+,M6Ʊ."@޽zpDWpuuuzk%Q7Fakٲ%X"q.^8uT{xxL4F/G100aժU0j۷^UHq Hw=z5jO.kDO0l28p x{k߿?U*FP՜ H-00MV^R/ѧO^c~H^#XZZ0kGk%5Fa "L&۷o z… LBbOOɓ'|?%Ck(믿}ݻw h6mڈ>ELQra{֢E 8wҒR/Ν;P^*JCJJʻwo߾l2Q0`U BåF Fazzj\r|$gΜI!!!TٴiS.]H\lT333qS VۢE x{nqSB`5k@A\x'-[vܹ~~~9,???555**j>XZZT*sy7 S_$ ecKKKЂakk͛7{xx>qyyϞ=+WZ%&j˖-OAa4P(`Dܹs*ud2Q;vb.:fff>"ɢEDpp0 BBB^cVh,^xȐ!W%o?@jذ2YJ%xFX-^8U*ׯl1\vݶm[jȑ#&D?vXbb"P(Q)QDWM&V]dQbEJUVby 4hǎOW޽{w۷oa%KYF5<ĸ@...Voəb8::QPY"qΝ;7~x{{{O>F/gZnGèqذa{-"B-bܾ}Vppz W^ ^F0j(a>Ph4#GXRaiD))) Z]PaTPR/ZrheeE6lUqĈ$u`mm prr4h nBRBgϞ@ 9sk\PP]7(|aFLv BqƑgƌ4zu[Ɓ#BX`k>|^Nb`(nzkPh4ǓXRaiDW\a҈ 4ZmggGb0f{FUޞ7!D)WѥK] Hxx89>}:Ύ/Ȳ, ٳgaR{ݺu)sN۵kG0k,5;Vps8Ĺ~{^c||<@1^O4"I&Z F0aU*FpUZ.(0ʖ-KvۓnDqQ]w@VZ {{{K܄=lٲ:?@ :u*9n1P((2,..NٳpXOz`IaٱcǍ7Hܾ}{8"3f8~xF8TP1P_sNשS;<@FJ(i&4")S8n8ah4?3U*FpUZΝ;G.pUa;tHb0a^#kDڵk08OOOqBe@:ubL<^/G [[[0 J#˲0P@$Ι3g^yhm6;vG0}t5N8QpJV(?E k׮رĶ܅ z¨\2^l^3F0uTa>Ph4T*,ڵk Z]P[.رcGgggתUR/1qD{F]6֖sww7!D)SѱcG] ȤI5qܔ)S赏(|aFeqΜ93l0͟?F/[n{8pD0jai8ĹvIlkk%$$  ++ ajjJ[₥!8qDah408PTXµk@j4"Pn]F2e(ҩS'c]DI&Q]w@N: [[[U|}L2:Fhw" PN0 JL&xHY!EllСCI`lٲ$ܹ3ԩSkFgj4DRaiDׯ_aj.11\P^==snnn$]6^cT5FoWNA Pti}@ Fۋ#^aȲ, ;d/\F/7ov8")S թS 5Q q-[Ύt@AUVk҈f̘^;h48]RaiDׯ_a҈ @z@K?AbJk; q6. ڵksNNN&!Kց0ڵkG4] IDAT zn|ڵkQPe2G$NLLIh"lڴ .]!L4 F^#7\\טB.kD/_0)c===4"YfSfJ҈nܸPA???FR(ҥKOOOשSR/NuUFaԮ]spp7!D)UViw"cƌ8qǏ>bԪU/ ёD,&''_ 4/˟yUwQiaB-b\reӦM$._L.kD/_0,,,(sN4"ٳg[ F9s&U*Fp Z.(*0y]v&1E6mupD@kMCJ*amۖv!O?k㸱ck1PjժFAidY 9}Idlܸʕ+$޽;„ k1c^#DP(`('87׈^aXZZRe׮]5biDs F={6U*FpMZ.(0yݻw%-^cƌTF`oo¨U$CJ,a1DFEqzbccFAid"eO߿?ׯtR_5v!qq̙{BqF;88p3z+^"""k[.F0w\g̘!l_%J҈n޼ PAFɒ%)ңGu"̚5pu`oo¨U RdI#$$v!2rHzs7zhz# _̘gYD:u5.[F/֭yaaa8pDcǎQٳdB![@| H]~\׈_aX[[SeϞ=5aiDq֬Y6h~7T*,֭[ Z]PqUaHlggG={6upu°jժ%nBRD uִ@ #Fk^RfM0 J#˲0P@$ΩSK ,_F/k׮={ā#B駟k3g^Onn."T*D⤤_NNN͛7zk ^"##k ҈,X^#̧yXRaiDoa҈ @@@Dzٳg@@."9szpD@5kj֬)nBRD UV@ aÆk8^&bԬY/(nܸ!RV~q>}A^֬Y^c^pF~Mpq̘1$V(p"qRRR֭[Gb''2 oKŋ$QF HܧOz~~r ۷F/!!!5FFFJm fpҥkגյ{$611rR ^F6/^*6.wn۶-j5{muօ},QbEy$VT4zٷoߝ;wHܪU5jEo}>5k qâEkСC g$V( qp}I\fMVՊܽ{wd;;;< DjiӦOTRnnnGOa8o`f޼y0CUH8[p!6. kkk{gF4|nDAĂ, $gF~mի6lj*Z*%%}SvdFaF^ aiR - '99y$vvv.dFFiZuȧFZ1(5޽{JkvdFa4h 5ӧq$vppԋ'}b0̂ y bѢE$F@VVV52'F4QFz$dɈGF)mM5jeʕ|QF) >ƅ  59 [@$ΥKkڍ55F)Z1(5~f#IQn0L @ׯI&$쐑 )?`f…TFCFprraX@!FDhDq,,,HIJlZZɈI!Q_ۣ^5khb /#n0СCk\h^IT*ϟ/`r%6\\\ ~ qQ(eQ^ۍ75"bPkLOOn0LÆ A7k֌Ďz1O)$ĂaEQ.]Jb^{݈F#{nDADVXF7n ǘ ˓I<`݈F#0qɒ%{C%Rյ5jg7JhԢ(nDQ^#"O#Mׯa 4lQX1J 0y$ňO,Yd Uq$F\\\@5j@=޽۲eK4QVΝ;hDH$߿/n6cii$ʼn'uFƍx, ,[5:99ŀFaA׸tR!C "qVXAbWWW~ @"jܹSt@nn. իW8N:u-U{888x߾}4z $qdddVhhڤLwQrew0\.}ETXؿ T*ׯl{˻stΓ/_}Zt{RRR^^˲ ={vɒ%ȨQF ###J 8E$և!cbbqN8P(\\\HGjj߾}666gF.]}5O?t7oٳGt߹s5j4{F5gXn.\lY"_\pA?ݻ7۫>j'1 ꚜ,D*$%%fmm-Ÿ_$ $X߿vvXSSSHÇ"K̑#G7o꜠O411IMMP(8qk׮$nܸ htK.xРAbUXַo%òM+Q˗---g`,_\pqРA$V*p؂}ӛ7oL"͛7ԯ_?**J竉0CM/Ƭ,$ OOO??+)SFtLfffzzÇ;wܹ :CR^>l0+fff?W _~1 **J_|3g+,֮]r*JtL~~~JJJ!Goɒ%%B^#c(9M<_~!L&={QDD\rsswex%))IG޽I(FFFʕ˱c`ܤI8F<~ݻwf۶m'33W^ʕ+{-J2k֬S^FNCƿe˖k@۶mWZ%)ءCxBSX4Q&?~Ėz56n7nInn. ΨqFx˕+C3xT0LHHeou֭NGLOn*vFZVHeA V={}qaaa#F wF?~K.$nҤ ~Ȓ%KHP^*JVZݹsO>-4A]Ԯ]Ȩf͚#ݓb0LJt 0={xzȈdǏa/7677!qtt4naZrO8##C{ºuk kݝr(_=z!333ÍO8ZÆ O8ҢH\zuN#zRԁ0> EG&:5F.kolիQPe2ٓ'OD /?޹sg7m?ŋk2d"o߾5\Rp$V*˖-qpIHHxkbyʢklݺ5Fn:tY[f U*Fp=Fvv6!7aЛ1d8"\pG¨^:gll,nBT*u F1DNaaaG  0+eYHcǎFJ-ZVw :ŅF/ѻwoW^-طo_+J JBBl=\׈ lJ8qP,aÆ 5ZJ֭[GbJ!ܻwի҈ @&M@aÆ"^w@ aaau( A@ 8b-G 0 J#˲8Dǎر#5kyf,\Ç!Kѐxڵ{pR\r#ŋ-ZDb&5"@nn.˗zm۶XƍkBGsT*,!==͛҈ @&M@ R/ÇC."5k;Ww@}؟ ASjFP@7Q@4r^&bQPe2ً/D /= G5kl֭4zY`ktuu bpu 5ՋJR!r…F8{Fh4 ׯ_Sɓ5j,aӦM5]VhnJ!ܿ[AҴiS_#FP$ƺixpu077(R4 a1D`Br^&b-[D,fff_=z}$n֬ٶmh29{5nذApvV*/A .,\Ğ'۷G=}tRRݻw?[JU\+xzz6jԨFbg*2J:u ciD[lq6T*, wq+ݻ7gΜ׿zЏ^|􄄄UV,߫WΝ;KvSӦMA#Gش(Ee?#BaddTT+VZVZNNNճezΆ zp,JpTw077gb_1bll,x&<666?I{\2d!v:E›\jUA4hEGGCV 6;VJ.MdEojvvvhhhTTR\f͚>~8%%ȑ#۷o7ʕ+͛7~ #Gk׎ā۷o˼y.^H͍F/͛7g͚շopoѭ[7C?k]_b@?~$*L&85|WTǏ̯~z޼y˗0/DF)Һuk%22R/Џ{%''o޼yĈ}a33g 7FބF߾}˿'B{XG:}5ڵ rPͣEv %+W>|KR1sνzѴiS=?=$2ddYL2eʔ177wttԩ0K,Yj*=|QFkԨƍ4Pݲe Pu_ZjEk\l4[b;w|կOTGAǣpY ҥKߺuK| }5yt8!(k\x1mL4I <ƿKXةS'Fa#""|}}{5\oOa˃U*K#<ah4bjǓ:Td f͚01F"0 SRť_~+%xڴiup=T :a<==AժUT* Nbb"y80  r^z0DFd29"&&Çfffb'S` r9UƲ;"'NaÆ"fb>|5ܹF/ۅ H{_Qe֭TF 5꼼@UV匌MHpJ*1++ K7 t ](I6m5.j5 ̌/(Ɋ jժϟ?'/9tPHH[hAٳgú1c*d:tu۷ov^R|q"qǿ5Tdƚ5k^t...chZL8aga4U&^"Akχie5=z`Y7B,{!j:I`` 0vX8dsYDDoNk; k#qy{{08EQQQQ"fbd2y]g>sС֭[E{ˬY`]qpH{ӧI̲l`` #c;w 5vؑFFFpC||Jbe>}L8\.ZHXJAZ-&DN||}8IDATe߾}W-h1c̙3_MMMxXՁ0$g|ϟi6[hРAPPT:4TLKKK>w\JJʝ;w233 EJ;tZd^TR/(ɊlrȎ;$^ ͛7ׁ0ƏFb Z˗/233^z…3gЙ`nnNڵFq]$YYY5m¢L2o߾x̙3;0̽{fΜ -QJF+6Oo_L:Xb_N988>|3-ÿ6Ḹ8hWƑB#HdaJa矯EČ aG3eV*HQϥ3y2z!4l;[d2z[\.4iR׮]̙aÆ}z||<'NX4>` "##۶mKb##;w ظtظq#1Y6lٳggΜIuFz!q'0} bp8p(8QQQ4hPRFb8z åE:ĉN;PسgJEk'Olժ9)((hbg$ SSJd* +{H̲,#C䣴hт^2Y9zhHH۷o߱cG6Ą/ 7׈ҁ8m4`ҤIE n} 5u##ݻw xfϞ=;v$E///^\ٳӧO'qz J#zw&J]\x$ׯ^СCTFPz_ΦMzA9r3Q"O< +;PS ^hBKb|HG~{$F Ybѣeʔ;)aWSc;N`Y͍FHqLF,z̟?ȑ$>|pѫ Ø) ׈^7^e˖p/ڿ^#Xkdd#e'N$߁sT̙3yPkO/eYN p^t)Rd9Bkա@]d&&&tg߯ZBFFD\NN !zHu ɓ'CEtuub(߿w@nݺ 1 DFJ& fCiӦdTL@\2_R$zpaxSNqʔ)5"`xav999Z"Qdd=?66vڴi${4_,  7o9))ȑ#$8p ^ѣY’C>} (UG6@>͛7ׁ0L" w@ @ݺuA+W~4_vLFɄvvv,b4i҄^2(+W +V;(?"\T !!rԤIęNtھmtQ'xNLNKҦ6c35\b"wzDcK|a=Ϟ{.*Ƞ8k|ϻFA8&p>yja,;JJJi1A+#Nj08!1:_h6ʠ\pAuDu Jzz:M/tߥFFKR:[ձ$&& qYr'//Os'XhZĐ!52(/^tꫯRkd h[TTTpR3ܜ/xIcfP8!ܼ_ T cq%AFukdP M+V 8!1>L{D6ʠ kdPibh/tCϦDwA寏f ^R@ B\ e2)Z>1dj(Foo{shhd22,222**J8p9yf!j;۷oq~."ꤤ$VhĺWϧ5n߾hRyy^kϒv~pDDVjPQQqA3335\./..y&yYWWCdZz^_v(1HRtihht/@ܝNtww(1bŊӧOFii^#:~ߴS % D`0̈=b`ppߦ/E #?? q# ]ݗ\XXHtaa / Je'NJJgX\\,H[@8۸qcEEyyĉC+`2N:E_eddh40mĒ۷DFFAT jY1??/J Ρ>x≅_ݾ/ܿ}ٻw/9{7ĎHq`kkA!"J5"bAA݉@wD\r[o /5+DcO~B $$$???,,lŊd&'' Ceeg}F CCC}}}E i֭k,))qטOJ;Zj}||D ܾ}ŋwyGx_}UE-IS _Rn:hb{rUV-ry]DBOVձӱ/|;)?FOJeKW^%/nݺ%nHN#>>I$ݻw=z[ܨillx"GhI\\\SSX8ETǏ?"S^^NPr]byy9R4##Á_N:s@&X__O_;w...Nx Css3&l}%$$H$zp;Ve2Ν;bЬ;g077_qVӤ: Z [l/ MZZJ/ip N{zzITXFv׸~{1--MZꦦEI?䔜&v,[l^㧟~p.V*`wq驭8>~版l­8p謬,u\jU""""""M>O ~555=>>.P-J;\u4Df͚5k}cqShbh4Fss3cccE.EEEm2225ұ xqellhF#}6Xsj%Z& x/2^s R^#R,f-eee 2٥Fd'pʹkt TVV 5Ҭs!:41V#Er mGXUUE4qbp5[[[F+WõX,t{q\%n< L5۷`wcsl59k[T*z;)++{嗉ɑQ,Ylpr9M [[[Q 7JPfqkd^^ctt4j͛5VWW-JSSSxqe||\ K$61ejjhB`0a^#Kp׸w^5kdX^^pnT* 7_\l]#"ibw^ȠTWW 5Ҭc^#b4ibt:52D6l`kWv)55^cMM v.ƶ6%- @l{pd0 y/9^={tGu됑AJ{YYYDTR^||}͕i4y B!y,mmmW^%zbbBZˎAFukdPF#MUV1!G\nk׮vtxu9KIIqAdddMՊõ0 ORd0n_sƗ^z7|y BZZ5LST4nNii޽{˓lUEPĠ7:k׮ͼFz4ȠLLLXz5a5 3ׯ_RJJýDs8ơ!B"c2hT*V0%8ko~c5fff Q cٱyf5VWW;kT*攖ٳ輼<3<3:::>>n0fB7>+0<<-P-fYN52---2L& qqD$C&IR "g|TSSc6#""%%%>0@Gc9SSSqܓO>GFP*gϞ%GB^ڱU0)dŚBHIIeee^:)))99922RcbccQr*8l6{yyeff[NZ Ng6jkkZb01:::))2ܜݻw?3յPloF@@OSqCdˑ#G;?G?ƹUV|ii;;v,??_Z"##hbYvp1\/gϞ=~8i@zAhiiw̙[?onnnkkKJJZ\\?33s鎎8{===}̙θ3gtuu{͛o^cbb&&&ѸDߺu+::z||kooott׷aÆ KGFF>/CCC֭>< E[n@XX"vww5k͛_FvBDrdbbb |tgMz0}l#+W -- +** ##̺LD|2dgg#"9<77?SCO>n݊/^|D,**={kNOO>|۟gDܸq5kܹsH#?cDܾ};\pSŋq۶mP\\[lDK.!"9ؽ eeeH6F@D2q7o UUU ;DWSSĘ'𒓓ƍ:DLHHDFDfDVDܰazD$HND$NpWW"GnD [n!"z{{#NAD !D$FFF2OF ɡucccFɂԉ D${ NNN"7R033fknnU*ࡇB?;#H!"0X|||zzz/ Ytktt ={6\d{6\Nh^ WNNޫڶm5\xyرc";wv܉dj>}_9DzSSS+**mq<ϓ5FRyLI4Y)x'ƀ\.y yRRyM~ *y2<&-'$ZZ<٫h2󼿿?x{{>><ϓF|}}y'7 Dτy2ִrJ!!!<y>44j2h<"j48Vy><**j2@~㰰455mmm>>)))qOxzzQ 33{j X#7~egg 0'xc"444ynNNΘ1c;o$}YG<~Z+44眜۷o;99{x---᳛J sC*;vLNNn<?=D:<{cFDDZPP:"߃D&n!%%eO{骅!nܸ!X; y8j#nMsVOӧ"l\$6?څn/Udee״uViH9 Z [n]ݻ#+x;w`>[VOO4yd,sqqAڵuk|}a˗/VDCKK+665޻#+(ҥKFZZZ#uMMHHha<I}%%%}oҥ?Ik.())7p ̻oau@Ot_^^ۻwoa.ڤSLAƴ)Zx-|}ݕ+ӧOށ#D`EEE͚5ԩSeeeIII0UZu@z_;>>=zh׮]cƌA{|hfVQQ9|pZZZuuuc^=ϻ//-1B`l W!@~N IDV)++5!/]t@~,nywZ+aDi>|Xl*AٳgJ.]4(r+(MUߥ:!x-MwY`kAzQCEESSS*++խ3lذN:G ju?~WVV&++о}{MMMCCCcccKK^z(AS/UUU׮]uVlllfffiiiMMM'xDn1\-[mVYY٤-,,v1b \xIAXEEE{O̴ qqqMi-y`4nWK\\\ctDLso90  `D0 0 0k(*333>>>33SWWx"++r_|/s8/_Ԓkjj>}/WWW}Y[[*<<<77WKK_ ˫%WTTDDD|ESS_.//ϯ%EFFhhhFFF֒KJJ⨨ZrQQQtttIIIǎZrAAW:t萟իZ/_bbb***ڷo/TVV֒ssscccڵk/966]v>}QSSsrr8 %I<8a[$a*YYYB2`K%%%JJJE0EpJڵ( ر#EQ>}hhhP ֦(*##KQTzz:@__!EQpQV.](a̍)z-ĄxEQqqq `Q+++$p˗/0K~{{{ET3C (ѣGÇSu}ȑ#){.`̘1Eݺu 0n8k &PuU5+WSL( >}:EQϟ899Qu5ީSs̡.?>EQǏ,\(ooo5 Çdpk VRPPpƍ7oXl7"deeX={PbKQǏY,)JOOgX(*55b>b:u8uibccY,vbΝ;GQTtt4:]QQQ*a6ג)0bBTT455;v BW\0a‘#Gl'H͆)}}}999l6YYYl6H>{, ##f;wG$ .l6SRRl66}e$fa߳ׯ޽{7oް7o"=l6Ν;ׯ_#9..f߻wffD2"##lǏl6l6aaal60y%͆_xff3$gϞl6S6 r(Pl6;<<f#""юZkݻm6::ZGGGCC_0REddBJJիKKK322n*/\Ю]Ço޼U˘Bdd<>|xΝ8W3AAA...vVWW i seTdDPn۶-@FFrv j|hii9|𘘘GYYY :4::߿gϞC z䉵cddd@@CDDӧO{5x޽{4(,,,((˗}8p/BBBkggٳٳg ׯ_HHȋ/ˁ'(((,,6000<<|РA{~iDD{cbbbeer֭[d2X^F(o$#є=zfٴÇ3"4i?۷aÆlllGݘ]gŊsLkڧO> 88x"Pʉ{ydd/VVVgΜaٟ>}=`0dddLLL4jcaaaccsQQ)i̛7DFF2w $IB1[SS7~x*((6mϟ?}WNb ƕ 9h 6 K.0~fa|:t萐4hOi^ Pv͝;Vu0B@@ZAHA <60DFFFbb"j, &$I.K*a߾}+Wݻn*77-VWW߰aô~)SD.2 }PPPBDӧOeccc'4/p*@MMm>} p#/ 4 b?+==`0ƒDz^^^P믿Ds0m4СCa0===dBN ea OdjjD楰_ӫFL B~A0( ȼ}}} i>Sm޼"tM>9bX~" SN0,VXX FaffD楰$ #1t20 4 b447OܠdsE5j,`? f@B`bb{" $$$%a":}tz Ϟ=C~";;;&=&0Ejj* 00000 B?ѧO6meMMMi\p̜99vXX ga dBNP>a Ϟ=CaeeD楨ҥKPVUU:u*`ĄϟCy͛7a 4 bMܹ`$'ԃhD(ô~Ν;yLT' a TQQǡlbb{" $44 a"???(L<^}0bBhh(Qԯ_? &ФFC\\t")OPf!---[@?LٳP={6sҤI rss?|PRRA:-Id2666Wt0M@Dȋ/a[\DV:}4@VV[n@a0ݺu[+?nhhHRիPVQQAFy%ۗ@D 貒 &#]vEy%@ /KNN%`$D@GG'33iyHxG mmmR Obm6i>}};w.Uk=ҥK!SSѣG訩ѢO WXXѣ0xΟ??22R^^.t C Qqq1Fmbb{" $,, F߾}i=}/^|!"ml߾c߿4#*oܸm"HXXػwlkkK\|hUg??[[Pi^tR;_{ZHJJzvڣG4H LO Js73gLs=֭[emmm6-0SN=z'jymF˗| {{-VVVzzz;vl׮!ðbDHxx82'իWdd$4NMM,*++G$3***8 D{f^x6m}aĈDxhllܳgORVV5kHzի*2g̘%$qOܿO$LOf(kkkرCaZ?'OD~dFڵ+2 a!@qqslbb{" $22'Bڶm3Ya hGW^ϟ 0r޿FnݺY[[ ܔ2+L]llllllP9sL($-yH(;;{Pٵkô~||| eggZ :::laH'N>-Z'0nݺ!_+%%%(ރ 0hdO$FH"QVV>|8`ĄW^%''CgϞL6 4 boߢ{6mlXYY|e+++ɓ'a 4 b7o޼|ݻw&Q6mpEL]z9|(${" ~"az,DGGg"{..]\`,D)2>}TiiסDDll,2Çc?!~"(+++;::ҫFLC~=z0? AaPRԩLDrr2)RP( 3-jӦ ζ*2-Z%$%':uz|}}HL eeeoP=xô~9y.[컞>Z}d033Cѷo_a*--ux52QFIٳgvvvu?yx$OTVV6m s} 呙tR(>|XaZ?}6w&nH[[{۶m/_NOO_ti…*(^#fB(++CI' a3)%c̙###3g///xɓ'ATO:::ڴi#61o6j(( 0bYjjjΝo۷ݺuKNN622Bu >OgqqqϞ=e#%0-[%$['j|}}Q7Կnݺt2p`xɓhy)H(33 ;zô~:tM(X[O$~"|ю_yPOtԩ͛7Kâ`aa c4UVVc? c񒿞( uRTTn۶-,hjjÇ &r<(+))ջ #y2tޝVAIɓQ7PVV|`ddaYJD!C4 >E*8yΝ;E_k $$0=QFFƯ N:/H3{vW^'D̛7Iϛ7DgΜپ};z={"ruyy9z DDRR2ӧK֭[h3JvvBO*((ś7o7S$LTTTDDD@YAAW^###&z! ӧOoR71cӞH"z mllaҥK.]ITccc\`hhdddrss* wTWWoP`оh0M}e֬Yٽ{72k׮1׭[z"ttrٚ(wfffgϮZJZa9@ #)$''#Ø9sDbbbm۶ Ν;*::O0QQQPVPPWÇl(w҅`ٰ@-Ȟ(22ʽz0 bA$ɖ0ϑa8;; }qƌP?wô~???(]a$c=F$ 2YEEϡ/fI!%%FNNDhA"DHee%r)((?19HIIɁrΝ۶m vJ?<:No/$=r޽*pS^7nI'@BCCa8;; O6 .\~Ϯ]._ ?[ODD^a_*++QT"a5H 0>}Dbbcc,//oiiI>1!--ӧOP600`nٲ@ F Gmmm &pS8:.tqqI'@^x Y(==}ʔ)P600tô~vݻ72 !|UVV8%%%jӑab?!~ׯ_CYAAH9?>sͰ@1RNXX]٧O#Fܔ1!###77zzzLwwwX  )˗݃r߾}G)pSrrr={^AWWWWWP"IDHXX2 gggaz'Bի"پ};mذỞ>0E>}a'z%y#233a|I}ݺuW2,~[cM$1R΋/ܹ5Jz!"0*2{" "<<0=QZZkhhxuil۶ ڸq㷞' CȝUUU(*jI #??I%&&BYNNk׮Pmmm_ Aah:۷oCcƌ)999sss酑QuV($=ٳUTTrrrZFff&@QQgϞBdDiiitCC[n@?Lg֭(˷?QRRғ'OJJ'/^?{ =B~a[r #)dgg#(((OtCYYY9..?TQ^^nii k֬ٹs'*ꤤ$(u҅^}0bBNNiii٨0ؒG-U/(z٢E޽{$Avϟ߼yvvvcǎKO$-cǎ-^AVVV-**jӦ h+x<λwn1o߾+WDѣGC-H9^^^Odٰ@4d…ϟG"EQ0ZݐJKG!d7o@UňO>!(,,Oھ}{MM *++׽cfff~~~<.{,LS)))F_>|4Ĵvݑa9s?@?Q?GEEe+Wd@O1!//;v$a 4 J~~~SLԩS/]@͛7>}hС^^^}i.E[`hРA(MH? ɓsʻwf̘QcM_z:7+"}+C֎2ӧO3_jN2R''O 2Æ wwww vڕ+WiF 9emҢWPPPDڵ#/_ $IɥO:88@yȐ!?UwhuB]]}˖-DE``˗0ydzH' Da"or߾}Ntuu:T]]- Dɺ_i{i e8p4XO$VX`_|Aedd,[lǎnnnfj` 0x`dB8 $ OFRXX 44P&^޽{h cʔ)4*p8djhhЫFL(,,DTUUe˖Ah6^|ٷo_(}/_رc菁7ӧO;GGG)ivOC>}Z+WwD~?~bB~"ۋJCLk篿BqICIASi >QgϞ5kxyyo :tzrl1aXZZ [m FR(**BQ^^D+:22甔@` ɠTUUU`hܹ?त$ww njyfkk&5(&p8\(3̎;ҫFL(..F bɒ%@$84[YYEGG7TKgz4iph׮IoڴڵkF &Oѽ{&5(OMA~"ͯىJCLkgÆ 0|||GD&ĉ`Ы55 >:ܼ4 <xomll޽3O\..\?nm<0ͅi@YwaZdQ,xA? Dm@}TG(yo3ѣG_jUNN0p=$//nu0Bqq12rbѢEв @àԵkW(wL0aطtP͛۷_s EQnڴiSLLLV\vvMtH5 ,$Ai#33ӫK,ZO6 "Z]***>|x)rP&IREE'U_K)**s΅$O8AbtRPPtСۥQ= .7OXNFFƖ-[?^SSS#{{{//6£GΞ= ÇϜ9Y`"~BtiE$s+j5Frr9s G;bĈH+4$֭[ رcL___hpOd=Qd$3 > 9Ν;_ʕ+c>|8q͛7׻\hذa0LMMQ墎=a00˕ܴLu5}_'N9sDPz۵$9c wwwEx<֊$IeefuQQQQ]] ebٰ@J.YQQnl "p7/^pqqudΝ;U__y3gi ,"⛼`4jF={vΝM6EEEUUU?qĊ+~d&&&\㡧]N)FMM EQi`0#UU2ֳ^(???77ZmvիW~CO$I"FʩB~"yyy-!I)F3@DӦE$ Pop.{9(YYݻ/_`iQſS5L5D#GVIIiϞ=kkkѪiCqQWКZD[Z!222K,ILLk%.++[jwE?>AE2uS]] `?(((ZUUUM闈YVVwޝ;wɉlC(EQ( IjFYYYY!BڴiRc޾}ݻ{33͛7O4I߷b )---[_OdccSo8Z M%&&*|ʪǴ V^ ȑ#Lu IDATSvd1rHd[nF]G0`COguc`d\.}-RXX؄u4'N6tP//zQ|KIp8ȐdddSI(ŕұcǼ<( $'w8f {77K.՚91nܸ͛7ѣEuHR'RWWG"77 gJzZtYLL̄ za*7oޠ߿e ~ Xj*df^|ڐ}ʳ̨Qa0aaa2x/^reٛ6m244( yL)"{xOPj왒t$\\\k]pƍRI z<\. 1ydX  RHN>~匌 zwcƌAI... (((,[ CDVӭ]zG٫I455mV1+W2r!4)IZ= yA&M0a… ?x߿ߺuʕ+۴i#b1 SDѵkפ$('''wܙ^}Z;3--Gׯ_lor` ($IL8H 111y?Hܹs1(7n(nʟpBtHR'ϥ[աƱ~A?zܹɓ'94Ǵ+d^~!'3fLNܥK![N;tPSSSÇyl)SHJ?BE'=zBfff#ﯛsر(bP"IڵkjC#={|cbb,--Շ^n߾ /\P־m۶#GwkȐ!\#Եr?5tC3LLL?JKK[xqk I/kVXd޼ynI;v,Z) VN:=z?tww?L4ʺ4Ed۶m\fѣFsssKHH@wqȑիW^ZEEF%yyydR'|o߾~XPPSJ*\.ܹsuҶk?\bEϒХJĠAD'Oզp8:tJpppz*z@2e Dxb(3 q $TSSZijjnذaҥh8~ܸq֭kA5% 񻗪'(jܸqީM6ѭmH^***z=zDQtVXpUUSO Rx֭ǥI_zeab0jjjPF~qC^^~ժU-ڷoߎ;G?~\d߿?33ٳ 4iݺQTTD!'''~"@\\\2224rEQQѮ]ߒZ3e͛:thA%$I>}Ve聢(MMϟ?ӧϋ/RvS7r`wRyyyl6hI;wNJJU޽{BfʕBQ|Q5}Ziڵt+E''˗/O zXXʶ2jaAjDGGNj/f'`={~CV|EG[[{6lXvmsϟ?;88\paر-^FII <@rr*$iaǎ[xquL$Ih~$A"66^}ʕ+wW\)pS\.b2uI+8==1/OOOi~h$*3iQ5|t+~wա'n49sGQOTwkte)wNګW/( C`CZ#F NS@6ma(((`?dee!lff6` .p=zܹsGH@$I>{^m{o޼ҥK5#˗(O>Yস\n^^Lf+%11JԩݻwZXVAIIIyy9}BrtttϞ=]6~xU yyy[l j\\ 1++zɒ%vvvtkOޯ_?(?0L&[Ȧ&??IaOx-z5ZC***0op8]v2554 q#Iٹ?D1{l/(('Ԅw`ʔ)tMjCؠznyƎ[+Ajj*M( "22RFF..\sN(;99YF8ziiQii*î7onaām۶mܸAѻǻʪHXOqܹZ@NNNJ֧VWW#.$..ˋ.rssav?ѫWP7 ׯ?q\KQTTTСCi;?28UQQ9|)SL&-˽ҥK333۷Ϙ1ܜ}f̘B:dddLKUӻw﨨(.koo/VoZ  E555dmڴO닔SPPHLL[#(,,bH@tٲetC'Эذa|e%͏Z\R󨨨l۶mܹH˟r;v@y̙k׮)e&Ib^މ b P555ftt4,5k544ׯ_ddduuұJ0pɴ7}{ː  DdӦM^zEBO2khSNfiQ 21k2%KJ:*aJJHHoH?f8* *=]Q bv״3f=st k:u]%!~W`G{_n'"s-фcoߞh>J06664157 bcc2α:aH̝;W{hqb[x1у6mZh4S)y9`~:;;իK!n@ =dРA:u"ZmLF;:: a(ԨQ&RzxxCVJF899 Ø={ٳ囙8 g9{]p!C>}zRմΔL&jFΫWv1(41ʕTOĨD,3\;c>ZMw6rS YZZZ[[שSWNvCrrrFFvvvRO";;&ׯ O{%OTZZRT*Ջ/^n:ٳgU,\OD :[nD+J!Cr:D>qē'O8qJpDΎ&Fjդ...9`kf(J:JoehT*Ull7vEXgΜ13gΤݱsFe]53]0n͚5bڵ&EEE555i֬YhhhddEΞ={Ic$R}"fFRHOO2TBB'Fi#HOѣݻw;wݻQŢED4 5[3WaÆуh>Ot&{41lllƙdL'N\d =@FIEP*691c)")cbbbٚ";wG}RS)**'`r9;qmd2#/_ "҄ra>[3kCn? 7&}"scx2>M8#>|8] ry&M6?<ƍر B&Fե DGGH 1^bΝ;ɥL& 5"16mv73qްرcΜ9D5JPTTD( cP\\{͝;wjOʫVruu16qILLLKK#AH.'bkfmrssibв#!!ˋh>~LÆ /^ܽ{t2>M8COAؚ"#F#RP Dl)H `c5jԐ" HؚXZZ (kf")ݻw>>DO.ٹO>3gׯ)Np9s<`lٲ2>M8c1rȿoD[XXJP4k֌hGGG}ԫW 4 -[`;iibJ s̕BD]۷u2a|pp0Y ]v 9L2yɓ'KPH ;vDGG7jԈ'kfqc_&ٳ UXXxܻwOW~h{"F""41RSS))) 5s9ib-Z 8}hW%4'˗//ф3rؚYQF 0hsss!CљD+Gclllffw}gHM5kJk֬)n@C鋌3QrrrBeE} (7oPJ̌4M|||||#G|||:d0C'M4i$zf&8.99YyɶmhFEEmffF+3`|RΝ;۷GȜts1DwBzyVf>ԩST7oޜj.]4o޼QFJݻ-x"$$$,Zhɒ%"kVXQ'aZ3S켴0෍G~1333 !!AvuЊ:tо}{Cc c&SMZjIW.n@Ƴ LDqpp~N2bbbƍӓA",XDs$~囙8 7N޵f6BnJ7Nȉ͛6m*ҢN8qСBrr۶mhכLq~a={F?]\\DY3!41 ئM ':666Ge1>j0"V\Y'W0ܚـ3f̠AV(B23331od2e˖~tR" fBNZZ5q B!p888\zU'C}( Rikk۸q-[S={H"L8 of"h!Cⳮ -[L>q-\Cܹshsssoooħ7|Mcƌ!:((`DO>>QݺuW[3*EEE41 خ]; '\|W^EEEн{+WXzuh _30C%Z`%sss:Dą C> ڶm{aT*vP:nݺ41x*JqA/\.: !te eT8qߟ7o~]&NyLLLf 7o-ZTi4 Z7|btS?_xAq0rZA*V/^q'6oXZZ >>>.\05k֔[1c>h>~rʰ0[hqyՕ&V} d2!K'CF!$U[+!ajj*p HA1L/p~2$ƍi`zJiLȉ=)ݥTbFP/^ҥ 'bP֯__'xaZXX 42P~}Đ Ð011UR{{d(!H/DCذalٲJw5-,,h>ѣ=<c}>7nܸqLq} aڵ+ĉWXQÉСڵkצM۷ojdC |LxA\\э52{Vhb<^z-O$H7n`Ƙ4Uׯ_e|"b!!!!b[KKK:5i҄8Ϟ=[x3|zǏK7jԈ&`iڴ) .**JpDF{B;a3gΌ());/AIIITTٳ?Qz)v\yq%%%k|7YYY۶mWVMTV^ѣv &* rnn.oii٩S'5h --MCzz SF mPj}-~5jlݺky^5jԠ իkDd͢OCu2>( m<==:D;hd՞bQ,--L$~޽{߽{wĈ#wx+/F͚5_Ν; 'M ggg(B .hVZZ*J$sQȪUĊf!)DK6#F]ҥK5ؿw"O1ܹvsf͚!TJ/ϗ'#####G7]\\w[~XYYuY2ϊgG*JRx ѱD7nJ2L0..bK{{/qW\ׁ1^v-M0!Ci9vwwU6lþj''Ç///4331/D"^@*nٲm۶"TG|0>HƍibԩSGVtr$%%7Q255uss{|1LLL\\\M^zJEV@<8h4t˫L&oA>J{쩃_?wܡ>ښ\D )M ;SRRL;ݻw񉲳 ,kk!Cmgg1M WWWv^R)RT BVrHd|nOx N8ARݻc|?!yRZ'bPr9M e|SRR F;={233 d(kkk:1Ai޼yNNnnnڐW"TZUzDaa!=P(؁DD[رcD+J???uZh!!l?P(hb\1hwww61+~Z܀zȘ1ctլYT9ZjdLТE KתW.RT 233)6x`>-'>rUTm?F͛7>|HtV5j 'bPhbXZZ *55uDAؿիWCFM(J:VZ3חүڊCϧ%̄*..{nLLÅ0.]1nݺ5/Y&k4,DϬY֭[gjjq\@@?\41r\R ۷Ҏ;}Z^=#d)) *''G"xzzzyyVVMGa2V0ǎ{yDDDdd… nnn^^^sa3c,HT*Ս7"""v%d{5lؐxbӦMIq?&Ν[reHHH߾} SSSY9sݶmg۶mY&ڂju֭0j¢855\ѼzcZ677W5YQQQZZ´O%%% KKK E~~Gj򨛗1rynnGjD"rrr222>Us'T*UEwަM^|yGrppcf͚ejjڭ[7S㓒0ƥIII:99=NIIy....SSSߣ5M9իhZ]NG.]XXMB[Ggddљt^^{tVVV9Kׯ=j*UDZx17uy2I5lc|=GEE@&M01s4k c|-hѢƍ׿c|5hӦ ʕ+Ю];qxx8tc|%o0ƿbϝ;~-ٳУGzBK~~~G@~0Ƈc81{!C`wÆ رF1޶m=ce;v,xƍ0~x ((cf0aƘBCC1˗/I&a-[SL/YO1^p!̜9c`5kx޼y0g9s`޼y~ ,X19s&,\c}۷y۷/ܸqyڵk<ϓ)/_y~РAСCҥK<ϓWH.\y8wٳgy'u9N>|08p'Oy< =*j̬&cnn!I wi rT|'&֤k =2mT*F|uãk׮-[$iP&*>ү_>88!1.,,Dխ[cruu"1* !1B yOKKCTyONNF`_|j޼9811!ԲeKqBBBugϞ!ڶm1~)B}'O "^ÇB]t߿!DhP݉7ٳ'&B{&BoF_ƍ`w^"˗B&>BhС/"FA| ШQO3f )B#8}4Bx'ODرc!9r!D{Lk::R3gWJknPL Eť?+={6|Jϟ_{ܺukE}Qoi: nC&Cs窫p-Y;YGjnEEE*2V;Rh'N2dH-}}…V_TiS:yu Bvv i-ү_l_O:U! fho?zHrT5ªwUˢ#NNNb$?\q\cN_'qHRi8NqoVB3gh1D`{?-$(uBff&_璉(>Ȼgʉ__k?GqcǎŦZ`:Nhb*`|ӯcǎϘ1S0rz/zUٱc߽{wQzj9%%^}Q?_óD. WlU3d 5^UKRUT!G`5|^2`(/Pկr<<<puuqF?W_}YB)ݾ}b?zՂڳD[ni8<ӯ竐~2_W, f]ϯo֍=ϫ?Cfff t -((h׮baaQEEE_}D51#K(Gs4irڿNqqƍ۷oߐJu#UVե̆T!OӰ0!nu̺Q{B\]] /++jNCyyy={T9ƍiӦ7nݿΝoVեIMYRTTԾ}{3{nݺĜLѣcǎXbРA|جYѣG*߃7F_-=` $}Ԟup)S6nܘ.J>|>I&U5(p6 Lz^ <|p51)K(EEEQuf͚L}Qc;0T>W/HBBB]b0T>_8nƌ W3gδK={ܲeKM}]u9ؾ}Jx͛7[hQ`]J4Yʺ~޺\ ٳu?h2^Ç9sի>,--H$vvv<Ϸm۶cǎ})̙3Ǐ믿>}YTTdbb"H\\\[hԮ];777TZv޽o߾7n<{P*Az: =T:FBBBzf 'Ν;wk׮ݾ};++ښ!C# 57 免 4biDAJ`ӈ  AA*M# TFAQTTTսϞ=sγgϚ4iB)|rjj/_NKKsssIWTT=\Id2% WWWE-Juzz.//W...ʕ+:33SI^r%++ٹ&]RRrl%]\\srrxWEEE׮]SԹJPI999)ꂂׯ+|%GGGEw E]XX3335 :ϟ722ݻ7!D,SJ !&&&RB)"H(EEE JiAA!ҒROBlmm)ل;;;JiVV!RAqrrx<J){ӧf?yҴiSJG!ҔByLJRz}B/4))ҼysJݻw !-[޾}ҪU+J[!қ7oBZnM)q!$((RzuBHpp0ի6mPJ !Ҹ8BHv(.]"tЁRzEBHXX… N:QJoB+B)={,!k׮l2/![n_֣GJɓ' !z8qһwoJc!}9rү_?JÇ ! (,,422ڹs*KG\onzܸq<=zR:vX;F)3f 'N='ORJG<[IyĈ<_Ç==R|ff&Ϗy-[>><PJٽ ӓJ󥥥ʕ+KHHH5jԬYx<.<ϳ se v.YYY00R|8y~ĈӧO| &PJ:IZju…Y!f dAAAJJJPPGEE#+**ZjE9v.\(5PuQQq'Ow}zzzYY!$///==ݟQ5jڟS9뢳e2Y-뢳+**=zNk\N̬ejFB222a?k#<Ь]dbKKK)Ga~fj;uҲ|-[LOOd1@ldr9~k|aȗicccBHEEEzz gKli"VVVe2Yzz:[P@;;;vuu%Ht{afsrrj_f=+Su?yyy/++S:GiBB۷ljjv"BH^^ѣKKK4i¾3bP[ZZl2??ڴiQQQ&L@off&!m۶ᐟPB?{l F\޷o3fBZnmjjʆʕ+fffr|ʔ)[laĄӧOFDD|嗡ǎk޼98.,,D0vô)h~##0v4aݔⰰ0kkkƇiVqXXabbbnf7AL SSӰ0솈i330vtӦMA$IXXhooo>>>ss0___]LUt-,,@ΙeXX?耀lI+++4;_q5췱~*+-- Dsyȑ#J[l< ߱i44ؠthhhrr !d~8~8!dȑC dNbPgϞ8sxAwwwHLäfqfhǎɇNMM(++Usss4{ MCtNNN-Z*6Pgggעe2YuVVfyh̬Eꌌ w^}UBHhhh۶m#˗ϛ76DDDQÇ\ڹsgX$%%EDD'''ٓ=e^>3Аf!nݺxڵ y/^!q׬Y3???f AJJJLL {oݺuӦM"D۶m#G΍7vYQQq\ZZ@ 7w/_D"ʘ܉'X^[dee]~iGGG: :zH5hk|7۷Ccggyyyj홖H$j,_3vX &ݻ7@ 1i-CbAF: FDkrMLLkD0"H|D2###رcl8( Nffkטvtt n< F45=J?kD66601??_^$++K#˞={ƌСC}e5"#$p]r2(--kTPEFEE$T;Pz IDAT*ERZDHxޕ#0Vadd=zAG1p233^ʴc6m &AxFڻw/;vDﰲB{666LK$5/oac5>x >>^$>}Z۱h///XܪUW^yEVrtt(Jae&Ł zu8rC&ט4usBuvmG!ڵkwՊ H4k֬%KB)42 ZDX).]>"%";|0߿"_unj1M-tss۷o_۶mOFFƕ+Wvrrb$ D$Qq* 톆vV[QiǏ>|lOZZ믿j%'''H ẌOK׈RCD\QQQ^cdddLL -[죏>nHZ|ĉ۶m=W^mċUTT(zt8: … ?cl !&&&7ouVBBs4qqqL<*D-FFF8R+![D8Ж( $;\~'j%xٻw@ zD"k­[8ڵ cԨQB)Ts\+ i֬V5kؼysѮ];jQ|Z>#TTT@b.h-~ATrl5:::fddh7sN:1ݹs[\.D5" er|D£lj<Ǡ8^@GT6HU8SL 1,k"@\\P}ݻ "D-!H`ZEYYz)--$Iaa Gݻw1Çlq8 xh߾@(`  M (/GRus@CE11 |+HOOtuTЉTbbz!8lذ?={׈0LMMkJj% EEEj,_v5|pGi;)yC8AD7AQ0AD7ݻ)㠣8/^vvvرHc8Vkz!FikH$j,_F$ !qd޼#H$rE.G55"h1E14yŋvvv \^\^<ˋ:m׈yÇG9zٽ{w%xώΐ½Q$U* Z _p k9kD.ݻw3qt.\`jRQQb;C&-˗JL(vR*ɘD_6kDW\k1bzHU,,,kD4W@gGHPj'f`ll,Ygbb"\-~! 1k9kD.;w2qt7Ϟ=P’ݨtϟCڹsg!jd^#va:vB#r8b0qӦMpE&PJ7nt+W@b59F*VVV5"?5†ܳoذAiƍ###m\\\ 1hllljjʴ򟘚 8:" byw.]?ydϞ=׈ ];v CkT22֯_#M.5+B"Ja ;ޕ#0ʺ"j,Qк9s677:s !;wJ Eq={T w(>#J[xII .H[8y#Gdܹ3??i77%K?&o߾7j+$pMLL| )>#P-add`ll,޾}v$ׯ=qDcc'.^ٰa66mh 1m&t>'\ᦦT*R^{M11^6AGQӧOɓٿ4;wڈNHKK;{,nnn>pk455Ŏ#())xˇ+b1p***ʘ4_{3=]  @A1pkD48zhѸFJƍasʔ)'O =M6࣒m۶SSS`ͅ{!queD###H SSSCxԩ^z1mnnѣG+D u5"hqQh|(Ng>|++Çlsmui77nݺ QKii)x>EEE-,,|Dd5ʼ|E3ɓJ'8qJ$\!¨#5"zQx hUhi۷xW=NٳÇ !%%%34n!hfffii4l,,,{!qu5"X,0333K.EEEEFFV='222&&鰰0ŮAAD73fC$h]84iRM4)66(\xΝ;~~~RHMMUw.D-%%%4DGQPPJ{#` 5.XիW-g*>l=:^#b׈heqرlQk6mMU|0ӛ7o^t=<٤IH F ;;;ˈkCLLL 1,,,׸o߾!C0mccVeee999ls޽#5"4Ċݐq3=o?6ׯ_Mc#ٳg.wwwx z),,0rrr^SJW8充LWzK322/.\PK.A 'r%kD ͣ5›(׸e0*T(}Te˖_Q6M4ktχzu Yid@bXZZ6B1$$L/Yd|>ʼzj-'׈ h-W˻ k׮s*1(>}zq6mڻwo!j)(((++c;#++ n1[8U0q{S^^ϴ$Gӈ47n@b 4N8?dzϞ=P1bูYRTSʜH$4b߿L1ehFḻCb T5 ˈzu8LMM!1רkDi48b<}ѣL7mڴo߾BԒf666qD0.(L+ o HMcݩiYi;q Hpɓ'׈T]PykDTN6kDwwwH FH T w899 5*6Ղ5H$TykD.6` O~K!(OvEP5"$z:z EvZ8b<~СCL{zz0@Zkǎ#HMM Rƴb1pJKK633lC$aӈ0n޼ .Po׈T[PI&L׈^3z!hoo)2"xj.qYTE"@bۣרs׈ ]k֬ CC ǏֺukXhÆ &MnH 8: fƍ'OA_I+hc&%%MheգG`+<<\Z222`8\ZpˮJǏVA"Nqq1<jnn}B)߿c`q<[ZZj>TWWWM\rٳgamʕ+i,EkDzQ}]1L~瀀Rz}?iMV.+&cPC8y///ˈzu8!1tkdܺukʔ).]b0 ƘiӦ-_ZQ!&bE: Z… ,ؿ[ i433(--۷ަMCEqB rvvƎ#x!ܲ{yyH)}!ӊ=H)**JOOgB'ƪͰtғ'OtUV&Mj۶Uؘȣ5"ZFD({`hE!|}}}}}5Y @w ܾ}{ݻwڵ&<^^^4v 5B֡׈<רuƍuVnڶmm۶r3ӱcGLj 믿f8->lr֭ ,ܽ{=ѣG=:x-)>ܳg u>\kDxhooo{pŋLWD"j،;~[*MFqMm,8!$<<\Z ٳGZC*pXў"H[8ܹcĈbTS^Ѷm_p!""ĉRcǎ]v3&22yZ RxyyAb TKf͘.#:FpvvF%J^1c=ΝS/'N#5UV%/zq#G,Z( d_~eo… HIIQ_{5!jIKK+**b;#99nٛ5kv199i$b>J XX-r|Ϟ=>cff[kDzQ?^c}8nȐ!ޱcGTTԭ[PiiU֭[7{s:88h1Nktuuw+Y^#XZZBb:zRQQoEGG߿_鐵|ب-VAD0+V8: ICFL&۸qcllGϝ;wZyV}IIIٽ{7>>>#j$55i777e ͛7WanpD),,LMMe5DYYVy~Ŋj [kDzQ̙6kT&&&Δ)S֬YtRX͝>gΜ+V,X`ڴiZ!1Zl4vy悮 Y^#XZZBbVo]|yvv!OOO?t} @/_qQ0Lhyyy+W\jU^^!__E;H]5ڵf͚ 6LZ={^c&M0޽ Ejݻ4q-ZPcRPP^#kƚʚ?~Zjm۶ W5"ZFD({}@Qh,Y,]t͚5Eo߾=zkoѪU+jiҤeD-[ 7Q$A֡׈VVVM4A% VٳgO?o3ڷoۿjGAjA ^#!: 4i￟7o^llƍy>>yM6IeDVZ 5B֡׈֐5D^cM\vmѢEWp7t@G bP-[qQ@EHH޽{/]qQO)ݽ{={F6Drr;n֬و#ɓ'L{xx`aܺu n 5KrD"p"~_@Q[^cM9s{UFFF&Mb{-^#y u]>}/^7nܺu)S>ӦMf͚AbG֭.# H$Ecmm ѴiS_ ^cM8p`ѢEW\Qojj:cƌO>E+!4>K,a8( ƠA{ț7oo槟~5kּyR]rrۙ9rZUѣG0ã>b$&&-{@@ڽD9 PcckyRQQujDOsrr^ zA<^ "F$3fȑ7oIIIC/̙7Ľk֬$pOzzz\2"@֭YƗX-R_~Yx1o8::Λ7o֬YZ AD>305ҵk.]J\\\>3fI+zFROy!kŽ#HHH[@{ L+>8yyy=bke˖UNӦM׬YS^^^kDzQ.\6p^^cnn>o޼Uܒ IDAT3gZꫯͅCO\ll쿊㠣`4_^XP```ttСCk%tG-D)))yyyL{{{WG ׯ-{ppڽׯ3-X8/ר^cMΞ=pBCC8PkDzQ#""zWWo棏>Zxa!$>>~РA:uݻ|}}!1۴i4v 88XPPE[[[H ///_T͛7WTT(ѣGlll.] N!a8(  6̟??**?p]v߿lll퓒~7vEoT~isrrn޼9vF\\ܲkN^c||<"(44T#KNNtӏ^#z?G|rppXdIaa ׈T zQWQE-Zh޼'Nnݺ?<-- OV\93gkX;t4v]vzu5"$/z/A.^zٲeJ4i`iӦ)#ܧ~8(&4_nٲN(,,o/_f!1qDX\{-III^cDseeo߾ڽƸ8VE $q^c%kEUN7o,jFZkD4׸x↴HEgcc{}}]QQJJJ?~=|jo[hѱcGˈ:tt U:\C!17o^Kp@!xbҥk֬)--U:z!k$`aܽ{Wk0a~yʕsr8tڵ!Ct!66_~޽{P v?;vTx%VE {1+^#PG&RRRLRc8]v=}4; FZkD4g B-[BbF*//_~eѿ\.CgϞѣG>}bcc[l٩S'бcGA5BFhѢz/AQ###wU5 E ,`8( ݻw7nȴĉU.* `ǎW^]h8pݺu6mqrqEiԩڽƋ/2-X8deeݽ{ikDhX/^۷oy٭[w磌"DA<^%KzвeKH ƚرѣGϜ9qCgΜ 0aBDDjDN: 5+L׈-[D%5jcǎEDD?JMLLNpBwww "?>SAG1p޽~z[j5i$Wѷo_wwO0R^^f͚ 6̘1c...jqΟ?;wVxVE ;w0^c%kPX -,,͛)Pn^#ye˖5E%~~~͚5VZeff2g=y$::'}k֬y?C|йsgA.]0^#888@bL&۴iSllÇ͙3߷FhܼyUtRF]iܹ5N4o޼wy\]ܹspޥK{7ӊO"Nff۷vtt|yF{UP-))Yrs$vuuoKKK .5"Gk/kD???H FxFG!>}w}_B6{/O?gDR5v-  5"Gk\|9@ 1|||% ''2NNN˗/?~ ݸqcذaڵyW1u&Jk޽;fff-_!1k| ?ݸq#\IΝ;K+!׈̙qQ@ ۷oOLBrME\nݺGGGoݺ?w={Źk_ݻwWo}Q|Z1p222k]k֭[F1\|Y-1#FD({_}UCZZQҪU+H ޞ1T2*ѪUm۶]~=22r޽T)8|#G^{蠠VMسgOq^#899Ab /_^hÇD#GFEEi%0A~q+Vn4pu1?m4!jIHH`:((G}#""N:rmv1nܸE N:={TxiE"Q=X8ddd$$$05X'Nܹs ӧO@"*^#yƕ+W Ü_]銊 x… #""޶m[hhvZk *((`Z]F%zիW.Z(>>Kumڴi ,puu(SPwL׈}k ;8: hF#!$11qLnz̙Brʕ/^0G%n޼{nvs٭[3gT=xڵSLxz̑#Gcj9´޸ /^\ri kFC6wQ\۟ٝ]\ @ETj@Qt[F1+PPT"bw)FƘ;u#>{[kjӛ7o^L/iŲ)SNVo@>{08@vFa@zqqqykٳgԩSL)..2eʝ;w-[:Pz^\. #1kDի0k,F4իWuԁ/yfCCC-d eY ryaabff%KHlooK,0́kffF;+f߾}=,,Lp1>>ժyz؍7kj޽{o=YfFF؉45"cqҥA n0LÆ AFWWWOOJ2;;'NHJJJNN~ٗ޽{[n;>GXXUy$FWpqqAӶm[4o(333)))))ĉwT*˗/ڵ6C+axLea Y?޺ukMdff&W{{%--ѣG$vwwӁ{.III#aYvڴi&L+=%..~V͛7k#1*"q=zFbHؽ{)z7nܸ@J^z^#}>-[Ftku֭[6n(v:Z666~~~:uӰaC^!2>{,))ŋ,޼yFֹ=zѺu߸qc .((pqqW.Cټys^# g")ՕTPXXhѢrʑ$u.3 boH~L&O֝eff_ ݛV, SXXؤIJbelllγkpŋIz KjjÇI^"풖ߞذaL2UTy) Œ%K {E{{!1*"q>|J5j^cTT8?{9KMMCR^'O~*]vQ^#"{˗/ܧƲeN6L"gNʕ+Ǐ9sfqq10ݻwOOOe땽=C +0VVV͚5TX)iϟ榥:tطo___*URxx8U1""ĸLMMAnnn:5a*W|}3 .eis9SN߿;)qزe  Θ1cADիM`YV$w0LLLO ر#,MOO~.\XhLӧOpScbb" 6\z5qOChӦͤI 2!ӹ$kܽ{7r(_ӧOX'Ƣ"OOOcQVZE~ b yDAwʂaaa(i׈h+V5>{JX4tի* \,J z^#X^#qFQ]3T>}jddDbccǏ褤x{{ĉS LHH qbb)ޗWL#ٳ'X"q222^!Ch^wP dΝtUV{;w$1*"q#<k҈t^Zkر#U*FpiZ.)8990U,??2e;ڵkGu_cH^#0JN2|򆆆VVV\\k&4Fz0 שS'du։(**Ͽ~BBBLLѤI  썞rܹ9sKbb"x8̦MkС^cll,ryvl_ݻK@&'O cccANFᛁ#n%{Bz"sNJ|!11Q6Z~իW|rrrDɁ?ϩhm͛7иJqD 5nذ<}şzUi47o' 7770JJ#˲0P .]VZ%={v֬Y$vqq=z4^;^cpp(?>6lQ/*8d"q޽{q[ZZrU&I4.]F.Cׯ)EPri6- IDATDѣڱcGPU*FP%Q/XpJDH|Az P%[Q~sΰ?,rK.$ƹkasm۶7!Q(((x͛73g-\FtSSS-cСMP~_fʕW(OA7770JJL&۴iHYQ덊 Κ5yT Μ9]]]njCG-,-u.]5­2rs6/w=v---KkXڵݻw?.S}4x_#\x-}k֬ٴiӈ#r \bE!WVuFbVk?˗?>Ν;7m%[h}v_Ox_6}_kDa8ZM"ea ʗ/_|ڵkjjܸqmڴp0w]v5у B5 7 F#b.Dt֭[_e#J Rn]gZ><Þb2 Nzz3H6n8>|5~pGGIիWؽ{wF-l0Dܹsȑ#$oN; !~tFKLL UtӧڥKPHR4"tZ.)H~~>*JLDAklܸq*UH,Ƞxtޝ}PV-_%(q\qjLMM]yi1;RZ5-cРA#,G:u*##͛yyy jժnnn۷,M,[^ ?>QRe2ٶmDJ'صkĞ $3Tv E&DRwrZѿ]7g|rǑ>믿k\PP7)|aFe;)6uTO4F/144TjGAXv-1Y:tÊ+kի^#={qD}6kC U믿@Xt) U,#Gݛ,5Ɓ?jh4}%J҈Ξ= P՜ހ;PpBCC,5'hcǎEDD{a͛GGG`ݛ}Pvm +-S###?~X|X$0%%ۛ>>>'Nq#H'n>_ɁZjE[ u"GHC2\xx8Xݽ{ :Bjj*kGqwwaЛRkڴ)lú5,[ldD~+ӇFxFa8;;s͚57!D111т0F#F+>>TEa/^LqBAu$899QRYHT=<}z$򊊊˞={n޼I-Z!q{2"qnܸd֭[;ty@rssAeʔ^cڵ4"#G(2F3l0T*,!##VKJ# cccJmX`TFx׈ GGGq& ZFϞ=iw#ͣ8qG0JJ#˲ Y-W0EEE:(s)SF.34'3̩SƍGb//3fe5FDD>ph4& / ";' ƍ{!5w XreĂo>}J*d#}a5jÓ'O@e˖ʕ+k4oڵkn~:̬mmm+V(JFqРA{Ç'J҈222@j4 )..>|p۶mHL`/^'ȈR/kȭ[bff(9߿200%!CP ^#X[[0Y]?ӧIlbbrEzuǏΝ;#""MtaÆΝ;… p (.. =x ylݺ5slppXâ$qOOO2pdp~~~RRXXyɓ'RnϫT*sԩcǒˮ]nܸA-[2pɩ]7ocZ֮]LÇ=zójժݻ… `ذa{2"q_{n׭[p8Lg„ zfff]r}6m^a4ͽ{`D[hXfhhrJ_Æ 595"@ݺuALΠh~gp)igggk֬/xСCoa'''1 >>>W\?AD $,?8ťKbcc-,,֭[ͮ߼yTJOD|<%%e̘1$9s&^v#H˗/;6..8''dqZڵkcfͦM& #F5nB1tPGׯܹ6665;wްa7lбcGqIy޽Zǎ4lؐ/(Ɏ;&NRO?g֬Y4zپ};kԩ#""χ}#GvXPP^#dD\v5rAt>}  w5kk҈ƍ^/ORaiD Z]Ra###mllHuFIk7 z`cc°$x&TRE ڵ+.}fϞM}DOiذ!_?XMHH)+DHNN{^%۶m~:۴iG0o<55J 瓘2"q]}vrIIIFaTTR/k׮~Xk PTXBff&CVF7غuz68"ȨQzDlmmA 6|}}MA*Wat҅v>2}tzsGu$QRY"qGR"uk׮XV!̙3FG|Buܹ$V(sD\zu۶m$WwI^#0M[F;;;,aĉ5X3n8T*,ŋ Z]RaT\R/mڴiР."O? (^ckk °㼽MA*Wa|@(zs7c z#z_%Q&8qBɓw޼y4zٲe xm۶Ł#B5knj#*xqsխ[^z\JJ y@={066klذ!F0yd'KIR4"/0juIiDAo˿Z#1 cƌހի°<==MA*Uatܙv>2uTzsGu$4hЀ/ ˋD,&''[ *ѣG 8\re˖-$_>w)^#<{ abbB 6!L2Fp&NHbJ!\t VKJ#>>> z^cvIuƍGk7 zP^=!nBRR%-cǎ@ɓ'kiӦkS4hFIidY 9qI`^r۷ w̘1F&L|BJV(cǎqDrJll,4h jժQeƍ5:99aiDӦM2T*,ҥK Z]RaTXR/۷wtt$1E?~<3T g"@A 4MA*VatЁv>2i$zs7ez#zJ()2$'N:t(ii&;tGƉ' > HOA$˗k_>NkDϟ0LMM)Fggg,a5?^J!\|VKJ# z^cHu&NHu_#q_#'T4hTXQ h߾=.}d„ /A aՕD,˦[$%%ƅ eƍ5vر^z4zAiӦqɂOU jF˗/#q 3gΐ ??QF JƂꊥ!̘1FpJ!\|VKJ#~~~ z^cǎa"L<o@׈ׯ9;;jAڵ7^/0LIidY IJJ W%+Y… k֬!w^#z ammM;v!DGGHcμyHR4"ׯ0juIiD 00Ao_cHuf͚E UBo@accڊ+WN hٲ%.}d#G>ԭ[/Ȳ, C~F/V~#B駟k;w[Te ĹpŸIbڵkFxƆR/v100K#BXhx4,X* K#B~:CVFAʕK=Iu̙Co_B?>6/ nݺ& ʕӂ0"""hw#C׸\.oS()2ի"eǎݻ7w\5aԨQ0j7opUB{x ^#~Q^=J޽Ơ ,_~qΜ9hT*,ƍ Z]~< 5$ƺͣ55"nݺNlٲZF-hw#׸\.] IDAT6l=ښ/Ȳ, sر^z888x4z?.\@^z!9F ,|BJV(sqDXr%7o zWpٳFaiD1115Xh"T*,ƍ Z]R a-[R/={ &1E?>3T ^#8::09+++qBtejAᴻ@Ak\.2^RN0JJ#˲8DGٳ'5jb X"##Ľ{ w1 .|Bvv+Jl8ϟ?Hݾ}<׈o޼aPe޽56iK#BXd x4,^* K#By&CVFA*R/zjԨ."@tt4} kDGGG5WV-QAtJa4oޜv>2`zr&=N:|aFLv-qBtGуč7)xaY|9x}#B6l-Z$*\P("D?U899qw!5"7o@^k4"K-xi!J҈n޼ P\VVy3T 88Ao_c>}4iBbpB ^#8990ԩYZZT*-#,,v>ҿzkS(),@8G{pϟ'q߾}hC qłOUJ.[@$ιshggITLr[n=~8ܹ3[XXT\'?wKdd$x;wыΒ{Y_pa$vppԩkԨagg'N~a``^Fv۷IR~Ze۶mmڴ!Z޽+nBKIX4i5j o߾!!!$1??|Μ9Ϟ=[_~5$F@VVVlqq ˝;w6 XZZ~S<<< f͚$d^_:XZZ֮] Gi1]ȑ#ݺu#q&MVZEe˖;w1((h͚5SCCCm# <P]dGFH+Jl8Ν[l]\\k Bi f2:۷oAz1,,L100 O3~;x/),,HR4"[n0juIi,,+بQ#F2e(ү_PK.24'!M1 C եKX|K.$nҤ6~W8p@)? Khd5o{pRsYX=|e7JyFݻw l42 sΝ;[v-^,Y2 4ɉF/zn0L~ ]pJV*p"qΜ9bpuurrrȃĽFCQ3Z޽as!#""\včFaVZ^p T*,!++ ҈(qa&M0J%^Nb)EY kae˖Q=CuŊ$G FD݈F#WjUdG(qaCuԉ!!!֭KLL xƁ5khdO>0j\|^c~HT*a+"qΜ9dr?&52g7Aϟ?ÇklٲK#hdf5.[Lpqʕ$VTXBVV#??믿ȃPO,M4a8hРK.2g7a_[ S0jժ^ܹsEh4"`iiyf4Dt8, bii٣GCuؑ!!!ׯŋϜ9C!C8;;EhѢ 5{}!RLd2E9rD hJ.׫WH˽88-<<|4zqΝ0*.EEE߿wQ566r X[[( wI kh4>}VRXR~Z ڵk7n;-addq,sٶm[6mHV\qs=z2 (vFZܹs,s"ӤI}nժuaȘ KKKXgX|9wbŊĠ~GdC$ӧOv/q疖ۧ"N>mgg?|+++1Oݻw?J*$3L޽{` biiyWV N9x`Ha/$6lXǢ"'OXN7oa-[ܹsu+={׸j*^zXTe ʕ+o߾d2.z֭{uW=*jZZŋI^c^^^Æ KdٳwҮ]M6 9+++;;;_uK7!!AB??L&VZrDIIh4/~tK… *J>y,AʕEi„ 3f L&%EAAAϞ=ڦdQѼ| |4z9v\fM|ڵkk4ؠA͛77h@K~~~>}Ŗ.] huAҥ[ a5qpꓖ|ׯ_7mOȌ zkl׮FqF.oPTX½{@޽]T/!!! z;߇޶m[c]DիWS=Cހ0 5krc& a!4bmGt5kQd^8P$nڴill,^,X]]]i]v}k֬k޽;JUl_RSS.\HbŋF(((ayR/5oK#BشixWB8._RaiDa4" nj1b]D5kP (䑯QfMш^q?.}}k?㘛Qd"eO6oL秥ā#Bҥ k\n^#\[T*EӧOGGGӓkDBUaIJJcǎXBll,xpPq* K#B>k acF ."F^#q_#xxx09(+rG D h ˩zbnnFIidYGa[&qXXؖ-[h2o޼T5͍F/ѹsg7l H+J'l=''d׈ wQĉ5vܙR/ޱe.(,,#,T*Fp}FQQGhD ac~G8 ~b ^#xzz09J%nB"˵ ___] RH\ND3330JJL&ĉ$qfͶnJsO?!tPݸq^#+u 8:u FOOOvIk|́Ξ={7oޔ-[r&&&7;S)**a#8'O/^Gm۶.(,,#,p A :ŝ;wΝz꿯}ŋ/޽b ez٩S'ZM6gQF4ddYsP( *T`bbbnnnkkWNmflܸFx_pTann͓'O 9ccc/((*S̿I۷O|dG H 666'N WY䈝N$;;~悴ת;z(d,v: e]?$GT\iӦÇK+/^VMw[jEⰰ۷eΜ9O&ѣi\v-**j֬Y}􉊊\I`_MiR)sԩsˋ]kk|eƍe6l_T*[nlٲӧ?}Tu"'99F8Gϟ?{ŋ'OSN={ܻw/4uMj3ѣtBb YXbŊ-,,%33sɒ%+V B7n{ƍ8lڴרT* X/@fffk\t)4kbbrEEE?ÈAQN2|%Kl333*0OLUWF 0JJL&w$69|0č51e߾}-Z qwI3g¾ƱczxxEﰴ SܔZi-[5k׎JrӦM6RRRfϞMbooo$(XCoo5уR/RSSڽ{7UT^# 8z,ڵk;::a;V>>>>_xQLDa˖-T5F>W7F\iZJ2221eYzo,"f" 0OLF_$:SSS08IJ,#I~~>ĕ*U1%..5ڵF/?3k?~>>L\#1QEDa3SSSJ2T;ٳgUT^#Ç R[d!JyeŊGi֬q~F>J-l۶m5F>W7Ԕ+}? *@i|9oC r9 ȦydTLE355 4dұZ<77O<7}$..^Owˌ3RRRH|(SL)]]]aӧo5cz"'OBlgg'b&}vgԬ=QzJc *#GBn,#1=X?%%yf͚k\&Q2z|ak,5^%9aիǏ ?ݻG^jۧq5N8Q $fYiӦ$Zj^Ν;Z Rm6_>:pFe{=axbJc5iGaYR/{%q>}(w,\>6jԨZj$Gkա|NuѣG|ap8pZ[ڵ+""ڙ:uɓN ӂ0&Lo\]]iw9sÇXܹFxFaF?8^RJ3f0`yӧCZѣGϝ;_MMM]ẙ[) ~Dnnn֭e ȏlL&e_V]VVѣGO:qͼtx~őG999>>>ﷷѣǾ}>OT'юLI&yyyE_HIIС]r+WtwkS bwi򏪫]GߢhlF:ɓ'ONb??JxY ˲7n NKKcÇ#F5jT >]_^lYTT` ²,ާ3g~QExbLL̊++Wnǎ ߿(RSSΝ;W .rx\rF7!z/_íZ:z(0^i֬YhhFy߿yfEBXX1qľ}Ņvw@qq/?wҥ'N# y\$Eg׮]T5L~:uꄄxzz[ZZVX͛7iii3g>>!!ٳYYYo߾UT+WZw}'d2snrI͚5mmmNM8x =T',999 *ppPTp96maL<$vrrݝm.5SLk7`ih_u$(S jժ}P D -`L&S(4z)M^2,$$^aΜ9$(GR飯mmM6Ղ0N:h ;88NߑGu_#q<,[lvww?p@ŊNJ`@UV4-[Vݢ64h$5׸L&e>GAb//WZ*_\rH^#߿_ ^IN8A⨨( zGhh(xk #A\\>f̘1aݻͣSL!q@@@Ii}H)Jr9޺ .9rĥc0U#cǎQAu5#PILL444_wǏAUT^|)nBaL6mȐ!$/߿o@abbAi,_HY!Eaa!l8!I5.ɂ鵏)&&&|ap0q^#hk8q"xӧOG!xAƦM.vG>t'OL ҈^#r=?&33nͥ w?~C?~ 022MABCC (X}ӰaC! o@z^c&MHl``pAG8/((4FaP22 s%(5"@RRUT^#0U剛hA3f9r$ׯO;D_8rUހ5"@`` XreHP/SI* J6hЀFCuj]&kSTB" *ի׮]믿޾}K$ ;;[.[YYaZZǍEya5k֬J*999999=~:EqqJrvv^zժUŚo }ǎkk bwmR\nݺa=zdeeUZjժxP šCn޼IϞ=-J&:…  _~ukkkQrP( z\|?}R/_ƍ;v [DAw}۷I\rUVɓ'_{gՙs{oFADA":D4VƙJ&I%J;ST*JŚ-5IF%qR$&DTFAdkḟyTumߧ?M{>}Y5xcwww||$x ?022#""B  4'OYd2ڵ4 QZZعsK/DZ*`0|ᇤNVK7gsss]p*TuF&Yt{}w$ld/sss% %FHH%O>bIId* sh4fYHv;ˊ Ib slzzZꠤߞ-|7_}Ԃ ;RG$ =իW  k|-[̫ܹs ޽{7xkܱcGee%?OR x^oܸQ gϞoy.َjma&&&M&kIOO_vmhh(['N ~~'9s _).. &>52))iAKQQYb_WsV>^?Nguu5iLVPPƒ|>,ZGDD0鬬2))رcommm%7!t:|ͼMw^#Xf [C^caa!iF6v8/2钒ETT9 Z}ׯ_7aչkdYǽFbHHH^#G2_>kqK.5Tsss]5^tL&q9.cccxBql6iF"m<Iȹ _|^#Ey555.׬YCZV;sԩ[hz5r%;::0q7sQ__Ve91kHFGG"&&&rC׳ZId,s266Izɒ%dbHǻlä5Ͳeˤp< 93^ /PQQA;9իټƋ/k,(( hΟ?ƒs]N:/޸q"66^ph4,1ɺa444kdYǽFcll%Frr29Ů :Nx8^Bcc#K.Id s2>>E:((HfI˰Z7n jCCCp< 93^SN~~wsϑǼƺ:{5Muu ιw9y /@TO޸qchhhvvVVK0)))˖- ukjjb~i"vuu) J[,Dt8ahooST bjjJQUUN׳[_&!!!..f%&&,uOX-1t uV>2Z9vٙ]]]z޵܋rZSPddd(JJJ Cnn+#=sss'NmɢRbcc ʕ+tνEDDhii1lP211!Bff`9<̯ZѸrJq8o֮]tNӭ'X,vԑ#Gz:ݝtQ͛;;;]qݻwtvv[o =VRRm۶+W;ݻw7o~uuuyyy/]777 ñgv}Ϟ= L766ݻfۻwoSSӗ/_ɱZ#=33o߾f[ZZ˯\=55~xլ,ҭYYYLG׮]$֖966ƴbٿ{{{FFׯgdd2=22rtҝZjՍ7WX+V2m2>䓁T҃===~mV[\\{/ĉeee~~~nSh4]s͟o[h٦jR^Lk4BYLk;聁;h???\6ͣi;;耀LX,w޻:Nd2ݍ Z{zzT*UUUUKKKbb{W^^N޳ `Zw>G333AAANgrr?ꩩ)N7_߼yd2ݍ^Rnjw6- `1=;;׷lwv1mZc1mَ?T*VΝ;l0ADP(:3363S(HsoU*"l6PՈhZ@"4#$Б@!"̷dD~K""u "N888aaa800QQQH;jEGG#"x4#%>>iY<^&''#b[[ bkk+}xHKKCĖXr%"^|9RYYYHcNN"{sECĚXz5"^p>D:֬YUUUvZDg?"VVV|nذO< %%% JKKM6!W^ywEk~ ۇYYYDekDܲe |yf8~8"nڴ {Dܸq#8qKJJɓX\\ H9sׯ_n:8{,"FΝCDڨHf|uu5"@ZD/"bnn.!bvv6((-Ѐ4 WZ͈H---HիWq@7)))ֆIIIpuDLHHDϰ iRVOO"b& i>Dٌ000aaa0440"###tR-8""" "j4 ??=JP^^CD=L&o[hmPP"Z,+88iUe˖!Ђf"##oA኉AĞCDc7p%$$ ")+\˗/GīW+vkժUx•vnn.ZjkkEC| E["ٳg`ݺuxk:s !"-U\\.rv6n܈' "~eDDDZyUVV_~%lݺu q̗@``?NwVk446 {cǎ&7o3l%K"++K&5559~'m61,,wߍ1.~?&&СCtk/YZZJwyuuuVbώa }g裏vA߂t))){q= G-//h4._=;r|V $$M_T?"J%"jZE~F#?hZQoEQEDQJJ8EQ~N'" DQ>"Ҵ˒%KDQ~KH7#"" 88XE E:gHS?ղeDQPQiUR.b||<DDDH`udd(W%"ͦ MiѢ(&''@LL(FbccEQLMM8QtZZZTTGuߨB0 b^^^RRRww7Gxx^#""N)Xb:22RSލ GGG=**4㤣'''=== 6tLLn^tt`7oޤvce2ccc *J֮VIiZNF)aIZӱ >99I:>>~jjiʫxJZl6Aʆ@_ϒHJJڲejB$z=Az=j#z=j"CTdHSa!M4B멘P;jQT2vv*TjŹXhFڵZ^Xh???߶=###??\ƿALiIENDB`./pyke-1.1.1/doc/source/images/rule_base_categories.dia0000644000175000017500000000467511346504626021773 0ustar lambylamby][s8~_r$!ƓLUQlv0x\2}% ـ'Y/!>:wtޟ=(v1`^_/w_~v='g9F˟n$9tsc$a|of~e6;uGˏ:Iy뇄gokg6 wmB?f;z~7y77~}p|q aOMҤIU,ۻ?xkvZN(; X@`&?/ץ;zZ8ZhZ8/^(/BN&o_LsÂ{$p@vZ6[h˓&H+8R^}^w^ qw'e+ΆFp)'t4+ii7t^PIS9(T3U$Nξ;7C{;Q<U!|D X 򐫄z]=[ " YB Y@l|ڄQP R RGuX/6 [kǽ.i§UBE89}ֽٯk׉w>n ;c~X$[E=gn61AB& 7~9QhP2 B2s -`bq^偵 LvPaI|a$@%a~vE2=ϕ{P7JmD,+-Lvbް4=5- g⼜ RvS$w#O{軧^RL;DK3T-]-'{dAe"VvτE C H%75-Qj}Q% )K&dS CKD.A686 5mNT-Zk8?( UE$#LKTҁw Ŀcʇ1r%ay> Z4ޟhVUp!I[7X/5a rhC^JLJ!f Wn:#lryzMDʧP'r(|j*[oCq-J/턽䌛ix[qpe |_F/jtFØ4&mh,3i -i&i!HCmڪO_Ǭ-eg ԍiD,%a,iؖp6];&_ZP;yv3l[tQ/Tͽ:ǑLR,ZSUzHY F]QWѩ\dUDY M2+,C=*Ր]jYUthTse˅SERxaKĀXbh1 ۱XcBN=ȷ[̸ Vo^ IwP:y :_ Mw Nӻdﯶ\LyG:&qꁊdMmܺ{t5z]C*r>u!A>pMK!p:A) "{%h(CCdLmtr8X "M!"ReMXM)V^立ԥg'RcZ*PDl:hBa2wBfmlЀ6Yݨ+r\LdşkS(@Z&ЪyG*VIRĆ 6J!nMF=_]+iHsfR 6?y9CI%(/(Yc1I9 EPr2p)d2ӺAB ; :qXGBّO X<5셐]}DBfz zPyaׁ1®\xG R̀A8hD J!d`SIB,6׬HP{` E-dt_MH0 T=Q>ZuB =T/:ס+-e,##Fck1w"t]Sf(>_v2a鑜~j"cDibsǯvIJ#}$6R/-?ɾ!3%7}Z9} ّ^L酂^(\>-UAL8umRӦ9$J)Rʔ&Tum{ЀTA1u]먝tyn͂,dn"P-d$m5 ]|Åa2{9nWa n$A2?G#w5]j&wq-x]/20#㾯FWH–= FДϩyM"O)i~iD__[ TϾsݗg9/5/./pyke-1.1.1/doc/source/images/plan2.dia0000644000175000017500000000226011346504626016625 0ustar lambylambyZK8@5`'=F{i{pӹo_H'@pU/_VJEb>M`~0 _|V +MIz7ӗR?ODXm@B("S%4՟4XA@$INJQ"`'+:x=*8J]P3-9{MBjWm^SQUZ S"rIR*%Ŷ!u,(w11 Z{\/\t_8q_8ױ0Y\qD Q8O"b2kaRgB6{C[hѲe\rW}7WEDVϸmWw'=!J)"ܰ&g¬,ӢiYMy*wc agQQvjy!^,[I>i7zƃ3O쟪EJ*}5(gMZjyn,U <4pӻn4 ^onk^UJixǗkIY`&evKv?'kP$ʤX17Fҷ"扬5,U:I<䅊9fKCy Qzej:vjq YK˅|4z个]Z5n.Ǜa-Wu.EtcTmR7>MFwt'/FUܝA`X`z7$7&!:5xO&G&IL.A# M"6DL#܍E`_e64(܁Fn`e<m?$<`n4=ax0'B۪(yQbq/FHw^ʅ$_ʞ8{ ~Uj0,&ɰ$Ipc:'v45ߥCs 8&@Ln&u )D!O}m;~-_?7'4gwӿL;$M8ȃ <C 0b/OgmKҧNz-./pyke-1.1.1/doc/source/images/header.gif0000644000175000017500000003324711346504626017062 0ustar lambylambyGIF89aM9!c!k)k1s9{BJR!c)c)k1k1s9s9{B{BJJR!)c!)k!1k!1s!9s!Bs!B{!J{!J!R!R!Z!Z))k)1k)1s)9k)9s)Bs)B{)J{)J)Z)c)c11k19k19s1Bk1Bs1B{1Js1J{1R{1R1Z1Z1c1k9Bs9Js9J{9R{9R9Z9Z9c9kBJsBJ{BR{BZ{BZBcBkBsBsB{BJZ{JZJcJkJsJsJ{J{JJJRcRkRkRsRsR{R{RRRZcZkZkZsZ{ZZZckckcsc{c{ccccksk{k{kkkkksssss{{{{{{{ƭƵƭƵέεƭƵƽέενƭƽέενֽ֭έֵֵֽ֭,Moٺ%KYLƒ PĊF|HqE~bɌI4eG/Ytc͑2o %O:}sϜEz(ФPJE:)իNo岵ׯ^k.YcŢ5KmWk붭ܴ\[V-޳|W] جacw-79Ύ7yИM{&MjԬ%'~lXʼn0툺_۠o{ _.͓_A ^vZ[ыGn<鳿o{{Og~7}7Kx8E >hu!W%PXҲa.yH؈r'Y%؋&آfHb\E#6b0x#0#CcL dA$)%@h%Sfi▹<")QrdmZ~Q)uY&lLՆ ~V+UE(G*hB~h8D2z饏vPjGfJ*Pg{J)ij^ lDjkjinjz)J*-& ,k^c֭͒o]fiYV/E*v^=l2ꯢ IK lqAfqW,pkK"2&{ sOhy<$r<7!{.KʑʸQ;P2PduǸI5H^X}5̂XM$6r;7۲uq]ww͵x E?k]>6n\D:GAfWi:…b^Ԅw%~T={E& `(> m Q!gb0#\a [hPqU An0}H2.AΒ5d]okFvVT߬"[2ŐQUb/-[ۘ7q0YcXdM [,c층?%u[F1Bt#%1bs)eQb&B惭c>M,;XI Ri_Z0t&ZW32#ʹ] 4Pk5 LHi_< 5]#p6A)mi[6ho{Mz[emHIْ+s\TW|H0Y-һ^׽E0|QǶgP,{b+z{ۛ1W^y|ݵ phobq' i,؋#rtc1s:;460͑' z0Lvri"Q@/ FqXP<&?9MndPYP  orL);QAC{*ŸSULEB[#d EOoPv,Ӈ@o%D">:˛1 AJn J1(\N6 ` 䦑g.Vc7@f@_ ;-L ;, {̾ _pӫJQ)cY1|rcƽ( ik;xdKbJf6EwY_jو_z8.cIR/P(0A3\HHvc,2`Ћ~$@ ̰/AЊ ?8AKt{I?։wєS{.MRvs&'4*l*Q,W>U{\)9ZQy o{mQ6yo,@W0w,C< NHB/ :8p9J t\@HnxLW~,bHؖaĖUQha?H*%+\m $O' S gjRI$(iz.wB[j$9xSH'YA]xݥpjH&;h/RvG gB7 ` w %go+I8rEH[ 07t@HXx` plVeՅd 2զZA_% QtB:S+b.]cU 7exbcq a?WD"vG[28%BSGsRk'Xz!'MR{Sdh #0t#͠.@CXt&sq2·Pe"@7& l5!Ӹ@ t}sTzc)fC24cRb3@ۢ[mDSriR: B),!<,IRzb.fmh_H/@ttW WY8BwW` jd@w'\(wr|(}r^f/q)>NS("Q_0xWsK9Vؕ$;edN&7Lc{$i4MI*[)$uY i_a{5?9s8 p6.& h:nj[ht5ICPƈV8 ' X~h.im:9ǎASMe[FWExv F^TZi$ hzXytQdiHp3$s-Hb3O4UOcxG%: Hw u|EHv0E 0t )%zypp SowX?Xxx,VՐVm0(+sɹC4=)>}ɏ7V v4/]2;U7W{ Dr(E"HG{H#aN)9"ИCgb@ S/It4i /џP`dgo[_2<0h"ׇ2!?DN7}\8Bڃvxc'':ɕ!Wmq#?VMɖ$jZU8GdzqDO(Y eBVI^a7@g?G87y霹@y 2_czƱqL]@ 'Ki_.<*j]Cs ,z3" >7u9%xh*xykm |ȕ tBi @ N@|]J CGwjNAWi#mZ51(p!`vQƨJC67}-Ix8V*2%7xWkW8Oˎ*}4 2q: kErXOꜻ Pg[6_@  z~ӱA6' 'w۬&PS_H5x%ri#]yY4%<8aPi".eõ/s:i5哔%C{4yD-g"EAE: ]J ^}Χ^j@V`U`TDVpڛ CWv9`0{ [plۑԕ,F$7j xk@r!sĻ&8$YFK7Cƣ9?ɗv<Y-zpyoV~'y \ PlfF _ ` kˊ /'~H+ m|pWefji(JJ>Ud%ϲ&jV.&DK iK>S;;̮A&ypSG0{C 60ho__1 bp B6w5P IIG*|^ ptІC)5Pet<7ukKC\5dD z~i93/d E) TQ;HR'*JYTD-9C墮! yk M| `b tu@B K#p>tI*ۉh! jj~$ GP(lEfE=Tc y'^|+';3 k pXI([zǣqiܻU}C;pۂp}ILj嵛B| j#}I7o_eRvB Ph ⁾w&%G4˥ɖZ9f"bjՔg\jrI$fz9oӧ ǡa۳:}K5жWvx tP@ >a cg!0%2 瞅[1 mh#mHZYͨv46 L\G}zރ.=G)e V2*Ƕ@Uh?ˋCppӵ»E `eU[m&d<+hTY&PλwPh\?oww`v$r9;X8aQ,j UI<:9hU7R].WDb!W-eo|d]h#cdX4w P0r Dtf :6EwO`0tt/ vp8Yw:Dl"fN訶22IuGlxm^Y7 $m 8 p%/M>f୾@ (XisښwhR"5e,q͹~xMr-^;ڪԃBz[bvw)ض9@` i&A4 s+b:hXEN̊_IGG|\.bʽQ 2*p 1z[E{ ë{p8? 1A AJi놾) fX'(!^,z.f@ THF{:Y<k~Vl2f^ƛ_%XPUpknd9U~i;:oȣF&㬲Ej;qݗYZv\օY&"&o\~qwJ$w\VRdP{/%d)Am239Ζ`а/!YP kV&5ICrS.F \`Ni UU}0G7n8hՀq.0 ā ~7s8nD5qk 7 @!NOK*E+ECn -+b4՝cbilWI( _K 6\rdDdI^K6dm'p6X9duj@N6D7Qn,'75ppm5QH'!F&%ndr8xdBpLi]·yOA`җR͛TFXGn? bAz 2\`1@rA d2Л֌j0,fԼBb$ M;/|P@E"/~ h_t!/P;J- Uhv%Xuq 8(p< ixC0Ѐ Tht MR9m Yp(X**R"p 7x-X Up3P BR?-zCrgjxx:D;N8| X|PObOdAZ5/0Q,#Pp2#_0b)bMtCƪ,6V{\kYbƑ>wk!h Cdo֜1׈Tn+,X @0**TD1~(,瘰5@qa'sEWQѠ2 7^)YxQ=G'i'UVLJPikH5@$\Sn\8ĔFVsDHF'D:t-D(NN,.*QNCZ\3k$l1S kW1iFïf܆LHSIXAM[B#0ò@ ez)A>FзYòVx)Er<8f(r+*\),CK9~\OPy~CIYg؈䢅EE*T1TsC/$IEm\=RH{j~ƍTl}q~9zB&&97Ne4: 9r,Ip;'G&"~;@Y`/+6(6ׄM4cT3v(3~eF %/O72Mڇ`d M0ibƠ?D8BL La H@xiN,]`0Fj42_>w_61,Vx[`羫Po>"wTOuTܧվٴǯ7wDčnͩKW9qXTp1v7Uŀ^,9plJv`2 k`UPQ U0֬8"{bailBj![7~k1p V`WW(,[W@˒6S)oc93\{лB/?wP_ gxZC tVӄ Xhc؈K N+ 86bH<4D(RKB#FhIhYhIhDH(BA<* $=c X4/ԑ$!+=Z3Q6h!?# Xnڬ޻)K#X FtBJn8U$j20!92 cHA2%;Xj!i2qF)O #WHȒFfh 4CF+/x$ >\@X* p=Jh%(\@Q:hR+5hQyL H,Y@ L HB H@s?`(`l2 ʪ2+i8fAp p,Tdp-rFFt,1ҹ\W9N|XPFdLڛ5+` b܉sBഒT1 i [00"((Q4$ ] C 5( (: |>~ W8d9s㱰LVdPLX(T[@.R C.Ɖ;)ΓdLP!3| Z1kĩd`d&>2讨ppdsÖHԙ 'hV]idKdVFC*K M?C(&gyl $Q$x6i+ DZkOfOv=#jTlOy-V|bU`ൺIMbZn Y(kc0y[AL5jSrLʢDpaLӖNY+Ui6>nՅ[Y#ZaVQVM@TYFglOlApoHBXulp^3ܧQg^𙈅 o{hAa@aٌ`}`p TC6RBáxmq)pW5|%>F:.@kpHAuu6H­Z0p/VuCG> NgS_ | XTub0% O' ^N@W:Uƈ2MIDpVf4xo$H)Ht^ aS lk!uʠjXC }c&6 U4xkVd:;w4 coTcʼnGkO;o|{a nxta|su6DWgTqgtSopful5)l>iV n&n<~ XO [^h&aaaǫ%O 2v& g OӢƖO@Q0Ax0;l\VhngOf֘ 3_w>⃖}k( 4ߟ6hO[,aIVX3GzaF'vtyx#QGLVժ ?[k},K芻TH4\~hqƗTߓcɈjfAu  gn ivÚhj6'q&"?_Zu&H'?V1K?S ʭ {nid{^a+s٩Kʦ,KTtD\A!a!eMwƛhb$e5E\y28AM`r,yQh F'uak х!D5 ɑofz ^yRUB>i_J,Adgz!,n.E %S= ,N& %x]Awa!MF^Be'\b&"s_U艼fj`a`DdI.nF_{f#a!P1i,苣iуNgK&kngQ'ژ H*$NuHwz ,5 BY犤jbmUnxI.bx[w1:h%sQw[Ɗ)g`\+bt%\1e,YkT(*fK[:JҵjZҷbWdeՀVy*dH 8g%x.JvubmW;f*Tj_ W<7hq}S 7ʶ*<+bSʬL~醦˶}7c 4q%m|HhEJZǙ: /Xz +jjț>nyMnnŒbC+~yr"b/FnoW޲r+W)tHM]r6 o^C'T,P P/I16 o@L,5co70uhj6,J) )=ɧ}淖;Sж4^|ʔ c*Pį܄M\3$]y|o !Z|mǙ{jݦ:4{JfJX@p ǧ`$LFRK幚ҋ1cL6]i.p{^K j#?cAyr ,)=.xoOvH/+O}bkb+r항Qsu?p"JKtTmpw!ӬKHd1ibs9FSW[V4D<,l<(s;./pyke-1.1.1/doc/source/images/bc_rules.dia0000644000175000017500000000543511346504626017416 0ustar lambylamby]Ko80W5c20C flȒ!˝/)9?$[D%@ġY*Wb?~wm4##y,'9QYdjd>l_weyxx& l8o̠0{w<Ϣ6ף$\4m2vfifa|7x_7in93:\iÿ꧆T:;vN7?OcٍژAǯ:uݛU]l|f(9eO\ QődbAÊ +.Ligaiäg[^ffS >߇?-Z]d!GߣM4uGILt xPV"R3ͮwm4כ pLLݰKwx\Su q_]hww,ߩ: L)CbfPn !-eX&4KNAB 9U >( DYK-y0ԁ,H \gTQuj'nL&FL&Y8z@QE c,ӟ |:C3Nf0k-uT$!e֜CS5Br1$憹4Ү:5R, ;R(iUEsxHC |,-u hLݝ(qGq}/ؤIMxpձ4ɫDG*͕f<䏖(?y4 Wpv+Ρ6B1QCi`~#;F6F JW?QlW Gmf-b/cF3o|zjNQ)8nJo@ZMg( 2N:q&@^ࠐ<nygPs#N) $v@# @ 䌊n$ |>CAٸO-o} D!УWG4xVZ1,@L|x2@ `LT @5m'u+֓eEe|<`!Ƹ{ qΌ 0ok8MTXqt֯r1qC=m1Ef6O8Nl`f!RO8cp!.sxѢwoPC{ vߠb E`Qhٍ5!Đ&`}rCz!uyiZ>4@>VtOyr+MJAuT{;iUlHD{6zR?xEI Xq\;᜔@P=QF(8S5kHazmzǞjn7Q!vLEqzտ:Ǩz8HSi4j̬Rr}v͎7R)sE~0Ftf1[b=l,*Nhfn7*<@Єv˦;kׄd@#{I'>Kjl?FkѽC1tJ5lN-alFwe4ܫFJyɆ'b5Tlj=Ll߭vH0 YT&**o@^ŶD'Bԫ5$Ĭ*\}0҇zG:-dv!pb1Z !E^-[Ԯ͍4ڧ=հ s 3"6_"$?B~ k?[k騺kPKo$`H?P̍Mca?PKKo|0DY.~S/!xhs /HguѹL3S`beLN0AƶxʨnT*X1NBa SvG,TKc r~ ې<W 8G'vo9^?V_./pyke-1.1.1/doc/source/images/plan3.dia0000644000175000017500000000242111346504626016625 0ustar lambylambyZK8@5C0` I4F#' ;Dt.۷x^y@@ E}.sPya"">U ?U~}z3T]J<o[-TFB 4 Ӄ;ɵ*Rd +6Utц{jf-0 sz͸5\0ٵqfvLn]1i?ɭb0&%ٰum+*W`~l2 nrns?/ N.gHHAYGQ(Pذ8񂆰N{Ό%d2ENނEm쵧ʼz| uX׶I#3s۰[` Cl^4ݣUGxw7i.v 1&;w$aRb`. AE$][5-zLl xbi?Uv 2hxh(!3|p4e (oC^2MzpKHhB4[GĂb8:rIO0YePHuY=aU Tjdh K>ǃT>VM!pűثdW dBH0BH`#jG!Ղ SȨYU%0BH`#jGׯ_oٲ%ñ>r:tF]]QF|XG{1ӧO;;;s\==!CdggW|ӰaÊPȌRp IjsBP(ܹc``p@+[[۫W>:mppP( nذaE#NNNϟ? &L0`˗/W4hmmM Y 0Y~MDHHu֭ݻWݻw+d/(//OKKKݻ.qxb/c2#""ڵkW+ +^۷o_񑧧}D'NdH*}ޥH$5܋v3>HK@T~_+٫͛7Yŗ~~~UmtHDR4 =Zit6H-˾遚{m8}ػF=Csasv VZ`@P>|ػwoG͛9s/D"ы/f͚+{Ubf D3gTw8ʽj_Wצ% 'bM},:uͲ-v5|Gٗ@,L*L7SwǢ tM>s#dˏu]:$?~ܩS'--- OO7nT~+++555[[ݻwW|9t萵.HiV._}^oySHw$7y tnt~20e/rJy|Q)wO ow}@v$q@lkzYkWeVi^%QJPs6>ݮ}xէF\} M5X*]j;vwbqDDĴi&MDvE$yHh$4oP񎙦QbaJCPba${_* 58+ȗJ]p'+t uַox ɓ'ϝ;H}@c.83_X4R^Vi\vd[:7hXϚdwŗIDRB*!~>-zZoqqq"(11|jR)r.gikgjٟp*:AFwpצ߹zܖn?uݯ j.##/ϏU?e8?)̱cǢɮBϟoffFb8"T*---p­[ȮE p*yB䈈VWW1cƐXB$?ȮB"##O8Av!-X*ŋT}A!ՂBs!T >B}R-!Z0BH`#jBtbA>4>& ! ɮ$"K@1 OȮ #41lHvUUv8S N8m&I~|K)UTpvX GWL~۴~BbWf>R7@v̠dWQeZgMyC<ѳWRAtTG 4GH0Ke&j!}DEGHq0a#8J"qsQR}D o䇡o}nȾ>Kf}"urPJ|g%-'$Pm(A[3A[YܽĴD^ʀel7e\߼=ɮ[-2000~a|_lahis鹷eGұGW]{ >[N>+Zh] t_dZՓѳ Έ\Uv,_T `(^>5sT05Ǎr]i%^'VX"{Pba${=͆'P6;"wjp+b~ K%B60!TF=?_5A,4^mkM;&ۮRPP$sU?w3Ӳ E|ly 03ԭg̪Gs#>+b왏Wtakb$X>O!Vϒm|˚7 !tG$)}G606JYŒ1ɻjRNl]'M5O׵>]^@_mǺC?}0͎T^ʅ~/vɥRPg<[6Ҥ>v y1߾I籎.&tkGvuHn[mFTkdC!2)Y֞OJSc1t1&jzu6{J3w^x𷩻nٸ"hl`TuǕg]`K̚Y^a١FisɮHj]yӰל:o. Q;h6|FdWAE8}_f^,6{T \{û9 E7_xJvE)>D:UY_ꭥQwd2mx7gD:oU^a !(;\x6ކm5-boW|mkAHQ0r 'n`0`:dT,s,e^AH!0_ E]\vSI=`gdׂB`/b23#Ҍ媣 z.9Z?}aEc [{rUR&}WeC sKv,O*^iGDHi-׉Юr_,n;b)1}Pq}܄"#@6K'xqjL},ÖL u բb3G+&w?t-(+ɿz:vjhf~/-SiH` 2r 4yk{.<{}aÄγ~ټ;155;YJv_!B?rTT|WE%X5:ήv+.UQWgO[Xv +(*H:j,9tE^>Bq^sQznW^0懫eq9쬼~cUz~esL'uWv*c}9[WfInb!mE1%T>|>$ټbKS//[ci[= e:{.vZqcÖHKUkjI#HA=ZIv 4dT*Kk&Vc_>fig21G] Jeo:6{xa[إXLU_4uf*I YHdA,\fz/M.{K{jdk"XW&-70=r=xB_B`76}pH^إ|(b.0ikXE%2Dͪak3|ѠE f i \{~9oe[sž/kO~/(:|F~+G.徱>>m"hrO3֩Q]k!S_n B|q9l˩^z /_$exСC`Сϟ' ljЦ5J$i+K }T`/ ϖ-T a/;6|K!1ŭF3&}9W+sAuW!>t5P y5s}mL/W]-npTR@dٵ 1-ݗLc]nkښr .GJ?^J~ꛐ]B cL/׎.E^Ks.,sMJv9) >ka>bٱl^1)@(7\jrqeTkaY1Ҫalb'2xdW@9N?~N_GuȮ!G?dawrv?0oZy%}t'*>%( A8b䱛! |@G' #3C S=&fUyE鼛ϣCd2FtwSWKv)> u6k@n-||2w8)ɭq 2mtr,ҚW &ѻm:ʞ*GDWjZ哻? 65 +(= 5&T)fJդLq~F-[/J?F|zd0L M uinխ1T >5KN.rJq~}x -L7P{5(#ggfCQiA@BZd0|;G$I;#NXTN#X &k=7cN4`0UuUL|ŝظLg2m,Ƃ~0_z&:= m}KE_uXmj%^vD?cƽ9 hnܘ9$eM\n 0b(}rABZTJ<{U|VObin$>"G  l𧗥Ǐ6VczYy}{'JDΛ]vEjab?apF\\C{mWD+%㽜-ɚPA;/=}]wGQvїO;{R_6ՈK猹F~ Fsó[ਗ|ߤgBUVPAQ^-IFlunxBZ/bMZ)U*2MK?^+VMKݦsYܸ6%I빥\,kOT@Lť…;Ēa][.EGNa (r~>R"Mʈez[7ԫ_6YA ^ø{k:)Hӓ\㔐q?yUA3x&dRp/8B~sUǻG]ڴMl :z %W9&٩O׵'>oweŇ/8TaG$*;j9 ~稳Y[O=L WXZ} sU7jo3/6וIU5l5s4ŠSJH/sͱn%])*fWY8Ưlyq^fF:ZsGvY\"ƾʄW >k1k#:2J_E^n4X sҘ6kXDP;k!ϣkk}jR ] ;YڮM]a#TڡfFoTgE#f{{24XsMrY9|/Ov9_qi|JN]cdK0W!^-ܛYm*zyY,9}2 =Djp|_ɘ Ʀ9 t5_~17 ƳY/ڮezdp>B!W&]FM|lB=wjTXy%_ HJNFUT>Bi:Tu]+'m:È-'H,WNѻ]S }Q>Uʩ= hs3$:>cVژ71CRks_Uw;fG#*!ޟ]]u] G:ds6ػ`yc=4::ST:=J ~2XԀ6>\){su!Ͽ\_2+:|',۪v2lĥwdWR ={ 8,ۉOIjhOrg7AO{{:.mƝr|xNOR܀Ϸ?sektԵzq9~Z<ijn\rͭ*УA/v#o&<)=翔\|D_8j\lk=t>3)!ezn?ueV=D,ʈ8m}JĂЬGS%<0iC* 58Ӈ 2I(Li;i 9QO) @BaqU[ho/i}I= l,NT8΃hyhɸWGKĂT~֬'k*x}֥g >GSM2 Y,ŏG|׏vlcVi^Vi\#zW mxyy D?ߧVNyu4MLmb-.g?Z2W.m5Jehu]ZT,*Ҩ3B>,=ozs}l[jV\VBv!է)woڮf'n(G/j6?t&K!gggkQ .ȾCX TG</#EDG)$p{K.dW YYYdP5ݡCG]jժI&$U\FBՁy}a#T8 s}D_Uǧ>u TuUG8ij(W̒\i>>g>U9CM82Bkσ s|,\!U|_G(3|vdC?QA90w.K~Q˞jzm>dB-*S*Mv!}adA􅹏*#s1}DW2%2䪓]0h sUT(G􅹏*S*10Qep|G sUqHyc> \>|1|"/}:M}yRtYyEdGa#G_d}nL6q+O[!E|D_ =<L u%gNϮ]|y\"5|D_ l5֨.pf|rJݚbKrP3U TkCZhr"%g }d0LvuT<0t;9,%:{Y?Hܫ]&6dWGED_;jd2[u?V}xσ s}G=S.v"ebѽZ1%. }a j,Hl9eQ˧Dbu}b58CR]8,1-o̊Iy5l L뺈0cwR uՎrw3?v\ /֤5σ s_\{} A?bn;yrMdU>g~M<`l|Z_w-bfMŇnaF_SG%>vvmDv-藘h/^KÌ sFyWG2)qyz:ԪIZNgkA6!:ܯ߀XHκBհKG6{_.>69KX"tQ;<&n?v3Rb'tyBяݖׂTowG_#5U͌tn7E-HWWd_~xܑR?}x/#=-C=%n>R Z6 IQ=[=yBcڷ7L 59R^&t>/\Fy`Uܯ6%.Qf-M}f^ѠD%}^,Ms UjIEoS7ַǜg?E5407gcQ}q>/J uz ),s7h_!&ڧ֌13f?=%+|7תj u kjcmbc~K #[?ť][.p>/ܯ,V[;4[{~fnQ_ywmA D"}h*{WPWPAvMB6] pN] l}ӳ j,KmTd=7|}Oɑ~>-\FΝ,!sKȮ>{QPOQS3}Y0'~!D58S#m[e]{ZPd^b2^ 8 yj=mzJ@?OGuWv-:߫d"w&Հy}2(kk L$exa,&W Sv$NxbR٥aBj سxVĵG]6` #:ܗs#] p[21[.KĬ{פ)& G/l?ƺ'R Ȯ}L$Yߝ? >#ܗv-l. 61s蒣|_T{mw$_12\xQWwf߇wy8 Yqp~a`a7ۻ}vMd׉`a{ѐFMr !O IO BJOo?]r6XtTTEq^~Q/b@W;.\[^4OW)澢H 瞥f71ơY:,j*zsH ]rvX\J@d߫x \{T=t8=\P{#~>}ŒJ[1B7 hPǰ6WGkI sesW bAQ/( J#ަF'dTb1[7mл].vښrN|vݬ)>B$ȸ̘L / rZ%Ɖu]O0PܰySa][joH ʊdOW;_WIX,PRxMJAqi!_PX,(, Kg~G$w4-Ovuth[6od̥p>/}0 {+S{+S Zv)om聃$" 5f#LtE'z6hFȮ||]D_8΃'-,VYO\iX+Ɨ ʊej=BیܚU,*18ʛHv-U 澪K%Gb.? <[׆8u^LSVzɔhM "s_u@\Wηi]TWBaʟ;.GamԦCYJA%b~7M5nkAj0U_ꋥB2_տoYvWȊ^ij(Y>gHn{- ujq ] BUrrv2Y6c@5fufJ[-&/\׷iJRPR)hs_gT !UZEQbdeŢu{k'.Jb@Rfitv+'sM㽝V4ҷZ俙CX ꈆ0oҳLv_pF$ Kjc /XzqM暓]&HZRBJv!U q^rU|!7ꚷʴ)r_3 sjzYy6#  Gv_Kx ~vյU͋hJ>zTޥB*H c#TЧq#r$ܧ'A4ES Y(B< >\}DOr q|>#Prhs=QB*H"'B*E6bRS PԢn'JCGRZ>ZC+0.`O-:vpGnJigIn=-@ N{`m?^%e}n{롹ܰ׹o@BH Qpc-JmNju=<ʢ|}_O-:z2lĥwdW:dypX+;fIi+vfGhṿ'L oyÕ/CϧN|R_'_m&oZУAGC3ˇh28΃h s_y.6420̛[77]PL|_lahis鹷Ξiiq GclmZ?.qMF?+$NW*;UTv.8q]6DSҭ'{a[/4WI%zYf}(W㊥Hg: 2Gh\-Xz@CsIr֔|]6~_}]J4꾢o7X>O!Vy-Ƿ>?XTݯ,r`@SYM m6}m:ccc]R뺈8:z_~/9vˊ >āZo⃿N]?hdRSG_ӷ"J#T.Myp>ZHF/"RA zTͮb >q>'ZHI&<~j!DqO"D#r.σhRAmqDKT? u]#T}}DCT? u]|"'}D98΃B:Us#k ~%|.u-aWј+>[@YPlcQmL}DCjX-Lp2sfku%0Hf sWs݁`ҼsDu]|"'wT@(.Mv!yR(Z*TX\u}|DOT?TPP +ʏ|DOS* L-QRAWd *.?8h꧖ )} sfKrع#׃`Δ'\>'}Jߧ5*21p^x/-Ԫ%*m QR[6070l'WtQŜ7#),_pDOd0qcRz IDAT!q}%ͭ]^6>j6=QR5c{2ШIȮ48hIu|~bZnv>?W\(XfvM^|C0 3CS:ƺ&T XܯYD_ܻq_IP_<K[ɥQwζ d]6σ s>f:w' b̡m&z&:dWWSAedsxű7ExUXoְv};2$Ouɮ* 6xt )A0ͺ۷inƢCdo=]r{oPs#]Ku]#zB~QWLƠN73!(21mlW_;gֺ1Y%.vPKLi9=?duj{Яd0zkzc횖 E^9{/b σyMa+p/p6ab3:dWDu6ko}b24ysmaH bol,i#Tճ^}.ɢlP.Rˏ"|gPmMoDw-e%o%J.[ymaCP3X1>P ]-.<WqrQp>-}8v3$WԨndB!j,q]`υg2.8"ڢ٥PR8թ]LGrI~P'FfE(iCpѐ~,^ϰH˻kKxViGuO`#٥PC@gW; . FSF<8є߯&<[6WR"pX>:~.nf",kܿ=8,2z3|G["kWe54wVoR)K"Ke]\VMa[N>JL35ԙ2cW~;Zf2b2հ_!>oσh*|ʦIv/}/lyf1<+cڭSSwe}Թ%AQI'V::݊pJ@t|Qgzmvߘ9]ȱV{ S}˥YLZ>!b01ЖWK{ii.uyͅc:|Z?f8/C=M#=?'tvVLnf'uMˏ#23P;㽠76尳s uuWM)@2OhhJyJe21֐ 뙖`i[JN.K3’ﶣYK$R+0Ry e:{.na )wa/8΃hKs_a "a.1+=i}ޯ a}j3ʖslXg!,}E`Y~ fhpv)ZiG.{WPWPAvMݠW&?oӫm&U޲wזMI$)9v^v;ŧBJH#%9UN>gђJ@'5 -@^+4rP@~vnpϵGs{&qԧDPݯL`?gl-Mvήvo13ߦц;J.OJ O98΃膡??<~+tBGw"*vSo2VP Ǎ\#Oo\:iٵ T4U)T7;xQR)ִrBp]6Os]=C=ͤ ^4kϢMy\> ~2q>)] d2ujN=! LJtwWJt}DO0FhLӰdB!RxLVGKyϢӺl8 .ELVRo'?f[i>u TE48`x7VMggo'GfUg+u y+0X,1MȜ"(ʃw`YstZ =a]WšW,k5Rk .۹#O]BAUP=mVČ1aġ'y6_gRnX0ߣ-.,>i$!T5=š._6qoT'z-x)$Ek}nј+j>cH`S~c]ׅ<ĶP"~^ .M!S'`ԅ6F>45 vfRdWPuPB߲79cӋt. 6itAm!9j|zT,tH3M ]BD Uݥ^ۃeY$}q(,]Pp]Vg^9cN+4Ԕ05BrO?ΦM|I/K|a=v{n ;ՆU%ܧp0oU'z<#]Z5Qd]Ɂ}1Lf}e3fTK`rF߽.Mٿs8PETLviUFsERҀm}n,x?:,ʥ MwwwI`!vaq^kw'vHtwwto\?$6=>̜/g=s朹/+pK%YyeA#$Ug <|"ߖkM2&L)h,u3" ޸t ןq)^19)O@X<1(t+DSH42N!)$DLSHtryKV}D$շg/Br1qǣQkl3>hvh@-d}ݴSkrxS#2pBe2b)QU:fVWLOBOLWLWLOLW}N?2RRUZSV[ZCll&gTUՅW$EOBOL]WLTJTROWL}P<vzX@e;s`&j=ꄇ~ݷPwqG8 v-OHsh zz]nLej 1%R 4D5D4DU~> hi(tP[_PJ|X# mS)=SI]S)=]QA55vASi-c|j8Eo_;l@i^pe/`sY=$kqIDN}Qu@ +KKKDZ''7WUSs@(y1 r撼UuP0v5鸻դz8EVBW-ןuu~vL/P4s)} 'Մ!菐Dp8bZ{ uQq60ҷ1:5"!*kQxPitiD@k7-oi/o&#]=k-?#7\X5J7R^ :`|pb[RE(4F<r)ݓ\W_,=1d3NTJmA%~+ * H5vNvd16`a@>ԓ%<҂IZ52גmvl v䀒~{[$yEo;6aDT${yPFZ)ڌ3  MLVTQ)'0EmXk3݇T:ޕ'-64 =]Z]15q%`uF9f՘4Pqwc#,U\"󢼀0 .}:kis%"g1*}@d7S[(MFJKEs҄F <cpx 0_^`B rR Br\9Hl={zʳ6BimZSDc \Jz9gwdrwߝ X4o5̖pAV:]^@{QL!35/}d,!/0L(KςgځvwP¦܆¼¦N1a9~)i9~)y)EYM%d.o:{v+2EchOg3Y/$>FGnhosW#7*߃J=(;Eu, ;̖c>Lݧ2hb|j[[ ̄􄪌r+8QNJvlY7^w(ʛܯ ]k8ob֞9{{_]fˏ۾uAGfyړUeW+ ?3:yt adi ܯs|2~ט:fX3)=;pXNyKqoQqxU["1Xoz~b"|.!nhg:}`}Xa  ?GSGH>pJCDj01rMxKL`P #pUqOROnJ uF91Ϭ˿Qâ1#6FUHqHRW^2sמ-#/GyD.^k8a Pv8~˵[7wGZM_uwA0 LY~|;եBx=ς?BG.&zD#AwRJ¡lh7_o )c;DTv:zț)/X4fɪc ,`4vӥ+ z8Tv}݋BrQsPZk դ;'Ҙ_vyDn;2wB£ioUt6,=![KjMΥǏ3?2+Kh{ndmXдuגO h,ן݌c 2]0oQܙxwrf=ڢЩ GQmbތnV faNzZB|;D=cnް|Ň6J7QZ%xE2=hUϘt o`tڦuv"2ly_i : *Zm2Y8An(- |3ť cpl4ZPIԩ;Մ|*/UOn ө1s3Ͼ~ZT//鸃?֚S#m2Y~0g>uYDGxF8 2'7})ɫFcpgX5HC1%,ԿhahmqhL~cI~cI`Iԗm4F$4 $? EUWHɮ/|o%5 )po?NvC7zxq u6P>3 IDATң*o0hfRzOz?"aŠh܍& a%V@oG\ʬayNd.#JrǙ 5Cg{u@ϲ?]_S?`v# Rkrl^kO8by߹T?KeΗړ2?*T1(| U M,"#?7_G]eQVgj-y{}`? V_\#/@D Bo0v%q[Ipi̙{ߙmUGo5]l#cLmy>?.ei 㬏G3=HFNR};՚x9^|~^A@BW^{YI6kj(=pjn5Ϫ/xJ#=s9=]C^ࡨL^U:a$>o ;rUq2X|vD'2v mfɢA5ɨ;+#3ZxˈE"J/[iu&w?A̠x3)V{k.qUGȍ8j\Y3@|퉗F0*f#Pw "WewX3NHy>hQ/pjz~ *h,eT\M~z= WG~o'=q۩b&6,n(odP/p8 ihy ";?jtuڜԚ'.%M ᓐdZ|9~+Mg.IEkl94Q9p9]rfVjJ#=Hw.anCt=1ُ{xXFdmb[-B#|rMJbaS)a`QJAcYIsů#|~gTas8 A9d~?kCXNU[sAJ䗇Δ;Gasl4 F1(4f,SOnr)񱍌qxEbXIW(ͦOf5x)t ,tC2((U7Tퟗ97`m(Z/"Doi醨K;}!?#Xu1׿Wv]WƠ5P(_/(L q=\@W$4v߆;UmlWuҐ\ ߎUVcјCVv PkN}=fjm#cUΗ>*p84_nAL ~3?͸0jπRlիziM0jһCz:yւ;l >ܲ@8UF'urM 's }ߢ>)-<"F1 ;dϾ0L~@an:7GWX>ߞkw0E^~m#e]WC v'%[b[w+.$>F (Dmyh}\ `GG+;͖Oz|Ek(>/@GӔk ܸ }fs38Y/=Sra#l^OSx55@ 3M}\JRuL2Okb#e5>!ׁvo0(Cd)d8Ec?`v+4{\?+SdS;84k9Z.\Q@4z6%:uo9*˜ƪ`/\p]Pi:XRRy>/;尿|i@ nv6'gro~E0^{ugm,Gh%yKdh-@ĥ8 vrғ ɗmeM8)vyz>C =a'ꟘW[o[@ԭqdGpL.}4WZs[ d(0]Q@m>6ן8di |w!s\9#Vp/33?mn-p|5ĶZu%_W7Rkwbj-& @G{%tjׅGjx.w\Sbx:SηR_Ht򪀃7loخ<,dDd`3_teWhy<q7OڶHwnLu}ԏ7/([lYS0 ( yzeT+܌%Fy^4;l]59i]BҗonUDѕ)/T{|LsPXwȀeٲ5sItOK f4TE oWPTvپ؋s?oIcENPdo9{Ѱ eAMHa^h=ٲ؍h{N6_Q36:6_خ$v='E?@A {PSis%;Dk4E_OC2/Y+L|U:QU!|X(m4}2@D_u5UmlekjŠğ| d) \ޭ )~m{x֊`!~p#4 ev[ M=('m8*kѿ_B\ppnAm42 f^!ftqꀃ E4=6Ξsݑ^KSs\j#n>>@3OKF:ecї5zWFs$2p53"׭ƿ]^DtˈVvſlل+Y,{cOMV;f{ 8l^W\D݇Y@kx ^[̏,  kD}e%c羔e;xe϶Y8.A_d ]DGT%ceU|(2x}3tLnƪ¼ץЩ |wP-#9*ڰ||pw7Q7! ǥQb59ssz,zAcf@+x:y>^Q:9 +?@g6ylݧ1G\Zϋ%s4#?۾dj,ҝϡ C$xDNml 9YGnU~6H5 X4?CA W,ԝ:JޢfOع^uݿUB4ҟޣa(>:6x*&9-uKА.Hh~6LꟅy! YL{w8NzU L[[8,TF^*`h(:\/U/R8 2iN뷹 U&:=z[-U]I4Jc3)>TV'TepXh^~waAa#P(t>>lk+ @cͥFIJrR < =}&DQo']I>m!'<>t@d= I i[[ծ?GG$ @Huws[#U)B OU[&jKl/Ki?ªv'bQP9Bk :t#&Tt  18Z[HY\qs?WeFʋ&jvC9Uu^ HvlDqzu+7?闸Қ÷|=6k fBȀbEWō3q$:wg3ʘB }W2hj!_}ĬR5}m3ux6qZ:*yXSfZJĺǾمDIQAh!^, }+.h,];ƽ^{e|h6҃a8s=B?=I/q[=ws]U?@դzջ jJ]5띟߸M&C',)J-{\8:p(1%lj>B  Ǚn՞~xEwWNw!VrMֻ4 ʫ/>8@x 8WSrxsL7 M{/x;c5ws;c5x2Ye>3N<"S2kj&ٗx2b7Y=V\#2mB7 4QiWZ9}pJHB^HB)02$=qwo1uv+s cHK: 6a}1x+V}ӿFe}Uvw5wՃ~!Cc m iʔ/ESvxvT>/B/Ӈ7W6se3۽3}sg~{|:4dH?(  ѕ)b2>锉*ry :bҊ>U PYf:R͞5y0Y#M O9&-ګ"mt]d8jvֻ Ơcgt,ן. gxfG1|2B[|VϝA -QQ濾V^'/lN !KϿ3Dt/ MuLuʪĽ H,,7lXcaLLQ>/*Nk" C#|lbOzt[}9 cۿ|xyuqk/[RU ^0Eu,}~`ݭ)tr4ox9Db0c)-&Iɿax 8#f;KkKCUw#ش iY~ɂ,Cq:r5pEa7J P(`gjgZPV[JXR~XRQ/dF'}^v;?ϫ/ٮ{錗 . lh&Y<9̖Ma6W*rb{:_Ycpay;~c\>m Ι6rOc?fC?4A~ipaEq]h87 &YzZf?I+z)fzJV?m<:r(G%.~PiT ^OLӴ>Nhb'}n~FѨqZ,2 ?DgDgi+KyZ64&phApiLDEϰ.g?ZP26DU&_;,V%/,k]>>pX${I YFe} Ij%j>R{⫀D+Ws/XIHZ= tjJ"UFqZ5Ha0Saw]1‰.rzGG=3TR_yp ^,AN@REH+')R'z!_Ha7k1[@Xc9\3;;$-06;06[SIbV@XlqSyaSy'v߿80VъAz뢰-%#]?o\vQkgg9s2llPEIPPT~37u*@ګW4:Λ`|͐ gyKXFd d:Dtt htƛ/CukCGp[/v1$y~zWЉvzJ/( ʂN>ӹ?:E<2b/C;~oؙcg1J-|:0u`(SA $(*n@'f ͤI(upuQp(2P2P.{6(9202Pw= IDATQZd1= >,HU[?_!F#n]vɏ(2BkgPuQ \GIFtbs%3i8)Ɉr[AE \V@iKrO^|g܇Q^cڏPp52PЩٔ8bT:yBaE?OWaՍ}0Ea36~oQf4}-.[\zN..ǨZ7SH q!z'Te 1M]VRxC>a 3:G?`1h[][ݤ2/߈ ttuYWs'k}c"3םx"aja+<&I{.d #4@]v@G0Ґ3kVO}^'9Nn&B|hsݤb}z~喳o@ a}v?O\Uv 񹲶TGatۡQ <@JT௹fz=*"^||uRx+/]0a';x%2S0ǁGC. A=pc1by6(mPm3~?ACQ\0Wim5! Hc ]15茝޷(h*^B)V2F%jڿp) ǭm[ŠRb23ν{tCCA:BXL- #6T"˸{Jn9 _\xk+]f3撂綸ЦF_6:),2A7ןxBQ!>nk a%5z8YЇFgU7[HͭVrs a 2o+̠ SiB(B uMس/qPx> A ,$#$ %7KJՍ-$JF  `rKk(?'l$-:,Pc%(ɈK ɋt)OX~Vc2KRr˳%ᐇA:ƭ<"$,5u!S37JrjKuL7i%Q3 b #/% %$#.kk220PT~fg7d -,9? :;(6:VR#x|bUQͭVrs+UT]TUY۔]]箟8 1lsr ~DI)O*,I.kl!uh *ħ %,/)(#σoȃx 8 0Hv-mRbC~YMc 1̋DNBD[~ϘKS ^G~sO}Mc\؋g40qBВsʒrʓK&'生{MUN|)?öV%(6sXzhbJo.+!d)o)$# %"'.A:PVJ/%6Pe e ޡiA^GKmW;=s]E4zڱ>`bض,$z` (0Ґk5#UXs=%s=%B O. /&Yv4e]GY$Si! yޡirId*󢞚m , Mf H^iMBfiBfIbvY~YK "lt]mu4:ב)-Bx~Q%5 Oఘc Nu!)<@|#2n .:U3lWϴ㶂!Sh/oQ(S[='k(3$/shOXzaE1*kF68ֿ.[6'_d桛>Mm| IM/p򕞚̺#Gjp[A(iHmn#s}p Huo   8;:p;8.wڶ[GLm֯Y/{/l#SfC89o O˫Xw⥡m3NLk,mn#/;$51qyҁGK-#M~ MpγM<sӿo}*~acXs0BN?!f3~5_1aJk%Mu$2u݉y2_3a=s6 -OiסqA>8,蚉=2F58mtݗ>CH?&j @ςg*̛bqυ5˦Zou@?fmW5y7)L^Ri &+swtiq<<ܿYJc_|MX|Ћ+I-UBx~\t+òFy'.rܩș֫IӼ_2j [f5^} ˺63]7.Wɀ9^63ٮ X͹1FOL)?y}}1M7 J%qH3e\s sGQ C^t9W(-ry,eT6kv y)s[gఘA9R 0d/z6(S #MnwcҍEf pw61y/NPa،b1xi|OBσ_9/p]ISQغZJR*- h]}FJs(͟w>tP yJ f3фLs7<l{#3Z`!ye'=Iՙ# mS7Fm?* 'G|6sw+FN0ȹ'A+.y{f80~yjn0xv9n)S,1u?ymCkݟ;j2wNUN?in?ꋯ 2vgg=xƑK FRi{Gg>DVϴς~ּ9,&zrNKƛ܊Ǘ Ü ;Ϳo q?b<:'~&"!Zhpypmcߥ5O-L-JhzﲺKu /< k>UvީPp󦢺D"d{])󫶝2r9t2VVհ7 '|^SR]߼SI=@LwÏyT"'T@Pe54:Wiz "]1S],/#,@M30a< L˯`>D\W#4dR]߬ /(S]O4]>tPgL*P{8 ꆖv_H/Y~;uI~J*9&ѯ(ʠ8?ϓUHp}֓>:gUu=ïSfswp"ue5Z(~ۑ/j`EJ )"uăΖo3Vi!e,)*PBgf*&0Ms}uK;f'}8 - a_u8xpVc?\Us£ۨL~K $W33URDڤ';Ly+.=⿳yLK~+WT?œom<-_s3P^X=M@)aq;qk]S[]ck'sƏ8psQE)q=LWVU ?OAYm?u:rL?~k]ckmC{_{9>trK4:@ jwήvz=T[ϩ÷00Qա"_&[MA[R? Bfjl(U45<ГY˄ y l۽#*+/)JL)襂bqnh)tZ6LWqѧfR ptX,:S ]%f%,v&,':Sֹs\we[V=}tqgӯ>8Ć są]񺴘9=T[|p,1$NEH^KD快K؋ џ}!nNf&D Q{|4u?ew붿37ބ_wxމ;#B=.=kOQ̖_Z,<կc7\5wiէq̷:t}+e6quy'mǼy[-:ck$ +X9&[ϙ妾gnIu_}9{]2[Y&iա* j,wL,i7`/E5fjk.0uV6(yOF7U)[nּ?BM^'ȳw-lg>)by 6\cLlG_2 *epīOcF}8)0HOyg<>~'}x)-%'l7j>]"׋l}bF69,fpBͭW>GgpX uT8HiQ7Tx~@2U #S ]tn!㢩,V5^,[Z_waJz7^sXy1iZ[]0f=/zs̄o^MC?BԡsMj!GVLuhojMJ _q<`n]3w5}NgGZ{mJ~ˆZɡSb:spY{+ &;#Ҏ)T3 [Y8y`_й+wbYƲy/6՘e(d{C&F89o(¼{y9݈FI_HU.5Ex' x 0 !K\Y)eԗCXsYgi4=);NRk2icBhrni4ɏĦj=\l[;+.oPnMC\㋷s]ߓˋdW)TEy^C O"C|=$Ӏ.||u yn/-A6@\EAI$ yGgg$f}`o7tA tW,U9kؐ"GlؐcCe'ܿޣ nM­Q6fS {kte %._ȫiV7BnMCUmc,:wz }6^ d7SGȯ/mP\-.3)'euzBs/gZ .LH/HHǫŌ{'"oW3qscab\~fy܌|^yU}Wڂ4*A(tN(FсhA+'HIr\*T2JP*Z#$+ߓ!cgT-{~X%k(A(kϴ)r 2yӳ4ppfdmkn\PZk||u`ҩC"="z鼍G;QxaW~comz~ܵ'm,vxyRW#x5Bnu]](YNCCQT&Q9[28֦k3age*гS\Fz/# UJ"ĄڶۭY"E׋ w sʟO¤.0O )΢ӌ4Sc[<)\)* L!`E'yx/!A};v|R1}lȿ`ɛ0-2Xݽ È65O a^1ǧ 8,:"?vV²CbYFm_2 z6 M`fLW]VwgA}<-.O~X8'][F={Kava ξG^IDAT1, m{{ՕU*s/.q!"L b4gzyy}#]PA=d D t.Ltu"yͽ=_\4DaCy+nD-̉L$J D2'*@Rb`P*̩~r&#mk ?k0߃Ke7CON~TWc7iBlQk5 eA^+;FKlK ƌʤ垙|O6!mloP4Npv+ꈛ[|:17$u#9Qagd]raNS*lѵՆhTI/}SV/_:<# et#_7L!JavnDv6ܭ k CG6?9%(ޜf3Loe+^FV/FTϸE'2 ^&BPQ]/%2XHbBRkTj!h *̢SY Ncѩ,:֢s ?ZE2DdO#6<8SVFc~ziTc+*dj go1yH)Y}[1Vc˰\^tX߼V0䇮yS/uD CQΣpwݎmIH'Ti2 xe5<*x%{ԧԁGuӮp&H ."t % R'sz,sS*Ka`>w9ni)31 sh6 mQH_p%%/^!ӱ!{HJ^swjGc#61tPOn]тU~v z ˙~|uɱ |7ö ySzqF7Q\ѿz@>sFtr&wڵr늈^ ez#hTIVܚyTU-V-}WkgũWs 8fWod9pM$I)#΍߱f -S5Iٻ2iք~3ƅ;ӮwyvVcLa~wSаO *%߳>U|ܜֶöADf-]8FނZ``okt:)\X+2Z 1EfXm .}jwk 6>8x^ gkĆM8z?yKLCJ翞J*5e-dhm5 sܹI}s] -1oHd 024mW>;"}|OJq4QםUbmd{KCGP,j{K?W0~82}gLH*+-VjT' 6>8(i^ϑ<yCjm o_keܿN}'>p"DV6-=a0{b+3'}{UcD|~H"7t8D"~+Lp 2}Wn]ԙڂSe3)OM>\ώ"ͽ\vY˂?ro6Dz-MoiD_ RWRFOFm8z=bIkx5Yǿ4=]h|;”;iO3Mkpe(O'P4\yt\>oy;D,\9$Y#43?ofo8Px(TU%?wX2m(B~ԏ+f֩UKk=ݛsHX06mxa}lnz?>]xtզL"]Otg0'?I oiGz :|eNgdPR Oj |W/['njzwjv\jcn<[%'oMZr?x9kJ YN >wԭ+W DbW{>9.B~"aYWmD [q;'zSC+e;C[{,~驂8BlZ"W++36?847mwМ'4jCb/ IseN}Fӕ}C>ѫS/Ƿ*~M] ɕgl'Jk,÷KԾNY|wm/; 𙿵<#sK:͌htyZFIg<]u]E{0bXu6JY2m@OPT֬V$mꇙ1¸[6g׆B"9}9}7/MK~Kɻ[W[W`id˺W/^.m=T-劫R:7n߂$g0/leL O'43K_{U/ShJHs%+S&/ij/|1_m:hJD a$+B>B,nTUQ:1QˌutT[7[ofF;. 2e6Ҵ]F\i@ `C`LWk55xw鉼Zyõka VvLk6ʞicǴcZOͤ_ٗ6*%\qU+h,o䊫+UF^x="IdcvLk[9ĔbQt2F.q잓>W3V!ɘI=`+Eo_!Wu^uۅ$%/ƺ9X:(D ծw$c٭3A!ߛ}M}nR8QX7`_tg_^s|۹*F!}'HQ8?@ W|(=){cۼ6O"'F3zB>{=+!NV  wBϋ߹WCڛŷ7h`Ga!]}p,^ tf;>nl"oJ&+J_ dҩO׾YTя۲L&a۰jY7(jI-Xtcf+e X^QPTrX_% NC<P$ÆtYoߜ-hlw+==[?*&=<^ᭌ0T&0jO HKД3r%= țQ(bR,S4gX2{rbD[O{$NH@5% $7- iDBS0*X%}g`'pЕNvmne ZM0^;/EU=İ0.ޘJ*027/6i?Rz4ϖfy!yag@Kmm)b@2bsơ_kTI$ܞ̜fbOϿ iUцncOc\#/O`'/}ƺF ʹ#]@NOyx?]Hl _lJew](, cec}E᜾ Ag5·'(Um =g"Hg ]. 눮Y=* ֋z:7 D0ywqɿ=/ ۞-Dw6f aF(#H{gȼjࣘӶWJ09|U> iR:YLA-=mvI CC8j)%$qJ1?l  ўSg{"苀?΢ґW#R(K-%"ŊURXI#PG}\cֿ/cQI nFq^.#ϸ@0/>n#H>~M{{bzY7ytP?JEgd4ڠ+r""lQZy0F܇gdd:@or&g{qr,L;СuHBR鍘ℸ[ [3ѨtV)gwe(o1uacJ_)5* 8\#]  m}ɂ%IRl:dې? 78!(>*'Izs:umѓDnzLs%C`˰2hA]|7c @'ӆ8w2y%[ ]OM6aLQ|lIBaC9B$18>H'z[{Bl?;aV][ZRRYPġ]dQ5 Y~Z~j/=*+yBt4 amwY]ZUVrUFjû5%&73dg˰4`C<7WWgTeVeU?lP46D$8۹8:8s\M6_9ZZ[*╊x%"^[*5V +!1Zz$ƌ H;ׅ~sZLW_46Dq2s5qp6Ḛ:p\LM8m>H))qTkԜ1h aô.ɕ%"nO%"^D|\fLe0(t&΢0:LgRn3t&0d:DeTKTRJ&U%*D%-VI4gedlq6qp1Ḛ:8p\M\M+ABydjEY#D+rKDbT+qfBflbbq1up18pM8f[t HSh*)~.Q*T-kږeTURF$әͮ 62zUH.IENDB`./pyke-1.1.1/doc/source/images/plan1.png0000644000175000017500000000746511346504626016667 0ustar lambylambyPNG  IHDRvuWsBITO pHYsIDATx}LSWO_,a )/TQ $#FJ6L 8ye35d180 Ʋed7\و+ PK h{ӧ>Whoo9nOO={\Sol~G[L͡t:l0 Va ;&33d)))UUUd0}ˁ r 1b  r 1b  rO?x<Fkc玱t҃p7_ Bz;r=z0Trz=;ІG6F+//L&shhH$9;;;:: BB3ܹsG XÇK$Ơ %*++i4t:._pT*պuwGGGwwVU( b0CKKK{{ܜH$JKKŋmmmJ.))rpp(,,\W5ű~1xٯ͹&&&6fSTvL&na%xnr2sc///ӈ'''rao 78{[ZZBBB|`&`0<== ΰ.{b9FO\\\NNaa#GJX,^L&j:q 1FR,fd $&&|rmm 0N۬W^iڞQoعc>PWA>'N0]Ι3gbcc\nQQ?ۢwy|ᇱKO(Uԛ`ȗ/~@A9A 1c@A9A 1c@A9A 1cĠp`mmuh4t:`XTP1MMMMMMdvP1VHTvvv.--Maaa, Yc,.._:ٳzwwwooo'[ i Y _GGG#""L&Ɓo7c `nxxxOOϺ Zի$QWWp87oj/ÇH>HAqW*Ν'N'r<22@ӯ\b^vr ^SSpvv0t ~DDEu1g"##e2[.ؾrss޽{e#333111VPP[%1QQQk|+{aaa/6^0L,Ƚ{΀oݿk cN:)K2??kxZD-$-nݺr^^^Q,l@ppT*tu#J;+HNNNUUU֩ سc4Mii#׷RSSYSSS,حc$Ihh(fffUH (HȒUءcVWWD߿lET*=x fbl {sLWWׁힼ<9/3TBB}&Ǩꢢ"xg߾}?&[߿ S;qǏcl*`XXbm)P1KKKyyy0]]]d+z=kkk0n+&&j-ڎimmkqq*ي@a:f~~>33R1pihh8::j49 cfffRRRرcvsˆBTrLmm-lV.{-JCaj8fjjJSVd(8Lܻw)\\\b1.`Ŝn+c|z{{ץY@TTmsDF pvvV*h#1]fx]xA͞KD3g#1=_QT0E&1 66vrr\$b4pٳgs <5 ɎYXXӟj1 pvvxxxԐ+08 d׶?~ð~Ν;Gy6Y8,Ht:ñ~Lc FWW͛79ǧDU6˺ax!//2t /Fp,laH`0 =!11Y~ ͙ =_r}0}nVF,Kќ>}ѣr|ddddddxxxyy_~1ƣGN9ydeeYM:VWllxoe[X Qʞ>}U-,,/,,8СCEEEVV;rY`  83 l="..l PXXc)Ͽ h44ƴ"loAЛ,@A9A 1c@A9A 1cİcc[mb-1c'7_[,f.31{J:>11J{ K)w,&?zzz~Wߖ|344$BB0>02:'))IRh4Zii)r}*̼⒑hll bX)ef9F(>zP__P__?o@ˆ>77'`իWJL&{ykkZP( @iLIIIWWWwwԔ|^&H&'']?󉉉>Lv@rrrqqb[[[gg\L造ǣ.]8hXiR_۫>11avJk.L@m$m??/^D\p}}}p[*z{{8ˇUf9fyyy*ꭷޚussST>g1?2 (f!O<9~8|F` 9h0L`0:Vwu%􎎎hwww___a31f[QQ7%544手0 .l-%ɴZ-\a 0nnx\ۣ/,[|su%)'oqp'݌~Y`+ ,np /E<-$N 2|707;{,4 ˘6CtUeupJ8[g?, eonƫZ1H O\ 3% S^p¥E*I4C$`YfSg}`bǦEڏ~eYrxdo+ջHŻsF(<{pt_=ZGqXu2m~vrxGk(FE(ΧS4'lnNޘ=!%LPKuwY9!HOߒͿw`4p}7hlH!0ߟߨHar 83lA)¦h$]j~gp#vzCVr$lwV):nX?$ϳ EzjAY.!DG6>;Ω9oO|dYO8:,*H>x`ً>F=Ytl;v+ݵWf]W"np+*jbr| pX)*2# 5!oa-HgHiB'!m\2Od$fB\$}MHJ#Ͱ( $EĊ` #Z OM }`v?eHz+V 8ny$߫E6 (2<bz@ DYI2Q/+#:!IE-{9a=v9WtTGĴް" EҩVڐH`C&Jsk4[aj7>~ZlgOΉbcy c-&q6<̢03% ;M'aBBX؇>4Y!%>*QW^{=ڿV쌝&΂4M9tyq5#BK a[=,i*Kh֖,ä00=Rٻ~8^kq GF*nkF j xFInSL1TH}{ApB{&K} ˫uơQB Fşbh2n[3k,B&yߴolk⻤hsVD}qV5,۵[C5,aZN`sݛZ3#kN;ML2|TB{!`n}TC8gB*rCfy$ݾ}ȇ!r!}]QĭS a0BN28ԎH<un7^֍쫉^e^Ig̦/fاlkt3^}aϷ^$y۫/ \ݵuWH#RE*G{پWq[<9z^  i}.=_kV=lZExg;/5yn\9%uqsD,g?nJw*Al! Q]lIwy[CA{Goa.TT3(ʁhmP&O9Mdkܷλk.+qO)Bm2q\$WOu`"Zg|ӧ=kӞz37pXz#;laSFVE@!O耸G'()Ë/:0f^z -=DY^zxq~┒٪ )ɷ׆-fEB_y_o;*^Bͅ)~3/e"f6*)+콈"zi֍بj3:ѺҷxڻT4:߉yvF^WO%/ /X?CޓgWIN2]Rs%s+Hp[. k6ظ8P|cy/OD₺ TM 5yJ8U'$6R<зy.s6uTf4i>ke@x;ߩlJuE=e(l~*@)FQĹ ]M@IQU@o**@dҶKed/Z(~XP-K0/S8#c0 ݇A 8yHz^Z=M_bQyƽ ?.% IzIӇ4H*16?shԢ hS+ %RxSiM-Ls2_z_ݱ@d|{*{akn a@ min.&K0}:b3҇ f}u"ѮT q{ici? Jc[{WWUazxtKmb$qڧ^qe!Q ׄ=OW7w!b/0R܀YKm}5zkH> LP$j>)QIcT!SsMۄ=O)Dl59ޥſ1d&x >?<כ' 0^,S./pyke-1.1.1/doc/source/images/plan1.dia0000644000175000017500000000205211346504626016623 0ustar lambylambyYK8WI2=0F{i{F&1;Fi-tC^<@~ĔU ߾JU¤6LPFL~ӧo#_'Vde &Rp"K qA G8'C0'8&|h|!ȊN9 JnDdfV](T+y^0pN`IL犒\AzMUv [WLplrD<}E9[V>x[l#^3Q,nƶ]\O7///Kfk"LW)RrJDƪՆIB!NeG_0/O.q k7V,:]-rS2]O,asNVτ6I;azl%")z7,ə4+4 -sṨ. L6T씂U9T( r/ uO.(m|1~7p&V1>e Z_%@kEx0Bvjh" \ "sXŶoGH9]BDlp;RT={o.KnQ`$Kϫ5,kEE+c[%J.PiDz+ؕpBɂ*x6iq?n,Ȋ, XM#ѻ}?ΦA+,$mTvsbYnŖSW> #;pp7xJnDpŊ'U*:iiAKU*[~RB^9'$oZomç/5|]T_WVtJ}J2*@|WkJQˏZek-gLӧPӧpZѮ"./pyke-1.1.1/doc/source/images/backtracking.dia0000644000175000017500000000406711346504626020243 0ustar lambylamby[o6)I= fK-盛e/k..&Y^Q\fYbZKN1pE~eH/xJϸE%rwOU׾TM\6>NΪ_uSS)ݟ um{Š/e<#nYfOMbvLhkL$-ՃV{Mrݽ(G|~~/c8g?lj΋42!Hmw|W6٘NMЋ(]$vӌAR*1qe0nFG*O+2(0ogid=\DWqEi1y0fƖ,omSFvM{۵>`*Ƈ}^Ϧ˫S4jfMդ9pĪ֊^Hĵaij U8J ס/AסF RC)]H#JuYӐ`"z@C^v.HkC@Kxi1&@R,KӉMUGս"µFl= vg,t޲Nq:C5lDkau;IIFoZ|(ϳК'MwFj2/m^P@vA3'|L%g|~:) 749?Hْ)ART ZRUrvļBKj,Tid3Ypl jEv-Kx;C .Pv$&QK~"ݪM^Ym(T+̙Dx.6l<*d̢1l4Ubih@#G:0DyF!L' %QXX0Ktp0SX$!cbv;)HRؓxbpR0 ) rBHەkXS(j JI"`W.8 }KyCm.yV!L+\Vxpqv`e@i=ZZh@ fq+j@-.njy }[z-l}W8R.۟#" c9w?4qn(h9`eJİ*R![[xbTHklnťʀ-nj- ߞ>80$ }ʌD "Ƶ{z^?Q%sjĽkQ.QNu*`q+CBfaL{|ؓE~hӡ{q{%"Ԉ qhQH&GM^OtA(.z*{Xlb$QZyi0j=<21İ1& XJ:=#ww݌L}9 \ 83FBb>!q#.zp:Bˌ>u2C>&QfvtRADu-n-tr7{UyFV&DچnϻoT F)f..{Db S{Fg"p*D6/nEsYk4שmX1C "qɾ " [#"g!SnD`E}8ə!|3}ayG4d2DܗbHIy]qD{AB]digs'Bn B"}W2.N\ B(3m ?UՏXl-1A {./pyke-1.1.1/doc/source/copyright_license0000644000175000017500000000223611346504626017325 0ustar lambylamby.. $Id: copyright_license 05a128b38e5d 2007-11-08 mtnyogi $ .. .. Copyright © 2007 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. ./pyke-1.1.1/doc/source/template.txt0000644000175000017500000001051411346504626016242 0ustar lambylamby <% title %>
 
<# for page in sidebar(indextree, div=''): val = page['crumb'] link = page['target'] if page['title']: print '
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads
<% body %>
<# print_details(default_section, page_title='

More:

', subsection_title='

Sub Sections:

', item_wrapper='%s', item='
%s
', split=False ) #>

Page last modified <# import datetime import re print datetime.date(*(int(x) for x in re.search(r'([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})', filedate) .groups()) ).strftime("%a, %b %d %Y") + '.' #>
./pyke-1.1.1/doc/source/links0000644000175000017500000002253711346504626014741 0ustar lambylamby.. _2to3: http://docs.python.org/library/2to3.html .. _about pyke: about_pyke/index.html .. _activated: activate_ .. _activate: using_pyke/index.html#getting-started .. _activating: activate_ .. _activation: activate_ .. _active: knowledge_bases/rule_bases.html#rule-base-activation .. _anonymous variable: `anonymous variable lp`_ .. _anonymous variable lp: logic_programming/pattern_matching/pattern_variables.html#anonymous-pattern-variables .. _anonymous variable syntax: pyke_syntax/krb_syntax/pattern.html#anonymous-variable .. _asserting new facts: using_pyke/adding_facts.html .. _ask_tty: knowledge_bases/question_bases.html#presenting-questions-to-your-end-users .. _assert: using_pyke/adding_facts.html .. _asserted: assert_ .. _asserts: assert_ .. _backtracked: backtracking_ .. _backtracking: logic_programming/rules/index.html#backtracking .. _backward chaining: `backward-chaining rule lp`_ .. _backward-chaining: `backward chaining`_ .. _backward-chaining rule: `backward-chaining rule lp`_ .. _backward-chaining rule lp: logic_programming/rules/backward_chaining.html .. _backward-chaining rule syntax: pyke_syntax/krb_syntax/bc_rule.html .. _backward-chaining rules: `backward-chaining rule`_ .. _bc_premise: pyke_syntax/krb_syntax/bc_rule.html#when-clause .. _bc_rule: `backward-chaining rule`_ .. _Bc_rule Syntax: bc_rule_ .. _case specific facts: knowledge_bases/fact_bases.html#case-specific-facts .. _category: `rule base category`_ .. _command: knowledge_bases/special.html#running-commands .. _commands: command_ .. _compound_premise: pyke_syntax/krb_syntax/compound_premise.html .. _cooked: `cooking functions`_ .. _cooking functions: about_pyke/cooking_functions.html .. _copy_reg: http://docs.python.org/library/copy_reg.html .. _create an engine: using_pyke/creating_engine.html .. _creating an inference engine: `create an engine`_ .. _cut operator: http://en.wikipedia.org/wiki/Cut_%28logic_programming%29 .. _doctest: http://docs.python.org/library/doctest.html .. _doctest-tools: http://code.google.com/p/doctest-tools/ .. _Docutils: http://sourceforge.net/projects/docutils .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall .. _engine: `knowledge engine`_ .. _engine.reset: reset_ .. _Examples: examples.html .. _extending: pyke_syntax/krb_syntax/index.html#extending-clause .. _extending clause: extending_ .. _fact base: knowledge_bases/fact_bases.html .. _fact bases: `fact base`_ .. _fact: knowledge_bases/fact_bases.html#facts .. _facts: fact_ .. _family_relations: examples.html#family-relations .. _FAQ: http://sourceforge.net/apps/trac/pyke/wiki/FAQ .. _fc_premise: pyke_syntax/krb_syntax/fc_rule.html#foreach-clause .. _fc_rule: pyke_syntax/krb_syntax/fc_rule.html .. _forall: pyke_syntax/krb_syntax/compound_premise.html#forall-premise .. _forall premise: forall_ .. _forward-chaining: `forward-chaining rule lp`_ .. _forward-chaining rule: `forward-chaining rule lp`_ .. _forward-chaining rule lp: logic_programming/rules/forward_chaining.html .. _forward-chaining rule syntax: pyke_syntax/krb_syntax/fc_rule.html .. _forward-chaining rules: `forward-chaining rule`_ .. _fully bound: logic_programming/pattern_matching/matching_patterns.html#binding-to-a-tuple-pattern .. _functools: http://docs.python.org/library/functools.html .. _functools.partial: functools_ .. _goal: `goal lp`_ .. _goal lp: logic_programming/rules/backward_chaining.html .. _goal syntax: pyke_syntax/krb_syntax/bc_rule.html#use-clause .. _goals: goal_ .. _hg: Mercurial_ .. _hgrc: http://www.selenic.com/mercurial/hgrc.5.html .. _hgrc_keywords: http://pyke.hg.sourceforge.net/hgweb/pyke/release_1/raw-file/tip/hgrc_keywords .. _home page: index.html .. _how to call Pyke: using_pyke/index.html .. _HTMLTemplate: http://py-templates.sourceforge.net/htmltemplate/index.html .. _inherit: knowledge_bases/rule_bases.html#rule-base-inheritance .. _inherits: inherit_ .. _Keyword Extension: http://mercurial.selenic.com/wiki/KeywordExtension .. _.kfb file: pyke_syntax/kfb_syntax.html .. _.kfb files: `.kfb file`_ .. _.kfb: `.kfb file`_ .. _knapsack problem: http://en.wikipedia.org/wiki/Knapsack_problem .. _knowledge base: knowledge_bases/index.html .. _knowledge bases: `knowledge base`_ .. _knowledge_engine.engine: using_pyke/creating_engine.html .. _knowledge engine: `knowledge_engine.engine`_ .. _.kqb file: pyke_syntax/kqb_syntax.html .. _.kqb files: `.kqb file`_ .. _.kqb: `.kqb file`_ .. _.krb file: pyke_syntax/krb_syntax/index.html .. _.krb files: `.krb file`_ .. _.krb: `.krb file`_ .. _krb_traceback: using_pyke/other_functions.html#krb-traceback .. _Literal patterns: logic_programming/pattern_matching/literal_patterns.html .. _loading: `knowledge_engine.engine`_ .. _Logic Programming: http://en.wikipedia.org/wiki/Logic_programming .. _logic programming in pyke: logic_programming/index.html .. _matched: `pattern matches`_ .. _matching two patterns together: logic_programming/pattern_matching/matching_patterns.html .. _Mercurial: http://mercurial.selenic.com/wiki/ .. _Mercurial hook: http://www.selenic.com/mercurial/hgrc.5.html#hooks .. _Mercurial Hosting Sites: http://mercurial.selenic.com/wiki/MercurialHosting .. _Modifying Pyke: about_pyke/modifying_pyke.html .. _MySQL: http://www.mysql.com/ .. _notany: pyke_syntax/krb_syntax/compound_premise.html#notany-premise .. _notany premise: notany_ .. _Other Required Packages: about_pyke/installing_pyke.html#other-required-packages .. _package: http://www.python.org/doc/essays/packages.html .. _pathological question: logic_programming/pattern_matching/matching_patterns.html#pathological-question .. _pattern: `pattern lp`_ .. _pattern lp: logic_programming/pattern_matching/index.html .. _pattern syntax: pyke_syntax/krb_syntax/pattern.html .. _pattern matches: logic_programming/pattern_matching/index.html .. _pattern matching: `pattern matches`_ .. _pattern variable: `pattern variable lp`_ .. _pattern variable lp: logic_programming/pattern_matching/pattern_variables.html .. _pattern variable syntax: pyke_syntax/krb_syntax/pattern.html#pattern-variable .. _pattern variables: `pattern variable`_ .. _patterns: pattern_ .. _pickled: using_pyke/other_functions.html#running-and-pickling-plans .. _pickle: http://docs.python.org/library/pickle.html .. _pickles: pickle_ .. _pip: http://pypi.python.org/pypi/pip .. _plan: logic_programming/plans.html .. _plan_spec: pyke_syntax/krb_syntax/bc_rule.html#plan-spec .. _plans: plan_ .. _PLY: http://www.dabeaz.com/ply/ .. _premise: `premise lp`_ .. _premise lp: logic_programming/rules/index.html#premises-and-conclusions .. _premise syntax: pyke_syntax/krb_syntax/bc_rule.html#when-clause .. _premises: premise_ .. _print_stats: using_pyke/other_functions.html#miscellaneous .. _programming in the large: http://portal.acm.org/citation.cfm?id=808431 .. _program in the large: `programming in the large`_ .. _program startup: using_pyke/creating_engine.html .. _Prolog: http://en.wikipedia.org/wiki/Prolog .. _prove_1: prove_ .. _prove_1_goal: prove_ .. _prove_goal: `prove`_ .. _prove_n: `prove`_ .. _prove: using_pyke/proving_goals.html .. _proving: prove_ .. _proving goals: prove_ .. _PyCon 2008: http://us.pycon.org/2008/about/ .. _Pyke: http://groups.google.com/group/pyke .. _Pyke project download page: http://sourceforge.net/projects/pyke/files/ .. _pypi: http://pypi.python.org/pypi .. _Python: http://www.python.org .. _Python egg file: http://peak.telecommunity.com/DevCenter/PythonEggs .. _python_premise: pyke_syntax/krb_syntax/python_premise.html .. _python_statements: pyke_syntax/krb_syntax/python_premise.html#python-statements .. _question base: knowledge_bases/question_bases.html .. _question bases: `question base`_ .. _question: knowledge_bases/question_bases.html .. _questions: question_ .. _Rebuilding the HTML Documentation: about_pyke/modifying_pyke.html#rebuilding-the-html-documentation .. _regular expression syntax: http://docs.python.org/library/re.html#regular-expression-syntax .. _relative import: http://www.python.org/dev/peps/pep-0328/ .. _reset: using_pyke/index.html#using-different-facts-for-different-cases .. _rest2web: http://sourceforge.net/projects/rest2web .. _rule base category: knowledge_bases/rule_bases.html#rule-base-categories .. _rule base: knowledge_bases/rule_bases.html .. _rule base's: `rule base`_ .. _rule bases: `rule base`_ .. _rule: logic_programming/rules/index.html .. _rule's: rule_ .. _rules: rule_ .. _Running Unit Tests: about_pyke/modifying_pyke.html#running-unit-tests .. _siege: http://www.joedog.org/index/siege-home .. _special: knowledge_bases/special.html .. _sqlgen: examples.html#sqlgen .. _statement: logic_programming/statements.html .. _statements: statement_ .. _string.Template: http://docs.python.org/library/string.html#template-strings .. _subprocess.CalledProcessError: http://docs.python.org/library/subprocess.html#exceptions .. _subprocess.Popen: http://docs.python.org/library/subprocess.html#subprocess.Popen .. _taking clause: pyke_syntax/krb_syntax/bc_rule.html#taking-clause .. _traceback: http://docs.python.org/library/traceback.html .. _Tuple patterns: logic_programming/pattern_matching/tuple_patterns.html .. _TurboGears 2: http://turbogears.org/2.0/ .. _variables: `pattern variable`_ .. _web_framework: examples.html#web-framework .. _when clause: pyke_syntax/krb_syntax/bc_rule.html#when-clause .. _wiki: http://sourceforge.net/apps/trac/pyke/wiki .. _with clause: pyke_syntax/krb_syntax/bc_rule.html#with-clause .. _WSGI: http://www.python.org/dev/peps/pep-0333/ .. _wxpython: http://www.wxpython.org/ ./pyke-1.1.1/doc/source/examples.txt0000644000175000017500000001562111354262654016252 0ustar lambylamby.. $Id: examples.txt 6de8ee4e7d2d 2010-03-29 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Examples page-description: An overview of the examples provided with Pyke. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: examples.txt 6de8ee4e7d2d 2010-03-29 mtnyogi $ /uservalues ======== Examples ======== .. this code is hidden and will change to the root directory and add '' to sys.path for the code section following: >>> import sys >>> if '' not in sys.path: sys.path.insert(0, '') >>> import os >>> os.chdir("../..") # get out of documents directory back to root dir >>> os.chdir("examples/towers_of_hanoi") Several examples are included to help you become familiar with Pyke. These are all in an ``examples`` directory:: $ cd examples/towers_of_hanoi $ python >>> import driver >>> driver.test(2) got 1: ((0, 1), (0, 2), (1, 2)) got 2: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) Each example is in its own sub-directory and has a README.txt file to get you started. They all have `.krb files`_ and a Python module to run the example that also demonstrates `how to call Pyke`_ from your Python program. Family_relations ================ This is a very good basic example to start with. The family_relations example takes an initial set of facts_ about people (stated in a `.kfb file`_):: son_of(david_r2, david_r, sarah_r) daughter_of(shirley, david_r, sarah_r) And figures out how any two people are related:: david_r2, shirley are ('brother', 'sister') This same problem is solved in four different ways so that you can compare them: - Forward-chaining_ only - Backward-chaining_ only - Backward-chaining only with a few rule optimizations that make the rules run 100 times faster! - A mix of forward-chaining and backward-chaining with some use of plans_ added too. The driver.py program also demonstrates how to use krb_traceback_ and the print_stats_ function. Knapsack ======== At the `PyCon 2008`_ conference, somebody asked about the `knapsack problem`_. We found a solution in Prolog here__ (starting on page 19), and rewrote it in Pyke. This is a quick simple little example. .. __: http://www.ise.gmu.edu/~duminda/classes/fall03/set3.ppt Sqlgen ====== Pyke was originally developed as the control component for a web framework. This example shows how Pyke can automatically generate SQL SELECT statements, given a set of tables that the calling program has keys to and a tuple of the desired column names. Column names specified at the top-level in this tuple are expected to have a single value each. Nested tuples are used when multiple rows are expected. The column names in nested tuples make up the columns in the result rows. The top-level goal returns a plan_ that takes the key values for the initial set of tables given to the goal and returns an immutable dictionary mapping the column names to the values retrieved from the database. The plan may be used repeatedly without re-running the rules each time to figure out the SELECT statements. Thus, this acts like a SELECT statement compiler resulting in queries with virtually no extra overhead. It is *not*, however, an Object Relational Mapper (ORM). The data model used for the requested columns is that tables inherit the columns from tables they link to. So if there is a 1-many relationship between tables A and B (1 A row has many B rows), the B table inherits the columns from the A table through it's link to table A. The Pyke rules will automatically figure out the table joins for this. The program automatically introspects the schema information. For this example, it assumes that ``id`` is the primary key for each table, and that when one table links to another, it uses the target table name suffixed with ``_id`` as the column name. This example was originally done using MySQL_ and includes the .sql files to create the database, tables, and example data. The example has since been converted to use the Sqlite3 database to make it easier to run, as Sqlite3 does not require any setup (the Sqlite3 database file is included in the example). Sqlgen lacks more general capabilities that would be required for real use, but may serve as a starting point for another project that's more complete. This example also has much more elaborate rules than the prior two examples and is a very real example of generating plans_. Web_framework ============= This example completes the Python web framework demo by adding rules to automatically generate code to render HTML templates from the HTMLTemplate_ package (you can run ``pip install HTMLTemplate`` or ``easy_install HTMLTemplate`` to install the HTMLTemplate package). This example uses the sqlgen_ example, above, to generate the SQL statements. An HTMLTemplate does not include anything resembling program code in it, so that your graphics designers can completely own the html files without the developers having to modify them in any way. Note that the code generated here is fully cooked_ code, custom built for that specific schema and HTML template. This runs extremely fast because there is nothing left at run-time concerning parsing and figuring out the HTML template, or constructing the SQL statements. A test was done comparing this web framework example to the same example done in `TurboGears 2`_ running against the same MySQL database. The results of the siege_ benchmark tests show that Pyke is just over 10 times faster than TurboGears 2:: - Pyke: 791 trans/sec - TurboGears 2: 76 trans/sec The demo is packaged as a WSGI_ application. It also demonstrates the use of multiple `rule bases`_ by using the sqlgen example above, as well as the caching and reuse of plans_ to achieve the order of magnitude improvement in performance over current practice. ./pyke-1.1.1/doc/source/index.txt0000644000175000017500000000751211346504626015542 0ustar lambylamby.. $Id: index.txt 7e7a566ccb5b 2010-03-04 mtnyogi $ .. .. Copyright © 2007-2009 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Home format: rest encoding: utf8 output-encoding: utf8 section-pages: , about_pyke/index, logic_programming/index, knowledge_bases/index, pyke_syntax/index, using_pyke/index, examples, PyCon2008-paper template: template.txt template-encoding: utf8 include: No initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt 7e7a566ccb5b 2010-03-04 mtnyogi $ /uservalues ============================= Welcome to Pyke ============================= ----------------------------- Release 1.1 ----------------------------- Pyke introduces a form of `Logic Programming`_ (inspired by Prolog_) to the Python community by providing a knowledge-based inference engine (expert system) written in 100% Python. Unlike Prolog, Pyke integrates with Python allowing you to invoke Pyke from Python and intermingle Python statements and expressions within your expert system rules. Pyke was developed to significantly raise the bar on code reuse. Here's how it works: #. You write a set of Python functions, and a set of Pyke rules__ to direct the configuration and combination of these functions. #. These functions refer to Pyke `pattern variables`_ within the function body. #. Pyke may instantiate each of your functions multiple times, providing a different set of constant values for each of the pattern variables used within the function body. Each of these instances appears as a different function. #. Pyke then automatically assembles these customized functions into a complete program (function call graph) to meet a specific need or use case. Pyke calls this function call graph a plan_. .. __: `backward-chaining rule lp`_ In this way, Pyke provides a way to radically customize and adapt your Python code for a specific purpose or use case. Doing this essentially makes Pyke a very high-level compiler. And taking this approach also produces dramatic increases in performance. And Pyke is very successful at this, providing order of magnitude improvements in: - Code adaptability (or customization), - Code reuse and - Performance Pyke does not replace Python, nor is meant to compete with Python. Python is an excellent general purpose programming language, that allows you to "program in the small". Pyke builds upon Python by also giving you tools to directly `program in the large`_. Oh, and Pyke uses Logic Programming to do all of this. So if you're interested in Logic Programming or Expert Systems, well Pyke has that too... Pyke on Google Groups ===================== Please join Pyke_ on Google Groups for questions and discussion! FAQ === There is also an FAQ_ list on the sourceforge wiki_, to make it easy to contribute. ./pyke-1.1.1/doc/source/bin/0000755000175000017500000000000011425360453014431 5ustar lambylamby./pyke-1.1.1/doc/source/bin/gen_html0000755000175000017500000000056511346504626016166 0ustar lambylamby#!/bin/bash # gen_html # This must be executed in the doc/source directory! if ! expr `pwd` : '.*/doc/source$' > /dev/null then echo "gen_html: must be executed in doc/source directory" >&2 exit 2 fi set -e bin/add_links (cd ..; r2w r2w.ini) status=$? bin/strip_links if [ "$status" -ne 0 ] then exit $status fi bin/make_sitemap > ../html/sitemap.xml ./pyke-1.1.1/doc/source/bin/gather_links0000755000175000017500000000025311346504626017035 0ustar lambylamby#!/bin/bash # gather_links usage() { echo "usage: gather_links" >&2 exit 2 } [ $# -eq 0 ] || usage set -e find . -name '*.txt' | xargs python gather_links.py ./pyke-1.1.1/doc/source/bin/make_sitemap0000755000175000017500000000245711346504626017032 0ustar lambylamby#!/bin/bash # make_sitemap STATUS_FILE=/tmp/make_sitemap.$$ set -e set -o pipefail hg status | sed -n '\, doc/source/,s,, ,p' | sort > $STATUS_FILE trap "rm $STATUS_FILE" 0 echo '' echo '' find . -name '*.txt' | sort | while read f do if [ "$f" != ./template.txt ] then f_clean=`expr "$f" : '\./\(.*\)'` code=`sed -n '\,^\(.\) '"$f_clean"'$,s,,\1,p' "$STATUS_FILE" | tr -d '\n'` #echo "$f_clean: >$code<" >&2 if [ "$code" != R ] then html=`expr "$f_clean" : '\(.*\)\.txt'`.html url="http://pyke.sourceforge.net/$html" if [ "$code" ] then date=`date --iso-8601 -u` #echo "$f_clean: using system date: $date" >&2 else date=`sed -n '1s/.*\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/p' "$f"` if [ ! "$date" ] then echo "$f: missing date" >&2 exit 1 fi #echo "$f_clean: using hg date: $date" >&2 fi cat <<-! $url $date monthly ! fi fi done echo '' ./pyke-1.1.1/doc/source/bin/gather_links.py0000644000175000017500000000412011346504626017456 0ustar lambylamby# gather_links.py from __future__ import with_statement import re import os.path ok1 = re.compile(r'\.\. +_[^:]+: *([^ `]+|[^ ].*_) *$') ok2 = re.compile(r'\.\. +_[^:]+: *$') ok3 = re.compile(r' *[^ ]+$') split_ref = re.compile(r'(\.\. +_[^:]+: *)([^ `]+|[^ ].*_) *$') def run_command(args): for filename in args: dir, base = os.path.split(filename) if dir.startswith('./'): dir = dir[2:] if dir == '.': dir = '' print "dir:", dir, "base:", base ans = [] gathering = False need_continuation = False with open(filename) as f: for line in f: line = line.rstrip() if need_continuation: need_continuation = False if ok3.match(line): #print "ok3, line:", line ans[-1] += ' ' + line.lstrip() else: #print "no continuation for:", ans[-1], "line:", line ans = [] gathering = False elif ok1.match(line): #print "ok1, line:", line gathering = True ans.append(line) elif ok2.match(line): #print "ok2, line:", line gathering = True ans.append(line) need_continuation = True elif gathering: #print "nope!, line:", line #for line in ans: print "dumping:", line ans = [] gathering = False for line in ans: match = split_ref.match(line) if not match: print "split_ref failed on:", line else: ref, link = match.groups() if link[-1] == '_': print line elif link.startswith('http://'): print line else: print ref.rstrip(), os.path.normpath(os.path.join(dir, link)) if __name__ == "__main__": import sys run_command(sys.argv[1:]) ./pyke-1.1.1/doc/source/bin/get_links.py0000755000175000017500000000404211353260076016765 0ustar lambylamby#!/usr/bin/env python # get_links.py from __future__ import with_statement import os import os.path def run_command(start_dir, outfilename): dir = start_dir links_seen = set() def doctor(link_dir, path): # Don't mess with paths that just refer to another link: if path.rstrip()[-1] == '_': return path path = path.lstrip() # Don't mess with paths that point somewhere in the outside universe: if path.startswith('http://'): return ' ' + path # Prepend link_dir to path if link_dir.startswith('./'): path = link_dir[2:] + '/' + path elif link_dir != '.': path = link_dir + '/' + path # Prepare dir (start_dir, minus initial './') if start_dir == '.': dir = '' elif start_dir.startswith('./'): dir = start_dir[2:] else: dir = start_dir rest=' ' last_dir = None while dir and dir != last_dir: if path.startswith(dir + '/'): ans = rest + path[len(dir) + 1:] #print "doctor(%s) abbr:" % (path.rstrip(),), ans return ans rest += '../' last_dir = dir dir, ignore = os.path.split(dir) ans = rest + path #print "doctor(%s) abs:" % (path.rstrip(),), ans return ans with open(outfilename, "w") as outfile: outfile.write("\n") while True: try: with open(os.path.join(dir, 'links')) as links: for line in links: link, path = line.split(':', 1) if link not in links_seen: links_seen.add(link) outfile.write(":".join((link, doctor(dir, path)))) except IOError: pass if dir == '.': break dir = os.path.dirname(dir) if __name__ == "__main__": import sys if len(sys.argv) != 3: print >> sys.stderr, "usage: get_links.py dir outfile" sys.exit(2) run_command(sys.argv[1], sys.argv[2]) ./pyke-1.1.1/doc/source/bin/strip_links0000755000175000017500000000135411346504626016727 0ustar lambylamby#!/bin/bash # strip_links usage() { echo "usage: strip_links" >&2 exit 2 } [ $# -eq 0 ] || usage if ! expr `pwd` : '.*/doc/source$' > /dev/null then echo "strip_links: must be executed in doc/source directory" >&2 exit 2 fi set -e find . -name '*.txt' | while read filename do if [ "$filename" != ./template.txt -a \ "$filename" != ./PyCon2008-paper.txt -a \ -e "$filename" ] then #echo "$filename" if true then mtime=`stat -c '%y' "$filename"` ed -s "$filename" <<-'!' H /^\.\. ADD_LINKS MARKER$/,$d w q ! # $; # hush! # ?^[ ]*$?; # hush! # .,$d touch -d "$mtime" "$filename" fi fi done ./pyke-1.1.1/doc/source/bin/add_links0000755000175000017500000000200011346504626016303 0ustar lambylamby#!/bin/bash # add_links # This must be executed in the doc/source directory! MASTER_LINKS=links TMP_FILE=/tmp/add_links.$$ usage() { echo "usage: add_links" >&2 exit 2 } [ $# -eq 0 ] || usage if ! expr `pwd` : '.*/doc/source$' > /dev/null then echo "add_links: must be executed in doc/source directory" >&2 exit 2 fi set -e find . -name .hg -prune -o -type d -print | while read dir do echo "Doing $dir" bin/get_links.py "$dir" $TMP_FILE for file in "$dir"/*.txt do if [ "$file" != ./template.txt -a "$file" != ./PyCon2008-paper.txt -a \ -e "$file" ] then #echo "$file" mtime=`stat -c '%y' "$file"` if [ `tail -1 "$file"` ] then # Make sure that file ends in a blank line! echo >> "$file" fi echo ".. ADD_LINKS MARKER" >> "$file" cat $TMP_FILE >> "$file" touch -d "$mtime" "$file" fi done rm -f $TMP_FILE done ./pyke-1.1.1/doc/source/pyke_syntax/0000755000175000017500000000000011425360453016237 5ustar lambylamby./pyke-1.1.1/doc/source/pyke_syntax/krb_syntax/0000755000175000017500000000000011425360453020423 5ustar lambylamby./pyke-1.1.1/doc/source/pyke_syntax/krb_syntax/fc_rule.txt0000644000175000017500000000616111346504626022613 0ustar lambylamby.. $Id: fc_rule.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Fc_rule page-description: The syntax of a forward-chaining rule. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: fc_rule.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ============================================= Fc_rule Syntax ============================================= Fc_rule ============= Forward-chaining_ rules have three parts: #. A unique name. #. An optional `foreach clause`_. #. An `assert clause`_. :: fc_rule ::= IDENTIFIER NL INDENT [fc_foreach] fc_assert DEINDENT The ``IDENTIFIER`` uniquely names this rule_ and is used as the corresponding Python function name in the generated _fc.py file. Foreach clause ================= :: fc_foreach ::= 'foreach' NL INDENT {fc_premise NL} DEINDENT fc_premise ::= fact_pattern | compound_premise | python_premise fact_pattern ::= IDENTIFIER '.' IDENTIFIER '(' [{pattern,}] ')' Here are links to the definitions for pattern_, compound_premise_ and python_premise_. If the ``foreach`` clause is omitted, the rule_ is always fired once. If the ``foreach`` clause is present, the rule is fired for each combination of true premises. Assert clause ================= :: fc_assert ::= 'assert' NL INDENT {assertion NL} DEINDENT assertion ::= fact_pattern | python_statements Here is the link to the definitions of python_statements_. The ``assert`` clause lists new facts_ to assert, and/or Python statements to execute each time the rule_ is fired. Each of these may include `pattern variables`_ which should also appear in the ``foreach`` clause where they are bound to a value. These values will then be substituted into the facts and Python statements. ./pyke-1.1.1/doc/source/pyke_syntax/krb_syntax/pattern.txt0000644000175000017500000000605111346504626022647 0ustar lambylamby.. $Id: pattern.txt 057d79259b20 2009-05-14 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Pattern page-description: The syntax of a pattern used to match data values. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: pattern.txt 057d79259b20 2009-05-14 mtnyogi $ /uservalues ============================================= Pattern Syntax ============================================= Pattern ============ :: pattern ::= 'None' | 'True' | 'False' | NUMBER | IDENTIFIER | STRING | variable | '(' [{pattern,}] ['*' variable] ')' IDENTIFIER acts like a STRING here, meaning that it is taken as a literal value. All variables in patterns must be preceded by a ``$``. Pyke does not currently support complex NUMBERS (for no good reason -- email me if you need them). Pattern Variable =================== `Pattern variables`__ are simply called *variable* in the syntax:: variable ::= '$'IDENTIFIER The variable must not have a space between the ``$`` and the ``IDENTIFIER``. .. __: `pattern variable lp`_ Anonymous Variable ==================== If the pattern variable IDENTIFIER begins with an underscore (_), the variable is an `anonymous variable`__. It acts like a "don't care". Technically, this means that multiple uses of the same IDENTIFIER may stand for different values. The name of the IDENTIFIER after the underscore is ignored and may be used to document the use of the anonymous variable. .. __: `anonymous variable lp`_ Rest Variable ================ The ``*variable`` at the end of a tuple pattern will match the rest of the tuple. Thus, ``variable`` is *always* bound to a (possibly empty) tuple. The syntax is taken from rest parameter syntax in Python function definitions. The difference here is that the variable needs a ``$`` on it. You may use either a named variable or an anonymous variable here. ./pyke-1.1.1/doc/source/pyke_syntax/krb_syntax/compound_premise.txt0000644000175000017500000002267611346504626024555 0ustar lambylamby.. $Id: compound_premise.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Compound Premise page-description: The syntax of compound premises. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: compound_premise.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ============================================= Compound Premise Syntax ============================================= There are three kinds of compound premises. These can be used in both `forward-chaining rules`_ and `backward-chaining rules`_, but the nested premises within each of these are restricted to the kind of premises legal for that kind of rule: fc_premise_ for forward-chaining rules, and bc_premise_ for backward-chaining rules. :: compound_premise ::= first_premise | forall_premise | notany_premise First Premise ===================== The ``first`` premise is used to prevent backtracking_ from finding subsequent solutions to a set of premises. The ``first`` premise always fails on backtracking (but does do backtracking *within* the nested premises). :: first_premise ::= ['!'] 'first' premise | ['!'] 'first' NL INDENT {premise NL} DEINDENT The ``!`` option can only be used in backward-chaining rules. When used within backward-chaining rules, the nested premises may include any type of plan_spec_. Forall Premise ===================== The ``forall`` premise forces backtracking_ within the nested premises to process all of the possible solutions found before the ``forall`` succeeds. After the first success, the ``forall`` fails on backtracking. :: forall_premise ::= 'forall' NL INDENT {premise NL} DEINDENT [ 'require' NL INDENT {premise NL} DEINDENT ] The premises within the ``require`` clause are tried for each solution found to the ``forall`` clause. If these fail for any solution, then the entire ``forall`` premise fails. Thus, the ``forall`` only succeeds if the ``require`` premises are true for *all* solutions generated within the ``forall`` clause. Thus, the ``forall`` clause may be read: "Forall X, require Y". The ``forall`` always succeeds if the ``require`` clause is omitted (even if no solutions are found to the nested premises). This can be used in conjunction with python_statements_ to gather a list of results. See `Notes on Forall and Notany Premises`_ and Examples_, below. Notany Premise ===================== The ``notany`` premise only succeeds if no solution can be found to the nested premises. ``Notany`` always fails on backtracking_. :: notany_premise ::= 'notany' NL INDENT {premise NL} DEINDENT See `Notes on Forall and Notany Premises`_ and Examples_, below. Notes on Forall and Notany Premises ====================================== #. All `pattern variable`_ bindings made during the execution of a ``forall`` or ``notany`` premise are undone before the premises following the ``forall`` or ``notany`` are run. Thus, ``forall`` and ``notany`` can be used to test values produced by prior premises; but to generate values for subsequent premises the values must be captured in Python variables within the ``forall`` or ``notany`` clause before the `pattern variables` are unbound (see `Computing a Value for Each Generated Value`_, below). #. When used within `backward-chaining rules`_, the only plan_spec_ allowed in nested premises is the ``as`` clause. Examples ============= - `Finding the First Solution From a Set of Values`_ - `Testing Every Generated Value`_ - `Computing a Value for Each Generated Value`_ - `Iterating on Tuples`_ - `Computing Values for All Generated Values that Pass a Test`_ These examples use the following subgoals: * ``generate_x($x)`` generates multiple solutions (as ``$x``) that will be looped over * ``test_x($x)`` does some test on ``$x`` * ``compute_y($x, $y)`` takes ``$x`` as input and computes a ``$y`` value Finding the First Solution From a Set of Values ------------------------------------------------- If you want the first ``$x`` that passes the ``test_x($x)`` test, you have two options:: generate_x($x) test_x($x) ... And:: first generate_x($x) test_x($x) ... The difference is that the first example will find other ``$x`` values that pass ``test_x($x)`` on backtracking_, while the second example will stop after the first value is found and fail on backtracking. Testing Every Generated Value ----------------------------- There are two general cases. You might want to verify that ``test_x($x)`` *succeeds* for all generated ``$x`` values:: forall generate_x($x) require test_x($x) .. Note:: While ``$x`` is set and used within the ``forall`` premise to transfer values from the ``generate_x($x)`` goal to the ``test_x($x)`` goal, it is no longer set afterwards and can not be referenced in the premises following the ``forall`` premise. The second case that you might want to verify is that ``test_x($x)`` *fails* for every generated ``$x`` value:: forall generate_x($x) require notany test_x($x) Or, more simply:: notany generate_x($x) test_x($x) Computing a Value for Each Generated Value ------------------------------------------ If you want a tuple of computed ``$y`` values for all of the ``$x`` values:: python y_list = [] forall generate_x($x) require compute_x($x, $y) python y_list.append($y) $y_list = tuple(y_list) This will only succeed if ``compute_y`` succeeds for every ``$x`` value. If you want to skip over ``$x`` values that ``compute_y`` fails on, you *might* try:: python y_list = [] forall generate_x($x) compute_x($x, $y) python y_list.append($y) $y_list = tuple(y_list) But note that if ``compute_y`` computes multiple solutions for a single ``$x`` value on backtracking_, you would end up including all of these solutions in your ``$y_list``. To only get the first computed value for each ``$x`` value:: python y_list = [] forall generate_x($x) first compute_x($x, $y) python y_list.append($y) $y_list = tuple(y_list) Iterating on Tuples ------------------- A simple common case of ``generate_x`` is when you are computing values for each element of a tuple:: python y_list = [] forall $x in $x_list require compute_x($x, $y) python y_list.append($y) $y_list = tuple(y_list) This can also be done by creating a new subgoal that recurses on ``$x_list``. If you call the new subgoal ``compute_list``, you would use it like this:: compute_list($x_list, $y_list) And define it like this:: compute_list_done use compute_list((), ()) compute_list_step use compute_list(($x, *$x_rest), ($y, *$y_rest)) when compute_y($x, $y) compute_list($x_rest, $y_rest) .. important:: Note that there is an important difference between these two examples if ``compute_y`` may find alternate ``$y`` values for any given ``$x`` value on backtracking_. The first example will only generate one ``$y_list``. If that ``$y_list`` doesn't work for subsequent premises, the ``forall`` fails on backtracking, so no overall solution will be found. The second example will not fail in this situation, but will produce all possible combinations of solutions to ``compute_y`` for each ``$x`` on backtracking until a resulting ``$y_list`` satisfies the subsequent premises so that an overall solution *is* found. Computing Values for All Generated Values that Pass a Test ---------------------------------------------------------- Finally, if you want to gather only the computed ``$y`` values for ``$x`` values that pass ``test_x($x)``:: python y_list = [] forall generate_x($x) test_x($x) require compute_x($x, $y) python y_list.append($y) $y_list = tuple(y_list) ./pyke-1.1.1/doc/source/pyke_syntax/krb_syntax/python_premise.txt0000644000175000017500000001167411346504626024246 0ustar lambylamby.. $Id: python_premise.txt 9c1b571b39ac 2009-02-15 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Python Premise page-description: The syntax of a *python_premise*. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: python_premise.txt 9c1b571b39ac 2009-02-15 mtnyogi $ /uservalues ===================== Python Premise Syntax ===================== Python_premise ============== :: python_premise ::= pattern '=' python_exp | pattern 'in' python_exp | 'check' python_exp | python_statements Each of these clauses results in a Python expression being executed. Their meaning is as follows: pattern_ '=' python_exp *python_exp* is evaluated and the result matched_ with pattern_. If the result does not match, the clause fails. The clause always fails on backtracking_, meaning that it only produces a single result (contrasted with ``in``). pattern_ 'in' python_exp *python_exp* is evaluated to produce a Python *iterable* and the first element from the resulting iterable is matched_ with pattern_. On backtracking_, successive elements from the iterable are matched with pattern_. When the result is exhausted, the clause fails. This has the effect of offering each element of the result, one at a time, to the subsequent premise clauses. Each element is thus acted upon individually. 'check' python_exp *python_exp* is evaluated. If the result is Python "true" the clause succeeds, otherwise it fails. The clause always fails on backtracking_. Python_statements =================== :: python_statements ::= 'python' python_statement | 'python' NL INDENT {python_statement NL} DEINDENT This clause allows the inclusion of arbitrary Python statements in your rules_. This premise always succeeds; and then fails on backtracking_. The current ``knowledge_engine`` object is available within python_statements as the variable called ``engine``. .. caution:: Always keep in mind the difference between `pattern variables`_ and *Python variables*. Pattern variables are always indicated with a ``$`` and are only bound to a value during inferencing. #. Thus, a ``python_statement`` may not set a pattern variable. Storing a value computed by Python into a pattern variable can only be done using the python_premise:: = #. When a pattern variable is used within a Python expression or statement, it must be `fully bound`_. #. Python variables are not visible to the inference engine. They are local variables that are also not visible to Python code in other rules_ or other invocations of the same rule. #. Finally, Python variables in the `when clause`_ of a `backward-chaining rule`_ are not visible to the Python code in the `with clause`_ of the same rule. (These end up in two different Python functions after the `.krb file`_ is compiled). So this won't work:: some_bc_rule use some_goal(...) when ... python x_list = ... with for x in x_list: process(x) In this case, assign the value of the Python variable to a pattern variable in the when clause and then use that pattern variable in the with clause:: some_bc_rule use some_goal(...) when ... python x_list = ... $x_list = tuple(x_list) with for x in $x_list: process(x) ./pyke-1.1.1/doc/source/pyke_syntax/krb_syntax/index.txt0000644000175000017500000001051211346504626022276 0ustar lambylamby.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: KRB Syntax page-description: Syntax of the *Knowledge Rule Base* (KRB) files, which is where you write your rules. /description section-pages: , fc_rule, bc_rule, pattern, compound_premise, python_premise format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues =================== KRB Syntax =================== This section describes the syntax for defining rules_ in KRB files. Keywords ================== ========= =========== ========= as foreach taking assert in True bc_extras None use check plan_extras when extending python with False step without fc_extras ========= =========== ========= Syntax of the Entire KRB File =================================== :: file ::= [NL] ['extending' IDENTIFIER ['without' {IDENTIFIER,}] NL] [{fc_rule} ['fc_extras' NL INDENT { NL} DEINDENT]] [{bc_rule} ['bc_extras' NL INDENT { NL} DEINDENT] ['plan_extras' NL INDENT { NL} DEINDENT]] The KRB file has three optional parts. It must contain at least one rule_ (either forward-chaining_ or backward-chaining_). The filename (minus the .krb extension) is the name of the `rule base`_. This must be a legal Python identifier. Extending clause ----------------- The optional ``extending`` clause, if used, is the first line of the file. This defines the parent `rule base`_ that this `rule base`_ inherits_ from. It may also specify a list of backward-chaining_ goal names to be excluded from this inheritance. Forward-Chaining Section -------------------------- If the krb file contains any forward-chaining_ rules, a Python source file will be created named _fc.py, where is the `rule base`_ name. The syntax of a forward-chaining rule (fc_rule_) is defined here__. The ``fc_extras`` may only be used if there are forward-chaining rules. This allows you to add other Python code (for example, ``import`` statements) to the generated Python source file. .. __: fc_rule_ Backward-Chaining Section -------------------------- If the krb file contains any backward-chaining_ rules, a Python source file will be created named _bc.py, where is the `rule base`_ name. The syntax of a backward-chaining rule (bc_rule_) is defined here__. The ``bc_extras`` can only be used if there are backward-chaining rules. This allows you to add other Python code (for example, ``import`` statements) to the generated Python source file. In addition, if any of the backward-chaining rules have plan_ code (a `with clause`_ or any subgoals in the `when clause`_ with a plan_spec_), a Python source file will be created named _plans.py, where is the `rule base` name. You use the ``plan_extras`` to include arbitrary Python code in this plans file. .. __: bc_rule_ ./pyke-1.1.1/doc/source/pyke_syntax/krb_syntax/bc_rule.txt0000644000175000017500000002024111346504626022602 0ustar lambylamby.. $Id: bc_rule.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Bc_rule page-description: The syntax of a backward-chaining rule. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: bc_rule.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ============================================= Bc_rule Syntax ============================================= Bc_rule ========== Backward-chaining_ rules_ have four parts: #. A unique name. #. A `use clause`_. #. An optional `when clause`_. #. An optional `with clause`_. :: bc_rule ::= IDENTIFIER NL INDENT use [when] [with] DEINDENT The ``IDENTIFIER`` is the unique name for this rule_ and is used as the corresponding Python function name in the generated _bc.py file and also for the Python function name of the plan_ function (if any) associated with the rule. This name will show up in stack traces associated with exceptions raised during inferencing or plan execution. Use Clause ============ The ``use`` clause is the **then** part of the rule. It identifies the *goal* that this rule is prepared to prove. :: use ::= 'use' IDENTIFIER '(' {pattern,} ')' ['taking' '(' ')'] NL | 'use' IDENTIFIER '(' {pattern,} ')' NL INDENT 'taking' '(' ')' NL DEINDENT Notice that it uses a single ``IDENTIFIER``. The `rule base`_ name is implied as the `rule base category`_ name (the name of the root rule base, see `extending clause`_) for the rule base containing this rule. Taking Clause ---------------- The ``use`` clause also defines parameters to the plan_ function (if one is used for this rule_) with the optional ``taking`` sub-clause. The *python_arg_spec* is not parsed by Pyke, but simply copied to the output plan function. Do **not** use ``$`` with these parameter names (or their default values). When Clause ============== The ``when`` clause is the **if** part of the rule_. It defines the premises that must be true for this rule to succeed. If the ``when`` clause is omitted, the only requirement for the rule to succeed is that the ``use`` clause `pattern matches`_ the goal. If the ``when`` clause is specified, the rule succeeds for each combination of true premises (see backtracking_). :: when ::= 'when' NL INDENT {bc_premise NL} DEINDENT bc_premise ::= ['!'] [ name '.' ] name '(' {pattern,} ')' [ plan_spec ] | compound_premise | python_premise name ::= IDENTIFIER | '$'IDENTIFIER Here are the links to the definitions for pattern_, compound_premise_ and python_premise_. If the *bc_premise* includes the ``!``, an AssertionError will be raised if the premise fails on the first try. This can help in debugging. .. Note:: This does not apply when the premise fails on backtracking_ (in which case it has already succeeded at least once). If a single *name* is used in the *bc_premise*, the `rule base category`_ for the current `rule base`_ (the root rule base name, see `extending clause`_) is assumed. If two *names* are used in the *bc_premise*, the first may name a rule base category or some other `knowledge base`_. If a rule base category name is used (or assumed), the currently active_ `rule base`_ for that category is used to prove the premise. .. note:: If the rule base category name is omitted, and therefore assumed to be the current rule base's rule base category, the current rule base does *not* have to be the active rule base for that category. It could be the case that a derived rule base is the active rule base. In that case, the derived rule base is used to prove the premise. In this way, different rules may be used to prove the same premise, depending upon which rule base has been activated. Plan_spec ------------ A *plan_spec* is required for each premise that returns a subordinate plan_. This shows what should be done with that subordinate plan_ function. Thus, a rule's plan function is composed first of the collected python_statements taken from its plan_specs (as described below), followed by the python_statements within its `with clause`_ (if any). The inclusion of any plan_spec containing a python_statement will cause a plan_ function to be generated for this rule, even if the rule lacks a ``with`` clause. :: plan_spec ::= [ 'step' NUMBER ] NL INDENT { NL} DEINDENT | 'as' '$'IDENTIFIER NL Within each python_statement, the subordinate plan function is indicated by ``$$``. The result of this function may be assigned to a Python variable, but not a `pattern variable`_ (``$variable``). Parameters from the rule's ``taking`` clause may be passed on to the subordinate plan_ functions. When multiple premises have python_statements in their *plan_specs*, the python_statements in plan_specs *without* a ``step`` clause are executed first in the order that they appear in the ``when`` clause. Then the python_statements in plan_specs *with* a ``step`` clause are executed in ascending NUMBER sequence. It is permissible for the NUMBER to be negative or a float. If the ``as`` clause is used, the plan function is bound to the pattern variable as a Python function, but not automatically executed. This allows you to call the function (or not) when and as many times as you please. The parameters required are defined in the ``taking`` clause of the rule used to prove the premise. .. note:: Within a forall_ or notany_ premise, the only ``plan_spec`` that may be used is the ``as`` clause. With Clause ============== The ``with`` clause contains Python statements to include in the plan_ produced by this rule_. These Python statements may include `pattern variables`_ whose values will be cooked_ into these statements when the plan is created. :: with ::= 'with' NL INDENT { NL} DEINDENT The *python_statements* are included in the rule's plan function after all of the calls to the subordinate plan functions made from the *plan_specs* in the `when clause`_. If the ``with`` clause is omitted, but the ``when`` clause has *plan_specs* (excluding the *as* clause), a plan function is still generated for this rule so that the subordinate plan functions are still called. The *python_statements* are not parsed. They are simply scanned for ``$`` pattern variables that don't occur within string literals or comments. The values bound to these variables are cooked_ into the code to produce the plan. Thus, all pattern variables used within *python_statements* (both in the ``plan_specs`` and the ``when`` clause) must be bound to a value. This value is a constant value that never changes for this plan_. .. note:: This occurs after the entire top-level goal is proven so that it is permissible to bind these pattern variables to values *following* the execution of the rule containing them. ./pyke-1.1.1/doc/source/pyke_syntax/links0000644000175000017500000000042311346504626017305 0ustar lambylamby.. _backward-chaining rule: `backward-chaining rule syntax`_ .. _forward-chaining rule: `forward-chaining rule syntax`_ .. _KRB files: index.html#lexical-structure .. _pattern: `pattern syntax`_ .. _pattern variable: `pattern variable syntax`_ .. _premise: `premise syntax`_ ./pyke-1.1.1/doc/source/pyke_syntax/kqb_syntax.txt0000644000175000017500000003342511346504626021176 0ustar lambylamby.. $Id: kqb_syntax.txt 4670da845e46 2010-03-05 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: KQB Syntax page-description: The syntax of *Knowledge Question Base* (KQB) files, which is where you spell out your end user questions. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: kqb_syntax.txt 4670da845e46 2010-03-05 mtnyogi $ /uservalues ========== KQB Syntax ========== This uses a different lexical structure than `KRB files`_. Textual parameter substitution is done with the standard Python `string.Template`_ class, which specifies parameters with a ``$``, much like `pattern variables`_. In the following descriptions, substitution parameters are acted upon in ``PARAMETRIZED_TEXT``, but not in ``TEXT``. The name of the `question base`_ is simple the filename with the ``.kqb`` suffix stripped. This must be a valid Python identifier. PARAMETRIZED_TEXT ================== ``PARAMETRIZED_TEXT`` may span multiple lines. Each subsequent line must be indented at least as much as the first character of ``PARAMETRIZED_TEXT`` on the first line. Line breaks and indentation are preserved. So syntax like:: match '!' PARAMETRIZED_TEXT Could be:: 2-10 ! This is the start of the parametrized text. ^ | +------ all lines must be indented at least this far! - what do you think? The ``PARAMETRIZED_TEXT`` here would be:: This is the start of the parametrized text. ^ | +------ all lines must be indented at least this far! - what do you think? But this is not legal:: 2-10 ! An example of PARAMETRIZED_TEXT with a second line that's not indented enough! Syntax for KQB File =================== :: file ::= [NL] {question} question ::= IDENTIFIER '(' [{parameter,}] ')' NL INDENT {PARAMETRIZED_TEXT NL} '---' NL parameter '=' question_type DEINDENT parameter ::= '$' IDENTIFIER Each question has a name and a fixed number of parameters. This is followed by one or more lines of ``PARAMETRIZED_TEXT`` that will be presented as the question to answer. These are terminated by a line containing only ``---``. One of the parameters is designated as the *answer* parameter on the line immediately following the terminating ``---``. This is the only parameter that may be unbound when a rule uses this question. For example, the file ``user_questions.kqb`` might contain:: ate($meal, $ans) Did you eat $meal? --- $ans = yn This question could be referenced in the premise_ of a rule_ as:: user_questions.ate(lunch, $ans) or:: user_questions.ate(lunch, False) But not:: user_questions.ate($meal, False) There are several different kinds of ``question_types``, each corresponding to a different way that the user might answer the question:: question_type ::= yn_type | integer_type | number_type | float_type | string_type | select_1_type | select_n_type The ``integer_type``, ``number_type``, ``float_type`` and ``string_type`` may include a match_ to force the user to enter a sensible answer. All of these may also include a review_, which is just ``PARAMETRIZED_TEXT`` that will be displayed when the user's answer matches a certain match_ value. Question_type links: - yn_type_ - integer_type_ - number_type_ - float_type_ - string_type_ - select_1_type_ - select_n_type_ YN_type ======= :: yn_type ::= 'yn' NL [review] The user answers "yes" or "no". The answer returned is True or False. If the `ask_tty`_ module is used, the user may type "yes", "y", "true" or "t" for True and "no", "n", "false", or "f" for False. These are case insensitive. Example:: ate($meal, $ans) Did you eat $meal? --- $ans = yn See review_, below. Integer_type ============ :: integer_type ::= 'integer' ['(' match ')'] NL [review] The user enters an integer. If the match_ is specified, the integer must match it or the user is asked to try again. Example:: hours_since_last_meal($ans) How many hours has it been since you last ate? --- $ans = integer(0-48) See review_, below. Number_type ============ :: number_type ::= 'number' ['(' match ')'] NL [review] The user enters either an integer or a floating point number. If the user enters an integer, a Python ``int`` is returned. Otherwise a Python ``float`` is returned. If the match_ is specified, the number must match it or the user is asked to try again. Example:: miles_to($dest, $ans) How many miles did you travel to get to $dest? --- $ans = number(0.1-3000) See review_, below. Float_type ============ :: float_type ::= 'float' ['(' match ')'] NL [review] The user enters an integer or a floating point number. But the answer returned is always a Python ``float``. If the match_ is specified, the number must match it or the user is asked to try again. Example:: price($object, $price) What did you pay for $object? --- $price = float See review_, below. String_type ============ :: string_type ::= 'string' ['(' match ')'] NL [review] The user enters a string (text). If the match_ is specified, the string must match it or the user is asked to try again. Example:: users_name($name) What's your name? - Please don't enter a fictitious (screen) name. --- $name = string(2-40) See review_, below. Match ===== There are several kinds of simple_matches that may be or-ed together with ``|``:: match ::= simple_match {'|' simple_match} The match succeeds if any of the ``simple_matches`` succeed. :: simple_match ::= '(' match ')' | [ STRING ] [ '[' TEXT ']' ] '/' REGEXP_TEXT '/' | [NUMBER] '-' NUMBER | NUMBER '-' | value '=' simple_match | value Regexp Match ------------ :: simple_match ::= [ STRING ] [ '[' TEXT ']' ] '/' REGEXP_TEXT '/' A regexp match can only be used with string_type_ questions. It matches if the regexp matches. If the regexp contains a single group, that group is returned as the question's answer rather than the entire string. If the regexp contains multiple groups, a tuple of the groups is returned as the question's answer rather than entire string. If STRING is specified on a regexp, it is used in the error message if the regexp fails. The error message is "Answer should be $error_msg, got $string". If '[' TEXT ']' is specified on a regexp, it is used in the prompt for the end user to inform him of what is expected. Generally, this prompt message is enclosed in '[' and ']' when it is displayed to the user. Example:: state_code($state) Enter your two digit state code. --- $state = string('uppercase'[uppercase]/[A-Z][A-Z]/) Range Match ----------- :: simple_match ::= [NUMBER] '-' NUMBER | NUMBER '-' A range match has a '-' in it. It matches if the answer is between the two values. If either value is omitted, that limit is not tested. If matched to a string, it matches the length of the string. Example:: age($years) How old are you? --- $years = integer(1-130) Value '=' Match --------------- :: simple_match ::= value '=' simple_match The '=' means "substituted for". The match_ fails if the match after the '=' fails. Otherwise it returns the value before the '=' rather than what the user entered. Note that you can or (``|``) several of these together to translate several different matched values. Example:: age_category($period_of_life) How old are you? --- $period_of_life = integer(child=1-12 | teenager=13-19 | young_adult=20-35 | middle_age=35-64 | elder=65-130) Value Match ----------- :: simple_match ::= value value ::= STRING | IDENTIFIER | NUMBER | 'None' | 'True' | 'False' A value match, only matches that one value. An IDENTIFIER is treated as a STRING. These are mostly used in reviews. Review ====== :: review ::= {match '!' PARAMETRIZED_TEXT NL} All of the ``reviews`` must be at the same indent level. The review is applied after the answer has been validated (validation possibly changes the value). Each match_ is checked and all of the matching review's ``PARAMETRIZED_TEXT`` messages are displayed to the user. Examples:: stupid_question($ans) Can you answer a question that is several lines long? --- $ans = yn True ! Correct! This is true because the sky is blue! False ! Nope! Remember that the sky is blue! wood($ans) How much wood would a woodchuck chuck if a woodchuck could chuck wood? --- $ans = integer(0-100) -10 ! more than that! 10-20 ! bingo! 21- ! I guess they're not as strong as you think ... .. This code is hidden. It will add '' to sys.path, change to the doc.examples directory and store the directory path in __file__ for the code section following: >>> import sys >>> if '' not in sys.path: sys.path.insert(0, '') >>> import os >>> os.chdir("../../examples") >>> __file__ = os.getcwd() Asking ``stupid_question`` and answering "y" to it:: >>> from pyke import knowledge_engine >>> engine = knowledge_engine.engine(__file__) >>> from StringIO import StringIO >>> import sys >>> class echo(object): ... def __init__(self, f): self.f = f ... def readline(self): ... ans = self.f.readline() ... sys.stdout.write(ans) ... return ans >>> sys.stdin = echo(StringIO('y\n')) displays:: >>> engine.prove_1_goal('user_questions.stupid_question($ans)') ______________________________________________________________________________ Can you answer a question that is several lines long? (y/n) y Correct! This is true because the sky is blue! ({'ans': True}, None) Select_1_type ============= :: select_1_type ::= 'select_1' NL alternatives This is a multiple choice question. The alternatives_ are displayed to the user, and he picks one (and only one). Example:: another_question($arg1, $arg2, $ans) question text with $arg1 stuff in it. on multiple lines - possibly indented - for who knows what reason... - maybe for $arg2? --- $ans = select_1 1: prompt for this selection with $arg2 in it too which can span multiple lines - and be indented ... ! Nope! Remember that the sky is blue! 2: next prompt ! =1 # same review as 1: 3: pick me! pick me!!! ! Correct! You certainly know about $arg1! yep, multiple review lines too... - and indented... Select_n_type ============= :: select_n_type ::= 'select_n' NL alternatives This is a multiple choice question. The alternatives_ are displayed to the user, and he picks as many as he likes. Example:: problems($list) Which of these problems are you experiencing? - select all that apply --- $list = select_n boot: The system won't boot. os: I hate Windows! internet: I can't connect to the internet. slow: The system is running too slow. ouch: Help! I've fallen and I can't get up! freeze: The system freezes or does not respond to input. printer: The printer doesn't work. senile: What's email? Alternatives ============ :: alternatives ::= {value ':' PARAMETRIZED_TEXT NL [alt_review]} All of the ``alternatives`` must be at the same indent level. The user only sees the ``PARAMETRIZED_TEXT`` values. The ``value`` associated with the selected ``PARAMETRIZED_TEXT`` is returned (but the user never sees it). The ``value`` *tags* the alternative. :: alt_review ::= '!' '=' value NL | '!' PARAMETRIZED_TEXT NL Each alternative may have it's own review associated with it. The ``'!' '=' value`` form uses the same review text as the previous alternative with that *tag*. Note that this can not refer forward to a following alternative. The second form specifies the review text for this alternative directly. ./pyke-1.1.1/doc/source/pyke_syntax/index.txt0000644000175000017500000001267411346504626020125 0ustar lambylamby.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Pyke Syntax page-description: The syntax of Pyke's three different kinds of source files. /description section-pages: , kfb_syntax, krb_syntax/index, kqb_syntax format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues =========== Pyke Syntax =========== Source Files ============ Pyke has three different kinds of source files for the three main types of `knowledge bases`_: #. *Knowledge Fact Base* (KFB) files for `fact bases`_. #. *Knowledge Rule Base* (KRB) files for `rule bases`_. #. *Knowledge Question Base* (KQB) files for `question bases`_. Each type of source file ends in a different file suffix: ``.kfb``, ``.krb`` or ``.kqb``. Place all of these source files into a directory structure. Then include this directory as an argument to the `knowledge_engine.engine`_ constructor. This will recursively search your directory for these three types of source files, compile them, and load them into the engine. How you organize these files into subdirectories is up to you -- the directory structure does not matter to Pyke. The ``.kfb`` and ``.kqb`` files are compiled into Python pickles_ with ``.fbc`` and ``.qbc`` suffixes. The ``.krb`` files are compiled into up to three ``.py`` source files. The names of these ``.py`` files are the same as the ``.krb`` file, but with different endings: - ``_fc`` (if there are any forward-chaining_ rules) - ``_bc`` (if there are any backward-chaining_ rules) and - ``_plans`` (if any of the backward-chaining rules have a plan_) These ``.py`` files are then automatically imported to define the rule base. This causes Python to compile them into ``.pyc`` or ``.pyo`` files. Subsequent runs of the `knowledge_engine.engine`_ constructor only recompile the Pyke source files that have changed since the last time they were compiled. The name of each knowledge base is the filename of the Pyke source file with the suffix removed. This must be a legal Python identifier. Syntax Legend ============== To describe this syntax, the following punctuation is used: '*any_chars*' Required punctuation or keyword: *any_chars*. *a* | *b* Alternation: *a* or *b*. [*a*] Optional *a*. {*a*} One or more *a*'s. **But** it is understood that if *a* ends in a comma, the last comma is optional. IDENTIFIER Any legal Python identifier. Example: *foobar* NUMBER Any legal Python integer or floating point literal. Examples: *123*, *3.14*. STRING Any legal Python string literal. Examples: *'Hi Mom!'*, *u"Hi Dad!\\n"*, *r'''don't gobble my \\'s!'''*, *ur"""leave \\'s alone!"""*. TEXT Only used in KQB files. This signifies any text (any characters) other than the delimiter characters containing the ``TEXT``. PARAMETRIZED_TEXT Only used in KQB files. This signifies any text (any characters) through the end of the line and all text on subsequent lines that are indented at least as much as the first ``PARAMETRIZED_TEXT`` character on the first line. All ``PARAMETRIZED_TEXT`` is treated as a `string.Template`_ and may include ``$IDENTIFIER`` or ``${IDENTIFIER}`` parameters. All other ``$`` characters must be doubled (``$$``). REGEXP_TEXT Only used in KQB files. This signifies any text (any characters) excluding an unescaped backslash (``\``) at the end. These are given to the Python's ``re`` module as regular expressions and must follow Python's `regular expression syntax`_. NL One or more newlines. INDENT The following text must be indented to a higher level (more) than the previous text. DEINDENT The following text must be indented one less level than the previous text. Lexical Structure ======================= The lexical structure is much like Python. Like Python, indenting is significant. It uses the same commenting, line continuation and literal formats for strings and numbers (but doesn't use complex numbers). It also uses the same rules for forming identifiers. The two notable exceptions to Python conventions are: #. Identifiers may be used as strings, without requiring quotes. - So ``foobar`` is the same as ``'foobar'``. #. Singleton tuples do not require a trailing comma. - So ``(1)`` is the same as ``(1,)``. ./pyke-1.1.1/doc/source/pyke_syntax/kfb_syntax.txt0000644000175000017500000000527511346504626021165 0ustar lambylamby.. $Id: kfb_syntax.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: KFB Syntax page-description: The syntax of *Knowledge Fact Base* (KFB) files, which is where you write your universal facts. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: kfb_syntax.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues =================================== KFB Syntax =================================== This uses the same lexical structure as `KRB files`_, except that the only keywords are: - None - True - False The name of the `fact base`_ is simply the filename with the ``.kfb`` suffix stripped. This must be a valid Python identifier. Syntax for KFB File =================== :: file ::= [NL] {fact NL} fact ::= IDENTIFIER '(' [{data,}] ')' data ::= 'None' | 'True' | 'False' | NUMBER | IDENTIFIER | STRING | '(' [{data,}] ')' Example ======= This is taken from the family_relations_ example:: # family.kfb son_of(bruce, thomas, norma) son_of(fred_a, thomas, norma) son_of(tim, thomas, norma) daughter_of(vicki, thomas, norma) daughter_of(jill, thomas, norma) daughter_of(nanette, arthur2, kathleen) son_of(arthur3, arthur2, kathleen) daughter_of(sue, arthur2, kathleen) son_of(ed, arthur2, kathleen) daughter_of(marilyn, arthur2, kathleen) son_of(david_b, arthur2, kathleen) daughter_of(m_helen, arthur2, kathleen) son_of(m_thomas, bruce, marilyn) son_of(david_a, bruce, marilyn) ./pyke-1.1.1/doc/source/about_pyke/0000755000175000017500000000000011425360453016023 5ustar lambylamby./pyke-1.1.1/doc/source/about_pyke/installing_pyke.txt0000644000175000017500000001654411346504626021776 0ustar lambylamby.. $Id: installing_pyke.txt 70f7f9ee163a 2010-03-11 mtnyogi $ .. .. Copyright © 2007-2009 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Installing Pyke page-description: System Requirements and installing Pyke. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: installing_pyke.txt 70f7f9ee163a 2010-03-11 mtnyogi $ /uservalues =================================== Installing Pyke =================================== Index to This Page ======================= * Licensing_ * `System Requirements`_ * `Other Required Packages`_ * Installation_ * `Run the Examples`_ * `Viewing the HTML Documentation`_ * `Repository Directory Structure`_ Licensing ================ This software is licensed under the MIT license:: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. System Requirements ==================== Pyke is 100% Python, so it should run on any platform supported by Python. So all you'll need is `Python`_ 2.5, 2.6 or 3.1. Other Required Packages ----------------------- No other packages are required to develop, run and distribute an application using Pyke. But there are package requirements to do the following additional things: .. table:: :class: table-offset +--------------------------------+------------------+------------------+ | If you want to | you also need | minimum version | +================================+==================+==================+ | run the web_framework example | HTMLTemplate_ | 1.5 | +--------------------------------+------------------+------------------+ | run the unit tests | `doctest-tools`_ | 1.0a3 | +--------------------------------+------------------+------------------+ | rebuild the html documentation | rest2web_ | 0.5 | + +------------------+------------------+ | | docutils_ | 0.4.1 | +--------------------------------+------------------+------------------+ If the docutils package is not part of your standard Python installation, there is probably a package for it in the package index for your Linux distribution. All of the other packages can be installed as the administrator using pip_ or easy_install_. For example:: # pip install HTMLTemplate Installation ============ The source code for the latest release can be found on the `Pyke project download page`_ as ``pyke-.zip`` (for Python2) and ``pyke3-.zip`` (for Python3). After unzipping these, go into the directory and run:: $ python setup.py build And then as administrator, run:: # python setup.py install The sources include a complete copy of the project directory, including the documentation, unit tests, and examples. If you want to clone the source code repository to contribute to the project development, or to use the latest developer version, read `Modifying Pyke`_. Run the Examples ================ There are several examples that are contained in the source directory. Each example is in it's own subdirectory under the ``examples`` subdirectory, and each has it's own README.txt file that explains how to run it. The web_framework example requires the HTMLTemplate_ package, version 1.5 or later. This can be installed as administrator with pip or easy_install:: # pip install HTMLTemplate See also Examples_. Viewing the HTML Documentation ============================== This HTML documentation may be viewed directly from your hard drive. The HTML files are in the ``doc/html`` directory. Start with ``doc/html/index.html``. Repository Directory Structure ============================== You'll see the following directories. * ``doc`` - the ``html`` directory has all of the HTML documentation ready to browse off of your hard drive. Start with doc/html/index.html. - the ``source`` directory has all of the sources that were used to generated the HTML documentation. See `Rebuilding the HTML Documentation`_. - the ``examples`` directory just has a copy of the examples used by the .txt files in the ``source`` directory so that the doctests will work on the ``source`` directory. You should be able to skip this unless you change an example in one of the ``source`` files. - ``cheatsheets`` are a collection of text files with notes on various tools used by Pyke, and processes used to maintain Pyke. * ``examples`` - There are several examples. Start with *family_relations*. Look at the ``README.txt`` file for each example to see how to run it. See also, Examples_. * ``experimental`` - This is a catch-all directory for various ideas that have been tried, but that have not been incorporated into Pyke. You can safely skip over this directory... * ``pyke`` - This is the top-level Python package directory for the Python sources. This needs to be installed into a directory on your ``PYTHONPATH``. The sources for the compilers are in the ``krb_compiler`` subdirectory, which is expected to be a subpackage of ``pyke``. * ``Test`` - This is where the unit test scripts are stored. These use Python's doctest_ package. Each test file has a .tst suffix. - See `Running Unit Tests`_. ./pyke-1.1.1/doc/source/about_pyke/what_is_pyke.txt0000644000175000017500000001170711346504626021264 0ustar lambylamby.. $Id: what_is_pyke.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: What is Pyke? page-description: An overview of Pyke's features. /description format: rest encoding: utf8 output-encoding: utf8 section-pages: , why_pyke, steps_to_using_pyke, installing_pyke, logic_programming/index, knowledge_bases/index, pyke_syntax/index, using_pyke, examples, PyCon2008-paper include: Yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: what_is_pyke.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ============================= What is Pyke? ============================= Pyke integrates a form of `Logic Programming`_ into Python by providing a knowledge engine that can: - Do both forward-chaining (data driven) and backward-chaining (goal directed) inferencing. - Pyke may be embedded into any Python program. - Automatically generate Python programs by assembling individual Python functions into complete call graphs. - This is done through a unique design where the individual Python functions are attached to backward-chaining rules. - Unlike other approaches to code reuse (e.g. Object-oriented programming, Zope adapters, generic functions), this allows the inference engine to ensure that all of the function's requirements are completely satisfied, by examining the entire call graph down to the leaves, before **any** of the functions are executed. - This is an optional feature. You don't need to use it if you just want the inferencing capability by itself. The Knowledge Engine Supports: ======================================== - Multiple *fact bases*, each with its own list of facts. - Both *forward-chaining* rules and *backward-chaining* rules. - Multiple *rule bases*, each with its own list of forward-chaining and/or backward-chaining rules. - Rule base inheritance -- *activating* the derived rule base includes the rules from the parent rule base. - The inference rules are compiled into Python functions, allowing Python code snippets to be used within the rules. This greatly enhances the expressiveness of the rules. Automatic Program Generation: ======================================== - Calls the generated Python programs *plans*. - Plans may be run multiple times without needing to rerun the inference rules. - Plans may be pickled and cached to disk to be used by other programs or in later runs of the same program. - Only one small Pyke module is required to run the plans. Potential Pyke Applications: ======================================== - Complicated decision making applications. - The back-end (code generation and optimization) of compilers. Pyke is used as the back-end of its own compiler that translates rules into Python code. - Automatic SQL statement generation. - Automatic HTML generation and automatic HTML template processing. - Automatic program builder to reuse a common set of functions for many different specific situations. This could also easily incorporate a new custom function into a much larger program, where the use of the custom function might influence the choice of other standard functions in other parts of the program. - The control module for a web framework tool. - A high-level planner to automatically distribute the modules of a large system over several computers in a distributed system to meet specific performance and capacity goals. This could be used to automatically scale the same system code from a small one program, one computer system to much larger distributed systems to meet a wide range of performance goals. - Diagnosis systems, including automated customer service systems. - Program or library customization for specific uses. - In addition to being able to build programs, Pyke can instantiate, configure and interconnect a network of objects to meet a specific need or situation. ./pyke-1.1.1/doc/source/about_pyke/steps_to_using_pyke.txt0000644000175000017500000001032411346504626022665 0ustar lambylamby.. $Id: steps_to_using_pyke.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Steps to Using Pyke page-description: A brief list of the steps involved in programming in Pyke (with lots of links). /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: steps_to_using_pyke.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ==================== Steps to Using Pyke ==================== #. You provide the following to Pyke's `knowledge engine`_: - A set of universally true statements_. - These statements are true for all time. - The text for all questions_ that you might want Pyke to ask your end user. - Multiple sets of rules_. - Your rules may include both `forward-chaining`_ and `backward-chaining`_ rules. #. Repeat for each specific use case: #. You provide a set of statements_ describing this specific use case to Pyke. #. You select which set of rules apply to this use case. #. Pyke automatically runs all of the selected forward-chaining rules that apply to the statements that you've given to it to deduce new statements. - Your forward-chaining rules may interactively ask your end user `questions`_, or get information by executing `commands`_ (programs) on the computer that it's running on to help in its decision making. #. You ask Pyke a question by having it prove_ a goal_ (which is just another statement). This goal may include `pattern variables`_ that allow you to ask "for what values is this statement true?". - Pyke runs the selected backward-chaining rules against the statements that it has in order to figure out the answer to your question. - Your backward-chaining rules may also ask your end user questions_ and run commands_. - You may have written Python code at the end of some of your backward-chaining rules. For each such rule, Pyke has compiled this Python code into a Python function called a plan_ which it has attached to the rule. - Once Pyke finds an answer to your question, it gathers all of the plan functions of the rules that it used to find your answer into a complete function call graph. The plan functions are linked together mirroring the way that the rules were linked together to find your answer. In this way, you can write high-level compilers that assemble together and configure a set of Python functions to solve specific problems. - Pyke returns the top Python function of this function call graph as a standard Python function along with the answer to your question. You may call this function as may times as you like. You may also pickle_ the function so that you can send it to another program or save it to disk. You only need one small Pyke module to load and run these pickles. #. You reset_ Pyke to clear out all of these case specific statements and prepare it for the next use case. ./pyke-1.1.1/doc/source/about_pyke/cooking_functions.txt0000644000175000017500000001364111346504626022316 0ustar lambylamby.. $Id: cooking_functions.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Cooking Functions page-description: Explanation of how Pyke "cooks" Python functions. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: cooking_functions.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues =========================== Cooking Python Functions =========================== "Cooking" a Python function means customizing it. And you customize it by cooking certain parameter values into it as constant values. Cooking a single parameter value ================================ First you define the function that you want to cook with an extra parameter at the start: >>> def foo(cooked, standard): ... print "foo called with cooked: %s, standard: %s" % \ ... (cooked, standard) Now you can call this function with two parameters: >>> foo('a', 'b') foo called with cooked: a, standard: b But your real intention is that it appear to be a function taking one parameter, with the first parameter cooked in. This is done with the ``partial`` class of the functools_ module in the standard Python library. >>> from functools import partial And then using ``partial`` to cook the first parameter: >>> cooked1 = partial(foo, 'cooked_value1') Now ``cooked_foo`` is a function that takes one parameter: >>> cooked1('value1') foo called with cooked: cooked_value1, standard: value1 >>> cooked1('value2') foo called with cooked: cooked_value1, standard: value2 And you can make other cooked functions from foo with other cooked values: >>> cooked2 = partial(foo, 'cooked_value2') >>> cooked2('value1') foo called with cooked: cooked_value2, standard: value1 >>> cooked2('value2') foo called with cooked: cooked_value2, standard: value2 And you can still use the first cooked function, so now you have two functions for the price of one! >>> cooked1('value3') foo called with cooked: cooked_value1, standard: value3 >>> cooked1('value4') foo called with cooked: cooked_value1, standard: value4 >>> cooked2('value5') foo called with cooked: cooked_value2, standard: value5 >>> cooked2('value6') foo called with cooked: cooked_value2, standard: value6 And you can keep going with this to make as many functions as you care to from your single starting function. Cooking a Function Call Graph ============================= This same technique can be used to cook a function call graph, by making the subordinate function a cooked parameter: >>> def bar(child_fun, a): ... print "bar called with:", a ... return child_fun(a) And now you can cook which function ``bar`` calls the same way you cook any other parameter: >>> bar_float = partial(bar, float) >>> bar_float('123') bar called with: 123 123.0 >>> bar_min = partial(bar, min) >>> bar_min((3,2,5)) bar called with: (3, 2, 5) 2 And, of course, you can use cooked functions as these subordinate functions too: >>> bar_cooked1 = partial(bar, cooked1) >>> bar_cooked1('abc') bar called with: abc foo called with cooked: cooked_value1, standard: abc Which means that you can create function call graphs to any depth: >>> bar_bar_min = partial(bar, bar_min) >>> bar_bar_min((3,2,5)) bar called with: (3, 2, 5) bar called with: (3, 2, 5) 2 Cooking Several Parameters ========================== In general, you may want to cook several values for each function. Some of these values may specify which subordinate functions to call, others may just fix certain constant values for the function. Pyke does this using a single extra parameter called ``context``, which is a read-only dictionary. It can then prepare this dictionary with as many values as it needs and then cook the whole dictionary into the function using ``partial``. Pyke translates each individual access to a cooked parameter into a dictionary lookup on ``context`` that looks up that parameter name:: context['parameter_name'] The Need for Pyke ================= Now that you understand how Pyke cooks Python functions, you should be able to understand how this technique can achieve the "order of magnitude" improvements to Adaptability/Customization, Performance and Code Reuse discussed on the `About Pyke`_ page. You should also now see the need for a tool like Pyke to assemble all of these functions to fit specific situations and use cases. .. note:: Pyke calls a customized function call graph a *plan*. Plans_ are explained later, after you've been introduced to `Logic Programming in Pyke`_. And, finally, you should start to get a sense for how "programming in the large" with Pyke dovetails with "programming in the small" with Python. ./pyke-1.1.1/doc/source/about_pyke/index.txt0000644000175000017500000001320611346504626017701 0ustar lambylamby.. $Id: index.txt f00035e4dab4 2009-11-02 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: About Pyke page-description: What pyke does for you, its features, steps to using pyke and installation. /description format: rest section-pages: , cooking_functions, what_is_pyke, steps_to_using_pyke, installing_pyke, modifying_pyke encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt f00035e4dab4 2009-11-02 mtnyogi $ /uservalues ============ About Pyke ============ ------------------------- What Does Pyke Do for Me? ------------------------- Pyke was primarily designed to allow you to "cook" your Python_ code. You write Python code, and then you write Pyke code to cook that Python code -- i.e. to assemble the Python functions that you've written and customize them for a specific situation or use-case. Pyke can also be used for difficult decision making applications where each part of the problem has multiple possible solutions and the selection of a solution to one part of the problem affects whether another part of the problem can be solved or not. Cooking Your Python Code ======================== Cooking your Python code is a form of meta-programming, or writing programs that manipulate other programs. I.e., it's a means of `programming in the large`_. Thus, Pyke provides a way to directly "program in the large", which dovetails with using Python to "program in the small". Pyke supplements but does not replace Python! Pyke helps programmers to achieve order of magnitude improvements in: - Adaptability/Customization - Using Pyke allows your Python code to be combined into thousands of different configurations. - Thus, your application or library takes on the characteristics of a Domain Specific Language to achieve an order of magnitude increase in adaptability without a corresponding increase in your program's "surface area" to your users. - Performance - Thinking of your application or library as a Domain Specific Language (DSL), you're using Pyke to "compile" rather than "interpret" your DSL to achieve an order of magnitude improvement in performance. - Code Reuse - Making your code an order of magnitude more adaptable and an order of magnitude faster allows it to be (re)used in a correspondingly broader range of situations. Examples of Cooking Python Code =============================== Database Access Library ----------------------- You're writing a library package to make it easier for Python programmers to access relational databases. You write Python code that deals with the mechanics of accessing relational databases, and then you write Pyke code to make a cooked version of this code for each database access with your user's application. You might also use Pyke to provide help installing and configuring the database and help creating the schema. By taking this approach, your library will be an order of magnitude faster than competing database access libraries because you've used Pyke to essentially compile custom code for each database access. The sqlgen_ example demonstrates this approach. HTML Templating Library ----------------------- Or you're writing an HTML templating package to make it easier for Python programmers to generate HTML. You write Python code that deals with the mechanics of HTML, and then you write Pyke code to make a cooked version of this code for each HTML template. By taking this approach, your library will be an order of magnitude faster than competing HTML templating libraries because you've used Pyke to essentially compile custom code for each HTML template. The web_framework_ example demonstrates this approach. It uses the sqlgen_ example to make a little web framework. The 2 HTML templates in this example were also done in `TurboGears 2`_ and then a siege_ benchmark test done on both: - TurboGears 2 ran 75.83 transactions/sec - The Pyke example ran 791.01 transactions/sec Linux Configuration Program --------------------------- Or you're writing a new Linux configuration program. You write the Python code to query and set the various system configuration options, and then you write Pyke code to ask the user what he wants and build a cooked version of your code to make the necessary changes. In this case, you're not looking for performance. You use Pyke to handle the complicated decision making and use its plan_ facility to postpone making any configuration changes until your program is sure that it's "dotted all of the i's and crossed all the t's". ./pyke-1.1.1/doc/source/about_pyke/modifying_pyke.txt0000644000175000017500000002674011346504626021616 0ustar lambylamby.. $Id: modifying_pyke.txt 70f7f9ee163a 2010-03-11 mtnyogi $ .. .. Copyright © 2009 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Modifying Pyke page-description: Which source code repository to use. And the other tools that you'll need run the units tests, and rebuild the html documentation. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: modifying_pyke.txt 70f7f9ee163a 2010-03-11 mtnyogi $ /uservalues =================================== Modifying Pyke =================================== Index to This Page ======================= * `Mercurial Repositories`_ * `Mercurial Keyword Extension`_ * `Which Repository Do I Use?`_ * `Compiling PLY Tables Files`_ * `Compiling the Compiler.krb File`_ * `Running Unit Tests`_ * `Rebuilding the HTML Documentation`_ Mercurial Repositories ====================== With Mercurial_, you clone the entire repository locally on your computer. Then you can make changes and commit those changes to your local repository. If you think those changes might be interesting to everybody, make your local repository (or a clone of it) publicly available (either on your own server, or on one of the `Mercurial Hosting Sites`_) and send me an email. I will pull your changes, examine them, and push them to the master repository on sourceforge. Mercurial Keyword Extension --------------------------- The Pyke sources use the Mercurial `Keyword Extension`_ as a holdover from when the repository used Subversion rather than Mercurial. The ``hgrc_keywords`` file has been provided to enable and configure this extension for Pyke use. You can append this file to either your personal .hgrc configuration file (which would then apply to all of your Mercurial projects) or the project .hg/hgrc file (see `hgrc`_ in the Mercurial wiki). If you use a ``post-clone`` `Mercurial hook`_, or append ``hgrc_keywords`` manually after cloning, the keywords won't be expanded properly when the project is first cloned. But they will be expanded properly if the clone is done with the -U option and then an ``hg update`` done in the newly cloned repository (after the changes to .hg/hgrc have been made). The keyword expansions are only used by the tools that generate the html documentation (see `Rebuilding the HTML Documentation`_, below). Which Repository Do I Use? -------------------------- Normally, you will clone one of the following four repositories locally to make a master copy of what's on sourceforge. Then you would clone your master copy (which is very fast) to make separate clones for each development task that you are working on for Pyke. So it is best to keep all of these clones together in a common directory. There are four repositories on sourceforge that you can start with: release_1 Use this for bug fixes, code and documentation cleanup, and anything else that would go into a point release for release 1. I merge the changes made here into all of the other repositories. So this code goes into both the Python2.x and Python3.x versions of Pyke. pyke Use this for major new features that would result in a major new release (e.g., release 1.2). I merge the changes made in release_1 into the pyke repository (but maybe not the other way around). And I merge the changes made in the pyke repository into the pre_2to3 repository. So the code here goes into both the Python2.x and Python3.x future versions of Pyke. pre_2to3_r1 Use this for bug fixes, code and documentation cleanup, and anything else that would go into a point release for release 1, but only apply to the Python3.x version of Pyke. I merge the changes made in release_1 into the pre_2to3_r1 repository (but not the other way around). And I merge the changes made in the pre_2to3_r1 repository into the pre_2to3 repository. So changes here only go into the next point release of the Python3.x version of Pyke. .. warning:: This code is maintained in a state just prior to running Python's 2to3_ tool on it. So you can't just run the code here directly. The ``run_2to3`` script runs 2to3 on the current copy of the sources. Do **not** run this in a repository clone that you still want to use to do commits! Instead, commit all of your changes, then clone the repository and do ``run_2to3`` in the clone. If anything doesn't work, go back to the first repository to fix it, delete the clone, and repeat the whole process. This was done to minimize merge conflicts caused by the 2to3 changes. The ``run_pre_test`` script will: * clone the current repository * then in the clone do: * ``run_2to3`` * ``testpyke`` -3.1 * python setup.py -q sdist --formats zip * insert '3' after 'pyke' in the name of the source distribution zip file. ``Run_pre_test`` assumes that you either have the keywording options set in your personal .hgrc file, or have clone hooks in place to copy these into the .hg/hgrc file of all clones within your pyke work area. See `Mercurial Keyword Extension`_, above. pre_2to3 Normally I merge changes from the pyke repository and the pre_2to3_r1 repository into pre_2to3 so that nothing needs to be done in this repository. Most major new features would be developed in the ``pyke`` repository and merged into pre_2to3. Making changes to pre_2to3 directly would only be done when those changes are for major new features that only apply to the Python3.x version of Pyke. So, for example, if you wanted to work on the ``release_1`` repository, you would:: $ mkdir pyke_repos $ cd pyke_repos $ hg clone -U http://pyke.hg.sourceforge.net:8000/hgroot/pyke/release_1 master $ hg clone master task_1 $ cd task_1 .. note:: This assumes that you've added the `hgrc_keywords`_ file to your ~/.hgrc file. See `Mercurial Keyword Extension`_, above. Compiling PLY Tables Files ========================== Pyke uses PLY_ (Python Lex and Yacc) as it's parser generator. PLY compiles the Pyke grammars into a set of three tables files: - kfbparser_tables.py (from kfbparser.py) - krbparser_tables.py (from krbparser.py) - scanner_tables.py (from scanner.py) A copy of PLY is included in the source directory (pyke/krb_compiler/ply) so that there there can be no version mismatch between the version of PLY used to compile these tables files and the version of PLY installed on your machine. To regenerate these tables files, at the top-level source directory:: $ python >>> from pyke.krb_compiler import kfbparser, krbparser, scanner >>> scanner.init(scanner, 0, True) >>> krbparser.init(krbparser, True) >>> kfbparser.init(kfbparser, True) or just run the "testall.py" program from the doctest-tools package:: $ cd pyke/krb_compiler $ testall.py Compiling the Compiler.krb File =============================== Pyke uses itself to compile your `rule base`_ sources (`.krb`_ files) into Python source (``.py``) files. The knowledge base file that Pyke uses for this is pyke/krb_compiler/compiler.krb. This gets compiled into compiler_bc.py, which is stored in the source code repository. .. this code is hidden and will create the pyke/krb_compiler/compiled_krb directory, if needed, for the code section following: >>> import os, os.path >>> os.chdir('../../..') >>> root='pyke/krb_compiler' >>> dir=root + '/compiled_krb' >>> os.path.isdir(root) True >>> if not os.path.isdir(dir): os.mkdir(dir) To recompile the compiler_bc.py file, from the top-level source directory:: $ mkdir pyke/krb_compiler/compiled_krb $ python >>> from pyke import krb_compiler >>> krb_compiler.compile_krb('compiler', 'pyke.krb_compiler.compiled_krb', ... 'pyke/krb_compiler/compiled_krb', ... 'pyke/krb_compiler/compiler.krb') ['compiler_bc.py'] $ mv pyke/krb_compiler/compiled_krb/compiler_bc.py pyke/krb_compiler .. this code is also hidden and deletes the pyke/krb_compiler/compiled_krb/compiler_bc.py file and pyke/krb_compiler/compiled_krb directory created above. >>> os.path.isdir(root) True >>> os.remove(dir + '/compiler_bc.py') >>> os.rmdir(dir) Running Unit Tests ================== The `doctest-tools`_ package is required to run the unit tests (see `Other Required Packages`_ for more details). The ``testall.py`` and ``testdoc.py`` scripts from ``doctest-tools`` can be run anywhere. In addition, the top-level directory contains a ``testpyke`` script that will delete all of the compiled_krb directories, then run ``testall.py`` twice. The first run must recompile all of the `knowledge base`_ sources (`.krb`_, `.kfb`_ and `.kqb`_ files) into the compiled_krb directories in order to run the tests. The second run reuses the files compiled in the first run. This makes sure that all of the tests run properly whether they have to compile the knowledge base sources or not. Rebuilding the HTML Documentation ================================= The ``doc/html`` directory contains all of the documents that you are reading now. These are ready to browse directly from your hard drive if you'd like. The documentation is generated using the rest2web_ package, which uses docutils_ (see `Other Required Packages`_ for more details). The sources for the documentation are in ``doc/source``. Each .txt file there is converted into an .html file in the doc/html directory by running:: $ cd doc/source $ bin/gen_html This takes about 9 seconds. It: #. Temporarily appends hyperlink references onto all of the \*.txt files. #. Runs ``r2w`` to regenerate the files in ``doc/html`` - except for those in ``doc/html/stylesheets`` and ``doc/html/images``. #. Strips all of the hyperlink references from the \*.txt files. #. Creates a new sitemap.xml file with all of the dates that the files were last modified. .. note:: This process uses the date information expanded by the Mercurial `Keyword Extension`_. See `Mercurial Keyword Extension`_, above. I've gone ahead and placed the generated html files in the source repository so that you can browse the documentation locally without having to run ``bin/gen_html``. So you only need these procedures if you change the documentation (i.e., change the .txt files in doc/source). To test all of the code examples in the documents, use the ``testall.py`` command from the `doctest-tools`_ package:: $ cd doc/source $ testall.py ./pyke-1.1.1/doc/source/using_pyke/0000755000175000017500000000000011425360453016036 5ustar lambylamby./pyke-1.1.1/doc/source/using_pyke/creating_engine.txt0000644000175000017500000001657311354264506021737 0ustar lambylamby.. $Id: creating_engine.txt efc7674a8d3a 2010-03-29 mtnyogi $ .. .. Copyright © 2010 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Creating an Engine page-description: How to create a Pyke *inference engine* object. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: creating_engine.txt efc7674a8d3a 2010-03-29 mtnyogi $ /uservalues =================================== Creating an Inference Engine Object =================================== The ``engine`` object is your gateway into Pyke. Each engine object manages multiple `knowledge bases`_ related to accomplishing some task. You may create multiple Pyke engines, each with it's own knowledge bases to accomplish different disconnected tasks. When you create a Pyke engine object, Pyke scans for Pyke `.kfb`_, `.krb`_ and `.kqb`_ source files and compiles these into .fbc pickle files, Python .py source files and .qbc pickle files, respectively. Each time a Pyke engine object is created it checks the file modification times of the Pyke source files to see whether they need to be recompiled. If you change a Pyke source file, you may create a new Pyke engine to compile the changes and run with the new knowledge bases without having to restart your application. Pyke also lets you zip these compiled files into Python eggs and can load the files from the egg. By including the compiled files in your application's distribution, you don't need to include your Pyke source files if you don't want to. Once you have an ``engine`` object; generally, all of the Pyke functions that you need are provided directly by this object: .. this code is hidden and will set __file__ to the doc/examples directory. >>> import os >>> __file__ = \ ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), ... 'examples') knowledge_engine.engine(\*paths, \*\*kws) Pyke recursively searches for Pyke source files (`.kfb files`_, `.krb files`_, and `.kqb files`_) starting at each source directory indicated in *paths* and places all of the compiled files in the associated *target packages*. This causes all of the `knowledge bases`_ to be loaded and made ready to activate_. Pyke source files may be spread out over multiple directories and may be compiled into one or more target packages. Multiple target packages would be used when more than one application wants to share a set of `knowledge bases`_, perhaps adding some of its own knowledge that it compiles into its own target package. Each ``path`` argument specifies a Pyke source directory and an optional target package. The source directories do not have to be Python package directories, but the target packages do. The target package defaults to ``.compiled_krb``. The leading dot (.) indicates that the compiled_krb directory will be subordinate to the lowest Python package directory on the path to the Pyke source directory. This uses Python's `relative import`_ notation, so multiple dots go up to higher directories (one per dot). If the target package does not start with a dot, it is taken to be an absolute package path and will be located using Python's sys.path like a normal Python ``import``. The Pyke source directory may be specified as a path (a string) or by passing a Python module. If a module is passed, its __file__ attribute is used. If the path points to a file, rather than a directory, the final filename is discarded. In the simple case, when the Pyke source files are in the same directory as the module creating the ``engine`` object, you can just pass ``__file__`` as the sole argument. >>> from pyke import knowledge_engine >>> my_engine = knowledge_engine.engine(__file__) Passing a package instead, this example could also be written: >>> import doc.examples >>> my_engine = knowledge_engine.engine(doc.examples) or, you can pass a module within the desired package: >>> from doc.examples import some_module >>> my_engine = knowledge_engine.engine(some_module) In the all three cases, the final filename is stripped from the value of the module's __file__ attribute to get the directory that the package is in. This directory will then be recursively searched for Pyke source files. If you change some of your Pyke source files, you can create a new engine object to compile and reload the generated Python modules without restarting your program. But note that you'll need to rerun the ``add_universal_fact`` calls that you made (a reason to use `.kfb files`_ instead). All of the compiled Python .py source files and .fbc/.qbc pickle files generated from each source directory are placed, by default, in a ``compiled_krb`` target package. You may specify a different target package for any source directory by passing that source directory along with the target package name as a 2-tuple. Thus, specifying the default target package explicitly would look like: >>> my_engine = knowledge_engine.engine((__file__, '.compiled_krb')) You may specify the same target package for multiple source directories. The last component of the target package will be created automatically if it does not already exist. .. note:: You will probably want to add ``compiled_krb`` (or whatever you've chosen to call it) to your source code repository's list of files to ignore. If you want to distribute your application *without* the knowledge bases, you can use the 2-tuple notation with ``None`` as the source directory. In this case, all of the Pyke source files must already be compiled, and Pyke will simply load these files. Also, the target package must be specified in absolute form (with no leading dots). Finally, there are four optional keyword arguments that you may also pass to the ``engine`` constructor. These are all booleans that default to ``True``: - ``load_fb`` -- load fact bases - ``load_fc`` -- load forward-chaining rules - ``load_bc`` -- load backward-chaining rules and - ``load_qb`` -- load question bases These parameters must be passed as keyword parameters and let you selectively load the various kinds of compiled files. ./pyke-1.1.1/doc/source/using_pyke/adding_facts.txt0000644000175000017500000000773611346504626021226 0ustar lambylamby.. $Id: adding_facts.txt 56035209fc8e 2010-03-08 mtnyogi $ .. .. Copyright © 2010 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Asserting Facts page-description: How to dynamically assert new *facts* from your Python program. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: adding_facts.txt 56035209fc8e 2010-03-08 mtnyogi $ /uservalues =================================== Asserting New Facts =================================== .. this code is hidden and will set __file__ to the doc/examples directory. >>> import os >>> __file__ = \ ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), ... 'examples') >>> from pyke import knowledge_engine >>> my_engine = knowledge_engine.engine(__file__) *some_engine*.add_universal_fact(kb_name, fact_name, arguments) The ``add_universal_fact`` function is called once per fact_. These facts_ are never deleted and apply to all *cases*. Alternatively, you can place universal facts in a `.kfb file`_ so that they are loaded automatically. >>> my_engine.add_universal_fact('family', 'son_of', ('bruce', 'thomas')) Multiple facts with the same name are allowed. >>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce')) But duplicate facts (with the same arguments) are silently ignored. >>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce')) >>> my_engine.get_kb('family').dump_universal_facts() son_of('bruce', 'thomas') son_of('david', 'bruce') These facts are accessed as ``kb_name.fact_name(arguments)`` within the `.krb files`_. *some_engine*.assert_(kb_name, fact_name, arguments) Call ``assert_`` for each starting fact_ for this case. Like universal facts, you may have multiple facts with the same name so long as they have different arguments. >>> my_engine.assert_('family', 'son_of', ('michael', 'bruce')) >>> my_engine.assert_('family', 'son_of', ('fred', 'thomas')) >>> my_engine.assert_('family', 'son_of', ('fred', 'thomas')) Duplicates with universal facts are also ignored. >>> my_engine.assert_('family', 'son_of', ('bruce', 'thomas')) >>> my_engine.get_kb('family').dump_specific_facts() son_of('michael', 'bruce') son_of('fred', 'thomas') >>> my_engine.get_kb('family').dump_universal_facts() son_of('bruce', 'thomas') son_of('david', 'bruce') There is no difference within the `.krb files`_ of how universal facts verses case specific facts are used. The only difference between the two types of facts is that the case specific facts are deleted when a ``reset`` is done. >>> my_engine.reset() >>> my_engine.get_kb('family').dump_specific_facts() >>> my_engine.get_kb('family').dump_universal_facts() son_of('bruce', 'thomas') son_of('david', 'bruce') ./pyke-1.1.1/doc/source/using_pyke/index.txt0000644000175000017500000001346711346504626017725 0ustar lambylamby.. $Id: index.txt b1a8e87f114a 2010-03-10 mtnyogi $ .. .. Copyright © 2007-2010 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Using Pyke page-description: How your Python program calls Pyke. /description section-pages: , creating_engine, adding_facts, proving_goals, other_functions format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt b1a8e87f114a 2010-03-10 mtnyogi $ /uservalues ========== Using Pyke ========== This describes how to call Pyke from your Python program. Getting Started =============== .. this code is hidden and will set __file__ to the doc/examples directory. >>> import os >>> __file__ = \ ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), ... 'examples') The simplest use of Pyke involves three steps: `Create an engine`_ object. >>> from pyke import knowledge_engine >>> my_engine = knowledge_engine.engine(__file__) This step compiles the Pyke source files, if out of date, and loads the `knowledge bases`_. There are three kinds of Pyke source files: #. `.kfb files`_ define `fact bases`_, which are compiled into .fbc pickle files. #. `.krb files`_ define `rule bases`_, which are compiled into 1 to 3 .py Python source files. #. `.kqb files`_ define `question bases`_, which are compiled into .qbc pickle files. See `Creating an Inference Engine`_ to control where the compiled files are written, load knowledge bases from multiple directories, distribute your application without your knowledge base files, or distribute using egg files. Activate `rule bases`_. >>> my_engine.activate('bc_related') You may activate one rule base for each `rule base category`_. Simply pass multiple arguments to ``activate``. .. note:: Even if you only have one rule base, you must still activate it. This is when the `forward-chaining rules`_ are run. Prove_ goal_. >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $son, ())') ({'son': 'david'}, None) The goal might be met by simply matching an already known fact_, or through the use of `backward-chaining rules`_. Then if you want to prove another goal, you can just repeat the last step. In this case, the `forward-chaining rules`_ are only run once and all goals operate against the same set of known facts. >>> my_engine.prove_1_goal('bc_related.father_son(thomas, $grandson, (grand))') ({'grandson': 'david'}, None) See `Proving Goals`_ to pass different arguments into goals, compile the goal statements once in advance, and to retrieve multiple answers for a goal. Dynamically Asserting Facts =========================== To dynamically assert_ facts_ within your Python program, a new step is added: Create the engine object: >>> my_engine = knowledge_engine.engine(__file__) Assert_ facts_. >>> my_engine.assert_('family2', 'son_of', ('spike_the_dog', 'david')) These facts must be asserted prior to activating the rule bases so that they are available to the `forward-chaining rules`_. This example shows asserting case specific facts that are deleted before running the next case (as shown in the next section, below). But you can also assert universal facts that apply to all cases. See `Asserting New Facts`_ for more information. After asserting your facts, activate your rule bases and prove your goal as before: >>> my_engine.activate('bc_related') >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))') ({'grandson': 'spike_the_dog'}, None) Using Different Facts for Different Cases ========================================= But if you want to prove goals against different sets of facts or using different rule bases, you need to reset_ the Pyke engine: Only need this once: >>> my_engine = knowledge_engine.engine(__file__) First case, as before: >>> my_engine.assert_('family2', 'son_of', ('spike_the_dog', 'david')) >>> my_engine.activate('bc_related') >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))') ({'grandson': 'spike_the_dog'}, None) Reset the Pyke engine. >>> my_engine.reset() This erases all of the case specific facts that you asserted in step 2, as well as all of the facts asserted by the `forward-chaining rules`_. It also deactivates all of the `rule bases`_, so you'll need to call activate again after asserting your facts. Second case: >>> my_engine.assert_('family2', 'son_of', ('felix_the_cat', 'david')) >>> my_engine.activate('bc_related') >>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))') ({'grandson': 'felix_the_cat'}, None) ./pyke-1.1.1/doc/source/using_pyke/proving_goals.txt0000644000175000017500000001203311346504626021453 0ustar lambylamby.. $Id: proving_goals.txt 2bd9a1746af1 2010-03-09 mtnyogi $ .. .. Copyright © 2010 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Proving Goals page-description: Using Pyke's API to prove goals from your Python program. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: proving_goals.txt 2bd9a1746af1 2010-03-09 mtnyogi $ /uservalues =================================== Proving Goals =================================== .. this code is hidden and will set __file__ to the doc/examples directory. >>> import os >>> __file__ = \ ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), ... 'examples') >>> from pyke import knowledge_engine >>> my_engine = knowledge_engine.engine(__file__) >>> my_engine.add_universal_fact('family', 'son_of', ('bruce', 'thomas')) >>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce')) >>> my_engine.activate('bc_related0') Though Pyke has the capability to return multiple answers to a single goal, often you just want the first answer: *some_engine*.prove_1_goal(goal, \*\*args) ``goal`` is a Pyke goal (as a string). This may include `pattern variables`_ (which start with a '$'). >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)') ({'depth': ('grand',)}, None) Returns the first proof found as a 2-tuple: a dict of bindings for the pattern variables, and a plan_. The plan is ``None`` if no plan was generated; otherwise, it is a Python function as described here__. .. __: other_functions.html#running-and-pickling-plans Args must be specified as keyword arguments and are set as the value of the corresponding pattern variable. >>> vars, plan = \ ... my_engine.prove_1_goal('bc_related0.father_son($father, $son, $depth)', ... father='thomas', ... son='david') >>> sorted(vars.items(), key=lambda item: item[0]) [('depth', ('grand',)), ('father', 'thomas'), ('son', 'david')] Prove_1_goal raises ``pyke.knowledge_engine.CanNotProve`` if no proof is found: >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, bogus, $depth)') Traceback (most recent call last): ... CanNotProve: Can not prove bc_related0.father_son(thomas, bogus, $depth) *some_engine*.prove_goal(goal, \*\*args) This returns a context manager for a generator yielding 2-tuples, as above. Unlike ``prove_1_goal`` it does not raise an exception if no proof is found: >>> from __future__ import with_statement >>> with my_engine.prove_goal( ... 'bc_related0.father_son(thomas, $son, $depth)') as gen: ... for vars, plan in gen: ... print vars['son'], vars['depth'] bruce () david ('grand',) Like ``prove_1_goal``, above, `pattern variables`_ in the goal_ may be specified with keyword arguments: >>> with my_engine.prove_goal( ... 'bc_related0.father_son($father, $son, $depth)', ... father='thomas') as gen: ... for vars, plan in gen: ... print vars['son'], vars['depth'] bruce () david ('grand',) Compiling Goals at Program Startup ================================== Similar to Python's regular expression library, ``re``, you may compile your goal statements once at program startup: >>> from pyke import goal >>> my_goal = goal.compile('bc_related0.father_son($father, $son, $depth)') Then use ``my_goal.prove_1`` and ``my_goal.prove`` as many times as you'd like: >>> vars, plan = my_goal.prove_1(my_engine, father='thomas', son='david') >>> sorted(vars.items(), key=lambda item: item[0]) [('depth', ('grand',)), ('father', 'thomas'), ('son', 'david')] >>> with my_goal.prove(my_engine, father='thomas') as gen: ... for vars, plan in gen: ... print vars['son'], vars['depth'] bruce () david ('grand',) ./pyke-1.1.1/doc/source/using_pyke/other_functions.txt0000644000175000017500000001606011346504626022017 0ustar lambylamby.. $Id: other_functions.txt 7b73a1960faa 2010-03-12 mtnyogi $ .. .. Copyright © 2007-2010 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Other functions page-description: Other miscellaneous functions available that you might be interested in, such as tracing rules and smart error tracebacks that show lines from your .krb files. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: other_functions.txt 7b73a1960faa 2010-03-12 mtnyogi $ /uservalues =============== Other Functions =============== Running and Pickling Plans ========================== .. this code is hidden and will set __file__ to the doc/examples directory. >>> import os >>> __file__ = \ ... os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), ... 'examples') >>> from pyke import knowledge_engine >>> my_engine = knowledge_engine.engine(__file__) >>> my_engine.add_universal_fact('family', 'son_of', ('bruce', 'thomas')) >>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce')) >>> my_engine.activate('bc_related0') Once you've obtained a plan_ from `prove_goal`_ or `prove_1_goal`_, you just call it like a normal Python function. The arguments required are simply those specified, if any, in the `taking clause`_ of the rule__ proving the top-level goal. You may call the plan function any number of times. You may even pickle the plan for later use. But the plans are constructed out of `functools.partial`_ functions, that need to be registered with copy_reg_ if you are running Python 2.x: >>> import copy_reg >>> import functools >>> copy_reg.pickle(functools.partial, ... lambda p: (functools.partial, (p.func,) + p.args)) No special code is required to unpickle a plan. Just unpickle and call it. (Unpickling the plan only imports one small Pyke module to be able to run the plan). .. __: ../pyke_syntax/krb_syntax/bc_rule.html Tracing Rules ============= Individual rules may be traced to aid in debugging. The ``trace`` function takes two arguments: the rule base name, and the name of the rule to trace: >>> my_engine.trace('bc_related0', 'grand_father_son') >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)') bc_related0.grand_father_son('thomas', 'david', '$depth') bc_related0.grand_father_son succeeded with ('thomas', 'david', ('grand',)) ({'depth': ('grand',)}, None) This can be done either before or after rule base activation and will remain in effect until you call ``untrace``: >>> my_engine.untrace('bc_related0', 'grand_father_son') >>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)') ({'depth': ('grand',)}, None) Krb_traceback ============= A handy traceback module is provided to convert Python functions, lines and line numbers to the `.krb file`_ rule names, lines and line numbers in a Python traceback. This makes it much easier to read the tracebacks that occur during proofs. The ``krb_traceback`` module has exactly the same functions as the standard Python traceback_ module, but they convert the generated Python function information into .krb file information. They also delete the intervening Python functions between subgoal proofs. >>> import sys >>> from pyke import knowledge_engine >>> from pyke import krb_traceback >>> >>> my_engine = knowledge_engine.engine(__file__) >>> my_engine.activate('error_test') >>> try: # doctest: +ELLIPSIS ... my_engine.prove_1_goal('error_test.goal()') ... except: ... krb_traceback.print_exc(None, sys.stdout) # sys.stdout needed for doctest Traceback (most recent call last): File "", line 2, in my_engine.prove_1_goal('error_test.goal()') File "...knowledge_engine.py", line 366, in prove_1_goal return goal.compile(goal_str).prove_1(self, **args) File "...goal.py", line 47, in prove_1 return iter(it).next() File "...rule_base.py", line 50, in next return self.iterator.next() File "...knowledge_engine.py", line 41, in from_iterable for x in iterable: yield x File "...knowledge_engine.py", line 41, in from_iterable for x in iterable: yield x File "...error_test.krb", line 26, in rule1 goal2() File "...error_test.krb", line 31, in rule2 goal3() File "...error_test.krb", line 36, in rule3 goal4() File "...error_test.krb", line 41, in rule4 check $bar > 0 File "...contexts.py", line 231, in lookup_data raise KeyError("$%s not bound" % var_name) KeyError: '$bar not bound' Miscellaneous ============= There are a few more functions that may be useful in special situations. *some_engine*.add_case_specific_fact(kb_name, fact_name, args) This is an alternate to the ``assert_`` function. *some_engine*.get_kb(kb_name) Finds and returns the `knowledge base`_ by the name ``kb_name``. Raises ``KeyError`` if not found. Note that for `rule bases`_, this returns the active `rule base`_ where ``kb_name`` is the `rule base category`_ name. Thus, not all `rule bases`_ are accessible through this call. *some_engine*.get_rb(rb_name) Finds and returns the `rule base`_ by the name ``rb_name``. Raises ``KeyError`` if not found. This works for any `rule base`_, whether it is active_ or not. *some_engine*.print_stats([f = sys.stdout]) Prints a brief set of statistics for each knowledge base to file ``f``. These are reset by the ``reset`` function. This will show how many facts were asserted, and counts of how many forward-chaining rules were fired and rerun, as well as counts of how many backward-chaining goals were tried, and how many backward-chaining rules matched, succeeded and failed. Note that one backward-chaining rule may succeed many times through backtracking. ./pyke-1.1.1/doc/source/knowledge_bases/0000755000175000017500000000000011425360453017015 5ustar lambylamby./pyke-1.1.1/doc/source/knowledge_bases/question_bases.txt0000644000175000017500000001145311346504626022612 0ustar lambylamby.. $Id: question_bases.txt 4670da845e46 2010-03-05 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Question Bases page-description: Explanation of question bases and .kqb files. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: question_bases.txt 4670da845e46 2010-03-05 mtnyogi $ /uservalues ============================================= Question Bases ============================================= Question bases contain questions for end users. These questions can be of various types (e.g., yes/no, multiple_choice). The questions may be parametrized, with the parameter values substituted into the question text when the question is asked. In this case, different parameter values are treated as different question statements_. The answers to all questions are automatically remembered so that if multiple rules ask the same question, the end user only sees it once. These answers are erased when an `engine.reset`_ is done. Finally, questions may have *reviews* attached to them to automatically display different canned messages (with parameter substitution) depending on the end user's answer. KQB files ========== Each question base is defined by a `.kqb file`_. The name of each question base is the filename of the .kqb file (with the ``.kqb`` suffix removed). This must be a legal Python identifier. These .kqb files are compiled and loaded automatically when you create your `knowledge_engine.engine`_ object. The .kqb file contains all of the information about the question needed to ask the question, validate the answer, and output the appropriate *review* text. The .kqb file also specifies which parameter contains the answer. All parameters, except this one, must be bound to values before testing the question. Example ======= In writing a program to diagnose car problems, you might have a ``user_question`` question base with questions like:: engine_starts($ans) Can you start the engine? --- $ans = yn mileage($ans) How many miles are on the car? --- $ans = integer(1-999999) 200000- ! Wow, that's a lot of miles! noise_from($location, $ans) Do you hear a noise from $location? --- $ans = yn These would look like the following statements_ within rules_:: user_question.engine_starts(True) user_question.mileage($mileage) user_question.noise_from('under the hood', False) Presenting Questions to Your End Users ====================================== Pyke provides two modules to actually present a question to your end user: - ask_tty This presents the question on stdout and reads stdin for the answer. - ask_wx This presents the question in a dialog box for use with wxpython_. You may write your own module to present questions to end users. Look at the modules provided by Pyke for guidance. To ask a question, Pyke looks for an ``ask_module`` attribute on: #. the question_base object, then #. the `knowledge_engine.engine`_ object If the `knowledge_engine.engine`_ object does not have an ``ask_module`` attribute, ask_tty is imported (by default) and stored there. .. This code is hidden. It will add '' to sys.path, change to the doc.examples directory and store the directory path in __file__ for the code section following: >>> import sys >>> if '' not in sys.path: sys.path.insert(0, '') >>> import os >>> os.chdir("../../examples") >>> __file__ = os.getcwd() Here's an example of setting this attribute:: >>> from pyke import knowledge_engine >>> from pyke import ask_wx >>> engine = knowledge_engine.engine(__file__) >>> engine.ask_module = ask_wx ./pyke-1.1.1/doc/source/knowledge_bases/links0000644000175000017500000000003111346504626020056 0ustar lambylamby.. _goal: `goal syntax`_ ./pyke-1.1.1/doc/source/knowledge_bases/special.txt0000644000175000017500000002075111346504626021207 0ustar lambylamby.. $Id: special.txt 4670da845e46 2010-03-05 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Special page-description: Explanation of the ``special`` knowledge base. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: special.txt 4670da845e46 2010-03-05 mtnyogi $ /uservalues ======= Special ======= There is only one instance of this 'special' knowledge base, called ``special``. The ``special`` knowledge base is a collection of miscellaneous helper *knowledge entities* that determine whether a statement is true or not in various interesting ways. Thus, each entity in this `knowledge base`_ is a Python function that does something "special" when run. The ``special`` functions are: - claim_goal_ - check_command_ - command_ - general_command_ Claim_goal ========== The ``claim_goal`` function has no arguments:: special.claim_goal() This acts like the Prolog_ `cut operator`_. In general there are multiple rules_ that might be used to try to prove_ any goal_. They are each tried in the order that they appear in the `.krb file`_. If one rule fails, the next rule is tried. The goal itself doesn't fail until all of the rules for it have failed. Example ------- Suppose I want to translate a number, N, into the phrase "N dogs". I could use the following rules:: one_dog use n_dogs(1, '1 dog') n_dogs use n_dogs($n, $phrase) when $phrase = "%d dogs" % $n The problem here is that both rules might be used when ``n`` is 1, but the second rule isn't appropriate in this case. ``Special.claim_goal()`` may be used to fix this, as follows:: one_dog use n_dogs(1, '1 dog') when special.claim_goal() n_dogs use n_dogs($n, $phrase) when $phrase = "%d dogs" % $n The ``special.claim_goal()`` prevents the second rule from being used when ``n`` is 1. Explanation ----------- When a rule_ executes ``special.claim_goal()`` in its ``when`` clause, none of the rest of the rules will be tried for that goal_. Thus, when ``special.claim_goal()`` is backtracked_ over, the goal fails immediately without trying any more rules for it. This ends up acting like an "else". You place it in the ``when`` clause after the premises_ that show that this rule **must** be the correct one to use. Then the subsequent rules will only be tried if these premises fail, such that ``special.claim_goal()`` is never executed. This means that you don't need to add extra premises in each subsequent rule to make sure that these premises have **not** occurred. Without the ``special.claim_goal()`` in the prior example, you would have to write:: one_dog use n_dogs(1, '1 dog') n_dogs use n_dogs($n, $phrase) when check $n != 1 $phrase = "%d dogs" % $n This is a simple example where it is easy to add the check in the second rule. But in general, it can be difficult to check for prior conditions, especially when many rules are involved that each has its own condition. Running Commands ================ The remaining three functions deal with running programs (commands) on the host computer that is running your Pyke program. Their difference is in what kind of output they provide from the command. These functions all use the `subprocess.Popen`_ function from the standard Python library. Thus, each of these functions accept these three parameters that are passed on to subprocess.Popen: - The ``$command`` parameter (required). - This is a tuple indicating the program to run along with its command line arguments, such as ``(ls, '-l')``. - The ``$cwd`` parameter (optional). - This specifies the *current working directory* to run the command in. - If omitted or ``None`` the current working directory is not changed. - The ``$stdin`` parameter (optional). - This is a string that is fed to the command as its stdin. - If the command expects multiple lines of input, this string must include embedded newlines (e.g., ``'line 1\nline 2\n'``). - If omitted or ``None``, no stdin is provided to the command. All of these functions fail on backtracking_. Check_command ------------- :: special.check_command($command [, $cwd [, $stdin]]) Succeeds if the command returns a zero exit status. Fails otherwise. Any output from the command to stdout or stderr is unavailable. >>> from pyke import knowledge_engine >>> engine = knowledge_engine.engine() >>> engine.prove_1_goal('special.check_command((true))') ({}, None) >>> engine.prove_1_goal('special.check_command((false))') Traceback (most recent call last): ... CanNotProve: Can not prove special.check_command((false)) Command ------- :: special.command($stdout, $command [, $cwd [, $stdin]]) This just looks at the stdout of the command. Any output from the command to stderr is unavailable. The ``$stdout`` is a tuple of lines with the trailing newlines removed. This raises `subprocess.CalledProcessError`_ if the command returns a non-zero exit status. >>> from __future__ import with_statement >>> from pyke import pattern, contexts >>> def run_command(entity, command, cwd=None, stdin=None): ... with engine.prove_goal( ... 'special.%s($output, $command, $cwd, $stdin)' % entity, ... command=command, ... cwd=cwd, ... stdin=stdin) \ ... as gen: ... for vars, no_plan in gen: ... print vars['output'] >>> run_command('command', ('echo', 'hi', 'mom')) ('hi mom',) >>> run_command('command', ('ls',)) # doctest: +NORMALIZE_WHITESPACE ('fact_bases.txt', 'index.txt', 'links', 'question_bases.txt', 'rule_bases.txt', 'special.txt') >>> run_command('command', ('ls', '-l', 'links')) # doctest: +ELLIPSIS ('-rw-r--r-- 1 ... links',) >>> run_command('command', ('tail', '-n', '5', 'template.txt', '-'), ... '..', # cwd (doc/source) ... 'stdin: line 1\nstdin: line 2\nstdin: line 3\n') ... # doctest: +NORMALIZE_WHITESPACE ('==> template.txt <==', ' } catch(err) {}', ' ', '', '', '', '', '==> standard input <==', 'stdin: line 1', 'stdin: line 2', 'stdin: line 3') >>> run_command('command', ('false',)) Traceback (most recent call last): ... CalledProcessError: Command 'false' returned non-zero exit status 1 General_command --------------- :: special.general_command($output, $command [, $cwd [, $stdin]]) This is the fully general form that gives you all output from the command. The ``$output`` is a three tuple: (exit_status, stdout, stderr). Both stdout and stderr are single strings (with embedded newlines). >>> run_command('general_command', ('echo', 'hi', 'mom')) (0, 'hi mom\n', '') >>> run_command('general_command', ('cat', 'foobar')) (1, '', 'cat: foobar: No such file or directory\n') >>> run_command('general_command', ('tail', '-n', '5', '../../r2w.ini', 'foobar')) ... # doctest: +NORMALIZE_WHITESPACE (1, "==> ../../r2w.ini <==\ntarget_directory = 'html'\nmacros = ''\n\n[uservalues]\nversion = '0.2'\n", "tail: cannot open `foobar' for reading: No such file or directory\n") ./pyke-1.1.1/doc/source/knowledge_bases/fact_bases.txt0000644000175000017500000000634111346504626021660 0ustar lambylamby.. $Id: fact_bases.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Fact Bases page-description: Explanation of facts and fact bases. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: fact_bases.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ========== Fact Bases ========== When statements of fact are stored in Pyke, they are stored in *fact bases*. Pyke allows you to have as many fact bases as you like to help you organize your facts. The fact bases are created automatically, as needed, as new facts are asserted. Facts ===== Think of a fact as a simple statement_. It has a name and a set of arguments. The arguments may be: - strings - proper identifiers don't need quotes: ``Fred`` is the same as ``'Fred'`` - numbers - None, True or False - tuples of any of these (including nested tuples) - singleton tuples don't require a comma: ``(1)`` is the same as ``(1,)`` Duplicate facts are not allowed. An attempt to assert a fact that already exists is silently ignored. But note that to be a duplicate, *all* of the arguments must be the same! Currently facts are thought to be immutable, meaning that they may not be changed or retracted. That's why dictionaries, lists and user-defined objects are not recommended as arguments. Case Specific Facts --------------------- Most facts are *case specific* facts. This means that they will be deleted when an engine_ reset_ is done to prepare for another run of the inference engine. Case specific facts are asserted through either:: some_engine.assert_(kb_name, fact_name, arguments) some_engine.add_case_specific_fact(kb_name, fact_name, arguments) They may also be asserted by forward-chaining_ rules. Universal Facts --------------------- Universal facts are never deleted (specifically, when a reset_ is done). You can specify universal facts in a `.kfb file`_, or add universal facts by calling:: some_engine.add_universal_fact(kb_name, fact_name, arguments) Typically, all universal facts are added once at `program startup`_. ./pyke-1.1.1/doc/source/knowledge_bases/rule_bases.txt0000644000175000017500000002410311346504626021706 0ustar lambylamby.. $Id: rule_bases.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Rule Bases page-description: Explanation of rule bases, overview of .krb files and how these files are compiled and loaded into your Python program. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: rule_bases.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ========== Rule Bases ========== Rule bases are collections of rules_. Rule bases are created by writing a *knowledge rule base* (`.krb`_) file with your favorite text editor. A single rule base may contain both forward-chaining_ and backward-chaining_ rules. The forward-chaining rules are run automatically when the rule base is activated_ to assert new statements_ of fact_. Thus, forward-chaining rules are not directly used to determine whether any particular statement_ is true. But backward-chaining rules *are* directly used to determine whether a particular statement is true. Thus, when a rule base name (or, more properly, a `rule base category`_ name, explained below) is used as the *knowledge base name* in a statement, it refers to the backward-chaining rules within that rule base. .. note:: Pyke runs all forward-chaining rules before running any backward-chaining rules. Thus, using rule base names as the *knowledge base name* in statements within a forward-chaining rule is prohibited, as this would cause backward-chaining rules to run in order to process the forward-chaining rule. Why Multiple Rule Bases? ======================== There are two reasons to have more than one rule base (i.e., more than one `.krb file`_): #. To divide a large set of rules into human manageable units. In this case, you want Pyke to use all of the rule bases combined. For example, you may have rules governing the automatic generation of SQL statements, and other rules governing the generation of HTML documents. You want to keep these rules in different rule bases to make them more manageable. #. To enable your Python program to choose between different rule bases that are tailored to different specific situations. For example, some of the rules governing the automatic generation of SQL statements may vary depending upon the target database (e.g., ``mysql``, ``postgresql``, ``oracle``). In this case, you want to be able to tell Pyke which of several rule bases to use on each invocation, depending upon which target database you will be accessing. You would never use more than one of these rule bases at the same time, so these rule bases are mutually exclusive. These two goals are met by three capabilities: - `Rule Base Categories`_ - `Rule Base Inheritance`_ - `Rule Base Activation`_ Rule Base Categories ==================== All rule bases are grouped into categories. Each rule base category only gets to have **one** active_ rule base. Thus, you place rule bases that you want to have active simultaneously into *different* rule base categories; and place rule bases that are mutually exclusive with each other (e.g., the ``mysql``, ``postgresql`` and ``oracle`` rule bases) into the *same* rule base category. Each rule base category has a unique name. In the example above you would want two rule base categories: ``database`` and ``html_generation``. The rule base category name is used as the *knowledge base name* for statements within rules of one rule base category that want to refer to rules within another rule base category. For example, rule bases in the ``html_generation`` category would need to use ``database.insert(...)`` to refer to the ``insert`` rules in the ``database`` category. Rule Base Inheritance ===================== The rule bases within the same category_ share rules amongst themselves through rule base inheritance. Rule bases use single inheritance to inherit the rules_ from one other rule base. This can go on to any depth. Both forward-chaining_ and backward-chaining_ rules_ are inherited. This allows mutually exclusive rule bases to share common rules in a parent rule base without having to duplicate these rules amongst themselves. Here is the definition, then, of a `rule base category`_: Each *root* rule base (through rule base inheritance) defines a unique rule base category. All rule bases derived (directly or indirectly) from that root rule base are in the same rule base category. The name of the rule base category is simply the name of its root rule base. So, our ``database`` and ``html_generation`` example would look like this: .. figure:: ../images/rule_base_categories.png :width: 450 :height: 424 :scale: 100 :align: center Rule Base Categories We have one root rule base called ``database`` and it has derived rule bases called ``mysql``, ``postgresql`` and ``oracle``. And a second root rule base called ``html_generation`` with ``firefox`` and ``internet_explorer``. The two root rule bases define two rule base categories: - database, which includes: - database - mysql - postgresql - oracle - html_generation, which includes: - html_generation - firefox - internet_explorer .. note:: The `.krb files`_ for these rule bases may be placed anywhere you want them within your Pyke source directory structure -- in other words, your directory structure is not required to match your rule base inheritance structure. Only one rule base from each rule base category may be active_ at any point in time. Within each of these rule bases, if the knowledge base name is omitted from a statement within a `backward-chaining rule`_, it defaults to the `rule base category`_ of that rule base, *not* the rule base itself. Thus, ``insert(...)`` within ``postgresql`` would mean ``database.insert(...)``, and ``make_tag(...)`` within ``firefox`` would mean ``html_generation.make_tag(...)``. .. important:: Note that referring to a rule base category (either explicitly or implicitly) *always* refers to the active_ rule base within that category. This may not be the rule base with that name (the root rule base), or even the rule base making implicit use of the rule base category. For example, ``insert(...)`` within ``postgresql`` will end up referring to ``insert`` rules within the ``oracle`` rule base when ``oracle`` is the active rule base. All rules referring to foreign `rule base categories`_ must explicitly use the rule base category name. For example, to refer to the ``insert`` rule for databases, within the ``html_generation`` category, you would have to say ``database.insert(...)``. In this way, all of the rules will work no matter which rule base is active within each rule base category. Rule Inheritance ---------------- There is an important difference between how backward-chaining_ rule inheritance works within Pyke rule bases and how method inheritance works within Python classes: * When a derived class in Python defines a method by the same name as a method in its parent class, the derived method *overrides* the parent method. I.e., only the derived method is used when a call is made to it. * In contrast, when a derived rule base in Pyke defines `backward-chaining rules`_ for a goal that also has backward-chaining rules defined for it in the parent rule base, the derived rule *extends* the set of rules that may be used to try to prove this goal_. All of the derived rules will be tried first. If all of these fail, then the parent rules are tried. If you don't want the parent rules to be used for a particular goal_, you must list that goal name in the ``without`` clause of the extending_ statement at the beginning of the derived rule base. .. note:: All `forward-chaining rules`_ in the parent rule base are always included in the derived rule base. The ``without`` clause only applies to backward-chaining rules. Rule Base Activation ===================== Loading_ rule bases only makes them available for use. It does not automatically activate_ any of them. This must be done explicitly by your Python program. Your program may decide to activate different rule bases in different situations. Additionally, `forward-chaining rules`_ may be used to activate more specific rule bases, based upon their inferencing. But once a rule base has been activated for a `rule base category`_, only children of the currently active rule base may be activated from that point on. Because these children inherit_ the rules of the currently active rule base; activating child rule bases only adds new rules, and doesn't take any away. Thus, any forward-chaining rule run during the activation of the first rule base are not invalidated by activating the second rule base. In our database example, your program could activate the root ``database`` rule base and let the forward-chaining rules within the ``database`` rule base figure out which derived rule base to activate depending on the particular database in use at the time the program is run. ./pyke-1.1.1/doc/source/knowledge_bases/index.txt0000644000175000017500000001071711346504626020677 0ustar lambylamby.. $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ .. .. Copyright © 2007-2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: Knowledge Bases page-description: Knowledge is made up of both *facts* and *rules*. These are gathered into named repositories called *knowledge bases*. /description section-pages: , fact_bases, rule_bases, question_bases, special format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 2 /restindex uservalues filedate: $Id: index.txt a2119c07028f 2008-10-27 mtnyogi $ /uservalues ==================== Knowledge Bases ==================== Knowledge is organized into named repositories called *knowledge bases*. A knowledge base is like a directory for files on disk, except that knowledge bases may not be nested. Therefore, the *entities* within a knowledge base always have a two-level name: *knowledge_base_name.knowledge_entity_name*. Here are some examples of facts you might see in a web server application:: header.cookie('session=123456789;remember=the_alamo') cookie.session(123456789) cookie.remember(the_alamo) header.accept_encoding(gzip) header.accept_encoding(deflate) request.path('/my/site.html') request.path_segment(0, my) request.path_segment(1, 'site.html') request.path_segment(-2, my) request.path_segment(-1, 'site.html') Note that three different knowledge bases (all `fact bases`_) are shown here named ``header``, ``cookie``, and ``request``; each with multiple facts. The second part of the two-part name is the name of the *knowledge entity*. You can think of knowledge entities as statement_ *types* or *topics*. So: :request: is the name of the *knowledge base*. :request.path_segment: is the name of the *knowledge entity*, or statement topic. :request.path_segment(-1, 'site.html'): is a specific statement about the topic of request.path_segments. What do Knowledge Bases Do? =========================== Ultimately Pyke is interested in proving statements, or answering the question "Is statement X true"? There are several different types of knowledge bases. Each type of knowledge base represents a different way of doing this: - Those that contain simple lists of statements of fact (as you see in the example above) are called `fact bases`_. - These determine whether a statement is true by simply checking to see if that statement is in their list of known facts. - Those that contain *if-then* rules_ are called `rule bases`__. - These determine whether a statement is true by running their if-then rules to try to construct a proof for that statement. - Rule bases may include both forward-chaining_ and backward-chaining_ rules. - It is also possible to create other kinds of knowledge bases that determine the truth of statements in other ways. Pyke provides two of these: - The `question base`_ which just poses the statement to an end user as a question. - The special_ knowledge base which has several special one-off knowledge entities to do different things like run a command_ on the underlying system and examine its output and/or exit status. - There is only has one instance of this knowledge base -- called ``special``. .. __: rule_bases.html .. note:: All knowledge bases share the same name space. So no two of them, regardless of their type, may have the same name. ./pyke-1.1.1/doc/source/PyCon2008-paper.txt0000644000175000017500000005200211346504626017074 0ustar lambylamby.. $Id: PyCon2008-paper.txt 057d79259b20 2009-05-14 mtnyogi $ .. .. Copyright © 2008 Bruce Frederiksen .. .. Permission is hereby granted, free of charge, to any person obtaining a copy .. of this software and associated documentation files (the "Software"), to deal .. in the Software without restriction, including without limitation the rights .. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell .. copies of the Software, and to permit persons to whom the Software is .. furnished to do so, subject to the following conditions: .. .. The above copyright notice and this permission notice shall be included in .. all copies or substantial portions of the Software. .. .. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR .. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE .. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER .. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, .. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN .. THE SOFTWARE. restindex crumb: PyCon 2008 Paper page-description: Paper presented at the PyCon 2008 conference in Chicago. /description format: rest encoding: utf8 output-encoding: utf8 include: yes initialheaderlevel: 1 /restindex uservalues filedate: $Id: PyCon2008-paper.txt 057d79259b20 2009-05-14 mtnyogi $ /uservalues ========================================================================= Applying Expert System Technology to Code Reuse with Pyke ========================================================================= ----------------------------- PyCon 2008, Chicago ----------------------------- .. |bullet| unicode:: U+02022 .. |copy| unicode:: U+000A9 :Author: Bruce Frederiksen :Date: Fri, 14 Mar 2008 :Web: pyke.sourceforge.net :Copyright: |copy| 2008, Bruce Frederiksen Abstract ========= This paper explores a new approach to code reuse using a backward-chaining rule-based system, similar to prolog, to generate a function call graph *before* the functions are called. This is compared with current solutions which build the call graph *as* the functions are called. This approach is introduced through an open source project called Pyke (Python Knowledge Engine). Finally, the initial results show that the utility of this approach far exceeds expectations; leading to something more akin to automatic programming rather than adaptable libraries. A call for help is given to explore the capabilities of this approach across different domains. .. contents:: The Thinking that Led to Pyke ============================= The Need for Code Reuse ~~~~~~~~~~~~~~~~~~~~~~~ At one of my contracting jobs, they had many clients running essentially the same program, but each client needed minor code modifications. Their objective was to maximize code reuse. What is Code Reuse? ~~~~~~~~~~~~~~~~~~~~~ The first question is what does "code reuse" mean? And the answer that seems most logical is *function* reuse. Where code modifications are required, a new function can be created incorporating those modifications. Then the remaining task is to bring the proper collection of functions together for each client. This gets more complicated as several versions of many functions will be produced for various clients that are all available for reuse by the next client. So it's not simply the case that there will be one standard default version of each function, and then several one-off customized versions that each only apply to a single client. The result of this function combination exercise is a function call graph. Example 1 --------- Let us imagine that we start out with two functions for client1: .. image:: images/PyCon2008/client1.png :scale: 60 And then client2 comes along. Let us first suppose that we need a new version of function A, but can reuse function B\ :sub:`1`: .. image:: images/PyCon2008/client2b.png :scale: 60 This is easy in any programming language and leads naturally to the idea that the functions to reuse are the lower-level ones, which can be placed into libraries. But now let us suppose the opposite; that we need a new version of function B, but can reuse function A\ :sub:`1`: .. image:: images/PyCon2008/client2d.png :scale: 60 This is where we need help. Current Solutions --------------------- The current solutions are all run-time solutions that trap the call from function A\ :sub:`1` to some function B and figure out which function B to use when the call is made. For example: * O-O Dynamic Binding * Zope Adapters * Generic Functions Current Solution Limitations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These solutions are all limited for the same reason. Let's look at another example to see why. Example 2 ---------- Real world programs have many more than two functions, but we can start to see the limitations of the current solutions by looking at a three function example. We start with one client and three functions. When client2 was added, it could only share function A\ :sub:`1` and had to have a new B (B\ :sub:`2`) that needs a new function with a different call interface than C, so we'll call it D\ :sub:`1`. Then along comes client3. This time things are looking up, because all we need is a new version of function D: .. image:: images/PyCon2008/client3d.png :scale: 60 Now let's see what happens when we want to call the program for client3. We know we need to start with function A\ :sub:`1`, since there is only version of function A: .. image:: images/PyCon2008/client3e.png :scale: 60 But at this point we have two choices for function B. All we know for client3 is that we're supposed to use function D\ :sub:`2`, so we're left to guess about function B. So we try the first one, function B\ :sub:`1`: .. image:: images/PyCon2008/client3f2.png :scale: 60 It's not until function B\ :sub:`1` tries to call some function C that we discover a problem. This is where the current solutions break down. Certainly for this example, it is easy to imagine a developer telling the binding system: oh yea and client3 is going to have to use function B\ :sub:`2` as well. But more realistic call graphs are much more complicated than this; so the developer would have to specify which functions to use going back many levels. And then when there is a change in these upper level shared functions later on, it will affect the call graphs for many clients. So the current solutions don't scale well. Continuing on with our example; what we need to do at this point is back up and try the other B function: .. image:: images/PyCon2008/client3g.png :scale: 60 After doing this, we discover the solution for the final call graph: .. image:: images/PyCon2008/client3h.png :scale: 60 What's Needed ------------------- By looking at this example, we discover two things about how to solve this problem: #. Do function selection **prior** to calling any of the functions. We can't wait until one function calls another to figure out what to do, because we may change our minds! #. Use a standard backward-chaining rule-based algorithm. The process of first trying function B\ :sub:`1`, then backing up and trying function B\ :sub:`2` is exactly the process used in backward-chaining rule-based systems like prolog. They call it *backtracking*. Applying Backward-Chaining to Code Reuse ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The next question is how do we use a backward-chaining system to produce function call graphs? Let's examine, conceptually, what a set of backward-chaining rules would look like to find a solution to this problem. Then we can determine how to turn this into a function call graph. The following diagram shows *goals* as dotted line boxes around the *rules* that prove that goal. In this example, some goals only have one rule and some have two. We also see how *rules* link to other *goals*. For example, rule ``Use B1`` and rule ``Use B2`` both prove the same goal: ``Find B``. But ``Use B1`` links to the ``Find C`` goal, while ``Use B2`` links to ``Find D``. .. image:: images/PyCon2008/bc_rules2.png :scale: 60 Now we can follow how these rules would be run by the knowledge engine: * The whole process is kicked off by asking the knowledge engine for a solution to ``Find A``. * There is only one rule for ``Find A``: ``Use A1``, so the knowledge engine tries this rule. * ``Use A1`` needs a solution to ``Find B``. * The knowledge engine tries the first rule for ``Find B``: ``Use B1``. * ``Use B1`` needs a solution to ``Find C``. * The knowledge engine tries the only rule for ``Find C``: ``Use C1``, which fails for client3! The situation now looks like: .. image:: images/PyCon2008/bc_rules5.png :scale: 60 Continuing on: * Since there are no other rules for ``Find C``, the ``Find C`` goal fails. * Which means that the ``Use B1`` rule fails. * So the knowledge engine tries the next rule for ``Find B``: ``Use B2``. * ``Use B2`` needs a solution for ``Find D``. * The knowledge engine tries the first rule for ``Find D``: ``Use D1``, which fails for client3. * The knowledge engine tries the next rule for ``Find D``: ``Use D2``, which succeeds for client3! * The ``Find D`` goal succeeds. * The ``Find B`` goal succeeds. * And the ``Find A`` goal succeeds. When we achieve final success, we have the following situation: .. image:: images/PyCon2008/bc_rules8.png :scale: 60 What remains is to translate this into a function call graph. It becomes obvious that we want to attach our python functions directly to the backward-chaining rules: .. image:: images/PyCon2008/bc_rules9.png :scale: 60 Pyke ===== Pyke KRB Syntax ~~~~~~~~~~~~~~~~~~~~~~~~~~~ How does all of this look in Pyke? Pyke has its own language for rules, which it compiles into python source modules and then imports. This gives a performance boost by circumventing nearly all of the inference engine interpretation logic. It also makes it very easy to embed short python code snippets directly within the rules to help out with the inferencing. This keeps the inference mechanism simpler as it does not have to deal with things that are already easy in a procedural language (like arithmetic and simple list manipulation). The Pyke rule source files are called *knowledge rule bases* and have a ``.krb`` suffix. We'll continue with the previous example here. First, let's look at the rules before we attach the python functions. Here's three of the rules:: use_B2 use find_B($client) when check_function($client, B, 2) find_D($client) use_D1 use find_D($client) when check_function($client, D, 1) use_D2 use find_D($client) when check_function($client, D, 2) Note that Pyke uses a ``$`` to indicate pattern variables (anonymous pattern variables start with ``$_``). The ``check_function`` goal checks to see what version of the indicated function should be used for this client. If this is the incorrect version, it fails. If there is no indication for this function, it succeeds to allow guessing. Attaching Python Functions to Backward-Chaining Rules ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here are the last two rules with the python code added. The rules have the python function attached to them so that the function can be returned from the goal as an additional parameter. Because this parameter does not affect the inferencing process, it is a hidden parameter. These examples just show one line of python code, but you may have as many lines as you want:: use_D1 use find_D($client) when check_function($client, D, 1) with print "D1" use_D2 use find_D($client) when check_function($client, D, 2) with print "D2" Pyke calls the function call graphs *plans*. This terms applies to both the final top-level call graph, as well as intermediate call graphs. Calling Subordinate Plans --------------------------- Now we do the same thing to add python code to the ``use_B2`` rule:: use_B2 use find_B($client) when check_function($client, B, 2) find_D($client) with print "B2" We have code for the B\ :sub:`2` function, but how does it call the plan returned from the ``find_D`` goal? The most common way is:: use_B2 use find_B($client) when check_function($client, B, 2) find_D($client) $$() with print "B2" In general, there may be many goals in the ``when`` clause that produce plans. Each would have an indented line of python code under it with ``$$`` indicating the subordinate function. These indented lines are combined with the lines in the ``with`` clause to form the complete python function for this rule (with the differences in indenting levels corrected). But in this case, this would mean that ``print "Dx"`` would be executed before ``print "B2"``, which seems backwards. To call the subordinate plan within the ``with`` clause, there is an alternate mechanism:: use_B2 use find_B($client) when check_function($client, B, 2) find_D($client) as $d with print "B2" $d() The ``as $d`` clause stores the plan function in pattern variable ``$d`` rather than adding a call to it to the ``with`` clause. Then you can decide in the ``with`` clause whether to call it, when to call it, how many times to call it, etc. Note that pattern variables in general can be used within the python code. These are replaced by their final bound values (as constants) after the top-level goal has been proven. Thus, the rules can also be used to determine and set constant values within the plan functions to further customize the code. This is the reason that the code for the attached python functions is placed directly in the .krb file rather than in a separate python module. Some Final Points about Plans ------------------------------ * Function parameters are specified at the end of the ``use`` clause with an optional ``taking`` clause:: use_B2 use find_B($client) taking (a, b = None) ... * A completed plan appears as a normal python function. * Plans may be pickled and reused. * If you add functools.partial to copy_reg. * You don't need to import all of Pyke to unpickle and run a plan. * Only one small Pyke module is needed. Other Capabilities ~~~~~~~~~~~~~~~~~~~~~~~~ * Pyke also supports forward-chaining rules:: fc_rule_name foreach fact_base_name.fact_name(pattern...) ... assert fact_base_name.fact_name(pattern...) ... * Pyke runs all of the forward-chaining rules whose ``foreach`` clause succeeds prior to running any backward-chaining rules. Thus, forward-chaining rules can not call backward-chaining rules and vice versa. But backward-chaining rules *can* examine facts asserted by forward-chaining rules. * There are different kinds of knowledge bases: * Fact Bases: * simply store facts. * Rule Bases: * store both forward-chaining and backward-chaining rules. * can use rule base inheritance to inherit, and build upon, the rules from another rule base. * But only single inheritance. * Thus each rule base has a unique root rule base. * All rule bases that share the same root form a *rule base category*. * allow selection of which rule base(s) to use through rule base *activation*. * But only one rule base per rule base category may be active at one time. * Extensibility. You can write your own knowledge bases. These might: * look up facts in a database * ask users questions * probe hardware/software settings Initial Results ================================ After writing Pyke's younger brother, it occurred to me that backward-chaining could be used to automatically figure out how to join database tables together and generate SQL statements. And if the backward-chaining rules could see which substitution variables are needed by an HTML templating system, it could automatically generate the SQL to get these data and build the code to update the template. It seemed that it would no longer be necessary to include anything that looks like code in the HTML templates. The graphic designers could just add simple attributes to their tags and the backward-chaining system would figure out the rest. This would mean that the programmers don't need to modify the HTML templates, and the graphic designers could maintain full ownership of the HTML. I had a WSGI front-end that would simply assert the data passed to it as facts. The forward-chaining rules took these starting facts, parsed the cookie information, form information, browser information, and url, determined whether the user was logged in, figured out which client the request was for, established all of this as additional facts and activated the appropriate rule base for this client. Then the WSGI front-end simply asked for a proof of the ``process()`` goal and executed the resulting plan function which returned the final HTTP status codes and HTML document. For a page retrieval (vs. form action), the ``process`` goal used two sub-goals: #. A ``format_retrieval`` goal that read the HTML template, and built a plan to render the template, given the needed data. This goal also returned a simple descriptor of this needed data as part of its inferencing. #. A ``retrieve`` goal then took that descriptor of the needed data, built the necessary SQL statements, and cooked them into a plan to execute those statements and return the needed data as a simple dictionary. Then the two sub plans were combined in the reverse order, to first retrieve the data and then populate the template, for the final plan that went back to the WSGI front-end. The Pyke examples/sqlgen and examples/web_framework are simplified examples that you can look at. Now, as it turned out, the company had been running without a president for quite awhile, and had finally hired a new president. So just as I finished the SQL generation logic to handle unique data (vs. multi-row data) and was preparing to show some demonstrations; our new president, coming from a java background and apparently never having heard of python, decided to cancel the project. End of contract! Code Reuse through Automatic Programming ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The fundamental lesson learned was that this technique ends up being far more capable than what I had first imagined. More than producing adaptable libraries capable of using B\ :sub:`1` or B\ :sub:`2` at some point in their call graphs, this approach leads to something more akin to the back-end of a compiler -- except that the compiler front-end does not target a textual language that needs to be written and parsed; but is rather a simple observer of already known facts: Show me your schema, and I'll build your SQL statements. Show me your HTML templates, and I'll build the code to populate them for you. This seems to change the whole concept of *code reuse*; elevating it from the realm of static *libraries*, to the realm of dynamic *automatic programming*. Going Forward =============== Thinking that others might find this useful, I've re-implemented the underlying knowledge engine from scratch, with numerous improvements gained from the experience of the first attempt, and made it open source. With the backward-chaining rule base system, many applications are possible: * Complicated decision making applications. * Compiler back-ends. * The .krb compiler uses Pyke. * Automatic SQL statement generation. * Automatic HTML generation/template processing. * The control module for a web framework tool. * Incorporate new custom functions into a large set of standard functions, which may change the selection or configuration of standard functions in other parts of the program. * Automatically re-distribute the modules of a system over different programs and computers to meet a wide range of performance and capacity goals. * Diagnosis systems. * E.g., Automated customer service systems. * Program or library customization for specific uses. * Instantiate, configure, and interconnect networks of objects to meet a specific need or situation. Up to this point, I've been flying solo. For this project to move forward to fully explore its capabilities, I'm going to need help! I'd like to see several early adopters run with this and try it out in different domains. Pyke is in alpha status now and is ready to start to lean on. .. raw:: html
Creative Commons License This paper is licensed under a `Creative Commons Attribution 3.0 Unported License`__. .. __: http://creativecommons.org/licenses/by/3.0/ ./pyke-1.1.1/doc/ast_syntax0000644000175000017500000000506311346504626014511 0ustar lambylambyfile : ('file', parent, ((fc_rule), (fc_extras_line)), ((bc_rule), (bc_extras_line), (plan_extras_line))) parent : None | ('parent', extending_symbol, (without_symbols)) fc_rule: ('fc_rule', name, (fc_premise), (assertion)) fc_premise : ('fc_premise', kb_name, entity_name, (pattern), starting_lineno, ending_lineno) | ('fc_first', (fc_premise), starting_lineno) | ('fc_forall', (fc_premise), (fc_premise)_opt, starting_lineno, ending_lineno) | ('fc_notany', (fc_premise), starting_lineno) | python_premise assertion : ('assert', kb_name, entity_name, (pattern), starting_lineno, ending_lineno) | ('python_assertion', python_code, starting_lineno, ending_lineno) python_premise : ('python_eq', pattern, python_code, starting_lineno, ending_lineno) | ('python_in', pattern, python_code, starting_lineno, ending_lineno) | ('python_check', python_code, starting_lineno, ending_lineno) | ('python_block', python_code, starting_lineno, ending_lineno) counting : ('counting', quoted_pattern_var_name, python_code_opt, python_code_opt) bc_rule: ('bc_rule', name, goal, (bc_premise), (python_code), (plan_vars_needed)) goal : ('goal', IDENTIFIER, (pattern), (python_line), starting_lineno, ending_lineno) bc_premise : ('bc_premise', _bool, name_opt, name, (pattern), plan_spec_opt, starting_lineno, ending_lineno) | ('bc_first', _bool, (bc_premise), starting_lineno) | ('bc_forall', (bc_premise), (bc_premise)_opt, starting_lineno, ending_lineno) | ('bc_notany', (bc_premise), starting_lineno) | python_premise name : quoted_STRING | "context.lookup_data('PATTERN_VAR_NAME')" plan_spec : ('as', pattern_var_name) | ('plan_spec', NUMBER_opt, plan_var_name, (python_line), (plan_var_needed), lineno, lexpos) pattern : variable | "pattern_literal()" | "pattern_tuple(())" | "pattern_tuple((), )" variable: "contexts.variable('')" | "contexts.anonymous()" data : None | True | False | NUMBER | quoted_STRING | TUPLE python_code : ((python_line), (plan_var_needed), lineno, lexpos) ./pyke-1.1.1/doc/html/0000755000175000017500000000000011425360453013325 5ustar lambylamby./pyke-1.1.1/doc/html/index.html0000644000175000017500000002014111346504626015324 0ustar lambylamby Welcome to Pyke
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Welcome to Pyke

Release 1.1

Pyke introduces a form of Logic Programming (inspired by Prolog) to the Python community by providing a knowledge-based inference engine (expert system) written in 100% Python.

Unlike Prolog, Pyke integrates with Python allowing you to invoke Pyke from Python and intermingle Python statements and expressions within your expert system rules.

Pyke was developed to significantly raise the bar on code reuse. Here's how it works:

  1. You write a set of Python functions, and a set of Pyke rules to direct the configuration and combination of these functions.
  2. These functions refer to Pyke pattern variables within the function body.
  3. Pyke may instantiate each of your functions multiple times, providing a different set of constant values for each of the pattern variables used within the function body. Each of these instances appears as a different function.
  4. Pyke then automatically assembles these customized functions into a complete program (function call graph) to meet a specific need or use case. Pyke calls this function call graph a plan.

In this way, Pyke provides a way to radically customize and adapt your Python code for a specific purpose or use case.

Doing this essentially makes Pyke a very high-level compiler. And taking this approach also produces dramatic increases in performance.

And Pyke is very successful at this, providing order of magnitude improvements in:

  • Code adaptability (or customization),
  • Code reuse and
  • Performance

Pyke does not replace Python, nor is meant to compete with Python. Python is an excellent general purpose programming language, that allows you to "program in the small".

Pyke builds upon Python by also giving you tools to directly program in the large.

Oh, and Pyke uses Logic Programming to do all of this. So if you're interested in Logic Programming or Expert Systems, well Pyke has that too...

Pyke on Google Groups

Please join Pyke on Google Groups for questions and discussion!

FAQ

There is also an FAQ list on the sourceforge wiki, to make it easy to contribute.

More:

About Pyke

What pyke does for you, its features, steps to using pyke and installation.

Logic Programming Tutorial

A tutorial on logic programming in Pyke, including statements, pattern matching and rules.

Knowledge Bases

Knowledge is made up of both facts and rules. These are gathered into named repositories called knowledge bases.

Pyke Syntax

The syntax of Pyke's three different kinds of source files.

Using Pyke

How your Python program calls Pyke.

Examples

An overview of the examples provided with Pyke.

Applying Expert System Technology to Code Reuse with Pyke

Paper presented at the PyCon 2008 conference in Chicago.

Page last modified Thu, Mar 04 2010.
./pyke-1.1.1/doc/html/logic_programming/0000755000175000017500000000000011425360453017024 5ustar lambylamby./pyke-1.1.1/doc/html/logic_programming/index.html0000644000175000017500000001540711346504626021034 0ustar lambylamby Logic Programming Tutorial
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Logic Programming Tutorial

Pyke is an inference engine that applies rules to facts to establish additional facts (through forward-chaining rules), and/or to prove goals and optionally assemble Python functions into customized call graphs, called plans (through backward-chaining rules).

Pyke may then be reset, deleting the last set of facts, so that the cycle may be repeated. For each cycle a different rule base may be activated.

The plan capability allows the postponement of code execution until the top-level goal has been completely proven. This shields the code from blind alleys and the backtracking that occurs within the rules.

Once a plan has been created, it may be executed multiple times with different arguments. It may also be pickled, and later run again only requiring one small Pyke module.

Pyke also provides an end user question capability, as well as the capability to run commands on the local system to guide the inferencing.

More:

Statements

What is a statement in Pyke?

Pattern Matching

Explanation of pattern matching and pattern variables.

Rules

Explanation of rules, forward-chaining and backward-chaining.

Plans and Automatic Program Generation

Explanation of plans and automatic program generation.

Page last modified Thu, May 14 2009.
./pyke-1.1.1/doc/html/logic_programming/rules/0000755000175000017500000000000011425360453020156 5ustar lambylamby./pyke-1.1.1/doc/html/logic_programming/rules/index.html0000644000175000017500000002531311365362364022165 0ustar lambylamby Rules
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Rules

Conceptually, a rule is very simple:

if
    A
    B
    C
then
    D
    E

Meaning, "if A, B and C are true, then D and E are also true". These are often called if-then rules.

And what are A, B, C, D and E?

They are simply statements. Specifically, they are generalized statements containing patterns as arguments. You'll see more about this later.

Premises and Conclusions

Rules have two parts to them: an if part (containing a list of statements called the premises), and a then part (containing a list of statements called the conclusions).

Note

Pyke doesn't use the words if and then, as you'll see shortly.

Each of these if and then parts contain one or more facts or goals which are just generalized statements (meaning statements with patterns as arguments).

Processing the Premises Within the If Clause

Because each premise with the if clause is a generalized statement, the premise is pattern matched to known facts. This means that it may match more than one fact.

Pyke tries all combinations of matching facts through a process called backtracking. This will cause the same rule to potentially succeed multiple times, once for each unique combination of matching facts.

Backtracking

Note that while processing the list of premises within a rule's if clause:

  • If Pyke succeeds at proving a premise:
    • Pyke will proceed down to the next premise in the list.
    • Trying to proceed down from the last premise in the list (when it succeeds) causes the rule to succeed.
  • If Pyke fails at proving a premise:
    • Pyke will back up to the prior premise in the list and try to find another solution for it. The process starts over at the prior premise, going back down or further up the list depending on whether another solution can be found to the prior premise or not.
    • Trying to back up from the first premise in the list (when it fails) causes the rule to fail.
Backtracking Flow Diagram

Backtracking Flow Diagram

Thus, execution within each rule's if clause proceeds both backwards and forwards, up and down the list of premises, depending on whether each premise succeeds or fails. The process of backing up within the if clause to try alternate solutions is called backtracking.

Inferencing

Rules are specified individually within a rule base. They are not nested or explicitly linked to each other. So Pyke must automatically figure out how to combine these rules to accomplish some larger task. This is called inferencing and there are two different approaches that Pyke uses:

  • All forward-chaining rules are processed when a rule base is activated.
  • Backward-chaining rules are processed when your program asks Pyke to prove a specific goal (i.e., asks Pyke a question).
    • These rules are designed to answer a question rather than assert new facts or activate more specific rule bases.
    • They also have the ability to assemble Python functions into a customized call graph or program, called a plan, to meet a specific need.

Note

Forward-chaining and backward-chaining rules may both be included within the same rule base. Pyke knows the difference between forward-chaining and backward-chaining rules because they have different syntax.

More:

Forward Chaining

Explanation of forward-chaining rules and how forward-chaining works.

Backward Chaining

Explanation of backward-chaining rules, including how backward-chaining and backtracking works.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/logic_programming/rules/forward_chaining.html0000644000175000017500000006123311365362364024363 0ustar lambylamby Forward Chaining
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Forward Chaining

Forward chaining rules are processed automatically as each rule base is activated.

When a rule base is activated, all of its forward-chaining rules are run in the order that they appear in the .krb file for that rule base.

Overview of Forward-Chaining

To do forward-chaining, Pyke finds rules whose if clause matches Pyke's list of already known facts (the if clause may match, or succeed, multiple time; see backtracking). Each time a rule succeeds, it fires this rule, which adds the facts in the then clause of that rule to the list of already known facts.

These new facts may fire other forward-chaining rules by matching their if clause. This can go on to any depth. So Pyke ends up linking (or chaining) the then clause of the first rule to the if clause of the next rule.

Note

Forward-chaining continues until no more rules can be fired.

Reviewing

  1. Pyke starts with the if clause of the first rule and checks to see if it matches the known facts.
  2. If so, it proceeds to the then clause of that rule (firing the rule).
  3. Which may link (or chain) to the if clause of another rule.

Since Pyke processes these rules from if to then to if to then in the manner that we normally think of using rules, it's called forward chaining.

"Foreach", "Assert" Rather than "If", "Then"

Finally, since the statements within the if clause of the rule contain patterns; they may each match several facts. In this case, the rule will succeed and be fired multiple times.

The statements in the then clause of the rule also contain patterns. Each time the rule is fired, the pattern variables within the then statements are bound to different values so that different facts are asserted.

To avoid confusion, Pyke uses the words foreach and assert rather than if and then for forward-chaining rules. This is to suggest that "for each" combination of facts matching the first list of statements, the rule is fired to "assert" the facts in the second list of statements.

Note

The use of foreach and assert identifies the rule as a forward-chaining rule.

Example

This example will figure out the paternal ancestry of individuals given a list of starting statements about who the sons of each father are. (Daughters and mothers are omitted to keep the example brief). These facts are stored in a fact base called family1 as son_of(son, father):

1  son_of(david, bruce)
2  son_of(bruce, thomas)
3  son_of(thomas, frederik)
4  son_of(frederik, hiram)

We want to derive father_son relationships of the following form:

father_son($father, $son, $prefix)

where

$son:is the name of the son (e.g., david)
$father:is the name of the father (e.g., bruce)
$prefix:is a tuple of prefixes before the 'father' and 'son' titles to indicate the number of generations (e.g., () for direct father_son relationships, (grand), (great, grand), etc).

This is done using three forward-chaining rules. Each rule is presented as a separate step:

Finally, you will be shown how to Run the Example yourself.

Step 1: Direct_father_son

First we need to establish the direct father_son relationships (those whose $prefix is ()):

1  direct_father_son
2      foreach
3          family1.son_of($son, $father)
4      assert
5          family1.father_son($father, $son, ())

The Use of Pattern Variables

This demonstrates how pattern variables are used to transfer values from one statement within a rule into another statement within the rule.

This rule has a single statement in its foreach clause (on line 3). This statement matches all four son_of facts, so the rule succeeds four times; but with different bindings for the $son and $father pattern variables. This causes different facts to be asserted when the same assert clause (on line 5) is run four times; because each time line 5 is run, the values for $son and $father are transferred from the statement on line 3 to the statement on line 5.

When the rule fires matching line 3 to:

1  son_of(david, bruce)

It runs line 5 to assert:

5  father_son(bruce, david, ())

And when the rule fires a second time matching line 3 to:

2  son_of(bruce, thomas)

It runs line 5 a second time to assert:

6  father_son(thomas, bruce, ())

The rule fires twice more for the remaining son_of facts, asserting two more father_son relationships. So this rule adds a total of four new facts:

5  father_son(bruce, david, ())
6  father_son(thomas, bruce, ())
7  father_son(frederik, thomas, ())
8  father_son(hiram, frederik, ())

Step 2: Grand_father_son

Now we want to add grand son-father relationships. We have a new rule for this:

 6  grand_father_son
 7      foreach
 8          family1.father_son($father, $grand_son, ())
 9          family1.father_son($grand_father, $father, ())
10      assert
11          family1.father_son($grand_father, $grand_son, (grand))

The Use of Backtracking

The grand_father_son rule is run for all combinations of father_son facts that satisfy the two foreach statements (on lines 8 and 9) and asserts a (grand) father_son statement (on line 11) for each combination.

This rule is a good example for backtracking and will help later in your understanding of backtracking with backward-chaining. So let's follow the backtracking in the execution of this rule.

The foreach clause has two statements (on lines 8 and 9) in it that are both looking for father_son facts with a prefix of ():

8  family1.father_son($father, $grand_son, ())
9  family1.father_son($grand_father, $father, ())

These will be matched to the following family1 facts (facts 5 through 8):

5  father_son(bruce, david, ())
6  father_son(thomas, bruce, ())
7  father_son(frederik, thomas, ())
8  father_son(hiram, frederik, ())

Pyke starts at the top of the list of premises and looks for a match for the first premise (on line 8). This matches fact 5, so the first premise succeeds, binding $father to bruce:

8  family1.father_son($father, $grand_son, ())    => fact 5, SUCCESS
9  family1.father_son($grand_father, $father, ())

Success means go down, so Pyke goes to the next premise on line 9. This succeeds with fact 6 (because $father is bound to bruce):

8  family1.father_son($father, $grand_son, ())    => fact 5
9  family1.father_son($grand_father, $father, ()) => fact 6, SUCCESS

Success means go down, but Pyke is at the end of the list of premises, so the rule succeeds and Pyke fires the rule to assert:

9  father_son(thomas, david, (grand))

Since this is a forward-chaining rule, Pyke wants to get all of the answers from it that it can, so it continues as if it had a failure (i.e., as if it's not happy with this answer).

Note

You'll see later that Pyke doesn't do this automatically with backward-chaining rules.

So Pyke fails back up to the second premise and looks for another father_son after fact 6 with bruce as the first argument. This fails:

8  family1.father_son($father, $grand_son, ())    => fact 5
9  family1.father_son($grand_father, $father, ()) => FAILS

Fail means go up, so Pyke goes up to the first premise and looks for another father_son after fact 5, which succeeds for fact 6, binding $father to thomas:

8  family1.father_son($father, $grand_son, ())    => fact 6, SUCCESS
9  family1.father_son($grand_father, $father, ())

Success means go down, so Pyke goes down to the second premise which succeeds for fact 7:

8  family1.father_son($father, $grand_son, ())    => fact 6
9  family1.father_son($grand_father, $father, ()) => fact 7, SUCCESS

Success means go down, but Pyke is at the end of the list of premises, so the rule succeeds and Pyke fires the rule to assert:

10 father_son(frederik, bruce, (grand))

Then Pyke fails back up to the second premise, and continues looking for another match after fact 7. This fails:

8  family1.father_son($father, $grand_son, ())    => fact 6
9  family1.father_son($grand_father, $father, ()) => FAILS

Fail means go up, so Pyke goes back to the first premise and continues looking for another match after fact 6. (Since fact 7 is just like the last case, we'll skip matching fact 7 and go straight to the last fact, fact 8). The match to fact 8 succeeds, binding $father to hiram:

8  family1.father_son($father, $grand_son, ())    => fact 8, SUCCESS
9  family1.father_son($grand_father, $father, ())

Success means go down, so Pyke goes to the second premise and looks for a father_son for hiram. This fails:

8  family1.father_son($father, $grand_son, ())    => fact 8
9  family1.father_son($grand_father, $father, ()) => FAILS

Fail means go up, so Pyke goes back up to the first premise and looks for another match after fact 8. There are no more facts, so this fails:

8  family1.father_son($father, $grand_son, ())    => FAILS
9  family1.father_son($grand_father, $father, ())

Fail means go up, but Pyke is at the top of the list of premises, so the rule fails and Pyke is done processing it.

Important

Note that the last statement in the foreach clause may succeed multiple times (which fires the assert clause multiple times).

But the first statement in the foreach clause may only fail once. When that happens, the whole rule fails and the show's over for this rule!

So running the grand_father_son rule results in addition of these three facts:

9  father_son(thomas, david, (grand))
10 father_son(frederik, bruce, (grand))
11 father_son(hiram, thomas, (grand))    (this is the one we skipped)

Step 3: Great_grand_father_son

Finally, we want to add great(...) grand son-father relationships. We have a final rule for this:

12  great_grand_father_son
13      foreach
14          family1.father_son($father, $gg_son, ())
15          family1.father_son($gg_father, $father, ($prefix1, *$rest_prefixes))
16      assert
17          family1.father_son($gg_father, $gg_son,
                                (great, $prefix1, *$rest_prefixes))

Note

Note how the $prefixes for the statement on line 15 are specified as ($prefix1, *$rest_prefixes), rather than just $prefix. This is done so that it does not match (). (But it will still match (grand) by binding $rest_prefixes to ()).

This is the only rule that can be recursive. As this rule asserts new facts, those facts may be used by the same rule (by matching the statement on line 15) to produce even more great, great, ... father_son relationships.

Recursive Rules

Running this rule normally will assert the following two facts:

12 father_son(frederik, david, (great, grand))
13 father_son(hiram, bruce, (great, grand))

But, since these facts may also be used by the same rule (on line 15), Pyke checks each one by trying to run the rule again just for that new fact.

Trying this for the first new fact: father_son(frederik, david, (great, grand)) fails to find anything because david is not a father.

Trying this for the second new fact: father_son(hiram, bruce, (great, grand)) results in one more new fact:

14 father_son(hiram, david, (great, great, grand))

Now this last new fact is tried again with this rule, which fails again because david is not a father.

So at this point Pyke is finished with this rule. The rule ended up firing three times, asserting:

12 father_son(frederik, david, (great, grand))
13 father_son(hiram, bruce, (great, grand))
14 father_son(hiram, david, (great, great, grand))

Running the Example

These rules could be run as follows:

>>> from pyke import knowledge_engine
>>> engine = knowledge_engine.engine(__file__)
>>> engine.activate('fc_related')     # This is where the rules are run!
>>> engine.get_kb('family1').dump_specific_facts()
father_son('bruce', 'david', ())
father_son('thomas', 'bruce', ())
father_son('frederik', 'thomas', ())
father_son('hiram', 'frederik', ())
father_son('thomas', 'david', ('grand',))
father_son('frederik', 'bruce', ('grand',))
father_son('hiram', 'thomas', ('grand',))
father_son('frederik', 'david', ('great', 'grand'))
father_son('hiram', 'bruce', ('great', 'grand'))
father_son('hiram', 'david', ('great', 'great', 'grand'))

More:

Forward Chaining

Explanation of forward-chaining rules and how forward-chaining works.

Backward Chaining

Explanation of backward-chaining rules, including how backward-chaining and backtracking works.

Page last modified Mon, Mar 08 2010.
./pyke-1.1.1/doc/html/logic_programming/rules/backward_chaining.html0000644000175000017500000004160511365362364024476 0ustar lambylamby Backward Chaining
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Backward Chaining

Backward chaining rules are processed when your program asks Pyke a question (i.e., asks Pyke to prove a specific goal). Pyke will only use activated rule bases to do the proof.

Overview of "Backward-Chaining"

To do backward-chaining, Pyke finds rules whose then part matches the goal (i.e., the question). Once it finds such a rule, it tries to (recursively) prove all of the subgoals in the if part of that rule. Some of these subgoals are matched against facts, and others are subgoals for other backward-chaining rules. If all of the subgoals can be proven, the rule succeeds and the original goal is proven. Otherwise, the rule fails, and Pyke tries to find another rule whose then part matches the goal, and so on.

So Pyke ends up linking (or chaining) the if part of the first rule to the then part of the next rule.

Reviewing:

  1. Pyke starts by finding a rule whose then part matches the goal.
  2. Pyke then proceeds to process the if part of that rule.
  3. Which may link (or chain) to the then part of another rule.

Since Pyke processes these rules from then to if to then to if in the reverse manner that we normally think of using rules, it's called backward chaining.

To make this more clear, Pyke has you write your backward-chaining rules upside down by writing the then part first (since that's how it is processed).

"Use", "When" Rather than "Then", "If"

But then-if rules sound confusing, so Pyke uses the words use and when rather than then and if. You can then read the rule as "use" this statement "when" these other statements can be proven.

Note

Unlike the assert clause in forward-chaining rules, Pyke only allows one statement in the use clause.

Example

Consider this example:

 1  direct_father_son
 2      use father_son($father, $son, ())
 3      when
 4          family2.son_of($son, $father)

 5  grand_father_son
 6      use father_son($grand_father, $grand_son, (grand))
 7      when
 8          father_son($father, $grand_son, ())
 9          father_son($grand_father, $father, ())

10  great_grand_father_son
11      use father_son($gg_father, $gg_son, (great, $prefix1, *$rest_prefixes))
12      when
13          father_son($father, $gg_son, ())
14          father_son($gg_father, $father, ($prefix1, *$rest_prefixes))

15  brothers
16      use brothers($brother1, $brother2)
17      when
18          father_son($father, $brother1, ())
19          father_son($father, $brother2, ())
20          check $brother1 != $brother2

21  uncle_nephew
22      use uncle_nephew($uncle, $nephew, $prefix)
23      when
24          brothers($uncle, $father)
25          father_son($father, $nephew, $prefix1)
26          $prefix = ('great',) * len($prefix1)

27  cousins
28      use cousins($cousin1, $cousin2, $distance, $removed)
29      when
30          uncle_nephew($uncle, $cousin1, $prefix1)
31          father_son($uncle, $cousin2, $prefix2)
32          $distance = min(len($prefixes1), len($prefixes2)) + 1
33          $removed = abs(len($prefixes1) - len($prefixes2))

Note

These rules draw the same conclusions as the forward-chaining example, with the addition of the brothers, uncle_nephew and cousins rules.

We can draw something similar to a function call graph with these rules:

../../images/bc_rules.png

Example Rules

These rules are not used until you ask Pyke to prove a goal.

The easiest way to do this is with some_engine.prove_1_goal or some_engine.prove_goal. Prove_1_goal only returns the first proof found and then stops (or raises pyke.knowledge_engine.CanNotProve). Prove_goal returns a context manager for a generator that generates all possible proofs (which, in some cases, might be infinite).

Both functions return the pattern variable variable bindings, along with the plan.

Backtracking with Backward-Chaining Rules

For this example, these are the starting set of family2 facts:

1  son_of(tim, thomas)
2  son_of(fred, thomas)
3  son_of(bruce, thomas)
4  son_of(david, bruce)

And we want to know who fred's nephews are. So we'd ask uncle_nephew(fred, $nephew, $prefix).

Here are the steps (in parenthesis) in the inferencing up until the first failure is encountered (with the line number from the example preceding each line):

(1)   22  use uncle_nephew(fred, $nephew, $prefix)
          24  brothers(fred, $father)
(2)           16  use brothers(fred, $brother2)
                  18  father_son($father, fred, ())
(3)                   2  use father_son($father, fred, ())
                          4  family2.son_of(fred, $father)
                               matches fact 2: son_of(fred, thomas)
                  19  father_son(thomas, $brother2, ())
(4)                   2  use father_son(thomas, $son, ())
                          4  family2.son_of($son, thomas)
                               matches fact 1: son_of(tim, thomas)
                  20  check fred != tim
          25  father_son(tim, $nephew, $prefix1)
(5.1)         2  use father_son(tim, $son, ())
                  4  family2.son_of($son, tim)                               => FAILS
(5.2)         6  use father_son(tim, $grand_son, (grand))
                  8  father_son(tim, $grand_son, ())
                      2  use father_son(tim, $son, ())
                          4  family2.son_of($son, tim)                       => FAILS
(5.3)         11 use father_son(tim, $gg_son, (great, $prefix1, *$rest_prefixes))
                  13 father_son(tim, $gg_son, ())
                      2  use father_son(tim, $son, ())
                          4  family2.son_of($son, tim)                       => FAILS

Each rule invocation is numbered (in parenthesis) as a step number. Step 5 has tried 3 different rules and they have all failed (5.1, 5.2 and 5.3).

If you think of the rules as functions, the situation now looks like this (the step numbers that succeeded circled in black, and steps that failed circled in red):

../../images/bc_backtracking.png

We Need to Backtrack!

At this point, Pyke has hit a dead end and must backtrack. The way that backtracking proceeds is to go back up the list of steps executed, combining the steps from all rules into one list. Thus, when step 5 fails, Pyke backs up to step 4 and tries to find another solution to that step.

If another solution is found, Pyke proceeds forward again from that point. If no other solutions are found, Pyke backs up another step.

When Pyke goes back to step 4, the next solution binds $son to fred. This fails the subsequent check in the brothers rule:

20          check $brother1 != $brother2

And so Pyke goes back to step 4 once again. The next solution binds $son to bruce. This succeeds for brother and is passed down to father_son which returns david as fred's nephew.

Further backtracking reveals no other solutions.

Backtracking Summary

Thus we see:

  1. The backtracking algorithm: "fail goes up (or back) while success goes down (or forward)" is not limited to the steps within a single rule's when clause; but includes the entire chain of inferencing from the very start of trying to prove the top level goal.
  2. This execution model is not available within traditional programming languages like Python.
  3. The ability to go back to any point in the computation to try an alternate solution is where backward-chaining systems get their power!

Running the Example

>>> from pyke import knowledge_engine
>>> engine = knowledge_engine.engine(__file__)
>>> engine.activate('bc_related')

Nothing happens this time when we activate the rule base, because there are no forward-chaining rules here.

We want to ask the question: "Who are Fred's nephews?". This translates into the Pyke statement: bc_related.uncle_nephew(fred, $v1, $v2).

Note

Note that we're using the name of the rule base, bc_related rather than the fact base, family2 here; because we expect this answer to come from the bc_related rule base.

This is 'bc_related', 'uncle_nephew', with ('fred',) followed by 2 pattern variables as arguments:

>>> from __future__ import with_statement
>>> with engine.prove_goal('bc_related.uncle_nephew(fred, $nephew, $distance)') as gen:
...     for vars, no_plan in gen:
...         print vars['nephew'], vars['distance']
david ()

More:

Forward Chaining

Explanation of forward-chaining rules and how forward-chaining works.

Backward Chaining

Explanation of backward-chaining rules, including how backward-chaining and backtracking works.

Page last modified Mon, Mar 08 2010.
./pyke-1.1.1/doc/html/logic_programming/plans.html0000644000175000017500000003650511365362364021046 0ustar lambylamby Plans and Automatic Program Generation
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Plans and Automatic Program Generation

Once you understand how backward-chaining works, it is relatively easy to do automatic program generation.

Adding Plans to Backward-Chaining Rules

The way this is done is by attaching Python functions to backward-chaining rules. These functions are written in the with clause at the end of each rule in the .krb file. They don't affect how the rules run to prove a goal, but are gathered up to form a call graph that is returned along with the pattern variable bindings that prove the top-level goal.

Example

Consider a small rule base to construct programs to transfer money between bank accounts. Each from_acct and to_acct takes one of two forms:

  1. (name, account_type)
    • This is a local account with this bank.
    • Example: ('bruce', 'checking').
  2. (bank, name, account_type)
    • This is a foreign account with another bank.
    • Example: ('my_other_bank', 'bruce', 'checking').

At least one of the bank accounts must be a local account.

Here's the example rule base:

 1  transfer1
 2      use transfer($from_acct, $to_acct) taking (amount)
 3      when
 4          withdraw($from_acct)
 5              $$(amount)
 6          deposit($to_acct)
 7              $$(amount)

 8  transfer2
 9      use transfer($from_acct, $to_acct) taking (amount)
10      when
11          transfer_ach($from_acct, $to_acct)
12              $$(amount)

13  withdraw
14      use withdraw(($who, $acct_type)) taking (amount)
15      with
16          print "withdraw", amount, "from", $who, $acct_type

17  deposit
18      use deposit(($who, $acct_type)) taking (amount)
19      with
20          print "deposit", amount, "to", $who, $acct_type

21  transfer_ach1
22      use transfer_ach($from_acct, ($bank, $who, $acct_type)) taking (amount)
23      when
24          withdraw($from_acct)
25              $$(amount)
26          deposit((central_accts, ach_send_acct))
27              $$(amount)
28      with
29          print "send", amount, "to bank", $bank, "acct", $who, $acct_type

30  transfer_ach2
31      use transfer_ach($from_acct, $to_acct) taking (amount)
32      when
33          get_ach($from_acct)
34              $$(amount)
35          withdraw((central_accts, ach_recv_acct))
36              $$(amount)
37          deposit($to_acct)
38              $$(amount)

39  get_ach
40      use get_ach(($bank, $who, $acct_type)) taking (amount)
41      with
42          print "get", amount, "from bank", $bank, "acct", $who, $acct_type

How the Plan Functions are Generated for This Example

Each of these rules will have a plan function generated for it. These plan functions are generated with the same name as the rule name. Thus, the name of the generated Python plan function for the first rule would be "transfer1".

The plan function generated for the first rule consists of two lines taken from lines 5 and 7 of this example. The $$ in each of these lines will be expanded to the subordinate plan function returned from the proof of "withdraw($from_acct)" and "deposit($to_acct)" respectfully. The generated plan function will be defined to take an "amount" parameter because of the taking clause on line 2. This parameter is passed on to each of the subordinate plan functions in lines 5 and 7.

The plan function generated for the "withdraw" rule on line 13 will have the single line taken from line 16 in the with clause. The "$who" and "$acct_type" pattern variables will be expanded to constant values taken from the values bound to these pattern variables after the top-level (transfer) goal has been proven.

Finally, the plan function generated for the "transfer_ach1" rule on line 21 will have three lines: two from the when clause (lines 25 and 27) followed by one from the with clause (line 29). These lines will be generated at the same indent level in the plan function even though they are at different indent levels in the .krb file.

For more detailed information about the options available for plans in the .krb file, see Bc_rule Syntax.

Running the Example

The plan is created as a byproduct of proving the goal:

>>> from pyke import knowledge_engine
>>> engine = knowledge_engine.engine(__file__)
>>> engine.activate('plan_example')
>>> no_vars, plan1 = \
...   engine.prove_1_goal(
...     'plan_example.transfer((bruce, checking), (bruce, savings))')

plan1 is now a program to transfer X amount from 'bruce', 'checking' to 'bruce', 'savings'. Using the above rule names as function names, plan1 looks like this:

../images/plan1.png

Plan1

And can be called like a standard function, passing the parameters specified in the taking clause of the rules for the top-level goal (transfer):

>>> plan1(100)
withdraw 100 from bruce checking
deposit 100 to bruce savings

The program may be used multiple times:

>>> plan1(50)
withdraw 50 from bruce checking
deposit 50 to bruce savings

Notice the strings: bruce, checking and savings in the output. These were specified as pattern variables in the code and are cooked into the plan along with the function call graph.

Let's create a second program:

>>> no_vars, plan2 = \
...   engine.prove_1_goal(
...     'plan_example.transfer((bruce, checking), '
...                           '(my_other_bank, bruce, savings))')

plan2 is now a program to transfer X amount from 'my_other_bank', 'bruce', 'checking' to 'bruce', 'savings'. Plan2 looks like this:

../images/plan2.png

Plan2

And is run just like plan1, but produces different results:

>>> plan2(200)
withdraw 200 from bruce checking
deposit 200 to central_accts ach_send_acct
send 200 to bank my_other_bank acct bruce savings

And the final use case:

>>> no_vars, plan3 = \
...   engine.prove_1_goal(
...     'plan_example.transfer((my_other_bank, bruce, checking), '
...                           '(bruce, savings))')
>>> plan3(150)
get 150 from bank my_other_bank acct bruce checking
withdraw 150 from central_accts ach_recv_acct
deposit 150 to bruce savings

Plan3 looks like this:

../images/plan3.png

Plan3

Note that the same transfer2 function is calling two different functions (transfer_ach1 and transfer_ach2) in plan2 and plan3. This shows how different functions may be chosen based on the rule inferencing. Also note that after the generation of plan3, plan2 is still valid; both may still be called successfully, resulting in different calls from the initial transfer2 function.

Conclusion

So you can see that it quite easy to use Pyke to automatically combine Python functions into programs!

It also allows data within each Python function to be specified using a pattern variable so that Pyke can customize these values to match the specific situation.

If you would like to know more about how Pyke cooks (or customizes) your Python functions, see Cooking Functions.

More:

Statements

What is a statement in Pyke?

Pattern Matching

Explanation of pattern matching and pattern variables.

Rules

Explanation of rules, forward-chaining and backward-chaining.

Plans and Automatic Program Generation

Explanation of plans and automatic program generation.

Page last modified Fri, Mar 05 2010.
./pyke-1.1.1/doc/html/logic_programming/statements.html0000644000175000017500000002713511365362364022117 0ustar lambylamby Statements
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Statements

A statement is a statement of fact, also just called a fact. They are the bread and butter of Pyke. Statements are the data values that Pyke acts upon.

You might also think of a statement as a spoken sentence. For example, the Pyke family_relations example deals with sentences like:

"Bruce is the son of Thomas (his father) and Norma (his mother)."

But we condense the sentence down to it's essence. In this case, the sentence revolves around three things: Bruce, Thomas and Norma. All of the rest of the words can be condensed into a single identifier that identifies the relationship between these three things:

son_of(Bruce, Thomas, Norma)

We can give these condensed sentence structures any names that we want. In this case, I chose son_of. I might have also chosen "parents_of", which might conjure the following English sentence:

"The parents of Bruce are Thomas (his father) and Norma (his mother)."

Or:

"Bruce's parents are Thomas (his father) and Norma (his mother)."

But the son_of form carries the additional information that Bruce is a son rather than a daughter. So this is the form used in the family_relations example.

Caution!

Statements are not functions! When we wear our Python hats, son_of(Bruce, Thomas, Norma) looks like a function call! We might expect that it can be executed to do something and possibly return a value. But when we wear our Pyke hats, this is just a statement, or a piece of data. It doesn't do anything and it never returns a value!

Note that it makes perfect sense to have several statements defining the same relationship between their arguments:

son_of(Bruce, Thomas, Norma)
son_of(Michael, Bruce, Marilyn)
son_of(David, Bruce, Marilyn)

But this only makes sense if they have different arguments. There is never a need to state the same fact twice. Thus we can never establish two facts (two statements) that are identical. If we try to do this, the second one is silently ignored.

So:

son_of(Bruce, Thomas, Norma)
son_of(Bruce, Thomas, Norma)
son_of(Bruce, Thomas, Norma)

is exactly the same as:

son_of(Bruce, Thomas, Norma)

Finally, we see that the position of each argument is important. In our son_of example, the meaning of each argument is:

son_of(son, father, mother)

Thus, changing the order of the arguments changes the meaning of the statement. So:

son_of(Bruce, Thomas, Norma)

and:

son_of(Bruce, Norma, Thomas)

mean different things! The first statement says that Thomas is the father of Bruce, but the second statement says that Norma is the father!

Syntactic Structure of Statements

So we see that statements in Pyke are very structured.

Pyke categorizes statements into knowledge bases. You create knowledge bases to help you organize your statements. A knowledge base in Pyke roughly corresponds to a module in Python.

Note

Pyke does not allow knowledge bases to contain other knowledge bases, only information about statements. Thus, there is only one level of knowledge bases; and beneath each knowledge base, one level of statements.

So statements have three components:

  1. The name of a knowledge base. For example, family.
  2. The name of a knowledge entity. For example, son_of.
  3. The statement arguments. These are just Python data. Currently in Pyke, there is a push for these arguments to be immutable.

The syntax for a statement looks like this:

statement ::= IDENTIFIER '.' IDENTIFIER '(' {argument,} ')'

Knowledge Base

The first IDENTIFIER is the name of the knowledge base. In our family_relations example, this is family.

Note

You'll see that within backward-chaining rules, the name of the knowledge base may be omitted. It defaults to the currently selected rule base for this rule base category. You'll learn more about this later.

Knowledge Entity

The second IDENTIFIER is the name of the knowledge entity. This is the relationship between the arguments. You could also think of this as the statement type or topic. For example, son_of is a type of statement with three arguments: (son, father, mother). Or the (son, father, mother) arguments are about the topic son_of.

Arguments

The arguments can be any simple Python data value (numbers, strings, None, True or False) or tuples of these values (including nested tuples). Currently, statements are supposed to be immutable, so all of the arguments are immutable. The arguments relate to the topic, above, to make a complete statement.

Note

Prolog allows arguments to be other statements (functors). But Pyke needs to integrate into Python and Python has no concept of a "statement". So we just use tuples in Pyke because Python is very happy with tuples!

So the complete statement for our family_relations example is:

family.son_of(Bruce, Thomas, Norma)

More:

Statements

What is a statement in Pyke?

Pattern Matching

Explanation of pattern matching and pattern variables.

Rules

Explanation of rules, forward-chaining and backward-chaining.

Plans and Automatic Program Generation

Explanation of plans and automatic program generation.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/logic_programming/pattern_matching/0000755000175000017500000000000011425360453022353 5ustar lambylamby./pyke-1.1.1/doc/html/logic_programming/pattern_matching/literal_patterns.html0000644000175000017500000001451211346504626026624 0ustar lambylamby Literal Patterns
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Literal Patterns

You want to ask Pyke a question. The simplest questions are just asking "Is statement X true?". Going back to Pyke's family_relations example, your Python program might want to know whether it's really true that Bruce's parents are Thomas and Norma? So it would ask whether the following statement is true:

family.son_of(Bruce, Thomas, Norma)

Pyke would search the facts that's it's been told about and answer "thumbs up" because you've told it before that this statement is true and it has remembered that.

In this case, all of the statement's arguments are literal patterns. You might consider literal patterns to be input to Pyke. You're passing Bruce, Thomas and Norma into Pyke. And Pyke just answers "thumbs up" or "thumbs down".

Literal patterns look exactly like data. Thus, your question would look exactly like you see above.

More:

Literal Patterns

Explanation of literal patterns.

Pattern Variables

Explanation of pattern variables.

Tuple Patterns

Explanation of tuple patterns.

Matching Two Patterns

Explanation of matching two patterns together, vs matching a pattern to data.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/logic_programming/pattern_matching/index.html0000644000175000017500000001622211346504626024357 0ustar lambylamby Pattern Matching
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Pattern Matching

Pattern matching is a little strange at first, so we're going to ease into it slowly...

There are two aspects to pattern matching: patterns, which you write, and matching, which is what Pyke does with your patterns.

In a nutshell, patterns are made up of two fundamental building blocks:

  1. Literal patterns.
    • Theses are just data values that only match themselves.
  2. Pattern variables.
    • These will match anything. (Including other pattern variables, as we'll see later)!

And one compound pattern:

  1. Tuple patterns.
    • These match -- you guessed it! -- tuples!

We'll examine all of the above looking at how Pyke matches a pattern to data. Then we'll expand this to cover matching two patterns together.

And finally, a pathological question to see if you've been paying attention.

Sound like fun? Good! Let's get started!

OK, so why do we need patterns? The simple answer is that we need patterns to generalize statements. One example is to turn statements into questions.

Important

  • When you want a direct statement, such as to state a fact, you just use data for its arguments.
  • But when you want a generalized statement, such as to ask a question, you use patterns for its arguments.

More:

Literal Patterns

Explanation of literal patterns.

Pattern Variables

Explanation of pattern variables.

Tuple Patterns

Explanation of tuple patterns.

Matching Two Patterns

Explanation of matching two patterns together, vs matching a pattern to data.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/logic_programming/pattern_matching/tuple_patterns.html0000644000175000017500000001721211365362364026323 0ustar lambylamby Tuple Patterns
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Tuple Patterns

Tuple patterns only match tuples. They are written as simply a comma separated list of patterns within parenthesis:

(1, $x, "buckle my shoe")

You can also write a rest pattern variable at the end using an asterisk (*):

($a, fie, $b, *$c)

This will match the rest of the items in the data value that the tuple pattern is matched to. Note that the rest pattern variable is always bound to a tuple.

Examples:

  • matching (1, $x, "buckle my shoe") to (1, 2, "buckle my shoe") matches, binding $x to 2.
  • matching (1, $x, "buckle my shoe") to (1, 2, "buckle my belt") does not match because the third pattern within the tuple pattern fails to match the third value in the matched tuple.
  • matching ($a, fie, $b, *$c) to (fee, fie, foe, fum) matches, binding $a to fee, $b to foe and $c to (fum).
  • matching ($a, fie, $b, *$c) to (fee, fie, foe) matches, binding $a to fee, $b to foe and $c to ().
  • matching ($a, fie, $b, *$c) to (fee, fie) does not match because the data value has to have a length of at least three.

Hint

You can use (*$foo) to only match a tuple. It will bind $foo to the entire tuple, but will fail to match any other data type.

More:

Literal Patterns

Explanation of literal patterns.

Pattern Variables

Explanation of pattern variables.

Tuple Patterns

Explanation of tuple patterns.

Matching Two Patterns

Explanation of matching two patterns together, vs matching a pattern to data.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/logic_programming/pattern_matching/matching_patterns.html0000644000175000017500000002170411365362364026765 0ustar lambylamby Matching Two Patterns
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Matching Two Patterns

You've now seen all of the different kinds of patterns and how they are matched to data. Not too bad so far...

Patterns are used to generalize statements. One situation where you need to generalize a statement is when you want to ask a question. That's been covered above.

The other situation where you need to generalize a statement is when you write rules, which are explained later.

But rules also need to be able to match one pattern to another pattern, and not just match patterns to data as we have discussed so far.

So before we can move on to rules, we need to examine how one pattern is matched to another pattern.

The short answer is that it all comes down to pattern variables and that pattern variables may not only be bound to data, but may also be bound to other patterns.

Binding to a Literal Pattern

Binding a pattern variable to a literal pattern is just like binding it to the data within that literal pattern. Nothing fancy here!

Binding to Another Pattern Variable

When pattern variable A is bound to pattern variable B, they essentially become the same pattern variable. Basically, pattern variable A becomes pattern variable B (or, you might say, defers to pattern variable B).

Let's say that pattern variable A has been bound to pattern variable B and that pattern variable B is still unbound.

  1. Prior to binding pattern variable B to a value, it doesn't matter whether you ask if pattern variable A is bound or pattern variable B is bound, the answer is False for both.
  2. It doesn't matter whether you match pattern variable A to a value or pattern variable B to a value. In both cases, it is pattern variable B that gets bound to this value.
  3. And once pattern variable B is bound to a value, it doesn't matter whether you ask for the bound value of pattern variable A or pattern variable B, you will get the same value.

So for all intents and purposes pattern variable A and pattern variable B become the same pattern variable.

Binding to a Tuple Pattern

Because pattern variables may be bound to tuple patterns, the term fully bound is introduced. Asking whether the pattern variable is fully bound means that not only is it bound to a value (the tuple pattern), but that all of the subordinate patterns (recursively) within the tuple pattern are also bound to values.

Being fully bound means that the bound value of the pattern variable can be converted into standard Python data without any pattern variables in it. This is important when Pyke wants to talk to Python because Python has no concept of storing variables within its data structures.

Pathological Question

What is the bound value of pattern variable $y after matching the following two tuple patterns:

Tuple pattern A:
((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b))
Tuple pattern B:
($x, $x, $y)

The answer is here (but no peeking!).

More:

Literal Patterns

Explanation of literal patterns.

Pattern Variables

Explanation of pattern variables.

Tuple Patterns

Explanation of tuple patterns.

Matching Two Patterns

Explanation of matching two patterns together, vs matching a pattern to data.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/logic_programming/pattern_matching/pathological_answer.html0000644000175000017500000002073311365362364027301 0ustar lambylamby Pathological Answer
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Pathological Answer

This is the answer to the following question:

Pathological Question

What is the bound value of pattern variable $y after matching the following two tuple patterns:

Tuple pattern A:
((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b))
Tuple pattern B:
($x, $x, $y)

Answer

Let's take this step by step, matching each element of the two tuple patterns in turn.

  1. Match (ho, $_, ($a, $a)) to $x.

    This succeeds with the following binding:

    $x:

    (ho, $_, ($a, $a))

  2. Match ($a, $a, $b) to $x.

    Because $x is bound to a value, this becomes the same as matching:

    • ($a, $a, $b) to
    • (ho, $_, ($a, $a))

    Which succeeds, binding:

    $a:

    ho

    $b:

    ($a, $a)

    $_ is an anonymous variable, so it is not bound (or bound to).

  3. Match ($a, *$b) to $y.

    Because both $a and $b have bound values, this becomes the same as matching:

    • (ho, ho, ho) to
    • $y

    Which succeeds, binding:

    $y:

    (ho, ho, ho)

So the overall match succeeds with the following bindings:

$x:
(ho, $_, ($a, $a))
$a:
ho
$b:
($a, $a)
$y:
(ho, ho, ho)

And so $y is (ho, ho, ho)!

Note

If you got this right, you should really be using Pyke!

More:

Literal Patterns

Explanation of literal patterns.

Pattern Variables

Explanation of pattern variables.

Tuple Patterns

Explanation of tuple patterns.

Matching Two Patterns

Explanation of matching two patterns together, vs matching a pattern to data.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/logic_programming/pattern_matching/pattern_variables.html0000644000175000017500000002520111365362364026754 0ustar lambylamby Pattern Variables
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Pattern Variables

To take this to the next level, you might want to ask "Who are the sons of Thomas and Norma?". In this case, you are passing Thomas and Norma into Pyke, and you'd like Pyke to pass something back out to you as part of the answer (in addition to the thumbs up).

Pattern variables serve as output parameters. They start with a $:

family.son_of($son, Thomas, Norma)

You can use whatever name you like after the $. Pyke will answer with a thumbs up binding $son to Bruce. If you don't like that answer, you can reject that answer and ask Pyke for another answer ("nope, try again!"). Each time Pyke finds another son for Thomas and Norma, it answers with another thumbs up and $son bound to a different value.

If you reject the last son of Thomas and Norma (or if Thomas and Norma have no sons in the first place), Pyke will answer with a thumbs down.

We say that Pyke binds Bruce to the pattern variable $son when it comes back with its first thumbs up. When we tell Pyke "nope, try again!", Pyke must first unbind $son before it can go on and bind it to the next value. The "nope" part does the unbinding, and the "try again" part does the binding again to a new value.

So at any point in time, a pattern variable is either bound to a value or unbound. If we follow a particular pattern variable through time, we might see that it is alternately bound and unbound many times as Pyke tries to find a suitable answer to your question. Specifically, when Pyke comes back with the final thumbs down, $son is unbound.

Anonymous Pattern Variables

Suppose we want to know who Norma's sons are? In this case we don't care about the father. We use anonymous variables as "don't care" placeholders.

An anonymous variable is any pattern variable who's name starts with an underscore (_). The rest of the name doesn't matter and just serves as documentation (and so $_ is all that's strictly needed).

So "Who are Norma's sons?" looks like:

family.son_of($son, $_father, Norma)

We're giving Norma as input to Pyke, and asking for the $son as output and telling Pyke that we don't care about the $_father.

Anonymous variables are never bound to values. (So you could say that they are always unbound).

Pattern Variable Identity

Now this is very important, so pay attention! The same pattern variable name means the same pattern variable. Thus, if you say $son twice, it's the same pattern variable. And, a pattern variable can only be bound to one value (at a time), so you mean the same data value must appear in both places.

Note

This does not apply to anonymous pattern variables. Since they are never bound to a value, each use of the same anonymous variable can match different data values.

So if you wanted to see all of the sons with the same name as their fathers, you would ask:

family.son_of($father, $father, $_mother)

Note

The Pyke family_relations example never uses the same name for both father and son because that would cause confusion about which of them was the father to both of their sons and daughters.

In these cases, it modifies the name of the son to make it unique.

Thus, this question would always fail within the family_relations example...

And so here is the complete explanation of how pattern variables are matched to a data value.

First, the pattern variable is checked to see if it is already bound to a value.

If it is bound to a value, then this bound value has to match the data for the match to succeed.

If it is unbound, then the pattern variable is bound to the data value as a by-product of doing the match, and the match always succeeds.

And so pattern variables only "match any value" when they are unbound. And in matching that value, they become bound to it as a by-product of doing the match. Once bound to a value, a pattern variable will only match that value (much like a literal pattern).

More:

Literal Patterns

Explanation of literal patterns.

Pattern Variables

Explanation of pattern variables.

Tuple Patterns

Explanation of tuple patterns.

Matching Two Patterns

Explanation of matching two patterns together, vs matching a pattern to data.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/PyCon2008-paper.html0000644000175000017500000010071711365362360016672 0ustar lambylamby Applying Expert System Technology to Code Reuse with Pyke
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Applying Expert System Technology to Code Reuse with Pyke

PyCon 2008, Chicago

Author: Bruce Frederiksen
Date: Fri, 14 Mar 2008
Web:pyke.sourceforge.net
Copyright: © 2008, Bruce Frederiksen

Abstract

This paper explores a new approach to code reuse using a backward-chaining rule-based system, similar to prolog, to generate a function call graph before the functions are called. This is compared with current solutions which build the call graph as the functions are called.

This approach is introduced through an open source project called Pyke (Python Knowledge Engine).

Finally, the initial results show that the utility of this approach far exceeds expectations; leading to something more akin to automatic programming rather than adaptable libraries. A call for help is given to explore the capabilities of this approach across different domains.

The Thinking that Led to Pyke

The Need for Code Reuse

At one of my contracting jobs, they had many clients running essentially the same program, but each client needed minor code modifications. Their objective was to maximize code reuse.

What is Code Reuse?

The first question is what does "code reuse" mean? And the answer that seems most logical is function reuse. Where code modifications are required, a new function can be created incorporating those modifications.

Then the remaining task is to bring the proper collection of functions together for each client.

This gets more complicated as several versions of many functions will be produced for various clients that are all available for reuse by the next client. So it's not simply the case that there will be one standard default version of each function, and then several one-off customized versions that each only apply to a single client.

The result of this function combination exercise is a function call graph.

Example 1

Let us imagine that we start out with two functions for client1:

images/PyCon2008/client1.png

And then client2 comes along.

Let us first suppose that we need a new version of function A, but can reuse function B1:

images/PyCon2008/client2b.png

This is easy in any programming language and leads naturally to the idea that the functions to reuse are the lower-level ones, which can be placed into libraries.

But now let us suppose the opposite; that we need a new version of function B, but can reuse function A1:

images/PyCon2008/client2d.png

This is where we need help.

Current Solutions

The current solutions are all run-time solutions that trap the call from function A1 to some function B and figure out which function B to use when the call is made. For example:

  • O-O Dynamic Binding
  • Zope Adapters
  • Generic Functions

Current Solution Limitations

These solutions are all limited for the same reason. Let's look at another example to see why.

Example 2

Real world programs have many more than two functions, but we can start to see the limitations of the current solutions by looking at a three function example.

We start with one client and three functions.

When client2 was added, it could only share function A1 and had to have a new B (B2) that needs a new function with a different call interface than C, so we'll call it D1.

Then along comes client3. This time things are looking up, because all we need is a new version of function D:

images/PyCon2008/client3d.png

Now let's see what happens when we want to call the program for client3. We know we need to start with function A1, since there is only version of function A:

images/PyCon2008/client3e.png

But at this point we have two choices for function B. All we know for client3 is that we're supposed to use function D2, so we're left to guess about function B. So we try the first one, function B1:

images/PyCon2008/client3f2.png

It's not until function B1 tries to call some function C that we discover a problem.

This is where the current solutions break down.

Certainly for this example, it is easy to imagine a developer telling the binding system: oh yea and client3 is going to have to use function B2 as well. But more realistic call graphs are much more complicated than this; so the developer would have to specify which functions to use going back many levels.

And then when there is a change in these upper level shared functions later on, it will affect the call graphs for many clients.

So the current solutions don't scale well.

Continuing on with our example; what we need to do at this point is back up and try the other B function:

images/PyCon2008/client3g.png

After doing this, we discover the solution for the final call graph:

images/PyCon2008/client3h.png

What's Needed

By looking at this example, we discover two things about how to solve this problem:

  1. Do function selection prior to calling any of the functions.

    We can't wait until one function calls another to figure out what to do, because we may change our minds!

  2. Use a standard backward-chaining rule-based algorithm.

    The process of first trying function B1, then backing up and trying function B2 is exactly the process used in backward-chaining rule-based systems like prolog. They call it backtracking.

Applying Backward-Chaining to Code Reuse

The next question is how do we use a backward-chaining system to produce function call graphs?

Let's examine, conceptually, what a set of backward-chaining rules would look like to find a solution to this problem. Then we can determine how to turn this into a function call graph.

The following diagram shows goals as dotted line boxes around the rules that prove that goal. In this example, some goals only have one rule and some have two.

We also see how rules link to other goals. For example, rule Use B1 and rule Use B2 both prove the same goal: Find B. But Use B1 links to the Find C goal, while Use B2 links to Find D.

images/PyCon2008/bc_rules2.png

Now we can follow how these rules would be run by the knowledge engine:

  • The whole process is kicked off by asking the knowledge engine for a solution to Find A.
  • There is only one rule for Find A: Use A1, so the knowledge engine tries this rule.
  • Use A1 needs a solution to Find B.
  • The knowledge engine tries the first rule for Find B: Use B1.
  • Use B1 needs a solution to Find C.
  • The knowledge engine tries the only rule for Find C: Use C1, which fails for client3!

The situation now looks like:

images/PyCon2008/bc_rules5.png

Continuing on:

  • Since there are no other rules for Find C, the Find C goal fails.
  • Which means that the Use B1 rule fails.
  • So the knowledge engine tries the next rule for Find B: Use B2.
  • Use B2 needs a solution for Find D.
  • The knowledge engine tries the first rule for Find D: Use D1, which fails for client3.
  • The knowledge engine tries the next rule for Find D: Use D2, which succeeds for client3!
  • The Find D goal succeeds.
  • The Find B goal succeeds.
  • And the Find A goal succeeds.

When we achieve final success, we have the following situation:

images/PyCon2008/bc_rules8.png

What remains is to translate this into a function call graph.

It becomes obvious that we want to attach our python functions directly to the backward-chaining rules:

images/PyCon2008/bc_rules9.png

Pyke

Pyke KRB Syntax

How does all of this look in Pyke?

Pyke has its own language for rules, which it compiles into python source modules and then imports. This gives a performance boost by circumventing nearly all of the inference engine interpretation logic. It also makes it very easy to embed short python code snippets directly within the rules to help out with the inferencing. This keeps the inference mechanism simpler as it does not have to deal with things that are already easy in a procedural language (like arithmetic and simple list manipulation).

The Pyke rule source files are called knowledge rule bases and have a .krb suffix.

We'll continue with the previous example here.

First, let's look at the rules before we attach the python functions. Here's three of the rules:

use_B2
    use find_B($client)
    when
        check_function($client, B, 2)
        find_D($client)

use_D1
    use find_D($client)
    when
        check_function($client, D, 1)

use_D2
    use find_D($client)
    when
        check_function($client, D, 2)

Note that Pyke uses a $ to indicate pattern variables (anonymous pattern variables start with $_).

The check_function goal checks to see what version of the indicated function should be used for this client. If this is the incorrect version, it fails. If there is no indication for this function, it succeeds to allow guessing.

Attaching Python Functions to Backward-Chaining Rules

Here are the last two rules with the python code added. The rules have the python function attached to them so that the function can be returned from the goal as an additional parameter. Because this parameter does not affect the inferencing process, it is a hidden parameter.

These examples just show one line of python code, but you may have as many lines as you want:

use_D1
    use find_D($client)
    when
        check_function($client, D, 1)
    with
        print "D1"

use_D2
    use find_D($client)
    when
        check_function($client, D, 2)
    with
        print "D2"

Pyke calls the function call graphs plans. This terms applies to both the final top-level call graph, as well as intermediate call graphs.

Calling Subordinate Plans

Now we do the same thing to add python code to the use_B2 rule:

use_B2
    use find_B($client)
    when
        check_function($client, B, 2)
        find_D($client)
    with
        print "B2"

We have code for the B2 function, but how does it call the plan returned from the find_D goal?

The most common way is:

use_B2
    use find_B($client)
    when
        check_function($client, B, 2)
        find_D($client)
            $$()
    with
        print "B2"

In general, there may be many goals in the when clause that produce plans. Each would have an indented line of python code under it with $$ indicating the subordinate function. These indented lines are combined with the lines in the with clause to form the complete python function for this rule (with the differences in indenting levels corrected).

But in this case, this would mean that print "Dx" would be executed before print "B2", which seems backwards.

To call the subordinate plan within the with clause, there is an alternate mechanism:

use_B2
    use find_B($client)
    when
        check_function($client, B, 2)
        find_D($client) as $d
    with
        print "B2"
        $d()

The as $d clause stores the plan function in pattern variable $d rather than adding a call to it to the with clause. Then you can decide in the with clause whether to call it, when to call it, how many times to call it, etc.

Note that pattern variables in general can be used within the python code. These are replaced by their final bound values (as constants) after the top-level goal has been proven. Thus, the rules can also be used to determine and set constant values within the plan functions to further customize the code. This is the reason that the code for the attached python functions is placed directly in the .krb file rather than in a separate python module.

Some Final Points about Plans

  • Function parameters are specified at the end of the use clause with an optional taking clause:

    use_B2
        use find_B($client) taking (a, b = None)
        ...
    
  • A completed plan appears as a normal python function.

  • Plans may be pickled and reused.

    • If you add functools.partial to copy_reg.
  • You don't need to import all of Pyke to unpickle and run a plan.

    • Only one small Pyke module is needed.

Other Capabilities

  • Pyke also supports forward-chaining rules:

    fc_rule_name
        foreach
            fact_base_name.fact_name(pattern...)
            ...
        assert
            fact_base_name.fact_name(pattern...)
            ...
    
    • Pyke runs all of the forward-chaining rules whose foreach clause succeeds prior to running any backward-chaining rules. Thus, forward-chaining rules can not call backward-chaining rules and vice versa. But backward-chaining rules can examine facts asserted by forward-chaining rules.
  • There are different kinds of knowledge bases:

    • Fact Bases:
      • simply store facts.
    • Rule Bases:
      • store both forward-chaining and backward-chaining rules.
      • can use rule base inheritance to inherit, and build upon, the rules from another rule base.
        • But only single inheritance.
        • Thus each rule base has a unique root rule base.
        • All rule bases that share the same root form a rule base category.
      • allow selection of which rule base(s) to use through rule base activation.
        • But only one rule base per rule base category may be active at one time.
    • Extensibility. You can write your own knowledge bases. These might:
      • look up facts in a database
      • ask users questions
      • probe hardware/software settings

Initial Results

After writing Pyke's younger brother, it occurred to me that backward-chaining could be used to automatically figure out how to join database tables together and generate SQL statements.

And if the backward-chaining rules could see which substitution variables are needed by an HTML templating system, it could automatically generate the SQL to get these data and build the code to update the template.

It seemed that it would no longer be necessary to include anything that looks like code in the HTML templates. The graphic designers could just add simple attributes to their tags and the backward-chaining system would figure out the rest. This would mean that the programmers don't need to modify the HTML templates, and the graphic designers could maintain full ownership of the HTML.

I had a WSGI front-end that would simply assert the data passed to it as facts.

The forward-chaining rules took these starting facts, parsed the cookie information, form information, browser information, and url, determined whether the user was logged in, figured out which client the request was for, established all of this as additional facts and activated the appropriate rule base for this client.

Then the WSGI front-end simply asked for a proof of the process() goal and executed the resulting plan function which returned the final HTTP status codes and HTML document.

For a page retrieval (vs. form action), the process goal used two sub-goals:

  1. A format_retrieval goal that read the HTML template, and built a plan to render the template, given the needed data. This goal also returned a simple descriptor of this needed data as part of its inferencing.
  2. A retrieve goal then took that descriptor of the needed data, built the necessary SQL statements, and cooked them into a plan to execute those statements and return the needed data as a simple dictionary.

Then the two sub plans were combined in the reverse order, to first retrieve the data and then populate the template, for the final plan that went back to the WSGI front-end.

The Pyke examples/sqlgen and examples/web_framework are simplified examples that you can look at.

Now, as it turned out, the company had been running without a president for quite awhile, and had finally hired a new president.

So just as I finished the SQL generation logic to handle unique data (vs. multi-row data) and was preparing to show some demonstrations; our new president, coming from a java background and apparently never having heard of python, decided to cancel the project.

End of contract!

Code Reuse through Automatic Programming

The fundamental lesson learned was that this technique ends up being far more capable than what I had first imagined.

More than producing adaptable libraries capable of using B1 or B2 at some point in their call graphs, this approach leads to something more akin to the back-end of a compiler -- except that the compiler front-end does not target a textual language that needs to be written and parsed; but is rather a simple observer of already known facts:

Show me your schema, and I'll build your SQL statements.

Show me your HTML templates, and I'll build the code to populate them for you.

This seems to change the whole concept of code reuse; elevating it from the realm of static libraries, to the realm of dynamic automatic programming.

Going Forward

Thinking that others might find this useful, I've re-implemented the underlying knowledge engine from scratch, with numerous improvements gained from the experience of the first attempt, and made it open source.

With the backward-chaining rule base system, many applications are possible:

  • Complicated decision making applications.
  • Compiler back-ends.
    • The .krb compiler uses Pyke.
  • Automatic SQL statement generation.
  • Automatic HTML generation/template processing.
  • The control module for a web framework tool.
  • Incorporate new custom functions into a large set of standard functions, which may change the selection or configuration of standard functions in other parts of the program.
  • Automatically re-distribute the modules of a system over different programs and computers to meet a wide range of performance and capacity goals.
  • Diagnosis systems.
    • E.g., Automated customer service systems.
  • Program or library customization for specific uses.
  • Instantiate, configure, and interconnect networks of objects to meet a specific need or situation.

Up to this point, I've been flying solo. For this project to move forward to fully explore its capabilities, I'm going to need help!

I'd like to see several early adopters run with this and try it out in different domains. Pyke is in alpha status now and is ready to start to lean on.


Creative Commons License

This paper is licensed under a Creative Commons Attribution 3.0 Unported License.

More:

About Pyke

What pyke does for you, its features, steps to using pyke and installation.

Logic Programming Tutorial

A tutorial on logic programming in Pyke, including statements, pattern matching and rules.

Knowledge Bases

Knowledge is made up of both facts and rules. These are gathered into named repositories called knowledge bases.

Pyke Syntax

The syntax of Pyke's three different kinds of source files.

Using Pyke

How your Python program calls Pyke.

Examples

An overview of the examples provided with Pyke.

Applying Expert System Technology to Code Reuse with Pyke

Paper presented at the PyCon 2008 conference in Chicago.

Page last modified Thu, May 14 2009.
./pyke-1.1.1/doc/html/images/0000755000175000017500000000000011425360453014572 5ustar lambylamby./pyke-1.1.1/doc/html/images/backtracking.png0000644000175000017500000006541111346504626017736 0ustar lambylambyPNG  IHDR,bMr>sBITO pHYse IDATxwXǿET@c/F #ŞXhb45jTlwb J{2?v9N8~ofwfB\tX#daaY5BaeMv2nىTg鯷Mc|[OOզ596?9zاT/)ɱATYL$+BX+oD,w?E׍*w8&HuvwO^^ }B6x˄7] 48Sr(UJ^@ʊk>L$ps:,BTVn|r S`pLFW\h!0Utڵƛ7..DXEj{'G?y =?3,co-86|{ڙ8hG̴ǻ\~@\C 5e/Х[~|O8ׇӄkjAyO$&,I` ]XZ ʊ.ڒrl,17/*_͛8NnN<)}+"""55Uq `ݶu߿/~7nhB ݻ, sZHsqjdFCu W9bG^Z(raya{׍sm>(=p}CIEԽkkr_xD$Baq|p# )bƍKHH ܺuybzkڴiׯWp,rёWn߾}fu=w\nnnxxiJ+C}(vbԣz4>:PggG=] ܇FyQggdBHtfOz} ٺՉgn_nM AFXW#ܑ00p$&_GC1h۟8qdeeXebDVVIK'_t3eɘ% WW,\c<Wւt?9ӧ#$"|}1v,]6WXfDjժ%Kzx:>>\.O\1Y mNJMqG\U6BYۻϞǼ{ȻMnF}퍬D2 kٝ9s}Lkn`od=@}fJ^;OO[Zk2軆l.cQ9VCf>\}<kCwRC=z|8_Zp{YwtPz|J\a)~~eIcEw#_ /hexژ5WX6š1BKB݋|!SudqMPb:S>B!rMJ CԿo$J+\ބC8ڪ!޽Ý;x{[4B)OO\?A:gRᮍ㾗w=Hk6o&VV!ݺA,,S!!C&}_l2a#dKj/+`nbkKҺ5)p MIH$)`15꣝>Q_ڒFGH;p*HVȋb8@!nIS6s!-ZHv6IM%摱cEȰa$!ő RR{Y#=DFLz5)p #UDB<<+O\\ݻ_abZG SdMp_'9L0Td2N=}{r!++R̩+HݺtҒ #^:B D֬!&&EmԔ̘A]+t}#y~IE Hl2SSS]]]WWtEAcI |wޮ6RS}|; *'t4mJbdv9{zn݈~Q_">nKj/k%iذCHNEmΎ,]JJą.lFf͚>}DFFO4i֬YODg/yRS[sEyLK*%H-#vv$Ow`eEN"D"!_ Kj_#$|H!_ٞYySO oYcAXsQqrEyʚ]}8[LĤ06 uS\DrsIH3 $!Ǔ!C Kj%r=rJmO[Cnݒ{ϩKz>+PYœu^uݡ>/XS,ruBQgIFD(+A{{kWYYdxKڵD (#L ͏# ҏ-E,͛7[nnnrP,] jx`F!EZ^;(;vspMBAxoUp`g3gP>;f 1|8~ܽ%8GX]c`3t)LέKmJ oݴYHe _^ %^]7M,XTfl_*CRТ'|yI46Z-1o^^e,Yя)|So_wsEy"4'#_f sS2<^?^&H|^ hzl샢w#J5@zEt#$5>:Pwgkc! ͩH, FIKܜ;-/)=wYYX-[Up0kbbIWhZP"xݸ[aR$K$xQy 7F5 @B$}OyEǩ0+$^koȫW KR5<phw{jp9? Xp!4COLːIz^faNGIN\ĴFsFh 8qs)^`۲k6*rMX/eVQѼ<̘ɓi 3ތY`q|dge&8dc_?W.رb&|G?r8S)]ßjK=,,ai#5/$۪M 5x&}#F 'p^5Z&&ȀKaѻ7mWLN_^ :+_,rz}A Z 9vMST?"ش 'C(q"Sy-Э11D۶eȲŐ{"yˆl*E:En=煦}mWuzlQFBSr{7OgF 05E@LL:Y}ۙ$a'06ÇFN P6f͵xZJD6PK۰~h:CRpPjx8=SL*'LO C6UA}E Ol.-p@; !=oo5iQG´4 OH5>668} >}„ v0۫XߐȤ7 c!Q[8nMoG<݈QqliǯZpq-ϟI*Pv<<`cVՊlj1={;tesjfM܏|Ɇ+@}g~C>^#cZrTG} xWٕel^ woJo^+V޵{pu]ny *w7syLR2¼<//hL3bPio*|vr´i;A=0 UMhms {#+b8%.GLdg_?.Mܽڵ+Dl^ƍCC|\KṫXZv=]GV$iqIz @Zw40gZTTH=i tvaT!4C::_5+͛hZZ–33+dd`͚ZgjQS[3RfOj^#u (5 >G1- T8қB4!{:"{eJM,[[;GrsIx86M[[:j`96r۝Fy!K3t 9=iEPF/ؐ=j)In`ٲ5پ]ARdr@HLVIwg뗱>Gs :CCSuT y;y3o:up{Ԉ/н; @_DgBq.ڶ .)?K9f u…B'%Ww*Bh_׏:;a=Deƹ\,6R,pptP+,)5U07r޸A+ϩ<Ν+jڽߨ`ڽ݉F/1P+*g$|c@"T)g2rr ?χDRj_| FOO: FrkC ?^Q~4&*hbDFLS3%ullaMT {eJM,[vw'>hAn*&M*3bq%O?lj6URcf4U/^֝ ީ0-Fτy+,8lZnyZzbCxG0C%#6N]@Qz,^:u`F$'s̈Yji:<CPU<>´"aN CVlڄ5 G`l 57V?:α2`ۻFmQ7L+TfF/Osбv(ܳ<mݗ? &70kװ{7`R_@v6"= uOk\ίjdM;'0³prB\\xays G/Kɓgz{{PUvWhF@lmˬ(AN -M&ޤk,, ?l cLJE/hWjR.667WU]PYb,}Z^ vU Z|C.X ''Cp08k3j(8ƒ/v=7rt1"τtZ/--Wc T={jJ dbT,:djB^'h -}$#2*`!5V-ڪܹs+ WxãtGX] G΅gDWjnKS?,mk(޹CnЀ^bܱcGfʹ*0`*rt _,\jwE8mןͅɹiU,\τB!4AX\<}5t;\\nK*x8:<ѸQ%Dw\އ񗩁zj[UarizA ,C\3r: yW6g7;szD,7ʏh޼܉4RgG6P@9' A6x\.ˑ'6'GH qAoR5/OIMr7eݩjΫHd }bj~kV'pV@L$jFR2wj8"t* H鉮]AZw١.#!B __ ȚHZ NxTI6oy{A+9.\HǙߚc+@(Ć ~qZO-wTv;dPE #FT$&F#!BФ ЊJDz%䤘t&La;|J=]GB+Wu54T.Xq8t2fOK5";Y* 6T$_wZz}*uՃR1Ǽ;Ҍp6: 'cx Pl(U8x⺪X-!'E+4/_p"tVT{bp!*M.u+oVPSkEsӖ=WqnFkb?_geQ%-Z`@ޕ-`DR|dBqq^~sЬc}žK6|<cpE,ZD*p?Qp!UMaxWfU-5G'jdu*)/]B\L $b ZSԡ9٢\ܽ~;*O#\dC8oUN?FTyTþxIUݖl0য়Tu2JdB cR&.[JD|.oCB"~tc],Zt5k v_[0K%}`_=ۗ] ƌ}}P&|<p#C?N}z Ő{ opXQA= [ 6;q`GS2'4,f$?^ƃ4}Y-mC!}.NutZlX3])'WmXxLVB;,eKV-$$5dXY!'-Z׷ⲓ?zU*eío &b3~?\EʑfPX }|#GIԮM/gW`MZb2Y­"3Bj(@Vc8W違ְAjkH10X]_ 9qj9wM@~060`UM6v2ܵi)՜8u޺:13èQWVS<6»w8$SnpfF Z6ֲ=|H *G2FHSSda]OYrPabÇWvqҾK{F P38zTqÔ˗ ;]z599RU3z[wncۑʹ+#|.ՃIɓ' Í8qRU?VBh"cO]UI0v,3 m۶-RRFF5keP! 89V-c)66JC邏OIM_~ @KK̙3?|y)nNo5@z~% 0"Хjűd o/%:hE /5I/2QteYJ:H:%3tPdjX¤# ?N9#"QXbРRT$u?ѱWk)"*xO4 7nwwwFGvRc-*vP`ү5#7!iBΘ1IItXЫW/kkkqqq7oiCcX*n_%6 ۷`dFT/] С04ClW͊p&ZJ42BHQF"Ii10&Lx\ [An?u>1BoWG;bdҗBhMﻣ *Ds[={IF\HIy37KVAػXup󓈲U!֭xbg!7E &d<2v6rݺ9Kɴi-Ҹ1;// $@RKK+11QӣYe໹WB;eH_O1ɹiWP׫=/M̈M˜ro圇*_lЫsⳓ㲓\Ms!.#;x!$"~E%:X#/y=!deu崑$<'%E试%f"oN[ǎե`~n۶my|Gaߞ/)b?{xz$#;; 9r+)9*f%ZVq^ J X\X -|hsP,*!\g[R&^@00}RΟݻh66THǃ˅H$xAzIM")rGcCFi}rԲg[-"=;vu8r-cؔ[}Idu%>;Yq}lvU3TVXp;l3e X";YYSHj*HHzz[rGB)7nz":LJx< @x%GzTHjĠ3-^DgƗ !Dgg,!=2'Ȏ)%P/qOQRH^d( S iNXP\͛+o,"'::AXM+@~>$l `f88:f1xL:H e_m`l}m-ݖ'Km B!I"X'm~uꐝ;PH k.~ĥKKKKHX,.Ȁp%9 $VSjnWCci4yw8~sRsHl6l̓\G~> 0{6_е+&N,~teĉ`q\Ăc.r9.4H\o-񼢟`e0Ю;>ѥ&O毿V/ɸq b$+ѳ2?.>>^Pq' @h?q@PRcPa=>TSU.ܧ.:}[ra`/i &Lcת;wr%ֳgfφsC݅B!]6nXI6lصkWBݽf?4a7ZVܒ/_"k!=mG^ѫD`qmoa[~kb~\Yl5m>T~!6ѷ4U3LT 诬n]ǴKZ"8<&ꕻְnK~y7rG߶7 ҉rA20`Sj  L[p7SU15K%\K512Y,]z2qclpq뱒rlnm66L)+<wtþb0;Q/˥Y#T[Μ#yNI ndMwqaفxewtˊ~fṰ|>FdZrtN~k&-j78ʫ\P>M)!):uҸe$}n+Ӧ'0yYP<$\.qYг'RC:d%$%Qe;$Mt8J,j/]lYuhavnEW~6Oy%|I@QƢ=3i$#?*HKk86@%QLl6i-ꅝ%5M(xnq\.bbpMKWFغ5y}fF!qF̙l"(11ӧ[ӦM^d72#e zH,h9fAk KpjiH-b<XNvڅ}U$:z>?|:Q=OflVC5D-E}DBgfQ-q ʂ▭[C:bh_C!///00pڵ[<| @7(-bY4ZՐj7'%HujЅ׫ZTM`l^TJM@x@` :wƦM5jԨwIs}/M=G?ȳw]qNʌF^ĚW>${&p_;z&j_{x31'%oƝZYaNAȏ\qWc4S9ﲔȻڄ,R6Ҳ5پ+<9ّ&K,!ffWdI1-PvǏ=Oboۛ(oq{-pm }iww@rXV68Sr(UJۓ]J IF'-Z[J9˗]!iSr<-4%uF54il.Oxo|~\BN\ozlVNo?#w:;[CF <^on{wLAX"^ﵯ~;[:1L-9)VngyQg,.*Yl+u28;n)A;th B!ȦãK)X ) --VZ,,,erW>rʽTA8a]'279 |7M 8tT7DGGtU:84iӄk3]9m}{Gj7Kzw!-njW%X*̹sh#%= r8psWQzeܼ}O{GwF짜w_99: 4KY/POޑVwbcѣogdD{Zc IDATt^n!/_\`… me{th ,K3zeEX0$K|.'O陾}Y|>Mjצa2w.%FF $&FAe[111TpQ9JWX3*r:5i~] M\͑HpuEr24l؁k,wk֠^7iÇV҄i4r@m怣* s< W0eѠ>}BӦxٳ4jh̘1'Nu#(8ԡ'/CVƑ#S!pv/F'fa|-7ocG ѪɓrL~jy41XleP4V'G%;m=l"ptpˬ,lRXJi Ç@=,-n3@LVª;Ѡm9r2憯T)?@_A%S~.4rXyqe3Bmm,_X( =C,ww4h uH~gCyEP QQ$Eh cZ"ujЇP)(5FE \xzb~hYZkY~&ZԎ@HS&d?QI۷ƢŋHvAGi5Ǧs:\ Ge`M;P9Ҕ1B6DV ?#7M4@8tMҝ}zʎCnΞU4?9R`LTc0EΊ ͠ػ#`A6K<<` nͰe02hw)7f3Os-}5}v _2 Y/ªUS6oFu,D,;XZW/(Mok:]eψ`mH{k2_| *V4Vн$,ӣ>I8f 5v"*Kͩ8i 03dHBŒ@j`h0c)eɎYPF/|fZZ ՠ ֖vlRQ# ZZL)'M J#0k=bTODrr妮NmˮR@`gа Ur88~l|}+KB&&T!1'Y%j§0|.H%#![[lb1ƎeUIp0]d#4ҧ T~v0FVZ<:wz?l,*!`[QBLѼ%#f AUa=ߏU'KF]f5i5BF䋅Zn(Tz5yLvj̤ .)[K:B.Δ@viVGPa3BFuTl:(TwSJ!_~WWv尢HBCcҀr  kJ+Uj_ʕ*! >j ai_kw:yBS%b:9kt!o_c лWsK=ͫ+o>Ry;[*V%?+kio^Wӧ4|NIM45, yMoSʟ24͛h?KY_ݻ͛HԤ\yCX桌P'h^;Jf{1xFêj&L|:욆p&6(~ߺ-&'Qڙ"RUK7x WbU)Gӱ-lєlQ:} z3]<~,.V[맖xၾ}Wug\/cǘVS&?5_^R޾E頚,YzrE{: LbZZp/._KQT˗tN__jUā$Ì5kV2j* 4RUd}}xx+` ܙ_Z:A޴ L)o>z ŪÔ#x|yEyFnm:tb"\\p͞ccgZ(w1P+&+aE?r zGÐ:'TMW .̟B!lZ Es2\xfŐ{TMhZ뿈$b8zYOrc k4q䕅Q.[b.p8 ѣ%DE=J:tˠA{tҴ,am&lЇAmjob8 Ee)!Erm+ڕaڣeX*We풻!r#TDDwM|~|Jsj;|w>~k~S%AXЀ!^ - |4zDb"Ӛ4SSE_'KMOO߾}155ٳ{XXٲe\n> ߱=ˎew!!Ӳj@PQ^a (-Ŷm)Mtyʕ*?zAO!@mr_gsj Q:*T?#&Dfa!fȑ2 'uU6ѣѨ1b>5$ZOи1BppYB̝/HI)ҥKď?E6(Y :@J$9o)j! r4<3}v|D"a+!+WJ]ݵ+ 'ń#ɓeĔO'@@يVQ\L<<*w[ W9Or`Sx‚ cgC]S1_Gvw8,X".]3c)hIĿF{{M4_X] κ^' A[Qk n>܈ Rő+͛LkRywEELFLt۶d.LwEYYãu]9А2aNN2P\Agn{pS_p%H+k|9"79=4nۂ|a˫\]qF(I/Fia=|e¨j ! ٴW^'$eѣȀȨ|%S>MF"ffm꟯#GɄ %ÇWFSť_GLPTʧτ %%]:τ/xU~ʅ9NxqS2kQ郘xz 6ms'U/1 +K#1j|y]"׮]Sjƫ)Gv3V$.JD$7-ƿX$xW1'GϿGJaz^IᲛ*h$uIyoEį..S>f[L~--/xoTKVɦMԴتORXȴ2ˋwEA_iοloߌ<#:ۡc/&$d<n+?=V~:g}>{XAX"w{8ؼ͚al@>*/l޼Yo/ƏXhȉmL;p8B໮i:hI˖0:Eg\Dɓ4YQK.;wOh&[hjJ, qqLS=t # R?{-CkԮ:ݤzwhخB5!DB._&Ǘ E/sp [oNAbqqE- R[Tݺ;tCP;~r ݄}K֭#XQ[ @v"))L _sHBBbjj֭[]]]۵kr4iңG~t^Mn׉:}T!SyKzs>;9CyQӧ'>xGGgg œ !G! nPEˈ|j@,\{`@kGNӘaN}ct̄D8|AA dcƠo_L^e!<OEE8بzϺaܴF-TF !Wo(_$6|U6>^SZ"8XFZkKKꅡC1dWSܺkꑳޮ{?$ܫvn `5v !aar/cuuб#uCϞP "de{ddT==8:W/88`yHн;R ;c/w]'׮NK:+oo43̐_&T7o'OxĨj'[[ؠS'8866Ж1L fW>D${։rikP f aMXk22-Ґ"'JX˅!ZLLо=ZF˖ J\ŰógE||Y>1t/;82b #TM0y‚ڜȨʢ-ZE 8-"JJ Td^pBO07;Iǎ39)aH1μR؜)%cr5v 3a$9# OxXia,soJTOX`?ʨyP:i(CXiHIaցwtƝì vRW9P5aԔi̴ [HfZN]2S:e|aMBd(2cY//1nY~sH"<*ym`MȢBt4i  J Ǽk@քtaOk5ds^aZK@@FnKd YTKVeR/WwQVuf>Y+~:(&`5jo߾}q2ʜwD<˗/rW\ H+Z4Ԅ} @&?*{esEn`ɜEKK+<Ѥ~U\pnSq&L [^*84djdMXAF*,%_s;Z41e, *MחV&0D*'`MH,j9LAYz-(󑩆{8;K_&mgؘZRmeMH###߫Sرc}||ݻw&0PxEjbRjz3Nt705<=]5!o IDAT]̤W^СC_H߿_,+W>}UEB$hdXjF]1KZq"ou_93NCYHG^tIĉ²D"Q~~Ç}||ƌ`PXNpR8c84qS$Jk/5!]|<QBg*RH1L}bnHfswʁON/_{9J)Stڞ4_7mڤ5#]} *fr '45 pS.L e˖EEE 4NcbbT, ,&CNݙVT&JD-86퐜X$0݃V3CKoƜS*iki?gZgBp?#WT{/9RH Z>CjfhkB6}xtf%ۨ!s1J>]-0jFX !]mNos; !1Ϩc]s&jy[Cyj{OȢ4b"1߇/Vi76h4Mamv6G5:'Ohfi"R#RfON|CB$Zxgք,ݽt#UԐ|C7Nɴ"Z(X"Yiϣt9.5?b9 Y="xE! r U`MRƟG /67 wK;E)cA׻ؿ]UEL$v>..+([Tmd x'Q46>ps X~Tψ;D܇4=x4͊pWڮRyra7 pvOף:aM%f&1pE㖍6[tEuڔσ~ٸY pASE#Bټһ;(οÚ#S,PVp2l?F-+M9<Ojsq8ӢjK=` f2D.3n%IOL"q-[^x8 ?Z ^̧߀>pq֧{ >a+~2T"! b.8 IG{ʔƢ g|8~秋ܢ"Ζ4HnL[ !Gݬ1-aMȢ.&uk=}ք,ȫ~K}ۤpU"Xhvۋom-.SleaMȢAexu]ǡm5lCkMzQ W,u.Jqyqu1 Y4/,NL}tjaצ6X0H*6g9ݧhH?U1~לbIj'\HadឞR ->FyCx %^ɾmyd#sQ{}*bG@&|s5}p;|աY?57 cvP/ ðgBaM0 YX5! ð&daaք,, ÚaX0 kB?}]SPIENDB`./pyke-1.1.1/doc/html/images/plan3.png0000644000175000017500000001644111346504626016327 0ustar lambylambyPNG  IHDRsBITO pHYsIDATxy@g'GQ!@jQ ^uqV(ZEAU ڊ-o(Uŭnaz"R ( $`11Ow^! kg~w$ \zuĉtWu׮] ]ӱX,6MwZAB ``ĉyyytWǏ B P`@ P`@|Ew!z{FGG={V_@_pa<o"HK x FU]v[ګm``.X,oX$@r+++͍.b:X,3444r\<-)[vvv...uuu|>_Ų U}Mo/PAC7nx뭷X,iCC~|xĄٟ9s&''g̘1ܹsJm>yۛr8;;;Ě\ҥKki\}1iGGFB܌~Et 222TSSzA:88ܾ}DzPz *++D"144xUUU+VPN``{::: \.Wʕ+/_^QQ!JJJ{ѣGНLRLj733CNq\WWd8};RS.'$$8;;Q6Ct3iҤ= C   (@0  (@0  (@0X,B+ F =薙رcux_BGe#pL6ŋ'O}' #2Ap0l(m  z m p 9`0c0l9`0c0A` ۀA` ` '`8` RۀA ۀA9  =s6`8f =s6`8`@ 1bq[[V1>}J[YzA#GXZZ=zի`۶mt֧ t[n@16o,i.Q_A04gcǎN/_4hPDD%+X,֦MB[nJdyfPTT%%+͛UYYy ˗/2dڵtW 4cٸHLLGEGG\cOPxzzm>CSSSK_cЏfoܸ!D1B(&&RA/1A.9!doo_QQ.JAgÆ x:66RA;1B&Kr###w5k]j \\\.ӁBw1ݣ~ã*tG}A  (@0  (@0|>_c0UgϞ.\>}:U#G5  ^| <\.?;COw;>\~A;/%g }ŋ 'N?)]@0NPdffΞ=0a©SەT ɓ6,))ŋ*_OA0qϞ=Çilٲ &,--V`hEQQG}D}ϟ~``r_ה)Sp\.wѢEׯ_h %J~meX^zz\.W=tΝ### __Js`hF]]ݗ_~@DDD( #={lӦMC uqqٵkWSS+(@0ڵkAAAgG^^^~Z[[:4rH\'|ѣ^H^jmm=z(9+]`Kf)iӦq8ʕԣ ѣG|>ӦM]G,TTTl2ccc\ޛoyi8xU u)9sp8ɓ]5ۼy-.U l߾Q#FϚK~.]-W^I$#Gxzz֮][QQE T U?c|3϶mțwiƃ#O?͛ $ R̙3'O&:mڴkwJZ 8,,cر;:: ;B0>>ɉ:bڵ=bFS L$mٲ ֭[Wm/`wɞɽ:t ѣ555]ztt`H$cǒ_G̟??77WP]S(3gl6{Μ9}6f``TUUĐݰaǏi,I ݿ>211kcԨQ}?w.x9w\ǟ8q V__?tPfk} dz}1oucc㐐[n]=X{{}||211Yr>`FIIɪUvvvNLL.e )77_xfg͚uEґ T?L2:bԩgϞeeL VVVj*+oo&yj H$JHH/׬Ys}3=<11b`)-7o\x1uȑ#8_`r0'O3؊+ݻGw]7@!H?>n8ρ.^H`Ν;~X,?? .ZFuuull Mllluu5uF? V^^fGo7DBw]_P(.^8o<7|z``m8PHw]}ŋ8-Y͛t/^hnnvFsssDDD9;"ԩSѬ__~z?޽{k֬ JKHHDt{-B?;#66!h"륟~i'gXӧOw4CPxŌL&;{ԩSɯ#L? X,9sssuÇ䗪nmm.u7'NP>11\ssUVH'; 2jjjڹs'mll>gϞD.t#%%!d``r#F߿ ; ](JG044ݻ3|rSLBڂq%}{)e``KKyD"ӟw^r###7n݅L6a6044\d Ȇ p`>|t BJ)>>QwdEW7nɓ'?~ܹ!OOO`lڴ !DlҤIgΜ_Gh4誺zVVV8>={֖-[Nܹsܹsbx}.Bf^ٳGطo_ppƛe'O.^X|W}V[[T*eDVd:<€5jUFqqo3A0  (@0 ;{*5I#wywf D" 155۱c~1r<66$((H,cx~~~"!$JlllwލzxΜ+W455kӁÇ"?~f>}*-->|8S/(3޾tRrC$e]w+=֠Wp8=^PQQRuuu|>s0!mllptt,++/yPXrU7׫ttݍ!^ccCvZ<괛 Z`AMMX,޲e˸q~YD"Q]]݌3MH޻377922s5tĉW]3KCjbboߎ_L&lllx<^``˗/ @mݺ,]D" X|9S&##P]/YᐷWir9!/&'MDp.Vjfi FFF|@̙3gϞMĢE233 2xܮ2JnCW0(_N:ŋW&!4s̺:P8cƌhBdd̙3Ba]]ݴiӢ2dHZZZ[[[uuuXXW7c899(c OcRU,[[[7664H,bu΍R}f[鱆 wwyN |̙1csHuW_YZZ // &k?쳷zkԩ&&&B׏;V)))~ٻo7o=11ϯl=/BAAA|I}}H$n@[QСCd2&GP;igg|~|>Bhܸq5P:RݿJ۷ s:x|:C)DlmmɳRܸ\krr2CסTtt4>g%|3f(//JŁA f͚% HQQQd x%%%':䪳*--ޜcgϞ͞={ݺuaW%dիWV~H~~+`.8 P`@ P`@ P" EwAieݽ{"W< T;wܹs'U'?ߩ&4qnnnj{ٚj|||4`?2Y2hJTT(_suuݻwՅg>IENDB`./pyke-1.1.1/doc/html/images/bc_backtracking.png0000644000175000017500000011646311346504626020406 0ustar lambylambyPNG  IHDRN㡩sBITO pHYs IDATxg\SW' !쩀q2d(PUkݭZGm}k{ZGVPPDA@@D{&!$pR~;yB{=B RλAQRJK"B5 a# P!0աf]p)(DL9J㔭@*Bޅ!TBHaC!$0!sB9Lu!:BbSB!1!T~XkDB9Lu!:ҏLfpn13˫^{tvDAͿNX.XeuT]%{:W-/n0yux3I tm6!;WS%ͻGJ6j_FeJrK=%uԔY̤ʬvO?XF\T_}Gյ[rK6J&7Tf 9ԭ" wyZMĜwv򆔱*%ii1!aY J)?HH5yυz __lv >dvKtw)hʻUWEqv~JCYɫοu@ POHPi&ULu4 AŽRλa?1殎JUlH).B !6`~|}'P "BlD!$0!sB9Lu!:BbSB!1!TBHaC!$0!s81jk,eU ; SũvaCL"(AmS*_KPŽ!$J0ա1hj*ŽCr/e̞s ;)K) ;P;piV$2V'eͳMŽ!$J[ B!1!TBHaC!$0!sB9Lu!:BbSB!1!TBHaC!$0!sB9Lu!:BbSB!1!vH`oߞZ^^^^^bƎ;qD[[[KKKaGBBNܽ{waaaA~ƍ7nqyf=jRfAyV{cHf{`IN4iҜ9sZƍsqq>|ph42ωpzTG)*2 /:T'om λ}p!n΍yK}4{"Sy򥼼CN4hPYYYnnc7vdj~YtnT'z>}JY Æ {677veUup;2!D99`hhEEE75U@F~9!SLjJh4 H5!SRy<]7v,oB݅NXXX@xxxcccJ ґ͘Cu4afzzzέ6#h;C i|zP&go?;Z7#\ر "9(BLu"#>>j;Zn۶mzj p_g%f%fV4T׳T[6vRe) 2Hp~P DRNNY,̙3~/eeey )1=sFnm=`Yi zSGَї>6`]ǎ[jrRRzWNIMM)..0Nx2%hÆdjbjPUUY%{S^]_Z:(1uFqAY5KAB0ՉƵk?~.\J"##=z㓕3gvI)N'>\e-hTe#āΝ;>}:̡g:`o߇A8ܯ<ϢѨ̵lvDWaYYY۶mKII)///-+RVlÚEzv >8|-,6/$g엋3 q#B {f/O vP O~Q4DC٘3WS(G!N܊H~ +ut'rtѳw}]4uey<÷<5.LubOnFEt5EfႏY65 Cb B SX N,(4eHaǂa8v,!Lubj cGj$;zk/]V7듍gRrJȽM<6K_||y| YnGյ[r/hۥ,>;ܷɣ%LYGf~9}2˧C@o^; , { T'VRsJ@XD'du~u[NSE//,iyӋXqkI:mŰnϓro"Ҫ#"U)&)!< &،;_ 8X@ Lu⣲UY˒a tS[K1ܭMH~Ԧ37,~IҒE'wNߴp̦n?LV c,lUZUӺVt U*Z8aRt`q:F46Tղ:?x\nIvr@%[*߳0::=JGCrK(v=M^bjJdj\ʠ B0Ň E|~π3li'lv{agz[<瓁9E<?-l!n;ݯnnq4Q-Ɣu`ޟ7[:RfAivE}R|H3 e5%UC4{_m9~o@%/<E{.zn `Ė1`oWJ*j \:s|tg|w͝<~O[xc=zA'v!907oEr5 ;={GMŽ{8M.Wןy..냐aXYnィk; aqF0!Sh3ҍ~uן ;j;_ݥu X:scAHAlIȦѨsl#BQVU큛ϓrT޵3vD3Lub']1Ao˧(;(w';N]E~73j ;(:1,c842 B Ee )FY3?_IEk7_J:qgY|S @]E~,;#*LûCW"?M&JvFVtMG+*.nTRDl:3jۓG#ZTq*Hx_ZMnP` QzFC5dde处j[@~w'6qj5ʚČEi4x#)GN2n}0} nE$&$fJY%yuy2MRrmFiaR PamGJcCHh0}|"%ufQrvIU-]SϮgԳʭ+WȪmғƧE^]FOZlE>@PTee R rR* 2cidja Lu)aUde>afQzLG}%;Y>iA9Q&Pba?IWAK#)Lu_Ȩ }\#@Pix03[_my=-nvd=Q?@ @ S%I~a F)iLizj*- ʉt'+h<`$/ʽ*BTZQ ܨPpӵ`:LdT5x?OZr^BH`C} yt3#$0;vV˂NPF7,OZЭ2v@IKSLm@jALuw*oEI`уh6ɑ>iA~ F]-OI^.=PoTzEfM_F_ƿL8 cӡm7^#Q/|ӃoԿ]O[Ncx *AHaCC[p-8tk.*IqOZ98iliNÜ8T>TY/#e7%=fҫ I MQKnTVt2pӲԃCP/3NVDs7-9zә"-bKTe[T{8;iхBH0ա)eU e7zNͤ%O,O#(1y8;L1B0ա.ɬ}TXuc}G=G1$27ozplr<]MuD[!9LuCq~!~aSȍ X4={w3mչ}҂&rbmk#O4.TZkK idLG!?¯B`ߴOh g ^Su!Luz.~!Y&єUt`:P7^UP_r#=7=8ِ+I;i2pg*$BT+cWd>t(fҫ2B}҂" b<S%-=32Q5B`HeK 4ߝL{JC(cWg=Fh{8`: (B>2o3C2˒-d7Liz2H||?1T uL}'m9 a Sk _ege%&`:;Yӂpt Lub}?͌;Y-{0,pF.vnvd-,f0Չ2v՝!L TOZp@փҍT =GR.Lub"\Re7+u#tDoJI jT$ÄB Sh+KkAۊ\4[\~Shߴ`ЖK{Ow6o LufڪkIf"2ȥ}ӂ2BȍRVFpDFC;$͌;dL,HG/}҂.AK+JMճ6p:a5q> |Ъt^NTeƶZ*UZ. _Yi<#EH05_FhLs`:ZƎv[ȥ}Ӄc[.堌fYlR}i:~2@5rtT&~Ӣ3O8Lu4vWTeuŖid:qv3A.g1%5R rtmFs(!dU2RE_1 񀩮3 ^x^UXoei2`ح-X`:Nֵn&HϹ6l ; $pF{YSe(g@Cf4={v3AUg4]Je'4Ae2rtYOHn '2. +Yu/J_yqoR(<Đ70Q{He} nfE " 9e Q{.PYwҐѪCg͞g6?/(TMٗUڈ/#!UE#L[-(S"Hl.>A756ֿǭml4ddy?#T"ꌈӓ~Sf(v>ϩ-|]23"}2+\X_k DF/NkexjI\o\LV I_ǭyaGҖSfز$8>r򒲿_LkRJU'C./)n(ב/p-5pF5iuՃF/~~'Gɍ-/N^x ʷu.B}IQB?ҏ+*c%#!@~}e/ڧJ*줺n{C^< z^VEG5"ԗ0ݨ.&vrcˋӎ.<[ٕo\̧w]aiʗ>"≃d}~**:qu\WβP}c%~˨f{3kԥU{B,1Lu o/*Y-/N;lgWms3&$.-Ȯ)XܨĐOĥg-%:{] hcWoCREz#'< UYªXc{A .͚U5y2@^x6 A2#k˩wr y w~ŃPOr߷w͹^[Nc٢kw1 ^ػ )pdf/R;;p]Fu0es`k·?? \~J@ "]:mu;Nm{yyq+D'ykIACRǜ<ﲳ1mrNu.Beg7MϮF_L]_*|F%ܮC':)_Z5ݷ[#B}I SKu[Ʈh{@m{BH(¥qʶ]UCec VGᄥTŽ;\sj 3>7X~cho^9G3[W x]BH41쁉B!TBHaC!$0!sB9Lu!:BbSB!1!TBH_S09A@ey#JQR}D$b\MYFW!FFbB.AKpY ZuE4@A]"{`+I Vz.:2pjϪq4ZZSLw+kJ dFR}X+!3"^݆=\VTKxLlg5%"ڕ^x=$.zV#JM9:ʋ^I+4Q uԄ5B#2\3,6/$X-5쀇C⒳KBჽL\gHJT~u\[Mѯ.DLXҙfion@RWB5`I: M!#xs=4.I2 Te{-u$؄>xy=$&4Tfy:kP,ۻ:~]Ғs'jqNWW^\ ~1XoDS&T6`"PXVA|aY HШNN&M}FRk'Ց}1c#22 R7HVZR/5I ]YM<~XLOhܣLa eOG;J],DF}֘Z?-q];7,ҽم >t%b:ZLumguHdV'W.1i0o':mS$ox5`K^4S,e<^x:̙lIwz uK:Oճ^Hn1d:yp)tyoP^2c"\yw֓sO, Y,P LuIJH{`'.%zhܽo؍('5fPB*'|FQ]w?<1{IO$Uf6qB`V>5PޑŽ!Z֭Dش2Pr$+c6b/m {c JyuW_U'NQS`-SrOJ224es!!0յv9vKA}'"cӯEL'h T`q-!> 7n1JJ0c̜ 3pxR>yM.d2vpck.^'!6g:N77XQ y :B`LhLDDpnN}xď{`VUU]xԩSmHTˎ;Lf,7o?*+WF`H4ΆW #1@o/{+"R ^&ciKGK*jCOVS8_~:UUKKEEii˃,Ȁ@x طll:*ҽ羡y U9f^&J} TwsjSSӽ{𼼼vޭ7QM@u=WIdʕp6:|%|)IIp"9&&ZZ}cs\{Gnd2zLZe+ P$7o`J1$;b3o?B~ ;vmpVDŻ!)1z|7 %DMh۶m#믿sU]]}ر~AVV6,,ҲbsX~_u}&x PY p̝6IΟPU˗ٹcy3݉z]9Ɣ#M탨_+wxZx={PY pvt?رx<03PRpWYc"_-VtL332Z[x3g$$$vڵnݺN?~@@Jxxѣ{;6Kbb*+J$$}(E^]Dbr˘aN&kOfV\61|qq`cWtr[֙G 9E>{-5y-f{b'x{{EGGꤹ7k,___ee億Mg:mɬK Rʚ*:.]zOs -?!&tt#xw/ eyiwNL-UaG%ߞJ+e;`dii0{6\A<|q!0Lr z,&25B[Blo,[gIJJ>}zȑgΜ駟{c^06#y0 HV2IYťuf])N͛7`6#((EZZZUUu%%%-z{{s8={6鶣W]ƻȫC(c`kk׀ `"6._[mWV+tDq>Qn_XjP>A8X =ѕ_Ͷ<W l6L&gnX ;Oݵv-PpRikU;j T.v:iѝBșZ*~%m"שyyy:::cƌiw޽֭+++KJJRTT3gN֮] wlTT*e;8ptpxW^4'0 u[0L.2.dB:hW'|;:>K]Uǎ\/S+%=.D6LCCaLPRyyX^w`h\.8`YV\i9RsγkXzWWSev];w{deewޭ*$؉T,jŦlk\%X%%@;  † ]С@Av6p=oWOM=;*.ޜJ8:Ӝz;Z">ܝ,D^\ 0rd;tS_G67ŚJ8Y:Y&g\ zyڃiOf=Ros ~4o } }7?Fiiggggee6us?'# 2.xHXH^߶MHvaZ()Z$(+CUw>@+_Jyu)sWrM>{CULmFo'B]jjJe-N4lSs~?JЋ_N遗ɧf~9燿 ~<kqLAmkϟ6Tjjj566 Rm#^U@Bn^kՁL{#(h406K`Ĉn:(/:YCԳW'`ofK݊Lcގ&&?55p ,^ nOK>d wbbSO?9b|7t>QSKu`FJLuaaa-i-;;u:/_OceFvQT4i:X 퍊iXh(_ tkJx )1M|_g rl'A_O- ?dr?`">m>NNpʕGA֯mRY II@^sQ'1yČ wb>N ~$ycjcX/& iիW,Xknnvommmaavog6zTNJC?>g$$@So;u#fďg Tf+ˏl9q'>@AVjϮW~csnn@PX,XIIyZ|p80}zOfQL]JIYgo?R ~<VЫ#!ToYn߾}6l6`x֓'O:t(mCvKQSf\DϬY`3gœ9 - vv0|8>D 7Wd|$$૯z9^|h@k]BA_zH\bΎa'Vy1Wsz~~OٳAZƌrr·T^Ա8<>ekg,>v$%I46}ۧc@B!R 6&* j:G|IaI3DkR|bsY6pڄ)=mg55pYͨHz=Jtĝ_O:⤛7a\`@BV~.<|l~<)m--5E5ey)]!)#EHeb5#m l.y466mF4@PPt!jzu [r%;^͛7{⅞Ç{{/~/9SbFص ~ 8Pkkhw u _ g_xϪӨqRrJRsKSsJtvWϲQU}_jtN/),(33 |HMTwo'%v} jB"O5y%UY婹eO9ܦtS#ɊHhΝ;|r6d2E7%"44ŋgϞ%BKKǃV}s֓8!TGk%'Îp4̧aYY!=T*̞ ?΄Ѩ46=M"T$a ݭ`@v)3L֎'O1v|Ͳ + $%a 7ѱHdťL΋M*,'d%͇N8b *@P 555UUUϏ$N IDATXv? /߷}7fŎRsJ%S?1+#b*Fmiippwwpw S y;2GK ' _N޽R7o'Tnx+:y)Fy̚b9.>*L1LM,,:^*Mׯ ܥ$/}?r>|)V} :tҤI[n0` NcӦ#$+I] M ؉ČR(0EGnHe( သꂦ&~CʺK_ ~Y]ǦKЖyNX2c|<~+JЋvngY=?{iU- FO6a44@]]!eՁQI&"s\:JxOu Ko8S0LK~>A{#Yʥ>Sŷ?r-4O VWPqW^jS_Ë_s\ۙDpӣD8\kqoOMHIeT˶~嗾F=I.9ufcFȡP(_fU4TMxi>ԧՔf: )-L)6eJJ7zH,J9#czJШ7_F<A' -X?{#un_2vX]gW =U )x֦I3:j]z{;s)QOw?IԹ_`>hk<&Q[^rZ^Y;Pzuc3f6`on|dPG{^y0]GgU|q|V~J*bRus?4A'fl)ɇ%Ղ-9(MP9-sG25[xW;Ѩ;rw4l`7t<#{V6((pȟ'mFߺ@sX_ a{%\13rVSgS&+*;wHua1&zl9iƤ Xi0np]avM?}v* &K.jInoz﫦l -5&CqlO7%h;ngfPUpȟ\s_<4 H?dix' t\_R=\W}}/W?~StBA_;n.=de(բ6Z5`V5PSNcӯ z,+%pދa W7cNn?+'] o`7:YMA/y.$:!dz5lw=}&R]ےUeFޱe)蒊ڡn[5PGzoڭJׂ_ }Zߪ،B *T俜1In遼=~'ؼh9eI/PP`ʩϼxwѪOE{cYsg>u?%tZh_=ӕKkS&q)|J~`\{Aho{oxw2↶ouWM|bϧYZ}St gf=3˟$fۙ7v:jKسvp`QS> wJ+g"eyi8yQbzdbUy.wzԮCȤWWk[ :!zD??|-! >Ap,FՕ*V?r%C˫OD}2Еʺ;@l:r ;Tw`˅/9ac}}*pԬ+_++_սȍi wȊʫ˪vy!1YFQY x쟇7%Kwñۿum_8TPZ(/N 9\OwilH]B*˪~|[-v|ە²:guRi˽$!6w{{o'^ҮCFoTˍGھBhИQ}_ Gr*ɟ +?Q%8:[J\VUgV؍K uJ|9a$K. 7fwT{'QyuOĤG<+(Xv8]w^ym8=j¿+5eAΆi$rrM '-zy+g~(?,rYRIK[*K"wQl'nw+K1%u(M HMY+5'u*_y lZ8iړmi~'}#il|Rwg $eٻﰦ6oLp8{VjsT{]-*  Ce F >}097!3{u? 5}רLқ-)r.%9)k:Y~gYeJia !iHTbӡ>fK'+WHu5٠Q~qA?|"W jxdm42ow|6s%+UvNrR UJ +U?@l窿Z~aUU)qWdǀՏK*[ov;)+*SK:^n.TTLqY5v3gf䱥oiF!ʦ3'H/S*-vymt9|A:ljJ-idiŊݑC CZY^<}7MUc+z%xOuUr_"꟏o~H7oK3HW|߶ܷԐ[IZ_9y,_^CPM6_jbÉ{gx2~ڭ8/KSY#62HP.jQ4jf^s%uڟ2 mR:g՟ `{ jxjkю? idHHig'4~eKHWcŹEeux'ltq^@ +*(#I?.YqI.%Iw^^fюBkm9XIJn@H+ }wkhpNAi)IUXy+{'AO?S/Tl|¹sGue}~3xᶎVr'?Lѵ5]'l^뺏#I[>޼be"=\Gu1wȢ\(/r}]Owy[&nv`h/c}/;`n;ׯaX, mU?,E"!ڏ2j]WP''t']p7Bڭ]~p[&m0ieORYh fm3fG<^Vh=fÐG}W[k5zX,IeԺWiuuBE˅~wTʟBxSuvsiI_L;w_1zw+Su{'AOn4bQQ=_(Q몎HsZw+%Gkաp Q?HZl0iřXуȤ9/8wvO]F&Ծ{K)Wc![տlSάڕ*;ȮBxŁ&'z7wIΚH,޳xW{;߫o0U1ž.U>ӕo#ӂ%7 ~]9q)W q ASfr66 _ f?gTU N\Z:WzY(Ods2ʅ?bmҸ|~ʃA&sORd[j_߄ 7=2 8坧[߳ņ//qcoeMg?叙-,N5E5Y̠C&bR^ ?Q//Gm-6mnžx:k{J0bAĽkb(?TaQ0XT MZy&)='V۩Fkn<]u6O 4Pi5Y~׃L"r!jyEeo<'`?DKG),S)Eu^ROk zIj^yA/e mjիbή }:bz#LQTַ%g ;ޛ0w.^Zr Kc?dG}x6A͋]!%K^{xC=FuhYtal73U΢wG缢Ĵ7 goGqV&LZ,삒–}D"y!ZV/R*Oɝ4_>[fgv 퀐(m:q.J7ѫ䕇nR(=[Laomz+ߞ"5DmYSxǙk/tilQ_=ܽB_&Iͭ};7nCH͹툸ӥ x8j"Ǧ?}/*'LL_5os3ryi9ܪ NcK۾6JMSԃȤ&IĀifkjbs+jhz/-,+*gs^K>xޘn=gdRzȤG$fT]D[ c ==6Kҡth PerOP+/dV63jgծcK[7~)HE'fd[Gӫ'xةEo2^OJLJϓ;AԦkӵt*CMҡS:t1&!W |'B.\y 2`mRPwPT"=O"AQnAջRtVQcl;f%?6;wnjw-D&%%%*TConknombomjcnة0&9k'vU2 %(#8=[,!3]ۉhTJcS}+3KS}+iTQ]4QTW 4i3(=^w ~,OPQ:r/S lef`j23dwV W AyW0Jv_"HLŠthLFEKDy3rɱMY_gI)GPT ^~Db2h :?m6bu;AEc:I$puVN]e1_rui:G~B{XyLs1Y:GgDjflQ3H9+EYw?KcR2`Пjd9t7$YH3mqH25w6PA'_$mJfFmxN񿿧ڙ6xs!&ɹ 9B S|ԧfs 3[ҧ"L?=;dǛSzz^PCN5w`EF:=`K]Y0M ?, d{gܝ!ϤS؁LurTk\S=՝~0n`mD}Lw>5`C,N^]P܁5N6`hiȔM{\;yҝf7kNvsO~ Fhaoj0^],b2}f„[nߛL"_:}V>ۓRU Tp 9j@p9d82˦S/N׏$Łu/Xw2|mw{GdP`Lurnjaݤ&e:|75njh. lRt3BKA lL:p\FxRUH Ya!̙σoOO==(*7nn_-<ߴ8f i6LuxEt3ceL}Hbce.aU[Ap,\l6h0d̘=z%ذJK 3gRZ[W|#͆dUB)ю =nE$1w=w";O6dpksyyu+µk II %VΥ9A8i(Lux~w7Y׏ַIDx.)-NX@j ={@K v۷a|xZrr-V9wGO`7NVžu7PZC켯ܛ81?YZ$.xA句Kp1:n9y;;x vt5 : Ql+S$AOsswT'K/:OkֵXsĠn$pmIe:@˖?zЩ =]$:lٺ+mؑ?uRs#A^80 i$Lu*Zuj($r&].aݵ7I+^}]}CP"Ru£|}aԨ/'wwo`0Ə\ssؿ`i{h/8~ IDATy O@,ApP$$4gi^֝:kcw=2ڱL Jy<͎֯dvT?WZ%)l!?@_ƌx_?hRS+ά\[wnݴ?vtwL#}=ۨ:::My1'W#0?߫[lOsc UH@n~{[.q1o<^O%m"OQ#psc__`2 6mI$=˺ UEyHp&b s#`R]4:Sh^\oYe{Ͻ+LI*RaFsی{3&0liCTƃ.=k  gXwM~1hց_zp0t?H-:p%e|Ncjn~^y;0X0ʡo[Sg:4j})XfR/ua]/M8w#:@;y x< k<<2~ ##;T5u%H%\SuYHalѶE~+'b _Q;UuOʜ2ɨs/G3hs__=DT{%$4@QQ(hΞ Aicqt1Y"!J8|&6#B`?l{s+'ńdfQz1qTviܾ{(RNjcΏ|TQbs{Nn8ٮcGRS`k+F"ܭ.\  0\]x\zU#JiϪݬo*d4$EN n~zGc.sC>i@o]Yn)0*/69W2i)z4VI9g?d*캤 ¥KO11 Ar2L#Gnŋ!8ڷ8{dou+ ׯ R ?f[^pxUH4P$..jbd2IdY KbY--Z6ӴTRLuNfqt"4n &긔!4hl+\Q2U2\*xNyax{Ch(_Y.]QLxQRz~bZ.L YvV&ζf[vje+E꾰#KctGĄvJKSKWY: 0;ͷI;jк5ph} κv8t>F"S3=6AgX :Aq\$. 81[G&OlQ=T'kIO\:Gձt#}%%St؋н;p@ҥh+X g_ArL=vNdnhd҃Ȥr<#vl6Hje<wYpEWm:08/iSO_;s)""kczcab8tdz=E A(l6Íp de@C5PT=չ \p5_=_s[sUDžڥ:0HUGzY4W<wUVXW/HD<ɗ4nokQ(nUwùJ/^mpҿ{ꂥ%0EERuֹi3V1@Q#"Շo]y֢ 4sXgr5Mu,[ Uu,1D"UGbjkbݜǃ3x>~yRB"%5an1y,k' Mp|M9 Pu13dauiC[$_ .gmGcXz4]CtJf.Z20~υ \n'WD408/~^87ӦSLo%\rʱM}jڌu_Q8UedFF/>[Jiduoo4S;'&\{tUglX#z@8B4ops-}<("kΚU< 6mQ휬2:!i(LuQ\q!N~m9s Kk*s[uLF;ؘf^Pu8GaCH=p]6EձԻ’6 aEjޗD7g:C^B2_Q(Ez*^Ahաwgj&r&4Җ~L>qEnQi%N"=\yNK˧9|ina߳;ژ@P۷KO(Risai{YYw1Lu Àh.qL_*|tȧB3#C:i+ $vn߇Nժַ};5O)FTOUgoS$D=/*i~n/vV&Oޗyrjtw\?pw 4YlJ+R/S^Mξ_hT3ՃN;?+*c32wT T:=)0!$+>%Z){v%| tuL||фI\<å b]1o>ƺt@O9o[:POXtB t~ի ^2XɠՃNN-,+`s,LVwK&3}+]-/P Y0Ԭv JQLdO&"*v*ܫt軏<g9hөbVf Z)`MQa !knۂie>l J@% H6щ )n-lV/[.W0gf䱥_ylelh|O6MY:mԐ[lrE]3'*X^#wDY5fc}7>1:Mr6<%bÉ{EnM8rUBQ3W] {?q[?~O_)z[q0(5H,$/yM/ !ϳƓ+cBԫ|p6Uu ivxHw W$/s5wi,{/!n|xkjv~d9"˨u޳Eu~r}H=gjG}2j݄T!ݪK/hFQhe[/.]SlVաUg[삒7eZ;.s6vwPrHF)Gcws%w9X/6rk!=9A<F:43d%Oį_/\FHQ @{[k5z}Uǂ~fw`V5rw+]ZqNpZt\GK{Aoػ[.bҘk;,-sv7uLNnF@ϕp11hk%f #s^.$ܪF%hs? 1gnqSfN}nQ&R! ??#bp3/E>1B[Q1 E/m=~.4dر91'8yqn=$s5ըZ+ml>%wښť033 yD"QE${N[sb G>C:i_RBA `s̙2e|:mhY "HH37Vq5\-!d2:ROOo…Ϊ޿4S&;ѦaiT! تCir-'L'Ν;ޥޱt]6kig!rKS=Z:"uWѪa)!FìnРA~~~ ]vϟ)phӦM}3w'U t_!]S[u!5K=zaii)ȑ#eUSSӱcsJҽ9'RձhB8!'NxÇ)A'L&SP.J|.qx\~WPrUXHb'T7 H:tCgY:t=S6sR ΫCi-زeKppg֮]|rE#bڴiL^rUϟviUeUB01`:ژ9ؘ[ jEQh̭!@OOѣ>>>+V(,,A,Xѣd-yq2BIDATEqi֨}c}6KbY:t]Sp?F@ _H[e\!+&9+_1"#9zs=&BH#T={9sf;v숊 444{Ǐ͛km休975w6kl[|vBJ^73B#C#SkC;c&BH#^?}tĈ :v8k,;;;cccwޅݺu t 85|]2)e³)ҙm-ԭ]};{to\!H2OѣG\ttK.Yp!PJrgD6 mbaY{,?BL6I_pqǾIE|Ʒw7ަ9@u _vjm{snj-$,dͭolގ]Ɵ"E"~u!ME+*\Na{ ݋F`c?Eپ`XM9W/!؁_r[[^^;0B"l= h s$:SXAv$ˤ< l;IDi^'o?F'e&sWrB_o~ԦM!}loI'n-*}sf BrowU3}tH,voEa' ɤ5]lFnƤb&k5cg/>|ina߳;ژI$D`F}nI͇-C>eNiO[Ph'зse>tVK˧SrF&psv`"4BE.:1wqa/r{V)bSysK^Mξ_hT3ljyl맬b.,:yvK=>ˣCYlƺLmwkb~ҫS{$gŕ<\iZ,}[>6?#SNMTV.m:5ͱ0[5-ccÿw5ըLCRZms줭:1BꭢS`ˬ ?[A!KCNaEGt s^QYM*"*1swy!|]MŒ\ ~ 0N.vwgs?%ܚjT&GS}:9n]}#05d426`Y=XW iŶ߾ZԂm٦)KGtMU-,v/|߾&̪>[_B}c12ʅ 4BE1$+?.O+*߫_v+fŒ;n )(-}>.SSf"ET- F+^y / !b@?%]MprFh9ؘ=MMJҶ#=w|fe2h:>kꐎp9[Xjom:ch'yDͧC->B0kQˎs_fV '-~ޔnG)x`-i9o;Z{4Ч\%*!*6t?j.m_6J!!{jߠOʌ !IEfO7&ëtA/TZa&BH#Tt`gBOژV%{)um=J0Qmn!]ybw_I8w'rL $6bR t#Ci{ѦwqBBtiʃA|Z]&:eąBM|z9,-!? ydueW_-k`"4.E,vy@@*!G>)@(OaΊ 4WgBā?zn& RxN"BH#j|Ψ[6}A Mqtj%G\(>x9'"HwͬZ*~8v`"4B˭M)k_{srĹn?2Cwv/˟vni}߶*v`"4Bm 5}{{)YLG=ѽ *@K ~bkXͭMJ؁_tmn}c2]~ 2ܝsw"uy;ziMSBGˆ!/D&Jq$x>S;'>t";0BMoZ4CFDZ!/n<8L"Y72t1ulb`cDQUrQ~1'!57!5}Jnbj^Jvaf=tiao`!m#MY2^yg>{UUxb$L]W=J82+(.UO=$owG'Vba:BՋTTI\~\z;tshlBS]>r}yTT޽{k:I&drߜd} y4&ylﹷ9sV !T70)_=rBL/((-,yչ;2>4idݱRZrkƭj?z"R_Kmig1}hnDHఔ Æ |r@@aê?[}KD__Fn=j5O 6ӇtlӴbFQ\~}6L6̶:! *Hw*,QTsd!O Ҷٹ'_1\ҡOYNma?tё#WJw/B?lUҥ4iՂDmܸqɒ%v/]ޮD*&(m=͛Z4B} bqƹߺ%oڴivv׆XO"$nZ\5Bj ;0+P(c3o(vl777sZuV~ɴ5Bj SݿV^ݬYȵk*~ԓ'O.] GUgɈd:[ѰenOT/u12zM6)ҵs8S!Ai~<~-Cw.sai u;P!Q)E!$:YӧOz---gΜY,A۷o\`͞HB4Udճ̳N"=\y׌2[|ZW:X=DBTjT|r:Sf!FEky};7_Cjɭ-*7_ {:"P޽5uq?7`$]>j-:Na]GYvGWDVFDlEŪcG-[0D$da6!{wqϽt& Ϗ6g%o=}G.u֨Vm=-.q)Z\n ='fo9ڡLM ˫qu?{j 梃5G oǾw[-ɲp]gg{xh':T:VZq5࠻0Ӻޮkv/p~nu]Jc.^㰨9kqNwau݆.1t,U ј@:')*I}Ij)}/ܳGH$r!ެT4,CPJc79ï ٭_WB8r@O7rrcKs:wU/*F Luo} Dc*1wxA]:}I,u  'f 0J!o\3 M`PB3D W[V8_Mru;O-OJx&jXgg40Cz1* i+?6oS9?F861 p]oc4zp0o IǺFw;Ymj:Sִ`LYYЛ@Mg^oŠw{j%crR=Ge(Ad]y-ΠcnİZȥqNKpJ4YlC[(\R|vݮ8앋.|]RRlD.Fq5+C-'iΧtB>:F#l-'#56oEY=5jD `z#MtvG,xTL&ć:hޘK3R+kR3~H/2~juGp)kiDİk+$-sgD/#:5n`βD^iM{*%-  sqnA^wSbl=ܒo4ZasW4| `>G;BxnX?x\ֿ܍@o+U1PF Gj`G"4xr?:s¨IENDB`./pyke-1.1.1/doc/html/images/plan2.png0000644000175000017500000001267411346504626016332 0ustar lambylambyPNG  IHDRUsBITO pHYs_IDATx{Pǟ$K DIbEֱN^*(-V-ziXZKPG tS⨃whbF(.ABi!B6 f999盳={v9g4MJkNccL&ZKP\ x-B@ ZE鸖`~;FTq_1bD}}=*L Z31;1;1;1;1;1;1;1;1;1Hff\.O0{4!!Ӗ?rʜ9s$g}1GmmԩS-ϿgϞZR=|P*FEE'$jDRg@jjH$*//ΝH2:tnĉ.\x7|}}322Jj6mrwwH$QQQmmmFu S'U6@ch VS*Jrܸq$Cnn[ZZZ"##cbbH%K[[[ܹcTrRRRAABhll߸qQ]F®^ʪ-kbICWߺj;V)">>>iiiUUU&K>nhhf*((xy6 :0۷oߞ>}9rÇfL6D"H$ =Ev횯oAAA?ǼcFS^3x,f4M_p#ooU4u] 6똁<Ri4X,H*++WXatvvV}V-..^޽{^:iҤW-ז}E,cv T쀀<f'NRte:.))\agee,Ulw'*j`G~VBt t t t t t t t t t t t ~jVTT$JV/tuuq-4v?% :fUTUU%|||V)P C`ݺu\ Z^#1AC& r ,i@:Ba&t 6Eȗc8a&<+1`X:"pÀc ,G } 6E8c0`Xc ,lq :"a&0`X1 a&2c,1 =ɶYw 1Q(...+W$:c֬Yo5ܾ}^>|Օc\1T*]nl߾ F s 鑸8Tz%ŐB0==~ʔ)Z 7cz;v>+%%~qJ>*Y ̛7>S6m׺sY颢"8< pJrԨQjzСϟ?1cƍ7%L&#we?]: f靆QFi43gp-cXl9~xVV*Lŋ;44k!IOO1b*R͈oJ?{`QVVf}d{,[l\+WfpUggg+׈#_蘗̔V$ytL5wBBӧi -?jkkNU0W[CAcQ PUQQ9d0RI2I&0oxbPPX,;|@ iZ(u:͛e2cttZ6ˤ<6lw^Ӥ$ۇ!GᘟP(ZmDDD\\RT*ƍ'rssoݺC,YښLccJNJJ*((P(7n4ˤ<߿P(˫HVıQl>lMV *I(lUUU,dA.$#Gi6)3Ϙ1cUcLI6w}LKsrrRT$͍l8::2ɓ'sssCBB|||Μ9cT'O&L@QH$ úLbR@}}Qrd 0,X`͚5zٳgفdhhhvvvSSSFFի>j:i2xzz2K^:ť54X,H$+V0_NTTԃ:;;zN3tժU?jіh3)`ҥk׮miij?KpsNH5EWJNN޿?*?  츖Vs@MƵmmmֶsǏ44ycǎi)S;w@*i ch333`Ȑ!|e…dVh4쎩${}Μ9lKHOOH$0a„HbP;_~!o9wqq-ZSXXH 9;;=zoE1UUUs%]|ɓ,mҥKNd9F:thС0|~ ?z(;f̘>,v\y{GzE566y%%%&LDs`qNKMMuttOOSN_]Y[d…---WyҥKPiVVT*QF[F0Օ.\f'OXwW)cݻB\ee:::Sf;20ѱm6p5n={v᤟y&b^  ׯ[#555ӧOvE#1 "?V]]][n 0{솆 17o $Ml`NNxxx\zk9imm]v-9vz뭂BCCٳys˖-]]]\+bO`vEsO^SSõ"KcZZZbccɅkhhhQQ׊Xa:ܹs#G~u.pQT9?mڴ. cN<)駟~]%p7ihhOH2sr=c~WnT10ۊc鵵']xx8B_±c8ֶ}}ʡ*[0p8%%,CaR9FN8܎#78>/1 ?~<L4ɚ g>|xʕEiZPz;v455UWWTWW;$O:%Hƍ7~Ƿ677D"Nwif~j4ӧP&9;;d2#a6lD 7ߐ #Yi`„ VfuuuV#G)iӦ1^777rㄎ,Ç>,)))))1,ȑ#{*z ׯ_!hWr}CffKzISSSGG*Ȭ%>dhHTSSõb֬Y\1cƥKVDGG8q[1;1;1;1;1;1;1;1;1;1$ꧯ Nlq1_wY8ד"ʻ"nj=B! *))=z4@|el''MTTTD;::/_Aݐoe***"## T*{0008:n21::ZV@ HIINNN_|ŋXT*JWX xbPPX,;|0z"DŽݸqΝ;goo9~zxxa6b[CDRrssoݺCk׮1_W(Z"""JR7n\||IJJ*((P(7n$yyyEEE۶m#[l{QYYYee֭[`ɒ%yyywa,B$!p¢EhOHH?>M .dW| )jj(ż]WWgvZޫ9s&F kN{} iÜ^^^d{ĈeeeddĈ4MUUU1efLV]]]>}:l0Z,pғc aD;^N}ӝAz_18ED"HDb9 J Ç>}4$H8o߾cǒ7Dӳl̳`5kz>`lOOJVKznL\nN njj`&^+%'' DsINN Ņf^~}SSR3GшbDRYYb VxYjUllǏZmqqqtt4I_~=-^$FEE3%!**z^h.3׳Meeep]ݻ'QÞmΝdYFYlD"d?E0-BA2"IENDB`./pyke-1.1.1/doc/html/images/rule_base_categories.png0000644000175000017500000005610111346504626021455 0ustar lambylambyPNG  IHDRZr.sBITO pHYs IDATxw\SWAKY(ā*TAZ-*.תuUE:Zh]HqEWr$܄Ν;#Fׯ߹sDil$&44Ąb:s _ {{5k66OŹkiiiii]~]A($e`x:1`ϟlݺ)2@c4i$g޽ ҥK!ww\WWWn߾f͚5 !tqI&Aq###?.SZzulllEEEEEEDDBhMHڵkL&sذayyyL&ڵksɓjjjݻw?~piQWWڵիkjj騑(Ǐ޽;ñ=qp2bΞ0a^Q'&&|Y1@X,EUUU?333)c0M b?ݻ'<֐!CBO>!v*$H2nZZB&&&MHOΣG=BI0_3{=<NKKKqqDKKO>-z=/y d»k111-掋q8Puu5S C3 OÇ<@v%IhF&钬<+ʚ4i )%~=B?i###PFFa/0WAAA^^^&Xwp8]tIIIx999'N*5w\ ;vTUU_C֟'"SLCŇц V ӌzkB.ʓEv8ԩS!#@)4޳g4uSAN>|{ѣG۷g NB޿?n8uu>}% Yڵknnn&5TAcĝb||&Z񺱠 b?#bbb>~rccc<-K=݉Oǎ\ׄTC+Cc:w,|qO'O&F)yeܸq=ro޼FpAb4… 7oNMM۷E|}}N (( F#A_Q <F|ۓ63v6EQ?nYdsPXx]]]II nX,|~ii?fKWSSmWVVֲ8m.[^^޲n⛠%TVVⶆ$qUUUۄUƵ544p\,I?$ !Oi,^^^rq[WW_'a\OOOMM x<^vRrusR%%%B ]CCC&ٲϟɾrvȁI ֱ%o(,,$m  qC8Yx^xQQ 9G$5~ĻtB/^ qOKK#^zxrr2;;;8sqq!$www'x$8"q|UC$K.\ q????s 'H<00ď9Bf"xHHݻ,X@⑑9sTTTPrPYYxbћ6m"l2߶m sN_`ٳΝK ?rO6g ӧO?ǍG$_xll,ׯ8"}6ݻwI|РA$NBQT~HܠEQT޽IKMQTI$ޥK"qkkkwؑHԔċH\T{p0I^z ݿKq;N 7Bf m$TAQQѧOF |ɧc I!Dַ!AdF$N6BL&ąKp\x~I'8l6ɸpjjj ƅ'"Kp8$N6QB ƅ^n$d\{ oxjjjjkk8p@KK ?&F6o?6IIN_:::$.Ӓ$.68p\xx==&!"ÓMK~ Dž700hR"<$qCCC.ڵk,Nٴq7 '[ڷoO}d oddDfddDE'V8.7Xʌ)И;w"իB7nG700SL  ;HBKKD 蘘4t.011ׇF@*SR2 Ra#(r--- dqKGGGN^9  CtC:tn:ytu:ڵK eee:t֭TTp(2gaa!|* dZXXu ة@srrBtg>PFFF]yyyIIeL___6<(!R$rssyB=(/33(bbb6 ?(oÆ t@- Ay PF@*lkkknTMM‚,ZUaaaUUY4LOOZe^2bhhhmm-R@$ɴCt^%|lk׮tھ+2vl >6vZQ]䭷HSR2 Ray!Թsg3ӧO/ʖvqI,۷oL&Ύ|8z/#""lmm+r[[['''ة@QP 쌌y 9d2p;ӧO.CCCCCC9򠫫'TT/_Dӝl㼔,ތ"îچBCCCwO>Tْ킡-[ 33b1u@w>`)9~xnݶl"wխ[ˣѶ=u-S^^ޭ[|T|;T ֮d0,CeeedI =k֬Z1߾}{ZZZZZZ׮]CQaggp:uuVX`Xݻ7h1hhh899YYYl /J)\.788,ޤƖ+Hn0$O@ ؼyMd ݻx4Ay˗/GWUU-^kٲeO<:x BhJH'b CXB說mۆ5kVeeڵkB۷o39Ǐ;88lٲE޽aIzPl̙ϟGR -Z8޽{7n[ZZ g\,-"7\5Ⴢ***ȇJxbKٴidP啕988 8P7rss\(y<BȈjj?d^^cǎbe/^| !MQϟB;w39\F۰SrfX--- BNxH $~[nn.EQ߿'7\5q٩S'KCuIL2b(W%X,P]]EQ|>|#TCK!{fb߽{7yg.]~7lѣ,K@E h?/Z5x7\5Sp$1JZF(ޕ.,,$b'NsΙ3g\.~-AIC;t f |[n̜7oߛsrr'?"Ekr%pU?gϞѝFM2!QSScKnmm->נ㧬۾}{uuBSN3RSSCY˗//)))//,h->}JOOϗG煅^(B;w^\I>?$/'9{fOKOO9B zIvkjjfΜI1\boo/r~<ؐjjjΘ1F/_6l{pٳ'=zL"ԫ{[S_jn>|!F5\5SpGr˖-`Xׯ;birԓ#e2*Λ)~qQ?6 @[YY+;# ݫW/ HȨW^̙Kw+?CBvss{Exx4bKWW_ѝ PSL'a޼yE5rB|ƌ3fhd$tttpz Q ;%%!ԧO3(77XW𡠠C2yKKKcXL''}ҝPNrrr\O?ɣs䡢iȐ!SR2 Ra;;;u@&&&m2gaalff&쬫~1 CBBBBB9򠣣'TT؏=BߟLPSSS1655/ܼ< 9ݳ/ĉ!cc㌌ ZT())IoݺL:ouu5~_MKz %///)));;[{.)))''G1Bo 76/B_n UOJJzB vhkv~YvjMnTsO<?puu'BVQ2׍˗/Ws=t𬰵=ra+' -[W_>~khhDGG4(;;{ĈQ2z,Zh޽/7n\hh(˝0aϟ[-I/b۶ml6{IjNllӋ/坪 bѝF:w܄ \nxxxdd$nݺuРA޽6mȋp< 77Ν;ˣs+++777KKKytn޼ٷoĎ;޾}{ɒ%ͺڵk)))G_*f 4Hԟ$ʕ+B2 Ç8LLLdmFF1Bh„ -NC >?l0Um)ɉʒG޽GE DDD!޿/>}OO͘1C ȶs޽ \7c\C#}o>|o^+}oJm%%%Ǐǿpd޽{ ,G*Bݲe >yr4~aÆ8;{ofhhxE|y<>hРAQQQ8F/}݊+X,֑#GfϞ-n=<<;@0y\Yu T١C\]]_~ݯ_'O;Vy955͛7o޼Yyxxxzzҽu,x5fw^WW7rH=<<+Ώ9q yt***Nsέi>s v%**>ͨ3fL=SdڹsEEE\jxx>y=z@c}ߤÇ3GmOo` ,@_xQu=555-mҩS=z<4vڅbXgϞ+h]]ݜ9sB/_nOܱcBPNOmRMMܹsԩS?0}!Л2Bqqq7nܠ; ٨1cBHKK`ܸqQݻ/_ʣ󬬬7oȣsEggg?x c!MM[nѝrxqqqF|)S1۷oG㻕ϟߚQx"~`ӧON_:::ߧ;%֮x'Oӻr Ѩ;wNCCcϞ=gϞm͏J燇7x?ӧIݻO^QQ1z蔔Qmr܉'={2ֹs\QtJ\F+**NiiiZZZcǎѝ YaaB?A"w.UѲ2wwwŋ/N_;JKK;@wv!djjzMiϟ?!T\\Lw: ^r4]ԩSff&4`֬YnݺӝdggfW^aL l߾?0tȐ!JS\PP~x|ᑖonnNwFM0akkk'LOѹ<:)S̛7Ν; bݻwv횾3g̙CQяfxyy)Ǚ.] ,WaH/>Mz^9stݻEѝQ2onׯߧONٲ(vAw.@N8JϞ=;9@br IDAT^\Ѭ,+++^`0Ԓ<teQ]]l6mZee%h|9֭[E+==m\,(&&&&&4(x򥅅BݽܤtR%9(#MLr_ cϟ?y<:o1>f͚*xVVV߾}B?- ` /44Zˍr Bqر#)Ϟ=ïhx<|ȑ#4"tBwjRF9rssS'9СC d?~G߿ܹ3n'8zxx{yy]|~Rjl6&&&W^ݴiB[n_.//;;>)dɒǗ|׏?7Y@0k֬ .  `ff͛tkU QFӎBɞ>>>ms'Od2֭}Z=,̙3YYY'ND#""BMM-22ܹszzztH+W0L|KNu7m7o"vJQԃym__Zz!33<|={NJ"111GNU7S|Cz5cccPN"B!j 'OV3AAAk޽{\.yLzzȑ#?~LQT]]BÆ ۽{7Bht'DL555k׮ǚFGSS!q]]݄---&zkMv!~gɓ':ӛLw! ]XSNEߟ9::"v܉y,W[>}wܙ_WWv= јO;>}:OOO_~2^r/ǏV"҆fL&ljKΟ?൝y@ x!)|(Bf5ɓ_}B/cǎ;Ə/cM 8}4]vx"y+ZBB"FrőwIt͛7x{AwMJMMի;xEIQշoߔ&) |򜜜O>](UV[[{'Nr\~nn.8(**Fmǻt?3驫LLLfϞ2_Y,Bɓt4n$a0FFF0kkkxO>㏮"/nj;5RF/]TYY*++ MHH;xQ8ʜ5k޾}i&###1x'ذaBڏ?X^zFDDܺu)ڵkn:!J=ILٌ y|;B pqqa0Fsuum^~Si>tsׯ\~>. *ȣ--- MMM @,Mσ :x`Ɲ1cǏ֤޹s璒'N믿1_u]Vn E &Cwt:|?OϜ9wIV;2F3tPW-,K沈oڴil|$z!GPRRR^^^Gsrra>߿dH>{ĉf!%''+1?_-Ǐ;;;K={J8xQĉ׬YӒZ/>E2#Ga>_ߣt/܇dҏ_ΡA R{@yWPs zӧO#222zxBhӦM?NNN.((PWW_lUVeffz۷W޺uE|>oU͛7޽[RR'2]'(|yq~N8N``I$z3(׋ !hѢ~I[[)Z` W^/qܹs555޽ (*<<ǧ0??ȑPworHѣ FtR/^<''lkϴIfŁ\]]-~02Jyyy$nee"#z _xannNQɓwܹcǎ&CxlsBBǏ]YYfEp`?Mα58V!/_CTxHY$/ex[qNo޼rE͙3_cu)22ݻw eK54uSaaaۙ6Lp`fX,_]腇aX|>y<.%Ν޽ϛ̖2d oOIF3TQ/$m͠A~W@?vX_WWGQTfgg7oLMMB/omH.(<̄CVḅG;vLڵ޽{nnn)**:pܹsB:tS[vO0SSӷo6ihpeedd=zÇӧGm{d!$$dYYY|>ٳg"/^pѢE7oΝӴVι `=K8ZXXgɓuḢ>"۩)((CG9G66u/^x1ceb̘1މ#GfffxtmL|^qS/ÝM6YYYܹs"TWWUWW2̮۷o{{{OCqv7nܨlr{nP[sӧO2Q?OI&MLHz..]$ri{{{6x5OibbӘ)Nx2e ͛7՟ δ6\FDѶAz''QIݻwʕYuhs;mlld՛IB7(͢e=k֬V04**͛qJa…֭rιt#Y(Νg͚%\N*ZHHʢD5WSSO՘Eg޼y͓a40>tBhtg]zz{"?~~999ɼs?~0̙Cw>@ ܸq#88ٳ<:::88866V SR2 Ra(=bիWHH<:0`@HHyX=ᄄhjjGSN3?Ue'TTC}7tg_Իw˼>~x2yr?:{޼y C2{Br .n4şFzC޾};,,,,,LeҥK7nܸqeR)Ύr\W_ɤ+ 300h=/_|rR tg$B^4"cـ8pNE[$׳gO;J~2 Rcڵ`,\LTѧO&&&:99 2D߿Ç 0`;@jkk߯ ;,e4!!!44ŋ<66644DPSSb Ee)O~b (()vXXRQ׆)h߾}†*6p@yt>>{?#55ǎϞ=m__ݻs?~|np… /_m??.]ٳgp{ĉgΜ!'o _޾}SLԩn8q"'';vǏӧѣG?|3g433Çٳq?~9sogܞ;w!n۷ϟQQQ帽pB޵kWee%njiiΝ;q{ɒ%긽}v.dm۶m|>-[bp{˖-'Zb^(ڼy32L;^WWuVfK.m?6oAօ76?|]EQrqq;ܹ!&LM6Zlo۶/^L;w$qplϞ=$>w\?p#GH|ڴi$?x@@>}H$.>h/I<66ą_1}u$۷o%hw%qyd2VVV$Hގ%\MF*/E͜9Tj&&&$>gΜB&s}&)?~qq1n/!`2&PRZx1. EXl%+VMbժU!|vbpUSS#q]#Q$%%:ujÆ Zr۝;w )Ȇ6,..pBH l߾!~zS4l)>>COOnݢ;/@3X,Vbb^ijuVff&B\ 8qU4<8zhmm<!`0< ZA\\n{yy]v|Ĉ ;و#ȅy QY!4zhxʈn:sPG;vw1pQFɤ+uA@o`Ph ڵ,hfСCHHY4tg@e֮]KwM۳gQZsڵJQ߿vګWҝaÆ7ҝP<ذaN<TTyy4~'!Ć1 0j(N=H(Hb دHJJZb^ bnٲe˖-t8/|і-[n޼Iw"(ة~ Cq֗͢Q s֭[n;f555!٩S'l___]]] QFWx%KWG]]݊+LLL*++[sܶn:f̘Psҥt֬Z*///33իWo߾]z5?|099#ƌhѢݻ/^!nݺ7o"nڴ˖-k͉ׯҥK=</yOrĞ>}rrrjrHssW^/)B58|ee1EQ;v| GVVV鸝ojj*&(… N177B䗫_޾}۾}{?p4V;҃$T}E2b|>nx<6M Ru}wwwmmm 2 <"Lx,6bX,$4ʨ_f͚ZbEvv6#~ѣӧOG 0 --妦_O\RUUgceeEz3.3WWW(E%K|t5/_ю;9sX 5jii#a2/K,U_:t6. !FFIKKS|'}2<^&Y,O gϞA(cǎ;v48A/^pѢEp8o߾ iӦ-X%%%-%$$dYYY|>ٳgr!O<ٱcG|||k~(@5///Ǎ?"uF޾  Dں!c.]p[OrqFSSS;;.]tqÆ 9zhxx_|1tPлwΝ;؈|!C >\KKkʔ)Ǐ,IDAT`bbή066FeffQ^|NB,_ ZS۪N!6@HSdSsNPXX:%6sAx;v0c 322x<^ZZy4HpڴiŸF sNP*^F#""e! [PP`kk+fw5y]|'7o m[prr #@111y444CBBO<@N|||555x CNdcc4zh)gϞ*%2QQȼ._\ZZ󳳳ϟrtt;/-~K[Y,"u0-Z;@ -N4˞jjj'NLJJ;v,yݻ#3(O޽b7\ѣG=,T\7 Rc@\x֭[tg&rvTT<H#**O>/B@Z^F4w޽{;7/;  J4xyѝh |eN1T̓:ĭ[͛w@0?ҝPKJJ; msҝENeKl!Ć 9KKK]/ؿ^d!m?H}|Gt'baϜ9rѣ'Nv``СCqرcw3fÇݻAAA >x̙3`VI(('Nlڴŋρ2z#Gඛ)?3n{xx2/඗.?G~Eex"00!JKx t$F 1bnM۷o;wnPFU {ƌ hO>Ow.-:}t'^?>tP% رcǎ; 222;#@N=P7y_Ne {*MND ?@w"(ԩS(7UqرS޺uKNٳgԩ>S@aAÇ'O̔S'O?PX)S~=իWLr)@B  L'69z(n֒555ɳJfϞ:::߷on̛7۵k۟>}Zp!nGFFvaaaXXnر?|ⶅŶmp˖-N:m޼߽{rJܶٸq#n~zڵݥKuW;֭ۚ5kpd=z>gՋ_ܜHdZGѣGdHɟI敫+IIId޺yHaÆ֭[4Ç1cnő눽2pիW?yd@o\X,\.khhxee%xII xQQx^^[XX>5~ĻtBix^H<99ĝI|xԩLydEQԝ;wN:;9!Z; ` i X,yP9٩s8%q 700 qMMMo߾=kkk xH\OO---IĭI]v$nkkK۷']1;88);::ӇIٙ;vH'qKKKwqq!q+++wss#q:t(wܙ===IΎGA]v%#G/}Eʩr6ܕT (s÷o߾}%KНKa0*/|>_MMMMMMxYL=`322dۭ[jjLvU'''uuu{{{aPF8|Inܸ!w5i$MG޿?l0;6 @Vb̙3lR^^{mUɓ_U\l%%%999Q__?<LNN(8x֭ nmR\' Z|ܹsBGVE!Ν;'?CsslL8heebiJccc:u)|VX,>/~H"`YYY81(իW8 sss[^^yҒR5gΜ_U|JbHQdZ\F|8f###KK]۷o_XXXr_dZ\.V?f6a„'NWWW7:eQE#\޽{סCrlvUU[ZZVTTvǎϜ9S\\,JKKEJRll$;رc /) vnnn(Q˗=zԯ_;wWҗQ86 :u4h |C޽ᇪ\rS/2{쬬,>31'⫫8@p8o߾ A .+$ٲe+V~:}5 ^|)>I&}ʼn-^pѢE _~˖- TqZ_(իd'''6miigͺM6YYYӧXfΏ=[[[6mcc@Qӧlk$韢˗/ݛّ-߸q+++LibbyfUX]]׫K.YYY }2z?XܻwONϟH>޽{W\)N=JIIpBvv rsspϟ?燇J8ViiiTTyhfooХK==K2 h߾… Cx3(fkF%Hj2 TŜ9sz-  ‚2 TE޽WCg @@b_}իWm۾{ɩU;9㘘w5ee<U78::ʩ˗O6My_@ Z (PٳhZ`7nڵ+..CCCnovذam6ҋe˖de˗/0RSSI믿&~ӧOI<00P9Ee;{I⡡pW^$dɒI&$ל"2 TٻѳgOJ4vi>}$|'hL=H(H(H(H(H(H(H(H(H(Hnmݻ[C׭[|RPFAKV7ߴ'N=H(Hv #O.>>^NEe]Wܾ}3PpSR2 R2 R2 R2 R2 ?$3?+yz⩝L@"F0pb 53!&12e H2!+;LBʻһ||'M=N~z |P )(HF(†Dѧ#11q߾}CCCoFݹsnP(.hi޻woyyOc={vQ:|{Ɨ/_VVV&'''''WUU|rIVT=\a3 SRSSM&SYYLXTSS322NlR\\xW^ˋ>7jXG[ZZ 7|\ovIM?+L^^ss۷&''Ý::: kkk=ٹsg]]СCϟ?yXUƠʉ'gΜYGGGmd~U]bq:by~[,p%/8_OfeeݻwO?0&$$/^zfff۶mYֶ3?MBf{>999}vf\mUUIKK[r A|>_SS~)۳gOBB(O<Btww_~n[+W0FBQ .9rŋBt:W=zd6M6YUU߯:55tᒍFcEEE__>,))9vBӚ ! {zz^oggѣGW!PbZw}E!Diii]]x<&n'緶|gVWWWVV/A윿;FrUUU铥CCCP( *cDf}6nk+*****~ɦvڥjfff{{`0ܜe0 ._8Vnw8|OOfSU5''GUM.]cUU]H6Jbٔ5(#&2jP )(HF@ 5 RQBjP )(HF@ sgϮQ^ds(!6jw}w/E(69ټ o@ 5 RQBjP )(HF@ 5 RQBj O"bGGGqqq$WDtQ>}:::gff";/XUjjj~^ 5woZzɴK`#&BjP )(HF@ 5 RQBjx)7j2EQE1 }_hK$&&۷ohhݹsF5 Di^r8EEE~a]-iv[^^vc={v](.{O`0xԩTTVV(mmmVUUU}888;ߧl6^oɄW^}Wiii*99ɓw | \kI&fddD:΃&&&nٲ!^}UG5 /|ɟ777߾}{```rr2..^u@ ЇIII>qFJJ|.\7n,閖dDNN~ۗ܆iPhxxfݽ{w%;|&S׷gϞEQY]]]YY922߿GۇÇO.PVV_{^S[[ƬVݻ/^(omm|ccc \n~gg5.J,-- B`p%`MlT駟ο  CAA,z@ O_~hLMMmiiY fz{{vivRU533]k- !TUCl;wNtҎ;TU˻vJߌQjQD1@Q )(HF@ 5 RQBjP )(HF@ P[[;]kG kQ8j܏??DrEu(69UUs1jP )(HF@ 5 RQBjP )(HF@ 5 RQBj ؐlw  J&Pؐ룽 @jqqq6s B +&IENDB`./pyke-1.1.1/doc/html/images/PyCon2008/0000755000175000017500000000000011425360453016134 5ustar lambylamby./pyke-1.1.1/doc/html/images/PyCon2008/bc_rules9.png0000644000175000017500000013316011346504626020541 0ustar lambylambyPNG  IHDR@ sBITO pHYs IDATxw|l/MM#AR t(E^S>˳D~ҋti{0LffM6Ι3wns0@נ9\p)R`K;.v\ XaDF6+HrR;v̢zJ<==bqRRRjj d@I[C6 >B&***F#qǂ0GŀAڄF )SL2dv EPRRgmٲE.whɘ1c-[裏t[֭[ !-[h1Rٳ}^l`0| ;'B89sfɒ% :rH7XؗY F?//, -,,S bob{]4%RٳOΖJk֬4]h?BdF#9?ƍS?3! 2 vA/O3mnn6Q*ڈ#jjj*++SSSomlrR'HSRg;f0mS?XcXw  2|M6u7K /V+s6l0!o ljjj*} o\x1ŋ .}] 6ao:ʕ+|Mrr2Y]7%Y] p(RXR`{G(/ZrV oߏeɓ'Ng{p6aÛ8p~wWņBܥEXLtU\\.śoi٤3g%$y G.344|h4:?nb ξc-Yva:)+uzP$ဧk楘0aeZsm2,X`ubҥL_!$9 Ƿn[o j{NwVz:]-+mDB9{e=tPk榤.rϛ=m=ㄫqҥ۷o .d={,]"4ͺufϞ ^EPr: kN5g)Xp8D[PΖmr6@ X)Sƍ#/p ''k qss 5k͛MW'ۇ@ УpMš|,hjj"\ᇶFr%r9K.%۶mÞ u>k8>vCC{k]\ؓpa>o_?N$ s̱q;+t:___۷wt6[b z+..&]7ŀhF1--?;;k|A;m+HBaȑ555jUUUmQ ;VӃ t\rDu3\ 6#LtyTjB_e &Ryr9H޸qC&Ik#uѢEvSZ\҆yٳglM.K,!uvsszjmp=`(vD2n8r瞞=֭[ZB߽{ʕ+xp),>|ؚ\cǎdEx]]$:x Z3̺:یq1`@7mooӧOw#kz]wP$׮]K_$>>ȑ#5.E׮]#OQ/Fs+ qqqD?_}0m;Zb-YYq EPXX3Xðqm߾ r\kD۶YCgbh<|Ν;+++z_PPPZZ#<d$&&׭[o B?7m6i[? B '11qD]kCюT#G={633^Vs\OOOCN4 p(R@LF{'ryii)YaXLL Yb4o߾Mh4r`:B`0ܹs,tz>//Y Ftt4Y޽K0LrCV'KX,9!h 6ABᄇ%*,r/J,xdB())!K|>Q#DŽ͍:(@@kJdP( $K$IEEYޯ_?!ANi{5u:ݚ5k&Mrr8N\N FH$TWWSt(:TPhh(EOJ W8p Edy]aÆQtL3c^wĉ#GPtMF1O;k,Aљ?>EǼE(:DM'xq,EFFҥKsrrL׮]C>W_}ՆxqsΟ?ጌaÆ_\!T__O J$!>o4C (zL!f)幕J7Y"jkkɒfۛ2?TWWGg̍UTTP؂)s~EEEdIdd7Ȓ;w$&&%dɍ7(y/^Hddd5,IMMTt%ѨQ?N={vʔ)dɸq:DӧO'KLw^СC?0B(--m׮]455;wڵkbUUU䳻w&G͞=,ٷoYFT"WGM2EP%Ǐu0aF!KN>`x3f y#(9 KLSF3a;v,C!PH655QYRWW7{lϏ,TYY9|$00\!TRRc%aaa۶m#K q$::zӦMd۷/_NdINNO?JHH "雴-[<@صk׿?7nJȖT*}WgB*JVSsԡm(:ǥMspAuF :Z+:SgΜ3f5پ} /`g:thmm-e]rnhnvzz:˗/K$`0Pt.^RpBϓ%ɬ Ey^Ey#t:Ey#4 E R 8BHRQt(nBHPPt(!\N1cIRqGskCC>ӔH`g}!$J/_/5dddOD"77wԩ 7X}}-ZDۚ*TFdO>׆3fp%@/g%?qF:dsmhh R`x'ju^^Mq|ŔJa#'x76o/:ۖnD;v7oުU@b(hx<=*N1 X,0LyŢOle!r;xJBSt(a#!777yi`@@ѡ BaݝcBAѡ <==M:h;vP;_{'|!Zl|?|{;v<#ҥKreh'Nlذ޼yy(M\^y湹ݸEݴVR}G\s_V.S.4j):4Z,,ߝ ͦP6!EMѩSt(Q%"!EͥQtLIII+WPtJ1_T=z4E20aEѣ|~ө3f8~M0C,YB1!TRRBn?/544Pt):2ckdd>GT*<Ήbao h4&$$ܼy,ܰaʕ+m0''gҥ׮]{bbb(ހHoFq|/X:Q\Uh4fL^xoܹs(9pSO=UYY/Qʗ$%%Qk׮QcPdW^S^ڕ+W(sC 5CR6`O 6|81cðC%F3OӇ B ʯ/t8#&BhZJ!NJJ"K4 eá4T*r D(l|^YPPHd2ӷ@ {?cԚǏO80J :oghӧ=F;.\hG#F4|﫯bL&{뭷 2hLGnOV^,ŹF`Sh,*****:x`sssW\ )Luz-aQQQ)))E |/+p[WsM|||؃/%j}}2Xh4 0 +,,,,,&MyQ/wmC,|N'rH^IPRgGQNNj||yqN\.Gw= 9;z0R:,w j|6iӦe˖%+VA<3yL)BHm@BnG@455yxx87o߾Yf%sٽ{=̚5g``}G7:˞\ nݺEje K噙dIߩhYSRY b:ېaUCǪ@/tVi{&eDk9n1y:+=نlw)Z삜bf;8?~B`Q~=LȨ4v/8,0}cl&LC :g>-`{T+R9"n k.V5~>u=ku'aÆT ݐ3BCLRGUHeU5<3,n1 X, pǚH>6SRQm;ME8O?Z=Qm,{qF\rE=Q4R!?ve0.^k:kwMBH)>WR(huxN\ӧO>+wd2Lf+T?'Brp)\bSCo4,? рV (pv1Q 74R(usת>vhOOƖ+&Ytav5 p&tV(3BoMq:*sZcR}!ü+7r :%!8,Gmse?fY,Kgu!ɠwׁa4cfoO_P%|>;'n-㾠QJG>ځ΢5?;UP#p%=ХȬ% }qkv=7L UuF=16v:]q?[qA& WCkM޻|UK+\ >],:=qE<"6PZL. N(awGF`99;IcU5Տ/3GMiPIa{sr'\\£4*7%dTG-tg +Mmaz"`8^+WU\&=9Q\sc?_\cV]ξT*aOlZؐB^cjî=MlčҊbiEF*~-0Ο?O>0lĈβQ>o%0]x vwfzELQ0,-5 ƨQPvƇZHlA``0th4͕ؖ E8ӦM+))!K9iNd$55uβ;|JʴtRuYW6v\ c̘1c+k|???@} ƙ3gm6mڴi]*(At:,c3AiiMz=iiir+=9r=2x۷o%\nf hv ˵Xk;'R`N"c6vX'V(k}vo?UwÇXC&8N:NSTd xβ=A:t5>&MT\\LNqՅ [>:t9˞\ JJJtJEE2ѨeUd\],-W5jテ /w.. k{/v b_㓽yl"0K ں粻^ 1yB?L/龢S}-`;0LOOOBu{R|NOpJI#9"ΨVe+Gf̤y m3}d=FYBM6*5 How=Pv3ΥͯcQ/ %oݗUߒTycJ{mliӦ566RR"jK]Ct J={t?rr7M<E'?)dXtf{p{숉_~ccήب8V 2,;YS(L\.sm0i o7c=YƦ/~m:u쾢S396O6k ]"nep EGW/  |y1qi+Owʹ s vN Ɔ lVzŗ\zB \ ao,[<`:!he5_8N_vE@lu-zC7גpu@KуȮjP9`OnK`̬lhҞ7KR^ə뙕ζ3O/ ce$L6Ծِ?e^*;( $Z;%׏{`0{CW4*G%c6ydgYc3M2^V:A4a y^Vbj ڦ2\)uz.dB<|b}7b3Ĭ 'Ͽ*مr\g0 FV_-Sݩk[qbIѻ2M|y ^WɔJk:b[i&q(F R6j'n9zh5>{yXYTp=-&dD/1khhuAY9\רusnce-V,H ?+?P~ n L﫰鷄Eg: V+J% BR1u)Ai[oxK HP/l{!oϞܠG'zs P .SD'(T˔/ݺT&ʮpE qB"'KH贷yfГǍ.[w߿5wnweFZC~Go5Lk[vT@C?:ÇSFO_V1q%U'~{ԮE57Bcǎ iMiii.ӫafmnԎyߐ)h_V%#OghIfa /v9]oĕZ}LY,ˬlQ ^ȩdgaXJ?QRwPEMEU R,:-^Ŕϊ8L !Ņ-^W]C|=Fgg'G uGҷ _01hx{1h5 wTd+V1v yC:F{ܨ)I/{Fz*_V\EqS'pN=y%V2/rʿ.JQg~*ƨ]V&hֶBqCҕsh4Qޞы]sEJ]˧=НjX,yb|d@=63+:#([hm Dғ}9EZ!ԬK{S4i׍[cCwJW?浽ﲷkIc~` M U2߻}{]={⫼gOڷIH}y޳#&Ύ߻ܒ 3H t k_enݚEg>0jVW j}#AL 1i+|d[okzʠ֦Kan-abzTAxNhs?.͚&=+.O%xvxlnT7^h F0 kofbi BHS`Pַ&13ߛø7f(:ť]ś1  C4 +L~mFh40 0.ܥ0Q)U~}>gLX_~Un~j_';dj Yn'lpnS!+KUflmhuNQ()T%//Gq<|E#zJdkE>@~LDᇍ2³_Г0h'DӦE5>Lt1DA%ўbyؐU *!t~FlϢ+c !j 1E!5PL7zd]&؉Qd'Io8~jf]+]1҉"tk/Un?#_ݝ̔xGޡOGavH+鰄Aw_:. c7a"}y0pKom t3ٿ櫡#9RDQԒ^f JàBg0TS@feގ/;,$Rt?Ug0s*o_G~~~~~~ȻEH V{ru|?hilBc[MElm`[<`ٟ>tF\mXT lMқ)1n@_/:㭂b]d`rGjBC${=6bcWPUUe-:!oOK kMX愠aDbum nٖ!n ާI/u<(83zBYtƠ5:;fsΡc2*H҂F)N Fx #nRuYnb k 2'/pPq½^d 4 `&boyUf18#GH;M >(h[Q"& vscBfWʊy_i:Ptf 0Ѹ 608KplNwL^rc>ʁziګ>LRH4^RokRٶt>qaإrw׭bPk0ީkS׌bi^~Do&m߂CE}s!Cp?S !rqtj1Ljp)VMxLgđǍ8Ȁ86ޛiPO~BNOܧ#_yB^} !.=: i"pm;M>NFS^QmdanP>8A2m"qs~C 7 7;5&ي=>ի[~ՒK6sK>7հx_}mg[MZq;&-:@bPcb SpY#[>6_Y\O o5]:#&T*#ab[mܩonVx=ۂ TH;u !F[.QIͪ{YNL&n,&9'ҬVk!!! !x>1=&$5$ƈiVO+RHWO (*Xc޹#_[NE^WR>7GJXBZn}oy,}XDUy|4U !(;bk ZC..Z2yIn9%yxeIZ;é-AFCͥg_d2q1c k|04"``[=1g3ǀUۜYYW/!QkeޭW'GQAXtyE(zÝ^VۡK -$w5>B m`l­2[}c=`:eL$eDG1S;ƒvxLZ Ko9 zDU҂3ȝbJ >I)CdzT:.Ġ4)u46W sxʏASSG>Sgd U7NStۜf9bAO;=l,BvS?Uf]z;Ɠۼv?!cX``rk8zYȒ9sRغh`#G,,,$]t)$ĞY`xRSvN19ٛO0X߈A>!LN/^QT޹UFZqi [\{sJ>R_7wx/Fɉu-s8Bg'Ja)j{mV٠7ھn7Uԏ #d< w@wi҂\(i]ԍdL4"ķ;7xq<*c.[[,@r?cZGggqslϮm:Vzt GwY۷N[t/[Q,'9!iyμQ!9Sq8L4aVN`YL"0Uk*>[quLܟ/N&ĚQ膗Aq#gڭSoAU[gvKouvWQ7UyJ2x Uuܦ`vvߑ]@*2$}?Aԍ.9cZIQVȯ8p^u]s(0R3bBlUN 0E( X#"B%v>U0 Lj6ʤr;W+\ $9`E|w"Mg yA–=oMgjG]$AM< y}g"޻I{yZTxo8ɥp*~yrU!'1C&F?aR:Ztzeen: s2x4T:}潷_{vDcN'B#4)/:M$}xk m^3zBA. O[[&K87wSI { o8;Ud#I'ЀAhgK-M~ɝ&da&?oCDk  ң7x1̀zZ8CR}%aC;oy(i_304skb|s?4>sjmwt0p;ݼ7MaY}}jF^|yBw`OoIسgCa%Li.8+1MjDir?;0!#Df0&Gnh)[Vˣ=H6gFo" ¥Ȩ} d&L0aB mu2{ϊ |~VQg~T7:?X~`5gNO-~+yDCCZx5^kO/Unȹixz8'r"!ty0-rA(F^|h:Ф;GJqm~rmum&BH 1 Xg᠈_ܨno /rxL!V_-S7_,%; #[()˒Q%F|>ˤ34XPߩk7̸`PdRc Qkx3J sl]k0t:R[/ u!ycfR̞ђϗ\V5ǖ pi~tW3W}xbаXH/;$ﲋ?MiQg\~gO8.0u|QhPOuʂegFl[ͮ6NxPR~6!Tq  Y#\r!TyBW5e+<1җnLޏt(C$p(0-C/.OG)tlXۃ'H q=9:ZQUXǺ9{,&ݔ`Ҕ߲ 4zBHӟ*'9̉/oNjȾ_,\"`숉{GJ9)w_wZ18'lZqݝw$Ӊu]'vExseOq=`ZFr;2uW&ϵI#_ځQ|6m}O_O? Ò^С?a 򞯌NXznfiKOwp̌:ý}">nOe6 p#upqZݭBiyQݬԫQ⋸`΍ps'/Z.iP7+t*e8~<0a`GH({plw(0ÑEV\́A!~ߍg^?ZϹK&^epGvL͊e}P0Qih_c@q":[PN<^+)--) qD`ʁW~t IDAT<]"6pDэnFTԐ%|>ǧsU 2س' wN4z`$''wC9~֐ZPЪRIFFFhh]\ 5z}=p)[ q@67 !B{V(rؤ\;vp @gܸquCXXg:Н_|>?2U*g=+W83qĻwv ~Ldt7•)pU`E{_w͘oF>0lβ^|,HMMu=@xGt:\ >}ڼG\|J(i:\. KA@@O).zÆ @9qDk|$%%%aaaFUe,u0[RS6j&q E> Q)! uB!ǝe 7֕&ZrJkUri]yu[ŎH8v<#K7.?k]<_v?ð6c:R0,1[+zNa|dj<&#AWDZFQܠnv9q<^zqܽ9=hElziiZҨ4ke|-BEg"uPZl'd%$7aI|Bo˨˛oe}~F!1i֗7:;h"q4A&մHoJDQP#BxXJAHÆ sm&큢Ӧ6aFH`G Ҥf7i :8՜Vl?Waj\{^svGVL!Mj؜XV_t??]X҄4j5+`w $ɿU/~{Miz An~ej-O|}AktWLmP賿;ǁg_l1ݖYHoP?Iva4\vm; 0›ӇsJn! NvRz> qkG-} 9qabCM΄gW\Vu-FsYhY%04D[Xk휦BHdĠi|_$W*f62z \ g8svلۧǴiԛAzD8`/x !8Hl5,Ͷ~F<~Fَo{7c[@'ҴwBqw.c:JڠQHo]P4ך$:70fMA37\~׋Cgٱs!, ON%㹋*\1[f.=;fテ(Xj,Iz ac› eϮq<(%i{v [l!cd+/R|BC}oZ/0{a)_=Awײ 7O8no4?q"BY*)]̨95)HjKw?VPej3Aw=ū_TxK-Ӈ5).޽Kܾ}j|3DK'4 JyCg@q2x"(_(e5V,nI>]jFF< v hC "j HY{)ӧm\\[qn{.F!u9ZlWF@UF'Zb/'@/PWDK"vU^-71 [|>BHDY'Zb/+W$tp)'++R#11Vpa" SƊe7ōKꚂ¬ v= L0j|N'::R㣠 <1׋{,.tF 2ĢN{;N2bCc: H/dq11$/Xpo gv5M;괰Ѡ(eg6f3h7vnnTWWw;1bq{(J(F>bu;F t;ۻ1ݎ vLlllc3tPo_Pm) ]m)4TTd_$$0)4?z9kv~3N^;N`lǁ?p̏<$Ҩ'/:r"0@JtUޥ-?>iow9zk&0\ Wp7Rfns=&>1aic;4qBȑ#G?& n7 AW+-; w7&/%Sh^'̏nR;vHD)TؠSpýYL(B(yʣXOս%DNSe q*{>i`Hh_xP+ؘIX td{yϐAw4lW)ܙɔ}}|Sۖ`L&Ӎ>%QR 7V}QR_nncŎGcU5U@e4rq][ǾIy6w=  pÍem3!:YܘkyǰΎ'._=cǎEbOl6áoJ\ ;0K(R^;QJlblcܹP"'-W ѡn|Kt- Ig1 4y<6^U߻pЩ1rߚ!xd4t?*o;)vRDJMiZB%ij(.v,zڠ\=<H"Cz!JZk]m; pQ%MY57 t&G=/Zwhɋ?Kll-opX Nv,"nļa<d2ඣH01/ic9}mpkǏoȃs{,zv9YsMgbF͵28v?|EV"-uwF yֺ%wߑ5UۡCYX陊R@J~/J?7̏VR]+z>Db|#8nF^}΢'r1 =gcb($x `R Zunir2dN |#SR}#S"Sw=-h2M3;d;H)L6n;9z?lyՃ gvRv=rv1p ܂+-5CKb\PeWՕ09?c]ٕdܿ zEUUl^U͇>ѣ҈ ^LڷUYkFR[* 6͏5GO C!n(S+% W='`әJ";p?aS-5E^!:ͱ-8hn}GܸΙBcď[]|:C H[9_p#^!qCf=}Ê}:?z('sd-j&Fپqp؄ImH)܃J\t$vlu$M} K)oRIY|}* 7~'D@M|bsɭ?uCFU}' v4hhL>î;BJrhh[! ދG kC ܿ~:Y{FSLdFT0t&g | 3WИEpemM׌i7t}= ){:NyhGISKvC9:) oز<+>RI,.F':( OλN"-jE:){xrEgļF{.D"ܰI+*5JZJtPNyzDa3U z&: K`Rp VL>}j"~GL_t)7>`pH H)@;xkpvW)K9s&"R ~papv)R ̥7CՕ6LFb[ R ;fQ#-- }~ YklF  MII7n\dd3#R "/_5>t:\.<~?&&f'Hx<ĉ}qxxĉ߸qF-^8((ȡ?BOyyy:th̘1j!tC=D?p3$ M6EDDlܸҥKz577??~#Elo֭۹s^G]p6pJ( مW6f!X]VTq8TEE@Aߗg%+זt$e򖺚ѪR?}S֬Yܾ}D"xJ|r,3fLnno|~JJƍsrrF1nܸA`$>dԗay@QJxee'D-oQz`'}3w {#m<:xzQ6ږ"1>>>eee,oڴie˾{z>$$Kщ'fΜi%ڣGΙ3JKHH0JKKg:,y&4K)i\kvŀ ;KFR]Tw+$^\_IQ!z)+Bhʔ)XJqGO< PZZZ\\\^^^ou<3/0^wP%_vFQ]Φhw1G9#0+jĬy}⋰a@8[Idʈ_z+8ԩSm`(~oEK,9:'*H1H)+_?b<A9Ox2B' >w ':+fUnƎyD QDG@ `3]0Ot)Fk̥pAW[pKhU2cqQP((ƖoKLZ@,##cG{n:o(LLq#}ӹjnݞ3""yPQQmaaaijjj(|~ [@\ !Sh6%Ђ Ƈ#B჈Δ찄 |O6kۘ2XE"*8R*VSTm^P(ݞ`0`m >=a„!\2 R) q˗wILL$0~ R boX")LAOAJkS\m{ϛ$ۭ5@@JwzR+_wv؁M2e/W0=[TL^x1,=RYY ןtn7=رciiivBB-W {@֭[ƎV͇Giؗo6o<|k,+:rܹs=MMMÆ +//7N<ٳ֟z n|J>/heeKKK/䔸wU|>! kbC ЍQQmu,5k+Yf|>|';=@7X,֞={Lbtҥė_~ 3Leeeڱcye~f2d2D"immͽqƥK,8p` i%\ w0/b'OΛ7b&uG&M:~8Q#t:}͚57oƗFW)KXt)}O{JP,̙3\zꌌNP(~֭766vɒ%<󌿿?ѱ p}R'N߿555z/888--mXҾ]gB0 >:nܸ>()H)`v)R .~X㏻¤qFO|OXXԩSgRs})R ^O&''GZb_~i1fڵcvi1fݺucon1^[ٰaŘ-[XٲeŘ 6Xy-Ƽkcon1fݺucvi1fڵcK1Vg1˗/O?YYtŘ,X`1ȑ#c̙c1ԩScf̘a~̙3hйsέ\+UZZj4:86L0ƘtR@'6n8uT|QͲ2gÇ/y*p O%|7j߳rJ zɓ3f$?e=OqDqJ\+bQcҥR|=iJ]Z@J;vs)LC$1*jLap [neXDQffEԕ+W `jʫ ID+':`7 B BFhnUUicV)4V3E fk}#SPiw z(kxtjaX=z zc/<_4^LVh$:"`7C}n7M`l5 W!L&^Rŵ{.XR=:`#(t*ԕ>@'B&׋3NpooZG`0o-cC}rA{(\weS۶k N& Z˾ޏ9-TC{Q#f'QHT~:OzO2R_Xxg FW͚Q" JqHzFʨjTdz]GDEE==z}{Xz:"pBɏC$9ů[/C"Jʌ߼|BRwףY)p oE˗wRTIםrYύJ$ ֬ d&PRٴN޻_~e|{RB(Ie%-j~9@~ 5sC' |,4eҨ&QD_RWj|'+F6ʦQ:C)QWm>Kl0qYBJK7䩏+;K)Z U9v'G*͍iс+X8J0zDjT2w0;$' `td#H)I~rm$!zHdV\B$T*{͋n .a͚5J㠧"##ׯ_4hPOOxŋ/(H)Kزe !G ;v)R@;Ng1SNX,@J\§~jQ_EG:y衇zt R 6olQcժURyW=iJ5K;v)R@;:y_U[CT0H)Kx饗,V1Ln*::_$%%$ .\p` 6p{ .QtY{13N{[ Gx1A\a q#ΌX)@vs J|vGDrNxzQ/)kY'/=BE\0Clglb2! Vk[vGZDth`@+{f;) hpK PNYv}a *p:a4pmY1!Thd,8!JYmϳ^rv߬F/Awv &ZghVK[e7* sa[F97FW-)F*.x  tE?Y$fB(hW־sPa}6%h$DRm6J?XR($Nй! gUmhͮkI~G+((_=~ )-% JPÍ$vNz9NV.A]_/RBtt6:˔LQit:x@(䠄Q~ΌvR;XXv-.ԨzFUFU#)Ž{|M|GּFpALAh_FĜUHnH_ѠtZBZXSr3 *n+"b#)Ɔa)EDAl0]sfh:W\Q`\Ȉ 0j݃Ej-Kr^àT[H8":թK'/ܬiTh$|Ǘ5wPZa($g{1=1j!t3"C]޿3Lnn9YYnmsDѤPtzH%2A0\v %x55kWJ2N3>*&z&x;Џ2Hu,B!dݾŗN #69֍l~^k{-Vh؝sWw nRs53 4¾?E/zǭJzӻ??ۥwl2iT2BQH*ުλ^[a~h:e^KAJUK3K*r~3Z}L(1wZh2])?UXeQZ}Ly^% P.şF! 메?=8kN|N`lp^P5}?T+_x|٪kևmThYS5/ܪV4f$g;;,1~#{wF!$oݷa74p!N $o ƾJ7|_$>Nu5wQrb4d{{Bqag 3Lݪnʮk89"Find2OKzQW|"li#|:Wcжh$9E2):ZjT2b' ̇شGg dZ5˵?{:!d49Ee.ѺP^aXt Ht+$~ے&/ÕJ^>޲% 66H)KxWܺƇ\s'LPXoɤRFR)Ų5 rSiO/gH~dihQ˦Q:}i|imq!d23@/G]*^$$$ /lFk0ȯZPBhܹ` 8'`,u[&:;;T2Ccsf8˵7^թ֦o@ϡO{cEGN{8r;7gA\_`=1?K)n4, v$sVUN|ܣ$6*H)Kx׈O.)uzs;HynTN!Yt=cfLpix~eWڟU&qAJ<m ?ӅU'>ɁߙMURs|H!ƅYm޼Ӈ&wL)ZUooD'ݻXcNOm>{f]-گr"뻾ӌOڹEFh,Z5B«GGAN}+֞': CYu-m&D.vS$ViT#T:%)6FI74+e U%'۸]*hznVw3!>wS5rțk ܬ:_V߻TJ2sǠ I { `̮k AޫG;9p4N8ܱ}Jn|ѻxOSf35x־Rwwk/B\JҫcÒ >4nbx$zڲ,3WrCQsU㧯a<ښ _f2KΨ =[PzV'bW ־ِӣ;FrKM^Ф𖕑7>Kx,j|tEe>TrjU"@>kWHRtgFok5REv}fL?keʧ *OV܇cɍj|OǍkfjn^ɦ2 0t|I J7x eX;#Ԗfޤ"87gQ=/jG`$RZXn"n4bC-)BGz#En~q9 _ɠRTHMF~Lʩo=YP9;=4u%J_um/ I|W.~TT\2BEe8f¨,j[Lu9 LȄ2L!m;۞Ip;Qi>X[\ceAJ s~-wj Fq~!D#=CREeO.gcotZ< 9gFƿ]г5C|N*\1h4 F!SdL#r^nk5zƠ굚Fm}]]Wshбa~οb46.?XrQDBe^s|[keX[kqYv dퟩ\6;~T%s* RdB_[opNYSg[:1Q(>W17>ӝT:6M' c0sG FӍAcZfIX^#U6)*B&q`wp(ϳKN tFu0gXDyWVyWD'iJ5Ţ2 .:nf2]vp/駅O:D'V^kPuSh|k% _mʒj:/Ǿ +kՐ[)3[ue{TR 0(dlKεaOB(W ]T$wv'B~IKzCǧl:jad1ԢԴ(5wkC<ˇRA.x4 ؐ N"dUx:ER m^ /Sɔ9OB嵖\uW,ҋMvN =7)bz`)Eênd GCqBb0)MRMJ Zs[AJDJ& BrUIY!8_aN^?3k #x*Ϯ2a`2ݮi rMS5p8!!M DǓQhK)J$Q,g #O'>b4Wg|p鳻?~7= o[fH*$BLZ}P' f>PRDNAs,0_XሹlUnK)j;X7)گ%mZїFLEr&_ 󥵏Ə`ұJ5kIkOTMb3cB?-d1X4`,6emUi,o~m|O\\B(A,J&ϊ =y͛؝Rⲛ ̦|j"g!Rb{dyRЈIA#VMK-z榹}{A#|ٖ?{wCE pI IDAT=`@aE ]~+у$E#jgM!f<.|!4+lulNftT"ʔ?*/ [o|nNOԷJ$U jECOM$fhQbP\\pkfbB&V!inKIF}cBx?hO #m C'pFQZQo_,֛،qa$%YB+9^!^aBIcP)ZQ7*5ReaYs&WZQUKYu-k_eP)S"{tK!<7Єpb=III=XNo-[ #REOnJ?W\s *ԨA~ݬևy0OjW :vt⾻Ek& H;z:u`5! $?g,޻o4pӒEo]uhm3*0jƿG<aB*$DZΘubgl}8r7\uaYw6zo]fHa3G&!AJQsNMd)5@43&:^֑='>:ĐAͧ W] L57>ۓE7W8|;~ knmoH[R2)^ `Kgn5f6Hj-jR6<:Nj%Lퟲ(zV 7T]=_Q%oV:LcQL?(M,($2Bӹ^,a0/F7.`p8ۑ[M?|Jض\NbCBR˛||ptZ\ȽqqFTj[lMّɃZ)VH UR-UZ`4!ĠRt. yvg(_T,-h5K%j\ N!T>fzs|N.Lf(\:-P REzC_eDz K0_huҢ>ǟ gaHMfvʭkm8GSEޭ;,~{>ė hBcLP[uϠ'{[$"E|3e{hGDV]w 'B=FuUB&V%(j2Vսk{=wѽ̎ 8ꦽw̷gG%^cřx<^TT׷'6Ѿ*=uSvnRi&,e='.n#ăxĭ[{bbbzzsΝ;~A`x+~3AJ\?OtQ*=[fn y'BQQQ# !Hd Fl>(0aoѡuR QX}B&IZ$r7"[pj YyRVkBn Ψu'亙KSӶAVNCkLcp5R!T*( $:L q{Y#=idxw?$RFCƠBt$.7$P$%qP.|Xl,b#yEsNNLɄ42yaZO3fncioTj`ŕPd)~{Н4LjVdqo~:LFF`Hj},$-Eewϕ= K2s'|γcǎL<.Bd.Q3*.a5>lb:!r_%m&RNj/QXK)n5Yvk,kp8{?3|ř[j:3pL*G juAJ޼$_s+%rӵ&BHR_NT0ΧܷàݾO08{w} YmZ .ͤ#!߾<R.AJ(d$ViG˩8v}H).%btQJBzzl3Lqu\e2Jn9ؼo4Q)`#6g>:}Ph呏?R zZfYppC'#H)%L\{~?V:@gkc? )GDqY34)TrpqYzɗa.x#_a^۶/$6Bgx( !`4,<[\c4T Bj8 *!tMmUf2Bt*iL41 .aǎ5>!fΜ9s̞>h^yzIhd_h-D"M gEBd2b@hHCJ\Ų=HXWi-`2 ptX^E'a>$,'qH)OU6`)Neҕ~Ok0T喩`Y@t qH)Ot_G-_X0!Og0S"!N@J}"Qkyl3y00L:LQ5LЮe) r&f[,:$nnlJ$|Obb"wTpR y;wEN:5>@߽ gϞO8xR gQcRktH)`pc[yX@"i, K <%|UR LFRUd59o9fg=Mێq=111DWRRV{BS@@T0Nk.ѿd-gv^tKgqKӧO>}:Q .aѢEDz⎝FNJgPe/<䣕 ٹ1gLxCY߈=Tqqc8#F-X8y ֑R 3 4֮+M`$gamHu5Nݽ{W,oQJJG7%<3r_¢Dj&J'}~z1!FpKp;'!& A*L:|Dj H)g4*&zٽw'ܓf:3B`>鶄+$~"ph<}7e|OBBQ7LZ&/7 Ci ,ڱjNs܇#dH1&"d>{HBVXVwh2!$jO],"8F|pV6#ɋ?Kll&G0&Qb{t7M 5 LRNp N>6VO„?*zOw i`gz yp zmcJtP'%%O6WJM JT2N!4eQ"'㽆{ J\)uz&gCD_2䄟:rXKz!T"˪kI$*7o{ & 9 .aŊ5>r3CS PEyL)Ξ=5>%bE` No;ona4ן*it~Vkeʫ ~<(s1Kk͇%*Gea@$n'0~IF`z7Md]Ws%G Ѥeu橚%-RZRU{Rn_ڧQtAzAY&}l_dR)zIʔbʩ4zçkJ!D58@$iTN_*;_Z[,ELb:l^A !%ciн+$:'(eXJDYPQu ,G\W)T*[58nQi[J:$<7*N!Yt=cfLpix~eWڟU&qAx ?A~ N}٥"W„<,G!H)R T`mY :IC"oC-TE.kK cYG2ܓvt{gյܪn2T8ј$ViT#T:%)tpau2% @R 3Gϭ-q[ 3g d븬fef ^Lq ֞U>a67!NmJGݪiJRRX%*Ç]$l@J\ž={C~FXyWaHt Lj[=*b޶衳!CSy! qtJ zª8I!nG'1!T*'6`e ЩJiSCIfmL?%l޺D؛ܴ.ғQhn_.&FxP=(E~Bߖ{WH,p2M\ >:,׿LK|fez8MYӈ}H[t 9ܓɏB<ݦťVgQ`8tVܿ)ak^B4RsYRg0*B.&~|/25iRQpţRBǍ?-lwOPcnp~_ pƇ[F1BF!Gzz$r9H&dBgntitBt͡;3Ut!H$Q`hIDeeYSu#[=O!8dlOqcޝ=]' w8L&d{} %L05);)ũ3ycysC;ݙ l0O%zB`v_wsRL&@ k67 :_m*~wN`{@J\²ed2g޽ezXIgHHasu˥;BƦ j -4{zח}> 5MMygw"$j A)dލsX=9;E^﫸}2j:!%32$}%YXa Q"V}ъUDm*TbDl ņ-N},ev?n 7;O.m2ӻ4?Uv .BHhb:NLjmG{qIDAT&إ+"%x$Ɂ^4M 5.U=Drzq\eR,4jvJCoe{ .HFmWAEvv |%9(dq8*O}^9/X=nj0s'u5BcO[.!˧xwSK k;-Wh~ejҖ6HbeRIZō0ˍ O܉j%'*]xݗ ϔ֖4u43]v\|Zo1Kq~3 3gCh)^~N߾Yԛ{z36rXBay*G4Z.RWHM嚶cBHSGױǣzlo-ݧ a/QxQEt[CCfHPRؽ{7BH_l5M!> N$C:_ |ULKMsheRTaM~|ۢYŘyݗ _:,.;TԺbI~:@ aPwŮy9U +5w-), \9bQ=().̘1{w줧~kzf#6v}mq\x͹FSNuéWtAMdIٲ: !|X^R(Bf dJ siB7O~G'g44<9VvJEu֎URco.M {C҅]emMYUx_UB$uFSPYբWk4}FMHة7j6mYs[qckiGJDK"t {삹 Txa w_[%5&/M}~* ĆMvf]ͺ~ڑ)uB>䘯/0s|Rx@r#y9U >m(*ш7Dǣzﱴ$GLi1rI|'yg)ptN 7=˳-&$$ y8Dž?xENP+ɣ=]_aFBys{UAҩoFMH .1.q~*WwOq/jhSk Z4]m:hS8qbU"59># c1^VI.{ylz,’J34@PR8~8+hh-E뼘`3PR([߼=;٫&_%FWkw]g'K%:*)`6(d?=S^PR ?I&q 3׺*e'E XBIv!##S&âk@JJJJh?dұeU"`ҸNCe8MH"0Z BHQcG5l# fX V/>z={NpppRRWuq{@Iva޼y3>ۇM`{N}J @IvԩS3> W8 !ԥm:iB1'jPR88W7ڢ+4m6s:ۛ NBONF w8 /3xJ1pPPR88R.E&BHK}ySuOIqM3qSuOm>b2uFzхnaSLmaTßȔӉ|:ѺgΠD!̞ pW;ۚNp| sV<3uyTʯL)=8d2V_μ曑S*`0"Y1ZR;0677b|}}Y1uuu`VLee%+&""S\\̊abX1W^e$%%b.\IIIadeeb~aVLff&+f֬YGbx VT*UZZi/ƏQ2T&i'N0W.]XSR0=*8`.]cm߾Yt~v0dqJ$ufD $kӻroo簾Εeee=ܧ~* T*ZmӦM C9Y 6mgƫVJKK41J%MUUUC'ӧOgy饗|]wZr%+r۶m^zuVVիY17ofż묘>ob{=VL]k׮elذfV̊Yz5+f˖-%Y1۶mcŬ\{eղeX1_5+geݻOb87oѕ+W<\ \RP*{yG=<<}ӁwI~?`nb2X%^g $h4!r 11 L Mj))$.nfB0Kv%99&##ٙ\`Đ_ᅅ>>C!i&f,K}xaRH$\g# Ǜ2eT*_paClٲׯ]vܸq@aRyy 0_c#{[TUrmmm~S  cRi1JOO~c 7&22ߘdzbz/MMHH7&99ߘ~cf̘o̜9sx{ݺu3f轫T:gD$r AIo s1E9d}JMM'Mmi35&g( MӬä(5}K17m>Mۆy=_Ȼ/>#LGKw.3p|Jhbӿ72M]Y}>s_ݡ zVD 0TsuZK}9s))gxuݿծzbe.]&c('5`M9(nj- I]gM~T!lGuCI`hZ֥mlkVߪ)Xq\u^evdz~tgXwYqSo\~wBI,w/Z>u")PTʳo2%EK}`3J /<*婉KeCqW+jġ;/p rE5f_Jyl]uf0o()1V`8wc)p?_3 )E%>՟^9q//3aک]Ƿ`-#+9w3LO()V7.g'|"zs@(BInd9*{wX܂w %:(/~M|7Vdroo0c߱^p%fɯ1co}3;PR8s=A"}) PRCid R+@IV%XJ `() PR+@IV3>6 .C4MKnX PCI?gP !m \ñ}>ÖG jܹsi999OXꊏϹsZقz=Q10rf@~SPΝ{;O>-H Ü?Zu퇿o鸽fs_|u+##MD nΝ;?KM+M7Zx~SUTTp6ol\9M6%P5gx/-<:::7ˣ{zz(#{7ӦMkMoW]]iӦ W,{zz󩧞ڶm[{2IJJ2dP(T(=zxg9rBh~ ?/x{{|QGڨjpHԞqNQ<]ߺ>y1NrJX\3fhrOs_Ml?dg@uu5-D(]Ql!ꊋi!FkϜ9C mOMԊߨR5jTk3[F}i7?uVu:_%0=\߾}QezKzzz 5rMX`Ak*E-^;kkkW_}eܷh<Ŝg|QgQW]]}կxXXX^^^;K69///DVVV;=h'SD _|E5*JL&+--o555-rJz9sm´Y-~~<ZD"5az5D"y*+++Z wv(j澾澕 # p+]lY:tܳgO#E[1miV uA >[򫯾j-DDjB矫T*5sGZ!←=y^5=Ǐ},,,EEE-h%~ȊW^ahhhRRj:dQG(//g֮]B Ji?:wpj65Us_ѳgϖopuuRl@kC~k׮顣.S;@!:K.[nСjII /0k,q4:;;S:7nܠvHHHk>FCP|w/7Fᇬ26mP,o޼;Cu øEDD?9rȦM۲eUܗRSSM2˥5|'*%X,"H$ BP 09::XoY-~V~UBp˖-,Ӫ¨k@ |E5r$.\0ߨj5Vjuuuuuu:Nz:AqJevgm>}zԩT?ԩS-i >oCJ#?Bx~Nj/kZJu?c…j'EJ߿?Α#G_:NT޾}{߾}|C=$_ink_޾}>x[@Ï R"""$`@Եɣ>~EEEuuuYYY;w\dINU5(ZpN]=?l'E XRqQ]L[Q}9 /%--5> uFw1 hѢqy֭gyѱ51bÆ Ih=7wxW/]VgZ]곍gm tQgmg>t`fȑGixjyseeed2B5`1c ݜ{ ?tPNNNIIJ2788844w7I_lٲeǎ/^ͭkK_[!~F3.iiimzRuFևN;zϟ?j\ݧOɓ'# 7`t`i{Fhi{pHLLl|͛ .p"^߂]\\l`_xkx{{smNg`uuuiiieee^^^ZK\t9ӓkJV՗/_nή𨩩iPTW\iήvwwJ2^ʵkjj V*F,0XBp̘1!P1j!X1EI$ZFd2JB9::bBNNNJ Ƹ!1.++C) qii)Bc\\\"1!6]1999N:aB,bo߾ gff":w1&;vc|MPn07n@`_ _z!Խ{w˗B=z_t !ԳgOŋB{?!ԧOsBg"1gΜAc|)PLL ĉРA0ǎC 2c|<@&" U1@9c#F1޷oBh̘1{"ƍ1?BǏWTBp˖-,a zŋ͛Dz1>pxܹ,p˲?8fY'۷ec/e|IɲSO=jsԩQ!$ dMMMfffDDD@@NKMM .t:O=_XXH{JJJ2jWTTP[TRJEϓ6ڵkԩ!u-ZWq555?Sxxxvv!;;!볳Be|uu5=_SSCmu&QDl?X__OM~ bt:]vv6>'H$D"6!\.GB;;;dDbωNmOOOV}||y???jսh4dybQ;88dI좢"zdJ|k쒒ZnἁvPʒJB9sF !`W={1,,'\|PﯿBBJ փRLOO'_}O?=i$B]'>^ztgJ$2$`!~mIJJzץKҹ`&22tXAddX,I'888DFF 9O:/<H)#I'HI:2<Ȑ^^^!XI:5쳃"zHd\u֭/_&`''iӦ' zW_}E:L۶mKOO߶m[FFFNNѢJJJZX\qqvQQQNNFi]XXC1ήmtvth[csss[im۶a'M$ /_NwG޽÷n"^!v}}}NNN^^6Qvu:v]]]NNljsrr hi%h[V5ӍKKK+**N$RXXH"pBjjzko~ꩧf͚e<`oKR2S__-2;vxW8`.`ol߾}̘1d3//O&1SLa&***::zĈtg裏^}UoQ˾}.^=`͛7Ϟ={ڵ#G7M˻ճgO@ l:tj'O~'%%%4vÉdׯ__|9yo@1 O?!Ak65'Nl۶M3 O޺+Wܹ!$t:'}osȴLTOOONvP(D"ѱcM;Hќ8qLJ.OOOB!RSS#P|}}i`Z,??<{1:"= ˉ]^^r2K!Nf1y&[] !0 Z4d2Ub& T*y!777^6QYvMرZϟOVpE}ƍ`Y,}1H@d22ڵ멧"vll0&&P\]]i`wm5'!TXXȫHFhh`TTTX "Ȳ)6c5Ba$h4DT*eNLbKKK0 kZ=Zc04QWݝ hhsΥZ? Z#@k\k  L&6as'xؓ'O<9X FymPxiԁPZ- *Q]]M[DgggHdY+-cLcFP|FjѣG!CX+(//r  E=,9hhsٽ{77l0a>jl:Z\\lrߟ2,77ׄˎ;͛G)SJ`^#@Q(40K`[4`^RWWGFbT*ttt mBnnn-cT*+QT*E"sazWuQVVvebGDDXsSPo IDATZ#{]v_ xyyRkĖyyy&,]o{ʔ)aÆ; RFJE@k(Jxh`Xֈ*VgVFw{# \ȯ putR555\Q(ZJ%WWWO ø|!H$:t0 CPSBFZa`]޲@7'K!=P`<,--={Xh+>[D>Xq)S3y5r"dW]ヷnV2lj'̠5Ξ=5N8Zӓ'\k!L&+((0a}s{ʔ)|Xj U=?޸_fp>Re,E~ř}xwwwz⩖ ]N*++yU!t:jZw7 E7"N*} A/ lFVgL& TUUQOgNS 5:j6k^j$ "aM|d&/y9J #r0kN_ZZKKkI˯/MI2I57Gg3cyfڹs'7;P0uWdEEE&,]m6gbO:U8br`yJ]$h-\=t+tNNbAw<բV.0j^Fuz}Kw]Q__O֒Z2fa!$rԵ:.=ܫ-X[[K*RivKY"m|tty赑#Gӕle9fZnB*s6.\ 'OT*XỹhP( (5j̯5q2պ5B<]KM3':>Wʭ)jMW8YfNN6J}}= Fc1Q][tsWBwǘsDʼn+] vw^<,F-D" j\c5AdF&3x `SQ( Oű| ZQPRRryb{yy8J$>l+Xh70cƌ!PN:eGw+P2NlLVZZje֭hG&ZCB<==i`ۗZZ-] eEV5ҨMj^jCci(.}<#L@KRʔ)Sh`8884U)))9w===###E$D[ j888pa±c Fb%oڴ)66ZٙT*knnwWϐee֭[}QbO>iawI!TWWGߖ,FUk_-mi` kY+$&& i&"f(SN!Fah9ũ򊊊h&,x h@ phfoN͛7逪Z6Bl\^QQae˖-3g$3F RFNO@J]]Z#-ZcLAYNbQ~ܮ^s4r z3mCgFƘn.{! DsϜ9Clooh>j1HglZ1 pC8qDbP̣5Θ15N4ZC.ĮZ3e2wٲe#)((8vG-:5BO(Xls^GkRknAENC.A'8fv 40@kZ#`fnzH:dcD߯A4zK E3YA_[חE"Dr!0Dk<Ш"Hر%wOB޾-MpTj+{5wsFT Z>e2C`|W"UUU.Jy]QN {F:aNg/s--ji\ld& S__݂B( gΜIAGj+zZ;U*tV\q)=Lus9<53gh+Z#Ak(Z#}v$Q=[117mOhoh`DEETT*98O@A"Z!>ZTQ%:uo{*"aM!TZT0VLddC~***+\*B4fʔ)hh}'Sж sB Brx2ޚƼÇSN<RQ*BO(:iCNGk5k9iTiu a[C M+*QlGϟ?O#66GhBUktrr"6heh>(90Z$ R8QH!Ta~Һ(C 6[(y*z*@qpp!Je29'/2nzاi4C`tLBYXT2en`414fɰ&)gDW]MR͑w!b?|ԢVi|/'1>,a(vNS ٳg@1F?Fll,OMY@kBlHFkڏ? Hj!)#@./SBn`7l`Y+o߾f_~*[¥R)k6ɓ42 C;x ;u4|p>jɁ#$ja(vN]]]\W$ ̙C4祝[sAfqĜ?Fll,OMٳAkɫvwrʎ;FOo߾jQT\G}BOS1maD"?N4… 40bccyj{1x{{5j4n5CqܹFҩS'ktrr)H wyq'k("C_63:݊$gN,5GӚxpOs`Gz@;'77&v@@ȑ#ZGP^^Nmۇ`nZ0 M;GVWW[, ͛G4… 40bccyj̙Z#^FbPvyH5N:o^3}Hbjlwww% ШbΠ5Z 5X X DsrrrOleGG-UUU\GPZZJmڑ7cV&/QZ-S, ϟO4ŋ40bccyjΝ Z#???^FOOObP FӧOЇ8OF"i`h-`%[b1 C믿8zh>jQ*Tktuu p2mcU204QFSYYIlD"|'Ɂ@  \xFll,OMy@kөS'^FooobPvuָ`rZ#@ FRZaFhE"puuZJ~7􀎡YM}]07_k+TkdYvر|RQQh#@(**]v1.**"67FC7 J ތ R=W1.єjKjjKTrF?vѬ?.e[ K=E*v ͙ݻw?CĎݱcL:F1b?P6o|SO3k s&bAis9/|:P5چQ[ϯɯ!=ȜM#˲40zS- חؐ2PeFhE"pssֈĕ%JKnU0}#G?k60 MC4@ki4;l Ob7Z˩֨P( qԦyS1.,,|F !t⫓WY,U5P xk\lXc5.\^c[/_;8 l+؜"BeYzwww???bKRjl&au5TJCPXRkDUiV:6!$j]~rҍ^n t̆𫯾"04Q0?7,Q ;zR>].޹sgϞ= ?~<GGm1'67ZF?.e0i\c~ݜ U^=.̋ ts2?5Fj-"\Cu22J+Iiq\i0hXgf)40ѩS'bCPˋahԁPR) www k\0B3n*3J+e]u輥2nNC:MOG)49'\/04Q8U5ގ%@FM[Gvʸ?>m:0cAZ&EurΝݻw;((h„ |RZZV #@ͥ]1ͽp3HQեĖdV5Z ?KOͽܨ(=NO8~Yf\2$$|h0?Z3k1 MV6!Ν;Ο7|QH*>˖-3>sۻv"vΝ'NG-%%%\GMmڑ7㜜b3 cr-Qjjj@kl7M\bp_d/ h0?Zs=G@kl+ <ӧO_QTW/^|ŗ_~Ղ~APP ={TW`{!e(k#h`xzz@[F.:~HII}%wwW^yeNNXh+ڵkM4V_tkҥ>MtTo߾sNbwܙ⚚{{{/;w.{PPi ߹s sjjj-AklM#AV嗫V***2bŊzʷ 5Pk|hB&K .O{=;B(//w͛',g Ok3Ar9O6GPPi@@40AklTY#Rf͚>HT\ڵʕ+gϞmN1NO>04QOh eee~Z]]mp{qqqe6h,:ڹsI&QKaa!}|| qYYYl1YYYfRZc5UV}t;O>III'Oam Z#`~ ŋ+y(w`?W_}믵Z-t…S?99ytйsg<ۥKbCPyiE.F.oNII? 2bjZYf]ah`i$ܼy3))_m<=rȔCdeeѡ`: Z#6ǭ[M;cIlaLe6J*,,$6h`pʕM65'NmO(5Fj/"9Rcƍϟ_r; ݻ3uԤ$MlLΝi`уZvJlHJ׮]y]CFk(40|}}AklRY#SN%$$ݻ@ xG]|G& 54GT IDAT?̙`rƀL8e>jϧY~~~8 e֭i gddAvNuuuAA@klzFq"howyXc5KFkcԨQFڵkWBBBjj*=_WWnݺ~a…˗/3y40BlHJnxH5GGG~~~56`mY#ce˖+W^t\.g.]i-:|pb('V4zotuK΋/~W Iܲe t2uTk@^^]Tzb-qzz:ɵLF#3h XyHt?srr2Lqss{WٹXc5+F@(>=ط~oKksϵgU`kԩ)#@ UkQZ#@qrrZc65r]n;C9._|…V{04QOli$|gnII7xc"Me޺ukڵiLRSN8jrƍf&44ԄKUUhMcM#?*** .'$$̙3G(4 Z#`~ %Kmgg7x>䓪*z)33sVZr̙3}Kҥ ݻs@@@xx8!e(ἮJP(40:uZc65r)))YzN|3))iڴihwߥ4QO:LHw֭[h .EEE%''!ܺu? v׮]O·9994 hcp5e7x5bs3HΩ/xi$dgg}]A%''=@k,h1_{5r#leꫥK&''nj3|𔔔Cr?եKi,I2ݻ:FLm(40@klCf\_qF^opiܸq111q `VZub('i$\r֭"a&MԷoߌM6]vÓJbB\BG&\Bl@ߠ`[TVVfggi$={6!!a9r3䭷nV?׿ {ܹs^ )#@ٳ'Z#: @K}}/ap^z]]]-0o= F͛77n܈999{졣ww%K ՒE5vGFz2֘Fl@@_JRymb45T7m_tOe˞~i#hc55nݺH$=܂ UV;^~>`ŊO=X,n}-}!6޽{5Ҩܹ3h @?>RPPP||yBE|0 LJJ]ah`@Hy_b̜9RYnݺ\r֬YT* \pvc-^Eb ޽{pvQ*vssh)ܯ[vmcGqqq-<@kFjFҭ[7aaajժ^ziժU_~emm-tʕGyo߾ɱMFW.]"## )#@۷/Z#:+ `=[|V52vX8VD>믿;44GmRRR~ƽx %%gnݺE.]@Ο?O}5HADΩiiRkl_U\=ztJJh@3CqʕuBBBBh`y剉w~衇vEλsɫHFFK.56Y#;w.!!aΝ6mZRR$&&; i/ 5kV; |QF_>88$6 h1iJHH #6lءCI;,,loF@@jlhF@Я_?bPh`t k4';wLHHC[ThѢe˖Z1&!!ܣ;!4Rnܸab>c|Ԓyu!~\.,Yi v˙3gh_~&Ϟ=Kl@mۥ͛vwwh)jIOO///'vHHB vmm_~jժB-_|…>}vor̙3澭 9eee56 4kPUTk׮}JKK >Ȳo1|Hćce0?ZoiY+$44FneĦ)#qҥ<̚5k>CGB(;;{ѢEWNHH3giʼnUkQBZc5Z-'|R]]mp)<<<11 ; i\~5Ν;Z8q4x՟Z6ԫW)S'O.L5:uܷU;ƍiXo5~%''O0ĎFjo96@ ߼"bS~~~~%K|>CBgΜ8qSRRFiz0p@^AFN#44 k92227lxcĈ)))C c4Z CFb?|rڵ2ZKG^i&^opioy9~84Z'@_JׯiX/^LHHؾ}@0'ONNNݻw{h1yrZ#@ ߼ݻ2Rzu3gtPkǎ#6mU)))z*===AkDlVo0!5?@G{7 .yxx,YwtQtFj.9y]=\ZhycN?1""a".\8o޼uֽ;Riie>e˖= GxWqذaĆyӓF=]kD-ʍ 4555[\\lp) `ŊO>X,oXW_k1 MFBW^oݽ{ Q˗j8={4sy|Lgϝ3a%b&%Nڊ}Z^[+Uv-JHlE"D&d'#[Ν=O8w>'q2g~Yn={y^ߤFtt4 һl\ ѣ .0jժ5,7JSh h%` >[SLڵLFBZ#axjgdQB#ԭ[vꚚ*UBŊM6bĈy-Y$##ߺrJg͚_wQ/B$hՐfH UTpqq!Fs9sX"++[^^^ӦM /F7nܬY;!p}8(HbF pҥUV1~}bjF2q,۷gΜf͚"oL>y͛vڪU~|_ك5>|ٺU CUQB#HXk,7nL6~ ((hl Օڵkug}?Hk$ OQq:d$*7JVhza` 4۳gϘmSF~ڵǏ:u͛q$>|888y-Ztqqq 6ܵkD !!!52FZ*+iZȍ$4%q)Sl۶MoGPT^=))IΊ+n۶ } LnZ[q"rFxد_?!F9wVٻı'O޵klҤ Vbfff͚nݺ;&22v]kēy,--Ik,DWn55Ǐꫯ+W|b8n̙Ǐ7o i)5~wtF) P^= Ҙm%6h`_ԩSKGL0͛K,~:oCXXZcӦMMZ#TZ͍BPn$x{233tD֭WsssxE5j8É4qT@ۊϋ/^ҿ!F9s j&:q&/]BCCwiee%W&MDDnڴ޵ƈfrܔHH={ժU#%(qHk|krrrjԨ2 vvvwWp^:5ָh"vAZ#%.4@10k&2*7n?~<&&&&&ĉoMLLlܸmۨKI4mTPYf̦8 ݸƬONMyWEG؁ʚa'ԬP.vI+[[h.o*&Hnnn|||LL̑#GbbbKl]t1{A%õDA> /;~QD MEsj-J,.^|rf0@QQk4щkz*ˑ111W\)5T&͙3/=j֬޵ƃ2[w*!qdȐNЅF/^,CiٹsΤy3i4˼.']|2,~vvv啙lKKKFy'2229rFsu!LNNwݻwWZhBP1\PPSvmcsмysA-Z0 CdQ+R_}frɎ5-),ehblO8NFFƄ /^\PP N8!S"44422׮],G!2xc7͋i`gD$'v_NoArJw]qBf{yy8qkӦ͝;weAAٰѣ+Vի۷hBZ-qS"!q-֘3;2.'Ĭ ,(SX[nfu.Ii)))vvvc ޽?kt STk\d 01ERgr₁!흝lF͋666ZXX.gϞ=|0::ԩSΝ;'oe˖5sI&jՊ٤5Hj00<==z#F7*2.uvaJg6 ΝswwΝ;JRDbcǎm'N1c.XCeq8Q0 Jx"A^VJ!S ~f}]3f}0nܸgUXɓ'rHZ#N 7xYLh 4VXeҳb<8L&8s짙?0[``` 4VWVMQ^S6m>|CN4k׮maaQjժU/_njJPwH ժUMk͛dIS*f5?#Kz׾RG"EGG3;::q#:VVVʝl$(#qё|1.$.]l77!C1ӧuF&W?PZ={-[lLJcccmeeceL&3HKRRFxx@qkҮ];ƜIk$G5' A 666닒M 䣏>TkHZ#XYYa`#LƤI0Eo.ÕJ_% %00P70A1e˖a\?\ܰU:u#ؽ{75nZZݻ-EG;}4WίX]d2J#)) #<<\8c Wi߾Zc6mMZ#;v%?j$!.5JQu~DS#ibee0@\#1qD Lm۶ pR |D qNsܹŋ3}ذaBr{1ߟ&㯿6m]kr7%޽{'OjԨQ_r%d ƭ[00J3g$x; 5k׎٤5HQ? qssД@ڴi#h]#~ #@WO\#1a L[ pRk4lP70 S#q4$Ν[h=<<.('Nƀ8?ڶm߉c~~LJݻw_W^.d2Fq- pRٳIk$^EV 5oߞ٤5_ 777 kkkFiРNW.(Ѯ];AFh< IDATL5H10>}aX[[ 0ƍ')eT*$L ߷o_fqj*"gj#FbǏc]c hH0v؁u۷׻ֈr ݻwO85kk֬a2R#}6Fxx@qΜ95ҹsgAƎ;2F)5' P%www Ɔ M }PԨT*ptQF@@߻woq{ fry^^YUFUZVӗԪU1vX7~d2ss *Ԯ]ݽe˖-[T(q߄{Rn0Q4h8(z4OIx,:~9%©Vc;+%Hg~w5j;vݻ ıٳgϞ=}Ç,YRrɓ'2ym۶رcGkxT248w9~8kժů[]d2WIv!|Ӿux}j۷10Js%#Gرc˗ҥZZf6ir Z]K+߅7||&+7qF֍Ozx W5F;FZ%00'I5jhq.>{,!!ѣׯgGDD|'ثEtQF\-@jԨѠA?]7aɒ5W*}jŦF]4u9 _[ժ{~LI[sw+jH$BPPPtt47nߟiƿ/F3w+۷oq*99ɖbyB"ՋYǕdpMk&erCçF̙3 ,`ѣѣZci8ƍwҥ 6W6l ql֭SNz[.wA'L;w;SժU?HL&hdgV;w``LQ9rf7nXNj/mgg'#Fu;wֻֈݺU sG2VZO?.d2S㑛XNU,DsӧOJ#l2D֭j:uһإKfT*JPՅQZ ݓKkl'=<<007n\Lf׬YSQF`T* "GӹsgzrkUF@@ߣGq&+/qFVӴn=%}reDYc̘1Ba=aFaj8' 7GW׳og.(111ofv&MJ'=siݺj]tѻֈG%<s혘f[[[M&6p~TǦbYZ`{.Fjj@qZcLoq={1cp=z@sz?fT*JPռkqwԮak:|CդIe =h6lذy>lH.]kڵ+@jժwM\yi9ywO~wqn~A^Akw=[GٱF &FEI=T>|x̙;vl޼9333;;{ϟ߶mijW _~8I x/2s̱S{r:6b;(&| /ZcPP&۷0aBNΝ;;w2eʌ3vPL6oތZc׮]5be\.ptuKZ#IL&ԨKreԛu&37"6 ޽sR~KZ#899ٳ%%%͛7`~9ٳ'j޵FT*JɓjǞLkdXQ6w6hN~Iz>CT\9f W=iԨQcذal~JlDk׮5' 55F@@{rYjRkԨaɎҺuk\GrjCH@PHvyx ~~~Q9S\2_Yvt߭ԴOK69tFGEE,񉣓ں-U%/Zcn5be78n:|0mllxL&XFQ(i0ѱS6;br= 4Rw}5J<5VPm&:J^zصkWkDRQj$'OPՅQZ#d棭9$Hxyya`w FHHH-Ϟ=C=InFB,7SzC3ur,Y[ײ(7@DV^1j(0!._vjՊyĉg&$$<}TPXZZvҥC Bl/5Faj8N]w'?Aus#&qqqsaرcСCnbvhh''E޵/Jnnnbbbbbo7n܈Zc=5rВl[[[_e2Sl#]I{00222J .$qM#۵kNɓ'CBB+VoDwި5vMZ#߮R(5SNa`(ee>ovVިYUeq]/// ի 4Jhh(=rg˗/wӳCEqpphѢE@@EffSfϞ~yٳΝkhGPٳ'*``;w!/Hν,'qwX1]<$'z#G =ђø۷oYYYussUV^vڵ"/_>444$$k׮{Q:R5k{Bn0Q|}}u05rW*go^#S}X[ 66v7nDFFjrNʕ+oݺ$mf̘r?FgϞz[.w]'LC1֖ū_T%\yרҶyq{S10pBw-Z5(ʮ]Λ7]_}Sj6ݻ]kիU*Fq) ZRke RQӢ=W.vXNk m#RˋeʔlذaΝT~:{,320={?@lll00Ryuf` #FzQ(F~|(W BкID qN;k,f?^Q"""Pkڴ&z᧟~bB#qf[ӆ Pkի޵Fy̶͛n.$5ܿ#;;[Q{ gСCKͬ_~5SZc޽R(5ӧOc`(5x )XM6T|rqСm۲/.[HoKP?Ik$[[[ ___cǎ:D!VVVÇ =Die߾}۷-[|DrJP($L(LD8O9s&}}}'L (y&5kFǷd]veŀԱn:{wqy{'111""miYxHmظq㧟~ݻ*5 0W^zlJE`b`Hy``XYY 4Jf*W6XbȐ!lFo> ?{j>}MZ#b`۷!VcذaBQ>3f7h`Ϟ=2/+{Bn0Q|||t05d?C$ӧOgĉ56oޜ&a̙&Mbv&MvUQkעاOkX<6@!$NbbmooϣCZ)+oIrr2n;K,!-xWnݺU 8p j~޵~1[RQj$j߾};FNkl޼yժUmkk+(ѧOAF RQZjEvvvBTk8p Ik${{{ ooou:D! F,]T+ A& K70 SL&۹sH^ɓ'Nl)S1ʞ={tF8Kw+y(Ĺ~KZݻiCuFJcذa5_ZR(5x Zڵ]P]#a`5~5j`6E8pZ#~H G}$CRjUŋ{Bn0Qt05r {43'@ 㴆/ɓ'Q_GQ7n`G}$O2Ьu5}`^,[ gCջֈaBA{FBBf@GG˜dQF֜C-I2ԬޭRWw- ݻ1<<\^۷۶m+(odɒ%Ç},((H"׮]]@ ;pX]JƼ|xƌ8q{{{{{{3"BZ#axjk\DiJ2D'u9 5ʫGjYcj*RNk k׮-( qT7 [YY5oÆ Tk1bIk$ OOO"d(VJ͔}Ί3999T*~Wh q Lr¼SͨXE,be˖?]t_ $O9ÉXT, x 9z7mM6}'%z2DD@DDEGo~3BX@eV/׫8qBWk>}_ nӦL qYx1jÇoM^^<6@!$εkװv!*.P.ԭ C@A= óg \+Vǁ+Ah(H=*Ǐ10ʔ)#(VBޞR#Qk"//oԨQVT ٳg10juaj <=F<8}Z&j4p;\^^aaи13hQJFiӦ JH #FTkO@J ƥ5;,l8q"" 2;hQ( @ڠ6Q# ¨6m,p@W (`TȀ#Gdl,{(`n.@L LeB`6M{8qĄ ݠA3g 1Ν;LvhH0{59RZ#P(`K(׮]*5ggg>""](*4olSj*>]t=w 6:̄AI6 7z1J%TիQktppH0ƌZ#yyy}U*FqY Z] C`am@6C4yr?wPkdo,/``T\YQڶmAQcȑjGf65#i/O]{W###E7aeEWJ*iaT𡡡̒d"axjԀݡ{w7 ݻ> v*pǏgvÆ g͚aǎXخ];8 b]Q5~y^ r՗C {{>}^CJ-uNNjx#O<077缞kעH`|嗨59RϙR(5sa`j>22]P]1[9m<|RSUzJ{o ;::2"=Z8yFqrrpww'1w"/bc %Ѕ`iqclH75Q08(F σ?øqǏk' ;)8u dÆ%8~qݰaٳg sl߾;t@G`?3k .dB-a\zgg7d2Ft 6m۝w'=]ofxM@5olYMXf ͑#'a]v(;vh~_E.]vGdt9L|J e֭jK%GgpV<{lU^` 4NkС~;Cё >35 5~&@00Hk,dwc8tH&/]zK 5Q18AN*WNS'{92"O۶mm Eѣ!4=۶mիWݱcG8ow3FZ P( !q\}vfשSfO IDAT M^k|WnЬ^CSFrNĄ66^4k2>iiHZctt.aA>G`XT`zXI޽:vhH cƌ/d6P%ggg _jFgjW\_[8pq`xY q̒dB|8<`9ܯp<|=MMZcNhH0͛;T k~-u;#G J O> <v8qB{ > O`%ذa֫WR#8q"P3f~,T*Fq Zİ J 6TR M@&0e ddѣ4y\8r3L ԦI??xɢSNc~.$/ Ɨ }`6YRo]͡IZHFߨQ#fqB;v 5j7ߔxk /z 4y2+- +wvm!N} 7qرz͛lB[Df׭[?z( @>}]P}_/w\/N-[j$QL4 FO/&LlJE`\pCVF@6lcTݺ뢐X&[./lؼ6o֮9dرPO@ N:5BZ!_m|4 ժ 8HF߰aCfqG3HqQިQu@:0hh4/Xp-޸9+-׮kl*IJ5sYq5v8/_~Ik<~8 @RRR00T"stCΟL^^4bɨ5;V˛8q"U*FqE Z]  X@tՕέ[Cݺ0v[t-@APv50i1~xAFSi/AZё GhdIt)S6ԦI0> ¨%Ɏ;&7pQ<ǮqsRlٲej]cٲЬ4kDGk9S|o,Hآ'B̚5 &]k3g l ˿;ի?살FIII|3ƍ{1Q ukhcҦK[>i'q3uTǏ߇}WVT ŋ10j5 vA=T $00C8K.~C^,Bб#t[t-<} ۷koiYx$5n5J&N(ֈ5Hݺu10\\\Hk,FMT2"B@X[I+ ¨qNs]qByfvnb޽wo+W " n݂ `G4Yz{ 3gYI5Κ5 B rҥ-[0~ɓ'iQMK&5jaaa!(|򉗗 =e| xj-gP"kIWW-~L0].^yff5"ߕ,_c囅IͦM{5BZ#axjzHƍ10uu 4j[fSɨ{ $$@BBao6LK3F͔)Sk6mIk$c`ԫWBHk$F~>idtttqKұ:_` 0<|8-Z$(6mtwnW|}/!7NjǏCVV1~Cݶ d`~{ SN_޵Ư bz|8a\x_~aK5ֈ(4'j6((@*]t2e4!!i4њl75K.7nQBF޽KՔ5- MBӦϟCtv5.ɬ,8t(W7Vxy\nH U!3f߇K10֭KZc!5beڏav7W5l  }{xx0K&ʼn a$DGG6AAA/b7أGLܼnv6YRoرؑ^K=K$&OiӦ]k톃-… 5iiĺƛ75kj?MZoU4YgOڵD~ i)5={V/)(M4i`ߟh>>U0 OOکdh(XY'oP]#``5bn=TYoV Az*k(8̙3zC CevPPВ%Kx"{IǷ `mo&cb画ŋp",Y2xxhdP$_ꫯp8c kX˨P(Ƒ8.\ظq#]]] FF99ʹiPM2o&y^Ӱf́ @  STk|xРA eXثW/8 NNF.hsdT<}ZׯjJC7q8{lk'NdBsX)5bg $-- AQmۆZcF(5-Mo-tKMmߢ8k,(H^^7|lJE`\rCVF@00u+~iFMyؑ||/ 7GzdesAA\,X<EFl5gA{' 5+i/AZ#!ƥ5YYp6M[@33h@&-@%yP!$NTTָl2!FYnݻ7MM2eӧCZDGk]%!* `(W5:ƍƹs]k?~< l ܹs6l`;evAZ#c`8:: 4QklҤ F>>SҦ J-o`aL\`jsѯyyygJH0^V S#P%` ޽{7i҄/M>ڷ!{ bo냣#8: 5' 53iH kʭ[کdDDtY@f;;#ԩ,p@H0;44tBvZ,ӧM%5z\M| *;wd? eڅi}{ 7J[ $ٳgׯ_lʕ+살FIOOp[;Pk (9ѱJFE-|~{?-z5pԭ͑!![ ??VT Ƶk00juaj$$88\rҧO`fS^4[`p((7vl+p*\egsF.K4<' i憁;99a+WѶm[ LlyZ;7N.=ZboxVJ<Ϗ=Z0It05dW$СCgvhh?((k֬Ao߾4q$cƌϗ¤Iǎi?[ /N'`lmoM;v, @HgϮ[e2Y~%;v0?fvڵ+0/f޽u߹so;vHmcfvZf{{{bTptP*Xט' 4V[@[ 4²\\ʾGRTz0MN:1[Vstʌk$m[wޯU%c ##޽{VTՅ9ihΝ n۶mڵhvW̙Sxb>bĈ7dz gaV-vvСr0i]_1Iu7nx' E۷_s $"^}U@Hv OݺG 96T갳yfI:tɒ%8tP~rJ!FYzsݯ_?WWW!F1fzqƒ-_'O$?Y ޳֭\/^ yFF$ș3gP(|C&i$ :Ųe]vo <<\QڵkرCQhժYQyyyzxիtV-S7o_?k)uV Zͣ#!!!aᝤ P^//,,2@HH{[Y`PO+[hsMWµk|FF߸W-P!wwE1QZ#Vn$ȍ$4 +d+g؊[ L^kqNFIr4F۷/VZ%(V:{,  ͉cIrdF=z4j .o?JR{Bn.:9v 23_ШQ̙3:g5%ˍ55F !F2uҞJ*MD櫯4iH4AAS5QQlzK"Z#w 꾬bYL~qJb]van0$EIՍҬhd5:T -[l @d$ŽJiIooDIHH￙@Z#@ r# a(Vn$HI(m<ڤF&gkk,LvuqWF M~Yr&(ϫrF5jj-һg1[Pa ƃpV|,MRo$>>bxUnШ!Ѱ*7JYhԈ5+ni֯|$F Mի5ׯatBOk|M#IQQP e 47olNEV42-ZF g^^$$hW\##{6nN`oJBzpx_<<<00Hk,HB#aHȍ$4< \TOaBC!$m{ :kCJYU8q:#2rF׬Y#(+V@qРAR8ʍ`Ĉ5.^XZQT*;=>xXo&߲6MEw0q"uǣUĵFrF iGWnШ19=1CӴl7 <׌ٷO^i,,43gskLIommiՍRhddee=|e˖"={`]~(ٲZxQ+V8h 5yz|Ǐk7ܯTBZm2 <k~ZjքSO=~޽{MZKHB#axPn$Qk{ DDUou~*z`lY!VZZ{i)M(q"""pyYfx~Y|3g=x`www!F1HB# 6 ƥK]k1bJ%`DL|MoFȑҸ1̝ Xoo7HkԼ%.4jHk&7Ш)Z㻒ټY3hٹ7J:h.]g0 5t 4 ٰa+aaaeʔh̖^2JtRA/_l[ZܾJFFMIms'SBºjxyya`Ԯ]ƗqF6mHh$D'x3spr0j|%35kdL&u떸ވ}ITIuh֬n =l2]CQLpC?]k>|8 ŋpn̙0i J0n0qqq+V`whLmQ£hN>q\TTՕ: kԨQ <޽u߹so~ǎޱcG۶m]h4qqq999O>5ժU3 ?t(\fױ,}RB~~~BB۞'߭HVVЫ-UTyooo7GNVw{^rJ޽= AAAqΝknn.?O^233$L'}a-[!0b;~* //cǎh۶Ν;Ν+z\jj{]#|>} ///ܵkו+Wڷo}vq1M49qD~~~ӦMΝabpƍ3v ֭[gffVXQ_ԯ__ɓbH~~KjuWق3g qqq(?HHp7hРgϞR3Eܹ#o j'ֶ(VVVϟ\(}gݺu+r­ߏ9R5kh;;7}B~+7o\޽{y͢ӧ}ݶmr~̚5kpԷo_k݉O?T{-[:uj*IH.] M2eEٌxfجY3ob#& .ۣRO:](?;w푘Ro^lwJ3vډ페$%%b{h4<9h4x)1qywޣGfCr=z0y??tҸ8f6Lӧm410DqԻ8x`f+~A{tO.Fvy.]ލX ___>99]+dddjѣG>LMMupp vUVRX;rrr00R޻|M߿ƶmۊ۵kpʕzͭL2VVV999J1j׮]Z#Va J%JjLOOo۶mBBµkgf 1|0lݺ5## ҁ#}q5.! %==~ zذab8OƦk׮UUU555tmذaFիБ L{{;$Fcc#O$$$8m45Rq޽ЮB(,,hiiwPZQPPiӦ{>z[ <صk0Ç={{ァ˞ƌpBjS0̳ӓJFFFvvvwuuݻ*#Tsǫ-6}iֽ! 333ooodI999EGGs IMM|}}UNvRR'eײ_~+V(**$7*//:>d;;;N謋c$ eiɓ'?t-$$$88̬4+++66ȑ#!dgg9M'66w!:$$ȑ#|w]r/ÃZ4p߈?=T8HKRh4딹|Oד7Xso0tP(믿{ί<~޽;$0jƍS-'NZNDpYgggbN1ȹs@ 5;vZ6oޜNEi xk]R)ЬKKKZ5ŽaÆ P( 1ZZZx%99Z4___hu:vx+wttkt<늋!1 ͣ"77h.y--`$p!^Fhud]KY7l0H ;;;q!SSS#\D" Ϗ*4fEEEF"0"W/Ss񬳳SN']#˲L8J#jaaa[(qȦM._LŋPRyO aƌÇ9dTEdz.--mDa՚xaC+P( 1?Onx"x3gMDRSG8~8x;::2{>M+..V~P߂nH$ȣGLLLG Q=ŋYAOOOjDiC=O[xaw'Q:///H ;;;T*6 122v˲*H PjkkAk/dʔ).N_grb<YՎA]UUƣDGGO4谰>jٰak\x18'd.kjY~=k\t)8f&$$hIiӦ4c8ONT*ugepҥ7#9}O/_N^?kc DpΥKkyPP%Ѻ~'Nkʨ<+tH Hk&>-[g?~ԩ &F9WoN k\t)t#xիWw /,X `0BqQ[@n Ph!!1lllZk׮k׮7oy)]\\z%`/^i&C ->όa$ϧjʔ)ȑ#ǎ Nd>D"^LYuZ=== C'O֚ x5,MOΝ;̙3r__XggYfEGGw^RR}jItt;v,L{r˺u҈u|xҥӧ)ݺu۽{NM:sqڴiDKÇsX¬ٳg_ikk?v옧#G^iԡnذh___1/k0̡CFyPii… -ZdddjllܱcGxx:Cy(nIKKN'O?۶m۵k'NЎ58u^#d.{d]zz#_9Bb{BptrܹI&#?~m۶!Cfff=zT;EBhh 믿5k:2d744޺u+==ŋ0t&888qǏJ;鯼I{̘1^^^Ύ&&&MMMW\Yn*((Xn4H F333ooo_zL&{O9&&9%Orpjj*mNID!$HϟWG˹-BP>SΎ2UuBy>}adB z8A 7e,%D+WΜ9sӦM{ᙔ Ì1?>},hyUΜ93n8Ǎw)>j ˖-ӑҿӧϙ3ZXgʔ)0i'JR8]yco%ރxNդ_h___1.={lxڵf}}nݺYXX{yy=Hĸ|2tgB$IRcccsss{{2߿W^BFDFF5BSց+BbH$vןѣZmrbᾉz ::Z^~ \R;e(oĉk<}4^#<.Jw nN81}t2^򊌌:(HIIYv-OF5RQg⩊+W9s9sT E㈎k^s>ܲ :4669Qk"eeeʉ!pxYwk0$Ɗ+r9trN:uuQQQDS%ٱcǼy?3..D8Qkf)oV:yaZ>h:!!!β,^uօ }"BJ91zzzDQk Wq?ڢHνF8V*eڵ˗/': **JQ/>5BHSU/ 0M:eddysAw/66Wz/EPRRXaarH CCCqkkQԐP$7|=;Q4H^FhH 1pwEQ(^#O&R41cW8˲z R91(5RxWqժUkƍFEEq;kmm0aRɓ}4~rr~Kt@@S]pQQ a 1{nڵkDϛ7Z(G\\l( :5R H ###1ڀBQ&44Tj**uUhxAbXZZ>5JR nYɄ2dR4ѣGW8˲z R912(5RxVJII!z>>>|B87ck7n2 v8k֬!zOF, sׯ_?xq={WzDLP QAb^O?%NRh^5M5RH KK˧Fm:a&tttHOr+R4QFW8˲z B910@F oq͚5kBBB-kܖJp;EIJJzk"}, s˙gϞ%xq?Wzk׮Fa!!!!*H5k@H'W)@LL ^#İxkJw6%z/<U$4ptqqX_,eR4 %hdd4w{%%%bO>}jmjƕ+W^xp___>jy>ݻw(//W}j\{`XǹJT*=s $3g$YРԍ޽{[XXXXX VZEß HOO/..?$/_%H$j͛7srrrrrz)H "8rNVV牞?>O<{yyyĠ>7k׮{$x^F:~cc'N@XXXhoo/H$jB^^߿$JHݻ=z$Hxxxrqqq:@II᥇ a 1áGtvv滺cD޽?iFk.B} xjje7m$tD4i$۷ cP1!!!L=ôv1NNNt? ϙ3gT5X"99k 1̞=![[`GGǮ] LNNv\qㆉ !=Ƴgr5-`wS[[\\\ W,,, I<|0???66.;vObb7|Ct`` 2vхBT{銿GGPHwyZ&Nɓ'y<|ή/˫0 CO:_=GGzǏHN`Y/3k֬K.†2afa9slܸHب͛UUUD;;; Iff o޼)T<* 2lΝ3g0DC500ۻdBHH$ WeϞ=s΅"KKKyL533^:::FFF>svdSSt+V=b1kV+",boȘ ]xu 99WN}FTWWCbXZZR"o߆qРAݻw6pEhP()))D,+LuD1555^n^zzz]t6 BQ%k .] 3ׯ R#F1!!s8dp;EIHHXl#G,,6zzz o߾ G+^Pt^F:5RjH kkk5R#''ƁRBHMM=T}}}9SSSDN\srr633c`[aahD!-H~s BQzA5^%Kkܰa)@9a#@T St .<5\t]#Ӄ`՜xz 55WzƆzs k(K.A͹טF>Fy555w!Ԕ^*{ ) E ^#oP׸xb8nƍžNQkLNNk $Z&sX8EspҥK kXܹs%kkkyq5BQ@bQ"#׏z˗]6l^czz:,zzzrX8Es{.ݻwg= pT*yR(zA5^W_}@͛H!ט¹& ):N||%K5jҒ{RD7rNnn˗^#HOOk^#ppp^#E0޽5S4th<==9\B4˲CpR[[XZZJ^0 T*P(zA5^EÇQ E155svd/^QF?VD"l|uo߾fff#F੖ttt߻wv­[bX,pBNw"Rz}^ff&]CA[YYݛZ<==:ڊ(:Nqqq}}=Ƙe٩Sr;dDIRr$kSѧOcOGGSbb"8''GOOXmmmҥS-Ʀ5+++##rKee%x0sKq̙fdd6m233㏅ ",[n?C^+illC555LJZ#E),, ޺ukpp0ܹsO>d T[[{At>xXUUI0_-**0`@EEE']\\ܿC߿.--ׯ_iioVVV֯_No߾Ň۷oQQQ']YYY\\qFLm۶G&%%7_L1c߿ IOOO,=_tD$WR5tUUU'-X}]YYР_CWTTt ü.//葲644|mw圜{{-[ܹhĈR333cYeʕӦMϟ6mZTT~sss.]0? E'hhhBY755noo亂 ^UUVV*떖Эtkk薖*eֳgԬ[_~tssC!qCC4ӧǏ_NH,c$H0!T1njj"!CCCRB9cLFv1&u c\]]255I:",,,0eee!+++qII Bc\TT"1!GGGq^^BG?!ԫW/{ȟ1EcL߿?bB ߼y!4x`qVVBcL>:WWWkBnnn㌌ Ƙ:t(Ƙ06lƘl1NMME`/^'%%!0ɺÇc/\1bƘZ1>wBhԨQ?!4zhqll,B(88c Yr%Bh~!Գgφ3k֬!O?a ңGPdd$Ƙ#0ơh1Ubnj[gbBϟ11%)) cL\LNN"RRR00Ę'<<<BcwwwիWɟ~:!1vvvFݸqc8!tmB999c2᜛1&ǽ{0 >Ƙ }0Ǝ1{O>ݥKի˲F3gNsssvv6ݲUpuue&++}ʔ)3f O‚܅l޼ի?CQ!}v++ݻw[xiƌC222`Hk,{19a7nѮQ011ӧݻwoݺsNIѷoߜ^+]?zrzm˲r1Dr)& b\.'ƀ\.'$D#$\.'KR\NX&L&<\.'^Ѥ400$ZCCC\Nj!tE."r9 "rr؈\.'7 D[YYD.nݺr2E-B{rSr{{{dL.9.ɜ\.'`rW^\\.'shVVVro߾<\.߿?BF.8ݱ\.4hBN.y<lkk|r2 uɧjoowqqqtt!djjjccCҩ{D}u33N?DussN?Dҳ[XXGnll$_O &B[ZZnooP(&INjdD"rJOOD"MO\744FFF$zSS\oiiafرQ\a޼yDۏ=4J ؐ0X! N"r4>DƇhMHdccCrhDFYbquIϺgzhXxe{oinnްaêU5pJG̙3g/32;6Hzj-R?MQ5"(11޽{ |С7Ay޽E744")^P(}ٔ)SȒ۷0`d6l@^5vZxxutt|… @ST֚5kȪ̙fme=IJeD"tܹsSSZf???2gϞaQQQ҇Vc fȗl6ӧ5x\#PJ&;V?MQYcǎ%ڼy ,h/+atH_MMM{UUUIl!%=6%%|u„ [a:2(o߾c:1Nsq4BͽlΝ;Kʏ=:dM}b="Ӵe!y6,H.Ns6m4r{ǎpn*SN'O$o/k+* ֬} }:TX333prRKKdcƌ!(--=}fڧP...ϗln۶M)vԉ~k>qۀ^+k.(hjj"V@C#T R [yS eD m@/WTd`RGQ0mtU<~XySDZo8q@>zP+ѣGL`---*ڵKy#T%'?:t#""Uz6#ё2hZzYxժU N!Xz5.ltUV__OnK HPf͚E\6WSe"#رcKԆN!Wr&F Tںo>02U#:322|oyy5ʠm;{dLLLσ,++ џVVV!r312UlmO}`Ub˗/ YU%`0^|Z2F =… '`7ReH"J ,_yΝ;㠈~"VpԠQDiii{uVii@ prrrwwOLL7nj H 'W ]zg}B%A|wQy:ݻZ= ,bYYY988GGG3N7hrO0L%&&&LhiiEKX,:Ռ .%Ϟ=CKlZԔa  rn  KKKD]]+++Dmm-] kkkiAԔ%666hIuuuii)Ab4dAQ/2!!bIaXذ+IVVVXLUUcooHQnnnX -`]2AAAAXLFFŤc1QQQXիW8,XL~'Ob1C bWMhc1ƍbO,fʔ)X͛Yfa1y(9۷ON.]eXbŊؓNsNBBŠ+B!*CwB!FKL&sZ[[%hImm-&A9 ʁ3sU///`^^v5?33-yq׮]ђΝ;c?߿>څ k׮%n݊CKz|r~В>}:u -9{!CВb?O81rHdذaCK:4n8dԨQ%3edĉ3رc̙3 :uNt/^\tΝ;۷orϟZ{^Zr"b8&& h/_FKZ[[郖X,칰MMMGK,--ﵵؿ9z(Z#F%NNN@KJKKGٳ-)((8q"Zə:u*Z>~XoJ q-&UvX,b| V#z4#TGoH=1ԣf ҩSHG,_~MMMA :֭[Xػnܸf{@]~ݥ[{"u* [[[,c1NNNXLkk+"~r+Xi X:,][V]]``:yGT ?~vضmTzuuuhaEEo.;}tP#JF®ޗak׮Z -pv=f̘y_p4Rd2Ji4Zxx8Zbnn.7FD``1X bAA`1ύ255bAm` 033b[wAc1؀g ,,,l3AXcmm-7^VN#e˖'N~aJJ zJeeeX9|-Y_qѣG0@J )`ڃ~%^:))Brrr^ϟĉ#G`djj*v;;;1rc<==ɍ &7{rcz)7wrc'7fȐ!H i#FFRH=z4:͛X@MMMVV62H.//̯B/@ q5terX}n߾=%%E U*Xnђ O!c@6H}}h4􂕕rkk+ e˖3g%6lPz밮2Pʉ'>3dذa_}=.?~ *5bO &Kn+&%/7Y+ 3G6G~>J˹UCp3wr3wHCbB\XQX!&`֥బ"QZO~P}N=4' =:fNqe+{[Tf ۑ}TMiྜ Y鴭W?ܲE;ݬx ّYaש4s|111辂 ~! *觪Z V,3i#~Se5 ggg!$pʼn ?/-o\c*WLy~=`п}7'^ng <<%Ą?~8Ϲ-&4uү\Qv$-AeM/U;6[-|$#>99999Y#X,dwKDys:i=S[(|qo~O 6#t)ZwɧK[.MS`pjy LTYqůܵ`X3تeJ^~UȗRJV1+wf%%cKEݻ7*% ~}<*5m߾]uuPʱc>cdĈk֬!Rnn.H̀r볳XBpkqmǿHUh+֐u 'ٳgu0xIIIIII`iidj`=x1Lllet FNP^^KIIIIIQ;w`C`2Pְað ,,^=. X ܵz\@gΜA)J~z`=sϟ?BK<<+^~#*_>_|kŇnm8qĉ²:9VL9333a]ek784΋G]6 |'޵|IcZyZy}ܧ<߫[ڪRrE,7?aYsX֑M/r}ґý669'&poάc+L JL蘘O>nٜ .60- K!zep3 .JMm.!%99N]yg[_bfUBo>5 Z]j"&۟+r<&OJP}`/{/>ԧtVڸ_*;BPVpiyNM3ǎC)СCun?؟{ǹu\τ`MW3@[p>Q.rjsO]M+|Knu5ΒahfñKw!^:n9zIyrp>9p׷'_AVSAAÇoo/Q\5';_3v1dCnW4W0MT.9'_ۻMcYͭ#h#\䓐䓠Z=4 15uTf=vC+q'y<.)=zM n|o߰JameK͏?qKbmrwjjٽ"_+Z}Ǐos]屝}|c.Yee ód4QA Q&MKr677ck4l+++ X?{:Xв-ONy.R  Ga 8#`W>/Emq9_JOyn~|@#bgv"=֥޸*emb1GQյ6waew徾.o2_~qF=#=--uݻwcO?DP$ eO}`YA {43Fxœ†Rv'kޮשZ… %/Xܚ>!ϟ\t=ݒiVU,;TvMZ쟖YTKP'$4}U Zٽ_2vIJĢ疙YhH w%SG=:{2FZɻcKDEE]ǢE[^rޏ4bٍ_ZAwe /jK` h]eCoo}zSnqR\ۀ~I^jt:i*jj0u7wǼke\^U]XI-W3?wd#3ҷH~_}W\*%Q#b6,YBs7Gk۵[~}1V"-sN,!D'i1FE o ƘҦV!+q--BQhN=DFevߓ m30Skl@$l7={~'|B6nq'k=:*n_@$ y^S?t{ kdgJhqfCoO\>1ݧR e%on&W@Фob=-YNlkz~d˲R MsϐSF`SF=gZ.q//2(zI&Qy"5 ŢQ>*?5{f(irj@ }^9ʛ+;~ mihwIH 6UۚjzMlU.pF9o4jt7W;^垒\4&ҥ>^[׿ާFàm])hZ(%%pajO@,uJ0FX,cmbr=`h9noP^}wEf8$Cn_/qQ[SGHRmY6bYq͝˸RTT`~⌦5*NLwd ڑ fx"Y6b~^_ja!ڬ^1F*挶WI4oWw >nKWwg 'eL]-%Bt'_g'Z=6L /)KL  [V73kl<t13?k}s,qFR{4͕ܚGs+kl|ToOzPRs/XֺNDi4O*n7kޙX,eU }?l'G^.sΊqjBcʮ{S[r[toږu0ޖn..y'+LL55e`3^#|1kLڇr8qu+yP dLXtS:EcYtKLX4SGչ紻ڨ>_͏A-lǭ5n+!kb>whaLѵ3E0ĹD2o)_y5ݎ>R,qdsNg ;UJ[Ӗ P(#GʹU2X " }>sѕoH\tѵEז\iMKԶEPP_-1tpVQ}.fL,D*:El-ׁͱQ~dMIcVFٲ~'ڃ|rFev R A5juM&(M|L76mW{M+7'm`)mzrzGb/mA۟\hl{rHnEuu6U&bgֱ~Siu< An~S*CkԸ!ȥߒqMXY! i+zŕ4ԫׯ}:k<@#lO7`q*2H͓T־ e6&\Kv=I慌=7N 3̻AY5Ϯ޽\z矢XS[vs]?(h!eM6YeTI4B ^ 淇{ cΜ.7~j|2FC=Γ7ݞ={f|qjj F풣$M,W[[kv24#+;/jg~رcׯ_Ohav-2 ɫ+jm|fwϝI8'`'ITzۓւ̟SO׿´~W%QspYK%鏪rSMVnZơ룡$MFed'P]]]&ҋC0<5ں2m~x<sp@gTf+8ܜ ʯEPEqJ=l˿ϵ<Rk9rm.K+y?g)olOmO^̶H,'T uV txQ+mBq6f!X_O]#Wm`}!ɺz<4PʌQ_DuzvT:2sK2"1G/ tk?PϑLS nn˘6qOթaIC-xAꕲjBKJ)FCA@ߢg%eD.Z]E=M#"ޭfp0x+@?GV[j,';ʃz1tH@Ĺ7=߳_]gP5"IVEfZjKq:Sn=͝|CSEW%۲H)kH]v_$1\`Vh}x FC{8*/s/fw'C#beVIv_ޅ*m\[DB߳-j0 ɘX4=T(=U8:?3*-BP=ǒYg6fWG;_OUvHޔd+Щ4M槼 Hi)A#V D hVPGdEK (/7az4BRCǤ\>^p 檸)'{+f]rJ:mv,C;Ƹ$STk%sڌ|gmOɞUkXxkrwo~v$ٮh/E{#hމs y1NJiܝ>䅗*l^Y5%)a"-A>}! 5jJvM#savI>Z3fZ !nk*C#BP\2ve7סC5 Z~}8zh۫5 支Ƅ>~vbwGs'gkbEU-uOy~}ӣhۘK.w^[sɑg9M7g_7deA#͜>Ǩ\(ED 3v3.ԆòM/Tf.?\d2mU}v?m7QkX{o{[ełi)ae [O^H/=MgM<{sNJJ"hVeK14+;gZp/XRk F>.Ak9w\>lL#~_Mv5]wePN5z0MMָ=˽H&5vܶS<d+#MgA߅p? KM`-cD;q"nZ!@Ak&8,c! x{@#F7rNCCǚ1LSJiz{ntB/k::Y{N^RzAev e]@K^[4ݗ3*>)xV_\Y",fv6vm߀Q~/9[|RIzqcEUKmˤ2L&,k'6ҭgg;fmEVv8\FlSF,V&\rrr߸ЭZ X4+s{SpwF +F(/}m'\.s2E3lL,ٶ]z8vNpa߸M:ǕpN :V~LfCz-L Aڽ~F봏NHU;}J999"kwpe[QZ^ҽ#Օ%֎.P.SIOL?0- ۾}d7ZZZÕۻw3В &@jll$  h~ @ h}U:ެJ\!3XCTN}F߱cG ׯZ3y7֬@e ~c!gg}.*0r 腻w:`𒓓թ*ej*Pƍ9r$ZBZz\cf3,&@!Fk? 8)@۶mC)ʔ)St 0PYYY7oDKt!i}=.PٳgUV}!6b%44+% =-@/DEE@MPs]eGGZUJ믿NL4i@h %V$z&:As\:6KnMUKm ^$Q)mJk/5eñ핃t+.`wߪʖj]: faYsXL3]NK^VԾV|.J?k; eEŧӱ]WIc\(]R{v[Վ ܹOk Z gY ;ӡI4䟗lИbBL!`z\dۄ6`nAzIvJo:͊ڢ\ j7nܨ{~!<-\lL,U衺ֆso^,Mtstº_gQ̡^)&~svm'ɒΜNGr8pݽc#nd;YnNv,>.КƇU9;%!UEQa\һ E?kn.Gm탤c\"w񑎡=ţ+wXW:I}"[nQnNEܚ ŷxE0׈=o(7ʩ߇lA 2k,oG]z- ٳ=w4\[Nz]~I޽ ZuR*++ {{{OOOk^u>-f@ v4iZ}vVyz\@>w\t_`BCCd@?ׯ_ݻw޽թ!22Ru@988ƃUN0B`Fz h\Cz\@?T*U6@edd\x- ի=* P]7n`*GGGpe_~AKLLL͛h ͞={6Z ̄%M6%ӧOGKjjjn݊p8)S%;v@KvBK&L_hرcђ-5jZRXXx;)) -yÇ??aÆ(z IDAT%999ǎCK dee%DK=ztisCK222Ξ=+OL,KX,+++, bJKK777,b ,&## bӱ(,իXL\\sy,_~Xɓ'!C`1GbF`];AƍbO,fʔ)X͛Yfa1O(#}З/_0L+W$={V,7J~bD" 1MMM*Bb;(F `1\.b|1\64iRUUX mڵd Ylt@=VgϞ0$ ]\ !M|6 llllX,JZZZGGG,b*++ɥXLpp0SPPńc1XLTTHHOObׯc1b.\Ō19u]$رcXLJJ ~KnnnɹAܹ3$$6IHf Noٲf|I e˖-:IOzD8[[[t<)))%%E<|܃.\pőZ2uwwOKKz@YG'W"PJ0Lɫ5l?hB ɓ'[V| ptt\t+ Co2VyRt``2 #P\Uz\@z\@`2  u݇tYW *Wmqm DZuTEPTTt}3,,LW@/@}2sph6@ h6Xe*5yyy9-ڵ@U*=. =. 0V`*5 UJOOOGK|||"""t  *=6@ h6@ hUza̘1غ0P(ghIdd@UN#mqmS``( cq "I-腿 [Wy„ 0\(%//ƍh_=tO{ЮHqoy%`0LLL,,,bcc}}}"`2 2P~kMR?qfϞ5!O\{555N +Lי/#jkk }˗uUH㒒M6>}:++ٹw'NUHw۷o|gggwwqƹI:nݻwgϞ---9R|T}{SX@tR&^_0jԨw'2h hc*eXp!8Q VH h 3f̑#GdW;\pĤ'O&%%x<ɓl6[nSVXX'$: ͛7Ov}֭\.ԩS nܸbŊ*vr8+Wfddݿٲe666A\t)55?r<=={M\bbb``Jv =f*=./W%#ƎǏpvZؓʴΈ2?344Tv]WWzMr[ 9~JD"`ӻ6X,r[zmQQ &}w8za鰮2PS@@YВ8]%Wd,jjs4}֒G'9SU_sJȈ=@@& '))ĉClŋj8 ܬ[;,]v ID^a'W_=.Y<<1FOȩǏ'&&J9 V*ӓݻ111;F2q^`*0 CWCiӦ3g%6lU>1W xzzJvO81x`U=zth{ݻK@D߾}ϝ;f2*s9s搻 ,10z…ZK7nݭ͛u&&&J/LDyyQ}=;wn\\ݚ:tKYS055ݶm[||d ȫW|#F\>WZ|X̗_~|WXŋoX̼y 6`13fblقŤ`1vbƎ۷1bsQ,FDNb㱘 .`1Xױuܹty!(y);;[>8ڵkUUUݒ Kqq1ptt$K`d D"QrrI b X,9sێ;B8za2PSΝ?#$::ZV^}Az\M6:u 3ft֍u EEEEEEi*///33&*-ZHO~m& =.&M3cƌsΑh FɒH?iGzX,zc'ݙʍ`=l6[n=`133cnnH_#Rbx֬Y?E`>.M,믋/vtt7;\!!!ģGB 555:`0v9ydٹz\x㵴%&&&,KWC,--[[[ ;{BHa%[68D[Ukjkjm}ZZnEP@PQ#d\o09y?O'-ޓ\:. [l!z</# md֭%>QҲ )((S@+ZҥKjZhp8` @3  )>d2*^FFF_$* sVXf a7|# r8q 8q"Y@TyS›bFYX" -h(8LXu A䛥l˄dT)!EPoSWky%D+94FFN(ghgm:.r랾seUZUBhKԉWȮRݠĒ;T #Y ^O#. {JT tIwzUtLP'jv`2 BJCE[ ˩G%ĝ|KbǼfxX,ϿU*0Wb$yAh%nxtzxa2l6[[vd}e2h`cg(-wifoĹ[άV$#qVs.*g]/M^Nt9_$RK n^C5 ϙ޿ٳH``رc={]W916>h0zF~o9ǥ̚G;!Cq, r+̜tX"nⷔT&{ُEn<(. iO MG̒yMd#|>(aiҦϮՀ"NH(dWD8$A&~k#ߜS4AJ/_.H<{?E4kO]yKa,eUZu.t\T?Ǝ]e'Gvf-I>tٵ||&vsss37/-~ mX}gDq<טS"hlqQKĿ<rڹ`rpEj4Q{Ŵ>+'7gyۑu!<:D%*Fzظ3OZx tmD#݋3:yPe3vjݪ[uŭ>'Bfm?e/_Dw>yS*2ۯ:.WNb%}H'A?*gQ/lNrL+`ݣrsssss++̤a6Axσ'[.RlͲN7(YnBK$ؘXL Ye-[FX{ͽhrMg=>+X0;qCGў#ݫnp3k~v蓋\!'oJߛSt IDATؘڰPY6چ{Ѻ;<<,=,Ic  h飧CظMq ^U >}}_6fXWh;vUzQ#G:.t2E¸ K>A}v}jGO.DyAޛB?]vmEɗB40c\i]81($AI$6~_'6 w,@<ҹ9Dԭ/ꠑ%bE1i e1hzw $H ԉS[AƠR'Ȋisqd nH{o<n#?샍G Ʈ}r铀ޑݫ~1sds%h"^j=!'@G:l}rIh9H,GLAnrS\vBH`_cnBu/$t%wc]*-1L3l|Lݤ㻕 'SM{Crv!Zt\ЇNEYvw5f)l”Z8IB<1sv},in/&$}]"93GsCVM6ƣ ~„5EjHw*Q}Az{#ב[R>tU\A#r9+v?kVL5~<_ӖYMK(=§ۚ;k~SlPC:{ί gXS\ސ͙: RV~l΍# W;̐{\Ur~pm}ĉXeUqA߲:dgWsG#yD^<ҽ;}k4KWU[?O.t.6XΟ+ˏߙwʴCrTq)?fqyNtTh ?fXiL5[΍ߥЬR|ֶ{…w7/|*}y zi|)QL]M8 mo|XJqo9xuפҫr$v!v<܍ iAӆ;:ھ7M$Bj#BfboͲ0Zm ' ٵyWK^-z&$i6[xMuiݭ8X9BwÇyBdº۷oWte8D[íWtA'53.ȯBgӦ(j'''8p {Mžu:tO>GԴd#seU !kro{ŀm7\o'_,y4;l]f?e -LX#Zrj$Bϴ}͟'ф `]eKXWVӺpBG:S? d\-8qt }J{]Xhդ.Qn</dҨ:qkB4 0t)W4'DOݕSt >'KqA_G:Nw.3u$^:Gd8o\mnFFmv' ned<5շ7!zflv E2sj4V׵7 eoc6hn_՞:jI %oTֶ7 t>Mτad4ghfk[t(T=!mol϶r7v7fN};U-踠#:z=I`k)]rQKOL'D"i|p;,c* Qm'PͬoL踠O#FGS.-~ =UQ {'a~ &YXXήלLJyi vXWh۷D/]OE]׿lsanuL(6lدxzz*3gΜ4rĉA@+̟?l?Hǃ΍ߥ7<<<<<^5j1mn 0^-n;.@x|Ͳ _tݟ ^u9! *** kkkv|$ :. /&믿t׀&4HJJڿ?>2|RG̺ʀu? *ڵ :.PH^^޾}h@}:. qM h\CGHb/=WOO:. {uA6mڴi=}G BZa֬Ydz=777%Z,*v$\f~+%5]'C4=PIku)oAٱ-G9 /B-@0NÝ (Rɭ%."N 4?#ւiJvE/SR.!, ɮ^yσz%6gA/&=.wߙ,- X",iݫ~XVä1.%#_/MO>%GnU@غiU9K r*'U^,WOB"ޠf 0&]PXT$cv!jciiӧO;;;ggg9oY֡H_%r|͠1}i~ٞ~%BuBBB(D||?t9޻{oYX`B obuy={#Whw[nUb]ڼԵ7"8tv :d̀eqC迱i.N2R&|߻qxO}T}tf:k)&!Ĥ1v?x >b5uOnl/Jv8$k`@c8zT uu[yMRz;qAO&eOKA/e~;"V08dJLȵ!WRNv9@غLc=>AwĽ$/~r**Os7r" Q,M,M.::ٍ1qqq!{ 4a00WhGU6m +ܽ{qqq8p Y@ht\@sCQQQ^^>NV=JNNnkkGBCCa f^zݝU$&&ڵ ZhBQn]e`]eN٘!DD|%6zNE5\ D?SXeǶ2apT]NVc%:<{,77qvv߿Vax27koڛ'_v1bt\?~FO+Wl۶ ~ӧa]es-u:wa]eEUXXxi|ʊb'@M ht\@:.tpvv?ZHw& h'ND/=kG5jԨg#ndܸqƍS]Qڵkl:. 111dz={{{{{{:. qM h\O>G\]]}}}ɪRuGh &x<|ٳ2PȥK~'|d̘1K,Qh#gϞuA}ᇰ2R/_uA\t qtt$du\@g@tacYr2U^$JӡYf c;0@gNY:.tpssG^& BX"h+oήͻP$9NsYg˲ B|bAZٳb@QG|2>bkkFƌ3fICᷩOmԵ?43$"epPZ!22@gcccccCv@e%Hom57sjU=H)2EXKGolOF]  Ypʩߙ}x_qX*mcG "N\e뎈UW'w4xqz+ŝ_:@6Xmⷼse5%u *@ |ӓ0W]yte@Oȅ dU]7{WץV> ĹBWȫmoxXJq4(^X7]mx!6_ /_D؅Osυ>Sezwҥ2R7n܀uA_zqss#$ߞt#ujW-q&ܑ[4Ԣ_F5;}X:fYFikVݤ ^7/-ڭ |(!.{C:'sjTdͿM5[#]'}ChDSkJ.=Ѝ4X"~7k}c;ɷ2;؄c oӂ؞uQ:4?qSUo϶qՠ}ugǫ6ovk˲8-м?SK$I.QRvipolؖ)buqSrJ{vo[oZ^*H;tlHg'Lއox_\鸁׼NeV!skID|fG @V1zTZ(2aC?q8I> fБ~j(s4ƥd1~ |fCZ˰2衱c޸qRt#Ѫ+J SMʨʽSbٳ<: yg]U#ܺʵc<´y)8Jv ׳RžNfE񷩻wF( ~rrlnoi?XqB)v\ v+q5f<8yb޿ "Yֱ~rcvrb܍D!Y^;XMRYzRY:ff4GO.#y ϱcwkok ׭R&lOcX,鲛/]Al_.J\ҧMwOt#k'RE$Ei$>GF\:<|055 V1ۋc_^$-Wg7/h,)Wȋ/J/J^uS4;Vݬ *vNϛ˰='O$_ox Iߎm쿕4 ,#m44M: J¦kwTfg4t4 IwkDj} t\Ш3/7f<8!]3EKP#B"B%~{ iV=BZmPRYtT޻:̿D]ab4%$CMeϛʎ_9cw4*&{~CN&gpQ~/bcƒoB#cO.)ͻWpcˏ(#OΗX:xyy͟?8pjw1)\J.UX|tK8)]5#fc&>}2BaKcj/Ŗ .̱~H5nWlJJq BlSލC)STUU#qV~: zhoG,,^X7x7TWT'lYb>>I_]/$>5Ue=QV|wϥ/&I;{<S5 23s"tV]p..? !tiB;.aڼԀVP+߇4iuϮJ'ߞ~ӽ#yx Vc٫.4s?3>5]7?{9ťS]GtEPe[m9.V-R\!X~άSruOwd/P,FXnUZ,Q Hq[U_(B:.h ao_TTA^S{q(Tr,Ugӫr$v!v<܍ iAӆ;fJ;ھ7M$Bj#BfboͲ0Zm ' ٵyWK^-d&$i6[xMuiݭ8X9B°&q]ΕU-6Py˽[:뷑Lre›ؖcuIc$L޷0a.Bj]BfnWnZ>ӾVnwlbL t\ d(_SuwwˏNvFՉs^򾫑wQf{nԊi&'Ic~K]9=߯;Mw{um#~]:d+?SiqÃRow IDATRR__*܌O.&,Ȭy\kooB3 < ޝ%%dTf?/h*)okoh%b:Lm4vlnV8u7JJn(M+imohpT]} Ȋi]7Pz:tC:L؞mno9&]73ҋDv7xȑ0c @+||$%%+:ujȤIK6 :"i|p;,c* QmϜRY ՒJ ,{իWU# ݃uAԤ#(L#N:OGq ?aw4 |}}/~iE!CU P=~x[骙>&V.W9s& TJJ aLT8qbPKO35%>K+ h|~x߹TnBk׮ A hK-)M:f3t L@W`!t,/<{r >9ht\@2tLJJGCCCɪV믿z :. ^zfFFLW 9yW_}L:u͚5 mڵk*CZna]c hXWP]]]vK'[iV&@4*@0tT E"%b !*n.BERqC@@t<<%]Q}DP?C(Ν[]]R`~ =H) Yި 166V~ǟBFlѠ;9"HEsbX,~ybb_|abbcǎS:.7ovpp׬YCn=03d'ܓ0tuu~Bl۶em޼qFBƍ 9[l!]m6BΗ_~I_ 9˖-#%Krd1,^#{bӧOrf͚Eȹt!gڴiDB΄ 9n"3F9r$!'333l0BA-[L(VUU͘1K ɓ'̓/_Nv9055%DBBBd\m۶ zʔ)3fPzk/;r䈻;ve:." (--=rOgjj}555=tН;wӵ?Hfff;wTAo ̣D|sssk@:;;#&&&(rB Ci1!J*cddD2Frd'inG6Gv;t$nۡ]n;9zzz]rLLLF4ydKK嗽żk֬پ}2O}7~">z%t\*++'௔tJ__ey9]ׯ.s 2gС]DDDt3jԨ.sbcc̙zj6z UVVVw,**r2 -KL&ÃGFF0`Ǐwqq}vEwƍml%O? X,[bMϳa6nyl_ڵk{轜 vB k3H$O +ZHT;\.aBBS9ؠo&K޽koo;dԨQ{N %$Ɍ3?N &?>%%GPTTT>|xΝd z5=rÇ?~<33odDm B";wn۶m999S'O޷o pJܜ}^|_w͛7mlpm㷳1-[_p…=vN;aOצV7l@dϞ=`W4Z2e UիW|meeɓwz֝;wR_~ex׮]MMMhB oh"dvlr27o;w.99矯ZX8vH$>}lذ!44T6#dG+WYH,n޼? _~f'q~Çx{{'$$\2Zŋ{yyGGGgff~ᇥ=xҖ-[E'[nM3#>>:cK F3f̘a^=oe\팩T*׮]pg͚d^:hР*aXWnkkʏٝbrBAAFs}S'~drqmp>MV¨N8aav#L&6mZw݂#h;Gwڃ[KÇ7|7ٳdDN^^D"999֭ g|I+0Z&J}7S}URN;a4K.^zj|+ߧzg-;sZnZ֍|77Fݘh*lСUUUaomޏ%r7SQ9rĊdj|@p8nݲ ΝKU[X}9 /tFؼyn 9-::t3Aΐ]zpB[} vB3MP_|>8p _Z]T*l k/ͫcϯUO֮]K?䓴g E`` `0iF?~p1" vBK}Yʌ_y}}}JJJ,;s}77ԩSTpbb"Y$$$3ϊ=Ru^RT̿okNhSc cѢE߷U?1e2wEqo?pGKWWWӞ=s uLB7u& .YD73z stBi4۷7TTm (˩c+l{q"`$&&ԱX,={ExƼc,TXXxY1Xxqa/F .X%N;-555ݽ{_dd 6̘1]+\Flp7\${+d2ڳߧ:zf533zL$ipNhGDDӷlْۿmIDvۻ"9G\ci@{yy}TjLO.sԵd~s GT}=tڍ7^z{W X9k!r+rmѣG$|>̙z̙C5ȑ#m =GfӧϪUr|۶mmݻb߾}\+l{8vAKW] /K$wwwF<==[ZZxrڈ-{WLMM4] /WThllbckdò{'4yFr ;Yݻ"9׺ ֳ`G׭؀d~ɊAgNh6>O;ȗd wT*+֭[ý] g3k 6P˽;$ u]/z5wE|rnmOsKXv`߷oT||1K-_L|JKK̎P4`'4yۛzvE3444Yn危]+>}P8p@U{Όi@w^ycǚ &P}-iNhj{רW+/*/..6jW_Vc̆#no6-P(T=?I/_NP(nNh3j+))!o7`W4Z2e Ug```^^8qc6q9lذ.~C@]mmm䵗{' vM2}rrJNٚҨj׬Y#H壏>"?mٳg\->>>n~~~SSD"[~=|'L+V0r3f' \\\{E=<<,oh XIV. x۷UTT򖖖;wڵkŊٯ3/$H{Gi !5ϛQ 9KťƼ8f0D'ON15.۱+JɯwF߿CSk ʕ+u zjLfFt}'T 3F@']3NhQhѢ./9"-Vq{Θt #33t{ [AS潝15.ǫܹҥKJ2 $$dʔ) 16lx7̨>΄X,~Y"{*ZԞZ^^^nPVVF  d4!!Q)zk\.Ww˥M"w*:Z/-FM І%Іdbi1YYYtZٳgi1#FŜ8q3fZ̡Ch1&MfM]h믿b͛GCYh-￧,_W_iZr!q:q/_tR*+W!Sۯ1+ :qÇGoT*YLF>TT<,p8Rˋ,%K h_iWi_zaرc&M"KƏO:x3f%SNȒ={̛7,5km;v,Z,Y`˖-ŋO>ݯ_۷KR'Nz{FFR$K/rF3l02b9s,>(Yri¶;,H$?ɒ{, ؽ{7YR^^>{l$$$dǎdIIIɂ ȒHWŋ%7SJbb"폛KM6%YYY/Y2x?,9/L >ȒӧON>333{=رck֬cǮ_jqnOj \RLuֵkn߾=;;[ ]O}} /?j( ڧ.\?@b| PPG7ZX+FVQܘztcS?~w% ~9s\cXAAA Cb%''%ctO]\\h1~~~CMWWWZQnnnڤ.chݝCuڄg&<ϧńb BZn*ooogm˖-[8@K .$o_C" Z9~!#Y_Ξ={ܸqVRRRB[ELPwȏ 6L>q a̙V:p޽{ifv@ 0c0&00`LXXh1c Ƥ:t#G3fI&fRzh3)t͞=IqEZ@CCCAAmfA׿{=R Ν#3k4˗]TTr]vwmUZZzP(̙smHY߿8Y(STo& o߾pB*]dI^^ކ &Mԯ_?ڇ&m*1 CLݡb4 m$hz'hƍ۱cǪUjkk l2HDњEk~^4,Qմk,+55,QT[l6{dRMнR(srrWWפ$D&Q޵\TJiɒk׮%---z-w&Z>On A .gb PH@MY__ .\@ ݙ) jժM61̨{lȐ!fWR3k,+698Z~|}omqEeee\\5EԮ-bXWb DV +J-qY,$aTY&$ ੧裏 F\\5 ,Owid2բh_~u.;tPk5jm!ɓ'ل!P(D<ݝ1|qCЦ@w+P ݤrrrLYWWGf8&A!-B61Ԭ#3a!d9s̙3ǒL&~C]kj zT0B)??erG"">a0`rP\NI<d2<x95 (wg1I8: 1!%қ 9Ԋa`h~[¢EfC~0mW K /&;1/7ZlV6I9.En܊:bEc0եU,Sܭv9'w\o kSPO  \]aB3`>}Va˜scE|yy;wȒhTo-ꍘ:)i)s'#G$c^ed¼ʹ{LP(dWk|P"d9???̫,}v **gee?d}+dc=!Ds-a$HnݺETTThB!d 8"B5j@vDH11=!z)cM>}9ڃ8--2B3C4^ .\hV^Jvv6mNTF:u*-?G\ja&^ӓbi-B!d 8"B>z(`3^Aԃ ȒPs6>C!7n*#3ݻ¼E"۪98LFKrhprWY?yb1YӕI_|,9s~8"WY,BȱIp> WBq1ւT < ))GCdj@/;O{%\Y,.%qy^X (Do܅}m/ul۶MMp>dg÷Ӱ`Xha &Yп˥5?% 1!kagƠ@RզT5ɋ%gJ*9q}0qEVR {fTFNSAc)oi<:qHKý{ԡ*Mkm+nhLҢ-/oR\e^>=7J(תL O(***hu0zۚF"@,X<O-5mDEAQ ̝ u+'y.]K B]ܸ6'*PUթjk5[HH?L+\ox Vʷkşg6jś]+U 7.z~%.P}?B>o~\HMԱϴ^uK}xq9^Κu/8b5D&Ӈ[a[++@YGBzիOrŶ+qr,Z*WV4klZ^G|)UV.i`2A>)}D U,nh:]\QT'vqX́A>Yi.!~ızriĘ]PZZaQd1Q}#i3F㝽Y/] oO۝9iiʊrjY'550oP;+Wر |>dP_׮ s't^ F,<$̝ 744ٳgjXx<]ec:G*Jd g+Ļt8ryM )o:pAwU̿K} e :vtu= Hav8R-ɢ:q]a˙}:6KV,WQ*]+u6B/qZ鋌jWkO %8ul-,&c81ʦmm^nn\G 믡M11pr7Y z/!:nǎWdzl6CHLٳfMU=lp{̙0s&nG%?l~;ͯǁzU8]ޞ+O¸l֪! +7Q*? [O2]"L q=lր@QåJ2WWked0 &Q1oijZV4j5_=7J`0@q%umdD 356҃Rbr_0X3F/d2J9 `'Nh>Xi `?pkذNH{O?p$DGwܿ~֮3ٔ۷;̯ǁ{11ᎆ2$XQԸ0SŇTIJR^;->2*c_Z@z/5Wַ)U\gk)SteҳK %Fsn=>24Q@Oޔx?U ߲. [U,~c+zؒstp6nosuDb={ػ fRWZJ֜<kh{ 1f)J}>K Z^'xϛ34GsoiJY0 zӒ 'w ouph<@ZP/7]Vpl7?p#!Ձy…x)|>,\ހ?Ky_H:ZVߵ9TߗYJ0#'>$s۳o޾d\.yնoW+޲elUͪ*OOOuyJKӦLjSct7Hޜz̙U}ڧz}(o~VYs^UUtdd`۔ 4`DR-ii+o?AGnj?54U7KNÛj;ZaR,WKZU_-SJw*ZJA:KϟOϝܴIcߖȀxfy^|&N)Fe!dHr7#2 c+٢zIQbFxQ"P/w: #?9^-!^=2LFz~"_zP3%Ωvc1} zuYl?{Lt[Mpb{O*\;a0.ksgM0wnO=ej'np> OÇCFdd@z:H@N_ר%j,1Žwգ!iq(! *[5bFy$\'3v?AA,qΜ)Rz/5fLb GG5jFQFuF hTj(ϭg`VbD>kwh*_w3?u|H`zx>3[>dn1 { 4ظ^~Sak+9GAF̟sv#w1j5tL7Yp0>\_W[wM>f03NeiFcB&5cn =s' qiqa]*:6Q5? f"L"7}Yr\HI1*]pJxn\Ϟ56Uva1D:I<\]ƅ K2eVu"dB~~ouS7tUGEE&sS%F8Xj8u nJ> wv4/-7\j*ofp:ձ!5?+G&uG ;[.i6j#1-D;Uu8(Qࢺ&[&%g)O רpNپk?jI -׽Jπأ7Z]q٥[v<=a2mϗ17dߗɄL̄e:r1=,#Nn?>}ڦ#.˗w\sk_q̈́=k5kџDw|,$MϩlRۺ1.ՕK}P<7 D&ϯh#zy4vn{c W4yvq~lA&Ą$ Ke*3ѐ˗s7;Lu%?;uw#YYIC:]~d_oY{ngVϫ'd1* kŵm>< k[ nb0 NC])zֽ q6K晊'OxQ'/=y,>3Qtt4#\_h\ؓ wM7Zݵ"aBGMt8H$iQ7Z[TFn/fG|>?MPRyyafyӸT*xY8tXz'T3Ϸh-Owg0`͚4 O=.tigUU0t(> ڪ*XZ}SBo׊f3BPn;}aY.5.~ɿZDBZ~I]L7HQAdO^B\Z_úseF!5IY֫mp ׮]#ן7ZKuoRK9shwXᶸɫvmfφ76|x뭮# -[ mlgw3g̛~;!Ԗ\QC[ou>a={6>ej>IQ[~sϯS'y$/G㹰 h++k߯&?'Gww_c|}~nn.,SR״ݪi*v7VƪM}} =\W6KRKʚrIkaN 9g*Owf2Ss}n+ErY(W_%&ۻY} , [tv{ɓl p(|Mx˖nBgeSOsQ0z4$%Al,p9_|1(":/p.\ PV> F0t(p6p5c`[Zw/`$?D"P(rsaW]Yt$[F,U(O(7y̕Z=RYh<ϋY6(! ޹Z>VLnbE-r%9gGw7\cQ#ҝ n:?.sKScC='4k,Yu#^ [̙jAçzo_[xp$<Pêp(lM L]6nvYE/00MG&10 ~C!?F ԗb.85OβjT y7$#a;UMvc7Zd#w:B̚0>fÂmx5/ǃ_~;!1 oj z n܀w#1 ?99yZ`4x{|ݹ SNz^R.im+y.l݇ϋ ~lƖrIkuMҦh+*fX=7j \ t+'c[ (羳1*dFɌrLpݼ&bbbzb2߼7z"ѱJ gg@2LF idfps3vٱ~BRR *#o˫lPZΝ;~id_5Bf**#gT*iJ6`S!!-|I*#;nU%)34h D!n&o"6B\cƌCn9b~d2 kkLj!9•+W͜9se9"iStwAHy輐p qDȖ\û7^ɆsY,Vw_pE ڇI!B޶m`,Z^ATAAŋɒtlB΂d1رcyMq]FӕIJKKoo {!Bzz:UFڽ{y;mo\[[yI~ŋ%O<8"EPr*#SjZ/R*9Bق,#5ȧYl+ZJYk9lw;Lat?Eh+ {7 6ww9.lCXʠA&*+o_06m?yYLFaD"k?eS'#0q]+nB @MhGѶiUz1?TZ~d>mtr"ܸ~|̞m֠s"Ba!@Byu[S_~}R#~[ ֭sر#)).ArSN]\IHH6lxtF[BC[ehh^^^Jh1&ZLhh(-4O)))$''bnݺEIOObt'deebFM9<-fĉ'Ob{1ZÇi1o>Z… i1v>QaX* |M6avyxxڵkܸqk׮wsP! i%IIIM[Гd+B`Cr.KxxxbhbtBZ-חӧOZ?-FIPP-&""Bѝ9Nׯ-&**HkŤbi1$%%ic faFSNx=8],ӧOll$''ddZt)CbmܸQp81<`Ph0`L@@`1aaac 73`1))))))c d2ڐ!C ?T'NļB02%&&,,,F( f̠N0 Z -B1=d2 ưX,Zn= u1/fqqq1{֭ͭz|رcRSSg͚ :M]ላ,wYkE'N!-B!-!dffb^ed Z8{5!]8s !B!-B!-\e=2~xLRZZz5$$$$!!^A*#Y0#rpxU!qB![!qB![!L0*# L<,߿.B^UF!lG\BpE!lG\Bp2rU2e  ߿C%%%٫=\e02UF!B![!qB![!´i02Pxx3Ȓ{5!]8W!B!-B!-\evM˫JjwyrB2eʔykzn]zuСmmmڇw1c1BYO`cj(ʵkr8ƂYf555|Wّb^*??W^>bc}L&_OFFơCxbŊL6rHC{\#ߓԠin:ZsQ1nnn1TСCt{b5kP5<37 8W9m۶*/Y+#ܾ}"Kbcc f8^xaڵ]>8p@;W*رC uƍԺ{yyVxb6 J_~>8gr{7 2~-Xލ|1~~~zDO>$Yld*?ݸq!<<&!B=S_jIFsrrh:tB{0ao7eʔ~ǬUWWg6BNnbccjڳdʈs?ѭrĥTf3x!Fcu,i^x:6r.OٗZ0rxBN<˥u޿:7 %r>{"tR̫,|rdĈjCѓXe]xkllE"]ڀ9BȦlsʘwf{JbMRQ;gL=f0qBC+tR5| u+zԨQviΜBoU^bNWF&)((8y$YG z̩O2E{ok~nݺeRnAW\6l#})T6#.r<F.ث=7-[FXbjOwz݈T*˵80qD=UӦM#K8֦iiǏ[9B!}l>K=|L_zMe؅ V(~vl! xWqܹ3ePYY9qDGت=܈#}Ϟ=vlB!docLfdWeD,"I BKѾ֪hk-EV-UJvX BUd';1LN~9ss:3seQQQ5 oܸ;wx^xqرY~Є /&jjjRRRnܸAt]&w@/u\zp5[Q%ܹsCU\x 22̙3l6[TOO-t:O?]|9i dc\f̘*V5k'<|r*,,$1%оdS:88ݻc\̙*V?>G zcǎ:oϟGM2׷K.*.k֬!;ie(Ѐ@E_|-&MBӡçLux<| |P1OQe&817ݱXc!LTVq68&*pZr3p8T*U&M6{7##cӦM_|܏ ͓dEw ;@|}} 'HN< ihh#;n۷o„ [[b@ hjj0 &IV>=<O("\\\/^  6V/ PKFFƹs=Wk7V/~G-:kkR|Onn.̜zaɒ%u͛%%%Tխh1"X .@tL&fJ_&&&/J@/,[0WyʕDjjѣG=aaaC%+v-:w]*^+Nʤ-a6q";2h~5*.xȐ,!4{[dSxǞdN{}s{_,0qέ)@}D;vTp !ԉ3zEPƁ ڽgS..I-D w9"mrXxJ>Xxǟ)oS)T BPq'!Vi6BM{m\eо?rrZYʤufn\r{a/2)ǼguX[*NAW.0Wc2$rqi)i 1^ϿCns6r|dK-א=/#g#;SOG@R+lxP5g^\l9 D177 š5ks*gg_-J^u)ʘiDm7U{~׻*{N: 4hZ[ܹs*g?c pPkyD޾_XyѣӧO=2+V hѢ+lj{hOm D^{_̊TlHmfߕtR|խ6u`*j"zs,^nƎ& 1Ȕ9 Q >pQ#h"h\})b02U6X[yqM ųyیi ރM)XΎ/nR ~.1.plmm=FFFd%/KKK¤w@/,^0WGեKo SN:urՍ7;xbS^pppp0L #] T\@:.Q___UU122211!+ND"|-T\VXAv~-^ZZڿ Wk~MuPԻwoXW5kU^lT\{Z 3}tu+.m.u\x㹸{LMMIc٠4 *. ˗/' ӦBCC{|OPPL4iҤIK tP/_V섊 ‚ N{dg2X[4+Cgm@[TÀnDX0M֞.^l]uak@PR\C6sԳS!!@GDRH*5l({P}6/Ia<#v({fNT"IkӶf=lQK%d:yߥl#vd >hz^/ш!;fȐ(dg4D8$CZaCFXY,a{w^=x,wӋ PqD͞6Iŵܚ{>:qr mm*|[Ls/?RSSS^^111177vj@mD1s\l=z@eVl}p`w~N7nsC7r'Uvrr ŋ s{W 4s9Ӥv/N(̋k'_ҩ/>|c`48k#x2Ȟ={`]v}K䒉ރ'_WW̻3怛@OOuWZWoQeaȔU{$7##~LdKOyI o$l1KVzt 5@cEVX?+# ;/" UܴLriEF %ɩgOFFo?s!,+:FX lˊmnϱ6s sٵ8T>ټWeJA ϐcͱf)rKo=`sMB˻^Mv^o u=]gSWLϰw Bo{xeׅǟ^^!/łG.$;&Jve^߷\R]TY;먏_E:iO Ki~#7'^:ڞH70$+ ܏;{eNomF$.vz6_,hPQg%VU]6gx7JM,E zu.T\U=F-evKdRGd?ʗ]&}e2211RwN /rnƎ<:W V j2+rnd9깒 G"[ҀB7ңeg3q˻3_+JEIeOeј<Q9g#vؿHBug!T\֬YӮU{mh IDAT}bn֯_Gu9u:.I70ζ.~~~~~~;yDŊ+WX_2­oXʧ-ls_#⿄z_ٷ&}Fѹ~ם6_w_#jA;[wF-0{-aY9$f*.ZtbNԐ[Sp-{!bsn̋kFt_,A!MM!_$/Kb@,RR܉e/8ǝWvcn]-]u黱sCGrugo4xӭ猻6_LA@ ޳=]O5c@Zٮ>-ʤg;Y\0%7*j44^u>:Sϣs{ ޒ! N>qlW[L<]ߙ'=AMT{:lO:fv(2{[u\nZaэ~-.efuTGcw\r_j)M$f*.Zt2$dqKPȿV<~8++++++:3D]PI^j7zqz*`fv7kHe2Ms,rrr&2 , ~v4]9.dߓS·W ܋iP}ӽ^zQc9~/zz/$µi2+Qj%[堺m#FvgΓӒ4f]~ A_`m/[̰v9J >/8p Ԗ-[*[Ul}kW!F)BooY}v['Ц{;ىh.wm}(nBMƈ 9p{U"G? B2 !' 5ȝå*.Zt&Nl ?T>uk=S}G:d NLɤYL!e`)7oɐH*CGY=ƤRWdöw`2 IU_YKs>r7v5ł 7&o\w:-k[jK/ꊱ#׆LAZώc93`);BBh~Bɫ-Rtʳsk T,w{Ph EȅgO][[LخƎ㹉*7|T\ЁNM1f>>69u!kR$BP-KpkCbll|fҌiR{[S]j)oSWoSש#2鵢uoߒ!~5!V*nH-J*J))L<==̷~t@/lذA*ÞNxϊc\?pfoddkL7⋛J3צ&R^]ڴ]{k}2=zشi!t$'A bfL㍽'L0a„'D|B(BاBA^nJF:E:MO/"Cv=lvZ_0l9],}X߹yO: 3f`+:ipVsB=xcDD:q:C9BǞ]Jifad- >]f!q@vY/}< n͟u4{`hؕCO H ݲG^~4Q1&%fD@uvXkJ" 5ފ.ៃdrD&*poE0vǬ7n!4!Nbf)ʭ)\xG6b#4N78s>>C޾< yTT=(]ҵ0ǣ'V-n(ƌtKyXkMGzXz\[*}veUяr1dQ5qADz4lƉs{]+5QZP7RtޟczצU9ߞ~kzۇ*ł9 [3N_I=z>\lwP(F1NlGjFXz6f}{,--훹5܌vEsvīE)}F8v1w7cS(Q峋ɿe]+ɥfN+:E9t 4u1eX4fYu[3gkv+rk SK"JM6s`@[ j={P}حwmDdu†a}627Û/ydd{pP+͟ߥ-= œ9s*o޼-+Þ5Dr~G'r=Mq ;U}E_ONʧMQevv߼ys޽?TksѺb0zQ#BZP w([an1GH˻<́k}"n3אl.-C].]^ ՟~7nϓ(>>Uzj׮]u6ZWt-RkOg4g.VL~˅wf$.? K/Yc=ڶmG*[qui[NJ;[$%4(foQ&x}sW4? sY-"m#.c<LIA鴂n`8#vMH(q(ed^ka%x8s%]/N_JP[T2eL@ ^v]VeI˲2ʳ^l(ln7IeR#:ǂeg6pjt7KֿhnTCa4aw9x8[x-m*ÀΣs-X\OS@ ^vxmQ킊 :(ii0fp-YfAmGSw9 7G\~<.@zT|md :4at;8Bmz%]KKC|oW}]k] }'C9i 􇟟[kú@_l޼Y"yzj;z=h·f]N~[sm1W^;vx{{;ȸqƍk~9;zb'T\&OLv ln6lk7^5mhuNrvH.1. -y׋vh6.>^sBM$c\@()):9iaСܿ 3fUޱcLW~rA={{G=PYWBg_QF@O矄um%;;{nu\@ oBR. |y *. ;wuA+?xxx;ѣGn8xb'T\ƏOv Р :.xO N!DrI>&Ic JN!u?C:Ej0T\Ў Ǖ@AW mE"7Uѩ{Y-K$o B IdgZP!PaeMU gA;&IN*o}3q]|M hT&"i-{TXΦ1N-юݯǞ]dd4`"!+4{IA~N=Ŀ T\ЎY3 !$tgl΍byIE/j$2)!MXTT3| hӏr/2iAK|Π3k~ ަʾ:zaaaLPɓB!?Peß^^ _D`?BL{a[4y՝;w{&O6nܨ*cMlAӹF^v]Lwu:pJŝ;ymiWӵa[GPq+^.!6?vݻ-B(''g߾}nՌ]{muBMc4l1ÈJ ;~0ǁ ڽz7Zdw )3UχtČiLv:Ħ1wD-Głdk6eh6B|!Y)jad2o-YEcxGPq(,3kى@3} hua20WCU=z4 +5޹sֵkW*. p T\@sl|'Yv͛p9E#m۶{bbbO  ױc*۷*.P:155%+u\@g@;μvE!$L@;ϩݲq"? gi<T\xٹ//?U-uu!{uIC<}SSVV6uOMG"""\]]=, *pq=T||< Ԓqqq G{<7{i@B}Yz+{j Ɓ r/P))Z#.<EK#CN[`P8 ?4v7q/SĔv $4:QÙy@VT&E6ʗ쌀;>x:u'BF5H ZvI-R$ $sX1cөT'xyVVյsJ>RPP)0e2l+j_rӷo_@/5J(|,͑#Gh4~5\xqӦMϜ9SAN8*֛>}: ӧ *,z'N{lllJEp.@tC/_ EfpQg聺w)| |}MA}p@>zqzQC^ݤ e/ʭrnƎ<:W V j2+rnd9깒 G"[ҀB7ңeg3q˻3_+JEIeOeј-f[);Y9wv!\k6)zUqɿ @+N?G,ė[E=m/ [s>#oKw-]O!oW ٪IZE3QǸ=]t!+u-]4g]n\7c'%?q$'ZP:}fmfLc7zFXYi 2rpXWҠA]ﱱQw_{I_fTESfKnlö_aytx[2#b?<utҿ$)t ijmN뤸 )Bb&}F*{}&8-8Mcu j0+$0qVe,3]ίR;obmIb&{"wmߺ(nBMƈ 9p{ UkG? `m$q5ȝå*.ѣ|OhhhmN*RU^;xUbnG_\!@]X3!kÿ?Zf5`Dbn堶k;rI1ºÇ BTT@զWteSN^pBIHHP\WYEc`B5Ft5bmϗul^?O&.W*7MLX[Su鄷4V^ŔP{X̍4-ZDXW9""h@/ܺu Uի7o{4'2x|q]+jP 4N;E"W^/NO*NL#i4*nBr}f@b&N1M^Wܚ?e݌i⧼M]M]ʤ׊RץNDd|+iX!}((Uަ J'myW~AyvnMA֐JbXrݦO :dƌ'*ށ> RwT 5!,!lzⷿgwxyaT&o'_vuf%/j\;* ܏ :nO$+A4_{iX[PCw}y{wzx{yDuGH-}}l|ʶǸɓ=]v&6DR1B(VnMcʭ)\xG6b*ȡIDAT#4Nc3Oyl^ҀVlKgʛG-6Хb=l=lF|qSZiڴ.$#צZs^+1bDii)*. W^uA+ 2֭[++>% {h}ON!$2++O٦D& pUS]X[SeqOƝRrڥe7Gw=@.~a2xV4diBǡs G{v0m^vj@/h0t@!]Z6Ǘ n9;Ww=\ZP7Rlcf#3S1#s Ytkc/G5b{c<=zX?m)Xp8'akɣ+ Ӻg[2ޝuD,{8#6{-(+ϖW܂O!tiW1g_MZ2gDcwsw31*jU>X[]+u:4se]Y!,ʡ[)Ǣ1EϪ/ܚ76Sڅg?3`\C^[SZ!TP}lr_{rQciuރ˅w.n2n#2$6Y|yΫ'#ۃևZirWVn2T\ p6L^Ԉmg=J>bDr[Ṽ+sZ5d7.Ƽ4|KG˅w ɺaYigtf{vv[) o9 IfѨ/ n* {γa[( cӘ0-fUʶ̊ox0wm+m#~L]f*. ]vzfzz:LWj9v7|9reʕ+*CZba]A LXWReeo=nz8 4S)L&IeJ C*2jR=AAA9'""BGxqr=0?@r~@"Bu98uW{x<}cTܹ3B!+UWW{LMMmmmzCN~v6 E^#[xR|{+7_wjA嶾L PqU:mDZRw9qǷnC rm| - DA=|O҆mAn>@L$ګݻwU2e T\=2PÇ/^3f̊+dƍ*Zt$usssaBIIJRD"Hb1T\>_RR響!+)-ZԥKPX__BǏN 3'NyƍN 7p…+WO,o߾|@O:qt1p8L&377ÂJheH.$$ܹs۷' =-dZ|Bxm#J=T*HDR__100rm#=4#Ս144dB%ŋ,X?a„7!MgРAiiii.LPEzzz32@JJ "d2\gb1!Nb!dddD"XXXb Bvvv|BLN1)!///BÇ 1աdBLxx8!ի(BLBB!&66sIBL||>>L&wQ5`X,|OSSa=d2|@aX CݘFPn ͦ444D"|144T73I$uc ԍJRŋ_}ٖ-[F@__I^l¢55{aLL ~P(6m2}Gׯ'Ĭ^zjB̆ 1˗/'lڴ_bvAY`!? ̙3xaƌs'O&Ĝ8q3~xB 1G&$&&b 17n 8J&ܿӫW/Bnݺ,X@,5jرXT\/x<ޱc,--CCC.\Hv:077'􄅅)9mڴ zĈcǎx4Б|b>䓲e˖m޼JĀ?W:t]*.w^R```R,bq1-۷өScZ h1&44Ř={bL~Zk1fÇW3v6=ٻdɒ&_q}v[[[]33(t,}*@W@ PtuT:?|pjjjFFݻw+**,,,lmm{1p@//'}0n F.@12v?<66)?a7cƌKjOuܹC;.[O>||Æ {(z ]N@KGGG'&&wlmm 6t`ꜜg۷oӦMEEEo3}IZv_oI)//?もtui]=OO&|PfuuQv܉_:;;ݻ7((1͝1cƮ]PS24AG3 A_׎'OlDQ\`AWǚUկuLadaaA;]]zY:Քppp uAAACR 욾U']|СCE)&43 #t2呍Ç)&4}j9s搳Mqqq&裏a6m?Biu:$@sU~K9##>yG342^Ët:AvzlMMX^zMiihVtF"mٳ۷F)KWEQ1wvvjdg]^z… zԣ $444/KK˅ 7𑲲/RzYL2uuQ'MDi׮?<66{ϴ _}Uc9?&S3M6=F|v,9g ˳f$C{gff޽{ɩ[n={J?7h-KhtuT:]U*@W@ 磢O󼽽=BH<'Ny!TZZ<Ԥyje? y?99955O:KAk"t:݀v*++v0o/B:ZUV;vXv-˲*HIIC{ի,6`DQ\pkkseddxzz2 gS///g?Eke̞TtʖLMMTjږ9311yR]SSSUU̚eL++mY&00rĉo֭[]]]ftݻwȐ!,t3gtQ>J1/ ٳgcn5jbŊؔ;vHgwuYYYE/_NJJbVvݺu!166611Ȑ>^y7•s>'''2 t>z1#u/@jkkES!####h@+N M(Պ+Hx뭷h2H ta=yЪ1҃.~t:9Ʋ,lk~7|Ci2Z((QIÀ ٴgl\JXU`(@,_4 <h8o'T;9xT#a(d ƒ֫10PHϫ更pbXؤeYh cuTt6eD1~u].0$'Y`*pi$MMM)FFFFaWx6B~-io68@9GYqu (P}x'16 Z>w}G;C;Gl kjjҢ"Ya__Ν;4Ƥay ƺuz eB:txkjjh21%%%YYYQioߞ5kΝ;;/_FX555/CBB֮]O7Uuu56²,V^|vvv ä{zzMeJKKɤ<Æ]xxx֮]K9BHXy իIXx18@y gӚ5kd>>M6sz 0_2XQQaÆW_}U Q6mHÀg(5MKoCŏ]F:#jXXp@yK.BSKKK#&a]u]ֱcGɋ/ \aa!y-l{ߟvI.'I׿E;G` tkג O4mڴÇzݺu|o-imUSy} 6 999Cᗻw>|Iy 99ߤ.^H^tܙbyIË---իW70[~AAAnn.`î(J:^XXhff_iAAIj~l9Օ  ]}jׯ3gck"kiggG^YY)}A||S?"opgϞ%KҽI&?MMM'OwᗫW=zZw޺u aWW366&_xĉ&|ɩS75/ո k—t= _~%&&FΔip@iݪ։ZlY?'2& gIIIma}:c-Dn޼k'''ذ+׭[7\,[[[K7 06l7ocbb+y@=Fё4 777a,8 1yɓ'NPW$jv2冓S/. khƍ0VW:(p899ADZ5.))v`p&M4i$)8r(0=Fµ lٳ'`M6X]atL;0@52 cp'O0dET!'= "\^pcu@l޼ PW0<==:h9ɋ޽{S GNNNFF]]]aî\>}pcu@lٲ ÅIwqj۶-i^^^tYX݀Xȑ#bȁݺu+==ڵ pcuyӍ GvHp"/O1 0W^uv:vH7h>.""WT'NqFvvvvvʷnnnto0VW p%555??????%%LNi:uȆCDQK<f1k,qpm1 3`=h 0 ^r%**jӦMzA]ee嫯gR7o޼r ۷ovyy-_ swwy^a/??ѣPTy0VWnu^}ٲew&f8p}"ƲN?HۓC1 XzzI!;ƲV/=<<6l3_vڵkN͜9:))Ν;T~[9sΝ; a"7n$%%QSScooO;h.cz/\PRRr]'Nlذ׽{MڵG cu+[]nذa2:FFFھ};桮N7 E_;i;wȂ^Z_OTTTTT@Nu]a"W؁'++… p#F_O||<桮N7 E_itYpv>؁gѣG顮>rHQ(z233ϟ?kOOOذ+7j(\X:Bhǎ0VWν;xzzѥKa,xzFEv2uuaT{$vh4 p&5a;wX]atP;xyy!0@ܶmhgv9rjϼ*C]}رP;\vٳ pѸRXvٵk7n:[u7itY<ԝ>}:88!!!<Ȏ#BevIOO?}4;vصkWy@q&L_ݻa0uuQ(mtؑ4 a,`B/_&wܙ̵bp7o0dE6_իWSSSqݩS'ذ+sJcu؁gϞ=0VW:(z:uD9 Z5#Ѝ7:tk yG@0>6lP`^OZZZrr2}}}aîܔ)SpŲ,tucuB{ա+@]Wk1!0uF7 Bݻvrrͥqׯ0dE6_ϕ+W=?!!!tiWy{ivvvtP|r80 /+8v}'KFhh(0@0VG2+++\<_\\L7~gbԩPGt]`BUUU633:+K/J|=~~~atޝn t4hIDAT#(sW]]M7bccq0 Y 0 sQŋO:kذ+8u:3JM6 Wj>222vu1yܹ3i={WVVZ՚իWa"W!8/u…?aWgmm]RR벲2 y肱p3fDeRa dc q]TTdccC7VZ+aȊ\2xϟ8q]t X>gg;w://n`<ܬYpcuRV= 4^z dcܲqB7~\1 CV*GΝ;wq\wҥwt^}U\, ]J࡮N7 ]p^K.aӇn שSW:==ˋn}wb^"؁h4Gu`` l={6XUsWx?C:Vu]Ϋ @Fxx80@0V/00Pܹst /nŊbUvΞ={\w6 ͝;W,˪ԡCcu PW.8/HF߾}}aaad$xtP믿˲dEB/u̙C:((_~tbbbp]԰a0bbb+@]WWyu8/յkW0O7 `/կ_?ؒ+L]WgUvN>:886 書bYV]vॎ9BFLL tuxӍBK{}嗴3؁۷/\90u]a"W!؁JMM=x uA5[oeY5wu؁:z(i111{qI [`0^e>s եá+L]WgUvRRRpI7h>w˲j/u10bbb+C |tُ?eN銋Z((Ut~)((vR};vX||U߯7a„011),,[$Zga*l롡uhhrbYVT~'?hLXȀ[`-.1HO84444 xރ2zڳgψ#XoӦ#O>~04WZZjggW]] j`lqݽ{α(OAN;50VWQ^yرGW@!Խ{w0zM7 C=έE0K._1 YV+//seZKJJڷo{1dyT;VEN3f̠&+pB^105B=z W^tYX!z!}ڵk ;fŸbYզtssqHt:ujϞ= :t(<@Nj8f2e 8X]y8UWyu,22rV@!ԳgO0zI5 ;|.]p}ܹyhѢS7nW!Q۶m{m{{<矻wuXXrRX])S ƌC;}0VWnɒ%/B(22_:B(,,4 +dddxyy:u{ 3>_1 YV```JJ qPv]vW^O"u;x:dQ ƍ'MD;}˗/7ocbb+y@qYhFAgϞG^~2--M,;|nݺ=FիW/0`8 JT,˪̶;vMOn>9gN֬Y3c ^^^׮]G }]2??'Nرc3rHa(WOMM566ՕA{Mh9D8Dܲe_Xjڴi9[̙iiiI%?~7ٯ3gNDݻ7iT2^׈۶mٶmD$%%In>ډr=݅a"קԓ'OL6 .$w%$''Jrq$>}XX&cJÑB"ƍS$VZseddVVVVVVQ`8)ӧi0VWMYjjjPPaFAv '^˲dMZb&/Zhtn H7nuddĉ-<{$SSSe&"##e4UUU$0p_Tcuq=8==㱱O'&&N2eΝhp @+uMo՟N+ύ3:PW_ a"Wa,Xpŵkw֮]G!0YMO9sf7n$=z֭z y饗pŲ,Y.6~xF_qc4tСCy*1@PHvvv6mGLLLdI 6_]y8rmΝ;o̫bcc.\H;5& stYw'O @NNN7nxT{q0 YSNH-$h۶дuzСSN4G,nmmMꢢ"I|2i|*..& HkNjȐ!a d 3K.nݺ84NT!+ru'uϞ=)&n߾}d47l0r@^eY5w,-*Fʕ+a8::BWWL6KKJJˠh:t(ipK2p`,///55u۶m6m[YYZJ:՜ @Wβ,gSg[ i<޽{ɴ<Ç嗩2Sdɟ,]]꺺fff666NNNAAAz8qPbذaa2p|p [N:I/ P!+rr{!1bڴiTp7oƕʏ9W|۲e >4; d?v`p:v=\a:˲d ݻLFzWoS=|dp?==4 _꺺Ϋ1 tYp*<7p Ð-n׮]: 233IÀ[ܔXQQQt,@܈#hg.SnbYlǓZ;W_4w^\XYYYa#Y@3L{ :v`p:t@nwPYݳg"t:]qqQ9cYaZ1o:::^ȅۿ?hǎ{͛7o"ܹC%zjMM Btԩb\~4Ν;ӊdTiW/((!0a*1 DBB.ZmVVcǎ5+C}7J|={ҍD׻tRRR_.Z裏> (3hРÐ->\~8O>P?Q>7---5 {Nm۶b \GGGϞ=J "X)9p*{m(~$ILL $@.R4p%K,\P9㸂: rrrH޽;0@u]Ϋbĉ&7؂V ClS8M ͛zoQn*¢EDQaŢ(n޼Y%KqFA>ٳg#STTϮYZZjZ({mt:̙3t59BHi4KKKR<h4mڴ!B555ٙ@uuF),,${BUUU!VhZ-IG*++5 ^_ TQQhIG+‚VVVAi4[[[R;88 JKK5]\\B%%%? Jr\{xx 5;uwF%uΝBEEEK. Dj4|YEh4<},o߾fffaÆ\rĈn@8ӧϟݓ蘗G;+v 6,>>~ԨQCܿM6?\_|.SaAY ޽0(.\`Ys:ŋOZ^tIZs[SSseimll#\"MLL:uTUU&MMM;vj^ۻ2==]Z{yyUTT\vMZ[XXxzzJ imiiQVV)ݥuiiiVVyC%%%ׯ_۷7nܐ666nnnݻynӦMvuQQQvvm۶maa[kAAANNwJU.B#xIENDB`./pyke-1.1.1/doc/html/images/PyCon2008/client3h.png0000644000175000017500000015254211346504626020370 0ustar lambylambyPNG  IHDR^fjsBITO pHYs IDATxwx\Ź잭7[n `cZBHM @~ynrS.4@ccp{U/mW3:^K=[);3y{f1F|( Fk[nFXgX>ӣG?b(JJPRR2}E Q04lp h4kq%^|ɓ' C@X,-[ВZ'nwyy9mJRRֻk4*xMK Q@~g!۞yiDBJf… nlP=}SɿkٶmhJq/2mDBBBMM͈>vz)Pin FMlb0Yw]ZOQ43ՍAK :uhq:?>ccCi 'M+ZxYY?: 7mnnǏ(/]v4-MvFq<ȤIRi|| {]v)sVm۶9sű,RƏO;v춟;@~O}^QVuv.HFS7sݙM h0~BŇiÜN/~ T:Xׯpj維7 i@~((h,u9A:::h!^Ϝ9C/((Mi0G^hѢgDW_}u lVm3gδZCs[|@~((+Wh!^$P(?u]^o4h8N4&nݺgϞ̛7_[o5̆=$:u믿^WWgX߿tR>P%>V Nyu9A.$Tǿ'x.{5{エ~6H%?='--ٳ_hS)s|'9 By-oK Z@~)?>nn:ߖ@uA:3f\MLB[0O?6B%;w'DGGWUU Q#݄Gyd8"vj;wbD` .8Ug4_+/,,lnne4.!!6~-MP̙3 /5zBmfy2_Г7oݷ--h(qqqdL.xTG&}{[j3 5MV 3ONNl{`&J~ UxԘxb?~B߶@`P?N7 mڅTXuoۙ_A7]#G^ ;Fs#j`L0abbbh`8P/G+J.(:4r;i&kqp^+++i?PTCfNCA)۷?Rtǎ! -..7qر۷7O?Jbֹs|2g8xzT*d2T*J%D"aYeYX,D"0/a#:P~U,ܹse T]>,D"իW.ʛKrE/Jvfvp8t:.v :L&7. `k֬nH$z7֬YVP]H0[vo(oƲ2:u뺅clW._o üꫛ6m l|.Tؼy3OoFsJn-tww{MЀKvww=y6I @unhh/FAEE.KN㚚9 e7׿OKB$ݣ@t uvѣl s@~@wwҥKkkk鞟89!3fx66b1=azѼ󽖟`1c|Q@u>9߷l߾?o<K/$%%Սn'Jnĉ {Qۼԋ3dFҀFwZUTB7nFo/zY|9-wx u))).]g[[[WX17z`{=ٶ!JO_v i@~? 9uK^lne@u?۸q`K^ SҦDDD?zs /%h/T1޸q#='--?_ӦMM#:Æ _|q8e1ohӧ3g? UJII<6헿%pz,X9T*<o\tnL>fhTb2M?s…իW{zzN^ohh8pu] b|kbbb544x}>@~Cs@JqqwBP\udZ_zj{{߽{O>ƯA1Zh>Lp:۶m㯞7L&N' &cl2Qd|ٜEcF#H˗/͡V0?}v}'""" [Y9{kaʕ6femQ@~ޝ?" +guPwHugϞַEߺ< ,\رcimabO=zNժP(T*Ubbbii3,Y17`9|0G=x#GZmggdJ "...999;;`ҤISNMMMI>[VX{իŽqqqfy`0řLk׮ FZ͏FGl2}:ĉ3gЬY0_~%BhΜ9cǎ!N2!4w\ϟ1>tBh…> !h"BK,߿!l2'|Z|dbYvΝ, n[rwA1:1޲e qo޼#G`~=1޴iq_|xƍ}{w㸯 caN81^~=q'O[ӧOc׬Yqܙ3g0ƫW8ܹsロc9x"xʕǑ0X+W`-[qիW1K.8ŋsw E8/X㸪*9ϝ;Zwq\}}=xΜ9Ǒ7 ̞=F̙39j3fpԄ1>}:q---Srڊ1.++8c\RRq\GGxʔ)ǑWL48nĉ9tbz=x qQQqdBf38??8ł18fG?_"ի[lq\QQ`0pG{{{9+..t:~OOq8qܔ)S0Ǖb8#VȢǑ@SSq"h8#FE;c\WWqpGPX]]q܂ 0ƕǑ BEEq/_~8rAvqp/_1|2q+W_x㸻 c|1Ν8nժUgrzj3g8[v-ԩSǭ[c|I6l؀1>~8qs8nƍ/c|1~#G8{0Ɵ9q[l:t|cgqCa8q?1O9{G1Ɵ| q=nOnjd2YII Y!'75d?q!\^RRBnp,(rC$.,,qQQBHTvDDDII IIlTTm`wFciiX,~ꩧ.ߋV}Q7,++{w;>A鲲z !D9 B/۷!aÆ &PYr9rc׿;$2&''WCnl\vdv_yrôk׮]vhZEuvvvVkFiZ*bÁ1nmm:niijNs1t:v㦦ae׮]ロegyGww>JMM޻wѣGkkk#vZ-].Vmnn:&ή:v8ZI&Z똸6m1qVױb!k{wuut'OzB{n{{{ӟO" "/^xܹ3fL8Q{M6yD7r9q\dC >'x.n|GK,! Yz50eee ,/}™O~Bx Qˁ.]TVV6c Euu/˳g޸qc…j.!!t„ " vȑ#W_eO 9dh]]]]]X,:w\||t:prssU*X,H$_~ "vĉ$bBU*D"ΝcNJ $''SaZqqq|-c: = zzzH2K!V,f SյB P@ZkT( Y !0 M0GדB>%l D"ݻ///㏅&+"^e˖ Q rpGCiZ{dAB *={{1ӧO'5* С-$ k(6 CӁ NG{XY6>cLְF1 CFɓ'1(aN{%Axk|衇7xFNT}5  E}} B={ w}7;c ^#@ /NGk(zTu5Fk^'EĐy-"!KbL?~9sfZ:ƍ$VT%Axk|o._\Z#55kZW!Be?0WZΚ5lPbcc0mёUk$Fb۩0z{{k`ODGGu^ Ƹ n5~da(aNOOIVK׸e5x!99s1== Bp tٽ{C=DUVg&"(` JE1n8j1 th F-HU^#@TF `F#FEEI$iV}[8ƘaJnFKR/̙VEOOϵkHRƏ׸y{Zb!GRRPטJbB?޺u+W^qd5JE!\h4ЖZ:X8* ^#0L&FDDkB-c+Qd"D"a=Jyj\tww_zj8%Z IDAT'5سgyBBBNטLbR壏>zIzjvܹdFV0KM&zAt:0fsyv,-~6*8IkNRf5,ABoo/.|zd&::0$H$#G04Qf݋5ۺq6\:2b#B;! 37;|\(py|ww˗I7a!jHBD;|k<:I4՘\MSrSy8q^W\)D-@O}5&%%XPp t裏lBիW#ZCՍU^jk H{V8zi oZ˜8q@f4 8N* xqoEなȋ{kr`YQiX,zmR(5@(ߋ0LddBiXH̲,saz Ww !5;.)Rf x(!B_s.(\r^~CxyRz,Y5/'e,?6O7mڴ{n5ZMTu:ϽƄ&Bhooa@k׮͛7x͚5 Ȇ5o~`w8zeq^ZQaL4IZ, } >h4 5RչCa°Z?RE=<B1dqE cmGJ*ro tKY""""B| q:tyX:t[p?Y]03tL.^HxG\.D@#tw!eO-" Ij5΁5cJY&ݤ75z'F.MHRI'Ox}ђ{=Z#^{qqq$V*>,]vuxڵ}]Fm9c"b",0dw8ݘAӋM '5XH)%F$QRT,B,v6&.. cbZL=@ (P(. fk8?=Q9+lOԿ|`GQߟ-jmfQ&, . 2 Pu<Z\.Ye}Q* T-(Wŷttps:;;ϟ?O!jH$B'„Z?b.]D5xs-yBQQQt@d2kzR||w׭[5°;@(eD94f Pl6^#=0jB Mp8A@2}t?cG53kW@Y C$u Ds:::Ν;G℄R!jɂgD"0˗5ӧOkܰaG}D;v"""耪bMbR|X8ܹsƍ$^~}_^#@IHH(eD9Nz8AFZƘtRɯNg{N;:&Pu\|nbz-acBuH۷l0 C 8s ʄc" i0 rJP5_5}BJ&vV^cTT  ٹsK׳+V *@IHH˜:UmEG05!N CI@\.0n7H$ {,!܅.E0aؽ{ctps^@# T"tw!anYCZJ53gk\nݮ]Hr9vϽƈ+Jeǎs7l5p@ILL˜6m@n:^#@ C=@ `! F - 0 |aw&04Qm?)emTp~)'%% 8U / <5vSeim.p- :Μ9C!׸v?ĻvZj!L&$v:> Jda@cǎדxÆ }]cIҴ{9XVTy K#  &nK&!SN0v)t@("r9<gݺux 0L8<=J2Nj:kB,\zeڵ|au DaqΤ89oOJ;y)m;tǏ'qrrYr}e!qNf+ ?Q$H$b׭[G7ƪhs.Mg%x0==v[N<'8;w C8q5Q*5~kW1|~"JYnUocJYنr:gg&DRRFYY@,KsaP,_芀aep{MOO 4-=a;¼k,++0o.t@("h&'k=vZ0Fah00Y $&]MKORKCBfw'_~%SRRfϞ-D-N5BW(Trkܰa^#Tdtut (ϝ;G!׸aDFF?n{v}!48|s k<Ա]uh0(Hdw*cR~wd2A@UGDT~OENg9x"gœ8yrJ_Op[qkk&O7,7krA9R? 1 .]Q֬Y-]#C33Z;9϶5Q,iii{sAR)$}b{-n?~p*%8#Tysx@k̮]npk<H/p:JhLFDp11SIdrr2pR)HjEPH UE$QaHR llŒ%igMo4~]B`]%Bu@ii1^E. F+Y/a(Z F 3tLMOSHVp-sJRF----ǎ#qjjw!D-6d2Hbk*W@ah9.f eٍ7 H4F 81N썑K cotnX=> nΟ?OQ^^.P׸qF5FDDF5+X21=q3K]٦eǔ*Rj4'|b1ՃL&cABp]gӒ}|bJ)a'B`s\o'| FNp\"ПիW52 CPNIjD< sHTeC {|BwX\.@zj0nZHI>)gAnTr4779rĩB_&(@BEEy338NBbe￟lD5… Tu6mO\\^ctt4k(^#=T%%%բT*E RF_^\I^#@aY/ l dʔ)~o-t@()\r\5ZeժU|au Ds>Lⴴ QlADD$a(a᠋J$vdC$A8|~pO,4\p \O||^clWP>[FzRaL2EZ"""hN H9ˋ T> ` U²,_oAȔ)S I"]QVZF_0 M#äСC$NOO_`L&#@_৯y@p8D"a|A!k/^(//k|k(hTo xz[l!54* 5FFFҋ@5DBЇ|vFJ%Z:՟ 8 :_\h:'=a o :9MMM}.\(D-Fn8** GCoꑽ`{zna(an7$J֭[ɆH$ \x \q5IJJk#1xeݷx ^#@IKKn^cTTHIRjBZM_"Tu5TJ^c^#@$b& @ja:\.PTBG.X bBJ>tAQB7g+a((d݄7U? Q"I"_Ί7Nظo>gff._\ZV+j5$Ʃ-cBb~ 9V3^#夦+u[;%yi)bZ)ZZ5>d#ks}L3'=&v f,HȠn^c\\\ZZ!e(}0Tu5\NV5it]M#(?9'+#\L䜬$N0/͈ahpZ EEΟ8$~'͛4Y6u6VGū>`hD,OK+VQKWWb!q||<$޲pqSMAab"B 1Hx~mۛ6mz{++-P045~Y1.5FxzO<ـ5A*kzVhj Q٦]}ATk8tAP P(0k 8Cd|*:t{+4X"f&$?=F./0  zUhzFc\neCIMṳ̈̀EyiQ~VCCÞ={HrJ!j{8FCcz#+0Z B ^ 3k`.tд-bY s" m^#<|;dƑ 49%nb|sJMJ]C5_5NY*g}2330&L P- $ddd:TGPPaǃHF>nOk;Wj{-n rSJZ _M"b\ԴUMz2;{n4mY:'3YnFDCCݻIEW}KGG|sQD=33ӷcI 0l6wttXThF>5M~`KHdEAA5~%5 X4/'eVfұC5fG -C5Kӧq `3330HT*9233HUETRa$&&؇F>VHmVgF/+H/M?D DrV 5[ml}B䔸 jVVw-D-mmmkLJJ [l1'1?N∈"kc94}hB8I$#x@?^}^D$kg-I=PtBr e7_;]+J `# YYYTՒCbHJvv^#U PJ%FRRx}5WjOk;~luŒ8x_#@``?a(BR8/J.~Ym1+ ,UT@W__O:lmm{B47c\WWGba|e!djkk#1xϬOѲBs>.1veaFz ^#<d@xIj[ޮѮ^QȥDύʢ?~@RF+Tu0DDDPa$''GF>: ͍~ASR'F3/"ݠ(2b#1۰oU =N)uuuu|qBBRRR q555=//ϷckjjH 0h4_ IDAT822>!kSթ[1x3t.qi~zB*P5Fxz?яxF~|ch}.7vH\-}M!ܼ|CPHUJDDFJJ x}[H]nBb0{Es2EHـ ` $b& @P 4)Y=1I}sh.f'/IUH|Aܹ999k֬I477FFFF Q rTUU[|haH,|e!hlnn&qTTx}mǍnB.I"cG̀5?&5"P@VgCsoH]ˢܴ9YRoKΦn^cZZZaa!!e(zTu5H*T>~ZkyΏILdEw ?D D X_ַ~VdhlW4-O(@mm;HvZ_kט#@qAAϽJ3 SPP`080!59]GZ׶X#QjlY75-^4K x@?^O>I6k idxI~Yɇkֵڜ}w9f;?nZV^?眜*q "CP]CP(QQQTiii5Y#Yuӗ vPJry!71Y A4QB)j|ܔUM۝`癊D`%~$]njeӣrJ ظqe/**x 3H 1 Z5˜,O4~Y\A|LO5>Sd1IBz卵Ey+g:ݼkyq+ 3rԷdl999TyI2q :5(*tY#vJ텖E + XxoI0 M1Ib%}+FCWZ^ȥEGl߾ݰah47I#@ve?~Ͻk׮X$ 7(51F>ZioEv~I)qmu޵35k|xaEzL71Tu~Ŗ.Eﱍ$#|&L 1XPFMqx}Uȧw_Ecmca',+HW)di@@`ߐaAaE^\fOѡWi~7'54m3SȥkȈm@rkz*)..a@Hbo!lF Fj[ MsD,87-R&Uu5^#ydhK.X$M4ɇ^#qll,x}@HMv硚/[.ǡHŊBnRJW5?^s^#@ˣ(,,){9U7}tumFg+Ӣ#VrTj+''Đ2)S5RՁPbbb0kѠTiOh]nOEeF, b0$04Qœwywm?#n0/Jimwը3uB.7obmm-srr q.\SLxED"P"t:F00YWj5w(YYD5^#<_mSa %>^jU+ R?6[5RՁPbcc0rrrkF!hқVjxg^^ȥD)0`mF7 UUU.`4w i1ߘVԠ3Xѩ1BZ/vg*D"%y8s[R{ϟ'H$%aNOOOmm-U*x}@H85ݽnhj{=]Tf2T45k|k(T^xN :#/[ׇH\+F )++kDSN%1x%66 #77> k'zVjH4;3iq^ZL=0 M0"qAA<*'Kѭwݟ9u-'J^!e}^59s޲O:^ٳgI,|X8TWWXVdFȼF]'?7#OKIƊeIQH #k_ 'p*|jϟ>}:BYtb<+wl'؜O[NDA˴io! nR0kƀr㓚UZq(R*Y:'3Y*my& @SYYopBRUUss)|J%13s _6}Vd9Fk ״,OȊ>}޲O6^3gHZsk)I UE}롚&sl-V!]>K 0?^?AHAAF^^p̘1*T,Z:;3h]fMgweei" ӧOkyERQadA\l]JQBB.#6Wm-Y|58qb>9]]]$)5Fsy_j[G T^QȥF+GS>7klP 08nܸN{2z|tZFo{CsCGcu_mKT"@hf͚%mH$3ga UV0 k1C]aoEcuW~Ô/+HW Oa(a΍7^u7n֭Bru57n#%[YU|S4.ai~JIC0W_[ٳgk<~8OaNWW׍7H ^-@H Eq0Uh4z~V$$?-Z&^kk6 ,/kFbÑKm۶ѵ|/@Ra?qqq2.1(1Jk ML;/[Ojd&/KJj0R̙#;0QaG^zd2YOOBхO*5F!+3+yanBmSO=E70鉏w[r2w#GI7xܸq=\vfǏ.qlSJmqHΊ;0w{{BK5!C"E4; ((JRH^/wrw5{w;yOtd9<./^W+\ktg"<Ν;611A 7|wQf<@Hgw3 k[{}-f;[v dn@Q>>\v]cTTCJcTT2Q<<< 1 崌ؙژ^/?QTS eG.b=ޜ`T6C aUk$6kDH ///kCF@{gץҘ^_hGDؚ <}qD~B\E(ȍ)sΗ_~IlOOy%%%vQr؋6Y3-#WS_GV̅ 122RZ - aR"s*++SRRmjjZ#E-7Shx5EKlvɹ6YSmͱnV&XFD?Cr[[nHQ$K^|||mbb’BKČst*[3PM|̢qnFkaHVP:t(QkDH ooo)Fq*r^vk}n^憽>令?MáWQk_~E(ʍ)s޽{sϱ%99Y^kTƱ'3 wv~7anWK05x6MG[Ν;QQQ ϟ?Ol٪ϩ@/7Vhx5Eusۉ]8u6|.lc0wgwֈ(Zm۸ HE_nHQ'$#K^|}}-#`$xeJU]w}wS؂iax~c3tPV56ب5")$jw%7ЈEIC񴂤j!MikZ%b慸)9BA#K/tע[r ,4RumyqlxIJJ 7(k:Vpn>NF wV49s 6LZٳg- <8SQQDl333{,4R5>2y5GL]kX 4Eb[k裏5FKzzzBbϯ æ,ya{C^' n+5m#.xΪ8bbֈ5r# ȣٵ!&^~23 W\ԁ /ihɐ!CAWWWxx8۷Cloo %11F5m/>J](vy+yj؎JĈ cbb- aR"soݺElsssȍ<)ήNhj?#0Os^{=[kjF EyyyAb1 Lj2 Ãrksjj]} m\Jl:̈#XGIlMMM j$jpKrkNQS+jS+ؘCQMQf3UCO:#GTxibVExNyyybb"Qk|]hɯm"(p1ˬNVplٲM'\5vؓ'O'mmm'ᒦ?ՄBJJָh"6Ǘ;00Pɍc{gׇ+lll,'ސiAAA00䖱]HQ?.O M7*#Θ1c@kTsI ;ب5"9$F@@Jh/;}}D{{*Wtt4\н! $v;H(HQE 3ʏ-D5a.]J,QP2pEQ;vm](j„ O=O?D.8))))v"ŋWVVF`e6u0u(+s Cuر O8AlPSZZ s!-,,x NJcwwwGGM_-cǎ%Q(rAbDGGT}]F+=qa]1Ѳ7б qBiCsUs۝,X(p0u75PrH'NdOkloo?~88,yy :bT_֥.klhlhuRd9NpKWwGuuwm1!t" F@STuskSgi;UqܸqF!1mbb;W0tK>U|=*q˺}0:T[̣=2Q@xNrrΝ;d6ܼyS^kTrxx0JCǎWq)\k<~8B1cxsD})--Y/ L XB~~>$Ftt4Kq*jL4UֈFx"aggǒTeD8gjPQkD H`=N 믳QG>b5e<Ģi>(*DHNN1zJ#M({ ϟ?駟K,a֭[}_|E6\rOM:[eYrKhW,u!LpJf6CxNQQիWmcc|B 4OUUՊ+~ÇrBbDGGT7oތZ#]v(%7w FҚ:2}tVFTJlڵkR4W`[ y:::볲._yLLO? e???H  8zt+++pG5!9@\Ƃ0ƨrL2uBf[FĈX9Iuu1MLL***=?{k֬O>0իW <8pK唬]1ߏk,tSTLfџs!>F㭓7vPm1 `̙C,}@h^z;w8@>9pm۶; ॗ^b˗8>FX5})-P~iT)~Wx:uµFX- |IQ_\Rkcc@=,%K?ō7 s_H`O?ͪ8m4bֈ׮]ĐJ5zyy]VVa$?${ZA`a?[FBG 솶#QR)Z#<QkDkkkH HE`B`kkk%$ƫʶ uA~I(g"u.=ihx۷vpp. 11q֭xrҥ"b<ύciC3FZ|oe~X8m4k[~*s /_Llo!ϥ>{F Ab԰TlقZ#R^>3gqԩ ONlD!\~C*Fj۶m)Hbxɒ%F=i$5K^Tn:][PN.z!qδiCU(gf͚m@ \_W۷owss06UZ rJ]&]ݭΪܚrSRv406Uc"d&=ihiӦ &Z;00W^aŋ =dȐ~8V〥$̐xԂ~ ӧ+\kSXXxEb2&ei|h={ ;I(..Ĩc4~Zc,MQ!6 p|(j֬Y5N6MZO?MlD!\~C*2] 8x/~z)#U!ChiiE馨Š⪡Nmӧqƌu`cc=T+**'Nx!#8CM.+oJ./;~mn8/]PEy3gi^u::::::'O^vԩS(:r[oa䒄>AAA,͗phj5#4hZtFZIC3EQ)e5 ƻq H^kyi(jDt-sqƌ aD!BbHҞsގBbB0tP]ݻk$x^)j[$wD9s&Z#<QkD[[[H0O!55lss`ii,G?QiiixvF jz}[ܖYUWX\\. h NQ(gHm*=i~oڴAAAVbٳgA6ljp`}L\O:;۪K tIzYf)\kap"rsޞկϥ1//OI&q 甔@b477ToߎZ#MFd<+)FiÞ5sq̙ vD!ܸqC*F>O644ˀ'|ې%00Ғ%/Æ O|ؽeߍ6]uZO¸oL$C- -TfQZEEQ5-m2&z+;z5k{Ξ=ظ*Ab1ӦM6 hjj?|O?J>ٻw ,--+V`ۅQTߔTZ_\%#|9X7<'XX@\zBqEQJGi/ٻH$bu$'FOii_vill믿Z;88xlx9s挼/G?(m137V_'4Ew#SI[gϞpVv gyF7Gԗgޞ_$Xcy_GD(L׹on?o$y3(\k3g% Fp H T|[רoff0`iӦp8|pb.2H#Yk;XS뛉OV͞=uu`ggCmJB |r]pB_g4DLV1 u=E"&5%$$D>1zJ#M( <'..nƍ^f ^bbb=b5 FaitdW2qΜ9 ae70 sΜ9Cl_%<yJKK!1ؚ'_Ȩ;x+؃, I8|gϞpqܹĖH$X͛7!1RiOiֈ< H #FG;2ƦU^fHa̙3U5"=$FHH3enBT %$ /JZEi]E9=oR_wsHIDM OH44 Ϲy{G쐐kײyyy9r$6HRi /, _8w\k'607srsscbb:t\ֈeee(;vkXE'dvuwSe|H)NXp!dݜ9s5>sĖH$XB\\$T*)?kґ`H 9fXye$g3y>SEΝ#6j`ooL<ۀ\ l2]'eKl{CZ)ٻH$bu$'FOiGQ!͛7ׯ_OuֱԩS55 LJp2h5NFz <8*W_8oZ#<QkDH fĉ fffJH%KQGvE"&5%((H>1zJ#M( <ƍCCC|M68qƱcb ]… 5ݻ `￉F(//`> F~,YZ5>ĖH$XB||<$T*eDԎH 333;M:88Q;,Xָh"bֈAAĀ  QA̔ ܹD"VM"jJ``|bF@pBTط~aaao^?.5bK-ZpVv3  <';;ѣFPBb޽[^k҈-[Z 5.^K#BHHHĐJ_E.p]#Bb57ʊX`ѢEjD!1s*!1@FyvE"&5%00P>1zJ#M((a(LMMP($/ˊJztbccA_ Q;v,''ǏWƱMwq ae70 s?Nlgggرc+hĈgϞmkk144$&988<䓜PQQ^yϹ'z7jZ*Z+Z+Z*d{׶46w `R&ښښ&ښ&MPc/_ZE5‘/K#BHHHĐJ=C3gL2HTbpChh(${ZmllȒSpN^WgSI;]l%giiii˨((OsCSexbVFxD"Q_ 1iU8~#EJJ!qťK"##a4СC'O6~ɥ_V? UDIQ Rnjsעih̍7"""=`֭C a]]]111k׮GGdDx_~ѣwӄ 86~t1!?`m$qpgJK`ҥ ?3bD" @ tT>W޼y3W9ںH@p̙HN9z(^'Ox 6<YW}E!jhPZ<0OO3be2bV---bK$&Q_~S[*2'N6 O<D"hkkdÆ \E 1LMMY2a[[[b+Ykd~g FȟHST_cǾFJE-YUqٲeu *Z#۸q#>>>\$mii9J`_lZrY.K?a,Ѩjnsm1**@W 5j͛P뎎xWD\SRWW>x^CCCi_ubGDDlx믿@kVrHQTC[s MWӊk=ܚ%>e AV&ʏgΝ0mٲe ae70 s`wLUgy'Os{(ofZES:4M4v;PP u ak,ֈ(Z#5{"@XX${Zctt4LBvrrbñғ"0&9f4(+{fAw6lbٲej˗/'6j BZ#`gk/544Os_ΟqͰMgĶ:NFQNVFQ@xεk׳?"ĉj v:v.EuwS]]Vz ϝ[aFE{ud UIe/211u6Sw#-CŌBٱch/bdpT20 s233aqWWWԩSkD*H MMMݻFGGGnK#@ӔX(QĮXb;¨tVѥ2YwFUdo +5|zE!^|EbK$,֭[R4ֈaaa&&lQ8q"JUh&y9 u:QtG)ojg3]ۜY {" $?j=ֈp?SO{?Vv"0ѝƻ*jAx3bb4 s]vZbGDD{lx9rHff&'M?b1y*]]wqvJ V8| t+VPG%D""jJff&Rsssc$ Z3GT[sVH酷JǴʺ$/snwTV\ Z#̧W2_&D"҈nݺ!J{J#,yy':;;䥿b+y6حhz{]Vsx.'GVXK/5" $jZ#5E^mʺ^4de2Ti/a F,@pinATWY ظq#^~wX8i$l5:GxeUM+ȮϻEU6cl ؾ};k|5~'fFqxYrZ#TWWCb5㫯KXiG kk7*"lGZk(kZ+{sL+["`iDIIIR9s u8ydbc]Tn)e5 w꾔Wz|gk>j꥗^bOkd\]]!1PkF+@)VIձƖ^_i0!Ü$"UAT fСĢi\zu0`φÇú'|GBSQ\Q剌Jd2.stdw4飰m6_~ek۷o'H$R-dddƜ;w\ֈՐ,ysK#h:4$š6Uy,|Npgb<V^ Z#̧W2W_%D"҈ 1RiOiD ""='tss#6i:,j~Ɍv]vN.xE~嗡kdCk' j Zc5"\]JOg7w@S<&L(Iya"##E4vʕ+V"7mĆCedd{ʔ)8*P0j3-2YEclCMZߺu+P]rµm۶[$)|QS>Llwww… FgAkҨL4(WgK唴zmnn!!tf7@K5kրL&>D!$''CbHҞ҈ @DD$K^LNl%bƹF:Z*[.wZryc7qtƹX)>\uqUpss@PkDBZc_4u,W.xw;OsZ2<fϟ6DEr L4h͛o8uTlEWC4q ;^ \`;Մv`˖-0CWRaW IDAT_UָuVb𜴴{K. А%/K*`%~iՉ›E%4zXo[])Znh+WTe2lg!H4"H T\x\0`H ƩSzzzՕ%/ȿX93e􂄒*y%ɞfmmYz^cUk' kD777H F+TAkci)eս4EXsTӒ0 "M( <ʕ+05hР> /kzz:R)6Pciy7E%V'UXu1R>UV)\kܲe E"l5%--~#serZ#@b77߀퍥Q3Y]p,-?Ӓ+KMƸK4_FO(d2ڵk-H4"H TS0`${pR7.NFKxgTM+ȭiϻc +*FӒWZD!1|||PkF+TYk;G z}Fipi ˗aRA>C6/5N6 G xZAIC3|.꺐Sz-|pgkmci&W^psqzxUrZ#BboKASQ|I Ӓ;b/E:Zu=F|7Akm-L&[n% FpmH TS8p $[ۃM6LJpp#v4de`i|DFau=%Qx!t F(7\f  Z#Cbx5"\ZZP~2W:bA"!P!N,@pnAT˗/a=='?F짞z ~P@75Wv:o.b=!%oܸƵk*\kGE"l5%55_~!'surZ#BbKc-ؙ_)9]sZr]k91YclBmLxZo Z5kL&{7-H4"۷oCbHRڵkPEBb5N>ϏXb` s%g[NKii11TfX7@+^rݺujD<<< 1QkF+ 9%%uƹY9:0aaaĢi\tI^kܶm^<Z3qHDLPGSYErOK.klf8w[osC{=_uk7n$H$R-ܹs&K.PkD:H sss5`ih&{9 s:QxgȪiol8wyFO(d2[oElD!ܹsC*F 8cƌbQ55R_.VȟW۸ke0 5;Z#<QkDH ///&$$ۀD___ 1c ] * . +0o{"///)4Mc.]b b2䣏>bˏ?(5bCL5gr>^Triii^f=lQ&{Ė9w駟ܼy\ֈuuu,yAk [,t%s 뚎.vy͝?Kq8-Y&As H4";w@bHҞ҈ A 1YO"b #qs=hwSTbIURiuX7[tZ*j q+!1Oζ D7 U9-fQe|qUhWC-wi8xzz'FOi7n(*Dxˉ}v6Ğ9s&6w!{JMC NK_~2lqo.֯_OlH+*lD}Ǔ ښ%/188K#Bx`]cLZYw,Ӓ/^+(do1JG2w%D"҈RSS!1R)G.pUd` qߧ%dwy"7dQ'n߾?ۛIHH 5"@}}=$ K^~'CCC4"7x5B3 1_~*jjw6x{3A4LabK$,!55C*F ǒgy&44XfR}ρ`!fJOgɟ[JNLv(p;3w` H$ 1<<M _U5~y*hM M߳QH$!%%֭[Fhhhİc? ZcXXFi&a?%bƻF9Z,WsZrycˁSzEcl] $D"҈ 1RiOiD`Ȑ!,y5kVXX===YׯZ[ D"Hi*$8Ӓ[e' /s`!J{J#:::,yyg DliӦ?0 <CU@f!֦W Nfյ!+5\NHg"!vH www<< QxB会Ϯv6ndF'33C*F 1:u޼yQQQƺ[laUk'j}a$xyJy*~SqvlՕquu6 DVBb<l@ԑ+Vwsa^z%6l9;ud񴂤jIidUU>1zJ@ HKK(*D8{ =lذ={/!\?PZU-Ea+^Xt4-N=%'դ[u5R_?r֭=o_xUUUvrr266& TGN0GaɓAkDVUUABBW_}E젠9s&X ZL& [ikkݲ$HBnMѴʺ^ h:dD=coM:R̄HZeΝ4]VVfjjT撒bK$KKK69r$++O<񄓓^T޴i8aǎ|r駟[$-]T7 UGsz}.P[Ѯ6Zlǀ#']]]1''Odooo< a [[‡իJaԊڣu>g;QzbNC "MF yyy}}tRxgϞ?> _e߾}IIIĞ?^TYf}w}}[]]Yj+]m5D[lQN7E%VO/(oX(do1JG@ILLQFg}Vz?,J*Zrػw/Lwc;vLFk| TBIƖ^_i0HGaNVZ"޽qN/{:H4,CĐHؚk>Ç-#EQCcm3T 01؊@T͢UͭUdFܲaNxZ2pqqH=TnDaȍ(4{:QX+m13zOKE(F~ E9s#ˆ{޺u .gؗ[^z ĝ۷+\k|W-uu]+?Ypiz⑮ȓeG쀀)oB#ZKnHw/;.喞*jj@K<6TWK srr Hx[aÆAb5.X`ȑeɋӗ[۷5D;v[rP0j%-=୬әElMMQ8yZ#E!7Ј(ʍ(4YLV6Y<3qn E | _~ebD"8lAil8Y|)iVzqF4E5ulk sGBBj~B#Zr_nHUk싺SEWe] 8ws9wuTǁJS5>d#^KnHQ԰a 19p-Z4j(be$+Yh(?fUk 5S};[QxӒkOliiNʮnia3v H {%7Ј(^r# lj=^_\H/_Yw >Kh;;Mr ˍ|)K6|5>|nF E-_ĝ;v(\k|-?#\95K+UR%nhzp{b7/>" Yt`` O.>pvȏ|D%/ǎq̘1|.C8h >Ev Z'|؛wvvڵDKֳn-?ʪmgU\NY恇^= ِR4"Py.4R5|pH ~̘1cɋZ *2vO@~^64|)yArR_ 1PkQhDȍ(4"7qEK Fl!'[[[b4>//_cbb̙C#G1fe݉^x1ǨNWW788X8fٲe5ܹSZˉ-9"EQmνS9ۚZgb10/FcX[[[!1*+/q9~8hƍyi$r# EٳFφܽ{7%Fm1svu}NuCܱV}cbVusXw[@rrr 1R)ׁ8dСHQ!1455YxquĈeEܹU*Rb$ȬzJb%6 6i66464W5uQ[dy.G"buBU2=00 {ə8q" '/(4n:UUUU]ٹ*DRzAV&$eJ\F8[hښXh846>s#ȑ#xb>FDGGHQҥKAkܵkµ^xp(ʦ4p '5vSw/ sfɔ9Tn7o4}9!aOOA*y%.++37`KggggggnU_WKohW3p9: 7|6JB=TD_ϝ;EEFF?눔DHHH|||gg'MӋ/޼yˌ1CCK,?~={lb9lxٵkWBB.]UuE]\\2339G=III\ųxbwvޭpqҥ*y;wOښ}r 2QfYb'>>>+*TZ~5&UMSGEOS\׷bQ!\d[k,))QضmE@y+Wr'ttt̙3\e# IDAT~ HFָthbs2kpvAN͹;t  yTsNkD@[ 9ϟoʴDk$`J%ZIKKZK/DR#?bj-Ij$ *++q`tuuF3i$|>[E~?|v(ɋLBB>, frZ5i9r$ ///ޞ[C`?&> x<Z&HHtss3G^ W^y111 |7Zcpp0s}vҹ8o50* ƽ{Xj̙3Xk9s&I޽{ָsN\V㣹b1Iĝ;wp`rj ɓq`k…3gD6ɋ̮]X׈{@9r$ ///֖[#0n`"lX2 Fv`0vvvȢ(#EjjY믿ƍ #k{ѹֈH o!)6mBQ|9hr<555##ŋeeeMMMr\,K$QFEGGp)ǨT*]]],YfH@۷kwj5./Ij$ ܹaZbwVGGGGGGeeeAA)7oޫj۞&O==?cSSʋE=->/ %#"" O ={5Д~ <-E1}ӧO+111AAANNNNNNJS)))_BQQQ\\aVȎݿ?|ϟGG}F+FAiiʕ+׬Y#V\i8|M\C~ҹֈH Bo1"uި:uzKoud=2MOk쌎ƝX,-Zg<$|}}}}}g̘qƭ[~f^?OV,ٳgdjh!loookk{nQQQ~~~vvsлJr˖->p1c _ֈ Zo>dbL<ZTɕ꺎ʶ򖎊HKjֵ5dP?{k.5p`(J!3g΋...#Fx_ ;vn('OƁG3̋BQW``9s[lپ};*UWWGGG9r$::kd*MIk| "'bx@j#vwvteV6h u+-~+t '3ӓ!غuѣG-H~ݩˑ#G>uֱA0qƱ݄Q0lذ7ߜ3gNEE@.Ϙ1חk8)dǪi,Y_ pUP5;_h\9.v1mmm//n1(Zv]3RRRqqq` ?c38>1cƜ?~ȑ(;ϝ;733k8_ݫs7D@ 㢟GʼcJwÇqmܸ٣GfpLS7oތӧO>gҥsʘP80re 33ks1prrJLL GyVVVbb⟎^eXk駟t{sZb3OK3o7g U vKպW7UUU80T*^hjO{q茑==?Fb`ȑ,b\͟?Wظqƽ{*2%_LxWָo>kP(9B_GҦ?j`]k̭Kzܹs_5\!dF\0j(=1^4 ZZkR+Hxx8N<ƟY7WոX,&;maRcuu5 FðW<+JKK=|p=1^bbbp`5~'8#!6.**NطoZ#d_Sߡw]'2j(jl;::rcǎe #UUUz x2 x<Zbo!npv`FMcL|%HNN #:F+ׯCE]CCO8aΜ9꯿sW :bhPPq ';wnÆ 3f w֨P>jh480OrY5,bhFj,Zj/X{j pBuuzC \!ʑN[[3- gEᵂ,b` kkkHj40%Okk+Mo51c{7x3gdKw3Iħ];0zS#MӦ1uԄf///n1F^|Ed'&&ڵkE#FT3\J+}QP`Q |s"Wm)0tnn}Z#VLFk8pK}堠 n1F 80F\(//Ƕ'wpCbb"{ZFʈ֨MW\lAQ"  Syyy_9u(Ł駟HdggcߟCO8aj$)k=p`3|>[tOㅦi="݄G͡''ـ㱪e/e-BID*j#QC(J铐oooTaQQQ:smmRPPpt]s>4m4d>|V֬Y5~gd(++@6EQ65k?xεF|bP(9]=[N0Xbv;ssscƌahEK,A/[]FT>BxAZ#|7;8q '>̪ֈQVW=ZhjOM?񏯾 ?t /XbٲeÁgH #.\m6r…: Q͍{epEXX TʘW;;UV;L& ׯBhѢkF77-L=f=3fb &L2e ~|OcU4:)T 篫vso1mw;kڻ |O[ cm*%)Rv`FMi ?~Q@}}}xxxJJJ@@m}}ܹs\ѽIRR?)SiOݲzs!_sssgϞOᶷOHH̙3GZY- Wh}FU&b>3g<}ϟհrm_9MaGNNڵk`U|7a„jkknj~'֏\|յu֕+Wj#1C 80sV ͛7o߾KKÇ?<)￳53g+.KPgsw;7}h7Z|>aoXYY:u_LKKܻwoʕ7o1bD"qttT*/_NII9p'ED\\_ܹsm&CFaGGG[[[kkkqqq~~ٳguAs^{!V5&)'=GVήʻe-w;?co!k /18E~[o{k7kЂS5w:OެhYo`3T*eLfaC9qĪUV^$ֽ{ݻ~z}4D(CUksH5 +V0¿x2 x<Z!hGSc]uTdO:gxxbsȔows3Ł}1cHLLԹֈO `w3"Hb#>^dGvscP4%Qolnߞ_ uѿpnnnFݫ@Mq$Pٻ9MӬjƻ;_W56 dSځ&50IIIz˜?&/]hĴiӰxkxP(Û6WZ\ƋvVoh*;;{ժUȎMDk$hRϟ?~8ϟR+#))UGꦟ/DW=m >zRWWIVaG==nL&C6\}"~;;l5>% oZ0zC580KfRlDOޠ($$$XnN4Z~pq9M2/\]]"Z#qʕZcXXEGչֈO `wC9qJd8F 6yTٳgqj$Z1(M:,XR+&_JJ Z#:5>[?M{ǖ=b'2rq80,--BC$..Nl2n`,=zUDk$`ƎՕOg

V͛7q鲲2N|0/nݜƪֈNϹ_lhTiW ZTkjڻ*v*uJՍ^[=m-8ӥFLgg>(!!!dɒT?PSSe2$$7bbb+W1 J{IDATHHHBB{=v۷ovpp0W?TVFy)Sv‰3kBZ72 tMdd$]ꫯݻ<׭[fmٻ9M&L`1Ѭ˫zGD;0z 9!ŕ pvvz 0++a6mz8z?,dZx޼yxpww+566fee]x_z-'.M4 k'OԹlHvgis*d8##cٲeȎb 0)ڱc{{;x[nq咞#""8,+p`W#իgΜAvSSK<:55 NHH߿JE\E@zz:Z#:3!9s^5@<^'xx7}E@SS '''pK̝;777%pUdS5udȕ'/_~ܹ 6+􌓓D۶m{78'##PSF@67no4n؜S~Zx@Đڈky@FJpcccQQ% ֆ߳ѧ+h,YrرWN2|j8ĬZ* oxe z@./YdӦMjsq픞8q"ZsN___=2q߮} 'Ҧ$[:|B)üY1A4MYYYyy9׎ [[[Ho Ks5.YD[k;v,<999|BBB{95:uJZ#., Dk }ףF=4e$mںM-5gtRdGEE1xhP5Ti0`vLa`oXXXfZy&D"ф 8\Hddd5j:(٨!yUJ.5xZjF/;WGw? D"1 `nϡH0(233q4n8kx鈈YQrZC+.ؘuCGGUnH9,T\\lDBݽ;pMPTa3BC p#XrkwX4XbW)\  ܢqř^z!hC`ĉXkLKKӹֈ+iD>۩ fKzz3x_Aina`oXTTShC ++UG9,&<%80\\\Hk׮8tPn!gϞرcu5fgg#i k׮!ɉjii9`Q*.窸.@ p A?Zg}g0֬YZA0~x5\kG%D"|;IOO_x1'LY,lN0|> ׮]Gk^̜lVFuDk$`q`%%%Xk2d \C5<<\ZcNNy<^XXoN0^KJJDEP*@` A5~O?kk׮%Z#ƌ kP(̜3gܧ5\d_#q`wKII ^FOF&''UG Rhθ~:Έ&{\/\kǍ4=zhޜ`\~َTcc#~ɉ#B9?ȓ@   A?ZEy֭ydܸqXkҹlHÛ3g|gȞ8q"#qh$ @X_VZ!ΝcUkQGF QZZ3AH@~iԨQ:MthhoN0^ZZZnܸl̑WBP( fNOONZB,!f A?Z'|5FFF ZcNNεF\W$᝵3'--mѢEȎf^FB!` 8q,XYYVU*͛7Ij$ aC+++*ILii7xLNNέ[{/_&|OOOWWs)Jhh} F0s!4MϜ9SCFP(HLLDGBVVVzyy v P&**+888$$חkܣV322 %%%|>hV(VVV bq!J ŕ+W Z!2dHpp0%9srr  лBUV_魷E\rƍ|͛ojC---]]]\jΝ;1117naׯhUUR߿?{ ~!11q۶m;/_(..~3iҤEEEׯ_h4۷o|rPPZޱc}ʕ JcǎW1BT&$$hۅ#FP( EEEÇٹs]\\<|;w^v-00P.ڵK.)) ڵk{޽4 s7n6lѱgϞ7o6}Ϟ=n׶~۷o߽{]VV6t֟~|С---}슊!C477ݻrȐ!MMM};w <0Lkkk[[햖>@ xj777kBOMMM}lHDb766vtthnhhcbծԶ---JJJ<==m۶UUU?^(xNNN4M,]t֬Yf:v옅Eww޽{}lF겴|V[V744hrU*U[,?T*힞?a+>B_잞&m[T  ɓ|>e;wtqq aGG4wޥKP1 !DR@ vwwB!P. Bx=%:BF >{{{ass3BNȒH$(º:+ J!UUUwww;wŠ 7 Я_?۷>>>›7o20p@!:Bx5!C EEECB Æ ^r!D_!/^A !!!BjȑBtԨQBT6,, B3f ٳcB333ƍu3gƏ!DO8Bx)@tt4ĉI&ASRR111d@llҥK-nڴ п2_|B8bĈ~= !DcǎAIIIB4HIIN< !|'ON8pi!:=-- B^HOO(Bp1++ BΆ";w h^HHB p.]! \rBz*PTT!2dڵkJJJ h¹B7oB|-!ABUVVBCqUUP*jjj nnnZ!U__!DU $: Bhii BTS Bhmm @!dii .H$BwѣGC:a֭º:ؔ_;.WCC;'v\O>w\O._+"">0a|R5ydqA?=z0e!ZZC7i _|q7B_~eX,(~'KK˄X|%: .KGb]ϟ]TTDJÇ(ʕ+*jƌs̉k=lذ… :_C0|-`˖- ѳf͚y:Q?D"#uٹ _vWWy:;;r|]wqqQ*VTh4FAjuqq( _x身+Ƕ@ 6>nnnb_׭Q,uuur_(jʔ) \wAIQDQT*En4-JQG]GQl u8<O*N]GudNQ#|>gloyu@3^:jPOc_$=z; `xGJFIENDB`./pyke-1.1.1/doc/html/images/PyCon2008/client3g.png0000644000175000017500000014254511346504626020371 0ustar lambylambyPNG  IHDR^fjsBITO pHYs IDATxw[ŹHGuڮ붻p,` 6%!& <ܔKH..ccc"moJZuqfŻZy{fFwϜwg0cZ@7]#tp5M@7]#Vg}vsuvvZVRRKJJfΜhѢa aCM9@ :n'OtR]]]OOXR̜9sk׮dnf M[[/]TUUUSSnXNgttJ***1cNZ}7oor-j$+#.]zHM@~?ϊn#'%%PTnRfϞ}iT:"\./K\>/@(..޳g m-jkkgӦMCµ9hh9s洴-g|b/8v2؋޵k8pQT˗/_lYIIFh4Nŋ{} ׯUf--MFo,[x oڴi6lXhQFFF9}[o{nrΔ)S:ߖPX[0 SZZtɓ''&&Z,K555#߿cWM&iQJg1|b jzy ŷoTff֭[n|&#7FF- [>|8))uV8ՍԩS_xG V-[h111z~L6%99ܹs#lkk]w5|p쥅) 3oڻw-;^ΝJ8q?~ȑ[~FQBG~^r]w\ko~C79o~;ezX,޺u]ZZZv{kk~8o<~Qo#`鯿z]]j޻wҥKg>3ÔGE[/4~;ZH^^P[x'3g z/KzZ||NIg} <( 9--dx|1i*{)))v~ߥj_SO=5|'O4ɇo1Z[Z M\.%?WAT:CCR^z%[^*cܑ%|4^j0S/^Lcǎ[>VXXH[ aG?/fAAs=څTBXuo}[_AG/]#G^ 9B;s$UᅥbҤIßGc86#BU~_?ͨ_TvAաQM<OOO˗~hggwM6׿/]cLL C_YYI|%JvH I}T*ݶm"AXC:a㋋9r>otwz1 ,age`H79J2L*JRD"HXeYV,D"H0׋袢|nب, y˲۷oc!.U7D7=Ht%kߒ\p~p8Nr\.xdAl<0< СnҥtLJ~8MP]|.ǬzPQQ"hZ%---/!WxT.'qkdПgˇbK .]Z[[K'?y駃$AՅ 4\cwǎ1Ϛ5˷ְQ/ c\ ׇ/ ?L&ӬY?c#x >ϛ7r|Kuuuy'Ʈ3ydz®]F6*| Y5 l6ƍƅ ߋ@/_Nos9_C=Oyy9mDjjŋG+V ]%{/ↆmW^ pA~.kݺu-[p8F5P6n8yhr|,&i)QQQ?φ_b qA`7nHIOO/:}}}?g̘1JGu  /HKu !?OgϞ-č(!?yuv/~ :!45h4.XsTx7޸xbKKX,~7K &?b̘1… _}+W\.аo߾뮻 +okhh|#b& ;aj?yʕv[__s'x"==_5Yb$ ޱ|r{9y#dɟ~ mJ05BX1F#oso_ϥKFpTBa{l~\>QQQ#i z뭡g{w5^Ͽ|rAAh326) ?F2κFηG3g|ߤo]a.\xȑ0xlj~駇>w\}}}ggfS(**))t֬YK,x>Ç߿СCzbHRB]PP0eʔӧo۷o'\xl6;?4u7.]4+u^\.ח_~_vl6+ؤSZjyTkpǗp`]#tp5M@7]#܄g-%%c|~ޞx;::b}E~ٙď].W;K. www'&&:a➞D~bhfFWlZ\2Tۛ7Ll2,իWfZf+X,^dv?STh4xk cǎ,d˲cÁJc͆dcՊR(cłL&Ptt4Ƹ!16 xqww7BHRaB ㎎Pbb"Ƹ !1nmmEU<-- cBcN8cЀ!0ͫ999jP^^Ƹ!1@b]0aʕ+'b/_4iŋ)S`ϟ?:u*ܹs3gBeeeӧO#O1>y$Bh̙Ǐ#fϞMBs=z!4w\#GBwq̋;_6|B .EaۇZd x޽e˖a?S- ˲۷oexmݺcI|-[8;x x:tc|swaM8/17n8ѣ{w㸯 ca?1^~=q'N[SNa׬Yqӧ1ƫW8ٳロsa9pxʕǑ0X˗/c-[qܕ+W0K.8ڵkŋswuE8/X㸪*9yb1s8an8c<{lz=x֬Y555agΜq\KK xǵb8koopс16mqSL8:yd Ƹ8ш18q"q& c\TTqY㸾>q~~>qVcqnE(m ˕+Wl㸢"d8rW\\16 Ǒ?~WWqiӦa9+--qG.ǑE9#&E@qG.Ǒ@}}=q~;Ƹ8r8 `+++9# /^1vqpU-_c|%V\1pqwuǕcϞ=qܪU0gΜ8nӧOsvZɓ'9[nĉmذc|1W_}qƍ1_~%qwȑ#Cq` lق1>pq> ?8k_x߾}=C>Gyc駟r裏:SCӑN$j?v0q{{N#$뜁ILVjkk9r ~=LܬixHBMMMNFƤDzB>l]vY!1c 6}}}J7,**tѤ :.66!xt:Y7t__HLTDb?vɯAb˥#~HK$d2+J߅쏉IL>'ZFA9ND'''4&{X"vN#Ӑ8334&Kl6NNLIىZ/]teBr<%%_Bfhƍv]&''L(Μ9UXXh6y䑧~ @N~~>H(++C F+7n,B]+W~[BMƘ86j5} É'FBHKK۹s'UH⽽?O?XS.\8{Y&O,h >M^"DUUUrZ2!|ɏx4>%KfB^aҲ З>1?۷ŋeeef"Q ^TWW8s.\H/oKKK'M$@xBh?:d2^}U6??ĉӟ:;;[ZZkпdggV***~ik)#b7DD?c0LKK yp^sND"q\{r@$VLuZgϞh4.r;@UTbX"=zvwn?~xrr21h_FRI$X|Yv\JJJ yU[ZZH P-@w1#B(11===@!Bjb8%>Ba*6v !k!FxB(>>^ЧBATHD1ݻw|Jrv̙dFGA:E֜P[[^#UxnSa a0h^#@ ˦Ob!a2`r9q=F 1 |Q x_7xFjjss߽F"Bc@k.:fg͚E6k(T B`0x]PFb۩0F#x@0}83 %Tc>[0Uw`{{{I,ɘcǎcgR`0\~*J x> |˗ Q vVz6 !P(X8ܹ"U9s J||<FQQ@ҡ-: y|PFoo/x@0LXBWW. wuuaX 8ƣG ahD8===׮]#Z.qAk!0^-[5j[[߽FVKbBX8ܹk_WZvmdC$Q@R0&L P-&mPk( d2 L{ĘD!Bww7j 1 R[>8LbT|ܹs* z*U*ĉ@^#07o޼{n[+V HNN~HP(X8|'[n%իolFR0Kf3[‹A@U+Ia6kbW(}mB(>>޿cFpaX,$H$Ç;#HB+WXs IDATV=F`5>v";^#@HLL ]]]~SRRHT*X8|> W^ydFV0K- FAr0Bk5]e隽`7'@dUKo6.*N)KFe Dץ8O | q:}}}$H$C04Q }N׻j.u? ..`mudG#]C ݗ.]"qBB¤I"EJw$ _I\\s燗,B n6Wwn~׸rJ!jFC5&''XPp |㏷lBիW#ZCՃ?V^lk J{xͿ?|qܙ7FVSaL0~/G\.j%1˲1:0tF_^% )tъ6uCQ'*4{%!H05<\j1E8(*:UrRL `s4qӦM;w$ ^#@Pt@`0kLLaR(v?/;vؼy3׬Y.Xl~^. gj[GKZBBƔ)SjҷPf^#U3Qn* Lu_a!$͚P8{ltB.79%B(***E豅 ):9Յ2!Ȯ… $h4%r\bpG C` ,d-ZD{,#j0] kkJY&d4$N\JQI'Nx}ђ{=Z#^{ $V*~,_vqxڵ]cFc;qQqQVlwNiq8] ElZ]AXqIeD*! VG @1uTjlte&XW| Maۃ5Zgꨜz@'Tv_>~u&jmAnQ&V+. 2 Pt<Yn7YeO6BЉ| cCc<<Je2, D,.YDFrx{/耪dרVIP(X8|G7y/&ZCA4 ƴipЕ XVAF:xI-@x\Z#&&Z,{7VP*ޯ">#uFv( C!t:Ұ%@z/<d!̙3 >FBWyFx 5kPaDa(NGGٳgIXZZ*D-"3Y0H|rzFpԩx6lIm6BTTPZ~cccIT*  —۷oܸׯk(T!E5)H  S=\]C5Z*lOi0_PGn5@XBL3 cL_.={ ahD8O&qRRRYYxM$г]a6-0a`W\I"J`ƻ[ZCTfk!B௼D2۷o{I~zvŊdP(Tӧ 5vFrQaT> (U ^c^D5~%ĐƘ>0 {nzN{{;k(q `P;0MkRK Frx֭۱c?#\.^EbRi2X8l۶{! F(IIIT3fCG0k(|aTWfa*Dܙ/ vΝ$b& g Ӆ JYPZ";VZ%D-@!].߽FBAbRiXX8l۶m$ްaC9wR'u^{N;pG@RRp^#@hH"nS浳^eK JBӧ@۷o }rnݺFzd&?N 't5RN8A└3g Q <  <իW(FsnI[6Lʖ0!Μ9C!H5Pd2@5kףHXbKuK'd\hB\s쌤60THNNkDTupP|`m{$pBAD1&~V'{Nۡk$@۶m ټ#r|>n:0nڸ_9>3D5nXYĉƯ:<)))TkH$2ٍ;q)?7d2zD"DŽo`<)) Ü<55Op4 ICkwOg47jC Liii ]L& 傮 )k֬ 㦮m.Ogۇ̘Zڴƹs QcRGB_Xpva|Xs=dcyVRkt)%8#ܹsTysx@bcck̎;npk<>x6uޞ2XEqqڸg$2%% CyR^`5KP(]RF"0R){A6ueaƒ|ꦝ]BP]sK0Bck F'\.tV LYf _]#04Q3fMUHVp-@ YZZZ9Bⴴo]Zv;×d8JPU*G Dpn~,˲7n$"h !d;UoeSRRaKc`0W\.LիB$M#n+%mh#'t5677>|wql65rH E?è| q\7y6m""h|tJ[vƑ0K]ն% ϟ(//kkRI ^#@o>1n3M?ޖTPHF|tJԇrc|T2IMMkT("/[(*x*@Tr}w sBǗ~k $>$BwX\.@zj0nZXĝSR΂ܴi~)*hnn>t͛'D-V MP0eg3 C q\V,˲?Dk07m^#0AXPFz7^#0vҨ0JJJETҋ@F²,_[o!ȴi ~[*pSrkaʪU477DEEAz{{qqq~/^8ND"a7oL6D"t#;d9~Tu?x@4^c|WP>䓛FzQaL6MZhN H;ˋ T> ` U²,_o!ȴi AI"]SVZF0 M[##$j ,!q+c~/SN']H$>H6D"t… Tu<x@vJum5oq˖-dFN!ܼhz|F"H0t:]=5hK=A`~KCV|Il@¾tNSSNbVpB!j11118zˮV=csu Dpl&T*enJ6D"t… Tu7oHrr^cBBk(;wkW@Jzz:pcbbEJ* T vjJ!HR*CB_'04Q"^~sh"!j1L|GEcz#/0W%| Lq8ER}衇ȆH$ \x \q˖-5IMMkh4$x{ ^#@jTSNXz|PR)Fll,xx!D Dpzg}F⌌ŋ Qh^c\\$xbb W%3 C ±$d#?kt=-S1̪ 9˜2e@TCP[Ca:L&ˆ׈`R0-#gߑD2쫯J"ah5b5h6.*`AtO?˖-5T*HBkk+鍼 W>lB; C4c8HfHŢF oƢ)Kݷ>u#k09;"B8*ɓ' TZNMM%\. //wr* JL!d;s17)((C7^%cX ` BIM~lCi1%bT,r=^x{!qff˅fXVCi1---$g@c<5RN?\缹cX)( <"1wсox@<^c=F6,.)7!_jjzδ؀>tqQkdtgVʢ[ * 5&$$RF_^0 CU^#@Tj:^#.cMWyj޴:t>؍ sRuZ%f%r?yoD CbKRTtY~C_|A'|b޼yNLSGu2P:,]Ra6ebH?illܽ{7333WX!D-]]]VGDoٵZ 75pj$X֮.+Cx&oi&zǼr J4 ĨquY"5k|q2UяϞXջBWob4ukM\ZU# 2220רh8#1>E *U TBAB^c&kSa]8*1%8T؆B¾K$b& 0&$_mSkq{цɋcdaF]v8++kʕB!q:F_`z= )}}}5F FbKמ ]uH*ݞ07=J V"5kM6k- BSS&5wuXl9PUC۝٩ r?@ff&ƤI%111##Đ2 A5ꀁ( * F^c?x0>[R.M#+En$@d4QFafqIU5mzuPmˢ) -p3*vI⬬,:_:::n,޲gffpqcc#$uttXTXF>N竆5M`KHbE!A5~;!5 X4/'uNf򑺖5}ZFujkgpP3330HT*;233HUETRa$%%؏F>6Pm6wFQʗhK5!8@`?a(BΊhN9X| ;lo_ݴRc}}=Dʺzɐ8zz˞1$g@cXI^c?Bd|vڜ^Y\Q'C"5kG6khdĬ9iܞ޾NUdG(HBVVFqq@RF-HUkRI ^c?Bg|zSπ?[0#/?$b& Ш.KRwAuzJ&ne! JPgvv@C|1**JZF_`H0߽L LX,mmm$&5i3[T.t '$ů,"5k~@6k ъ4Z>]iw R+ ԘqYYYT'N\CPrss]C5(* Vȧ`Sa0hZfy6)}M`ج ݦ=]t?\s煖tͲmR'^իgBjjj-{^^ ԐA1 IDATlnmm%qtt4xBȧӸBWc/f\|mB*P5AF x{?xF&={*tzccms2AfggSa71---??Đ2ҹRnC`mWmwf,ISHAm߾999k֬K^477fEGG Q vTUU[|haH,ealnnn&qLL xlǃν>!]zgv7^#Dklf6,]s}_`sCVkwP]ˢY)RoKΦn^czzzaa!!e(zTu5h*4 9оZo{ϏIid&";HȄO"ahHtGvʬ4Y7&sە5MK2ģݶmsss׮]v&5C***h\PPw p |1L5Ne|.ẖ-VHZ)[VMO׈F|"5k|'xa/ޞrp]g|M %?PaL0A6kڢ"CP]CP(111T5Y#yuцVu(5F ` (aM]51s~nc.Obڸ$P%~$]n&ӍeZmLLp^N ׯ_EEE~_Nb~ D8&Iדƛ7Y#Y=`x K+ x@<^O>I6k+NYR {L/0#G}SƖC!q4 )#@0a`^#@j53.F>f맕-ϋWr, bߐa(㒤hŃKL{*t[CE8YKȡ 6N{㝔8W['kz*E"p"@x۫H ^c?>k7ZvW4^k7xg mk x@z)^cD =N#ݏet?UR _FFƤIH )#@)..kRap^c?5SXm/beZB׿5$Q%~I; {*t3BTys NܴiI~z\zƌX)/_qqq߽+Wab?/$ey!kd 㯴u՟n<s޶ाŹ2j^#0RF>ydPHF`.NR`lG,'BHD9ݞC-JYc.++^ e(&MkRadddOzlSJ}g된I*gy;"կd#&>]Xcӣ6JẖiwdJ>-kʂ \t޲O<^KH,C@c4Hⵆ;7n2^gF$soLyF xkOJ6`^#@ˣ(**bE۳RfqIG>n2;ߥg;?ZyI~l.Y,NJ⸸AV9")S5RՁP0kƑ`wԷisz_T ٲ mngD Dp8??ƍ/K=+`MjׅM [vuuuF㍅x!q.\SNx"E"є)SX8ƺ:ǃdpixJV(䦤& sU5g?#5<*¡NwOȜyuц6l3[_?ST[999%%%$L6MPFG ^c?5ͱJ\x+*#>ze!WTUU;$.((n8t䜅yUO;<[Fo'cWr kkkט#@8i$ٳ^sH,C@S[[KbJ^c?5R5EMw뺚KOjN!gZ)KE@X^#xg}lFO8V\is`K&CVϯ$_/F )++kDӧO'1x%>> #77~ k $WzvWꚌHt[f$( Ba(Nee[oE₂xULLVIL]_5u8YT4Bk9v{V()}~Ϝ9CbHTVV𥧧j~ k5z׸?h:AƊeIUH k_ 'xB*|jϟ9s&~G<;woNOܟUԷ.I3;E ,3fk/* #//~ k :n>kW7^yis3SbQP@pӛ7hD8o& 7o,D-UUU==7WTbs[fL.hCM&lv8?EM9I:q˩S-35>}Uxq5MZB4_;K 0<^?!HAAF^^p̚uJuT*-M-3p]f`uweڲt:̙3`^#@QTT5Ycbu4o6 i ?/݀  TTT-[T  &(c^NGZn=nqr_rE!7)E 8ĉ}֬Y~Oԟi q^BŒluLP@X>3$b& @SQQEEE>\~2đQ(/J݅.~X.%Ư(2f ;v޲ϙ3^I,C@UQQA℄^P4]i+ XXx@_ Jaa!p'LIbRF/b(ͻw~+SSpъ1V͜9sMDs%1 Pj5Faa!x@8n6h/btͲF _0$SO=E"ahD8ׯ_II7"0h4KQwQO߽;@QM^TWTԳFc55j&hh,QW *`AE@T?É%&>w|g&dS;͐tk׮Y={ʕ+5zzzk5)U3՛r_rQNVoɓ'ᑽN:kN"lUD<}ի633C JJJط'O.PRV/^H5ph)i€Z#}kf"]`twwwH zZlEO+,+EWհMm NRԭ[El\׈fffbOʍ(4SzYT\F.v5>8"8aGbYhD'Fq \zuݻ7 /iiirJ}tN mDoV̉'1**wĉĖJ0)9O>MKK#9j qQB##c.V݇ F\Ͱ>FD̙C.D52)42 QfMJ^sssmffF˿kfd;dw o~MyVfsW;+S'**#6j`ffZ#|DnDQyc{/mLZyZT+a\_"^Î5Ų(ʍzʕ+٧O^._5HXo8IIiHLgnWj#+ǎGh޵Ǐ[s*"rQkˍk7\xZ[g*MqRV;6&FCqwj)5Λ7O؀tF xxx@b899Q㓗(lSAZj Sxyinf&nUa ܂'/u'kիGu]cZ#Cbx{{rr# xj=ˑlCyB͟$zժ^E"9r[eΝ+l4RNn0̕+W4ƾ}rҥlbxX܂}\yv7<8s> Α#G~kG%T*!DK.F5)7YhdPkl{Fr.MX ,ôpoTF˱/5"ڧ8|r!uMQϒ5|asssJ^PxPϨks^vEC j 6$6j`nnZc 7Ј|>%57(5}肋:DFA,eQ'u֕H$aaa"W\_կ_?^RSSAkԽGS$gə+B+hGǃ߰aC޵xbKR'O\x5E.425~6%). Ƣ~45"ڧָpBrZ#?QB#05ÍIO[FA`wɿyS{9⢎ӰaCZcFmhhH wԨQw?;vBhy0EuEړgV3EŹo }LJXX'n-2kVlUEanذabYRn݈W\YlOKJJ h6jW1Sײyۚq.0,xԨYX5СCިQ#޵Ç[s*"rE P9߿cǎ$kk͛7 5_~ZvmR? HK@@o7QCO^Sn$A{yX)vg}CyڸqcZc&MF~~~:5_~e̘1 HNN&5kÅ I yX31A @Un={z5x:h?vСo-駟YfO ?O*7n܀Ke/_Ni5琖50'O; @ˍcQIkcYY(xɹ?W^*k9ٛ4i»T chtHk9s.84&Ν; H$FLL 8m45jՊب5"3g( Ao]cHH4 zG˖-Z#F#88߿!:cĉ] Ȏ; .Oz#zJddfbFrF@Q!ťK-ZDl___8ԓ_>|{Ehh(6aϞ=:UVk0B*Ç ĶVXA.$ FHJq5"Ӯ];Zc6mZ# $u ЪU+! F +l@bggĘ0am>}vzrxW @DDfbFeD.]Zp!+^Ν;°qD'Nhݺ5cII֨yr8"fWZE.$ FHJDyRIOk,..%6jpwF"aggGKxx84VVV zG6mjPQkD+++HPw vvvZHqv#;r9u\>}Ųʕ+ -.^>+ IDAT56gºpl®]`]cll,Z#J!dee;w666իɅD"҈222 1bbb(ƙ3gֈOjڵ#6jP^k; ƈaˈTP(_!1¸^z  I"WRf͚M6mڴL&Nxcر]=g>,Hкͼ{R|6[n7\.nS54, B4//////##/^ɓq ŋϟOl??#Fr̙,bGFFqT77on?;H5qh)0;v]vkpT2qi"r233Ϟ=Kl[[[nڵB"4ϳgφk׮;wV\Yp&###&&RiCPPڑ~qN`7OiMԩUQTF8w$R,+VC⼼۷o>}zݺu))).]f֖Hѭ)y1XV).yݜٙyo7O/]1ڵQ*l \׈֐+6yyՉmffSju\\ᓝ;w ٳo六N* i}ᵽ7ZK&j}Oߘr01cNkb$G\=Ųl ?˲ƍz'ׯ:oi%2۶mu:t]kݚUqibr+H$b.K.AF dffBbP*gFp^;|3#v Zcy;uDlB!?CTFjjz޼y0I.m4z0YYYJ9s4 Y?am[WauZcxtBlB!?CTrT] jz[l5jԩSSG!1UuVTG0 YմjfotԉΝ;5"-$FXXxP}evvvjj]lkyVv!`u=T{J^ .?~C/jݴ_qUфڵ+XUilllll;a„_taݻwO2eڴiB($)))f"v`` 2'N1**JT'5>jٮpa+a񠅛 ɖ-[@kܹ3Z#J;vqpDy;ZMȅD"Uie^^^/^`f)++ #??Ri;w.j Ø;X0L{j"^߽{w;vȻB҈ 1J%{2v]#zСY,**Znݷ~+tPAbEEEIP^ Ô2ҹsg#,,99H˖-=j||K&;zg42 siK#BL&c' LȲ,]}u5T>|?|*$''kj?vh"o͌x򵀑ΦM@kڵ+Z#86@AD΃?Nl{{{D"4VRl":C<_?|˗J5FFCYFDGңG;w̻( ,!!!CTFk v$ Az*W~FBua7%`4H!]v5F e\v l *H '%% *L}̾q@WSEYauƻ|+Jq5r^jm63ic|+*ž5zk׮kp~B҈.\T*JvZ۶mɥ۶m IX 1(y_>L}O^^uzꭺfSײz1S8TlUYQA%AC2g2 ЭVڎݺuj݉{"=$Fhh(סCa;wnݺ͛7scc+WJ=J 1|p.tҗřy=OzCNr0A@O 8i&M'Hz0G+Fi\z5e2uee Aիo߾=((^I.wy5j:y$ӼpBMBD*aZ{8777fj޽Ak/x{AlB!\pCTrp5ԨQ?""CfffB+Bb4hPjUb.rI%X.11rVߪ>)+1TUzݻw!1BBB yu۹MXZZj!1 Fۅ |FJ$C5cmǪU .ɨD`(+,B3f;((h4߿ 6[ 22L^ˬ_=z5n`Dܻwȑ#vttoN.D5"}P(4"$H RYVcO>KG 1,--)yiԨQo7 ź9x;$ 0pua^zQCwF 1XaBt -$СCiHptb;T\Q"0˗/7L&nS44J$?C"11qԩ 8q" /q8~73^i=*f{ͻ+9 P~ab;99q T7Ǐ!1`=,^"0w?6QPUCI8`{ɻطo_b+ ,!)) CTr;w$ 1i7677'CE8M ޽{S]w@#((qBb 2 DYle2ueeYhsMqҤI4?\&Q]7)QVYF9.\2yd^8ZcfͰqD+Wk+W$6q7"r޽f͚ܞ={jH z,]F} Zc߾}yHlB!$''Cb(JN w@DĨQ%/͚5E zG~j 6jȵlR؀FZH xFCM,YBopLFu$h&FYiH$w(*DHHH:e^߯5b~X24`޵FXql;w5ݻ\ֈZHe˖ijXСCAkׯZAP(4"H R_5"@HH$=yƺ 5Fprr Zh!l@bnnA4Yhe2ueeYh q èj8[HR﫤'!!Pe߾}w%v-tq}S$t捊ce˖8h ޵FXql;w߿ܾ}ȅPZiÆ =ZXXSZ5A"={F&9::mV!1˗ 5txUySMvw;~Z\eQqQ LP&jaÆ8`޵F8EP`iD)))J4 5>y򤰰aZ}ȑv .OAb Ġ5h֖NNN|w_Ww_wNV JRI ޼*.{[V39 s^|0 0ͫj3AQ(_j֬ }]j---LMM I(N:ov+lHSot?: : *lӦMZ, pBxx8<'VZuܹua/_0a._P(a԰0O_{ԲeKm6_'~& [yh/qҥW0d޵ƥK[&\0pmj*9D}ƍ'wD"9r$**Jx {AP "66VSklݺ5 /DUZ:@BqGl /,Z}v#s̡7L&?>=W31J#˲( "ܹs&L vxxixٽ{[ݦMlO?]ydL [DOuRsuu$ٳg7_jhXѣAk|RFElB!\xCTF 1WNK֭:;;SÇ59ظjժկ__؀zZH޽{v#gϦ7L&7o=W31JD"9|@Q!ٳgǏO숈3fk.XئMl‚ `]#x'bs [DOy;ZѣGjsH ccZ+֬YZcZ4"oÇ;J믉P(4"K.Ab(Jȑ#5"@XX$=166VZƺ#G5rw@\׈...\zEWٳ'm>Gop㨮D(+,Bgώ7?# /;wum۶!̛7QF5.X2rMw\]]cǎ +Weڵ!7FO*j̘1V(X¥K 1JeYiD <<ضm[WWWb Fw@LJ6 D155Bbу D9s&95k=G31J#˲A"3gΌ;ؑnU;vy&۵k#B;w.P=z4Z#dE7nܹnnn܉'jabBuց!?FO*  FpeH RYV055]vnnnƺGo!6>>>\:u ALMM_~%m>BicBqTM"zfbFDrqBt3gdڵkS5oG0{l:f޵ƹs[sgD\~ԩSFɁĨV%/ׯK#B8q"hGwpJY( ,!-- CTr'O$*DDD@b۷oAlJ^o55"+$WvmaBtjժi!1wNL6Qk54, "rΜ9/j׮MioܸAlR#B5ktcǎ]ke2/l=ݹӧO  '' _F///,aҤI5|zPT&L B҈ 1JeYiD ""4REl0vX{Fpss""" AU֭m>2uTzs7}tz#zfbFeDOIkצtm@kС6a̙57ws͝qsڵwƳgϒ ŋfffo5z{{ciD'O J5qDb+ ,ʕ+J4" QjUJ^:t\6 IDATMl8AƏOu]#QkD777H ooo.,,L؀jժZH/ D qu奙eQ"9sFpXO:u`I~߯_N;bf̘Z xQL [DOvڶmۈ?\ֈ/^ĨQ%/6lK#B@k?~}:t&M]ke2/l=ի;===rZ#BbXXXPi&4"FO* RB!\zCTFj׮ AOkܹ?(yAI&QC5";$',l@bbbܹ3m>2ydzswS<==54,$B8uÉ]nyfMG0uT[޵FEZsgD\zu֭j aeeE͛Ak ҈N ZI\RAsP(4"WBb(ʲ҈ @ڵ!1LLL(yҥK@@."ɓk; j h!1:uD)j9 A(+… E'O6l,X@˦M]F]b{L»G%d2e#\eb{zzrF˃İe˖-5aiDӧO٪* 6=Q(Xµk 1J%D.pUS$=k׮AAAvww;LBu]#QkD͍<<<8 @*U;vG^A)+,B'O~WĎZp! /7nz* lw}3T{޵F8*Y&Q^#\i&b{yyq)))F˃İe֭5`iD3fkT*P(4"k׮Ab(ʲ҈ @:u 1TB_|Bl9Cw@_<==!19???aBt*Uh!1:t@P= 7C>fbFDsĉC;**jѢE4lذnݺa&O ]?֨.Giiih/^$5"@~~>$=%/;hX̙3Ak)3|B҈_T*J#uօĨ\2%/ݺu %%/1uT{5"$;#l@Rre-$RGƎKop&L@o|DOqssL חX,˦ [8qbȐ!ĎZx1 /ە+Wݽ{wl·~ ]ixaL&-mذܥKj@˶m@k ҈@kBRV(X!1JeYi=Tnݺݻ"L6Fֈ^^^!:Hʕ۷Gzs7n8z#zfbFeQ@DL%K믿G8"'8c ޵F8J&S._oLJ{jKH '''J^oZcdd$F0{la>=_T8b+ ,ƍJ4"AOkѣGdd$===)yA3fP{"7$$}m۶GFMopjꪙeQ"\|Y$vz~g^֭[{Oia5㏼kL&s_>>>Z#|ٙ;vXvm,aܹ5Θ1U*լYP(4"7n@b(ʲ҈ @TT$1%/={]6."̙3 w@\׈ސnnnw#66 D5j93f =U3181ϲ,?9v옦ָtR^֮] `{ꅍ#B7ntqqqkpTL&-\ti]F.PkDHZjQsN֭!̛7ƙ3g;J3g FpMH RYVqUĠ5իnݺ;⨮k; kDH WWWM؀H ѦM.}dȑ8믿7>h&FYidYD;vĮ_ehxYf ,ݻ76oqj=Ű\.suϏq@ 1\]])yٵkhX‚ @k|QRR2oQ2=E31JD"~@Q!ѣGG/4^>}`F ]9sxǎKlL- "ŋk׮%_ Dҷxصkɓ'}ΝgϞf͚իW'v~m޽{[lI옘ݻw Z]D>{ RRR֬YC=zRsr9h*J*87o*UDlBQPPoߞJu|Go#ieڵz,csss zÇV(VVV4޽nݺu͚5ixYnݺogΜ -xXh<6p/^LlL6dG[nٳ...Z6 a{^^^xVB ;;Oٳg´ cY͛F}ݻW=ڷo_bׯ_Ŋ4ZҥK۷7 /Ltt >mʕ_C8o<޵FEZ&͞=%55D ??/$ZA~w ~111hk.J^tO?s4kLA(J_ #!1J%bhPWlPeaᝨW$Bo߾ 4 [FI0̓oEq… wpDĨUصFr# m>!7Ј BAU`YQ QGӧ4hrJ^V\yEb_F Ì9 5.r[@DNjjU_hFFq jF j!(5޽{_ F ԯ_د_FLJco&DƂa e2٢EZ#Ab8;;0QhDFD@8GGGbI$;w`|Pnc#G` +x~Yb(}QB#0#Fq…kF"L&Zx_nШFQ/7YhTֈAyrrFaׯ{1΀7nLlѶ̇O0 .55"$jeQhDI9FX,˦ E5^˗8p@17rȅFa ZExGAl\>|G6ʾ֨~WnШFQhʍ"ը5"BP^k>/%rȅFa4hAOk8p`ӦMKɋ^Bo,YhU&6jZ;܈B#}@nDA#˲" AnxxܨQ#8ƌ_-[JAq$r# :%K56r[@DNrr/B/PkTOnШFQ܈BFDk;GԫWo"iР$!%/ j޼9E220K,5.[ب5"@@@$F͚5Qk|wj FDmۆB#gccC,Da''P%*>_QFK K.hx#bbbPhdfȐ!5ϼk_}5O@DNrrˉQ@j۷Y=v(..֔:#qk׮\޽{[lI옘ݻwk.bڵu4[jurrrQQQNN]?~BNqvvvvvJwAryqq1U*y#)PRRrm'ojjq\`` ˲?ڷoOlReee &7nիӧ:~i䒒e gll,`< 6000e-Z .IIIǏ?|L& 8si֭[޽5&&&..N?S9QR}7׍7bccw)tDZnݺΝ+))iذa\\;]A@@$F͚5givZͻpժU 楥eBBCDDnnn߾}5^uTk 8aTdLUBs˲H)o9p@bi/_^ >|{nԨixSRR=dQiiittɓ'5?U֭[G;񌌌.]$T< qgٲekC !\.ϭ[^zH$h+<7n^zB_MJJAAA:5hN [ !i߿_s];VkJk駟CAA'O]YyAk,--:u|²B$$-SZZzrޒ%K,H%%%q={qgҥ5}+WDHJJԩ144LOOd2ZzzCZcF k.~_`Up~WmWV-33S舄^0 +t8_u6mGH߿vpBGVt,N5juRR\[1kȑR7M:ԭ[7b7nX~Ȓ%K=tPAzD dmmt~h/54r[2SN$nf":u~III ǏɅP«W5k\gggAj֬lnnnff&Ԍ"H/^PrA[n-HiUV6mn߾} SZZZ֪UٹH.k?5kրdժUV(Ƃ֭[߾}֭[O<)7A]ԪU}!1rssY2 SF sifftgFDD;22RSEEEP (M:tҨQ#{{{^>MTTԉ'}ɓ'~ kׂثW/޵FI* UVVlCfccY>|d >|X1ё$G.k!145f]@ .lMBujT*)F~Ⴃ 54J$G [:tk׮nҤoFŋAkꫯD8/ qŊk$\._t)#Kbb;Z#JkDt"H\J^:Zc6m4"kׂEIIիP(4"{Abq!ӨQ#H z1+8"XpWș r-$FÆ i@ScRi^荏)eYjj[h6lX@@ /ѷo_WZŻؿbr zJbb"l=}\ֈŐyyy>|ƶmbiDׯqʕ^RRvZb+ ,޽{/_,+4n8|m"Zw@ ((#DdZHv#p  Z8QVY!}[.8S!@RAbPrQJ%Fa׬Y%%%p4B҈@*R2=^31UeٜBtcǎnڴ-[hx?~bb"G#Bѣhׯ]k]r9C=%!!a… 4>1h_ᅬ?|ݧO~ZPTV< 44aÆ5k:RQT^رc5vK#Bشih֭w._P`iD<(,,TSܻwo֭{e߿rJeӵkW.{jҤ $==fԨQЛVȲǾd+W677ussS6#q֯_OUk;`EǬaooϨFsňTSߔ׾}x9s DEݺu} ^~mFF/?~,t8L%2^gݑ#G  ZVsUT!I$7[5//m۶Ij՚5kִiӀ33333^x[n_BZZZ)tЁ͚5ۺu+ /ͻp렠 ^7nL:u̙:u7/a~wv600}qY,X@찰/$ ř^ט + &~̛7|&5_CLL %/mڴ/v}UZZŋ{nܸqȑ2 0666O%.t{T*~=?]ct?tkܾ};ĠT*9zG Zҥ 4%R(#Gڵk>}4˃hҤ $==믿=D2,kbbbbbbooۥKa._dɒ+WV)33aÆwnذ ïM]OY9@bUXqҥ0۷?/--ovA]'8}*Uܼyx5Vx5~:]$I^^W]'77wĉp|eٸ8###C8gb4oeܹ֏=Z$LjpBpp{ի׉'Ku7l_[.8DΟ??|bqCfEZ׸xb8Sm۶m۶oL<􉒒H7oPr XإKFavI~'Oܱc^e֭~w._P42u###CRq6 ZyԩS5vޝ}D&Hؾ};hj/))ieo~g]ff|Cނn0s˗&&&ƣ4kL Lcǎ!!!#1o߾6mE;we̙q8޽{qb,۬Y3b':vmƻعsgb"cY'Ξ=;k,bGDDpNh,oҤIrѢE|Fkkk^}j5$Lgςֈa AOܹPk>u!++ C*ƊĐ!CΝK"o߾?lݺ￟2e ͛7Ġ5?*"nOHJJ_rȑ#;TDZ:a!1 jժӧOόeY-$Ovf oٲ%|+4JTLY~ȑs]x9992F:uj۶myL(H*lՁݻwϞ= <~822>>>o?~ܫW}<++@u}jՊ-[מ?;w&LyxΝ;)ժU[z'J%hZcǎm``e;1뜜m۶lݺ_u:˙3g &,iӦ_paFDD5jwAQyVD9D$"xr+qkI^\Uڨ+޺&*-]W-z!r"p5mg$1;4c3߷##Ç޽[ucHbn^rssɠ?VC=zΝ=o… z$tkTK充LHbbwYիWWXݽ{C#""-,,̆ SRRN>"&**J׿ZGn4//ƍ3uT|=WsN5''K^'Νaooobb}]|!}˼yHb O^Ν;e2٫~R/S䐚[9e TKwBH"lذǯԊZb&>Ң)3emmmRf0::J楝^3x|Y`k֦D䢌aPm۶k8pɓ/=R }k֬0 Z^cbb.\E+ȥ-[t񙸸Y&66ښX7$gϞUHNJ`wuN ٳz> :Mc ]n0}oJNNȸ{n]]][[ɓrܹs/^Lw$G*]bܺu >GhBP*Xٹx{{988HNGF!զ&D4G1\_OyH-[q"2o Ξ=˝(HH81t泮11o<666:BS]~҄Tթa$@U UCD:af?Jy%HsE.Zٻw͛7?0rJ5Z kTO?rԩSh6l;5o>2@b=B~!GPz$tkT;׮]#?؏>hb\677Đd"tS/H83gQ ΝPH$H^Ƹ~e1MK,9x O :V2k@э#)$::˜ؤX!r mjbu)K.ikܳg`۶mcZ,_x/^THQJXܹsk֬sMHH;(w^~HQE&n߾ull,GP.]k$YG_??_̙3'%%QGUC4!w]=)ڢ5[neYk:Rt'.\D+9|痚jbbwPj& $SCㄙ5&r@ Zx'22˜x}:/1Bd)**v6lਕS]]M6%'Mt ^HKKk$Yoll|92(P[[[SSSSS%BAT^Pח RSSuvo% $ݻɈus7>>O>*ィG055S|0 흙_=ztΝD ?~ &aaazTsqW0TK$$+yA3ܻwO5VZ500wP_qqq|G4ٴi9pC+Teee| PzZd' z&zs_'ORRƭ[feeaw^<8"Laoo?i$^0]]] "++ݻ޽{&&&xb5^rE^cDD2a:;;=<<HO``\.766%$ 㺺FRȲÇy'##cX"all]__?&!tjNaLIPP__Grr2I~V/_Nϟ稕c[[1?_.6  PݶwxxX_ m}ww7__Ǚ3gH$ofDyy{wM1<<<̞@ ݿQqMqqq[[fff|ERXXtzR^\\W<ܜ|2ȑ#k׮1 jhhzGGG322 jU9~ut<&UXXH^'$$B())upMټy@ ~~~"hɒ%|044Dp4A__ߧ~ѩǤ: * ݢX -/ 4M``  }=Ӡ^/; abb"ܴdY3^c\\E+@NNNqqs;Zh^v,J`w~gNxGFF[!ضm֡"i 8:::::"ܭ*..NOOǺV^ L6NFuE6mڴiB+W;$y]nnn CPt 5Rfƿ/^cHHPkQHvdde-EIKK۸q# . BTJ$~hfffaaaP(>|8222<<\]]MF T$D".*"D!TVVVUUD"PP(D999555ot!dgggeeT*9jT*!r8Eihhaz-2"RiPPٳg>:uzS5D' :u߱Qgdd-##b1G 1 T* CBBf̘Q+񅍍R,**'kՋBWWW2sKqbcc}ݜ|GGǟP9p|o~ٽ{紡>[[[PZZZtt4-RFDDpJEE}ӹk<{#Gpwر!TZZlٲ'O:88,^OSIIIEEѣG gϞ=22wߩꢢٳgwWJ2>>kppرck``رceeeǏW垞}}}ǏPս'Nxɉ'UuOOɓ'ݻO|pU… ŋ@JJ B(""B۶mCmܸk{zzcvڅx{{O6 !>211B.]|WTXhBʕ+`еk잖tdff~p1++ Bw >/771"!tA'B<<<B777PII "`̙r yOIDATΕjNpMM [ZG=xCŏ=Pcc#X[[#/jii<ćֵ)BN{ vuuBBɓ'/@&!4w\8s ·Çsp܌?cc㺺:KiDžW4jmm}iҎ^⎫Waa: xY5|xY*$%%! !!!d/]Ο?Z|9={!bŊXF777700׿swEk;wBU.^hhhu ,\066v``n٪kxyy 7|w]tt~]r_|wQ>\"++x|iG^:<<_绹{G@F`槟~L󇆆{Шk899UUUzzz9rk2@8??ٹF)چhaaQSSCWmyL|0 ˲xP(dYSbAD"e1 Y$X!$ ˲aTʲ,cd2˲xFOOeYEa{L}}}eq,Z<,˚!XsAX9"cccea#&&&,g²,kR rP˭b1hl|"I)722¹dmmGI@ XdB*0ׯnݸS6668 alllpGqGq5|ָ 666Xc;畋D1e9ָ畋1e9rD2=,T*}Ad/(=<<Ȋj*>&w<\IENDB`./pyke-1.1.1/doc/html/images/PyCon2008/client2b.png0000644000175000017500000007053411346504626020361 0ustar lambylambyPNG  IHDRAssBITO pHYs IDATxg@GgT"ENPEz {Wb-F451Qc FB4E$"" ^0ɸr{ӳ{ۛ{vٙg0 QXL `00=a0 `O`{" <a00kkk^zZ---]]]WW#Fc5v^ X H>ጌ[Z̙3#""?]___UUr%KhjjA@y~ [痕@WXɯOĐ!C"###Sm$&&nذ|"mٲWz}+++2??v?T+[P255?Ҥ| %%O=t_m X^+HQnii;v,/yyycƌՑ_@T#zmwBGߥ~yyyϟ9s&5ۯ_?&HQ޳gڡ[ZZ[hRyp[DDcǎ}NJݾ}[OO?H">K_ȯI*~~}p[tDfffW\dXyyyX3AϞ=^G7qDi%UUU'NX`A~ u֧OE9sF[VVV``tttlͲeܹc'j~ٹsVVV Xʕ+Q}";)q >I 'mllܽ{>8ׯ_喝M6)((|ɓ'GmGI[>Jk%nZ/{}Tɓ'VVVBÚp+//1b x.Zwwv$^7IW棬 U't=B*KOaM]8AUTT? rss['O2ZUTT']p!4#""jkkKJJ\2rHjI??v$_.wwAPfGHӦMC߿Ч&. f͚t=..łP1MMLAN hiii[rٲerYY(_V@譍YpHdDTco5 v>YԩSzJJJ;-^^` '|W+YWWGݻWo!譍YpHd[J,--GHBÚhj%={?iyy9\\\ɓԴSMP9s-:1 w#$MU>i$RuǾh%ԾA}}'ϻjժO6}I׭[~nB#ķ(wC$M%""ZmۄJHe9ʴkVB/YD9sxOڵkW ڜn nDNNYQQ1;;[ڄH2#ܹsك;;;dSTO>@vyy 0j$B`WWW;w.kkkw`eev P={EssYўUV ]$IdKի Po…,~zA4` $ Ayюr~'򕜵5 >Y!]Y͛h*00P DȦEɡك RRRRTTTTTTPPPPPgrrrrrr,bCJKK?y rDa>wIwڵF(u 鉨ϟlhhohhhhhhlllllljjjjjjnnniiAo%WWW]2 $} _Q1 ُ?QDg 0ww̞=nǏl!=렝nݺ!D`>1%X///cX^^^T.| dHNNwT`%/_5 9s&] ڹs'MzhA}6J0ݒCZZȑ#O?'G-t !.N;sps`Ũ܄ѓ Q Y$7oޤrZn+fmU*D=#R{_8}}}oi{uhi%---}E.\!mTԷUUUb'PM<ƚiic۷YEi|# DItahh yyyGաxhT@__?##C@mq٤$1˓ppB+++ܨ/]T;oUU 6TTTs\w5J ӧOGew~MM]\\D9iO2رc ur.w^^~w]]uD8`> |ذaTZZZgώLHHmhh~ \`-~MtHuu _|YZZT^^qڵ۷3*Oͬoedd444R$I&Qkτn@O{mjj b}ξ}^|_MR]]M '|MMb:,i~w|nb1ۇΦe˖QG~ );)) /{NN+@vvvLLLrr2 ++φ333씔wbbb޼ypo촴4۷ocbb7o޽dffVWWSPy`~ouܹׯWUULUUU0.mce---쯩+vvsss;kkk?X_p2p0 ڍK;-C߶ o9?`Çs܅ ӹ\.\.wҥ7op\U_~rW}]r% ))~WD. =/\_ x9]v- >>~8.wrw8u˅ N8r7r r=z#Gp_} s}}}߼ybYG@^zeaa`E], ##6v-[̙3vlyRRR~իW9X]] md![]]ٚF.mddlcccd"ܼb,--meelkkkd O>ȶClwwjn4h aÆ!"{ȑ5jnjq! ٓ&MfS?ӧO̙3'O ٳgO:sΝ>}:g͚ ̙3ڋ/?>fXyyy;wtpp}9=͛7oݺk׮ʪ<==a<$IOOOO6L _6 <==a65CCCjyzzw { |6=z@ >0ֆ󃶅,--ݫW/OOO330^lmm 'UUU-Z 6Zihrrr.]g677?}L@T\\HKȣGtttvڅxD@@ S1ݻ6M@ á  GUUU?~{O /_DŨ6(| `oo Δ`hr{^j շn݂u-y,glmm( DDKK j͢T 8xfb$~ Ξ޽-b<#X;"ߢTUU'q" 5 SSS'`0C;\CCj0®]|||? ]Uuu76tPaD'ӧ[㡆!b Ɂia(DaڲrJLtTUUǏݻR'F ӧjfff8N`,KaΝ0+-jjj#FA Я_?6Z' @ CU999^^^q" "<<lj08D8NA5 sssDELmj$phUUU]v 0_5 0]ϟ8UCbjpID w+VkD8NA١ѳgO:e8---Qbᔯlj08˗Mg}F>Lٳgo޼[AAn8!''  GnnԩSD[ʼnpkL[/_|rZRSSCȈ:1Rj5c"C0EsssSSDa0lj08QeeKM$Z#ǧ@ё7 ~0D|7}ths802+#,[ $'q" 5 KKK}*dԄflj08硭s8qqq_3[UUnHxGUUU1Eammݻwo4~GG C7yyyf͂69x`8QUU޵kףGV$X,ÇMLLMeIxu_~嗴TE$D):_٘C899ѫW/PIcǎf0RVVFNNN/_d@444VQWWǴHLL򒗗tpp`w3Csww eG0ﵲX,Fۗ™b:::_~qYv+VP;666b% dYYYLX0$IB}L=xmX,[[[KKˁ2G̔<}i...^l٩STX$yyyś6AkQFݻwFJ/^tuuMJJ'I1Л7oQ.m'NNQZZ}Y&L`P]}7644į_~AKB=<<į*** ;ϟ"8tիWP޽)366.++ yj,_A1a2Dtzy}E1@jjjݻLz[SSbd<ӧOW_1]d~?n1(f޽t'8s ѻz*J8}]y'Nzy-WWgϞ=ǎLllW_M޽'N.;>Y޽T՞ϛ7GWZ%n"//~fvj5A5GEG *ttt]HHdɒ%KRm``@KW^cƌy۷o۾5\]] 011իJPð&JJJgZZZbV>N ##۷N-8 H"J"""Pؼy(UQ.[=S `[XX@YR':x ǃ5bРAw܁#""P IcuHjI0,6GoooԨ޽KKK&I"7nx1xDI EFFn޼YZ/BG@ )_59z"|(Nj5!EyfZZUTTNj*RfffLL'3*%y͛7455'8 amggR(P q"?jܹ|R_*Ϝ93ĉJKKQ(((`P!Ntut rQEE ի!@p$7NԫW/(K*DhQvf>tO>]^^.bTСCtʼnJKKmmmmKaD'O'Irɨ8p!ׯ_#VVVpC*;h[33C6lh^[[{رKW$TEEEk׮6Ñ>OvqVqdfu*aDm1o޼͛7͈sΥk07|m]]]Z(N8Fm{{{;5NDKDZZR=?C 8QII j *Ύ 027nDei 8'p8PʼnOb={4h۷Q[l|uÇiC[GGGܻwA|l󃂂x<Ç^B;%&&&..ڞ<s觥:׬7D lM<;hs8)Dn'4gΜAq"555?ìNڵkYYYVWW4iR;j@NNիWG¹O#..7M 3gΜ9shJKK y")ờ(p\4$QaʼnKnwSSǎ]ĉQcPt^˨fuuv WVV/^QHnʒ8QYYӧ>ٳ"fMDEE'*))ߡdIG $h- \v ʼnFɶ':zh]]Ї9rÇݻ }"E[[5 ...6ÑOyw7nrHضmG1Ngh|4cXhX,̝;wp'O$&&Ӈȑ#kjjQ[[ɓk׮222¾{\z]QFagg'~GH{:%**C;v CPya%GrDQQQa|T%yL:g5j:v͚5҂rXZZvPHN?5ZQ:tR~~>544&NءýB.]7n*%Yf͚5QUIMMMhF+/=x{{~Çlf9 `D LݥL`'''"}"%KQòeP 'NdyTXs!9}B*T":Ξ=*ШuuuWfgϞXB;_$Oʒ8QQQѹsЦp ( 8|QQt;ѣGi޽:::˗/APM>]YYC+**Θ1FO0:re'=zt+OĜ*zjll6qww[[[1**LJN]nݺ!
b4tuO~(.^Qz})L(NDD z3gΜ9'%wC AR&GR٩K~ y W%9q0T":ƉPW&t=[nE|LL",rJ+q"GGG(K D8v]q]vA[GGgʕ4c!֮]K >7o?~|}}=`̘1?-ҥK?1 #Yꢆall,JU%%%p8]Wzyy _¹ttX 1c QҪP D6-uO1c ?$? ]\\Zy"j# pzzzϘi 5bQQQMMMgg+W$ɴ(5ju9Z8N$ m~^WNxqD?3u놪Ŵ޽{/_g\zU&xÇ=vV9UfÁ(UA`O$?ZR/_Fq"6 O>}tZAlj!$$4hЅ  1f+#A5xpvvFx{{{1 ((J".Ȁ"fqssCofpΝ;iC[__Ϗ}ٳghsʔ)RRR iiiٰaʟ ߖ.]ʔ'O޾}SN%0GuU C#z!=z@kJ7, I2!!A4s]vA{ҤI˗/+++hs8}d(022r=z:F<`aaaEE_~v:88}:u]l+++UbX,<\`ffv1iJ[VVv!hkii͙3GJKK铓C);w(((׏A nJHH!C|Jlnn޶m@UUo߾ ^#qbjA,Z_;0CϞ=[ښߜ:z /2|Ԥʢ"ǎ]˟ Avvv:::6668͛V7?uuu;vhnnVPPySbČӧO^^^{E2ȉ'аiӦ===ᆄ;q$< ^5 eee(pDJJJ۶m }QrrrMM |M;ϟ?Auu5G$IN4I__3JJJ RQQ?۷Qݻ7(A’1d &L0tuuQO̢0hРAuF---QQQ6mBw&&&CA8SNE7A\7.?$SP=ߛ,ݻw%)*MM+VФK9w_x_WWw劊LQFAKBD@MaO8s ]q@hoܸ}۷}}}.C_f!Qh<s?ajjF޽E_6Ñ2O3??K.WTT\l.#$wakkn{etǏOKUzzz{쁶Gv*o޼ٸqܹsLMM%6m6p t5III633[_ҤK" ? fvfiWa?Z'Og|={8z"300]S~;whᡡ489~8J2sV9U 033C C;|yy޽{p'_~DeShh̾w{ѯ_? $ 3>ƍ7n8ZC +ƉuPPД)S$gp|bƌ}Zy1c0a/_(" %KФK?~|Æ |"ƛ6mfٌh<<<<<<&{رq"Ν;Gc000@s$/=oκuVX!Eiرcׯ_Yب.=LtƇ9j -p8ݻvv55իW]VCCaRQðg={YA~:cر,"zzzIxŋ~~~ϟۯd\bfΜ$yxxDAr3>={-XhM:onڴȑ#|>Ś={v``9SڤmєkY3>Ο?OW(??644D9%Ə,;Ȋ#Gbbb=gΜV9U ٳ'j"+**m#9|֭;vhEhaaa7;y!jNNNӧO3+HOg|3!IHv׮][l)))>44tь)f͚\qwwo剼R0錏oߢltտ tJSS7o6[ePPдid@M6ZSD錏 .'B+PR'<?ܰaC5c6lذpBܑAohϝ;=i$Ap`aakTTTDDD@0≮\SSVZ%9ٝeG"1uP'''O0G8u544x𡯯͛7|W~$֖M?~2'NA۠`ggm{{xf0Njj*Z].˗/Ϝ9w_hц  ҆i {ʔ)В8~:ŋtʼn[hmٲ}풑iӦÇg5cƌ͛7+L[:t5hϟ?'bN|XZZaoo/JUh5jө 44~C{#>ЮعVbccQpwwq"HNNF %NňNee3gNWzl>***oO?UVV}4`NZ #4vqq!A6(kyW^ж~z͛7(X֫W/EJ]]ݯv~5өM-Y3>.]Dchڵ644ܺu+ 477 Ebӧ)))vrr"P t5JJJtttݭ[Bf0ׯ4`kkk'''qܾ}{EEGaaaCxD&Ѳa;w5(77w622駟)\__wސlmm'L ai/_ سgφx<Df1kkk0E p8D͇HOO, `Μ9pƐ!CpԠMdۇTUU]t $I5;{==/]TQQQZ1b$>>͛7vtt$tX9sR0uuuhqaUUժ*f0ΫWP-j7o>|o5k$IH8N 讫T[[ˬơ7N222矡S???4|r__nݺ }R~Y^p!{޼ypCxd#5 QByAaׯ_oܸ1::/2 ''7=zrFLٳga 6 lj!gf#MTUUOCC͛7vIQ(s'rpp Pz} mPaX)EǏmcc#bPRRe˖]v}rӥqa뷴*5N:~=MP.+PLe߾}hŋpC䨞HƇ5 777*ihh l;fO4I=,r} IDAT8N/h~S}}=γ% ---Gٴi۷o>ѣG@@yl6#0BBBjvvvA6(@VIIY=̒#ht[t'$$֭eb>{bXpb*1118Ν;f-,,,,,RSS@/\%K.\7dO=bcc'? ӧOLNN.x-?5rH'mmRhihh0qcǎu$̙w > #11ʼnG mPE fhii1Y^|hݢܠ}'b„ Њ xG9q e|S~ᇟC zKBxϞ=)eѢ/_~%{plj#ֶaٹsd}Clj0D0FDbll \fHGPPPNNGVVVAAASNͬ͛VUUʼn_1/_x}! 7@A-j%$&&޿ڶ6l@ݻw߸q 0E8ѿedd@ݻw2'm;]C[[{ݺu+WD>FNN;FPhٲelMΐD}0aǏsssܐ7| hj4lj0$0ƌDҫW/ڳgOf0HbbϞ=˷_AAa UWW߾}jjj M(+Blmm_n_2'a_~ 픔KKKf0BZZڦMz,k֬Y1 ?dY[8???$$d޽ |>}_~՜h"hgϞ=߽|rOl۶m۱cGM g͚%ԠpP7nK~^xDYP[[{-[].44T5ۡݻVUU0`ubW^{ڽ{&v7@#ٳgNHH۷/z:,,,,6olkk܇Ů#[W\ -%˞HFx<މ'6lؐ -Z$//7LO_@GUa_^re+OĜ*摅yg׮]ۯ7=C C555׮]6a^B ljKѣG~~~ׯ_ۯjժԠ#Y&99ͯp8_~nX,tA,))|yy nذȨQϟ?u,-h-쉐-Og|bM6-((ћoF GO=|hBW&vڅDVzd|<4;+,, ݳgO}}=GG o;;;0 "=lj0ׯ_1i$'aÆ4:n ۷o+++> A8qH_F(1(A6(t={ѷo ^۳gϐ_vpp[2l0H+VVV9UbX쉺Ysssddd@@077 ={vGhܹs5 QQVVV```DDDSSu?ASL W0D_[[8q_LrIh:uˋY=ⰰݻw}4rP'''F Hmm'O"j1b#55gii޺u+ e |:ڱcǶm>rss >|x'ӧh('RVVY1m@oZ,K=D=544ݻ7$$$??#`//N}wj>>>x̙3gB4**}ώ;N8'8qĉJ;;;~ᇭ[߿hiii|̝;4; GGG0Dݻwmbdoߢ1k,11o߲eţF>} meeef`$\hٓزe eb==v<<j*RUUիWi<'riӦѥٶmj'{x"?A uuÃ;Kuppɓ%⻸!JUuuu({l%##5Ynݺ湹Vr)Sܿ lii9vƍSSSoڴۛf PWW h+))I2((()7@A^[[/pceevqܹ3f̛7eGp?WXGGwŊJJJ$bcc\mWWב#G2#Cl㵍.\PcQFNNŋwYYY~Z1NSLK!uV06l@s\\`|) mڴnssUPv^ҥKׯ_ON5 ׏GYADU޽{Faa!@ll,oߚst:{iQ>}199ٳgRG2E}}=JJJH(NdbbB nFsssn঱qfff;fϞr_aggCǏ ӧ;Ǐ%} q"7at)fΜNɒO>M<9!!Ali8L4.٨O$iCZϜ9vMVVѣ&۷]+pssC CQV#12KVVjEEE8N//_D#11ጤѣG]j;vltJף!iJFmllLlܸn 񌌌ttt ݻ7~+ի˗/V*=ztEh=Y=BD<[0aBۏΜ9,4haa-'^[le0;޼y3lp2' >ѣmD˖-k%%%ww!C 2]p%!z 4UFZF ljޓֳgǏSn߼yM>X^EEu$b͛QTTwHL%''ʼnwN mPfׯӧO+VݻZ@UUzWWWv.>pdr ]"cX ?~𪭭5kӧ$I0zgg.;wꫯc7nΞ=K>Lg˖-G9U.]twե_@hhhss3|UohhsѣGvIY,ѣǎj;JQà> ACC ܘ$''5 {{{'$$X,V޽_|ɴ(q ''gll066ߥyCCׯثW/f`$\>9pE\۷L'rrr|~ϔΝ;3A1قDm/\|,L)G1Ӭ=|?Z_߮b$''3cj1#Qnn.;… PyCQqg#1w Ce8pj5{P& 0JJJ1 '2.L/'R:\.֭FBIIIUUm۶"##i8v̜7n8qaÆ/=rwwV 0ڶm۶m[vI0{1chZ1#QnnѣvssKHHh7>T~6AN0 ƌ!>'b a9)--eƨȸ0ɜ-˻t"m=PVVr6m< f׏;F#BBB J.R]ʚݻ`ܿ!$f$5jnnn/^[`lڴ)66g#Dc"j5{beee555&R-BN0kjjvqqa2OsڵkGzȑ'O6+X k QQQT<#@y1ČD999#GGL7~wTo޼H91b3F~thq!BEE3Fmm-DƅDj\a(`TTTR,X68cA̹z#G9r)S JX3k T<#@Q( rrrNu=\"<ذaÁ޲egڀ`9c,+FO` uuu󼝝8 4ɜj\.YQUUr"'''nɒ%Ӵ4DO6AՌ888888 S͉^ܹs]J`z9JzW Zo:k׮eoj8S͉^^z1͞:-FbݻZ-}ꯩ̜fȉC]][wgoo_]]- 0HՖQ-F9S[[r"{{{!,,6xo+LbRSS޽t.]=z3 J&jFe G{2]6[RSS1ČDك gϞnj7kײ`۶mF"͉!;wd6YXX,[LҊߟC|NĞ#|Q[[ˌh9FQToX={,]T| a9Qee%2Iz#j[[[#mp.īuOy#x/nj3sLdZ֖5E&9 ͙3g˖-%--AHR}]{쩿n 0g>S}6mN";}17'z;w <#G^|?PTT+III?Y+vtpiFv`ee%̙368cAgϞL뿆رk,GJ:Du@@ٳ yHbM<ϛH͓tf WWW1#J0`?s+|zǎF"3ωB;X[[:tGN^cuKLW:Cuuub+L&f Z 93n޼9tP=<<_S '/t: 666 MMMjjKKK-y]͓SN160O~XYhR-biiiiiɚG<-|G fvډ̶Jr劏ϫ DBr0gjFZ. lcPٖ4빺;vU!… lyq->\.[wXXX̝;؀Oi)Yffկׯ*tVZŌ_ Osˉ۵k׿O:vYf;C|N.V1ZiDȉ^78thZ-Ղ se0sΟ?67n 8e#@Ag#GPD%FGVV3~9E BlqIGٳݻw G  dpwwӕ[ZCANd8 l8 9`hZNGL&BCCiy6!̜sα#,X`pWǵl0od2ǩ7:uɧQT"πdzzz/bLꨨg#' ƍܹ3=z{Xf1!^q+5=?ЕTs'9c#E$_gϞNuPP… 2_څx.#;;[nb3--MlqIrJ}W>+^IDATF"ȉvJ|B&o"L8*0sΜ97P=qİ01=w郩7zi,>}*ȉFNN3233پ"^^^(DpAAAo6L [J W;q3qD~ӧI-2+B[JNǖ(j ˅ڐ*' G=}T,cii9k,x3ۅ0 'Z$22򹜈NDtB]tiʔ)a$\t<ʘ4il"8Ɔj 3 Dqqq3f̠[n 4HڒѣG} 6lڴIڒ=?uR[[ۧOva˖-~~~{6JuڵիW/*JF|||tt4!!!˖-3+Nrw筭[>ͧ-4{*D".^a_/EZZp |嗬p1]eddZBM?dƈX0!!}h>ϗG1c,_\b(!!!{Zɋ_P=cƌUVܕFa fNUUU]]Վݻwir"Q\\̌־FNNDˬ_ӧ}ʌ3 ȉٰas9QrrFȌ3Զm[1] ХKaB01 '^鞞RW;vdҫW/i+f FSSիmll W>naadɒ ~ѶiiiR))) K.^^^b3!q@@BcѣG & rss1lqHHQBRg#RSSB)))!BA>u6B(99:!?Sg#Sg#ܹCuu6Bƍk׮Qg#\BJLLF]x:!@pu6Bܹs3gPg#N:EF?~:!tQlÇ/_.._m۶Ν;9sãU ϟGmڴ;))"---##JPdff?ʢ~kQ) JբΦ|A jjjZ "77]UUP(Zԏ=oQWTT(Ǐ E~~~!TVV.,,DKJJ EQQQ.4} -'O( 뢢"BQRRҢRM-&%%?G>!$>>^Ӆ$$$hZJeaaAT*B:NTj{{{VU*4+T)FTjzZV*LWUU!J%]I5᭩IT6551VtJT_ACCRL[zRiccôӎJM5mVT7fjjjJ%}jzݻS+J777{IT*oJe>}Sg+0M"TJn{F5}LT6ʊdر111ƍ[f x:wa:&''{yyݻwU:|||N>4mڴ+VߟNYB}=|GY]]]Bs}7oرc^`q\>,,l„ T/Z(88?,Y2yd.]:e:_bө^r̙3={6Yjܹs?>?Xz… ۷oooohZ|XbzEIMMy[ӥkL奯ZÇ &==]_r}V322EϞ=333{SSSVVzبR[CCCvvyu}}}NN޽{]]]nn֭]vy葾vppҥ~vttܹsUUU~~vrrԩ,((;v(,,...:t(///**rqqGZ$1BlIENDB`./pyke-1.1.1/doc/html/images/PyCon2008/bc_rules8.png0000644000175000017500000011276011346504626020543 0ustar lambylambyPNG  IHDR{Ve;psBITO pHYs IDATxw|SU4m{C[ ed^ (<tlٲN .ùݝZ޴i+ yjI<rssn]]ĉwX,޿?kahmmG@"~Ea)wcyپ};իF~7 ȑ#ofX:BvppMõ;Zj=Bwvony[hqzÆ  h>>>)))V|cǎ-^899#66v~iUUUoǎEh4fbΝ)0TBc$''cv, U񫯾^N6mڴi[PfkgLRvZ:>cƌVӧOWP(5kHR])--wC(((h4#Y.*a7^^^@U4Zr;ڑk2gZ`맭]9qℛ| "X֭[<`,}Xk݃#ܼyJ=,/끪h*x s/lqyF޽wYZZ*HO:5f|;UW\W/.qƼmf0)7Z'`?tzaaep=PMbYf_.K]EJR[۷໴ƶt[SSƦz{H]p!ܹsx\ sS(ͷn^zmfy\TESGX}9zr0(bXO֮]/XB  Ny0 .ܹsgbvu} 4P b/ !zQˏ8{Uò*++07r_ NMM%|1)))Va322RZbk@%41hѢ L)+c*;Mk9rlKm+=̙ctLd9е8l:h4w{wT*} 0^r9iŜm-%55Uƶ7n`}5戝PQQѕ+W$iŝ-^k_^PP`f?cɒ%!LiӦiӦAkTEc\ =.I+e2ӊ l?F:|pch\uQ 􌊊:u;޽ۣGm#Go9{U/{A\\5f---ض1_1XMV9Zd gp.P MwI1d ٫bhh(dŜm5[\ wúe8s 6I)Ś>}Yfa9}u l*W^ݖvoy^wZ1gۃʺb BÃ5///H['СCfF8,WBHeܿߊػwT*52_~byٽbar,a͊UXX=tG6ƍþkYR  ob]{@U4϶m۰ JKKMZ~7UAy OmVBM 쒸f0#2|bQՓ&M 3uuuǏba-n z]yfK*⟽>|쒸f05⧰OOOWT@U4[kkk޽l=<<{=P+"h˖- chqr ~__> ??U(_C.X*5 ?Ŋ+i.Jhcrkkk+--=xEzzzfgg[^U`9wޝW[[+E"QqqVX; chqş8q˜ ҆ Fn466h4,k׮ Fkll40.* +\.… %Y72&SbL"R\~=H=z8yu c*hq ߾}BYF&Q]9Cbbyh4,?|\ TB3tuD=tE:}*ZǏW\it$i?# i,^ ʘ6p`QW'Nؿ͛7kjjJe```XXؤIfϞf^tcnڴ6#'|핧377׼|\ TB3Ǭ`lϚ56V$HN|OP(IIIRYPPOhy Eaa!>NSd2YQQ> BH*S ~BH"Scbb)b&OD𶶶2|6m߂bLJ| ީh|l]-ooojj| 477 |||ߍJP#Gd0v ~b-6Mijj"boPB YBbM2B())GIOO'dggb1W\! <syB̨Q1'O$L0;k@~3{lBO?DYh!$,__k?Zjq:qoݺtRl۷o#LaÆz˘{?ԩS999#GܰaJ**dSt:rP"x{{Sx<>Dq\`CCAPPr$22p9XRRBxOyAFF>G;wvAeff^zrS @tҨQ)Æ ;u>ٳ&L;p9xiӦS&OS:4{l|ʌ3K۷oѢEyBٳgٲeŋ444\x1'''11q }Ȑ!Jre"f r%|\.6l>`օDGƧX,;'\#G)SL1Z1jڌ|tcL>>:uرce4Ͷmz-H8q͛7 |k׮wT=ZMuR1>>>BA $rB BH*b!DB}* 11Z[[ 1W[;Cxņx¯ޑ!_|P{Ʃ~WB%%% LrhEڵk'NLMM/kիWk[PNN ccc_{5!@xIVXGݵk477Ϝ9S P> H.]R߿_$JrƌuuumŊ[lTݻwYVZ(JHHIN'dJP)ctoi4!ߟC 1N!777BQBNcB«;'!!E!txFX,BLdd$!` !Nc뫍>`ٱc… 񯯌! Y,Vmm-!tH￟9s1cLͤ0CЙO6m:u*!Ӈ zǏ>|S_l1c DDD5l0&--`L޽ 0`СC ƌ5`̄ =)tM2ГB̙3=)nܸAhii)(( 2(222??>bƸz*~frF|2cċ-))YjՁomUUUꫯb b֬Y=ށt2cEEB JJNN~>q .4/ûw.Y$//oӦM&LHLL$4)n)V!Hwh-?0F B< 7޾}V^lٲ.ݻ7Hلb_K[>}Sj56BħT*«e*ګW/|R$t}R(ܹOqssKKKçd2lw-ѳgO|D"!g2X|=|aH$\x`XBB0&<0CKK af\.7&&f…0 G'L! HիWoݺL&$%%%%%M2fgR:4c +6HDfϞ=t[lٰaťuuuIIIXY;zxYDP @{V>,H,iq) 4$gOГ233u{E[`{n˖-]rGTd2F0[Bӵ/ 4߿`0 `|#F $p1p8\.dzxx0L2-.p)] ܢ5:kÝ;wZZZ)E*:: ?1֙lZ\ZBߟP`P 0ìYf͚eId2t:ݪ0)??0}%Gd0}=Xs,)p@ErB\ry";RtkS~Kjw)dRVlf}K}:2gJ/anT/͍ uIJ^7H3"×if}%-;F`OdJtFmR+Er K3zOpRX{vhPOJlF^+Su[oBWfCBByqK:z RJ&7֓Ƽ5{7A>j@ ]FrWEIS):yHUX.ŠHhbb8T.fɤ@/> ÍL;oFp0#p02ݻ-W9++0rvv6\=z_ǧL2eZ\UVa0P(,,|j"ZlZ\#F<5IUTHH"۫0gϞwӛ:uԩS-ɁbV|t:a4pz-\p…䐓C*&OLK-.wšXxk -PϜ9'HFWi,((01c020Ç-WYS\[88LF`xxxث<@)BCCa^e`~y>%??+/2>e~)002H$*++ç466j7$Jٙʫs ʄU< _2i7;'71+LjQP{Ԁnoq_9z lR}y gsw$ы˄5>UOl[:fu Sdٓd=X=|DŽ>Fv@%4Iykͧw)8ܦ>jZ墊 ~|pHC2&O?Bra8q)nU5iU0.>~%2,<|sW-c'͛7o:aO7G6KeiSSN`CԠu,77Z7*]Fr\w8w>^vpg۷ 3?%IS] eـ. ,80Ǟ:7QTƚs'8x/0L&ӏ?\SӄƶjdJ= e^=zmbUq)$Dz3~*8b&m!BA¹uϽ\s%]ons.c0&}zV:KA@jOU6>))EYRcb{{jYq^1nJP_␐Ƈ#.Z ޻%m!!ҦEѣG$iĉ*ZZ6U DFZ6+m2XdP),7z3%'ߛL>L7*ޡ~O*mʭ~]|ݻOJII_oCn?ʩf(>sA1\ o(enx'D& Ɋ`JrRNFIsLyx 9)j؄!ĉƶ}E&mli󢵊d]TBSWZ(ڟX{ZS Z$WֵJ 5kZe |Hɕ ^JU= C hq7DR_G<;;vy3)T]Qշ7ݙ?"pR[K $uM7+Xy~í{tR&Qʚ%U2ߗRV}n}Chx*>OʿTッ>1_ aY)!JJNXLdlФaǂʎ`tm;y3ƐU8o% `ӈ\s?U~|qnskR[VWt޿QQ/Ow/bWiԞ>R[W7=5rLLLӜ!t M^n>=}NUxJ,o+,i4eOJNNX.={^\F%Gݽ]:׋ʖ*D%(2NL sGTb.U%JT)nOQpiM 8I} 492g`YfȜŽ_߱ԔH|s+_;(0JWJą%k!ByrNZM\A\c<7*g7=fU]Z溫b2"$2B$vD&!2T55jFRTЃ22"HNmqj/DusC}!ܱҎ!~5 (;>:f=N1i;}VTj{eonnz3KoZժ=T^៹ ~+;[7JRR zi߽/`|<=4G}V:ŵgM0;GF2e ~Lv7x7IgK:n''ELrĝ&B]{&97L[ o! +kq뚥JI6MnNq5Bќ\3|w-) >\|Gl{Ӏ:iwkOk'0s5fpUL 7/ObNL|Oa]$7Hͣ6n=u9{%c֪Ɏz!{L \iޘJJˍ7;RYBHR߫k2rkOF0bt:B}nby"l6X36~QvB=r17zTaNF]{@iYt (eGJ/NjqCBwgjSBFL8{%;{D:/rhb!4*u%H ƶZЏ9+_$uB\_yFh2;}xky{Lfxakuw;y4HHҞI*t/25ͮ/l֡8{%:y:%)L>i_}ۉ۞AITSc݀B1\BXUB`i(6dZV>~Ui_X6IÇ\mZJC9oppυ|wﷂ//1A1aac샃3JOxlۊ>u5LM!IZ D)s\/vQŘw}ކCWB:&Sɵm e'`0:?9q;$:0c)ْfaIN!Gb|X1\poO/~Ԑ'zzEH]c>=4dDJ\ܬlӡIãGDž)Nyw]ɹTä2988O@G2lȎH~Ýy=VIczҘ)#'sk*B-{ @@B$Π1nTBw2ܩnOR7wG+:Un /;3v-+rJEcb-@-N↱=IA>QR6 !ĥuqϰWc$neg Z a7ߤ>#cBMFF74j4J6}˞w)ObL3WBT'̊NذڨhГJѴ_dۿ."?7iL4w粔6Y̟8Ԥ/9D:&03'k%<\!$i4ͦ Ey0/u6DR4  *It gJy=5 dB/mcP)=kO x20W 6 <UJA_~,]vʓNtyYRRrSobbbB2ԣ.߲)ɑv8ͩ5j=y9}Q,;O%(e+޵/6|}Nxc8 D [ [sGEU= rLYSVwu'}χ=7C%ċ]d1jEnxPVM3f>2BƦTnFk:B=OS8I5GbR͈݅&D ѶU7u[\[xyyyqs'C6C%Jؕ^ɿn߬_4EIw(uuSe&*xh^47ǼINj!T&y,,i괾' + #w=4@cZIN1^}Xes^71kFjt0ZRR0rջ+sXeUP!2d L5lr0PA5/Uqp[wcY=\7vkX~ٌ8ۋOD7g}-[s+VlkU7%>uZRvWJؕWߴq/ԥ>m6dɃSXjYA1n7=ӽxFJgk hXZ0#I,ep5MJ lUշy" dü=BalPǺltˋ)iֶ)nI}B2ûF~{,4~bJD'rTk?銫ڏ4Hݟw_||g@I  *3S<p[$MPKkLoGљWKc-S@%J/`bZ.Vgc[.ï_r/d#BP&55=5;7F!l[#{ń?SDB\v,/[vt.ij^GDžG *Ш]?ڨryM"٭}n!:$ЬaY+έd+!jگ.8E([vËןKnvHϧxɊ[~b[DĈx ٠{'FӺFv]/޷̝:U-2H"u9 WWP?fpP2y~m1A=fcsm+'H\&.;cۺ I@ _ƿKY+'E RLݍ}f|G7+$n*RFFFNNL B#Sj%B\ǂhvo=TjD#SE 6:3dr㴷27O_olN`l6vF\w_WJ/vw=,^ \;Z=eu߾}z6쫝JIjð'B|͛3Y [!3 1rץ{A=8 3Zyjniӟfc-nNup[رcǎ}jF0ɉn_GGGKO zGePq=hT =T>s[r{mgIn|]Wgȣ[kX"wrp;!bbbZ!T.ɝ{ eԋPN]:V7B^4|ʍ3Em,e4-uQ~y$zB3ѣ'31!=Z[.߬l?"VP+-^A\lemiiiӺc^ez駱ǵA/Xwc+.Rŝ==~̙3 o~dji1;G57 'LDB"UǿVрovRжnoqx_v0ݝFQrQ$-l_.zJsn]3$i_ ݏe x47*ERKFF(.  j>P`Q77*Rl$$A^:y ;gxUsWz.yƨ~X76%U^zpuY#BMDrXTFB\¯8Syu[^j$+dUϹf UڅӫE #xnxh֜ ӂ<"/k-7=:_u| l|gw)$ 7םex+:Q]^銫Ox5=t:sYucI .Ɍ{D;D<_Rs0.7*e^zlOCJa}7ORWz8AO_VȊDr%j Z-"҃sQoy)!uK-;|ŋȍbFY,l*;3𤯺ZIe/:M9_u{gƯ2?zǢ{o7gk )t ma8*~?}^̟g=h']'!ҰЬc7gocؠfݻO"̙M`,̈_,rv1&ʤSYV?ǘGgVE5BqH$ \P5Q)tj3mtǫ nZqOP$Hemr\t Ih>L#6v>Sw ;\}qH=eg?'!!W/0@wĞ1?KknȄ-R!B`qXi {b̊Kn|{챰V,動RFEuJ{6n|ά ];fFK9g_ήjoE Ls0(VhwDxیdPn~>龉fĎqUTB"Y!_ ǃ8UqRu^A'm*eTǍKcdXGfE7w Z\-ۀɻrЭ P eZfxcuFP cBnuiyMjqOݕ ½V>"YázyM:1rUJ!!Ґ޶A S,=t7SݦFs#[^fyk̫lp,555]?s~Z\aSjY}^eJ%a 8}o~)@ u}ϫ 蕣׌IlR @H}0Oһ uϞ=zO0jԨQ,)(=5g :*噎$'?5GP(s 8۷oۻM>}PSSC*nxfϞ=m4| > 0Ȗ)JWj L@?)vHE٫4Iܸqe.YOP:{&#XIMM$---UUU֢=.pYYY02-W9 jm*믋/Ƨ̟?DFPr| ̫ LV HTj7` &RH6R$WJ6W*{{ЩtjDo3cu& IDATQÛ| iwq@7r| o.ۓat7F$7Je* ]SpC\-JX2'oql #A$A"aLJJ"T~ukٴåniH.]Ѓ撇͏n1[ XNW\3ھF}^1U R1eBMNǃۛ}nL\ٹB'(nb)al}w#F1b%Gٳ*D7W8e#x8Ev?K>妧Sº|KZZ5\xKO1/+7 v0?FQGKR`.bbAo?32WՄ P+ 88"~9ss>7 xF۰j+9=}XC:VSwC)q"Xxk€mω}-BW|K!ՆvF#SYOs"H]U$3)ɑ3RܨPӼLb>iq#8 tGȄդpcBn/Ӊw8H{XI7_zpʜʘV'@_lIS}½=r=_;. a}*ЬP@{EFI˅2Z5us!~I5g0! DZ|e8߿r >%55u*8 bsMP10֭[uU/ۄN+020 +++çEDD3? ]U$ A`^e`?y\nKK >E X0 _~?>>e…wF0#`-.` ԕ+W Ajj*" 4^DݶmСCjI}1c^eO2)*JBR`DE #Xox -@ /_* pRyyy7>%--mȐ!*8 WX*_~0r20I}}}II >% &&BnnnV('QTfL&k笅~!1U:z*By{YF 444744xmժUT_SL /OD~->ˋB(ooKSZZZv܉Or-§x={S o|J``ܹs)+>%$$gŧTUUo3fS'>%66vGS&LO)((8q>%99yر>}ңGQFSΞ=>|ҥK[n9s&2 yQ*00:#..Τ/BgLviCee%ƧF6`hJaل&B!J)++#b 1IIIF(0 !p1QMD*b1pB #M!$''b1鄘BBLVV!&??#;;3rHB̵k1Ǐ'\p3eB̩S1Gb.\H9pӧ#~甔³Isύ3!DRw#t[\c˥WzS"0~nԩ .kY歷ӧM ;q 'yhoD`$BEOMb2\/BQT lݺfcƌ Xv!_"=<_P*1Nb !`b<== 1Bg."vp8B!&88@}GB"Ąbt{NFFFb 1111TBL||} >F߿ǘj02-WC),BStl3w2$CN//n!c( !FJ7 u1/JhϺw=zff3BCC.d2`%lZ\`Ez* OJJWa=[-@ -@ U̙30cǎ$UUUçث<@_e`G`988x -@ -@_eƍ* M8ңG{]Wx -@ -@_e;FWyҤId2\TTTܹsf@}Cy`^ehq[hq[!<302Pddi)* 2` Thq[hq[!}ث0肾-m` -.` s D¶/SƠ_%̫:*S-^ҿ{8gggݻ!$ .]z%{*Mٱquu?p邂@4ty 4Lj'֭B6iҤٳgjú0@*jw<8m4 c&g`cr (ʵkڂ3f?~]pͭ'ON:U&gȐ!'Od2 i< i山JR[UUeA \ܪUm޽wYZZ*HO:5f,6l*W[.qƼ]aaaRRҀF:ϘROFӧHLLt;;;qbKF,Yĝ3.]y 6v*x >c̴4<V(wO:hp|j:0qF9]tܶNIIqnyv>؇\@]e0H=gʔ)_qQ\egx厶P(xkNsd2ٱxl6H~fX;9M;w7o=uC rH\p8Vu#jz؆3@ǏwSSS}2ȸ\"av{ O`3d\DFF::.S>lo~ȸ\^bW pa@G>̜~>U.**#W\9x '99cq3B{;%%ŝפ 0dҤIwBeq_`2*0FBj<`$W\)**O|5k6Z[[/w9g,XaƕH$yyy}B7|9kUV9^^(L{e\;y$6 /‡が ?VWW:_Fϙ3+//GLH,_~ᨠ~0͛7k@;v,55'Xp}۷C˗/o36ͦR EwwK;m۶czgH}/s\0xw0gq'~׮]zseiӦܹdz4*#|q_XjUca{&MJg>qDQQљ3gz H=[oER<6gK.}br\Wk\;Onmm5!!!wygDDpH$FrYYY'OId\ qo xU~?4؞0]x|߾}؞ӧj<)`=.ܰb2pd2ns BNhq'J1 ݽ܉h$i8bzj4Np.bߺ ?`0pF׃#ƍqEEEE\̣>Op1> ./s=-[b,Yٶm.f…1ݻq1b&O9q.ƹٳgq12\LRR [ze˖իWKR˖ߎ, %883Sj.ZhٲetjnnbV\_;:իWC]e0HO=g„ ;o}v\'d\?||0555''A~7?$fX w~i{d2=T*2.-[lҤI>bh48qtzqq^t`0q>na2g`1nưX~cl6.8D"f=CJnii~iX l{O?tڴi!55w ( F끀o]|9G,BAc{h4NxHd0\hDEGGgfffee=q_xwqUo<]va{233g͚A֭[,OIpppgg'fN /*?䓐qGq<͸ ˸*`ȸ7s\AӅB!dj0`_$"s_Xf n+\Μ9?c{oV_ȸ04-Zmíg6k#D~Hzp̴YQI? FB]OVJJIA|ٚsf7/Z3Q& dQdzM'BcM_{SXx*wjjP8M/UI:Faw+Z6dQP#`dQ5xVѴxW̤RbBȸ/書?ܪTK{6 H aaX4_Cum}eرۓ=o{qF"zYgp|=0\DLzI@A!3($̡Q|=;,F{B/Q+)Q4t`P*vu3[mkM F$ x\@%=< Mz'Hq_xqsa;zੜ^z ۓAV\r!Q;v̹2. ?FT;2ȸ7@@Zݍp8|>W#T{{t]wX k~;_z~<u_~ۓpB{ZWgSNO書k֬ ;"`^F"$!e&$;d5fIۦ(ሽ?$~CiXAa`-6қ-rFw!# ٔF RUҚ3e'Οx5j=wƇȸ^o[l6"UM]-JJ$-?d$ߏߺԞtF};d\Mqd&Ʒ65Z˥zvLd^b丈a ?U-T-oP:F0Ɍ 0%J Pnw?(d fg67z*I":lGo1 v1}=@`w.IF3MV+HGa\f{(zrIVLZN&qi(;58 %OiܧUL."}h\dЮ&قRL[Kk6/J zh#-( /j5]] zQjX;Rl"/.n?O1YIݱ· QEQ,VŪ1U.^vcwU3.ifܦҞl 0oOpID?lIA=- ͆YQF,Nuf4|@)ad5~-kV,I{w{,%-NZ Q)Ql,bX(噊JuXJKKcG;]u8uMrn؆йViYGwAlhAlsS'$$`?轱N8m(+pUby\*`1 riwWt׹8ʨ$qYbhnx۹ [AYmև J;?kQЩy4}GDGGEP ojiJGѩBy"N&6ܦ6UZjC̖ZW@ e yl&5UjJ͆PIĜ0pΎ5t8"]j;7w#Lzjb oXwW6j\*;w}wF<6~+_C-;0yϭ4]20"!&8m^ڔpv` rdr_e={8wB~aվ kךc=:>sJ"PT>ğQ'ShP?\u[rlR8xCddjA|:u>:TZiI"&Gx}IKKKKKbN3~URu1#uw+xy"=W\Vp$ 0;[S. mBJo]l\-@'*b{.O4v t&󮊦{6ljM͘ɷ cRg7L?7Y50/׿$z2ջGKÃ%Erh,C^h w]'\/l}k?UT;3/oxDqĎvEA`HzG;q״:Jv RηIu&3BU %9DωAa&Gyuٟ\=0 j&>5Zs\Yz&LIM*t\se?,݂ҙ u_8PzfKL2/`0Y{&Lif.4)4 *{CN&]{|kXk:L ,OrnzK oК{m<biSBsL8no7紟# B6 !hٱW?jv3ȸ {Ar걝FK^%GQHnN(K,zOE907z} Y%ԭ5"S)5XR($"HTB$Iēǎ~Fh4 fᶅ _YH$o<׿"$#&-;1XmVE.w6u38bS)w{{y3_Ҥ {@_J&揙64Еڣ%GZk<˾/pFtlwd·b!w#q I!+,Wgd\l"tH  ҂V*+ﮊ@>n.JqYcjqzjwYWrLp]3dt&N93#$`qz ۩؅P(׆t ;t~jƭU4jFш=J$ bVaYy=oِmÅo&6>\wF)7(zeˑ36fENHCP^ *3)djƕh<"J4=7Tw lƄ` [t]޷!txlVrZx詾3ȸ$ ][%Q:ؙD^]H"}~GIkb\1t\}ǕQHN5tf @7e nXAKgуX[cYkcu҄9,JG;{Ϗ}kUm>wj/uiõuAwcZWq Gf^}zg~, יc>i. =]֋[狹ocX/::dwfKnw*M');Tw1~alOap˿^+7?877AV:u=MGl~pP$W|#ݲ(歋zy$2+!b` *%Mg+ʌkdBMrt2 =rqrtpV!ɌE)Ybai\!2+';uGzMz.†\Z õa\VFglԴQzb7[VKh"tWWWKK '000,,lhˋ8;|ZW>#b|0.#T,txvg;W_󷃯_>.C и 2]m6;LhnأwzϦU4,Ch:o_qsx҄B2Ŭ 1iUᢴ5r,2&6dS5 ZnP^՞(;vtElL$Gu;yAߩp\|`_HJJ d\V^aÆᘮ,bʌt]jr|dPa2)divR"?؉{WV& /ŋ "^(b0($ hX4.vLi!K3ZC\RNNjx9hG$Ū34VJ*Lު}aXSRs]vg0L33֣{gӦM#7Ǐ믱=SLnϮlO`{[&-BHnP{nӻ6|+p}dŸ讆~U+>I_CPWiǃdluJI~Θd޺-vƫKa 3~Cwl; QXel:*u(H d g'Dk'GK%IKT6c&*Z\G…jrcqJHϏz^Į#G[K.H* n!$s4n(qrhΜn|`IץRIeew}M%˵ffPY" - ~8sIp7[|p˙fuT/טtT"A!Lnx7/ߒ$״*TWMdIl*9#dܟ%fReDQ-U*lXH"DdR\%IdC,.1L$2($6cE 8Yb+䟟 ޟ0 H(mIQڒAG Ǻi^MC2*"L ˛7w d\pC0h'F vq.LMpn n;H};.˾*_F'd\7ml9_mc~]ko ˻b.<|ܴ#--ǻn ņ ,fA7tl+9^5.> vɟ~)'))Ӄu]w?;wB~aŊ.j~TooG 8ÔnBk ުW 'd2.`9-BV]PxqF_UaU_U5p4e& ׸У###\pAnw,ȸ/Z WWO?ʣ:9rdӦM؞n{=P}֭ީ |k6/^ uꫯpu?cȸ#_|L&{q>ȸ7@@Xd\0&EfX-Up!:!lfZ8=I߯,&i!ФnpPbSAy}|Sl qaJl垜ʖA%}= уFz|؞X_ \ƍ tv=?*/Yg.=}4'66vر8qok  =+++= ?^WsҤI0s e˖Lmw߹S|؞3fytuyZWg PW۷*qG~Gl@ `pqo xUqMF!/wʭ0EfI 7+4i!)!Al`zDEE͚5 ۓt&u%NULqGFפP7){ZEO #SL0 ¯jZ= .# %%%؞l/3eD2he5+5wgō q k~Ur^ZG!&/id 7g=*t9ځ,”`6 whFڤPh" fKyg OJ&~M<;>m*ު@0[r2. JCKZl6ez8`4&L"HB+汮 @K.a{bbb]|D7#qld;"`@@&i$FiAlz5&#bҽ9 =/^`f-Ei0*;١Q<:էٳ=, 2. ;vZ <5gΜ{b{<=ܹsΝ;tîKSռeBRcIDATT&/:>NBCCCCC}= 0dpmɬ3YUF^jZle&D 8o $:$`P.3'LjSiַll!K+&%xo/`2Ja.NTf1Y_hX ٢d8lBo+ =*++KJJ=IIII#B}BIh:dD*ȡQxtj0e yLZ2Z;/wʛj5dFSRD ?k,*VPLu]* 3|ȸ/̞=h4b{ӕGv܉+\XXSOyt?ӹʸ-Jצ9,VŪ1U.^vcwU`kfܦҞl 0oOi51*p]6f'|*?ua j0R)Ql,bXlLED:]%#A#>ZnEU+S*FZ F Ѵl#IQ!<N'VhnSisNš feJ%@ e yl&5UjJ͆PIDa0hqB㯄˝r;2.Gڵ&c=:>)A% *AMNvV4u.:-N]96)rˡQ2B2BT5y b\)GmR#?ʸ0WFnGhKc8[e%-{N&=6!nfƇO u&S׌ )Ѯp$z$%%-[ ۓxJYg8kZQ"DoLfPIdAJo,:!za`vl͆ ޽{2y>|Af͚5k֬W0'4Ԥ4t_]¡QEsWN&光Է#LkY, +l;nڼ|0i$_x!!!H F=U͋cp* |{Ew}q=$s<Q"6hopbl,ʔ=r]D:qv˰ }_zp Ԙ]S#SȔTqL76+Ddb?ZUC.:G[s} sFQHuU F{Ar걝FK^%GQHnN(K,+a3`C6F/dqYTq5˗/c{RRRt6r&;uئňY_%^61YWW\i\k lƲXm}[W&۷oU^hd\ СC0]xdǎog…>Gٳgs]͸1h$KO.}45XĿ)?*(ԽU_h3qoQID'υps=ꨤ!꙼W>kM@() i RE\ݪJzL"4J*L ֝!WXmת*%=ajb三@@qB^dcN… 5גVT126c"%&dZÅ6i$}_N;{͜Q/S[ _e2Af<.C{c6aNh0;Clwj5t6T9*\2)2c{| C#ℼX!㮝RPil7mԀ_p (2+!b` *%Mg+ʌks=݅Ǟt2if~IݻG.N X;3*s cPV)BHe0;VvOv늏&|h}] vH}N[*blb Mg\GrqD,]]G^lUK#EI!Hc4tNŻ ݤPwAźx7A 岂X D!kF_%?Z)-d&G8(W#z"^N(Z)42hL.U(6lћ,ZCmdue کБo2d\L܄.دtu&_5a6uF#Ί p}dzS~S:Oj`* __"$ B^IQ!s#noql~5ȸba(| @ NpskOr^ĦqY13u1/+ruVRYt)d\&Md4='N#뫯yt;Uk'`&FO q4jj055xr+w+TI[Kk!Qz WWyƌ¹s砮2$DrlONNFjrcBޔ w.ȸnP]TgYpM.~ug?\khAp9"r>E<^5>m 2.HOO_jgxCǶU3#U/wuU~ĉR0Qx[o0t`Ρڶ_.Qp[/ _j/dffz` HegE-Odȸnt)o^^~7@*@ .9rۓ=i$_ -Uoo{!0vX]KJJ`2_|ElwܱfrAʐq֮]Cv<`1(W" R&G 8pڶF&wthoSaS2.())9x '77npH_*?q_؞r xv1PT\.Ţ鵵?,ѰlD"ܵ~!77w׮]؞O>2eA7f)Jl@rcZU*H$r8Oc,ZH$6=1fY`{d24d2iZOc( 4h4wxX,{g/_nݺ4P7tӼyΞ=[YYi ())(..fL&4Yٌ R\@q8\Lww7.F$bp BF\̘1cp1)D\LYY.&##꼼<\̉'p1&M:tSPPٳg.fٸpB\O?Yx1.f˖-˗b6md X,luyܩt=~MR95. yyy+Wܸq%F۴i̜ mmm ,{7m4)))t:CO:qU@ Gft:iN3 AF Pws[~=N߾t ȶnݚx[d>P踜 [l9unG~m{[$}GC0JpcͣD@{׀P(111؞Cgc!@&#p1D"q1|>~jo L8Ov>sqcpӑB P~N F#& `QQQ- FwuuYfÆ AAo nXعl6 ]GG}R+o 7&00ߘ~cƌoLbbb13q~cMo̙3),,7fѢE-rta /̙3'77wNFlƝ?'|"B-- 7Mx57Q!f$rq1 nx<\-U|>1ؘq =zIENDB`./pyke-1.1.1/doc/html/images/PyCon2008/client3e.png0000644000175000017500000014021411346504626020356 0ustar lambylambyPNG  IHDR^fjsBITO pHYs IDATxw\g  PŮ1|MfM@cj4SQ%E靃;,G+~dzf;>gg# eM#F4Ph4@i:[\MM_uĉK.edd888{{{GEE>}M0 vٜs[g9rWfddabcc 2i$Xl4Mo ._|ջw殮VUUj'''ww޽{O2GFW^y52 3x~A4XTs+X#W^]nݸq\\\l7k׮GDD<88˫,99y;vjݻ?~͸P]k0[0 =rnݺy{{TUUedd=ztƍiii#[lӪD[ӮVVVՋl2LG֮]DIX_- ;ww}6~k%-丸&N8CO;w2@uĀoѣGk6=QSS3gZsvvv4:...{ҥf~6??رQIom[_k=-v:tg^pޞ0˗[Ek=}SFýkYjUkJ3|xs૯j~bJr,o߾ rq6Yv-r,Pݻ2vX5k֬տ⋭᜚5眊ݻΝ ?˔1-cǎc!Bu{~raMO>Io[S}9I%3gΤ^xinnnYYYͩrJNW̗^z#J ǸY, ?˔NI >>Mb zٳ -¸Y, ?˔%77ӓ,srrXYYP^{°bgiܪZgT*YkF?؀ %K4]]v5[f,S~F3dno 7#:Qjww7VUUVʘ;w9/d1=7ǏģƔgΜy|,<<ƅ,h& ?d{뭷j*:d#!!!6lhm&4;{r8y$ Ԝp_ѵkצOpuuL&kN@s!K߆ >so988v^!Qtk׮mٲehqqk6c 0itvve^ܹCN:5#\/;o1ݛ>.7RٜC);wDvN$j@uT0[dd'Oܹ_]dIJ7`VJJQv4'GE"X,D"^( B;;;@ {V!x-bY9W ݻwLTgϱc=@pu2Ƚ%IMM5ߨRJJRTjZVk4Fju:xUUS Ҹpĉ'~ᇉ'7+ 2w\t/ icbbh|y揶ao (_>zhj1 W_͘1üYP0{lsk)4甎=jp[(--57m䁦Y#G,--{>y1%Y \{c`zpm\ !((iiif0: ? !//!>NkK$jݣ@4 p/ '.8qŒFg 92==YxҥK͘ꬅb7ZG`@c\VV}7))ɰrV\t6}wO>fXb-:Ύp Hz|WYY٧On/B?nrq/zٹs'-c5tƍ4__ߌ}\-\*tݺu'߿EVsgrgՀZaS(UTBӧOgZq/z=z4->2ï:...&ʕf~6??'hkJ0;v̭ 7n0qzV ȯ ?F3ydnɣFRT- TuӧOol|7pDҚ_U^zT^jڵqr2J0ӧO?M[ݻ5S֭[ל2[S]4|OӾ}qoɀCMT*|PͧPL6tP>k֬~ʕ+yyy*޽{/5@NFRIUUU޽g6쫯~zYYFd?|~:vXTJ[t>v_~ oizm_]~d0i$kTFW=gݺu juEEEff}.\ȭAќ5&h4UVqWk&ݺu;x`9I%㪪*mT3ie?C$oT~-"\^ڜP:a^xrK/9::6'Cn߾19Cٱc#׌7z<ڵkaaa-Mߥ9[) ?oƚFΰ[/M0̰aN<ٜ< [x=jjjB($$cj׮Ƙy}{!:v1sBSN۷o#17oDuc|uP.]0׮]Cuc|P1Ɨ/_Fc|%PTTŋqrr2BW^#bcc1gϞEۗB>}!4`ɓ'B=̋4h߫چ 1>z(Bhذa!4|pÇB#F:t!4j(BG w6dc,X3w\e91&ѣG1saYرcٳg,{q̙3Y=qxƌ,˞:u c<}teO>1~'Y1SNeYٳ)S,{9ɓYpxĉ,&''c'LlJJ xܸq,^t cDzljj*x̘1,˒0<,^v c˲dQ\eE ''eYrbY\3Ç,b/e{9Y}U*U=Ο?__B@VWWgddDFFi4ZaIMMh4dcǎ;wn…BH$ 8p޽{NNNYYY>(++܇'qcIT* /((;~=۟wN$h4M;_6fgg877Wӑ8///(( srr9tii)i,hLHPvvvUUOH_P`?qDx˖-/_jP]]P]]m۶,'''CVV BHeee~\NWWWӘDE$&AVK $&I>&++>'~{{{b;88 j5.d3OޘHb9=<>>t/Oc7BJ2++,OC␐Ҙ,P( ~S"#jjjد_z555U("233%[Br<""bJ2((חŋr[t>kzN:L&3A ޽K7o~Ǎ\ cOnjbqTTY!'75d?˲!DEnpvG*FEEcǎt?y?'irppx~GGǨ(ikl4Bh;;Eeddxzz;bKnns=WSS۶m_;`;A阘E!39lڴ?DM:5..k׮0TfǎC!q={]~x 骫&Mdt͛7w&%%egg/fgg+VU8jcophZAWcN1if\SS17nP(\t)GiiO? (--=p'Ӊ#鲳Ijsss h juvv6/ٚXRegg7ZT2&NB08!k;wN&IҀ}Q./^QQxד"`SSSSRRӭ[7^k??c ݻw% jd̃'o=zTE1 |JRf„ DGG :e>쳷~<&{;|r+WӇ 8dhFFFFFOVJJFh4p:tngggooiVkDJٳĠv xyy٥{E QaZ4//Ğ<Xz|7.c: = H\VVV22K!Af1yF[] !0 BAFTʐB Ў`d2xB͍ק,AT@@1qqq{壖g}GG-,Y!mt,Jd]eؿ?O⸸8all,Rawm5'!TPPHU^#@Q*T5fnnn5l )Ƙab,3 J.Q)Hsc8\#^#y3B~ \{E!Tietq}!5777* >(//C[5B^F:Rd5fC&"\]]`RalB ÐWR X,fΜ9CLYEyy[H_hxO?4m6zh>j:o\QP^!RX8`۷g%#5777*jC[tdI$T** QYYIqqq ѽ@qII uJ5>}l0 C; SVVvM{xxq@k4^9s?FH,JX8`۷g!'lI;FΝymPJKKyk(* Flr":;;ۛ7B(--q 1 n+ERrD"ԩS؀̔`Yݸq]t1o>FL5Ξ=$޾}OjHỹ⣹{{{0C !yKſ_˨Ri܊js%c.ĠXz=;;8{ ƙ3gr1cQ `uxyyR{$JF,^9sx„  sxkʝ+yzs*̒ȖY2''L!6xxxPat֍Z[`rPQQ h0jjj5r)JWo0~]C=͒kT* TVV3 dkDԐX(2ǎȒםVI@9 նrO$-4xQpP{;KJJ^#G Ĝ5:ړW*mSlw5إ 1Ѽ3f۷Ŀ+xÃk~hRHB#X/IIIg&ĉC%x)EJk;:L'Fy\"yt6VP(q*C!{;>#b TH$v.erttkDGdž fTdrSVg4."ʫL4ˋD"XỹP(> (>|8L5v2rMC\=gJ4:]*'q\% WG?g)S:w ƧzcZ#`㸹Պ {$vpp(**2b4sLO4i4רJuccZ#W+UJJ0ZsY!B;'Y,r;EvPyʔxzzRaуZ ] (y*+ERa(Jy5jͲC;*}8:֔;ŲMgo4v4yA^Vk3#JbFPSSCK|&H H&,VKFP(t^o^#@puuF=<lބ $66h([*GBWyF݁H$  &Pai*`_t^^^QQQ|booooo%~k 0>L8rHPL5N6sθ8>jggg:ZUUetl={+ ѣ… L5N:? ]kt@^ ˍX8`޽{$2eJm^#@ˈh4 ^#@Qռzp^k 1ՃF5TD#{2s\Ǝ5TZ-k3mCgFƘP] O0 (6NQQQrr2}||bbbEo" i0 p̘1$k2e k7nV) {$Jܕw[fO>$L"|' P0zkd[ s F|@gpTZ-x z:O -C|X#c0ctpq ^#OG C> YCZJ5dx'ONJJ"={kz*^#*++X8`ڵkڴi$:ujmYLJ w<բx+ x z\@[ [H8UM#ܹs$󋍍xhx 0 u &FVw9niOz.L*\`x"^#^#@}^lai+y% U"WRVDK%}}̚K(pSzv[\V{y1L f= i$Ę@v Q(0"0yd04t MBAlmӘ^ZY\r'?Kٳ$۷/@_^|ĉd{$Ey9H{>|g1fJЂx"iPHR{:^#{v2nƲ>؛6 nӿD}vvvBm[~@ ί.#@a+ -K+T?,% z{B5;Ųpo[ca|WX#}P&MFm0 (=s)L {&g?<|v!4gΜ!_~E}PGhh+ 8cFCb@ 5&FkW6|~)"<zA\ZD!ҒˆPH0 Sl|+aBpA>ozyli10vw5kON"zeҤI\a6 ЎB[JQZIb6xZ d~YeMO>MbQFzq+H$kDNJ7ڤ}8m4OI{4:1(NpLIIk:u*x@}kL̞={xtM޳c5zpeiӘS4&]i0hoo/?Si C,:@ zmvqyDhϳ{{ 2v[c'W(Uٲ*/K&/yXgqs01&ow5RYY_5Z+eĉ\aiZ;Q.\K>[DӸVefqQZc":{H1ֈVvvviӦ5֨5WK jg_t ?qڴi5qqq01IIIuFzl{SrK4{4:]JNv~ uX7 WG)q6Չo^H$)XHR^.#@T"Hc&gSDŽ؜O}/g߭+q K ::hZL&{I"Hx]R&NFm0 (+kI0NgO djgXNzNNN$uƧzlbN8ICܝ ($!!bNѠPcfiSa5JRz `^#@prrbC&FYvBcGD#{2s\Ǝz8/Ք,~n14I4l0>j*qeeeãe!$㲲/cvGRrD"ܹsɆ@  Ra4Ξ=F>z$۷H5@* 5:;;ӋH$d:H$pvvRB~$bv';;ȑ#$fYv|RYYJJJhLoƘdX)*S$ }Y!iW\ˆi3gx@}yH ^#@FDѣGjqqq)2j8q"x@}D"^B5]vyabюa46Nm pZ5j)`CL4,Kѽ{wjqww#1t/k2 CU^#@TnnnfB_qXf- ybrdc~&~W$bvLC4@ij4;d <]~IMK+;\LL ruM#àADbSa7300e(ō0Tu5DBaf FXZYZIJ[2͝vs%Ӏvpa6m0bQ0;y>妬?9?$^hBi~ WBg_g  n}R0N Nx{{/^_۷ڵꇋX/[1~=HƩ.**"C[56Fuuڵk}||6lP(FLkx_}U^Jo⋏?}W?sr/EHH^ Z6 mBBBxHU ZZ?ZLnݺ>L&СÊ+fΜi Y~ÈahG6i$~'_|\.;Թs+WN:U 0K6$33]vƍ㣖5B dff[P1$1 8UUU$vt FcyQPPo48ӣG={t:Wj5fF{ , 5򍏏ڵk~믿VTPjjĉ{0zh3&Ih׮Fdd$Ooߞe(zTu*@qppTsĭ[ֿ 0 11qС4?'0MHH_dɒUVZ:}aÆ ؿDQK~~>k3047cAbaeVJUUUAAk^cc\~}ڴi h3&99dPkx_u^ҥˎ;._bŊ{bω'Z&֧]vT n;t@b2:*Uk(T~~~5bJ1Ο?|C5tL IDATO>ʕ+MM!\nݠ\vlm :_]j۷9;;/X୷rww7J]w&q'Nhbͥݻw-{N;1{.ѽLJ幹$vvv=^y V)%&&VTT3^#`zƷzlhgyf֬Y~{wM-zWZ*ihh(\20^F:F@@xXj} Ŗ-[>C1oҥχ-F C; 5}Qqqޡw}w޼y搞k.waҤIɵ.999k #@aaaFܹCbaŒX8`TVV0V56FEEEbb[?yhhw}V_x0=^… xV+|ׯ222}իWXboߞ s<Ab2^P5T3F`` xXj}׬YqF'J׮]WZ5i$h !BX5^^^onٲETC׮]2eJtttBBرc+!==/ta|䙝M{AAAu֭[=""^[HA6Nee%}:XӥK&jhӣ5Ǔ Jǎ0;soիKBAݸq'ٳgBBB\\\j߾}TTPzɫHU^#@quu Pm 9997|R&&&9,aBBƹ{?Lⰰz ܸqE]Gc%&&LcwڵgϞ#F C#@HIIF/]Db@@JltFJ+8~AӍU`Հ}qʕd5N:Qa56ƠA?~sq{wU#`5 ^zFFѡCkє۷oth"H^x%K%1'TƹsI6k,Wk׮-[!~BϿW_}u…^^^Fp-{^5^x &&ƈKYYٽ{HQ{F){l>j{nYY;uD)/rz_t~ sz޻wo{$> 856 x{PW/be7oެRxJ 0/5Gk|Zm0*;WK>}H\g B/~饗֭[vZPVV /f͚˗Ϟ=Ύ ˫HU;FNkT-O>dr\PDDʕ+M> yw膍 iܾ}5Ι3Z^GQQњ5k6n#t-!!a„ uܹsO>Fϟ?ObӪSZZzX)56Fnnc8z:pO&F{>ـmN߼ƈB׹Mk/b…V1c?11qذa0 }kׯk(Taaa5VGZZZBB۵ZޡC&&&0,`-[F"ahGqn߾uVGDD<|r֭RZK;:lݺuɒ%+WܹsN;6pѣG'$$hIszޯ_?{gϞ%}(qJJJn߾M:skb^cc60&LHMM5J΀ 0=^~H hq6Exx8;w\\\Lbzt}Ϟ=1III{:uʕ+;wʊׯ_rc!0;P<<<0k6ӧ;n̙+VСY*.YD Ў`ܺu'qΝΝG-7oޤ^cΝ[q 0ѣG~Vm۶_~gywU#`D_z޿{gΜ!1iU)))ukxk .!-_}\ F{k֬iMKk]o!ZlVkm۶-::ӣ5~Gdf5<8%%LBko0Mboo?snٲ?ˣJJJ,Yg-Y_;***;lٲ{N$%7o?;π(.vEQATzo EA]kl{Kb]c/ @FĊ%lpaܝ]>Y윽IQF#0׮][f ԩӳgO\rvñ/_Ν Yz&Mڵ+q&5bbb/Cb\+Wi4j%5~̟TRƆ!vESkȅdFv4Faj׮ ¨Q^Hޔ0([/2t9s,^͛77o3f̘:uj6my?=TׯObagg^#|nDQFFFk֬w'A{'O$\.!ϳgdJ*@^}QF/&..qFFFUTGe3f!^#{ {χ"nw:u0kttt| DoocǎM:uʔ)7Zv[x1%4lؐبQ#׈+Va888X؍h4"_۷o[,YRY!=pÇ'˲0P&~~~2,??KuիCd tt/ 8pA311AFH,aR""q>}Bʕ+FFZw}իWz؉55"׸pBr^#?QF#0 z_~Mb2*ʻwƞ;wG"--^z˧hԨUq$8RJ GGGc޽۷G6rss/]{̙،OK##wA!C ea Y233۴i-v""s˗޾o߾4ztx:p n"5266͛dYf=ZǏ{ոqc'N?[8O>MNN&1zr"qkOFDD5[T|iz)5.ZHݤGDD$&&VRe֭; ggoԭ[WV~-zzqqq{OǛ7oΜ9sV_9##Ǐ=ZjաC6mʯ:& ??ͭF0I&TƦM5"@J@NNNإ˗Q>Kڵkʼnא_6lL&cF&yyy'] |}}oܸ!Ać55U|tL ^xAb##+Vt]|=شiSFrHK.Xƙ3g~UV-BHLL!)133̬O1ApAZ1(5.^\1\reb&MkffƟPo޼yԩsjZarss{qQ;;;" D2>?^477 477/[X)gϞ\x^۷˗z5k^%LHbr ggg)S&--MDcΨ[Cڵk;9s0a3B D> ,@Ay ,ZB4􁠠o߾\&&&>+W,] Kbb",wsseximmm7m$LQq5j?3}tryY29r(xrH'O$%%]]\\@O>% a͚5䭐SN%xaaaÜ9sDAW)[l~~(i׈^# ?nffq\k111)_RT(-Fف0 rrrHޔ0Rf͖-[>{ٳgN4,--kԨQL+VXիWJR7oNbr Pccc9L̙3S!qLLLzGtLLL)wFFlŀ :eY( '55uɒ%$vpp8p ^^XGD8t|eo޼^ÇI,˛5k&`bbbnL!0)HGFFjՊF/Ѻu\k4aMɁm*?C$ٳg۶mKbZ]PaS,%2"@-zPkDzzzB׈ pܞee˖ '"WX*,/^| =<>5z{{!DFFºƐƽ{X.c|ѣsΑ̌[f dXƒ@JYkD>]vT6mڐF(5PEGGG=z8dD{BiT*6.UTaxyyM??N2dɒKQcf͚5k1p IDATP(F]Bk4h׈ paaa$bY E>}z*T4i9;;III?>NGW:G版M6{{!1qi"q>|OU@&iŋ m~ӰalArf ___Qr8pL])22R/rM@錌 Qr5}WT6.vajnݺuI6Ufgg߹sٳׯty=**SNYdqttaJP|}}aTJJ hӦ urf[FR ۛTS5?ϟ9s&W#H߆>Qc}>Dtݻ˲E~QX;vk6l@^ٰayyĈ4z9{,k,σ]vi m۶{$塡6.>a$/0`177$oCF5~-?}5׈^Ʌ5B %Xn]XX&!#Vzڵ#1z`ffu&nBzL&@G݅yfz+۷k1P((,@A\zի^FU0.](h4/_޹s'..nݺu ؈>`ffa5v lڴ^ b֭G OOO0 J#˲0P(J|N{{YfQ toFbF/< _8۷ءC;v8Z-`XwPQ]%,ۭ[tAzNp].ч=TJ~&5R}va(h 6l۶mȑ3`ϯx{P~Сu;v$1kDU0$q^^zYrrrddmd˖-#""D=yy'?<A:wL"e%5,Ydɒ%---CBBƏ߶m˗/3 o߾ɓ'O6MҥK+]]])͗_RGgضmx;vkr}6.ߗòe˒_Q*$oF]'{kDIadFB*UL_;)pvva(QVO(?>;vFV k,?\_ |0jz2jaԈ uԉ| XSfM{Z?N 'kŞ$8III|>N1 @tزe x;wk(ĹӧIlnn^{Z6''~KT~oKЇQ#z%}5׈^#|0Lvv6İN0Q~RJXCFǣsTF FV'puߝSSS7Ex1|p]_ZZZttsRRRܹP(*UޡCТQlNBQ$wD0 J#˲uw޽{yy}=zt$))i֬Y$vuu3f ^N< ~m $>p,-- sN۷7m^c.]aRL@$NzzSHlaaQ{iiipa;w~y/ߗlak%z5maaɣ׈H^#l !e_Kr*nJ055K J.M_"?++Mzyy988XXX)S۷/^9scw̙3gϞ)ХK{vڕĸ*0<==իWwݽ{wn@]aJ,yůE}5JF/?B~~>lufmmèADk׮,[$G_B vrssA8sL;F/|HiˏeiӦOnzzW秗5vUp6?#Kzzɓ'I, Q*ݺu èA7`Adſ?y}5׈^#x-uŊ+SLJ}||ڵkgll,vR+ذaCSH?KII!ڵ+uݻw'1kDsssVM"s111с0J Qб`K82DV^MqBAu$bQPY"qg̘Ab77q%** FIm(7n$F#˲Æ ;aذaxݻwk(Ĺw^tt4WVL&^V~ƻw(GFF822KէO4Ȍ{K] OnnnXXU*FPQj^#\]]AF/_mu|'Olժ4|3ݻS^#XXX0YmB $666~외|9=!3gl)LL cbbի'n>_ѣGCBBdKÇ #IFF322*U$HbXpmڴ!˲٫IHH>}:ǏO'Nܻwč7ƁgϞ;񢗗I֭1,,Lpq$8[n6.iiiQQQ$"""zGp-Z4~ 6mѣGȑ#W}u]pW^$VTXBbb"CVF?ԷBqssaPq,◰bŊѣG˔)#vRFu՞={Fau|;Ruy9r$>L.׈ p!!!$dw7DOHHH'DŽ hrq4i0}'E9*vZ{)+9 Pv [ZZr`> F/8/F 0LLL U^c={MbJ!$&&0j5gr AklҤIŊIVٳ'upDWpss r:ƠAhw"˗/׸BXr%Օ/Ȳ, sE' ǎKKK#qӦMq0aիWػwo5k֐88w=~8---{ ӧ I Β%K^#F0`{)طo_T*,!)) V J#zm+,M6Ʊ."@޽zpDWpuuuzk%Q7Fakٲ%X"q.^8uT{xxL4F/G100aժU0j۷^UHq Hw=z5jO.kDO0l28p x{k߿?U*FP՜ H-00MV^R/ѧO^c~H^#XZZ0kGk%5Fa "L&۷o z… LBbOOɓ'|?%Ck(믿}ݻw h6mڈ>ELQra{֢E 8wҒR/Ν;P^*JCJJʻwo߾l2Q0`U BåF Fazzj\r|$gΜI!!!TٴiS.]H\lT333qS VۢE x{nqSB`5k@A\x'-[vܹ~~~9,???555**j>XZZT*sy7 S_$ ecKKKЂakk͛7{xx>qyyϞ=+WZ%&j˖-OAa4P(`Dܹs*ud2Q;vb.:fff>"ɢEDpp0 BBB^cVh,^xȐ!W%o?@jذ2YJ%xFX-^8U*ׯl1\vݶm[jȑ#&D?vXbb"P(Q)QDWM&V]dQbEJUVby 4hǎOW޽{w۷oa%KYF5<ĸ@...Voəb8::QPY"qΝ;7~x{{{O>F/gZnGèqذa{-"B-bܾ}Vppz W^ ^F0j(a>Ph4#GXRaiD))) Z]PaTPR/ZrheeE6lUqĈ$u`mm prr4h nBRBgϞ@ 9sk\PP]7(|aFLv BqƑgƌ4zu[Ɓ#BX`k>|^Nb`(nzkPh4ǓXRaiDW\a҈ 4ZmggGb0f{FUޞ7!D)WѥK] Hxx89>}:Ύ/Ȳ, ٳgaR{ݺu)sN۵kG0k,5;Vps8Ĺ~{^c||<@1^O4"I&Z F0aU*FpUZ.(0ʖ-KvۓnDqQ]w@VZ {{{K܄=lٲ:?@ :u*9n1P((2,..NٳpXOz`IaٱcǍ7Hܾ}{8"3f8~xF8TP1P_sNשS;<@FJ(i&4")S8n8ah4?3U*FpUZΝ;G.pUa;tHb0a^#kDڵk08OOOqBe@:ubL<^/G [[[0 J#˲0P@$Ι3g^yhm6;vG0}t5N8QpJV(?E k׮رĶ܅ z¨\2^l^3F0uTa>Ph4T*,ڵk Z]P[.رcGgggתUR/1qD{F]6֖sww7!D)SѱcG] ȤI5qܔ)S赏(|aFeqΜ93l0͟?F/[n{8pD0jai8ĹvIlkk%$$  ++ ajjJ[₥!8qDah408PTXµk@j4"Pn]F2e(ҩS'c]DI&Q]w@N: [[[U|}L2:Fhw" PN0 JL&xHY!EllСCI`lٲ$ܹ3ԩSkFgj4DRaiDׯ_aj.11\P^==snnn$]6^cT5FoWNA Pti}@ Fۋ#^aȲ, ;d/\F/7ov8")S թS 5Q q-[Ύt@AUVk҈f̘^;h48]RaiDׯ_a҈ @z@K?AbJk; q6. ڵksNNN&!Kց0ڵkG4] IDAT zn|ڵkQPe2G$NLLIh"lڴ .]!L4 F^#7\\טB.kD/_0)c===4"YfSfJ҈nܸPA???FR(ҥKOOOשSR/NuUFaԮ]spp7!D)UViw"cƌ8qǏ>bԪU/ ёD,&''_ 4/˟yUwQiaB-b\reӦM$._L.kD/_0,,,(sN4"ٳg[ F9s&U*Fp Z.(*0y]v&1E6mupD@kMCJ*amۖv!O?k㸱ck1PjժFAidY 9}Idlܸʕ+$޽;„ k1c^#DP(`('87׈^aXZZRe׮]5biDs F={6U*FpMZ.(0yݻw%-^cƌTF`oo¨U$CJ,a1DFEqzbccFAid"eO߿?ׯtR_5v!qq̙{BqF;88p3z+^"""k[.F0w\g̘!l_%J҈n޼ PAFɒ%)ңGu"̚5pu`oo¨U RdI#$$v!2rHzs7zhz# _̘gYD:u5.[F/֭yaaa8pDcǎQٳdB![@| H]~\׈_aX[[SeϞ=5aiDq֬Y6h~7T*,֭[ Z]PqUaHlggG={6upu°jժ%nBRD uִ@ #Fk^RfM0 J#˲0P@$ΩSK ,_F/k׮={ā#B駟k3g^Onn."T*D⤤_NNN͛7zk ^"##k ҈,X^#̧yXRaiDoa҈ @@@Dzٳg@@."9szpD@5kj֬)nBRD UV@ aÆk8^&bԬY/(nܸ!RV~q>}A^֬Y^c^pF~Mpq̘1$V(p"qRRR֭[Gb''2 oKŋ$QF HܧOz~~r ۷F/!!!5FFFJm fpҥkגյ{$611rR ^F6/^*6.wn۶-j5{muօ},QbEy$VT4zٷoߝ;wHܪU5jEo}>5k qâEkСC g$V( qp}I\fMVՊܽ{wd;;;< DjiӦOTRnnnGOa8o`f޼y0CUH8[p!6. kkk{gF4|nDAĂ, $gF~mի6lj*Z*%%}SvdFaF^ aiR - '99y$vvv.dFFiZuȧFZ1(5޽{JkvdFa4h 5ӧq$vppԋ'}b0̂ y bѢE$F@VVV52'F4QFz$dɈGF)mM5jeʕ|QF) >ƅ  59 [@$ΥKkڍ55F)Z1(5~f#IQn0L @ׯI&$쐑 )?`f…TFCFprraX@!FDhDq,,,HIJlZZɈI!Q_ۣ^5khb /#n0СCk\h^IT*ϟ/`r%6\\\ ~ qQ(eQ^ۍ75"bPkLOOn0LÆ A7k֌Ďz1O)$ĂaEQ.]Jb^{݈F#{nDADVXF7n ǘ ˓I<`݈F#0qɒ%{C%Rյ5jg7JhԢ(nDQ^#"O#Mׯa 4lQX1J 0y$ňO,Yd Uq$F\\\@5j@=޽۲eK4QVΝ;hDH$߿/n6cii$ʼn'uFƍx, ,[5:99ŀFaA׸tR!C "qVXAbWWW~ @"jܹSt@nn. իW8N:u-U{888x߾}4z $qdddVhhڤLwQrew0\.}ETXؿ T*ׯl{˻stΓ/_}Zt{RRR^^˲ ={vɒ%ȨQF ###J 8E$և!cbbqN8P(\\\HGjj߾}666gF.]}5O?t7oٳGt߹s5j4{F5gXn.\lY"_\pA?ݻ7۫>j'1 ꚜ,D*$%%fmm-Ÿ_$ $X߿vvXSSSHÇ"K̑#G7o꜠O411IMMP(8qk׮$nܸ htK.xРAbUXַo%òM+Q˗---g`,_\pqРA$V*p؂}ӛ7oL"͛7ԯ_?**J竉0CM/Ƭ,$ OOO??+)SFtLfffzzÇ;wܹ :CR^>l0+fff?W _~1 **J_|3g+,֮]r*JtL~~~JJJ!Goɒ%%B^#c(9M<_~!L&={QDD\rsswex%))IG޽I(FFFʕ˱c`ܤI8F<~ݻwf۶m'33W^ʕ+{-J2k֬S^FNCƿe˖k@۶mWZ%)ءCxBSX4Q&?~Ėz56n7nInn. ΨqFx˕+C3xT0LHHeou֭NGLOn*vFZVHeA V={}qaaa#F wF?~K.$nҤ ~Ȓ%KHP^*JVZݹsO>-4A]Ԯ]Ȩf͚#ݓb0LJt 0={xzȈdǏa/7677!qtt4naZrO8##C{ºuk kݝr(_=z!333ÍO8ZÆ O8ҢH\zuN#zRԁ0> EG&:5F.kolիQPe2ٓ'OD /?޹sg7m?ŋk2d"o߾5\Rp$V*˖-qpIHHxkbyʢklݺ5Fn:tY[f U*Fp=Fvv6!7aЛ1d8"\pG¨^:gll,nBT*u F1DNaaaG  0+eYHcǎFJ-ZVw :ŅF/ѻwoW^-طo_+J JBBl=\׈ lJ8qP,aÆ 5ZJ֭[GbJ!ܻwի҈ @&M@aÆ"^w@ aaau( A@ 8b-G 0 J#˲8Dǎر#5kyf,\Ç!Kѐxڵ{pR\r#ŋ-ZDb&5"@nn.˗zm۶XƍkBGsT*,!==͛҈ @&M@ R/ÇC."5k;Ww@}؟ ASjFP@7Q@4r^&bQPe2ً/D /= G5kl֭4zY`ktuu bpu 5ՋJR!r…F8{Fh4 ׯ_Sɓ5j,aӦM5]VhnJ!ܿ[AҴiS_#FP$ƺixpu077(R4 a1D`Br^&b-[D,fff_=z}$n֬ٶmh29{5nذApvV*/A .,\Ğ'۷G=}tRRݻw?[JU\+xzz6jԨFbg*2J:u ciD[lq6T*, wq+ݻ7gΜ׿zЏ^|􄄄UV,߫WΝ;KvSӦMA#Gش(Ee?#BaddTT+VZVZNNNճezΆ zp,JpTw077gb_1bll,x&<666?I{\2d!v:E›\jUA4hEGGCV 6;VJ.MdEojvvvhhhTTR\f͚>~8%%ȑ#۷o7ʕ+͛7~ #Gk׎ā۷o˼y.^H͍F/͛7g͚շopoѭ[7C?k]_b@?~$*L&85|WTǏ̯~z޼y˗0/DF)Һuk%22R/Џ{%''o޼yĈ}a33g 7FބF߾}˿'B{XG:}5ڵ rPͣEv %+W>|KR1sνzѴiS=?=$2ddYL2eʔ177wttԩ0K,Yj*=|QFkԨƍ4Pݲe Pu_ZjEk\l4[b;w|կOTGAǣpY ҥKߺuK| }5yt8!(k\x1mL4I <ƿKXةS'Fa#""|}}{5\oOa˃U*K#<ah4bjǓ:Td f͚01F"0 SRť_~+%xڴiup=T :a<==AժUT* Nbb"y80  r^z0DFd29"&&Çfffb'S` r9UƲ;"'NaÆ"fb>|5ܹF/ۅ H{_Qe֭TF 5꼼@UV匌MHpJ*1++ K7 t ](I6m5.j5 ̌/(Ɋ jժϟ?'/9tPHH[hAٳgú1c*d:tu۷ov^R|q"qǿ5Tdƚ5k^t...chZL8aga4U&^"Akχie5=z`Y7B,{!j:I`` 0vX8dsYDDoNk; k#qy{{08EQQQQ"fbd2y]g>sС֭[E{ˬY`]qpH{ӧI̲l`` #c;w 5vؑFFFpC||Jbe>}L8\.ZHXJAZ-&DN||}8IDATe߾}W-h1c̙3_MMMxXՁ0$g|ϟi6[hРAPPT:4TLKKK>w\JJʝ;w233 EJ;tZd^TR/(ɊlrȎ;$^ ͛7ׁ0ƏFb Z˗/233^z…3gЙ`nnNڵFq]$YYY5m¢L2o߾x̙3;0̽{fΜ -QJF+6Oo_L:Xb_N988>|3-ÿ6Ḹ8hWƑB#HdaJa矯EČ aG3eV*HQϥ3y2z!4l;[d2z[\.4iR׮]̙aÆ}z||<'NX4>` "##۶mKb##;w ظtظq#1Y6lٳggΜIuFz!q'0} bp8p(8QQQ4hPRFb8z åE:ĉN;PسgJEk'Olժ9)((hbg$ SSJd* +{H̲,#C䣴hт^2Y9zhHH۷o߱cG6Ą/ 7׈ҁ8m4`ҤIE n} 5u##ݻw xfϞ=;v$E///^\ٳӧO'qz J#zw&J]\x$ׯ^СCTFPz_ΦMzA9r3Q"O< +;PS ^hBKb|HG~{$F Ybѣeʔ;)aWSc;N`Y͍FHqLF,z̟?ȑ$>|pѫ Ø) ׈^7^e˖p/ڿ^#Xkdd#e'N$߁sT̙3yPkO/eYN p^t)Rd9Bkա@]d&&&tg߯ZBFFD\NN !zHu ɓ'CEtuub(߿w@nݺ 1 DFJ& fCiӦdTL@\2_R$zpaxSNqʔ)5"`xav999Z"Qdd=?66vڴi${4_,  7o9))ȑ#$8p ^ѣY’C>} (UG6@>͛7ׁ0L" w@ @ݺuA+W~4_vLFɄvvv,b4i҄^2(+W +V;(?"\T !!rԤIęNtھmtQ'xNLNKҦ6c35\b"wzDcK|a=Ϟ{.*Ƞ8k|ϻFA8&p>yja,;JJJi1A+#Nj08!1:_h6ʠ\pAuDu Jzz:M/tߥFFKR:[ձ$&& qYr'//Os'XhZĐ!52(/^tꫯRkd h[TTTpR3ܜ/xIcfP8!ܼ_ T cq%AFukdP M+V 8!1>L{D6ʠ kdPibh/tCϦDwA寏f ^R@ B\ e2)Z>1dj(Foo{shhd22,222**J8p9yf!j;۷oq~."ꤤ$VhĺWϧ5n߾hRyy^kϒv~pDDVjPQQqA3335\./..y&yYWWCdZz^_v(1HRtihht/@ܝNtww(1bŊӧOFii^#:~ߴS % D`0̈=b`ppߦ/E #?? q# ]ݗ\XXHtaa / Je'NJJgX\\,H[@8۸qcEEyyĉC+`2N:E_eddh40mĒ۷DFFAT jY1??/J Ρ>x≅_ݾ/ܿ}ٻw/9{7ĎHq`kkA!"J5"bAA݉@wD\r[o /5+DcO~B $$$???,,lŊd&'' Ceeg}F CCC}}}E i֭k,))qטOJ;Zj}||D ܾ}ŋwyGx_}UE-IS _Rn:hb{rUV-ry]DBOVձӱ/|;)?FOJeKW^%/nݺ%nHN#>>I$ݻw=z[ܨillx"GhI\\\SSX8ETǏ?"S^^NPr]byy9R4##Á_N:s@&X__O_;w...Nx Css3&l}%$$H$zp;Ve2Ν;bЬ;g077_qVӤ: Z [l/ MZZJ/ip N{zzITXFv׸~{1--MZꦦEI?䔜&v,[l^㧟~p.V*`wq驭8>~版l­8p謬,u\jU""""""M>O ~555=>>.P-J;\u4Df͚5k}cqShbh4Fss3cccE.EEEm2225ұ xqellhF#}6Xsj%Z& x/2^s R^#R,f-eee 2٥Fd'pʹkt TVV 5Ҭs!:41V#Er mGXUUE4qbp5[[[F+WõX,t{q\%n< L5۷`wcsl59k[T*z;)++{嗉ɑQ,Ylpr9M [[[Q 7JPfqkd^^ctt4j͛5VWW-JSSSxqe||\ K$61ejjhB`0a^#Kp׸w^5kdX^^pnT* 7_\l]#"ibw^ȠTWW 5Ҭc^#b4ibt:52D6l`kWv)55^cMM v.ƶ6%- @l{pd0 y/9^={tGu됑AJ{YYYDTR^||}͕i4y B!y,mmmW^%zbbBZˎAFukdPF#MUV1!G\nk׮vtxu9KIIqAdddMՊõ0 ORd0n_sƗ^z7|y BZZ5LST4nNii޽{˓lUEPĠ7:k׮ͼFz4ȠLLLXz5a5 3ׯ_RJJýDs8ơ!B"c2hT*V0%8ko~c5fff Q cٱyf5VWW;kT*攖ٳ輼<3<3:::>>n0fB7>+0<<-P-fYN52---2L& qqD$C&IR "g|TSSc6#""%%%>0@Gc9SSSqܓO>GFP*gϞ%GB^ڱU0)dŚBHIIeee^:)))99922RcbccQr*8l6{yyeff[NZ Ng6jkkZb01:::))2ܜݻw?3յPloF@@OSqCdˑ#G;?G?ƹUV|ii;;v,??_Z"##hbYvp1\/gϞ=~8i@zAhiiw̙[?onnnkkKJJZ\\?33s鎎8{===}̙θ3gtuu{͛o^cbb&&&ѸDߺu+::z||kooott׷aÆ KGFF>/CCC֭>< E[n@XX"vww5k͛_FvBDrdbbb |tgMz0}l#+W -- +** ##̺LD|2dgg#"9<77?SCO>n݊/^|D,**={kNOO>|۟gDܸq5kܹsH#?cDܾ};\pSŋq۶mP\\[lDK.!"9ؽ eeeH6F@D2q7o UUU ;DWSSĘ'𒓓ƍ:DLHHDFDfDVDܰazD$HND$NpWW"GnD [n!"z{{#NAD !D$FFF2OF ɡucccFɂԉ D${ NNN"7R033fknnU*ࡇB?;#H!"0X|||zzz/ Ytktt ={6\d{6\Nh^ WNNޫڶm5\xyرc";wv܉dj>}_9DzSSS+**mq<ϓ5FRyLI4Y)x'ƀ\.y yRRyM~ *y2<&-'$ZZ<٫h2󼿿?x{{>><ϓF|}}y'7 Dτy2ִrJ!!!<y>44j2h<"j48Vy><**j2@~㰰455mmm>>)))qOxzzQ 33{j X#7~egg 0'xc"444ynNNΘ1c;o$}YG<~Z+44眜۷o;99{x---᳛J sC*;vLNNn<?=D:<{cFDDZPP:"߃D&n!%%eO{骅!nܸ!X; y8j#nMsVOӧ"l\$6?څn/Udee״uViH9 Z [n]ݻ#+x;w`>[VOO4yd,sqqAڵuk|}a˗/VDCKK+665޻#+(ҥKFZZZ#uMMHHha<I}%%%}oҥ?Ik.())7p ̻oau@Ot_^^ۻwoa.ڤSLAƴ)Zx-|}ݕ+ӧOށ#D`EEE͚5ԩSeeeIII0UZu@z_;>>=zh׮]cƌA{|hfVQQ9|pZZZuuuc^=ϻ//-1B`l W!@~N IDV)++5!/]t@~,nywZ+aDi>|Xl*AٳgJ.]4(r+(MUߥ:!x-MwY`kAzQCEESSS*++խ3lذN:G ju?~WVV&++о}{MMMCCCcccKK^z(AS/UUU׮]uVlllfffiiiMMM'xDn1\-[mVYY٤-,,v1b \xIAXEEE{O̴ qqqMi-y`4nWK\\\ctDLso90  `D0 0 0k(*333>>>33SWWx"++r_|/s8/_Ԓkjj>}/WWW}Y[[*<<<77WKK_ ˫%WTTDDD|ESS_.//ϯ%EFFhhhFFF֒KJJ⨨ZrQQQtttIIIǎZrAAW:t萟իZ/_bbb***ڷo/TVV֒ssscccڵk/966]v>}QSSsrr8 %I<8a[$a*YYYB2`K%%%JJJE0EpJڵ( ر#EQ>}hhhP ֦(*##KQTzz:@__!EQpQV.](a̍)z-ĄxEQqqq `Q+++$p˗/0K~{{{ET3C (ѣGÇSu}ȑ#){.`̘1Eݺu 0n8k &PuU5+WSL( >}:EQϟ899Qu5ީSs̡.?>EQǏ,\(ooo5 Çdpk VRPPpƍ7oXl7"deeX={PbKQǏY,)JOOgX(*55b>b:u8uibccY,vbΝ;GQTtt4:]QQQ*a6ג)0bBTT455;v BW\0a‘#Gl'H͆)}}}999l6YYYl6H>{, ##f;wG$ .l6SRRl66}e$fa߳ׯ޽{7oް7o"=l6Ν;ׯ_#9..f߻wffD2"##lǏl6l6aaal60y%͆_xff3$gϞl6S6 r(Pl6;<<f#""юZkݻm6::ZGGGCC_0REddBJJիKKK322n*/\Ю]Ço޼U˘Bdd<>|xΝ8W3AAA...vVWW i seTdDPn۶-@FFrv j|hii9|𘘘GYYY :4::߿gϞC z䉵cddd@@CDDӧO{5x޽{4(,,,((˗}8p/BBBkggٳٳg ׯ_HHȋ/ˁ'(((,,6000<<|РA{~iDD{cbbbeer֭[d2X^F(o$#є=zfٴÇ3"4i?۷aÆlllGݘ]gŊsLkڧO> 88x"Pʉ{ydd/VVVgΜaٟ>}=`0dddLLL4jcaaaccsQQ)i̛7DFF2w $IB1[SS7~x*((6mϟ?}WNb ƕ 9h 6 K.0~fa|:t萐4hOi^ Pv͝;Vu0B@@ZAHA <60DFFFbb"j, &$I.K*a߾}+Wݻn*77-VWW߰aô~)SD.2 }PPPBDӧOeccc'4/p*@MMm>} p#/ 4 b?+==`0ƒDz^^^P믿Ds0m4СCa0===dBN ea OdjjD楰_ӫFL B~A0( ȼ}}} i>Sm޼"tM>9bX~" SN0,VXX FaffD楰$ #1t20 4 b447OܠdsE5j,`? f@B`bb{" $$$%a":}tz Ϟ=C~";;;&=&0Ejj* 00000 B?ѧO6meMMMi\p̜99vXX ga dBNP>a Ϟ=CaeeD楨ҥKPVUU:u*`ĄϟCy͛7a 4 bMܹ`$'ԃhD(ô~Ν;yLT' a TQQǡlbb{" $44 a"???(L<^}0bBhh(Qԯ_? &ФFC\\t")OPf!---[@?LٳP={6sҤI rss?|PRRA:-Id2666Wt0M@Dȋ/a[\DV:}4@VV[n@a0ݺu[+?nhhHRիPVQQAFy%ۗ@D 貒 &#]vEy%@ /KNN%`$D@GG'33iyHxG mmmR Obm6i>}};w.Uk=ҥK!SSѣG訩ѢO WXXѣ0xΟ??22R^^.t C Qqq1Fmbb{" $,, F߾}i=}/^|!"ml߾c߿4#*oܸm"HXXػwlkkK\|hUg??[[Pi^tR;_{ZHJJzvڣG4H LO Js73gLs=֭[emmm6-0SN=z'jymF˗| {{-VVVzzz;vl׮!ðbDHxx82'իWdd$4NMM,*++G$3***8 D{f^x6m}aĈDxhllܳgORVV5kHzի*2g̘%$qOܿO$LOf(kkkرCaZ?'OD~dFڵ+2 a!@qqslbb{" $22'Bڶm3Ya hGW^ϟ 0r޿FnݺY[[ ܔ2+L]llllllP9sL($-yH(;;{Pٵkô~||| eggZ :::laH'N>-Z'0nݺ!_+%%%(ރ 0hdO$FH"QVV>|8`ĄW^%''CgϞL6 4 boߢ{6mlXYY|e+++ɓ'a 4 b7o޼|ݻw&Q6mpEL]z9|(${" ~"az,DGGg"{..]\`,D)2>}TiiסDDll,2Çc?!~"(+++;::ҫFLC~=z0? AaPRԩLDrr2)RP( 3-jӦ ζ*2-Z%$%':uz|}}HL eeeoP=xô~9y.[컞>Z}d033Cѷo_a*--ux52QFIٳgvvvu?yx$OTVV6m s} 呙tR(>|XaZ?}6w&nH[[{۶m/_NOO_ti…*(^#fB(++CI' a3)%c̙###3g///xɓ'ATO:::ڴi#61o6j(( 0bYjjjΝo۷ݺuKNN622Bu >OgqqqϞ=e#%0-[%$['j|}}Q7Կnݺt2p`xɓhy)H(33 ;zô~:tM(X[O$~"|ю_yPOtԩ͛7Kâ`aa c4UVVc? c񒿞( uRTTn۶-,hjjÇ &r<(+))ջ #y2tޝVAIɓQ7PVV|`ddaYJD!C4 >E*8yΝ;E_k $$0=QFFƯ N:/H3{vW^'D̛7Iϛ7DgΜپ};z={"ruyy9z DDRR2ӧK֭[h3JvvBO*((ś7o7S$LTTTDDD@YAAW^###&z! ӧOoR71cӞH"z mllaҥK.]ITccc\`hhdddrss* wTWWoP`оh0M}e֬Yٽ{72k׮1׭[z"ttrٚ(wfffgϮZJZa9@ #)$''#Ø9sDbbbm۶ Ν;*::O0QQQPVPPWÇl(w҅`ٰ@-Ȟ(22ʽz0 bA$ɖ0ϑa8;; }qƌP?wô~???(]a$c=F$ 2YEEϡ/fI!%%FNNDhA"DHee%r)((?19HIIɁrΝ۶m vJ?<:No/$=r޽*pS^7nI'@BCCa8;; O6 .\~Ϯ]._ ?[ODD^a_*++QT"a5H 0>}Dbbcc,//oiiI>1!--ӧOP600`nٲ@ F Gmmm &pS8:.tqqI'@^x Y(==}ʔ)P600tô~vݻ72 !|UVV8%%%jӑab?!~ׯ_CYAAH9?>sͰ@1RNXX]٧O#Fܔ1!###77zzzLwwwX  )˗݃r߾}G)pSrrr={^AWWWWWP"IDHXX2 gggaz'Bի"پ};mذỞ>0E>}a'z%y#233a|I}ݺuW2,~[cM$1R΋/ܹ5Jz!"0*2{" "<<0=QZZkhhxuil۶ ڸq㷞' CȝUUU(*jI #??I%&&BYNNk׮Pmmm_ Aah:۷oCcƌ)999sss酑QuV($=ٳUTTrrrZFff&@QQgϞBdDiiitCC[n@?Lg֭(˷?QRRғ'OJJ'/^?{ =B~a[r #)dgg#(((OtCYYY9..?TQ^^nii k֬ٹs'*ꤤ$(u҅^}0bBNNiii٨0ؒG-U/(z٢E޽{$Avϟ߼yvvvcǎKO$-cǎ-^AVVV-**jӦ h+x<λwn1o߾+WDѣGC-H9^^^Odٰ@4d…ϟG"EQ0ZݐJKG!d7o@UňO>!(,,Oھ}{MM *++׽cfff~~~<.{,LS)))F_>|4Ĵvݑa9s?@?Q?GEEe+Wd@O1!//;v$a 4 J~~~SLԩS/]@͛7>}hС^^^}i.E[`hРA(MH? ɓsʻwf̘QcM_z:7+"}+C֎2ӧO3_jN2R''O 2Æ wwww vڕ+WiF 9emҢWPPPDڵ#/_ $IɥO:88@yȐ!?UwhuB]]}˖-DE``˗0ydzH' Da"or߾}Ntuu:T]]- Dɺ_i{i e8p4XO$VX`_|Aedd,[lǎnnnfj` 0x`dB8 $ OFRXX 44P&^޽{h cʔ)4*p8djhhЫFL(,,DTUUe˖Ah6^|ٷo_(}/_رc菁7ӧO;GGG)ivOC>}Z+WwD~?~bB~"ۋJCLk篿BqICIASi >QgϞ5kxyyo :tzrl1aXZZ [m FR(**BQ^^D+:22甔@` ɠTUUU`hܹ?त$ww njyfkk&5(&p8\(3̎;ҫFL(..F bɒ%@$84[YYEGG7TKgz4iph׮IoڴڵkF &Oѽ{&5(OMA~"ͯىJCLkgÆ 0|||GD&ĉ`Ы55 >:ܼ4 <xomll޽3O\..\?nm<0ͅi@YwaZdQ,xA? Dm@}TG(yo3ѣG_jUNN0p=$//nu0Bqq12rbѢEв @àԵkW(wL0aطtP͛۷_s EQnڴiSLLLV\vvMtH5 ,$Ai#33ӫK,ZO6 "Z]***>|x)rP&IREE'U_K)**s΅$O8AbtRPPtСۥQ= .7OXNFFƖ-[?^SSS#{{{//6£GΞ= ÇϜ9Y`"~BtiE$s+j5Frr9s G;bĈH+4$֭[ رcL___hpOd=Qd$3 > 9Ν;_ʕ+c>|8q͛7׻\hذa0LMMQ墎=a00˕ܴLu5}_'N9sDPz۵$9c wwwEx<֊$IeefuQQQQ]] ebٰ@J.YQQnl "p7/^pqqudΝ;U__y3gi ,"⛼`4jF={vΝM6EEEUUU?qĊ+~d&&&\㡧]N)FMM EQi`0#UU2ֳ^(???77ZmvիW~CO$I"FʩB~"yyy-!I)F3@DӦE$ Pop.{9(YYݻ/_`iQſS5L5D#GVIIiϞ=kkkѪiCqQWКZD[Z!222K,ILLk%.++[jwE?>AE2uS]] `?(((ZUUUM闈YVVwޝ;wɉlC(EQ( IjFYYYY!BڴiRc޾}ݻ{33͛7O4I߷b )---[_OdccSo8Z M%&&*|ʪǴ V^ ȑ#Lu IDATSvd1rHd[nF]G0`COguc`d\.}-RXX؄u4'N6tP//zQ|KIp8ȐdddSI(ŕұcǼ<( $'w8f {77K.՚91nܸ͛7ѣEuHR'RWWG"77 gJzZtYLL̄ za*7oޠ߿e ~ Xj*df^|ڐ}ʳ̨Qa0aaa2x/^reٛ6m244( yL)"{xOPj왒t$\\\k]pƍRI z<\. 1ydX  RHN>~匌 zwcƌAI... (((,[ CDVӭ]zG٫I455mV1+W2r!4)IZ= yA&M0a… ?x߿ߺuʕ+۴i#b1 SDѵkפ$('''wܙ^}Z;3--Gׯ_lor` ($IL8H 111y?Hܹs1(7n(nʟpBtHR'ϥ[աƱ~A?zܹɓ'94Ǵ+d^~!'3fLNܥK![N;tPSSSÇyl)SHJ?BE'=zBfff#ﯛsر(bP"IڵkjC#={|cbb,--Շ^n߾ /\P־m۶#GwkȐ!\#Եr?5tC3LLL?JKK[xqk I/kVXd޼ynI;v,Z) VN:=z?tww?L4ʺ4Ed۶m\fѣFsssKHH@wqȑիW^ZEEF%yyydR'|o߾~XPPSJ*\.ܹsuҶk?\bEϒХJĠAD'Oզp8:tJpppz*z@2e Dxb(3 q $TSSZijjnذaҥh8~ܸq֭kA5% 񻗪'(jܸqީM6ѭmH^***z=zDQtVXpUUSO Rx֭ǥI_zeab0jjjPF~qC^^~ժU-ڷoߎ;G?~\d߿?33ٳ 4iݺQTTD!'''~"@\\\2224rEQQѮ]ߒZ3e͛:thA%$I>}Ve聢(MMϟ?ӧϋ/RvS7r`wRyyyl6hI;wNJJU޽{BfʕBQ|Q5}Ziڵt+E''˗/O zXXʶ2jaAjDGGNj/f'`={~CV|EG[[{6lXvmsϟ?;88\paر-^FII <@rr*$iaǎ[xquL$Ih~$A"66^}ʕ+wW\)pS\.b2uI+8==1/OOOi~h$*3iQ5|t+~wա'n49sGQOTwkte)wNګW/( C`CZ#F NS@6ma(((`?dee!lff6` .p=zܹsGH@$I>{^m{o޼ҥK5#˗(O>Yস\n^^Lf+%11JԩݻwZXVAIIIyy9}BrtttϞ=]6~xU yyy[l j\\ 1++zɒ%vvvtkOޯ_?(?0L&[Ȧ&??IaOx-z5ZC***0op8]v2554 q#Iٹ?D1{l/(('Ԅw`ʔ)tMjCؠznyƎ[+Ajj*M( "22RFF..\sN(;99YF8ziiQii*î7onaām۶mܸAѻǻʪHXOqܹZ@NNNJ֧VWW#.$..ˋ.rssav?ѫWP7 ׯ?q\KQTTTСCi;?28UQQ9|)SL&-˽ҥK333۷Ϙ1ܜ}f̘B:dddLKUӻw﨨(.koo/VoZ  E555dmڴO닔SPPHLL[#(,,bH@tٲetC'Эذa|e%͏Z\R󨨨l۶mܹH˟r;v@y̙k׮)e&Ib^މ b P555ftt4,5k544ׯ_ddduuұJ0pɴ7}{ː  DdӦM^zEBO2khSNfiQ 21k2%KJ:*aJJHHoH?f8* *=]Q bv״3f=st k:u]%!~W`G{_n'"s-фcoߞh>J06664157 bcc2α:aH̝;W{hqb[x1у6mZh4S)y9`~:;;իK!n@ =dРA:u"ZmLF;:: a(ԨQ&RzxxCVJF899 Ø={ٳ囙8 g9{]p!C>}zRմΔL&jFΫWv1(41ʕTOĨD,3\;c>ZMw6rS YZZZ[[שSWNvCrrrFFvvvRO";;&ׯ O{%OTZZRT*Ջ/^n:ٳgU,\OD :[nD+J!Cr:D>qē'O8qJpDΎ&Fjդ...9`kf(J:JoehT*Ull7vEXgΜ13gΤݱsFe]53]0n͚5bڵ&EEE555i֬YhhhddEΞ={Ic$R}"fFRHOO2TBB'Fi#HOѣݻw;wݻQŢED4 5[3WaÆуh>Ot&{41lllƙdL'N\d =@FIEP*691c)")cbbbٚ";wG}RS)**'`r9;qmd2#/_ "҄ra>[3kCn? 7&}"scx2>M8#>|8] ry&M6?<ƍر B&Fե DGGH 1^bΝ;ɥL& 5"16mv73qްرcΜ9D5JPTTD( cP\\{͝;wjOʫVruu16qILLLKK#AH.'bkfmrssibв#!!ˋh>~LÆ /^ܽ{t2>M8COAؚ"#F#RP Dl)H `c5jԐ" HؚXZZ (kf")ݻw>>DO.ٹO>3gׯ)Np9s<`lٲ2>M8c1rȿoD[XXJP4k֌hGGG}ԫW 4 -[`;iibJ s̕BD]۷u2a|pp0Y ]v 9L2yɓ'KPH ;vDGG7jԈ'kfqc_&ٳ UXXxܻwOW~h{"F""41RSS))) 5s9ib-Z 8}hW%4'˗//ф3rؚYQF 0hsss!CљD+Gclllffw}gHM5kJk֬)n@C鋌3QrrrBeE} (7oPJ̌4M|||||#G|||:d0C'M4i$zf&8.99YyɶmhFEEmffF+3`|RΝ;۷GȜts1DwBzyVf>ԩST7oޜj.]4o޼QFJݻ-x"$$$,Zhɒ%"kVXQ'aZ3S켴0෍G~1333 !!AvuЊ:tо}{Cc c&SMZjIW.n@Ƴ LDqpp~N2bbbƍӓA",XDs$~囙8 7N޵f6BnJ7Nȉ͛6m*ҢN8qСBrr۶mhכLq~a={F?]\\DY3!41 ئM ':666Ge1>j0"V\Y'W0ܚـ3f̠AV(B23331od2e˖~tR" fBNZZ5q B!p888\zU'C}( Rikk۸q-[S={H"L8 of"h!Cⳮ -[L>q-\Cܹshsssoooħ7|Mcƌ!:((`DO>>QݺuW[3*EEE41 خ]; '\|W^EEEн{+WXzuh _30C%Z`%sss:Dą C> ڶm{aT*vP:nݺ41x*JqA/\.: !te eT8qߟ7o~]&NyLLLf 7o-ZTi4 Z7|btS?_xAq0rZA*V/^q'6oXZZ >>>.\05k֔[1c>h>~rʰ0[hqyՕ&V} d2!K'CF!$U[+!ajj*p HA1L/p~2$ƍi`zJiLȉ=)ݥTbFP/^ҥ 'bP֯__'xaZXX 42P~}Đ Ð011UR{{d(!H/DCذalٲJw5-,,h>ѣ=<c}>7nܸqLq} aڵ+ĉWXQÉСڵkצM۷ojdC |LxA\\э52{Vhb<^z-O$H7n`Ƙ4Uׯ_e|"b!!!!b[KKK:5i҄8Ϟ=[x3|zǏK7jԈ&`iڴ) .**JpDF{B;a3gΌ());/AIIITTٳ?Qz)v\yq%%%k|7YYY۶mWVMTV^ѣv &* rnn.oii٩S'5h --MCzz SF mPj}-~5jlݺky^5jԠ իkDd͢OCu2>( m<==:D;hd՞bQ,--L$~޽{߽{wĈ#wx+/F͚5_Ν; 'M ggg(B .hVZZ*J$sQȪUĊf!)DK6#F]ҥK5ؿw"O1ܹvsf͚!TJ/ϗ'#####G7]\\w[~XYYuY2ϊgG*JRx ѱD7nJ2L0..bK{{/qW\ׁ1^v-M0!Ci9vwwU6lþj''Ç///4331/D"^@*nٲm۶"TG|0>HƍibԩSGVtr$%%7Q255uss{|1LLL\\\M^zJEV@<8h4t˫L&oA>J{쩃_?wܡ>ښ\D )M ;SRRL;ݻw񉲳 ,kk!Cmgg1M WWWv^R)RT BVrHd|nOx N8ARݻc|?!yRZ'bPr9M e|SRR F;={233 d(kkk:1Ai޼yNNnnnڐW"TZUzDaa!=P(؁DD[رcD+J???uZh!!l?P(hb\1hwww61+~Z܀zȘ1ctլYT9ZjdLТE KתW.RT 233)6x`>-'>rUTm?F͛7>|HtV5j 'bPhbXZZ *55uDAؿիWCFM(J:VZ3חүڊCϧ%̄*..{nLLÅ0.]1nݺ5/Y&k4,DϬY֭[gjjq\@@?\41r\R ۷Ҏ;}Z^=#d)) *''G"xzzzyyVVMGa2V0ǎ{yDDDdd… nnn^^^sa3c,HT*Ս7"""v%d{5lؐxbӦMIq?&Ν[reHHH߾} SSSY9sݶmg۶mY&ڂju֭0j¢855\ѼzcZ677W5YQQQZZ´O%%% KKK E~~Gj򨛗1rynnGjD"rrr222>Us'T*UEwަM^|yGrppcf͚ejjڭ[7S㓒0ƥIII:99=NIIy....SSSߣ5M9իhZ]NG.]XXMB[Ggddљt^^{tVVV9Kׯ=j*UDZx17uy2I5lc|=GEE@&M01s4k c|-hѢƍ׿c|5hӦ ʕ+Ю];qxx8tc|%o0ƿbϝ;~-ٳУGzBK~~~G@~0Ƈc81{!C`wÆ رF1޶m=ce;v,xƍ0~x ((cf0aƘBCC1˗/I&a-[SL/YO1^p!̜9c`5kx޼y0g9s`޼y~ ,X19s&,\c}۷y۷/ܸqyڵk<ϓ)/_y~РAСCҥK<ϓWH.\y8wٳgy'u9N>|08p'Oy< =*j̬&cnn!I wi rT|'&֤k =2mT*F|uãk׮-[$iP&*>ү_>88!1.,,Dխ[cruu"1* !1B yOKKCTyONNF`_|j޼9811!ԲeKqBBBugϞ!ڶm1~)B}'O "^ÇB]t߿!DhP݉7ٳ'&B{&BoF_ƍ`w^"˗B&>BhС/"FA| ШQO3f )B#8}4Bx'ODرc!9r!D{Lk::R3gWJknPL Eť?+={6|Jϟ_{ܺukE}Qoi: nC&Cs窫p-Y;YGjnEEE*2V;Rh'N2dH-}}…V_TiS:yu Bvv i-ү_l_O:U! fho?zHrT5ªwUˢ#NNNb$?\q\cN_'qHRi8NqoVB3gh1D`{?-$(uBff&_璉(>Ȼgʉ__k?GqcǎŦZ`:Nhb*`|ӯcǎϘ1S0rz/zUٱc߽{wQzj9%%^}Q?_óD. WlU3d 5^UKRUT!G`5|^2`(/Pկr<<<puuqF?W_}YB)ݾ}b?zՂڳD[ni8<ӯ竐~2_W, f]ϯo֍=ϫ?Cfff t -((h׮baaQEEE_}D51#K(Gs4irڿNqqƍ۷oߐJu#UVե̆T!OӰ0!nu̺Q{B\]] /++jNCyyy={T9ƍiӦ7nݿΝoVեIMYRTTԾ}{3{nݺĜLѣcǎXbРA|جYѣG*߃7F_-=` $}Ԟup)S6nܘ.J>|>I&U5(p6 Lz^ <|p51)K(EEEQuf͚L}Qc;0T>W/HBBB]b0T>_8nƌ W3gδK={ܲeKM}]u9ؾ}Jx͛7[hQ`]J4Yʺ~޺\ ٳu?h2^Ç9sի>,--H$vvv<Ϸm۶cǎ})̙3Ǐ믿>}YTTdbb"H\\\[hԮ];777TZv޽o߾7n<{P*Az: =T:FBBBzf 'Ν;wk׮ݾ};++ښ!C# 57 免 4biDAJ`ӈ  AA*M# TFAQTTTսϞ=sγgϚ4iB)|rjj/_NKKsssIWTT=\Id2% WWWE-Juzz.//W...ʕ+:33SI^r%++ٹ&]RRrl%]\\srrxWEEE׮]SԹJPI999)ꂂׯ+|%GGGEw E]XX3335 :ϟ722ݻ7!D,SJ !&&&RB)"H(EEE JiAA!ҒROBlmm)ل;;;JiVV!RAqrrx<J){ӧf?yҴiSJG!ҔByLJRz}B/4))ҼysJݻw !-[޾}ҪU+J[!қ7oBZnM)q!$((RzuBHpp0ի6mPJ !Ҹ8BHv(.]"tЁRzEBHXX… N:QJoB+B)={,!k׮l2/![n_֣GJɓ' !z8qһwoJc!}9rү_?JÇ ! (,,422ڹs*KG\onzܸq<=zR:vX;F)3f 'N='ORJG<[IyĈ<_Ç==R|ff&Ϗy-[>><PJٽ ӓJ󥥥ʕ+KHHH5jԬYx<.<ϳ se v.YYY00R|8y~ĈӧO| &PJ:IZju…Y!f dAAAJJJPPGEE#+**ZjE9v.\(5PuQQq'Ow}zzzYY!$///==ݟQ5jڟS9뢳e2Y-뢳+**=zNk\N̬ejFB222a?k#<Ь]dbKKK)Ga~fj;uҲ|-[LOOd1@ldr9~k|aȗicccBHEEEzz gKli"VVVe2Yzz:[P@;;;vuu%Ht{afsrrj_f=+Su?yyy/++S:GiBB۷ljjv"BH^^ѣKKK4i¾3bP[ZZl2??ڴiQQQ&L@off&!m۶ᐟPB?{l F\޷o3fBZnmjjʆʕ+fffr|ʔ)[laĄӧOFDD|嗡ǎk޼98.,,D0vô)h~##0v4aݔⰰ0kkkƇiVqXXabbbnf7AL SSӰ0솈i330vtӦMA$IXXhooo>>>ss0___]LUt-,,@ΙeXX?耀lI+++4;_q5췱~*+-- Dsyȑ#J[l< ߱i44ؠthhhrr !d~8~8!dȑC dNbPgϞ8sxAwwwHLäfqfhǎɇNMM(++Usss4{ MCtNNN-Z*6Pgggעe2YuVVfyh̬Eꌌ w^}UBHhhh۶m#˗ϛ76DDDQÇ\ڹsgX$%%EDD'''ٓ=e^>3Аf!nݺxڵ y/^!q׬Y3???f AJJJLL {oݺuӦM"D۶m#G΍7vYQQq\ZZ@ 7w/_D"ʘ܉'X^[dee]~iGGG: :zH5hk|7۷Ccggyyyj홖H$j,_3vX &ݻ7@ 1i-CbAF: FDkrMLLkD0"H|D2###رcl8( Nffkטvtt n< F45=J?kD66601??_^$++K#˞={ƌСC}e5"#$p]r2(--kTPEFEE$T;Pz IDAT*ERZDHxޕ#0Vadd=zAG1p233^ʴc6m &AxFڻw/;vDﰲB{666LK$5/oac5>x >>^$>}Z۱h///XܪUW^yEVrtt(Jae&Ł zu8rC&ט4usBuvmG!ڵkwՊ H4k֬%KB)42 ZDX).]>"%";|0߿"_unj1M-tss۷o_۶mOFFƕ+Wvrrb$ D$Qq* 톆vV[QiǏ>|lOZZ믿j%'''H ẌOK׈RCD\QQQ^cdddLL -[죏>nHZ|ĉ۶m=W^mċUTT(zt8: … ?cl !&&&7ouVBBs4qqqL<*D-FFF8R+![D8Ж( $;\~'j%xٻw@ zD"k­[8ڵ cԨQB)Ts\+ i֬V5kؼysѮ];jQ|Z>#TTT@b.h-~ATrl5:::fddh7sN:1ݹs[\.D5" er|D£lj<Ǡ8^@GT6HU8SL 1,k"@\\P}ݻ "D-!H`ZEYYz)--$Iaa Gݻw1Çlq8 xh߾@(`  M (/GRus@CE11 |+HOOtuTЉTbbz!8lذ?={׈0LMMkJj% EEEj,_v5|pGi;)yC8AD7AQ0AD7ݻ)㠣8/^vvvرHc8Vkz!FikH$j,_F$ !qd޼#H$rE.G55"h1E14yŋvvv \^\^<ˋ:m׈yÇG9zٽ{w%xώΐ½Q$U* Z _p k9kD.ݻw3qt.\`jRQQb;C&-˗JL(vR*ɘD_6kDW\k1bzHU,,,kD4W@gGHPj'f`ll,Ygbb"\-~! 1k9kD.;w2qt7Ϟ=P’ݨtϟCڹsg!jd^#va:vB#r8b0qӦMpE&PJ7nt+W@b59F*VVV5"?5†ܳoذAiƍ###m\\\ 1hllljjʴ򟘚 8:" byw.]?ydϞ=׈ ];v CkT22֯_#M.5+B"Ja ;ޕ#0ʺ"j,Qк9s677:s !;wJ Eq={T w(>#J[xII .H[8y#Gdܹ3??i77%K?&o߾7j+$pMLL| )>#P-add`ll,޾}v$ׯ=qDcc'.^ٰa66mh 1m&t>'\ᦦT*R^{M11^6AGQӧOɓٿ4;wڈNHKK;{,nnn>pk455Ŏ#())xˇ+b1p***ʘ4_{3=]  @A1pkD48zhѸFJƍasʔ)'O =M6࣒m۶SSS`ͅ{!queD###H SSSCxԩ^z1mnnѣG+D u5"hqQh|(Ng>|++Çlsmui77nݺ QKii)x>EEE-,,|Dd5ʼ|E3ɓJ'8qJ$\!¨#5"zQx hUhi۷xW=NٳÇ !%%%34n!hfffii4l,,,{!qu5"X,0333K.EEEEFFV='222&&鰰0ŮAAD73fC$h]84iRM4)66(\xΝ;~~~RHMMUw.D-%%%4DGQPPJ{#` 5.XիW-g*>l=:^#b׈heqرlQk6mMU|0ӛ7o^t=<٤IH F ;;;ˈkCLLL 1,,,׸o߾!C0mccVeee999ls޽#5"4Ċݐq3=o?6ׯ_Mc#ٳg.wwwx z),,0rrr^SJW8充LWzK322/.\PK.A 'r%kD ͣ5›(׸e0*T(}Te˖_Q6M4ktχzu Yid@bXZZ6B1$$L/Yd|>ʼzj-'׈ h-W˻ k׮s*1(>}zq6mڻwo!j)(((++c;#++ n1[8U0q{S^^ϴ$Gӈ47n@b 4N8?dzϞ=P1bูYRTSʜH$4b߿L1ehFḻCb T5 ˈzu8LMM!1רkDi48b<}ѣL7mڴo߾BԒf666qD0.(L+ o HMcݩiYi;q Hpɓ'׈T]PykDTN6kDwwwH FH T w899 5*6Ղ5H$TykD.6` O~K!(OvEP5"$z:z EvZ8b<~СCL{zz0@Zkǎ#HMM Rƴb1pJKK633lC$aӈ0n޼ .Po׈T[PI&L׈^3z!hoo)2"xj.qYTE"@bۣרs׈ ]k֬ CC ǏֺukXhÆ &MnH 8: fƍ'OA_I+hc&%%MheգG`+<<\Z222`8\ZpˮJǏVA"Nqq1<jnn}B)߿c`q<[ZZj>TWWWM\rٳgamʕ+i,EkDzQ}]1L~瀀Rz}?iMV.+&cPC8y///ˈzu8!1tkdܺukʔ).]b0 ƘiӦ-_ZQ!&bE: Z… ,ؿ[ i433(--۷ަMCEqB rvvƎ#x!ܲ{yyH)}!ӊ=H)**JOOgB'ƪͰtғ'OtUV&Mj۶Uؘȣ5"ZFD({`hE!|}}}}}5Y @w ܾ}{ݻwڵ&<^^^4v 5B֡׈<רuƍuVnڶmm۶r3ӱcGLj 믿f8->lr֭ ,ܽ{=ѣG=:x-)>ܳg u>\kDxhooo{pŋLWD"j،;~[*MFqMm,8!$<<\Z ٳGZC*pXў"H[8ܹcĈbTS^Ѷm_p!""ĉRcǎ]v3&22yZ RxyyAb TKf͘.#:FpvvF%J^1c=ΝS/'N#5UV%/zq#G,Z( d_~eo… HIIQ_{5!jIKK+**b;#99nٛ5kv199i$b>J XX-r|Ϟ=>cff[kDzQ?^c}8nȐ!ޱcGTTԭ[PiiU֭[7{s:88h1Nktuuw+Y^#XZZBb:zRQQoEGG߿_鐵|ب-VAD0+V8: ICFL&۸qcllGϝ;wZyV}IIIٽ{7>>>#j$55i777e ͛7WanpD),,LMMe5DYYVy~Ŋj [kDzQ̙6kT&&&Δ)S֬YtRX͝>gΜ+V,X`ڴiZ!1Zl4vy悮 Y^#XZZBbVo]|yvv!OOO?t} @/_qQ0Lhyyy+W\jU^^!__E;H]5ڵf͚ 6LZ={^c&M0޽ Ejݻ4q-ZPcRPP^#kƚʚ?~Zjm۶ W5"ZFD({}@Qh,Y,]t͚5Eo߾=zkoѪU+jiҤeD-[ 7Q$A֡׈VVVM4A% VٳgO?o3ڷoۿjGAjA ^#!: 4i￟7o^llƍy>>yM6IeDVZ 5B֡׈֐5D^cM\vmѢEWp7t@G bP-[qQ@EHH޽{/]qQO)ݽ{={F6Drr;n֬و#ɓ'L{xx`aܺu n 5KrD"p"~_@Q[^cM9s{UFFF&Mb{-^#y u]>}/^7nܺu)S>ӦMf͚AbG֭.# H$Ecmm ѴiS_ ^cM8p`ѢEW\Qojj:cƌO>E+!4>K,a8( ƠA{ț7oo槟~5kּyR]rrۙ9rZUѣG0ã>b$&&-{@@ڽD9 PcckyRQQujDOsrr^ zA<^ "F$3fȑ7oIIIC/̙7Ľk֬$pOzzz\2"@֭YƗX-R_~Yx1o8::Λ7o֬YZ AD>305ҵk.]J\\\>3fI+zFROy!kŽ#HHH[@{ L+>8yyy=bke˖UNӦM׬YS^^^kDzQ.\6p^^cnn>o޼Uܒ IDAT3gZꫯͅCO\ll쿊㠣`4_^XP```ttСCk%tG-D)))yyyL{{{WG ׯ-{ppڽׯ3-X8/ר^cMΞ=pBCC8PkDzQ#""zWWo棏>Zxa!$>>~РA:uݻ|}}!1۴i4v 88XPPE[[[H ///_T͛7WTT(ѣGlll.] N!a8(  6̟??**?p]v߿lll퓒~7vEoT~isrrn޼9vF\\ܲkN^c||<"(44T#KNNtӏ^#z?G|rppXdIaa ׈T zQWQE-Zh޼'Nnݺ?<-- OV\93gkX;t4v]vzu5"$/z/A.^zٲeJ4i`iӦ)#ܧ~8(&4_nٲN(,,o/_f!1qDX\{-III^cDseeo߾ڽƸ8VE $q^c%kEUN7o,jFZkD4׸x↴HEgcc{}}]QQJJJ?~=|jo[hѱcGˈ:tt U:\C!17o^Kp@!xbҥk֬)--U:z!k$`aܽ{Wk0a~yʕsr8tڵ!Ct!66_~޽{P v?;vTx%VE {1+^#PG&RRRLRc8]v=}4; FZkD4g B-[BbF*//_~eѿ\.CgϞѣG>}bcc[l٩S'бcGA5BFhѢz/AQ###wU5 E ,`8( ݻw7nȴĉU.* `ǎW^]h8pݺu6mqrqEiԩڽƋ/2-X8deeݽ{ikDhX/^۷oy٭[w磌"DA<^%KzвeKH ƚرѣGϜ9qCgΜ 0aBDDjDN: 5+L׈-[D%5jcǎEDD?JMLLNpBwww "?>SAG1p޽~z[j5i$Wѷo_wwO0R^^f͚ 6̘1c...jqΟ?;wVxVE ;w0^c%kPX -,,͛)Pn^#ye˖5E%~~~͚5VZeff2g=y$::'}k֬y?C|йsgA.]0^#888@bL&۴iSllÇ͙3߷FhܼyUtRF]iܹ5N4o޼wy\]ܹspޥK{7ӊO"Nff۷vtt|yF{UP-))Yrs$vuuoKKK .5"Gk/kD???H FxFG!>}w}_B6{/O?gDR5v-  5"Gk\|9@ 1|||% ''2NNN˗/?~ ݸqcذaڵyW1u&Jk޽;fff-_!1k| ?ݸq#\IΝ;K+!׈̙qQ@ ۷oOLBrME\nݺGGGoݺ?w={Źk_ݻwWo}Q|Z1p222k]k֭[F1\|Y-1#FD({_}UCZZQҪU+H ޞ1T2*ѪUm۶]~=22r޽T)8|#G^{蠠VMسgOq^#899Ab /_^hÇD#GFEEi%0A~q+Vn4pu1?m4!jIHH`:((G}#""N:rmv1nܸE N:={TxiE"Q=X8ddd$$$05X'Nܹs ӧO@"*^#yƕ+W Ü_]銊 x… #""޶m[hhvZk *((`Z]F%zիW.Z(>>Kumڴi ,puu(SPwL׈}k ;8: hF#!$11qLnz̙Brʕ/^0G%n޼{nvs٭[3gT=xڵSLxz̑#Gcj9´޸ /^\ri kFC6wQ\۟ٝ]\ @ETj@Qt[F1+PPT"bw)FƘ;u#>{[kjӛ7o^L/iŲ)SNVo@>{08@vFa@zqqqykٳgԩSL)..2eʝ;w-[:Pz^\. #1kDի0k,F4իWuԁ/yfCCC-d eY ryaabff%KHlooK,0́kffF;+f߾}=,,Lp1>>ժyz؍7kj޽{o=YfFF؉45"cqҥA n0LÆ AFWWWOOJ2;;'NHJJJNN~ٗ޽{[n;>GXXUy$FWpqqAӶm[4o(333)))))ĉwT*˗/ڵ6C+axLea Y?޺ukMdff&W{{%--ѣG$vwwӁ{.III#aYvڴi&L+=%..~V͛7k#1*"q=zFbHؽ{)z7nܸ@J^z^#}>-[Ftku֭[6n(v:Z666~~~:uӰaC^!2>{,))ŋ,޼yFֹ=zѺu߸qc .((pqqW.Cټys^# g")ՕTPXXhѢrʑ$u.3 boH~L&O֝eff_ ݛV, SXXؤIJbelllγkpŋIz KjjÇI^"풖ߞذaL2UTy) Œ%K {E{{!1*"q>|J5j^cTT8?{9KMMCR^'O~*]vQ^#"{˗/ܧƲeN6L"gNʕ+Ǐ9sfqq10ݻwOOOe땽=C +0VVV͚5TX)iϟ榥:tطo___*URxx8U1""ĸLMMAnnn:5a*W|}3 .eis9SN߿;)qزe  Θ1cADիM`YV$w0LLLO ر#,MOO~.\XhLӧOpScbb" 6\z5qOChӦͤI 2!ӹ$kܽ{7r(_ӧOX'Ƣ"OOOcQVZE~ b yDAwʂaaa(i׈h+V5>{JX4tի* \,J z^#X^#qFQ]3T>}jddDbccǏ褤x{{ĉS LHH qbb)ޗWL#ٳ'X"q222^!Ch^wP dΝtUV{;w$1*"q#<k҈t^Zkر#U*FpiZ.)8990U,??2e;ڵkGu_cH^#0JN2|򆆆VVV\\k&4Fz0 שS'du։(**Ͽ~BBBLLѤI  썞rܹ9sKbb"x8̦MkС^cll,ryvl_ݻK@&'O cccANFᛁ#n%{Bz"sNJ|!11Q6Z~իW|rrrDɁ?ϩhm͛7иJqD 5nذ<}şzUi47o' 7770JJ#˲0P .]VZ%={v֬Y$vqq=z4^;^cpp(?>6lQ/*8d"q޽{q[ZZrU&I4.]F.Cׯ)EPri6- IDATDѣڱcGPU*FP%Q/XpJDH|Az P%[Q~sΰ?,rK.$ƹkasm۶7!Q(((x͛73g-\FtSSS-cСMP~_fʕW(OA7770JJL&۴iHYQ덊 Κ5yT Μ9]]]njCG-,-u.]5­2rs6/w=v---KkXڵݻw?.S}4x_#\x-}k֬ٴiӈ#r \bE!WVuFbVk?˗?>Ν;7m%[h}v_Ox_6}_kDa8ZM"ea ʗ/_|ڵkjjܸqmڴp0w]v5у B5 7 F#b.Dt֭[_e#J Rn]gZ><Þb2 Nzz3H6n8>|5~pGGIիWؽ{wF-l0Dܹsȑ#$oN; !~tFKLL UtӧڥKPHR4"tZ.)H~~>*JLDAklܸq*UH,Ƞxtޝ}PV-_%(q\qjLMM]yi1;RZ5-cРA#,G:u*##͛yyy jժnnn۷,M,[^ ?>QRe2ٶmDJ'صkĞ $3Tv E&DRwrZѿ]7g|rǑ>믿k\PP7)|aFe;)6uTO4F/144TjGAXv-1Y:tÊ+kի^#={qD}6kC U믿@Xt) U,#Gݛ,5Ɓ?jh4}%J҈Ξ= P՜ހ;PpBCC,5'hcǎEDD{a͛GGG`ݛ}Pvm +-S###?~X|X$0%%ۛ>>>'Nq#H'n>_ɁZjE[ u"GHC2\xx8Xݽ{ :Bjj*kGqwwaЛRkڴ)lú5,[ldD~+ӇFxFa8;;s͚57!D111т0F#F+>>TEa/^LqBAu$899QRYHT=<}z$򊊊˞={n޼I-Z!q{2"qnܸd֭[;ty@rssAeʔ^cڵ4"#G(2F3l0T*,!##VKJ# cccJmX`TFx׈ GGGq& ZFϞ=iw#ͣ8qG0JJ#˲ Y-W0EEE:(s)SF.34'3̩SƍGb//3fe5FDD>ph4& / ";' ƍ{!5w XreĂo>}J*d#}a5jÓ'O@e˖ʕ+k4oڵkn~:̬mmm+V(JFqРA{Ç'J҈222@j4 )..>|p۶mHL`/^'ȈR/kȭ[bff(9߿200%!CP ^#X[[0Y]?ӧIlbbrEzuǏΝ;#""MtaÆΝ;… p (.. =x ylݺ5slppXâ$qOOO2pdp~~~RRXXyɓ'RnϫT*sԩcǒˮ]nܸA-[2pɩ]7ocZ֮]LÇ=zójժݻ… `ذa{2"q_{n׭[p8Lg„ zfff]r}6m^a4ͽ{`D[hXfhhrJ_Æ 595"@ݺuALΠh~gp)igggk֬/xСCoa'''1 >>>W\?AD $,?8ťKbcc-,,֭[ͮ߼yTJOD|<%%e̘1$9s&^v#H˗/;6..8''dqZڵkcfͦM& #F5nB1tPGׯܹ6665;wްa7lбcGqIy޽Zǎ4lؐ/(Ɏ;&NRO?g֬Y4zپ};kԩ#""χ}#GvXPP^#dD\v5rAt>}  w5kk҈ƍ^/ORaiD Z]Ra###mllHuFIk7 z`cc°$x&TRE ڵ+.}fϞM}DOiذ!_?XMHH)+DHNN{^%۶m~:۴iG0o<55J 瓘2"q]}vrIIIFaTTR/k׮~Xk PTXBff&CVF7غuz68"ȨQzDlmmA 6|}}MA*Wat҅v>2}tzsGu$QRY"qGR"uk׮XV!̙3FG|Buܹ$V(sD\zu۶m$WwI^#0M[F;;;,aĉ5X3n8T*,ŋ Z]RaT\R/mڴiР."O? (^ckk °㼽MA*Wa|@(zs7c z#z_%Q&8qBɓw޼y4zٲe xm۶Ł#B5knj#*xqsխ[^z\JJ y@={066klذ!F0yd'KIR4"/0juIiDAo˿Z#1 cƌހի°<==MA*Uatܙv>2uTzsGu$4hЀ/ ˋD,&''[ *ѣG 8\re˖-$_>w)^#<{ abbB 6!L2Fp&NHbJ!\t VKJ#>>> z^cvIuƍGk7 zP^=!nBRR%-cǎ@ɓ'kiӦkS4hFIidY 9qI`^r۷ w̘1F&L|BJV(cǎqDrJll,4h jժQeƍ5:99aiDӦM2T*,ҥK Z]RaTXR/۷wtt$1E?~<3T g"@A 4MA*VatЁv>2i$zs7ez#zJ()2$'N:t(ii&;tGƉ' > HOA$˗k_>NkDϟ0LMM)Fggg,a5?^J!\|VKJ# z^cHu&NHu_#q_#'T4hTXQ h߾=.}d„ /A aՕD,˦[$%%ƅ eƍ5vر^z4zAiӦqɂOU jF˗/#q 3gΐ ??QF JƂꊥ!̘1FpJ!\|VKJ#~~~ z^cǎa"L<o@׈ׯ9;;jAڵ7^/0LIidY IJJ W%+Y… k֬!w^#z ammM;v!DGGHcμyHR4"ׯ0juIiD 00Ao_cHuf͚E UBo@accڊ+WN hٲ%.}d#G>ԭ[/Ȳ, C~F/V~#B駟k;w[Te ĹpŸIbڵkFxƆR/v100K#BXhx4,X* K#B~:CVFAʕK=Iu̙Co_B?>6/ nݺ& ʕӂ0"""hw#C׸\.oS()2ի"eǎݻ7w\5aԨQ0j7opUB{x ^#~Q^=J޽Ơ ,_~qΜ9hT*,ƍ Z]~< 5$ƺͣ55"nݺNlٲZF-hw#׸\.] IDAT6l=ښ/Ȳ, sر^z888x4z?.\@^z!9F ,|BJV(sqDXr%7o zWpٳFaiD1115Xh"T*,ƍ Z]R a-[R/={ &1E?>3T ^#8::09+++qBtejAᴻ@Ak\.2^RN0JJ#˲8DGٳ'5jb X"##Ľ{ w1 .|Bvv+Jl8ϟ?Hݾ}<׈o޼aPe޽56iK#BXd x4,^* K#By&CVFA*R/zjԨ."@tt4} kDGGG5WV-QAtJa4oޜv>2`zr&=N:|aFLv-qBtGуč7)xaY|9x}#B6l-Z$*\P("D?U899qw!5"7o@^k4"K-xi!J҈n޼ P\VVy3T 88Ao_c>}4iBbpB ^#8990ԩYZZT*-#,,v>ҿzkS(),@8G{pϟ'q߾}hC qłOUJ.[@$ιshggITLr[n=~8ܹ3[XXT\'?wKdd$x;wыΒ{Y_pa$vppԩkԨagg'N~a``^Fv۷IR~Ze۶mmڴ!Z޽+nBKIX4i5j o߾!!!$1??|Μ9Ϟ=[_~5$F@VVVlqq ˝;w6 XZZ~S<<< f͚$d^_:XZZ֮] Gi1]ȑ#ݺu#q&MVZEe˖;w1((h͚5SCCCm# <P]dGFH+Jl8Ν[l]\\k Bi f2:۷oAz1,,L100 O3~;x/),,HR4"[n0juIi,,+بQ#F2e(ү_PK.24'!M1 C եKX|K.$nҤ6~W8p@)? Khd5o{pRsYX=|e7JyFݻw l42 sΝ;[v-^,Y2 4ɉF/zn0L~ ]pJV*p"qΜ9bpuurrrȃĽFCQ3Z޽as!#""\včFaVZ^p T*,!++ ҈(qa&M0J%^Nb)EY kae˖Q=CuŊ$G FD݈F#WjUdG(qaCuԉ!!!֭KLL xƁ5khdO>0j\|^c~HT*a+"qΜ9dr?&52g7Aϟ?ÇklٲK#hdf5.[Lpqʕ$VTXBVV#??믿ȃPO,M4a8hРK.2g7a_[ S0jժ^ܹsEh4"`iiyf4Dt8, bii٣GCuؑ!!!ׯŋϜ9C!C8;;EhѢ 5{}!RLd2E9rD hJ.׫WH˽88-<<|4zqΝ0*.EEE߿wQ566r X[[( wI kh4>}VRXR~Z ڵk7n;-addq,sٶm[6mHV\qs=z2 (vFZܹs,s"ӤI}nժuaȘ KKKXgX|9wbŊĠ~GdC$ӧOv/q疖ۧ"N>mgg?|+++1Oݻw?J*$3L޽{` biiyWV N9x`Ha/$6lXǢ"'OXN7oa-[ܹsu+={׸j*^zXTe ʕ+o߾d2.z֭{uW=*jZZŋI^c^^^Æ KdٳwҮ]M6 9+++;;;_uK7!!AB??L&VZrDIIh4/~tK… *J>y,AʕEi„ 3f L&%EAAAϞ=ڦdQѼ| |4z9v\fM|ڵkk4ؠA͛77h@K~~~>}Ŗ.] huAҥ[ a5qpꓖ|ׯ_7mOȌ zkl׮FqF.oPTX½{@޽]T/!!! z;߇޶m[c]DիWS=Cހ0 5krc& a!4bmGt5kQd^8P$nڴill,^,X]]]i]v}k֬k޽;JUl_RSS.\HbŋF(((ayR/5oK#BشixWB8._RaiDa4" nj1b]D5kP (䑯QfMш^q?.}}k?㘛Qd"eO6oL秥ā#Bҥ k\n^#\[T*EӧOGGGӓkDBUaIJJcǎXBll,xpPq* K#B>k acF ."F^#q_#xxx09(+rG D h ˩zbnnFIidYGa[&qXXؖ-[h2o޼T5͍F/ѹsg7l H+J'l=''d׈ wQĉ5vܙR/ޱe.(,,#,T*Fp}FQQGhD ac~G8 ~b ^#xzz09J%nB"˵ ___] RH\ND3330JJL&ĉ$qfͶnJsO?!tPݸq^#+u 8:u FOOOvIk|́Ξ={7oޔ-[r&&&7;S)**a#8'O/^Gm۶.(,,#,p A :ŝ;wΝz꿯}ŋ/޽b ez٩S'ZM6gQF4ddYsP( *T`bbbnnnkkWNmflܸFx_pTann͓'O 9ccc/((*S̿I۷O|dG H 666'N WY䈝N$;;~悴ת;z(d,v: e]?$GT\iӦÇK+/^VMw[jEⰰ۷eΜ9O&ѣi\v-**j֬Y}􉊊\I`_MiR)sԩsˋ]kk|eƍe6l_T*[nlٲӧ?}Tu"'99F8Gϟ?{ŋ'OSN={ܻw/4uMj3ѣtBb YXbŊ-,,%33sɒ%+V B7n{ƍ8lڴרT* X/@fffk\t)4kbbrEEE?ÈAQN2|%Kl333*0OLUWF 0JJL&w$69|0č51e߾}-Z qwI3g¾ƱczxxEﰴ SܔZi-[5k׎JrӦM6RRRfϞMbooo$(XCoo5уR/RSSڽ{7UT^# 8z,ڵk;::a;V>>>>_xQLDa˖-T5F>W7F\iZJ2221eYzo,"f" 0OLF_$:SSS08IJ,#I~~>ĕ*U1%..5ڵF/?3k?~>>L\#1QEDa3SSSJ2T;ٳgUT^#Ç R[d!JyeŊGi֬q~F>J-l۶m5F>W7Ԕ+}? *@i|9oC r9 ȦydTLE355 4dұZ<77O<7}$..^Owˌ3RRRH|(SL)]]]aӧo5cz"'OBlgg'b&}vgԬ=QzJc *#GBn,#1=X?%%yf͚k\&Q2z|ak,5^%9aիǏ ?ݻG^jۧq5N8Q $fYiӦ$Zj^Ν;Z Rm6_>:pFe{=axbJc5iGaYR/{%q>}(w,\>6jԨZj$Gkա|NuѣG|ap8pZ[ڵ+""ڙ:uɓN ӂ0&Lo\]]iw9sÇXܹFxFaF?8^RJ3f0`yӧCZѣGϝ;_MMM]ẙ[) ~Dnnn֭e ȏlL&e_V]VVѣGO:qͼtx~őG999>>>ﷷѣǾ}>OT'юLI&yyyE_HIIС]r+WtwkS bwi򏪫]GߢhlF:ɓ'ONb??JxY ˲7n NKKcÇ#F5jT >]_^lYTT` ²,ާ3g~QExbLL̊++Wnǎ ߿(RSSΝ;W .rx\rF7!z/_íZ:z(0^i֬YhhFy߿yfEBXX1qľ}Ņvw@qq/?wҥ'N# y\$Eg׮]T5L~:uꄄxzz[ZZVX͛7iii3g>>!!ٳYYYo߾UT+WZw}'d2snrI͚5mmmNM8x =T',999 *ppPTp96maL<$vrrݝm.5SLk7`ih_u$(S jժ}P D -`L&S(4z)M^2,$$^aΜ9$(GR飯mmM6Ղ0N:h ;88NߑGu_#q<,[lvww?p@ŊNJ`@UV4-[Vݢ64h$5׸L&e>GAb//WZ*_\rH^#߿_ ^IN8A⨨( zGhh(xk #A\\>f̘1aݻͣSL!q@@@Ii}H)Jr9޺ .9rĥc0U#cǎQAu5#PILL444_wǏAUT^|)nBaL6mȐ!$/߿o@abbAi,_HY!Eaa!l8!I5.ɂ鵏)&&&|ap0q^#hk8q"xӧOG!xAƦM.vG>t'OL ҈^#r=?&33nͥ w?~C?~ 022MABCC (X}ӰaC! o@z^c&MHl``pAG8/((4FaP22 s%(5"@RRUT^#0U剛hA3f9r$ׯO;D_8rUހ5"@`` XreHP/SI* J6hЀFCuj]&kSTB" *ի׮]믿޾}K$ ;;[.[YYaZZǍEya5k֬J*999999=~:EqqJrvv^zժUŚo }ǎkk bwmR\nݺa=zdeeUZjժxP šCn޼IϞ=-J&:…  _~ukkkQrP( z\|?}R/_ƍ;v [DAw}۷I\rUVɓ'_{gՙs{oFADA":D4VƙJ&I%J;ST*JŚ-5IF%qR$&DTFAdkḟyTumߧ?M{>}Y5xcwww||$x ?022#""B  4'OYd2ڵ4 QZZعsK/DZ*`0|ᇤNVK7gsss]p*TuF&Yt{}w$ld/sss% %FHH%O>bIId* sh4fYHv;ˊ Ib slzzZꠤߞ-|7_}Ԃ ;RG$ =իW  k|-[̫ܹs ޽{7xkܱcGee%?OR x^oܸQ gϞoy.َjma&&&M&kIOO_vmhh(['N ~~'9s _).. &>52))iAKQQYb_WsV>^?Nguu5iLVPPƒ|>,ZGDD0鬬2))رcommm%7!t:|ͼMw^#Xf [C^caa!iF6v8/2钒ETT9 Z}ׯ_7aչkdYǽFbHHH^#G2_>kqK.5Tsss]5^tL&q9.cccxBql6iF"m<Iȹ _|^#Ey555.׬YCZV;sԩ[hz5r%;::0q7sQ__Ve91kHFGG"&&&rC׳ZId,s266Izɒ%dbHǻlä5Ͳeˤp< 93^ /PQQA;9իټƋ/k,(( hΟ?ƒs]N:/޸q"66^ph4,1ɺa444kdYǽFcll%Frr29Ů :Nx8^Bcc#K.Id s2>>E:((HfI˰Z7n jCCCp< 93^SN~~wsϑǼƺ:{5Muu ιw9y /@TO޸qchhhvvVVK0)))˖- ukjjb~i"vuu) J[,Dt8ahooST bjjJQUUN׳[_&!!!..f%&&,uOX-1t uV>2Z9vٙ]]]z޵܋rZSPddd(JJJ Cnn+#=sss'NmɢRbcc ʕ+tνEDDhii1lP211!Bff`9<̯ZѸrJq8o֮]tNӭ'X,vԑ#Gz:ݝtQ͛;;;]qݻwtvv[o =VRRm۶+W;ݻw7o~uuuyyy/]777 ñgv}Ϟ= L766ݻfۻwoSSӗ/_ɱZ#=33o߾f[ZZ˯\=55~xլ,ҭYYYLG׮]$֖966ƴbٿ{{{FFׯgdd2=22rtҝZjՍ7WX+V2m2>䓁T҃===~mV[\\{/ĉeee~~~nSh4]s͟o[h٦jR^Lk4BYLk;聁;h???\6ͣi;;耀LX,w޻:Nd2ݍ Z{zzT*UUUUKKKbb{W^^N޳ `Zw>G333AAANgrr?ꩩ)N7_߼yd2ݍ^Rnjw6- `1=;;׷lwv1mZc1mَ?T*VΝ;l0ADP(:3363S(HsoU*"l6PՈhZ@"4#$Б@!"̷dD~K""u "N888aaa800QQQH;jEGG#"x4#%>>iY<^&''#b[[ bkk+}xHKKCĖXr%"^|9RYYYHcNN"{sECĚXz5"^p>D:֬YUUUvZDg?"VVV|nذO< %%% JKKM6!W^ywEk~ ۇYYYDekDܲe |yf8~8"nڴ {Dܸq#8qKJJɓX\\ H9sׯ_n:8{,"FΝCDڨHf|uu5"@ZD/"bnn.!bvv6((-Ѐ4 WZ͈H---HիWq@7)))ֆIIIpuDLHHDϰ iRVOO"b& i>Dٌ000aaa0440"###tR-8""" "j4 ??=JP^^CD=L&o[hmPP"Z,+88iUe˖!Ђf"##oA኉AĞCDc7p%$$ ")+\˗/GīW+vkժUx•vnn.ZjkkEC| E["ٳg`ݺuxk:s !"-U\\.rv6n܈' "~eDDDZyUVV_~%lݺu q̗@``?NwVk446 {cǎ&7o3l%K"++K&5559~'m61,,wߍ1.~?&&СCtk/YZZJwyuuuVbώa }g裏vA߂t))){q= G-//h4._=;r|V $$M_T?"J%"jZE~F#?hZQoEQEDQJJ8EQ~N'" DQ>"Ҵ˒%KDQ~KH7#"" 88XE E:gHS?ղeDQPQiUR.b||<DDDH`udd(W%"ͦ MiѢ(&''@LL(FbccEQLMM8QtZZZTTGuߨB0 b^^^RRRww7Gxx^#""N)Xb:22RSލ GGG=**4㤣'''=== 6tLLn^tt`7oޤvce2ccc *J֮VIiZNF)aIZӱ >99I:>>~jjiʫxJZl6Aʆ@_ϒHJJڲejB$z=Az=j#z=j"CTdHSa!M4B멘P;jQT2vv*TjŹXhFڵZ^Xh???߶=###??\ƿALiIENDB`./pyke-1.1.1/doc/html/images/rule_base_categories.dia0000644000175000017500000000467511346504626021437 0ustar lambylamby][s8~_r$!ƓLUQlv0x\2}% ـ'Y/!>:wtޟ=(v1`^_/w_~v='g9F˟n$9tsc$a|of~e6;uGˏ:Iy뇄gokg6 wmB?f;z~7y77~}p|q aOMҤIU,ۻ?xkvZN(; X@`&?/ץ;zZ8ZhZ8/^(/BN&o_LsÂ{$p@vZ6[h˓&H+8R^}^w^ qw'e+ΆFp)'t4+ii7t^PIS9(T3U$Nξ;7C{;Q<U!|D X 򐫄z]=[ " YB Y@l|ڄQP R RGuX/6 [kǽ.i§UBE89}ֽٯk׉w>n ;c~X$[E=gn61AB& 7~9QhP2 B2s -`bq^偵 LvPaI|a$@%a~vE2=ϕ{P7JmD,+-Lvbް4=5- g⼜ RvS$w#O{軧^RL;DK3T-]-'{dAe"VvτE C H%75-Qj}Q% )K&dS CKD.A686 5mNT-Zk8?( UE$#LKTҁw Ŀcʇ1r%ay> Z4ޟhVUp!I[7X/5a rhC^JLJ!f Wn:#lryzMDʧP'r(|j*[oCq-J/턽䌛ix[qpe |_F/jtFØ4&mh,3i -i&i!HCmڪO_Ǭ-eg ԍiD,%a,iؖp6];&_ZP;yv3l[tQ/Tͽ:ǑLR,ZSUzHY F]QWѩ\dUDY M2+,C=*Ր]jYUthTse˅SERxaKĀXbh1 ۱XcBN=ȷ[̸ Vo^ IwP:y :_ Mw Nӻdﯶ\LyG:&qꁊdMmܺ{t5z]C*r>u!A>pMK!p:A) "{%h(CCdLmtr8X "M!"ReMXM)V^立ԥg'RcZ*PDl:hBa2wBfmlЀ6Yݨ+r\LdşkS(@Z&ЪyG*VIRĆ 6J!nMF=_]+iHsfR 6?y9CI%(/(Yc1I9 EPr2p)d2ӺAB ; :qXGBّO X<5셐]}DBfz zPyaׁ1®\xG R̀A8hD J!d`SIB,6׬HP{` E-dt_MH0 T=Q>ZuB =T/:ס+-e,##Fck1w"t]Sf(>_v2a鑜~j"cDibsǯvIJ#}$6R/-?ɾ!3%7}Z9} ّ^L酂^(\>-UAL8umRӦ9$J)Rʔ&Tum{ЀTA1u]먝tyn͂,dn"P-d$m5 ]|Åa2{9nWa n$A2?G#w5]j&wq-x]/20#㾯FWH–= FДϩyM"O)i~iD__[ TϾsݗg9/5/./pyke-1.1.1/doc/html/images/plan2.dia0000644000175000017500000000226011346504626016271 0ustar lambylambyZK8@5`'=F{i{pӹo_H'@pU/_VJEb>M`~0 _|V +MIz7ӗR?ODXm@B("S%4՟4XA@$INJQ"`'+:x=*8J]P3-9{MBjWm^SQUZ S"rIR*%Ŷ!u,(w11 Z{\/\t_8q_8ױ0Y\qD Q8O"b2kaRgB6{C[hѲe\rW}7WEDVϸmWw'=!J)"ܰ&g¬,ӢiYMy*wc agQQvjy!^,[I>i7zƃ3O쟪EJ*}5(gMZjyn,U <4pӻn4 ^onk^UJixǗkIY`&evKv?'kP$ʤX17Fҷ"扬5,U:I<䅊9fKCy Qzej:vjq YK˅|4z个]Z5n.Ǜa-Wu.EtcTmR7>MFwt'/FUܝA`X`z7$7&!:5xO&G&IL.A# M"6DL#܍E`_e64(܁Fn`e<m?$<`n4=ax0'B۪(yQbq/FHw^ʅ$_ʞ8{ ~Uj0,&ɰ$Ipc:'v45ߥCs 8&@Ln&u )D!O}m;~-_?7'4gwӿL;$M8ȃ <C 0b/OgmKҧNz-./pyke-1.1.1/doc/html/images/header.gif0000644000175000017500000003324711346504626016526 0ustar lambylambyGIF89aM9!c!k)k1s9{BJR!c)c)k1k1s9s9{B{BJJR!)c!)k!1k!1s!9s!Bs!B{!J{!J!R!R!Z!Z))k)1k)1s)9k)9s)Bs)B{)J{)J)Z)c)c11k19k19s1Bk1Bs1B{1Js1J{1R{1R1Z1Z1c1k9Bs9Js9J{9R{9R9Z9Z9c9kBJsBJ{BR{BZ{BZBcBkBsBsB{BJZ{JZJcJkJsJsJ{J{JJJRcRkRkRsRsR{R{RRRZcZkZkZsZ{ZZZckckcsc{c{ccccksk{k{kkkkksssss{{{{{{{ƭƵƭƵέεƭƵƽέενƭƽέενֽ֭έֵֵֽ֭,Moٺ%KYLƒ PĊF|HqE~bɌI4eG/Ytc͑2o %O:}sϜEz(ФPJE:)իNo岵ׯ^k.YcŢ5KmWk붭ܴ\[V-޳|W] جacw-79Ύ7yИM{&MjԬ%'~lXʼn0툺_۠o{ _.͓_A ^vZ[ыGn<鳿o{{Og~7}7Kx8E >hu!W%PXҲa.yH؈r'Y%؋&آfHb\E#6b0x#0#CcL dA$)%@h%Sfi▹<")QrdmZ~Q)uY&lLՆ ~V+UE(G*hB~h8D2z饏vPjGfJ*Pg{J)ij^ lDjkjinjz)J*-& ,k^c֭͒o]fiYV/E*v^=l2ꯢ IK lqAfqW,pkK"2&{ sOhy<$r<7!{.KʑʸQ;P2PduǸI5H^X}5̂XM$6r;7۲uq]ww͵x E?k]>6n\D:GAfWi:…b^Ԅw%~T={E& `(> m Q!gb0#\a [hPqU An0}H2.AΒ5d]okFvVT߬"[2ŐQUb/-[ۘ7q0YcXdM [,c층?%u[F1Bt#%1bs)eQb&B惭c>M,;XI Ri_Z0t&ZW32#ʹ] 4Pk5 LHi_< 5]#p6A)mi[6ho{Mz[emHIْ+s\TW|H0Y-һ^׽E0|QǶgP,{b+z{ۛ1W^y|ݵ phobq' i,؋#rtc1s:;460͑' z0Lvri"Q@/ FqXP<&?9MndPYP  orL);QAC{*ŸSULEB[#d EOoPv,Ӈ@o%D">:˛1 AJn J1(\N6 ` 䦑g.Vc7@f@_ ;-L ;, {̾ _pӫJQ)cY1|rcƽ( ik;xdKbJf6EwY_jو_z8.cIR/P(0A3\HHvc,2`Ћ~$@ ̰/AЊ ?8AKt{I?։wєS{.MRvs&'4*l*Q,W>U{\)9ZQy o{mQ6yo,@W0w,C< NHB/ :8p9J t\@HnxLW~,bHؖaĖUQha?H*%+\m $O' S gjRI$(iz.wB[j$9xSH'YA]xݥpjH&;h/RvG gB7 ` w %go+I8rEH[ 07t@HXx` plVeՅd 2զZA_% QtB:S+b.]cU 7exbcq a?WD"vG[28%BSGsRk'Xz!'MR{Sdh #0t#͠.@CXt&sq2·Pe"@7& l5!Ӹ@ t}sTzc)fC24cRb3@ۢ[mDSriR: B),!<,IRzb.fmh_H/@ttW WY8BwW` jd@w'\(wr|(}r^f/q)>NS("Q_0xWsK9Vؕ$;edN&7Lc{$i4MI*[)$uY i_a{5?9s8 p6.& h:nj[ht5ICPƈV8 ' X~h.im:9ǎASMe[FWExv F^TZi$ hzXytQdiHp3$s-Hb3O4UOcxG%: Hw u|EHv0E 0t )%zypp SowX?Xxx,VՐVm0(+sɹC4=)>}ɏ7V v4/]2;U7W{ Dr(E"HG{H#aN)9"ИCgb@ S/It4i /џP`dgo[_2<0h"ׇ2!?DN7}\8Bڃvxc'':ɕ!Wmq#?VMɖ$jZU8GdzqDO(Y eBVI^a7@g?G87y霹@y 2_czƱqL]@ 'Ki_.<*j]Cs ,z3" >7u9%xh*xykm |ȕ tBi @ N@|]J CGwjNAWi#mZ51(p!`vQƨJC67}-Ix8V*2%7xWkW8Oˎ*}4 2q: kErXOꜻ Pg[6_@  z~ӱA6' 'w۬&PS_H5x%ri#]yY4%<8aPi".eõ/s:i5哔%C{4yD-g"EAE: ]J ^}Χ^j@V`U`TDVpڛ CWv9`0{ [plۑԕ,F$7j xk@r!sĻ&8$YFK7Cƣ9?ɗv<Y-zpyoV~'y \ PlfF _ ` kˊ /'~H+ m|pWefji(JJ>Ud%ϲ&jV.&DK iK>S;;̮A&ypSG0{C 60ho__1 bp B6w5P IIG*|^ ptІC)5Pet<7ukKC\5dD z~i93/d E) TQ;HR'*JYTD-9C墮! yk M| `b tu@B K#p>tI*ۉh! jj~$ GP(lEfE=Tc y'^|+';3 k pXI([zǣqiܻU}C;pۂp}ILj嵛B| j#}I7o_eRvB Ph ⁾w&%G4˥ɖZ9f"bjՔg\jrI$fz9oӧ ǡa۳:}K5жWvx tP@ >a cg!0%2 瞅[1 mh#mHZYͨv46 L\G}zރ.=G)e V2*Ƕ@Uh?ˋCppӵ»E `eU[m&d<+hTY&PλwPh\?oww`v$r9;X8aQ,j UI<:9hU7R].WDb!W-eo|d]h#cdX4w P0r Dtf :6EwO`0tt/ vp8Yw:Dl"fN訶22IuGlxm^Y7 $m 8 p%/M>f୾@ (XisښwhR"5e,q͹~xMr-^;ڪԃBz[bvw)ض9@` i&A4 s+b:hXEN̊_IGG|\.bʽQ 2*p 1z[E{ ë{p8? 1A AJi놾) fX'(!^,z.f@ THF{:Y<k~Vl2f^ƛ_%XPUpknd9U~i;:oȣF&㬲Ej;qݗYZv\օY&"&o\~qwJ$w\VRdP{/%d)Am239Ζ`а/!YP kV&5ICrS.F \`Ni UU}0G7n8hՀq.0 ā ~7s8nD5qk 7 @!NOK*E+ECn -+b4՝cbilWI( _K 6\rdDdI^K6dm'p6X9duj@N6D7Qn,'75ppm5QH'!F&%ndr8xdBpLi]·yOA`җR͛TFXGn? bAz 2\`1@rA d2Л֌j0,fԼBb$ M;/|P@E"/~ h_t!/P;J- Uhv%Xuq 8(p< ixC0Ѐ Tht MR9m Yp(X**R"p 7x-X Up3P BR?-zCrgjxx:D;N8| X|PObOdAZ5/0Q,#Pp2#_0b)bMtCƪ,6V{\kYbƑ>wk!h Cdo֜1׈Tn+,X @0**TD1~(,瘰5@qa'sEWQѠ2 7^)YxQ=G'i'UVLJPikH5@$\Sn\8ĔFVsDHF'D:t-D(NN,.*QNCZ\3k$l1S kW1iFïf܆LHSIXAM[B#0ò@ ez)A>FзYòVx)Er<8f(r+*\),CK9~\OPy~CIYg؈䢅EE*T1TsC/$IEm\=RH{j~ƍTl}q~9zB&&97Ne4: 9r,Ip;'G&"~;@Y`/+6(6ׄM4cT3v(3~eF %/O72Mڇ`d M0ibƠ?D8BL La H@xiN,]`0Fj42_>w_61,Vx[`羫Po>"wTOuTܧվٴǯ7wDčnͩKW9qXTp1v7Uŀ^,9plJv`2 k`UPQ U0֬8"{bailBj![7~k1p V`WW(,[W@˒6S)oc93\{лB/?wP_ gxZC tVӄ Xhc؈K N+ 86bH<4D(RKB#FhIhYhIhDH(BA<* $=c X4/ԑ$!+=Z3Q6h!?# Xnڬ޻)K#X FtBJn8U$j20!92 cHA2%;Xj!i2qF)O #WHȒFfh 4CF+/x$ >\@X* p=Jh%(\@Q:hR+5hQyL H,Y@ L HB H@s?`(`l2 ʪ2+i8fAp p,Tdp-rFFt,1ҹ\W9N|XPFdLڛ5+` b܉sBഒT1 i [00"((Q4$ ] C 5( (: |>~ W8d9s㱰LVdPLX(T[@.R C.Ɖ;)ΓdLP!3| Z1kĩd`d&>2讨ppdsÖHԙ 'hV]idKdVFC*K M?C(&gyl $Q$x6i+ DZkOfOv=#jTlOy-V|bU`ൺIMbZn Y(kc0y[AL5jSrLʢDpaLӖNY+Ui6>nՅ[Y#ZaVQVM@TYFglOlApoHBXulp^3ܧQg^𙈅 o{hAa@aٌ`}`p TC6RBáxmq)pW5|%>F:.@kpHAuu6H­Z0p/VuCG> NgS_ | XTub0% O' ^N@W:Uƈ2MIDpVf4xo$H)Ht^ aS lk!uʠjXC }c&6 U4xkVd:;w4 coTcʼnGkO;o|{a nxta|su6DWgTqgtSopful5)l>iV n&n<~ XO [^h&aaaǫ%O 2v& g OӢƖO@Q0Ax0;l\VhngOf֘ 3_w>⃖}k( 4ߟ6hO[,aIVX3GzaF'vtyx#QGLVժ ?[k},K芻TH4\~hqƗTߓcɈjfAu  gn ivÚhj6'q&"?_Zu&H'?V1K?S ʭ {nid{^a+s٩Kʦ,KTtD\A!a!eMwƛhb$e5E\y28AM`r,yQh F'uak х!D5 ɑofz ^yRUB>i_J,Adgz!,n.E %S= ,N& %x]Awa!MF^Be'\b&"s_U艼fj`a`DdI.nF_{f#a!P1i,苣iуNgK&kngQ'ژ H*$NuHwz ,5 BY犤jbmUnxI.bx[w1:h%sQw[Ɗ)g`\+bt%\1e,YkT(*fK[:JҵjZҷbWdeՀVy*dH 8g%x.JvubmW;f*Tj_ W<7hq}S 7ʶ*<+bSʬL~醦˶}7c 4q%m|HhEJZǙ: /Xz +jjț>nyMnnŒbC+~yr"b/FnoW޲r+W)tHM]r6 o^C'T,P P/I16 o@L,5co70uhj6,J) )=ɧ}淖;Sж4^|ʔ c*Pį܄M\3$]y|o !Z|mǙ{jݦ:4{JfJX@p ǧ`$LFRK幚ҋ1cL6]i.p{^K j#?cAyr ,)=.xoOvH/+O}bkb+r항Qsu?p"JKtTmpw!ӬKHd1ibs9FSW[V4D<,l<(s;./pyke-1.1.1/doc/html/images/bc_rules.dia0000644000175000017500000000543511346504626017062 0ustar lambylamby]Ko80W5c20C flȒ!˝/)9?$[D%@ġY*Wb?~wm4##y,'9QYdjd>l_weyxx& l8o̠0{w<Ϣ6ף$\4m2vfifa|7x_7in93:\iÿ꧆T:;vN7?OcٍژAǯ:uݛU]l|f(9eO\ QődbAÊ +.Ligaiäg[^ffS >߇?-Z]d!GߣM4uGILt xPV"R3ͮwm4כ pLLݰKwx\Su q_]hww,ߩ: L)CbfPn !-eX&4KNAB 9U >( DYK-y0ԁ,H \gTQuj'nL&FL&Y8z@QE c,ӟ |:C3Nf0k-uT$!e֜CS5Br1$憹4Ү:5R, ;R(iUEsxHC |,-u hLݝ(qGq}/ؤIMxpձ4ɫDG*͕f<䏖(?y4 Wpv+Ρ6B1QCi`~#;F6F JW?QlW Gmf-b/cF3o|zjNQ)8nJo@ZMg( 2N:q&@^ࠐ<nygPs#N) $v@# @ 䌊n$ |>CAٸO-o} D!УWG4xVZ1,@L|x2@ `LT @5m'u+֓eEe|<`!Ƹ{ qΌ 0ok8MTXqt֯r1qC=m1Ef6O8Nl`f!RO8cp!.sxѢwoPC{ vߠb E`Qhٍ5!Đ&`}rCz!uyiZ>4@>VtOyr+MJAuT{;iUlHD{6zR?xEI Xq\;᜔@P=QF(8S5kHazmzǞjn7Q!vLEqzտ:Ǩz8HSi4j̬Rr}v͎7R)sE~0Ftf1[b=l,*Nhfn7*<@Єv˦;kׄd@#{I'>Kjl?FkѽC1tJ5lN-alFwe4ܫFJyɆ'b5Tlj=Ll߭vH0 YT&**o@^ŶD'Bԫ5$Ĭ*\}0҇zG:-dv!pb1Z !E^-[Ԯ͍4ڧ=հ s 3"6_"$?B~ k?[k騺kPKo$`H?P̍Mca?PKKo|0DY.~S/!xhs /HguѹL3S`beLN0AƶxʨnT*X1NBa SvG,TKc r~ ې<W 8G'vo9^?V_./pyke-1.1.1/doc/html/images/plan3.dia0000644000175000017500000000242111346504626016271 0ustar lambylambyZK8@5C0` I4F#' ;Dt.۷x^y@@ E}.sPya"">U ?U~}z3T]J<o[-TFB 4 Ӄ;ɵ*Rd +6Utц{jf-0 sz͸5\0ٵqfvLn]1i?ɭb0&%ٰum+*W`~l2 nrns?/ N.gHHAYGQ(Pذ8񂆰N{Ό%d2ENނEm쵧ʼz| uX׶I#3s۰[` Cl^4ݣUGxw7i.v 1&;w$aRb`. AE$][5-zLl xbi?Uv 2hxh(!3|p4e (oC^2MzpKHhB4[GĂb8:rIO0YePHuY=aU Tjdh K>ǃT>VM!pűثdW dBH0BH`#jG!Ղ SȨYU%0BH`#jGׯ_oٲ%ñ>r:tF]]QF|XG{1ӧO;;;s\==!CdggW|ӰaÊPȌRp IjsBP(ܹc``p@+[[۫W>:mppP( nذaE#NNNϟ? &L0`˗/W4hmmM Y 0Y~MDHHu֭ݻWݻw+d/(//OKKKݻ.qxb/c2#""ڵkW+ +^۷o_񑧧}D'NdH*}ޥH$5܋v3>HK@T~_+٫͛7Yŗ~~~UmtHDR4 =Zit6H-˾遚{m8}ػF=Csasv VZ`@P>|ػwoG͛9s/D"ы/f͚+{Ubf D3gTw8ʽj_Wצ% 'bM},:uͲ-v5|Gٗ@,L*L7SwǢ tM>s#dˏu]:$?~ܩS'--- OO7nT~+++555[[ݻwW|9t萵.HiV._}^oySHw$7y tnt~20e/rJy|Q)wO ow}@v$q@lkzYkWeVi^%QJPs6>ݮ}xէF\} M5X*]j;vwbqDDĴi&MDvE$yHh$4oP񎙦QbaJCPba${_* 58+ȗJ]p'+t uַox ɓ'ϝ;H}@c.83_X4R^Vi\vd[:7hXϚdwŗIDRB*!~>-zZoqqq"(11|jR)r.gikgjٟp*:AFwpצ߹zܖn?uݯ j.##/ϏU?e8?)̱cǢɮBϟoffFb8"T*---p­[ȮE p*yB䈈VWW1cƐXB$?ȮB"##O8Av!-X*ŋT}A!ՂBs!T >B}R-!Z0BH`#jBtbA>4>& ! ɮ$"K@1 OȮ #41lHvUUv8S N8m&I~|K)UTpvX GWL~۴~BbWf>R7@v̠dWQeZgMyC<ѳWRAtTG 4GH0Ke&j!}DEGHq0a#8J"qsQR}D o䇡o}nȾ>Kf}"urPJ|g%-'$Pm(A[3A[YܽĴD^ʀel7e\߼=ɮ[-2000~a|_lahis鹷eGұGW]{ >[N>+Zh] t_dZՓѳ Έ\Uv,_T `(^>5sT05Ǎr]i%^'VX"{Pba${=͆'P6;"wjp+b~ K%B60!TF=?_5A,4^mkM;&ۮRPP$sU?w3Ӳ E|ly 03ԭg̪Gs#>+b왏Wtakb$X>O!Vϒm|˚7 !tG$)}G606JYŒ1ɻjRNl]'M5O׵>]^@_mǺC?}0͎T^ʅ~/vɥRPg<[6Ҥ>v y1߾I籎.&tkGvuHn[mFTkdC!2)Y֞OJSc1t1&jzu6{J3w^x𷩻nٸ"hl`TuǕg]`K̚Y^a١FisɮHj]yӰל:o. Q;h6|FdWAE8}_f^,6{T \{û9 E7_xJvE)>D:UY_ꭥQwd2mx7gD:oU^a !(;\x6ކm5-boW|mkAHQ0r 'n`0`:dT,s,e^AH!0_ E]\vSI=`gdׂB`/b23#Ҍ媣 z.9Z?}aEc [{rUR&}WeC sKv,O*^iGDHi-׉Юr_,n;b)1}Pq}܄"#@6K'xqjL},ÖL u բb3G+&w?t-(+ɿz:vjhf~/-SiH` 2r 4yk{.<{}aÄγ~ټ;155;YJv_!B?rTT|WE%X5:ήv+.UQWgO[Xv +(*H:j,9tE^>Bq^sQznW^0懫eq9쬼~cUz~esL'uWv*c}9[WfInb!mE1%T>|>$ټbKS//[ci[= e:{.vZqcÖHKUkjI#HA=ZIv 4dT*Kk&Vc_>fig21G] Jeo:6{xa[إXLU_4uf*I YHdA,\fz/M.{K{jdk"XW&-70=r=xB_B`76}pH^إ|(b.0ikXE%2Dͪak3|ѠE f i \{~9oe[sž/kO~/(:|F~+G.徱>>m"hrO3֩Q]k!S_n B|q9l˩^z /_$exСC`Сϟ' ljЦ5J$i+K }T`/ ϖ-T a/;6|K!1ŭF3&}9W+sAuW!>t5P y5s}mL/W]-npTR@dٵ 1-ݗLc]nkښr .GJ?^J~ꛐ]B cL/׎.E^Ks.,sMJv9) >ka>bٱl^1)@(7\jrqeTkaY1Ҫalb'2xdW@9N?~N_GuȮ!G?dawrv?0oZy%}t'*>%( A8b䱛! |@G' #3C S=&fUyE鼛ϣCd2FtwSWKv)> u6k@n-||2w8)ɭq 2mtr,ҚW &ѻm:ʞ*GDWjZ哻? 65 +(= 5&T)fJդLq~F-[/J?F|zd0L M uinխ1T >5KN.rJq~}x -L7P{5(#ggfCQiA@BZd0|;G$I;#NXTN#X &k=7cN4`0UuUL|ŝظLg2m,Ƃ~0_z&:= m}KE_uXmj%^vD?cƽ9 hnܘ9$eM\n 0b(}rABZTJ<{U|VObin$>"G  l𧗥Ǐ6VczYy}{'JDΛ]vEjab?apF\\C{mWD+%㽜-ɚPA;/=}]wGQvїO;{R_6ՈK猹F~ Fsó[ਗ|ߤgBUVPAQ^-IFlunxBZ/bMZ)U*2MK?^+VMKݦsYܸ6%I빥\,kOT@Lť…;Ēa][.EGNa (r~>R"Mʈez[7ԫ_6YA ^ø{k:)Hӓ\㔐q?yUA3x&dRp/8B~sUǻG]ڴMl :z %W9&٩O׵'>oweŇ/8TaG$*;j9 ~稳Y[O=L WXZ} sU7jo3/6וIU5l5s4ŠSJH/sͱn%])*fWY8Ưlyq^fF:ZsGvY\"ƾʄW >k1k#:2J_E^n4X sҘ6kXDP;k!ϣkk}jR ] ;YڮM]a#TڡfFoTgE#f{{24XsMrY9|/Ov9_qi|JN]cdK0W!^-ܛYm*zyY,9}2 =Djp|_ɘ Ʀ9 t5_~17 ƳY/ڮezdp>B!W&]FM|lB=wjTXy%_ HJNFUT>Bi:Tu]+'m:È-'H,WNѻ]S }Q>Uʩ= hs3$:>cVژ71CRks_Uw;fG#*!ޟ]]u] G:ds6ػ`yc=4::ST:=J ~2XԀ6>\){su!Ͽ\_2+:|',۪v2lĥwdWR ={ 8,ۉOIjhOrg7AO{{:.mƝr|xNOR܀Ϸ?sektԵzq9~Z<ijn\rͭ*УA/v#o&<)=翔\|D_8j\lk=t>3)!ezn?ueV=D,ʈ8m}JĂЬGS%<0iC* 58Ӈ 2I(Li;i 9QO) @BaqU[ho/i}I= l,NT8΃hyhɸWGKĂT~֬'k*x}֥g >GSM2 Y,ŏG|׏vlcVi^Vi\#zW mxyy D?ߧVNyu4MLmb-.g?Z2W.m5Jehu]ZT,*Ҩ3B>,=ozs}l[jV\VBv!է)woڮf'n(G/j6?t&K!gggkQ .ȾCX TG</#EDG)$p{K.dW YYYdP5ݡCG]jժI&$U\FBՁy}a#T8 s}D_Uǧ>u TuUG8ij(W̒\i>>g>U9CM82Bkσ s|,\!U|_G(3|vdC?QA90w.K~Q˞jzm>dB-*S*Mv!}adA􅹏*#s1}DW2%2䪓]0h sUT(G􅹏*S*10Qep|G sUqHyc> \>|1|"/}:M}yRtYyEdGa#G_d}nL6q+O[!E|D_ =<L u%gNϮ]|y\"5|D_ l5֨.pf|rJݚbKrP3U TkCZhr"%g }d0LvuT<0t;9,%:{Y?Hܫ]&6dWGED_;jd2[u?V}xσ s}G=S.v"ebѽZ1%. }a j,Hl9eQ˧Dbu}b58CR]8,1-o̊Iy5l L뺈0cwR uՎrw3?v\ /֤5σ s_\{} A?bn;yrMdU>g~M<`l|Z_w-bfMŇnaF_SG%>vvmDv-藘h/^KÌ sFyWG2)qyz:ԪIZNgkA6!:ܯ߀XHκBհKG6{_.>69KX"tQ;<&n?v3Rb'tyBяݖׂTowG_#5U͌tn7E-HWWd_~xܑR?}x/#=-C=%n>R Z6 IQ=[=yBcڷ7L 59R^&t>/\Fy`Uܯ6%.Qf-M}f^ѠD%}^,Ms UjIEoS7ַǜg?E5407gcQ}q>/J uz ),s7h_!&ڧ֌13f?=%+|7תj u kjcmbc~K #[?ť][.p>/ܯ,V[;4[{~fnQ_ywmA D"}h*{WPWPAvMB6] pN] l}ӳ j,KmTd=7|}Oɑ~>-\FΝ,!sKȮ>{QPOQS3}Y0'~!D58S#m[e]{ZPd^b2^ 8 yj=mzJ@?OGuWv-:߫d"w&Հy}2(kk L$exa,&W Sv$NxbR٥aBj سxVĵG]6` #:ܗs#] p[21[.KĬ{פ)& G/l?ƺ'R Ȯ}L$Yߝ? >#ܗv-l. 61s蒣|_T{mw$_12\xQWwf߇wy8 Yqp~a`a7ۻ}vMd׉`a{ѐFMr !O IO BJOo?]r6XtTTEq^~Q/b@W;.\[^4OW)澢H 瞥f71ơY:,j*zsH ]rvX\J@d߫x \{T=t8=\P{#~>}ŒJ[1B7 hPǰ6WGkI sesW bAQ/( J#ަF'dTb1[7mл].vښrN|vݬ)>B$ȸ̘L / rZ%Ɖu]O0PܰySa][joH ʊdOW;_WIX,PRxMJAqi!_PX,(, Kg~G$w4-Ovuth[6od̥p>/}0 {+S{+S Zv)om聃$" 5f#LtE'z6hFȮ||]D_8΃'-,VYO\iX+Ɨ ʊej=BیܚU,*18ʛHv-U 澪K%Gb.? <[׆8u^LSVzɔhM "s_u@\Wηi]TWBaʟ;.GamԦCYJA%b~7M5nkAj0U_ꋥB2_տoYvWȊ^ij(Y>gHn{- ujq ] BUrrv2Y6c@5fufJ[-&/\׷iJRPR)hs_gT !UZEQbdeŢu{k'.Jb@Rfitv+'sM㽝V4ҷZ俙CX ꈆ0oҳLv_pF$ Kjc /XzqM暓]&HZRBJv!U q^rU|!7ꚷʴ)r_3 sjzYy6#  Gv_Kx ~vյU͋hJ>zTޥB*H c#TЧq#r$ܧ'A4ES Y(B< >\}DOr q|>#Prhs=QB*H"'B*E6bRS PԢn'JCGRZ>ZC+0.`O-:vpGnJigIn=-@ N{`m?^%e}n{롹ܰ׹o@BH Qpc-JmNju=<ʢ|}_O-:z2lĥwdW:dypX+;fIi+vfGhṿ'L oyÕ/CϧN|R_'_m&oZУAGC3ˇh28΃h s_y.6420̛[77]PL|_lahis鹷Ξiiq GclmZ?.qMF?+$NW*;UTv.8q]6DSҭ'{a[/4WI%zYf}(W㊥Hg: 2Gh\-Xz@CsIr֔|]6~_}]J4꾢o7X>O!Vy-Ƿ>?XTݯ,r`@SYM m6}m:ccc]R뺈8:z_~/9vˊ >āZo⃿N]?hdRSG_ӷ"J#T.Myp>ZHF/"RA zTͮb >q>'ZHI&<~j!DqO"D#r.σhRAmqDKT? u]#T}}DCT? u]|"'}D98΃B:Us#k ~%|.u-aWј+>[@YPlcQmL}DCjX-Lp2sfku%0Hf sWs݁`ҼsDu]|"'wT@(.Mv!yR(Z*TX\u}|DOT?TPP +ʏ|DOS* L-QRAWd *.?8h꧖ )} sfKrع#׃`Δ'\>'}Jߧ5*21p^x/-Ԫ%*m QR[6070l'WtQŜ7#),_pDOd0qcRz IDAT!q}%ͭ]^6>j6=QR5c{2ШIȮ48hIu|~bZnv>?W\(XfvM^|C0 3CS:ƺ&T XܯYD_ܻq_IP_<K[ɥQwζ d]6σ s>f:w' b̡m&z&:dWWSAedsxű7ExUXoְv};2$Ouɮ* 6xt )A0ͺ۷inƢCdo=]r{oPs#]Ku]#zB~QWLƠN73!(21mlW_;gֺ1Y%.vPKLi9=?duj{Яd0zkzc횖 E^9{/b σyMa+p/p6ab3:dWDu6ko}b24ysmaH bol,i#Tճ^}.ɢlP.Rˏ"|gPmMoDw-e%o%J.[ymaCP3X1>P ]-.<WqrQp>-}8v3$WԨndB!j,q]`υg2.8"ڢ٥PR8թ]LGrI~P'FfE(iCpѐ~,^ϰH˻kKxViGuO`#٥PC@gW; . FSF<8є߯&<[6WR"pX>:~.nf",kܿ=8,2z3|G["kWe54wVoR)K"Ke]\VMa[N>JL35ԙ2cW~;Zf2b2հ_!>oσh*|ʦIv/}/lyf1<+cڭSSwe}Թ%AQI'V::݊pJ@t|Qgzmvߘ9]ȱV{ S}˥YLZ>!b01ЖWK{ii.uyͅc:|Z?f8/C=M#=?'tvVLnf'uMˏ#23P;㽠76尳s uuWM)@2OhhJyJe21֐ 뙖`i[JN.K3’ﶣYK$R+0Ry e:{.na )wa/8΃hKs_a "a.1+=i}ޯ a}j3ʖslXg!,}E`Y~ fhpv)ZiG.{WPWPAvMݠW&?oӫm&U޲wזMI$)9v^v;ŧBJH#%9UN>gђJ@'5 -@^+4rP@~vnpϵGs{&qԧDPݯL`?gl-Mvήvo13ߦц;J.OJ O98΃膡??<~+tBGw"*vSo2VP Ǎ\#Oo\:iٵ T4U)T7;xQR)ִrBp]6Os]=C=ͤ ^4kϢMy\> ~2q>)] d2ujN=! LJtwWJt}DO0FhLӰdB!RxLVGKyϢӺl8 .ELVRo'?f[i>u TE48`x7VMggo'GfUg+u y+0X,1MȜ"(ʃw`YstZ =a]WšW,k5Rk .۹#O]BAUP=mVČ1aġ'y6_gRnX0ߣ-.,>i$!T5=š._6qoT'z-x)$Ek}nј+j>cH`S~c]ׅ<ĶP"~^ .M!S'`ԅ6F>45 vfRdWPuPB߲79cӋt. 6itAm!9j|zT,tH3M ]BD Uݥ^ۃeY$}q(,]Pp]Vg^9cN+4Ԕ05BrO?ΦM|I/K|a=v{n ;ՆU%ܧp0oU'z<#]Z5Qd]Ɂ}1Lf}e3fTK`rF߽.Mٿs8PETLviUFsERҀm}n,x?:,ʥ MwwwI`!vaq^kw'vHtwwto\?$6=>̜/g=s朹/+pK%YyeA#$Ug <|"ߖkM2&L)h,u3" ޸t ןq)^19)O@X<1(t+DSH42N!)$DLSHtryKV}D$շg/Br1qǣQkl3>hvh@-d}ݴSkrxS#2pBe2b)QU:fVWLOBOLWLWLOLW}N?2RRUZSV[ZCll&gTUՅW$EOBOL]WLTJTROWL}P<vzX@e;s`&j=ꄇ~ݷPwqG8 v-OHsh zz]nLej 1%R 4D5D4DU~> hi(tP[_PJ|X# mS)=SI]S)=]QA55vASi-c|j8Eo_;l@i^pe/`sY=$kqIDN}Qu@ +KKKDZ''7WUSs@(y1 r撼UuP0v5鸻դz8EVBW-ןuu~vL/P4s)} 'Մ!菐Dp8bZ{ uQq60ҷ1:5"!*kQxPitiD@k7-oi/o&#]=k-?#7\X5J7R^ :`|pb[RE(4F<r)ݓ\W_,=1d3NTJmA%~+ * H5vNvd16`a@>ԓ%<҂IZ52גmvl v䀒~{[$yEo;6aDT${yPFZ)ڌ3  MLVTQ)'0EmXk3݇T:ޕ'-64 =]Z]15q%`uF9f՘4Pqwc#,U\"󢼀0 .}:kis%"g1*}@d7S[(MFJKEs҄F <cpx 0_^`B rR Br\9Hl={zʳ6BimZSDc \Jz9gwdrwߝ X4o5̖pAV:]^@{QL!35/}d,!/0L(KςgځvwP¦܆¼¦N1a9~)i9~)y)EYM%d.o:{v+2EchOg3Y/$>FGnhosW#7*߃J=(;Eu, ;̖c>Lݧ2hb|j[[ ̄􄪌r+8QNJvlY7^w(ʛܯ ]k8ob֞9{{_]fˏ۾uAGfyړUeW+ ?3:yt adi ܯs|2~ט:fX3)=;pXNyKqoQqxU["1Xoz~b"|.!nhg:}`}Xa  ?GSGH>pJCDj01rMxKL`P #pUqOROnJ uF91Ϭ˿Qâ1#6FUHqHRW^2sמ-#/GyD.^k8a Pv8~˵[7wGZM_uwA0 LY~|;եBx=ς?BG.&zD#AwRJ¡lh7_o )c;DTv:zț)/X4fɪc ,`4vӥ+ z8Tv}݋BrQsPZk դ;'Ҙ_vyDn;2wB£ioUt6,=![KjMΥǏ3?2+Kh{ndmXдuגO h,ן݌c 2]0oQܙxwrf=ڢЩ GQmbތnV faNzZB|;D=cnް|Ň6J7QZ%xE2=hUϘt o`tڦuv"2ly_i : *Zm2Y8An(- |3ť cpl4ZPIԩ;Մ|*/UOn ө1s3Ͼ~ZT//鸃?֚S#m2Y~0g>uYDGxF8 2'7})ɫFcpgX5HC1%,ԿhahmqhL~cI~cI`Iԗm4F$4 $? EUWHɮ/|o%5 )po?NvC7zxq u6P>3 IDATң*o0hfRzOz?"aŠh܍& a%V@oG\ʬayNd.#JrǙ 5Cg{u@ϲ?]_S?`v# Rkrl^kO8by߹T?KeΗړ2?*T1(| U M,"#?7_G]eQVgj-y{}`? V_\#/@D Bo0v%q[Ipi̙{ߙmUGo5]l#cLmy>?.ei 㬏G3=HFNR};՚x9^|~^A@BW^{YI6kj(=pjn5Ϫ/xJ#=s9=]C^ࡨL^U:a$>o ;rUq2X|vD'2v mfɢA5ɨ;+#3ZxˈE"J/[iu&w?A̠x3)V{k.qUGȍ8j\Y3@|퉗F0*f#Pw "WewX3NHy>hQ/pjz~ *h,eT\M~z= WG~o'=q۩b&6,n(odP/p8 ihy ";?jtuڜԚ'.%M ᓐdZ|9~+Mg.IEkl94Q9p9]rfVjJ#=Hw.anCt=1ُ{xXFdmb[-B#|rMJbaS)a`QJAcYIsů#|~gTas8 A9d~?kCXNU[sAJ䗇Δ;Gasl4 F1(4f,SOnr)񱍌qxEbXIW(ͦOf5x)t ,tC2((U7Tퟗ97`m(Z/"Doi醨K;}!?#Xu1׿Wv]WƠ5P(_/(L q=\@W$4v߆;UmlWuҐ\ ߎUVcјCVv PkN}=fjm#cUΗ>*p84_nAL ~3?͸0jπRlիziM0jһCz:yւ;l >ܲ@8UF'urM 's }ߢ>)-<"F1 ;dϾ0L~@an:7GWX>ߞkw0E^~m#e]WC v'%[b[w+.$>F (Dmyh}\ `GG+;͖Oz|Ek(>/@GӔk ܸ }fs38Y/=Sra#l^OSx55@ 3M}\JRuL2Okb#e5>!ׁvo0(Cd)d8Ec?`v+4{\?+SdS;84k9Z.\Q@4z6%:uo9*˜ƪ`/\p]Pi:XRRy>/;尿|i@ nv6'gro~E0^{ugm,Gh%yKdh-@ĥ8 vrғ ɗmeM8)vyz>C =a'ꟘW[o[@ԭqdGpL.}4WZs[ d(0]Q@m>6ן8di |w!s\9#Vp/33?mn-p|5ĶZu%_W7Rkwbj-& @G{%tjׅGjx.w\Sbx:SηR_Ht򪀃7loخ<,dDd`3_teWhy<q7OڶHwnLu}ԏ7/([lYS0 ( yzeT+܌%Fy^4;l]59i]BҗonUDѕ)/T{|LsPXwȀeٲ5sItOK f4TE oWPTvپ؋s?oIcENPdo9{Ѱ eAMHa^h=ٲ؍h{N6_Q36:6_خ$v='E?@A {PSis%;Dk4E_OC2/Y+L|U:QU!|X(m4}2@D_u5UmlekjŠğ| d) \ޭ )~m{x֊`!~p#4 ev[ M=('m8*kѿ_B\ppnAm42 f^!ftqꀃ E4=6Ξsݑ^KSs\j#n>>@3OKF:ecї5zWFs$2p53"׭ƿ]^DtˈVvſlل+Y,{cOMV;f{ 8l^W\D݇Y@kx ^[̏,  kD}e%c羔e;xe϶Y8.A_d ]DGT%ceU|(2x}3tLnƪ¼ץЩ |wP-#9*ڰ||pw7Q7! ǥQb59ssz,zAcf@+x:y>^Q:9 +?@g6ylݧ1G\Zϋ%s4#?۾dj,ҝϡ C$xDNml 9YGnU~6H5 X4?CA W,ԝ:JޢfOع^uݿUB4ҟޣa(>:6x*&9-uKА.Hh~6LꟅy! YL{w8NzU L[[8,TF^*`h(:\/U/R8 2iN뷹 U&:=z[-U]I4Jc3)>TV'TepXh^~waAa#P(t>>lk+ @cͥFIJrR < =}&DQo']I>m!'<>t@d= I i[[ծ?GG$ @Huws[#U)B OU[&jKl/Ki?ªv'bQP9Bk :t#&Tt  18Z[HY\qs?WeFʋ&jvC9Uu^ HvlDqzu+7?闸Қ÷|=6k fBȀbEWō3q$:wg3ʘB }W2hj!_}ĬR5}m3ux6qZ:*yXSfZJĺǾمDIQAh!^, }+.h,];ƽ^{e|h6҃a8s=B?=I/q[=ws]U?@դzջ jJ]5띟߸M&C',)J-{\8:p(1%lj>B  Ǚn՞~xEwWNw!VrMֻ4 ʫ/>8@x 8WSrxsL7 M{/x;c5ws;c5x2Ye>3N<"S2kj&ٗx2b7Y=V\#2mB7 4QiWZ9}pJHB^HB)02$=qwo1uv+s cHK: 6a}1x+V}ӿFe}Uvw5wՃ~!Cc m iʔ/ESvxvT>/B/Ӈ7W6se3۽3}sg~{|:4dH?(  ѕ)b2>锉*ry :bҊ>U PYf:R͞5y0Y#M O9&-ګ"mt]d8jvֻ Ơcgt,ן. gxfG1|2B[|VϝA -QQ濾V^'/lN !KϿ3Dt/ MuLuʪĽ H,,7lXcaLLQ>/*Nk" C#|lbOzt[}9 cۿ|xyuqk/[RU ^0Eu,}~`ݭ)tr4ox9Db0c)-&Iɿax 8#f;KkKCUw#ش iY~ɂ,Cq:r5pEa7J P(`gjgZPV[JXR~XRQ/dF'}^v;?ϫ/ٮ{錗 . lh&Y<9̖Ma6W*rb{:_Ycpay;~c\>m Ι6rOc?fC?4A~ipaEq]h87 &YzZf?I+z)fzJV?m<:r(G%.~PiT ^OLӴ>Nhb'}n~FѨqZ,2 ?DgDgi+KyZ64&phApiLDEϰ.g?ZP26DU&_;,V%/,k]>>pX${I YFe} Ij%j>R{⫀D+Ws/XIHZ= tjJ"UFqZ5Ha0Saw]1‰.rzGG=3TR_yp ^,AN@REH+')R'z!_Ha7k1[@Xc9\3;;$-06;06[SIbV@XlqSyaSy'v߿80VъAz뢰-%#]?o\vQkgg9s2llPEIPPT~37u*@ګW4:Λ`|͐ gyKXFd d:Dtt htƛ/CukCGp[/v1$y~zWЉvzJ/( ʂN>ӹ?:E<2b/C;~oؙcg1J-|:0u`(SA $(*n@'f ͤI(upuQp(2P2P.{6(9202Pw= IDATQZd1= >,HU[?_!F#n]vɏ(2BkgPuQ \GIFtbs%3i8)Ɉr[AE \V@iKrO^|g܇Q^cڏPp52PЩٔ8bT:yBaE?OWaՍ}0Ea36~oQf4}-.[\zN..ǨZ7SH q!z'Te 1M]VRxC>a 3:G?`1h[][ݤ2/߈ ttuYWs'k}c"3םx"aja+<&I{.d #4@]v@G0Ґ3kVO}^'9Nn&B|hsݤb}z~喳o@ a}v?O\Uv 񹲶TGatۡQ <@JT௹fz=*"^||uRx+/]0a';x%2S0ǁGC. A=pc1by6(mPm3~?ACQ\0Wim5! Hc ]15茝޷(h*^B)V2F%jڿp) ǭm[ŠRb23ν{tCCA:BXL- #6T"˸{Jn9 _\xk+]f3撂綸ЦF_6:),2A7ןxBQ!>nk a%5z8YЇFgU7[HͭVrs a 2o+̠ SiB(B uMس/qPx> A ,$#$ %7KJՍ-$JF  `rKk(?'l$-:,Pc%(ɈK ɋt)OX~Vc2KRr˳%ᐇA:ƭ<"$,5u!S37JrjKuL7i%Q3 b #/% %$#.kk220PT~fg7d -,9? :;(6:VR#x|bUQͭVrs+UT]TUY۔]]箟8 1lsr ~DI)O*,I.kl!uh *ħ %,/)(#σoȃx 8 0Hv-mRbC~YMc 1̋DNBD[~ϘKS ^G~sO}Mc\؋g40qBВsʒrʓK&'生{MUN|)?öV%(6sXzhbJo.+!d)o)$# %"'.A:PVJ/%6Pe e ޡiA^GKmW;=s]E4zڱ>`bض,$z` (0Ґk5#UXs=%s=%B O. /&Yv4e]GY$Si! yޡirId*󢞚m , Mf H^iMBfiBfIbvY~YK "lt]mu4:ב)-Bx~Q%5 Oఘc Nu!)<@|#2n .:U3lWϴ㶂!Sh/oQ(S[='k(3$/shOXzaE1*kF68ֿ.[6'_d桛>Mm| IM/p򕞚̺#Gjp[A(iHmn#s}p Huo   8;:p;8.wڶ[GLm֯Y/{/l#SfC89o O˫Xw⥡m3NLk,mn#/;$51qyҁGK-#M~ MpγM<sӿo}*~acXs0BN?!f3~5_1aJk%Mu$2u݉y2_3a=s6 -OiסqA>8,蚉=2F58mtݗ>CH?&j @ςg*̛bqυ5˦Zou@?fmW5y7)L^Ri &+swtiq<<ܿYJc_|MX|Ћ+I-UBx~\t+òFy'.rܩș֫IӼ_2j [f5^} ˺63]7.Wɀ9^63ٮ X͹1FOL)?y}}1M7 J%qH3e\s sGQ C^t9W(-ry,eT6kv y)s[gఘA9R 0d/z6(S #MnwcҍEf pw61y/NPa،b1xi|OBσ_9/p]ISQغZJR*- h]}FJs(͟w>tP yJ f3фLs7<l{#3Z`!ye'=Iՙ# mS7Fm?* 'G|6sw+FN0ȹ'A+.y{f80~yjn0xv9n)S,1u?ymCkݟ;j2wNUN?in?ꋯ 2vgg=xƑK FRi{Gg>DVϴς~ּ9,&zrNKƛ܊Ǘ Ü ;Ϳo q?b<:'~&"!Zhpypmcߥ5O-L-JhzﲺKu /< k>UvީPp󦢺D"d{])󫶝2r9t2VVհ7 '|^SR]߼SI=@LwÏyT"'T@Pe54:Wiz "]1S],/#,@M30a< L˯`>D\W#4dR]߬ /(S]O4]>tPgL*P{8 ꆖv_H/Y~;uI~J*9&ѯ(ʠ8?ϓUHp}֓>:gUu=ïSfswp"ue5Z(~ۑ/j`EJ )"uăΖo3Vi!e,)*PBgf*&0Ms}uK;f'}8 - a_u8xpVc?\Us£ۨL~K $W33URDڤ';Ly+.=⿳yLK~+WT?œom<-_s3P^X=M@)aq;qk]S[]ck'sƏ8psQE)q=LWVU ?OAYm?u:rL?~k]ckmC{_{9>trK4:@ jwήvz=T[ϩ÷00Qա"_&[MA[R? Bfjl(U45<ГY˄ y l۽#*+/)JL)襂bqnh)tZ6LWqѧfR ptX,:S ]%f%,v&,':Sֹs\we[V=}tqgӯ>8Ć są]񺴘9=T[|p,1$NEH^KD快K؋ џ}!nNf&D Q{|4u?ew붿37ބ_wxމ;#B=.=kOQ̖_Z,<կc7\5wiէq̷:t}+e6quy'mǼy[-:ck$ +X9&[ϙ妾gnIu_}9{]2[Y&iա* j,wL,i7`/E5fjk.0uV6(yOF7U)[nּ?BM^'ȳw-lg>)by 6\cLlG_2 *epīOcF}8)0HOyg<>~'}x)-%'l7j>]"׋l}bF69,fpBͭW>GgpX uT8HiQ7Tx~@2U #S ]tn!㢩,V5^,[Z_waJz7^sXy1iZ[]0f=/zs̄o^MC?BԡsMj!GVLuhojMJ _q<`n]3w5}NgGZ{mJ~ˆZɡSb:spY{+ &;#Ҏ)T3 [Y8y`_й+wbYƲy/6՘e(d{C&F89o(¼{y9݈FI_HU.5Ex' x 0 !K\Y)eԗCXsYgi4=);NRk2icBhrni4ɏĦj=\l[;+.oPnMC\㋷s]ߓˋdW)TEy^C O"C|=$Ӏ.||u yn/-A6@\EAI$ yGgg$f}`o7tA tW,U9kؐ"GlؐcCe'ܿޣ nM­Q6fS {kte %._ȫiV7BnMCUmc,:wz }6^ d7SGȯ/mP\-.3)'euzBs/gZ .LH/HHǫŌ{'"oW3qscab\~fy܌|^yU}Wڂ4*A(tN(FсhA+'HIr\*T2JP*Z#$+ߓ!cgT-{~X%k(A(kϴ)r 2yӳ4ppfdmkn\PZk||u`ҩC"="z鼍G;QxaW~comz~ܵ'm,vxyRW#x5Bnu]](YNCCQT&Q9[28֦k3age*гS\Fz/# UJ"ĄڶۭY"E׋ w sʟO¤.0O )΢ӌ4Sc[<)\)* L!`E'yx/!A};v|R1}lȿ`ɛ0-2Xݽ È65O a^1ǧ 8,:"?vV²CbYFm_2 z6 M`fLW]VwgA}<-.O~X8'][F={Kava ξG^IDAT1, m{{ՕU*s/.q!"L b4gzyy}#]PA=d D t.Ltu"yͽ=_\4DaCy+nD-̉L$J D2'*@Rb`P*̩~r&#mk ?k0߃Ke7CON~TWc7iBlQk5 eA^+;FKlK ƌʤ垙|O6!mloP4Npv+ꈛ[|:17$u#9Qagd]raNS*lѵՆhTI/}SV/_:<# et#_7L!JavnDv6ܭ k CG6?9%(ޜf3Loe+^FV/FTϸE'2 ^&BPQ]/%2XHbBRkTj!h *̢SY Ncѩ,:֢s ?ZE2DdO#6<8SVFc~ziTc+*dj go1yH)Y}[1Vc˰\^tX߼V0䇮yS/uD CQΣpwݎmIH'Ti2 xe5<*x%{ԧԁGuӮp&H ."t % R'sz,sS*Ka`>w9ni)31 sh6 mQH_p%%/^!ӱ!{HJ^swjGc#61tPOn]тU~v z ˙~|uɱ |7ö ySzqF7Q\ѿz@>sFtr&wڵr늈^ ez#hTIVܚyTU-V-}WkgũWs 8fWod9pM$I)#΍߱f -S5Iٻ2iք~3ƅ;ӮwyvVcLa~wSаO *%߳>U|ܜֶöADf-]8FނZ``okt:)\X+2Z 1EfXm .}jwk 6>8x^ gkĆM8z?yKLCJ翞J*5e-dhm5 sܹI}s] -1oHd 024mW>;"}|OJq4QםUbmd{KCGP,j{K?W0~82}gLH*+-VjT' 6>8(i^ϑ<yCjm o_keܿN}'>p"DV6-=a0{b+3'}{UcD|~H"7t8D"~+Lp 2}Wn]ԙڂSe3)OM>\ώ"ͽ\vY˂?ro6Dz-MoiD_ RWRFOFm8z=bIkx5Yǿ4=]h|;”;iO3Mkpe(O'P4\yt\>oy;D,\9$Y#43?ofo8Px(TU%?wX2m(B~ԏ+f֩UKk=ݛsHX06mxa}lnz?>]xtզL"]Otg0'?I oiGz :|eNgdPR Oj |W/['njzwjv\jcn<[%'oMZr?x9kJ YN >wԭ+W DbW{>9.B~"aYWmD [q;'zSC+e;C[{,~驂8BlZ"W++36?847mwМ'4jCb/ IseN}Fӕ}C>ѫS/Ƿ*~M] ɕgl'Jk,÷KԾNY|wm/; 𙿵<#sK:͌htyZFIg<]u]E{0bXu6JY2m@OPT֬V$mꇙ1¸[6g׆B"9}9}7/MK~Kɻ[W[W`id˺W/^.m=T-劫R:7n߂$g0/leL O'43K_{U/ShJHs%+S&/ij/|1_m:hJD a$+B>B,nTUQ:1QˌutT[7[ofF;. 2e6Ҵ]F\i@ `C`LWk55xw鉼Zyõka VvLk6ʞicǴcZOͤ_ٗ6*%\qU+h,o䊫+UF^x="IdcvLk[9ĔbQt2F.q잓>W3V!ɘI=`+Eo_!Wu^uۅ$%/ƺ9X:(D ծw$c٭3A!ߛ}M}nR8QX7`_tg_^s|۹*F!}'HQ8?@ W|(=){cۼ6O"'F3zB>{=+!NV  wBϋ߹WCڛŷ7h`Ga!]}p,^ tf;>nl"oJ&+J_ dҩO׾YTя۲L&a۰jY7(jI-Xtcf+e X^QPTrX_% NC<P$ÆtYoߜ-hlw+==[?*&=<^ᭌ0T&0jO HKД3r%= țQ(bR,S4gX2{rbD[O{$NH@5% $7- iDBS0*X%}g`'pЕNvmne ZM0^;/EU=İ0.ޘJ*027/6i?Rz4ϖfy!yag@Kmm)b@2bsơ_kTI$ܞ̜fbOϿ iUцncOc\#/O`'/}ƺF ʹ#]@NOyx?]Hl _lJew](, cec}E᜾ Ag5·'(Um =g"Hg ]. 눮Y=* ֋z:7 D0ywqɿ=/ ۞-Dw6f aF(#H{gȼjࣘӶWJ09|U> iR:YLA-=mvI CC8j)%$qJ1?l  ўSg{"苀?΢ґW#R(K-%"ŊURXI#PG}\cֿ/cQI nFq^.#ϸ@0/>n#H>~M{{bzY7ytP?JEgd4ڠ+r""lQZy0F܇gdd:@or&g{qr,L;СuHBR鍘ℸ[ [3ѨtV)gwe(o1uacJ_)5* 8\#]  m}ɂ%IRl:dې? 78!(>*'Izs:umѓDnzLs%C`˰2hA]|7c @'ӆ8w2y%[ ]OM6aLQ|lIBaC9B$18>H'z[{Bl?;aV][ZRRYPġ]dQ5 Y~Z~j/=*+yBt4 amwY]ZUVrUFjû5%&73dg˰4`C<7WWgTeVeU?lP46D$8۹8:8s\M6_9ZZ[*╊x%"^[*5V +!1Zz$ƌ H;ׅ~sZLW_46Dq2s5qp6Ḛ:p\LM8m>H))qTkԜ1h aô.ɕ%"nO%"^D|\fLe0(t&΢0:LgRn3t&0d:DeTKTRJ&U%*D%-VI4gedlq6qp1Ḛ:8p\M\M+ABydjEY#D+rKDbT+qfBflbbq1up18pM8f[t HSh*)~.Q*T-kږeTURF$әͮ 62zUH.IENDB`./pyke-1.1.1/doc/html/images/plan1.png0000644000175000017500000000746511346504626016333 0ustar lambylambyPNG  IHDRvuWsBITO pHYsIDATx}LSWO_,a )/TQ $#FJ6L 8ye35d180 Ʋed7\و+ PK h{ӧ>Whoo9nOO={\Sol~G[L͡t:l0 Va ;&33d)))UUUd0}ˁ r 1b  r 1b  rO?x<Fkc玱t҃p7_ Bz;r=z0Trz=;ІG6F+//L&shhH$9;;;:: BB3ܹsG XÇK$Ơ %*++i4t:._pT*պuwGGGwwVU( b0CKKK{{ܜH$JKKŋmmmJ.))rpp(,,\W5ű~1xٯ͹&&&6fSTvL&na%xnr2sc///ӈ'''rao 78{[ZZBBB|`&`0<== ΰ.{b9FO\\\NNaa#GJX,^L&j:q 1FR,fd $&&|rmm 0N۬W^iڞQoعc>PWA>'N0]Ι3gbcc\nQQ?ۢwy|ᇱKO(Uԛ`ȗ/~@A9A 1c@A9A 1c@A9A 1cĠp`mmuh4t:`XTP1MMMMMMdvP1VHTvvv.--Maaa, Yc,.._:ٳzwwwooo'[ i Y _GGG#""L&Ɓo7c `nxxxOOϺ Zի$QWWp87oj/ÇH>HAqW*Ν'N'r<22@ӯ\b^vr ^SSpvv0t ~DDEu1g"##e2[.ؾrss޽{e#333111VPP[%1QQQk|+{aaa/6^0L,Ƚ{΀oݿk cN:)K2??kxZD-$-nݺr^^^Q,l@ppT*tu#J;+HNNNUUU֩ سc4Mii#׷RSSYSSS,حc$Ihh(fffUH (HȒUءcVWWD߿lET*=x fbl {sLWWׁힼ<9/3TBB}&Ǩꢢ"xg߾}?&[߿ S;qǏcl*`XXbm)P1KKKyyy0]]]d+z=kkk0n+&&j-ڎimmkqq*ي@a:f~~>33R1pihh8::j49 cfffRRRرcvsˆBTrLmm-lV.{-JCaj8fjjJSVd(8Lܻw)\\\b1.`Ŝn+c|z{{ץY@TTmsDF pvvV*h#1]fx]xA͞KD3g#1=_QT0E&1 66vrr\$b4pٳgs <5 ɎYXXӟj1 pvvxxxԐ+08 d׶?~ð~Ν;Gy6Y8,Ht:ñ~Lc FWW͛79ǧDU6˺ax!//2t /Fp,laH`0 =!11Y~ ͙ =_r}0}nVF,Kќ>}ѣr|ddddddxxxyy_~1ƣGN9ydeeYM:VWllxoe[X Qʞ>}U-,,/,,8СCEEEVV;rY`  83 l="..l PXXc)Ͽ h44ƴ"loAЛ,@A9A 1c@A9A 1cİcc[mb-1c'7_[,f.31{J:>11J{ K)w,&?zzz~Wߖ|344$BB0>02:'))IRh4Zii)r}*̼⒑hll bX)ef9F(>zP__P__?o@ˆ>77'`իWJL&{ykkZP( @iLIIIWWWwwԔ|^&H&'']?󉉉>Lv@rrrqqb[[[gg\L造ǣ.]8hXiR_۫>11avJk.L@m$m??/^D\p}}}p[*z{{8ˇUf9fyyy*ꭷޚussST>g1?2 (f!O<9~8|F` 9h0L`0:Vwu%􎎎hwww___a31f[QQ7%544手0 .l-%ɴZ-\a 0nnx\ۣ/,[|su%)'oqp'݌~Y`+ ,np /E<-$N 2|707;{,4 ˘6CtUeupJ8[g?, eonƫZ1H O\ 3% S^p¥E*I4C$`YfSg}`bǦEڏ~eYrxdo+ջHŻsF(<{pt_=ZGqXu2m~vrxGk(FE(ΧS4'lnNޘ=!%LPKuwY9!HOߒͿw`4p}7hlH!0ߟߨHar 83lA)¦h$]j~gp#vzCVr$lwV):nX?$ϳ EzjAY.!DG6>;Ω9oO|dYO8:,*H>x`ً>F=Ytl;v+ݵWf]W"np+*jbr| pX)*2# 5!oa-HgHiB'!m\2Od$fB\$}MHJ#Ͱ( $EĊ` #Z OM }`v?eHz+V 8ny$߫E6 (2<bz@ DYI2Q/+#:!IE-{9a=v9WtTGĴް" EҩVڐH`C&Jsk4[aj7>~ZlgOΉbcy c-&q6<̢03% ;M'aBBX؇>4Y!%>*QW^{=ڿV쌝&΂4M9tyq5#BK a[=,i*Kh֖,ä00=Rٻ~8^kq GF*nkF j xFInSL1TH}{ApB{&K} ˫uơQB Fşbh2n[3k,B&yߴolk⻤hsVD}qV5,۵[C5,aZN`sݛZ3#kN;ML2|TB{!`n}TC8gB*rCfy$ݾ}ȇ!r!}]QĭS a0BN28ԎH<un7^֍쫉^e^Ig̦/fاlkt3^}aϷ^$y۫/ \ݵuWH#RE*G{پWq[<9z^  i}.=_kV=lZExg;/5yn\9%uqsD,g?nJw*Al! Q]lIwy[CA{Goa.TT3(ʁhmP&O9Mdkܷλk.+qO)Bm2q\$WOu`"Zg|ӧ=kӞz37pXz#;laSFVE@!O耸G'()Ë/:0f^z -=DY^zxq~┒٪ )ɷ׆-fEB_y_o;*^Bͅ)~3/e"f6*)+콈"zi֍بj3:ѺҷxڻT4:߉yvF^WO%/ /X?CޓgWIN2]Rs%s+Hp[. k6ظ8P|cy/OD₺ TM 5yJ8U'$6R<зy.s6uTf4i>ke@x;ߩlJuE=e(l~*@)FQĹ ]M@IQU@o**@dҶKed/Z(~XP-K0/S8#c0 ݇A 8yHz^Z=M_bQyƽ ?.% IzIӇ4H*16?shԢ hS+ %RxSiM-Ls2_z_ݱ@d|{*{akn a@ min.&K0}:b3҇ f}u"ѮT q{ici? Jc[{WWUazxtKmb$qڧ^qe!Q ׄ=OW7w!b/0R܀YKm}5zkH> LP$j>)QIcT!SsMۄ=O)Dl59ޥſ1d&x >?<כ' 0^,S./pyke-1.1.1/doc/html/images/plan1.dia0000644000175000017500000000205211346504626016267 0ustar lambylambyYK8WI2=0F{i{F&1;Fi-tC^<@~ĔU ߾JU¤6LPFL~ӧo#_'Vde &Rp"K qA G8'C0'8&|h|!ȊN9 JnDdfV](T+y^0pN`IL犒\AzMUv [WLplrD<}E9[V>x[l#^3Q,nƶ]\O7///Kfk"LW)RrJDƪՆIB!NeG_0/O.q k7V,:]-rS2]O,asNVτ6I;azl%")z7,ə4+4 -sṨ. L6T씂U9T( r/ uO.(m|1~7p&V1>e Z_%@kEx0Bvjh" \ "sXŶoGH9]BDlp;RT={o.KnQ`$Kϫ5,kEE+c[%J.PiDz+ؕpBɂ*x6iq?n,Ȋ, XM#ѻ}?ΦA+,$mTvsbYnŖSW> #;pp7xJnDpŊ'U*:iiAKU*[~RB^9'$oZomç/5|]T_WVtJ}J2*@|WkJQˏZek-gLӧPӧpZѮ"./pyke-1.1.1/doc/html/images/backtracking.dia0000644000175000017500000000406711346504626017707 0ustar lambylamby[o6)I= fK-盛e/k..&Y^Q\fYbZKN1pE~eH/xJϸE%rwOU׾TM\6>NΪ_uSS)ݟ um{Š/e<#nYfOMbvLhkL$-ՃV{Mrݽ(G|~~/c8g?lj΋42!Hmw|W6٘NMЋ(]$vӌAR*1qe0nFG*O+2(0ogid=\DWqEi1y0fƖ,omSFvM{۵>`*Ƈ}^Ϧ˫S4jfMդ9pĪ֊^Hĵaij U8J ס/AסF RC)]H#JuYӐ`"z@C^v.HkC@Kxi1&@R,KӉMUGս"µFl= vg,t޲Nq:C5lDkau;IIFoZ|(ϳК'MwFj2/m^P@vA3'|L%g|~:) 749?Hْ)ART ZRUrvļBKj,Tid3Ypl jEv-Kx;C .Pv$&QK~"ݪM^Ym(T+̙Dx.6l<*d̢1l4Ubih@#G:0DyF!L' %QXX0Ktp0SX$!cbv;)HRؓxbpR0 ) rBHەkXS(j JI"`W.8 }KyCm.yV!L+\Vxpqv`e@i=ZZh@ fq+j@-.njy }[z-l}W8R.۟#" c9w?4qn(h9`eJİ*R![[xbTHklnťʀ-nj- ߞ>80$ }ʌD "Ƶ{z^?Q%sjĽkQ.QNu*`q+CBfaL{|ؓE~hӡ{q{%"Ԉ qhQH&GM^OtA(.z*{Xlb$QZyi0j=<21İ1& XJ:=#ww݌L}9 \ 83FBb>!q#.zp:Bˌ>u2C>&QfvtRADu-n-tr7{UyFV&DچnϻoT F)f..{Db S{Fg"p*D6/nEsYk4שmX1C "qɾ " [#"g!SnD`E}8ə!|3}ayG4d2DܗbHIy]qD{AB]digs'Bn B"}W2.N\ B(3m ?UՏXl-1A {./pyke-1.1.1/doc/html/google45f8c2316aa06307.html0000644000175000017500000000005511346504626017463 0ustar lambylamby Ignore this... ./pyke-1.1.1/doc/html/sitemap.xml0000644000175000017500000001443011365364130015512 0ustar lambylamby http://pyke.sourceforge.net/about_pyke/cooking_functions.html 2008-10-27 monthly http://pyke.sourceforge.net/about_pyke/index.html 2009-11-02 monthly http://pyke.sourceforge.net/about_pyke/installing_pyke.html 2010-03-11 monthly http://pyke.sourceforge.net/about_pyke/modifying_pyke.html 2010-03-11 monthly http://pyke.sourceforge.net/about_pyke/steps_to_using_pyke.html 2008-10-27 monthly http://pyke.sourceforge.net/about_pyke/what_is_pyke.html 2008-10-27 monthly http://pyke.sourceforge.net/examples.html 2010-03-29 monthly http://pyke.sourceforge.net/index.html 2010-03-04 monthly http://pyke.sourceforge.net/knowledge_bases/fact_bases.html 2008-10-27 monthly http://pyke.sourceforge.net/knowledge_bases/index.html 2008-10-27 monthly http://pyke.sourceforge.net/knowledge_bases/question_bases.html 2010-03-05 monthly http://pyke.sourceforge.net/knowledge_bases/rule_bases.html 2008-10-27 monthly http://pyke.sourceforge.net/knowledge_bases/special.html 2010-03-05 monthly http://pyke.sourceforge.net/logic_programming/index.html 2009-05-14 monthly http://pyke.sourceforge.net/logic_programming/pattern_matching/index.html 2008-10-27 monthly http://pyke.sourceforge.net/logic_programming/pattern_matching/literal_patterns.html 2008-10-27 monthly http://pyke.sourceforge.net/logic_programming/pattern_matching/matching_patterns.html 2008-10-27 monthly http://pyke.sourceforge.net/logic_programming/pattern_matching/pathological_answer.html 2008-10-27 monthly http://pyke.sourceforge.net/logic_programming/pattern_matching/pattern_variables.html 2008-10-27 monthly http://pyke.sourceforge.net/logic_programming/pattern_matching/tuple_patterns.html 2008-10-27 monthly http://pyke.sourceforge.net/logic_programming/plans.html 2010-03-05 monthly http://pyke.sourceforge.net/logic_programming/rules/backward_chaining.html 2010-03-08 monthly http://pyke.sourceforge.net/logic_programming/rules/forward_chaining.html 2010-03-08 monthly http://pyke.sourceforge.net/logic_programming/rules/index.html 2008-10-27 monthly http://pyke.sourceforge.net/logic_programming/statements.html 2008-10-27 monthly http://pyke.sourceforge.net/PyCon2008-paper.html 2009-05-14 monthly http://pyke.sourceforge.net/pyke_syntax/index.html 2008-10-27 monthly http://pyke.sourceforge.net/pyke_syntax/kfb_syntax.html 2008-10-27 monthly http://pyke.sourceforge.net/pyke_syntax/kqb_syntax.html 2010-03-05 monthly http://pyke.sourceforge.net/pyke_syntax/krb_syntax/bc_rule.html 2008-10-27 monthly http://pyke.sourceforge.net/pyke_syntax/krb_syntax/compound_premise.html 2008-10-27 monthly http://pyke.sourceforge.net/pyke_syntax/krb_syntax/fc_rule.html 2008-10-27 monthly http://pyke.sourceforge.net/pyke_syntax/krb_syntax/index.html 2008-10-27 monthly http://pyke.sourceforge.net/pyke_syntax/krb_syntax/pattern.html 2009-05-14 monthly http://pyke.sourceforge.net/pyke_syntax/krb_syntax/python_premise.html 2009-02-15 monthly http://pyke.sourceforge.net/using_pyke/adding_facts.html 2010-03-08 monthly http://pyke.sourceforge.net/using_pyke/creating_engine.html 2010-03-29 monthly http://pyke.sourceforge.net/using_pyke/index.html 2010-03-10 monthly http://pyke.sourceforge.net/using_pyke/other_functions.html 2010-03-12 monthly http://pyke.sourceforge.net/using_pyke/proving_goals.html 2010-03-09 monthly ./pyke-1.1.1/doc/html/examples.html0000644000175000017500000003123011365362360016032 0ustar lambylamby Examples
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Examples

Several examples are included to help you become familiar with Pyke. These are all in an examples directory:

$ cd examples/towers_of_hanoi
$ python
>>> import driver
>>> driver.test(2)
got 1: ((0, 1), (0, 2), (1, 2))
got 2: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2))

Each example is in its own sub-directory and has a README.txt file to get you started. They all have .krb files and a Python module to run the example that also demonstrates how to call Pyke from your Python program.

Family_relations

This is a very good basic example to start with.

The family_relations example takes an initial set of facts about people (stated in a .kfb file):

son_of(david_r2, david_r, sarah_r)
daughter_of(shirley, david_r, sarah_r)

And figures out how any two people are related:

david_r2, shirley are ('brother', 'sister')

This same problem is solved in four different ways so that you can compare them:

  • Forward-chaining only
  • Backward-chaining only
  • Backward-chaining only with a few rule optimizations that make the rules run 100 times faster!
  • A mix of forward-chaining and backward-chaining with some use of plans added too.

The driver.py program also demonstrates how to use krb_traceback and the print_stats function.

Knapsack

At the PyCon 2008 conference, somebody asked about the knapsack problem. We found a solution in Prolog here (starting on page 19), and rewrote it in Pyke. This is a quick simple little example.

Sqlgen

Pyke was originally developed as the control component for a web framework. This example shows how Pyke can automatically generate SQL SELECT statements, given a set of tables that the calling program has keys to and a tuple of the desired column names. Column names specified at the top-level in this tuple are expected to have a single value each. Nested tuples are used when multiple rows are expected. The column names in nested tuples make up the columns in the result rows.

The top-level goal returns a plan that takes the key values for the initial set of tables given to the goal and returns an immutable dictionary mapping the column names to the values retrieved from the database. The plan may be used repeatedly without re-running the rules each time to figure out the SELECT statements. Thus, this acts like a SELECT statement compiler resulting in queries with virtually no extra overhead. It is not, however, an Object Relational Mapper (ORM).

The data model used for the requested columns is that tables inherit the columns from tables they link to. So if there is a 1-many relationship between tables A and B (1 A row has many B rows), the B table inherits the columns from the A table through it's link to table A. The Pyke rules will automatically figure out the table joins for this.

The program automatically introspects the schema information. For this example, it assumes that id is the primary key for each table, and that when one table links to another, it uses the target table name suffixed with _id as the column name.

This example was originally done using MySQL and includes the .sql files to create the database, tables, and example data. The example has since been converted to use the Sqlite3 database to make it easier to run, as Sqlite3 does not require any setup (the Sqlite3 database file is included in the example).

Sqlgen lacks more general capabilities that would be required for real use, but may serve as a starting point for another project that's more complete.

This example also has much more elaborate rules than the prior two examples and is a very real example of generating plans.

Web_framework

This example completes the Python web framework demo by adding rules to automatically generate code to render HTML templates from the HTMLTemplate package (you can run pip install HTMLTemplate or easy_install HTMLTemplate to install the HTMLTemplate package). This example uses the sqlgen example, above, to generate the SQL statements.

An HTMLTemplate does not include anything resembling program code in it, so that your graphics designers can completely own the html files without the developers having to modify them in any way.

Note that the code generated here is fully cooked code, custom built for that specific schema and HTML template. This runs extremely fast because there is nothing left at run-time concerning parsing and figuring out the HTML template, or constructing the SQL statements.

A test was done comparing this web framework example to the same example done in TurboGears 2 running against the same MySQL database. The results of the siege benchmark tests show that Pyke is just over 10 times faster than TurboGears 2:

- Pyke: 791 trans/sec
- TurboGears 2: 76 trans/sec

The demo is packaged as a WSGI application. It also demonstrates the use of multiple rule bases by using the sqlgen example above, as well as the caching and reuse of plans to achieve the order of magnitude improvement in performance over current practice.

More:

About Pyke

What pyke does for you, its features, steps to using pyke and installation.

Logic Programming Tutorial

A tutorial on logic programming in Pyke, including statements, pattern matching and rules.

Knowledge Bases

Knowledge is made up of both facts and rules. These are gathered into named repositories called knowledge bases.

Pyke Syntax

The syntax of Pyke's three different kinds of source files.

Using Pyke

How your Python program calls Pyke.

Examples

An overview of the examples provided with Pyke.

Applying Expert System Technology to Code Reuse with Pyke

Paper presented at the PyCon 2008 conference in Chicago.

Page last modified Mon, Mar 29 2010.
./pyke-1.1.1/doc/html/pyke_syntax/0000755000175000017500000000000011425360453015703 5ustar lambylamby./pyke-1.1.1/doc/html/pyke_syntax/index.html0000644000175000017500000002612611365362366017717 0ustar lambylamby Pyke Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Pyke Syntax

Source Files

Pyke has three different kinds of source files for the three main types of knowledge bases:

  1. Knowledge Fact Base (KFB) files for fact bases.
  2. Knowledge Rule Base (KRB) files for rule bases.
  3. Knowledge Question Base (KQB) files for question bases.

Each type of source file ends in a different file suffix: .kfb, .krb or .kqb.

Place all of these source files into a directory structure. Then include this directory as an argument to the knowledge_engine.engine constructor. This will recursively search your directory for these three types of source files, compile them, and load them into the engine. How you organize these files into subdirectories is up to you -- the directory structure does not matter to Pyke.

The .kfb and .kqb files are compiled into Python pickles with .fbc and .qbc suffixes.

The .krb files are compiled into up to three .py source files. The names of these .py files are the same as the .krb file, but with different endings:

These .py files are then automatically imported to define the rule base. This causes Python to compile them into .pyc or .pyo files.

Subsequent runs of the knowledge_engine.engine constructor only recompile the Pyke source files that have changed since the last time they were compiled.

The name of each knowledge base is the filename of the Pyke source file with the suffix removed. This must be a legal Python identifier.

Syntax Legend

To describe this syntax, the following punctuation is used:

'any_chars'
Required punctuation or keyword: any_chars.
a | b
Alternation: a or b.
[a]
Optional a.
{a}
One or more a's. But it is understood that if a ends in a comma, the last comma is optional.
IDENTIFIER
Any legal Python identifier. Example: foobar
NUMBER
Any legal Python integer or floating point literal. Examples: 123, 3.14.
STRING
Any legal Python string literal. Examples: 'Hi Mom!', u"Hi Dad!\n", r'''don't gobble my \'s!''', ur"""leave \'s alone!""".
TEXT
Only used in KQB files. This signifies any text (any characters) other than the delimiter characters containing the TEXT.
PARAMETRIZED_TEXT
Only used in KQB files. This signifies any text (any characters) through the end of the line and all text on subsequent lines that are indented at least as much as the first PARAMETRIZED_TEXT character on the first line. All PARAMETRIZED_TEXT is treated as a string.Template and may include $IDENTIFIER or ${IDENTIFIER} parameters. All other $ characters must be doubled ($$).
REGEXP_TEXT
Only used in KQB files. This signifies any text (any characters) excluding an unescaped backslash (\) at the end. These are given to the Python's re module as regular expressions and must follow Python's regular expression syntax.
NL
One or more newlines.
INDENT
The following text must be indented to a higher level (more) than the previous text.
DEINDENT
The following text must be indented one less level than the previous text.

Lexical Structure

The lexical structure is much like Python. Like Python, indenting is significant. It uses the same commenting, line continuation and literal formats for strings and numbers (but doesn't use complex numbers). It also uses the same rules for forming identifiers.

The two notable exceptions to Python conventions are:

  1. Identifiers may be used as strings, without requiring quotes.
    • So foobar is the same as 'foobar'.
  2. Singleton tuples do not require a trailing comma.
    • So (1) is the same as (1,).

More:

KFB Syntax

The syntax of Knowledge Fact Base (KFB) files, which is where you write your universal facts.

KRB Syntax

Syntax of the Knowledge Rule Base (KRB) files, which is where you write your rules.

KQB Syntax

The syntax of Knowledge Question Base (KQB) files, which is where you spell out your end user questions.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/pyke_syntax/kfb_syntax.html0000644000175000017500000001426511365362366020761 0ustar lambylamby KFB Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

KFB Syntax

This uses the same lexical structure as KRB files, except that the only keywords are:

  • None
  • True
  • False

The name of the fact base is simply the filename with the .kfb suffix stripped. This must be a valid Python identifier.

Syntax for KFB File

file ::= [NL] {fact NL}

fact ::= IDENTIFIER '(' [{data,}] ')'

data ::= 'None' | 'True' | 'False'
       | NUMBER | IDENTIFIER | STRING
       | '(' [{data,}] ')'

Example

This is taken from the family_relations example:

# family.kfb

son_of(bruce, thomas, norma)
son_of(fred_a, thomas, norma)
son_of(tim, thomas, norma)
daughter_of(vicki, thomas, norma)
daughter_of(jill, thomas, norma)

daughter_of(nanette, arthur2, kathleen)
son_of(arthur3, arthur2, kathleen)
daughter_of(sue, arthur2, kathleen)
son_of(ed, arthur2, kathleen)
daughter_of(marilyn, arthur2, kathleen)
son_of(david_b, arthur2, kathleen)
daughter_of(m_helen, arthur2, kathleen)

son_of(m_thomas, bruce, marilyn)
son_of(david_a, bruce, marilyn)

More:

KFB Syntax

The syntax of Knowledge Fact Base (KFB) files, which is where you write your universal facts.

KRB Syntax

Syntax of the Knowledge Rule Base (KRB) files, which is where you write your rules.

KQB Syntax

The syntax of Knowledge Question Base (KQB) files, which is where you spell out your end user questions.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/pyke_syntax/krb_syntax/0000755000175000017500000000000011425360453020067 5ustar lambylamby./pyke-1.1.1/doc/html/pyke_syntax/krb_syntax/index.html0000644000175000017500000002422511365362366022101 0ustar lambylamby KRB Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

KRB Syntax

This section describes the syntax for defining rules in KRB files.

Keywords

as foreach taking
assert in True
bc_extras None use
check plan_extras when
extending python with
False step without
fc_extras    

Syntax of the Entire KRB File

file ::= [NL]

         ['extending' IDENTIFIER ['without' {IDENTIFIER,}] NL]

         [{fc_rule}
          ['fc_extras' NL INDENT
             {<python_statement> NL}
           DEINDENT]]

         [{bc_rule}
          ['bc_extras' NL INDENT
             {<python_statement> NL}
           DEINDENT]
          ['plan_extras' NL INDENT
             {<python_statement> NL}
           DEINDENT]]

The KRB file has three optional parts. It must contain at least one rule (either forward-chaining or backward-chaining).

The filename (minus the .krb extension) is the name of the rule base. This must be a legal Python identifier.

Extending clause

The optional extending clause, if used, is the first line of the file. This defines the parent rule base that this rule base inherits from. It may also specify a list of backward-chaining goal names to be excluded from this inheritance.

Forward-Chaining Section

If the krb file contains any forward-chaining rules, a Python source file will be created named <rb_name>_fc.py, where <rb_name> is the rule base name.

The syntax of a forward-chaining rule (fc_rule) is defined here.

The fc_extras may only be used if there are forward-chaining rules. This allows you to add other Python code (for example, import statements) to the generated Python source file.

Backward-Chaining Section

If the krb file contains any backward-chaining rules, a Python source file will be created named <rb_name>_bc.py, where <rb_name> is the rule base name.

The syntax of a backward-chaining rule (bc_rule) is defined here.

The bc_extras can only be used if there are backward-chaining rules. This allows you to add other Python code (for example, import statements) to the generated Python source file.

In addition, if any of the backward-chaining rules have plan code (a with clause or any subgoals in the when clause with a plan_spec), a Python source file will be created named <rb_name>_plans.py, where <rb_name> is the rule base name.

You use the plan_extras to include arbitrary Python code in this plans file.

More:

Fc_rule Syntax

The syntax of a forward-chaining rule.

Bc_rule Syntax

The syntax of a backward-chaining rule.

Pattern Syntax

The syntax of a pattern used to match data values.

Compound Premise Syntax

The syntax of compound premises.

Python Premise Syntax

The syntax of a python_premise.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/pyke_syntax/krb_syntax/compound_premise.html0000644000175000017500000004452711365362366024351 0ustar lambylamby Compound Premise Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Compound Premise Syntax

There are three kinds of compound premises. These can be used in both forward-chaining rules and backward-chaining rules, but the nested premises within each of these are restricted to the kind of premises legal for that kind of rule: fc_premise for forward-chaining rules, and bc_premise for backward-chaining rules.

compound_premise ::= first_premise
                   | forall_premise
                   | notany_premise

First Premise

The first premise is used to prevent backtracking from finding subsequent solutions to a set of premises. The first premise always fails on backtracking (but does do backtracking within the nested premises).

first_premise ::= ['!'] 'first' premise
                | ['!'] 'first' NL
                          INDENT
                             {premise NL}
                          DEINDENT

The ! option can only be used in backward-chaining rules.

When used within backward-chaining rules, the nested premises may include any type of plan_spec.

Forall Premise

The forall premise forces backtracking within the nested premises to process all of the possible solutions found before the forall succeeds. After the first success, the forall fails on backtracking.

forall_premise ::= 'forall' NL
                     INDENT
                        {premise NL}
                     DEINDENT
                 [ 'require' NL
                     INDENT
                        {premise NL}
                     DEINDENT ]

The premises within the require clause are tried for each solution found to the forall clause. If these fail for any solution, then the entire forall premise fails. Thus, the forall only succeeds if the require premises are true for all solutions generated within the forall clause. Thus, the forall clause may be read: "Forall X, require Y".

The forall always succeeds if the require clause is omitted (even if no solutions are found to the nested premises). This can be used in conjunction with python_statements to gather a list of results.

See Notes on Forall and Notany Premises and Examples, below.

Notany Premise

The notany premise only succeeds if no solution can be found to the nested premises. Notany always fails on backtracking.

notany_premise ::= 'notany' NL
                     INDENT
                        {premise NL}
                     DEINDENT

See Notes on Forall and Notany Premises and Examples, below.

Notes on Forall and Notany Premises

  1. All pattern variable bindings made during the execution of a forall or notany premise are undone before the premises following the forall or notany are run. Thus, forall and notany can be used to test values produced by prior premises; but to generate values for subsequent premises the values must be captured in Python variables within the forall or notany clause before the pattern variables are unbound (see Computing a Value for Each Generated Value, below).
  2. When used within backward-chaining rules, the only plan_spec allowed in nested premises is the as clause.

Examples

These examples use the following subgoals:

  • generate_x($x) generates multiple solutions (as $x) that will be looped over
  • test_x($x) does some test on $x
  • compute_y($x, $y) takes $x as input and computes a $y value

Finding the First Solution From a Set of Values

If you want the first $x that passes the test_x($x) test, you have two options:

generate_x($x)
test_x($x)
...

And:

first
    generate_x($x)
    test_x($x)
...

The difference is that the first example will find other $x values that pass test_x($x) on backtracking, while the second example will stop after the first value is found and fail on backtracking.

Testing Every Generated Value

There are two general cases. You might want to verify that test_x($x) succeeds for all generated $x values:

forall
    generate_x($x)
require
    test_x($x)

Note

While $x is set and used within the forall premise to transfer values from the generate_x($x) goal to the test_x($x) goal, it is no longer set afterwards and can not be referenced in the premises following the forall premise.

The second case that you might want to verify is that test_x($x) fails for every generated $x value:

forall
    generate_x($x)
require
    notany
        test_x($x)

Or, more simply:

notany
    generate_x($x)
    test_x($x)

Computing a Value for Each Generated Value

If you want a tuple of computed $y values for all of the $x values:

python y_list = []
forall
    generate_x($x)
require
    compute_x($x, $y)
    python y_list.append($y)
$y_list = tuple(y_list)

This will only succeed if compute_y succeeds for every $x value.

If you want to skip over $x values that compute_y fails on, you might try:

python y_list = []
forall
    generate_x($x)
    compute_x($x, $y)
    python y_list.append($y)
$y_list = tuple(y_list)

But note that if compute_y computes multiple solutions for a single $x value on backtracking, you would end up including all of these solutions in your $y_list. To only get the first computed value for each $x value:

python y_list = []
forall
    generate_x($x)
    first
        compute_x($x, $y)
        python y_list.append($y)
$y_list = tuple(y_list)

Iterating on Tuples

A simple common case of generate_x is when you are computing values for each element of a tuple:

python y_list = []
forall
    $x in $x_list
require
    compute_x($x, $y)
    python y_list.append($y)
$y_list = tuple(y_list)

This can also be done by creating a new subgoal that recurses on $x_list. If you call the new subgoal compute_list, you would use it like this:

compute_list($x_list, $y_list)

And define it like this:

compute_list_done
    use compute_list((), ())

compute_list_step
    use compute_list(($x, *$x_rest), ($y, *$y_rest))
    when
        compute_y($x, $y)
        compute_list($x_rest, $y_rest)

Important

Note that there is an important difference between these two examples if compute_y may find alternate $y values for any given $x value on backtracking.

The first example will only generate one $y_list. If that $y_list doesn't work for subsequent premises, the forall fails on backtracking, so no overall solution will be found.

The second example will not fail in this situation, but will produce all possible combinations of solutions to compute_y for each $x on backtracking until a resulting $y_list satisfies the subsequent premises so that an overall solution is found.

Computing Values for All Generated Values that Pass a Test

Finally, if you want to gather only the computed $y values for $x values that pass test_x($x):

python y_list = []
forall
    generate_x($x)
    test_x($x)
require
    compute_x($x, $y)
    python y_list.append($y)
$y_list = tuple(y_list)

More:

Fc_rule Syntax

The syntax of a forward-chaining rule.

Bc_rule Syntax

The syntax of a backward-chaining rule.

Pattern Syntax

The syntax of a pattern used to match data values.

Compound Premise Syntax

The syntax of compound premises.

Python Premise Syntax

The syntax of a python_premise.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/pyke_syntax/krb_syntax/fc_rule.html0000644000175000017500000002005011365362366022401 0ustar lambylamby Fc_rule Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Fc_rule Syntax

Fc_rule

Forward-chaining rules have three parts:

  1. A unique name.
  2. An optional foreach clause.
  3. An assert clause.
fc_rule ::= IDENTIFIER NL INDENT
               [fc_foreach]
               fc_assert
            DEINDENT

The IDENTIFIER uniquely names this rule and is used as the corresponding Python function name in the generated <rb_name>_fc.py file.

Foreach clause

fc_foreach ::= 'foreach' NL INDENT
                   {fc_premise NL}
               DEINDENT

fc_premise ::= fact_pattern
             | compound_premise
             | python_premise

fact_pattern ::= IDENTIFIER '.' IDENTIFIER '(' [{pattern,}] ')'

Here are links to the definitions for pattern, compound_premise and python_premise.

If the foreach clause is omitted, the rule is always fired once.

If the foreach clause is present, the rule is fired for each combination of true premises.

Assert clause

fc_assert ::= 'assert' NL INDENT
                  {assertion NL}
              DEINDENT

assertion ::= fact_pattern
            | python_statements

Here is the link to the definitions of python_statements.

The assert clause lists new facts to assert, and/or Python statements to execute each time the rule is fired. Each of these may include pattern variables which should also appear in the foreach clause where they are bound to a value. These values will then be substituted into the facts and Python statements.

More:

Fc_rule Syntax

The syntax of a forward-chaining rule.

Bc_rule Syntax

The syntax of a backward-chaining rule.

Pattern Syntax

The syntax of a pattern used to match data values.

Compound Premise Syntax

The syntax of compound premises.

Python Premise Syntax

The syntax of a python_premise.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/pyke_syntax/krb_syntax/python_premise.html0000644000175000017500000002511311365362366024034 0ustar lambylamby Python Premise Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Python Premise Syntax

Python_premise

python_premise ::= pattern '=' python_exp
                 | pattern 'in' python_exp
                 | 'check' python_exp
                 | python_statements

Each of these clauses results in a Python expression being executed. Their meaning is as follows:

pattern '=' python_exp

python_exp is evaluated and the result matched with pattern. If the result does not match, the clause fails.

The clause always fails on backtracking, meaning that it only produces a single result (contrasted with in).

pattern 'in' python_exp

python_exp is evaluated to produce a Python iterable and the first element from the resulting iterable is matched with pattern. On backtracking, successive elements from the iterable are matched with pattern. When the result is exhausted, the clause fails.

This has the effect of offering each element of the result, one at a time, to the subsequent premise clauses. Each element is thus acted upon individually.

'check' python_exp
python_exp is evaluated. If the result is Python "true" the clause succeeds, otherwise it fails. The clause always fails on backtracking.

Python_statements

python_statements ::= 'python' python_statement
                    | 'python' NL INDENT
                          {python_statement NL}
                      DEINDENT

This clause allows the inclusion of arbitrary Python statements in your rules. This premise always succeeds; and then fails on backtracking.

The current knowledge_engine object is available within python_statements as the variable called engine.

Caution!

Always keep in mind the difference between pattern variables and Python variables. Pattern variables are always indicated with a $ and are only bound to a value during inferencing.

  1. Thus, a python_statement may not set a pattern variable. Storing a value computed by Python into a pattern variable can only be done using the python_premise:

    <pattern> = <some python expression>
    
  2. When a pattern variable is used within a Python expression or statement, it must be fully bound.

  3. Python variables are not visible to the inference engine. They are local variables that are also not visible to Python code in other rules or other invocations of the same rule.

  4. Finally, Python variables in the when clause of a backward-chaining rule are not visible to the Python code in the with clause of the same rule. (These end up in two different Python functions after the .krb file is compiled). So this won't work:

    some_bc_rule
        use some_goal(...)
        when
            ...
            python x_list = <some python expression>
            ...
        with
            for x in x_list: process(x)
    

    In this case, assign the value of the Python variable to a pattern variable in the when clause and then use that pattern variable in the with clause:

    some_bc_rule
        use some_goal(...)
        when
            ...
            python x_list = <some python expression>
            ...
            $x_list = tuple(x_list)
        with
            for x in $x_list: process(x)
    

More:

Fc_rule Syntax

The syntax of a forward-chaining rule.

Bc_rule Syntax

The syntax of a backward-chaining rule.

Pattern Syntax

The syntax of a pattern used to match data values.

Compound Premise Syntax

The syntax of compound premises.

Python Premise Syntax

The syntax of a python_premise.

Page last modified Sun, Feb 15 2009.
./pyke-1.1.1/doc/html/pyke_syntax/krb_syntax/bc_rule.html0000644000175000017500000004115111365362366022402 0ustar lambylamby Bc_rule Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Bc_rule Syntax

Bc_rule

Backward-chaining rules have four parts:

  1. A unique name.
  2. A use clause.
  3. An optional when clause.
  4. An optional with clause.
bc_rule ::= IDENTIFIER NL INDENT
                use
                [when]
                [with]
            DEINDENT

The IDENTIFIER is the unique name for this rule and is used as the corresponding Python function name in the generated <rb_name>_bc.py file and also for the Python function name of the plan function (if any) associated with the rule. This name will show up in stack traces associated with exceptions raised during inferencing or plan execution.

Use Clause

The use clause is the then part of the rule. It identifies the goal that this rule is prepared to prove.

use ::= 'use' IDENTIFIER '(' {pattern,} ')' ['taking' '(' <python_arg_spec> ')'] NL
      | 'use' IDENTIFIER '(' {pattern,} ')' NL
         INDENT 'taking' '(' <python_arg_spec> ')' NL
         DEINDENT

Notice that it uses a single IDENTIFIER. The rule base name is implied as the rule base category name (the name of the root rule base, see extending clause) for the rule base containing this rule.

Taking Clause

The use clause also defines parameters to the plan function (if one is used for this rule) with the optional taking sub-clause.

The python_arg_spec is not parsed by Pyke, but simply copied to the output plan function. Do not use $ with these parameter names (or their default values).

When Clause

The when clause is the if part of the rule. It defines the premises that must be true for this rule to succeed.

If the when clause is omitted, the only requirement for the rule to succeed is that the use clause pattern matches the goal.

If the when clause is specified, the rule succeeds for each combination of true premises (see backtracking).

when ::= 'when' NL INDENT
             {bc_premise NL}
         DEINDENT

bc_premise ::= ['!'] [ name '.' ] name '(' {pattern,} ')' [ plan_spec ]
             | compound_premise
             | python_premise

name ::= IDENTIFIER
       | '$'IDENTIFIER

Here are the links to the definitions for pattern, compound_premise and python_premise.

If the bc_premise includes the !, an AssertionError will be raised if the premise fails on the first try. This can help in debugging.

Note

This does not apply when the premise fails on backtracking (in which case it has already succeeded at least once).

If a single name is used in the bc_premise, the rule base category for the current rule base (the root rule base name, see extending clause) is assumed.

If two names are used in the bc_premise, the first may name a rule base category or some other knowledge base.

If a rule base category name is used (or assumed), the currently active rule base for that category is used to prove the premise.

Note

If the rule base category name is omitted, and therefore assumed to be the current rule base's rule base category, the current rule base does not have to be the active rule base for that category. It could be the case that a derived rule base is the active rule base. In that case, the derived rule base is used to prove the premise.

In this way, different rules may be used to prove the same premise, depending upon which rule base has been activated.

Plan_spec

A plan_spec is required for each premise that returns a subordinate plan. This shows what should be done with that subordinate plan function.

Thus, a rule's plan function is composed first of the collected python_statements taken from its plan_specs (as described below), followed by the python_statements within its with clause (if any). The inclusion of any plan_spec containing a python_statement will cause a plan function to be generated for this rule, even if the rule lacks a with clause.

plan_spec ::= [ 'step' NUMBER ] NL INDENT
                  {<python_statement> NL}
              DEINDENT
            | 'as' '$'IDENTIFIER NL

Within each python_statement, the subordinate plan function is indicated by $$. The result of this function may be assigned to a Python variable, but not a pattern variable ($variable). Parameters from the rule's taking clause may be passed on to the subordinate plan functions.

When multiple premises have python_statements in their plan_specs, the python_statements in plan_specs without a step clause are executed first in the order that they appear in the when clause.

Then the python_statements in plan_specs with a step clause are executed in ascending NUMBER sequence. It is permissible for the NUMBER to be negative or a float.

If the as clause is used, the plan function is bound to the pattern variable as a Python function, but not automatically executed. This allows you to call the function (or not) when and as many times as you please. The parameters required are defined in the taking clause of the rule used to prove the premise.

Note

Within a forall or notany premise, the only plan_spec that may be used is the as clause.

With Clause

The with clause contains Python statements to include in the plan produced by this rule. These Python statements may include pattern variables whose values will be cooked into these statements when the plan is created.

with ::= 'with' NL INDENT
             {<python_statement> NL}
         DEINDENT

The python_statements are included in the rule's plan function after all of the calls to the subordinate plan functions made from the plan_specs in the when clause.

If the with clause is omitted, but the when clause has plan_specs (excluding the as clause), a plan function is still generated for this rule so that the subordinate plan functions are still called.

The python_statements are not parsed. They are simply scanned for $ pattern variables that don't occur within string literals or comments. The values bound to these variables are cooked into the code to produce the plan.

Thus, all pattern variables used within python_statements (both in the plan_specs and the when clause) must be bound to a value. This value is a constant value that never changes for this plan.

Note

This occurs after the entire top-level goal is proven so that it is permissible to bind these pattern variables to values following the execution of the rule containing them.

More:

Fc_rule Syntax

The syntax of a forward-chaining rule.

Bc_rule Syntax

The syntax of a backward-chaining rule.

Pattern Syntax

The syntax of a pattern used to match data values.

Compound Premise Syntax

The syntax of compound premises.

Python Premise Syntax

The syntax of a python_premise.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/pyke_syntax/krb_syntax/pattern.html0000644000175000017500000001655511365362366022456 0ustar lambylamby Pattern Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Pattern Syntax

Pattern

pattern ::= 'None' | 'True' | 'False'
          | NUMBER | IDENTIFIER | STRING | variable
          | '(' [{pattern,}] ['*' variable] ')'

IDENTIFIER acts like a STRING here, meaning that it is taken as a literal value. All variables in patterns must be preceded by a $.

Pyke does not currently support complex NUMBERS (for no good reason -- email me if you need them).

Pattern Variable

Pattern variables are simply called variable in the syntax:

variable ::= '$'IDENTIFIER

The variable must not have a space between the $ and the IDENTIFIER.

Anonymous Variable

If the pattern variable IDENTIFIER begins with an underscore (_), the variable is an anonymous variable. It acts like a "don't care". Technically, this means that multiple uses of the same IDENTIFIER may stand for different values. The name of the IDENTIFIER after the underscore is ignored and may be used to document the use of the anonymous variable.

Rest Variable

The *variable at the end of a tuple pattern will match the rest of the tuple. Thus, variable is always bound to a (possibly empty) tuple.

The syntax is taken from rest parameter syntax in Python function definitions. The difference here is that the variable needs a $ on it.

You may use either a named variable or an anonymous variable here.

More:

Fc_rule Syntax

The syntax of a forward-chaining rule.

Bc_rule Syntax

The syntax of a backward-chaining rule.

Pattern Syntax

The syntax of a pattern used to match data values.

Compound Premise Syntax

The syntax of compound premises.

Python Premise Syntax

The syntax of a python_premise.

Page last modified Thu, May 14 2009.
./pyke-1.1.1/doc/html/pyke_syntax/kqb_syntax.html0000644000175000017500000005277511365362366021004 0ustar lambylamby KQB Syntax
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

KQB Syntax

This uses a different lexical structure than KRB files. Textual parameter substitution is done with the standard Python string.Template class, which specifies parameters with a $, much like pattern variables. In the following descriptions, substitution parameters are acted upon in PARAMETRIZED_TEXT, but not in TEXT.

The name of the question base is simple the filename with the .kqb suffix stripped. This must be a valid Python identifier.

PARAMETRIZED_TEXT

PARAMETRIZED_TEXT may span multiple lines. Each subsequent line must be indented at least as much as the first character of PARAMETRIZED_TEXT on the first line. Line breaks and indentation are preserved. So syntax like:

match '!' PARAMETRIZED_TEXT

Could be:

2-10 ! This is the start of the
       parametrized text.
       ^
       |
       +------ all lines must be indented at least this far!

          - what do you think?

The PARAMETRIZED_TEXT here would be:

This is the start of the
parametrized text.
^
|
+------ all lines must be indented at least this far!

   - what do you think?

But this is not legal:

2-10 ! An example of PARAMETRIZED_TEXT
      with a second line that's not indented enough!

Syntax for KQB File

file ::= [NL] {question}

question ::= IDENTIFIER '(' [{parameter,}] ')' NL INDENT
                {PARAMETRIZED_TEXT NL}
                '---' NL
                parameter '=' question_type
             DEINDENT

parameter ::= '$' IDENTIFIER

Each question has a name and a fixed number of parameters. This is followed by one or more lines of PARAMETRIZED_TEXT that will be presented as the question to answer. These are terminated by a line containing only ---.

One of the parameters is designated as the answer parameter on the line immediately following the terminating ---. This is the only parameter that may be unbound when a rule uses this question.

For example, the file user_questions.kqb might contain:

ate($meal, $ans)
    Did you eat $meal?
    ---
    $ans = yn

This question could be referenced in the premise of a rule as:

user_questions.ate(lunch, $ans)

or:

user_questions.ate(lunch, False)

But not:

user_questions.ate($meal, False)

There are several different kinds of question_types, each corresponding to a different way that the user might answer the question:

question_type ::= yn_type
                | integer_type
                | number_type
                | float_type
                | string_type
                | select_1_type
                | select_n_type

The integer_type, number_type, float_type and string_type may include a match to force the user to enter a sensible answer.

All of these may also include a review, which is just PARAMETRIZED_TEXT that will be displayed when the user's answer matches a certain match value.

Question_type links:

YN_type

yn_type ::= 'yn' NL [review]

The user answers "yes" or "no". The answer returned is True or False. If the ask_tty module is used, the user may type "yes", "y", "true" or "t" for True and "no", "n", "false", or "f" for False. These are case insensitive.

Example:

ate($meal, $ans)
    Did you eat $meal?
    ---
    $ans = yn

See review, below.

Integer_type

integer_type ::= 'integer' ['(' match ')'] NL [review]

The user enters an integer. If the match is specified, the integer must match it or the user is asked to try again.

Example:

hours_since_last_meal($ans)
    How many hours has it been since you last ate?
    ---
    $ans = integer(0-48)

See review, below.

Number_type

number_type ::= 'number' ['(' match ')'] NL [review]

The user enters either an integer or a floating point number. If the user enters an integer, a Python int is returned. Otherwise a Python float is returned.

If the match is specified, the number must match it or the user is asked to try again.

Example:

miles_to($dest, $ans)
    How many miles did you travel to get to $dest?
    ---
    $ans = number(0.1-3000)

See review, below.

Float_type

float_type ::= 'float' ['(' match ')'] NL [review]

The user enters an integer or a floating point number. But the answer returned is always a Python float.

If the match is specified, the number must match it or the user is asked to try again.

Example:

price($object, $price)
    What did you pay for $object?
    ---
    $price = float

See review, below.

String_type

string_type ::= 'string' ['(' match ')'] NL [review]

The user enters a string (text). If the match is specified, the string must match it or the user is asked to try again.

Example:

users_name($name)
    What's your name?
        - Please don't enter a fictitious (screen) name.
    ---
    $name = string(2-40)

See review, below.

Match

There are several kinds of simple_matches that may be or-ed together with |:

match ::= simple_match {'|' simple_match}

The match succeeds if any of the simple_matches succeed.

simple_match ::= '(' match ')'
               | [ STRING ] [ '[' TEXT ']' ] '/' REGEXP_TEXT '/'
               | [NUMBER] '-' NUMBER
               | NUMBER '-'
               | value '=' simple_match
               | value

Regexp Match

simple_match ::= [ STRING ] [ '[' TEXT ']' ] '/' REGEXP_TEXT '/'

A regexp match can only be used with string_type questions. It matches if the regexp matches.

If the regexp contains a single group, that group is returned as the question's answer rather than the entire string.

If the regexp contains multiple groups, a tuple of the groups is returned as the question's answer rather than entire string.

If STRING is specified on a regexp, it is used in the error message if the regexp fails. The error message is "Answer should be $error_msg, got $string".

If '[' TEXT ']' is specified on a regexp, it is used in the prompt for the end user to inform him of what is expected. Generally, this prompt message is enclosed in '[' and ']' when it is displayed to the user.

Example:

state_code($state)
    Enter your two digit state code.
    ---
    $state = string('uppercase'[uppercase]/[A-Z][A-Z]/)

Range Match

simple_match ::= [NUMBER] '-' NUMBER
               | NUMBER '-'

A range match has a '-' in it. It matches if the answer is between the two values. If either value is omitted, that limit is not tested. If matched to a string, it matches the length of the string.

Example:

age($years)
    How old are you?
    ---
    $years = integer(1-130)

Value '=' Match

simple_match ::= value '=' simple_match

The '=' means "substituted for". The match fails if the match after the '=' fails. Otherwise it returns the value before the '=' rather than what the user entered. Note that you can or (|) several of these together to translate several different matched values.

Example:

age_category($period_of_life)
    How old are you?
    ---
    $period_of_life = integer(child=1-12 |
                              teenager=13-19 |
                              young_adult=20-35 |
                              middle_age=35-64 |
                              elder=65-130)

Value Match

simple_match ::= value

value ::= STRING | IDENTIFIER | NUMBER | 'None' | 'True' | 'False'

A value match, only matches that one value. An IDENTIFIER is treated as a STRING. These are mostly used in reviews.

Review

review ::= {match '!' PARAMETRIZED_TEXT NL}

All of the reviews must be at the same indent level.

The review is applied after the answer has been validated (validation possibly changes the value).

Each match is checked and all of the matching review's PARAMETRIZED_TEXT messages are displayed to the user.

Examples:

stupid_question($ans)
    Can you answer a question
    that is several lines long?
    ---
    $ans = yn
        True  ! Correct!  This is true because the
                          sky is blue!
        False ! Nope!  Remember that the sky is blue!

wood($ans)
    How much wood would a woodchuck chuck if a woodchuck could chuck wood?
    ---
    $ans = integer(0-100)
        -10   ! more than that!
        10-20 ! bingo!
        21-   ! I guess they're not as strong as you think ...

Asking stupid_question and answering "y" to it:

>>> from pyke import knowledge_engine

>>> engine = knowledge_engine.engine(__file__)

>>> from StringIO import StringIO
>>> import sys
>>> class echo(object):
...     def __init__(self, f): self.f = f
...     def readline(self):
...         ans = self.f.readline()
...         sys.stdout.write(ans)
...         return ans
>>> sys.stdin = echo(StringIO('y\n'))

displays:

>>> engine.prove_1_goal('user_questions.stupid_question($ans)')
______________________________________________________________________________
Can you answer a question
that is several lines long? (y/n) y
Correct!  This is true because the
          sky is blue!
({'ans': True}, None)

Select_1_type

select_1_type ::= 'select_1' NL alternatives

This is a multiple choice question. The alternatives are displayed to the user, and he picks one (and only one).

Example:

another_question($arg1, $arg2, $ans)
    question text with $arg1 stuff in it.
    on multiple lines
        - possibly indented
        - for who knows what reason...
            - maybe for $arg2?
    ---
    $ans = select_1
        1: prompt for this selection with $arg2 in it too
           which can span multiple lines
               - and be indented ...
           ! Nope!  Remember that the sky is blue!
        2: next prompt
           ! =1     # same review as 1:
        3: pick me! pick me!!!
           ! Correct!  You certainly know about $arg1!
             yep, multiple review lines too...
                - and indented...

Select_n_type

select_n_type ::= 'select_n' NL alternatives

This is a multiple choice question. The alternatives are displayed to the user, and he picks as many as he likes.

Example:

problems($list)
    Which of these problems are you experiencing?
        - select all that apply
    ---
    $list = select_n
        boot: The system won't boot.
        os: I hate Windows!
        internet: I can't connect to the internet.
        slow: The system is running too slow.
        ouch: Help!  I've fallen and I can't get up!
        freeze: The system freezes or does not respond to input.
        printer: The printer doesn't work.
        senile: What's email?

Alternatives

alternatives ::= {value ':' PARAMETRIZED_TEXT NL [alt_review]}

All of the alternatives must be at the same indent level.

The user only sees the PARAMETRIZED_TEXT values. The value associated with the selected PARAMETRIZED_TEXT is returned (but the user never sees it). The value tags the alternative.

alt_review ::= '!' '=' value NL
             | '!' PARAMETRIZED_TEXT NL

Each alternative may have it's own review associated with it.

The '!' '=' value form uses the same review text as the previous alternative with that tag. Note that this can not refer forward to a following alternative.

The second form specifies the review text for this alternative directly.

More:

KFB Syntax

The syntax of Knowledge Fact Base (KFB) files, which is where you write your universal facts.

KRB Syntax

Syntax of the Knowledge Rule Base (KRB) files, which is where you write your rules.

KQB Syntax

The syntax of Knowledge Question Base (KQB) files, which is where you spell out your end user questions.

Page last modified Fri, Mar 05 2010.
./pyke-1.1.1/doc/html/stylesheets/0000755000175000017500000000000011425360453015701 5ustar lambylamby./pyke-1.1.1/doc/html/stylesheets/default.css0000644000175000017500000001256011346504626020047 0ustar lambylamby/* :Author: David Goodger :Contact: goodger@users.sourceforge.net :Date: $Date$ :Version: $Revision$ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. */ /* "! important" is used here to override other ``margin-top`` and ``margin-bottom`` styles that are later in the stylesheet or more specific. See http://www.w3.org/TR/CSS1#the-cascade */ .first { margin-top: 0 ! important } .last, .with-subtitle { margin-bottom: 0 ! important } .hidden { display: none } a.toc-backref { text-decoration: none ; color: black } blockquote.epigraph { margin: 2em 5em ; } dl.docutils dd { margin-bottom: 0.5em } /* Uncomment (and remove this text!) to get bold-faced definition list terms dl.docutils dt { font-weight: bold } */ div.abstract { margin: 2em 5em } div.abstract p.topic-title { font-weight: bold ; text-align: center } div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { margin: 2em ; border: medium outset ; padding: 1em } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold ; font-family: sans-serif } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: red ; font-weight: bold ; font-family: sans-serif } /* Uncomment (and remove this text!) to get reduced vertical space in compound paragraphs. div.compound .compound-first, div.compound .compound-middle { margin-bottom: 0.5em } div.compound .compound-last, div.compound .compound-middle { margin-top: 0.5em } */ div.dedication { margin: 2em 5em ; text-align: center ; font-style: italic } div.dedication p.topic-title { font-weight: bold ; font-style: normal } div.figure { margin-left: 2em } div.footer, div.header { clear: both; font-size: smaller } div.line-block { display: block ; margin-top: 1em ; margin-bottom: 1em } div.line-block div.line-block { margin-top: 0 ; margin-bottom: 0 ; margin-left: 1.5em } div.sidebar { margin-left: 1em ; border: medium outset ; padding: 1em ; background-color: #ffffee ; width: 40% ; float: right ; clear: right } div.sidebar p.rubric { font-family: sans-serif ; font-size: medium } div.system-messages { margin: 5em } div.system-messages h1 { color: red } div.system-message { border: medium outset ; padding: 1em } div.system-message p.system-message-title { color: red ; font-weight: bold } div.topic { margin: 2em } h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { margin-top: 0.4em } h1.title { text-align: center; font-family: Times New Roman; margin-top: 3px; margin-bottom: 0; color: #00478E; line-height: 30px; font-style: italic; } h2.subtitle { font-family: Times New Roman; text-align: center; font-size: 18px; color: #00478E; margin: 0; } h2 { font-size: 18px; } hr.docutils { width: 75% } img.align-left { clear: left } img.align-right { clear: right } img.borderless { border: 0 } ol.simple, ul.simple { margin-bottom: 1em } ol.arabic { list-style: decimal } ol.loweralpha { list-style: lower-alpha } ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } p.attribution { text-align: right ; margin-left: 50% } p.caption { font-style: italic } p.credits { font-style: italic ; font-size: smaller } p.label { white-space: nowrap } p.rubric { font-weight: bold ; font-size: larger ; color: maroon ; text-align: center } p.sidebar-title { font-family: sans-serif ; font-weight: bold ; font-size: larger } p.sidebar-subtitle { font-family: sans-serif ; font-weight: bold } p.topic-title { font-weight: bold } pre.address { margin-bottom: 0 ; margin-top: 0 ; font-family: serif ; font-size: 100% } pre.line-block { font-family: serif ; font-size: 100% } pre.literal-block, pre.doctest-block { margin: 1%; background-color: #eeeeee; font-size: 11px; } span.classifier { font-family: sans-serif ; font-style: oblique } span.classifier-delimiter { font-family: sans-serif ; font-weight: bold } span.interpreted { font-family: sans-serif } span.option { white-space: nowrap } /* span.pre { white-space: pre } */ span.problematic { color: red } span.section-subtitle { /* font-size relative to parent (h1..h6 element) */ font-size: 80% } table.citation { border-left: solid thin gray } table.docinfo { margin: 2em 4em } table.docutils { margin-top: 0.5em ; margin-bottom: 0.5em } table.footnote { border-left: solid thin black } table.docutils td, table.docutils th, table.docinfo td, table.docinfo th { padding-left: 0.5em ; padding-right: 0.5em ; vertical-align: top } table.docutils th.field-name, table.docinfo th.docinfo-name { font-weight: bold ; text-align: left ; white-space: nowrap ; padding-left: 0 } h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { font-size: 100% } tt.docutils { background-color: #eeeeee } ul.auto-toc { list-style-type: none } ./pyke-1.1.1/doc/html/stylesheets/pysrc.css0000644000175000017500000000061611346504626017562 0ustar lambylamby.pysrc { border: #c0c0ff 2px dotted; padding:10px; font-weight: normal; background: #e0e0ff; margin: 20px; padding:10px; } .pykeyword { font-weight: bold; color: orange; } .pystring { color: green } .pycomment { color: red } .pynumber { color:purple; } .pyoperator { color:purple; } .pytext { color:black; } .pyerror { font-weight: bold; color: red; }./pyke-1.1.1/doc/html/stylesheets/pyke.css0000644000175000017500000002211711346504626017372 0ustar lambylamby/* css file for pyke content pages. http://pyke.sourceforge.net. */ /*********************************** Ids used: #body -- #body-tr -- #copyright -- #crumb-left -- #crumb-line -- #crumb-right -- #foot2 -- #foot -- #header1 -- #header2 -- #header -- #icons --
#last-modified --
#left-nav-div --
#left-nav -- #main --
#main-td -- #nav --
#page-table -- #return-to-top --
#right-nav-div --
#right-nav --
#startcontent -- tag, has no contents ***********************************/ /*********************************** Classes used: -- these may be combined: .docutils --
.literal -- -- these may be combined: .arabic --
    .simple --
        .doctest-block --
         .document               -- 
        .head --
.literal-block --
 .nav-branch             -- 
.normal-nav --
.pre -- only inside .right-item --
.section --
.subtitle --

.title --

.title-nav --
***********************************/ @import url("rest.css"); @import url("pysrc.css"); table { border-collapse: collapse; } table.table-offset { margin-left: 10px; margin-right: auto; margin-bottom: 20px; } p, td, li, ul, ol, h1, h2, h3, h4, h5, h6 { font-family: arial, helvetica, geneva, sans-serif; } #main h1, #main h2, #main h3, #main h4, #main dl, #main p { padding:0 10px; } #main h1 { padding:0 10px; } #main a[href] { font-weight:bold; border-bottom: 1px dotted black; text-decoration: none; } /**** #main h2 a {border-bottom: none;} #main h3 a {border-bottom: none;} #main h4 a {border-bottom: none;} ****/ #main a.toc-backref {border-bottom: none;} #main h1, #main h2, #main h3, #main h4, #main h5 { color: #545D4D; } #main h1.title, #main h2.subtitle { color: #00478E; text-decoration: none; } #main h1 { font-size: 25px; text-decoration: underline; /* border-bottom: 1px solid #00478E; */ } #main h2 { font-size: 21px; } #main h3 { font-size: 17px; } #main h4 { font-size: 13px; } .title { text-align: center; padding: 15px; max-width: 80%; margin: 20px auto; } #main h1.title { padding-top: 2px; padding-bottom: 0px; margin-bottom: 16px; font-size: 20pt; } #main h1.title + h2.subtitle { padding-top: 0px; padding-bottom: 0px; margin-top: -8px; margin-bottom: 16px; } .title img { display: block; margin: 20px auto; } hr {text-align: center;} body { background-color: #E0E3D8; color: #000000; margin: 0; /* min-width:850px; */ } a[href] { color: #545D4D; } #main a[href]:visited { color: #9080A0; border-bottom: 1px dotted #98A; } #main a[href]:hover { /* background-color: #d8d8d8; */ background-color: #e8e0ff; } #copyright { background-color: #67735D; text-align: center; color: #fff; font-size: 10px; padding: 5px 0 5px 0; } #page-table { font-family: arial, helvetica, geneva, sans-serif; width: 90%; min-width: 1080px; height: 100%; margin: 0 auto; border-collapse: collapse; border: 1px solid #67735D; } #foot1 { background-color: #86947A; } #header1, #header, #page-table, #header2, .head, #crumb-left, #crumb-line, #crumb-right, #nav { margin-top: 0px; margin-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; top: 0px; bottom: 0px; } #crumb-line { color: #fff; } #crumb-line a { color: #fff; text-decoration: none; } #crumb-line a:hover { color: #000; background-color: #EEE; } #header { height: 77px; background-color: #B4C0AA; background-image: url("../images/header.gif"); background-position: 8px 0px; /* was 8px 0px */ background-repeat: no-repeat; text-align: center; color: silver; font-size:31pt; } #header img { display: block; margin: 15px auto 10px; } /* #header a[href]:hover{background:none;} */ #crumb-left, #crumb-line, #crumb-right { background-color:#67735D; padding:3px; } #nav { text-align:left; font-size:10pt; font-weight: bold; padding-left: 8px; } #nav ul { margin:0; padding:0 } #nav li { display:inline; list-style:none; margin:0; padding:0; } #left-nav, #right-nav, #crumb-left, #crumb-right, #final-left, #final-right { vertical-align: top; } #left-nav { padding: 40px 10px 0 10px; border-right: 1px solid #67735D; border-left: 1px solid #67735D; width: 170px; min-width: 170px; background-color: #CFD8C8; } #left-nav a { font-size: 14px; line-height: 27px; padding: 4px; } #left-nav a, #right-nav a { color: #606B57; } /* #left-nav a:visited, #right-nav a:visited { */ #right-nav a:visited { color: #809B87; } #left-nav a:hover { background-color: #E2E2E2; padding: 4px; } #right-nav a:hover { background-color: #f2f2f2; } #right-nav { padding: 10px 10px 0 10px; border-right: 1px solid #67735D; border-left: 1px solid #67735D; width: 170px; min-width: 170px; background-color: #DEE5D9; font-size: 13px; line-height: 17px; } /* #left-nav, #right-nav, #left-nav-div, #right-nav-div { */ #left-nav-div { height: 330px; top: 0px; bottom: 0px; padding-top: 0px; padding-bottom: 0px; text-align: left; margin-top: 0px; margin-bottom: 0px; } #left-nav-div { /* padding-right: 4px; */ } #icons { padding: 80px 8px 20px 0; font-size: 12px; margin-top: 55px; } #icons a { padding: 0; } #project-page { margin-bottom: 15px; } #main-td { background-color: white; padding: 10px; } #main, #main-td, .document { vertical-align: top; line-height: 20px; font-size: 13px; padding-right: 10px; padding-left: 10px; } #document { width: 100%; } pre.literal-block, pre.doctest-block { /* margin: 1%; */ background-color: #eeeeee; font-size: 11px; /* was 11px */ line-height: 1.6; border: thin black solid; padding: 5px; /* was 5px */ } /* div around each directory in left nav */ .nav-branch { margin-left: 7px; } /* directories in left nav */ .title-nav { font-weight: bold; } /* files in left nav */ .normal-nav { } #return-to-top { padding: 20px 0 15px 15px; font-size: 12px; } #last-modified { text-align: center; color: #000; font-size: 10px; padding: 7px 0 6px 0; } p { margin: 0px 0 15px 0; } ol.simple, ul.simple { margin-bottom: 18px; } #right-nav h3 { font-family: Times New Roman; font-size:18px; color: #00478E; font-weight:bold; margin-top: 30px; margin-bottom: 0; } #right-nav-div { padding-right: 8px; /* border-left: 2px solid black; */ } .right-item { padding-top: 10px; padding-bottom: 0px; margin-top: 0px; margin-bottom: 0px; } .right-item p { padding-top: 0px; padding-bottom: 0px; margin-top: 0px; margin-bottom: 0px; } /* FIX: not needed anymore #right-nav ul { padding-left: 6px; } #right-nav li { margin-top: 10px; } */ /* from default.css: */ div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { margin: 2em ; border: 1px solid black ; padding: 1em } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold ; font-family: sans-serif } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: red ; font-weight: bold ; font-family: sans-serif } /* from rest.css: */ div.attention, div.admonition, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { background-color: #C7D0C0; padding: 3px; width: 90%; } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { text-align: left; font-size: 120%; background-color:#97AB8D; display: block; margin: 0; } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: #cc0000; /* font-family was misspelled here... */ font-family: arial, helvetica, geneva, sans-serif; font-size: 120%; text-align: center; background-color: #999999; display: block; margin: 0; } ./pyke-1.1.1/doc/html/stylesheets/rest.css0000644000175000017500000000402711346504626017377 0ustar lambylamby/* :Authors: Ian Bicking, Michael Foord :Contact: fuzzyman@voidspace.org.uk :Date: 2005/08/26 :Version: 0.2.0 :Copyright: This stylesheet has been placed in the public domain. rest.css - used for rest2web documents Content rendered by docutils as part of a website http://www.voidspace.org.uk/python/rest2web/ Stylesheet for Docutils. Based on ``blue_box.css`` by Ian Bicking and ``default.css`` revision 3442 */ @import url("default.css"); em, i { /* Typically serif fonts have much nicer italics */ font-family: arial, helvetica, geneva, sans-serif; } a.target { color: blue; } a.toc-backref { text-decoration: none; color: black; } a.toc-backref:hover { background: none ! important } body { background-color: #E0E3D8; color: #000000; margin: 0; } div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { background-color: #cccccc; padding: 3px; width: 80%; } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { text-align: center; background-color: #999999; display: block; margin: 0; } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: #cc0000; /* font-family was misspelled here... */ font-family: arial, helvetica, geneva, sans-serif; text-align: center; background-color: #999999; display: block; margin: 0; } h3 a.toc-backref, h4 a.toc-backref, h5 a.toc-backref, h6 a.toc-backref { color: #000000; } table.footnote { padding-left: 0.5ex; } table.citation { padding-left: 0.5ex } pre.literal-block, pre.doctest-block { border: thin black solid; padding: 5px; } .image img { border-style : solid; border-width : 2px; } h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { font-size: 100%; } code, tt { color: #000066; } span.pre { white-space: normal } /* When set to 'pre' it breaks my page layout on IE */ ./pyke-1.1.1/doc/html/about_pyke/0000755000175000017500000000000011425360453015467 5ustar lambylamby./pyke-1.1.1/doc/html/about_pyke/modifying_pyke.html0000644000175000017500000004646111365362362021411 0ustar lambylamby Modifying Pyke

 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Modifying Pyke

Mercurial Repositories

With Mercurial, you clone the entire repository locally on your computer. Then you can make changes and commit those changes to your local repository. If you think those changes might be interesting to everybody, make your local repository (or a clone of it) publicly available (either on your own server, or on one of the Mercurial Hosting Sites) and send me an email. I will pull your changes, examine them, and push them to the master repository on sourceforge.

Mercurial Keyword Extension

The Pyke sources use the Mercurial Keyword Extension as a holdover from when the repository used Subversion rather than Mercurial.

The hgrc_keywords file has been provided to enable and configure this extension for Pyke use. You can append this file to either your personal .hgrc configuration file (which would then apply to all of your Mercurial projects) or the project .hg/hgrc file (see hgrc in the Mercurial wiki).

If you use a post-clone Mercurial hook, or append hgrc_keywords manually after cloning, the keywords won't be expanded properly when the project is first cloned. But they will be expanded properly if the clone is done with the -U option and then an hg update done in the newly cloned repository (after the changes to .hg/hgrc have been made).

The keyword expansions are only used by the tools that generate the html documentation (see Rebuilding the HTML Documentation, below).

Which Repository Do I Use?

Normally, you will clone one of the following four repositories locally to make a master copy of what's on sourceforge. Then you would clone your master copy (which is very fast) to make separate clones for each development task that you are working on for Pyke.

So it is best to keep all of these clones together in a common directory.

There are four repositories on sourceforge that you can start with:

release_1
Use this for bug fixes, code and documentation cleanup, and anything else that would go into a point release for release 1. I merge the changes made here into all of the other repositories. So this code goes into both the Python2.x and Python3.x versions of Pyke.
pyke
Use this for major new features that would result in a major new release (e.g., release 1.2). I merge the changes made in release_1 into the pyke repository (but maybe not the other way around). And I merge the changes made in the pyke repository into the pre_2to3 repository. So the code here goes into both the Python2.x and Python3.x future versions of Pyke.
pre_2to3_r1

Use this for bug fixes, code and documentation cleanup, and anything else that would go into a point release for release 1, but only apply to the Python3.x version of Pyke. I merge the changes made in release_1 into the pre_2to3_r1 repository (but not the other way around). And I merge the changes made in the pre_2to3_r1 repository into the pre_2to3 repository. So changes here only go into the next point release of the Python3.x version of Pyke.

Warning

This code is maintained in a state just prior to running Python's 2to3 tool on it. So you can't just run the code here directly.

The run_2to3 script runs 2to3 on the current copy of the sources. Do not run this in a repository clone that you still want to use to do commits! Instead, commit all of your changes, then clone the repository and do run_2to3 in the clone. If anything doesn't work, go back to the first repository to fix it, delete the clone, and repeat the whole process. This was done to minimize merge conflicts caused by the 2to3 changes.

The run_pre_test script will:

  • clone the current repository
  • then in the clone do:
    • run_2to3
    • testpyke -3.1
    • python setup.py -q sdist --formats zip
    • insert '3' after 'pyke' in the name of the source distribution zip file.

Run_pre_test assumes that you either have the keywording options set in your personal .hgrc file, or have clone hooks in place to copy these into the .hg/hgrc file of all clones within your pyke work area. See Mercurial Keyword Extension, above.

pre_2to3
Normally I merge changes from the pyke repository and the pre_2to3_r1 repository into pre_2to3 so that nothing needs to be done in this repository. Most major new features would be developed in the pyke repository and merged into pre_2to3. Making changes to pre_2to3 directly would only be done when those changes are for major new features that only apply to the Python3.x version of Pyke.

So, for example, if you wanted to work on the release_1 repository, you would:

$ mkdir pyke_repos
$ cd pyke_repos
$ hg clone -U http://pyke.hg.sourceforge.net:8000/hgroot/pyke/release_1 master
$ hg clone master task_1
$ cd task_1

Note

This assumes that you've added the hgrc_keywords file to your ~/.hgrc file. See Mercurial Keyword Extension, above.

Compiling PLY Tables Files

Pyke uses PLY (Python Lex and Yacc) as it's parser generator. PLY compiles the Pyke grammars into a set of three tables files:

  • kfbparser_tables.py (from kfbparser.py)
  • krbparser_tables.py (from krbparser.py)
  • scanner_tables.py (from scanner.py)

A copy of PLY is included in the source directory (pyke/krb_compiler/ply) so that there there can be no version mismatch between the version of PLY used to compile these tables files and the version of PLY installed on your machine.

To regenerate these tables files, at the top-level source directory:

$ python
>>> from pyke.krb_compiler import kfbparser, krbparser, scanner
>>> scanner.init(scanner, 0, True)
>>> krbparser.init(krbparser, True)
>>> kfbparser.init(kfbparser, True)

or just run the "testall.py" program from the doctest-tools package:

$ cd pyke/krb_compiler
$ testall.py

Compiling the Compiler.krb File

Pyke uses itself to compile your rule base sources (.krb files) into Python source (.py) files.

The knowledge base file that Pyke uses for this is pyke/krb_compiler/compiler.krb. This gets compiled into compiler_bc.py, which is stored in the source code repository.

To recompile the compiler_bc.py file, from the top-level source directory:

$ mkdir pyke/krb_compiler/compiled_krb
$ python
>>> from pyke import krb_compiler
>>> krb_compiler.compile_krb('compiler', 'pyke.krb_compiler.compiled_krb',
...                          'pyke/krb_compiler/compiled_krb',
...                          'pyke/krb_compiler/compiler.krb')
['compiler_bc.py']

$ mv pyke/krb_compiler/compiled_krb/compiler_bc.py pyke/krb_compiler

Running Unit Tests

The doctest-tools package is required to run the unit tests (see Other Required Packages for more details).

The testall.py and testdoc.py scripts from doctest-tools can be run anywhere.

In addition, the top-level directory contains a testpyke script that will delete all of the compiled_krb directories, then run testall.py twice. The first run must recompile all of the knowledge base sources (.krb, .kfb and .kqb files) into the compiled_krb directories in order to run the tests. The second run reuses the files compiled in the first run. This makes sure that all of the tests run properly whether they have to compile the knowledge base sources or not.

Rebuilding the HTML Documentation

The doc/html directory contains all of the documents that you are reading now. These are ready to browse directly from your hard drive if you'd like.

The documentation is generated using the rest2web package, which uses docutils (see Other Required Packages for more details).

The sources for the documentation are in doc/source. Each .txt file there is converted into an .html file in the doc/html directory by running:

$ cd doc/source
$ bin/gen_html

This takes about 9 seconds. It:

  1. Temporarily appends hyperlink references onto all of the *.txt files.
  2. Runs r2w to regenerate the files in doc/html
    • except for those in doc/html/stylesheets and doc/html/images.
  3. Strips all of the hyperlink references from the *.txt files.
  4. Creates a new sitemap.xml file with all of the dates that the files were last modified.

Note

This process uses the date information expanded by the Mercurial Keyword Extension. See Mercurial Keyword Extension, above.

I've gone ahead and placed the generated html files in the source repository so that you can browse the documentation locally without having to run bin/gen_html. So you only need these procedures if you change the documentation (i.e., change the .txt files in doc/source).

To test all of the code examples in the documents, use the testall.py command from the doctest-tools package:

$ cd doc/source
$ testall.py

More:

Cooking Python Functions

Explanation of how Pyke "cooks" Python functions.

What is Pyke?

An overview of Pyke's features.

Steps to Using Pyke

A brief list of the steps involved in programming in Pyke (with lots of links).

Installing Pyke

System Requirements and installing Pyke.

Modifying Pyke

Which source code repository to use. And the other tools that you'll need run the units tests, and rebuild the html documentation.

Page last modified Thu, Mar 11 2010.
./pyke-1.1.1/doc/html/about_pyke/index.html0000644000175000017500000002414311346504626017474 0ustar lambylamby About Pyke
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

About Pyke

What Does Pyke Do for Me?

Pyke was primarily designed to allow you to "cook" your Python code. You write Python code, and then you write Pyke code to cook that Python code -- i.e. to assemble the Python functions that you've written and customize them for a specific situation or use-case.

Pyke can also be used for difficult decision making applications where each part of the problem has multiple possible solutions and the selection of a solution to one part of the problem affects whether another part of the problem can be solved or not.

Cooking Your Python Code

Cooking your Python code is a form of meta-programming, or writing programs that manipulate other programs. I.e., it's a means of programming in the large.

Thus, Pyke provides a way to directly "program in the large", which dovetails with using Python to "program in the small". Pyke supplements but does not replace Python!

Pyke helps programmers to achieve order of magnitude improvements in:

  • Adaptability/Customization
    • Using Pyke allows your Python code to be combined into thousands of different configurations.
    • Thus, your application or library takes on the characteristics of a Domain Specific Language to achieve an order of magnitude increase in adaptability without a corresponding increase in your program's "surface area" to your users.
  • Performance
    • Thinking of your application or library as a Domain Specific Language (DSL), you're using Pyke to "compile" rather than "interpret" your DSL to achieve an order of magnitude improvement in performance.
  • Code Reuse
    • Making your code an order of magnitude more adaptable and an order of magnitude faster allows it to be (re)used in a correspondingly broader range of situations.

Examples of Cooking Python Code

Database Access Library

You're writing a library package to make it easier for Python programmers to access relational databases. You write Python code that deals with the mechanics of accessing relational databases, and then you write Pyke code to make a cooked version of this code for each database access with your user's application.

You might also use Pyke to provide help installing and configuring the database and help creating the schema.

By taking this approach, your library will be an order of magnitude faster than competing database access libraries because you've used Pyke to essentially compile custom code for each database access.

The sqlgen example demonstrates this approach.

HTML Templating Library

Or you're writing an HTML templating package to make it easier for Python programmers to generate HTML. You write Python code that deals with the mechanics of HTML, and then you write Pyke code to make a cooked version of this code for each HTML template.

By taking this approach, your library will be an order of magnitude faster than competing HTML templating libraries because you've used Pyke to essentially compile custom code for each HTML template.

The web_framework example demonstrates this approach. It uses the sqlgen example to make a little web framework. The 2 HTML templates in this example were also done in TurboGears 2 and then a siege benchmark test done on both:

  • TurboGears 2 ran 75.83 transactions/sec
  • The Pyke example ran 791.01 transactions/sec

Linux Configuration Program

Or you're writing a new Linux configuration program. You write the Python code to query and set the various system configuration options, and then you write Pyke code to ask the user what he wants and build a cooked version of your code to make the necessary changes.

In this case, you're not looking for performance. You use Pyke to handle the complicated decision making and use its plan facility to postpone making any configuration changes until your program is sure that it's "dotted all of the i's and crossed all the t's".

More:

Cooking Python Functions

Explanation of how Pyke "cooks" Python functions.

What is Pyke?

An overview of Pyke's features.

Steps to Using Pyke

A brief list of the steps involved in programming in Pyke (with lots of links).

Installing Pyke

System Requirements and installing Pyke.

Modifying Pyke

Which source code repository to use. And the other tools that you'll need run the units tests, and rebuild the html documentation.

Page last modified Mon, Nov 02 2009.
./pyke-1.1.1/doc/html/about_pyke/steps_to_using_pyke.html0000644000175000017500000002223311346504626022460 0ustar lambylamby Steps to Using Pyke
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Steps to Using Pyke

  1. You provide the following to Pyke's knowledge engine:
  2. Repeat for each specific use case:
    1. You provide a set of statements describing this specific use case to Pyke.
    2. You select which set of rules apply to this use case.
    3. Pyke automatically runs all of the selected forward-chaining rules that apply to the statements that you've given to it to deduce new statements.
      • Your forward-chaining rules may interactively ask your end user questions, or get information by executing commands (programs) on the computer that it's running on to help in its decision making.
    4. You ask Pyke a question by having it prove a goal (which is just another statement). This goal may include pattern variables that allow you to ask "for what values is this statement true?".
      • Pyke runs the selected backward-chaining rules against the statements that it has in order to figure out the answer to your question.
      • Your backward-chaining rules may also ask your end user questions and run commands.
      • You may have written Python code at the end of some of your backward-chaining rules. For each such rule, Pyke has compiled this Python code into a Python function called a plan which it has attached to the rule.
      • Once Pyke finds an answer to your question, it gathers all of the plan functions of the rules that it used to find your answer into a complete function call graph. The plan functions are linked together mirroring the way that the rules were linked together to find your answer. In this way, you can write high-level compilers that assemble together and configure a set of Python functions to solve specific problems.
      • Pyke returns the top Python function of this function call graph as a standard Python function along with the answer to your question. You may call this function as may times as you like. You may also pickle the function so that you can send it to another program or save it to disk. You only need one small Pyke module to load and run these pickles.
    5. You reset Pyke to clear out all of these case specific statements and prepare it for the next use case.

More:

Cooking Python Functions

Explanation of how Pyke "cooks" Python functions.

What is Pyke?

An overview of Pyke's features.

Steps to Using Pyke

A brief list of the steps involved in programming in Pyke (with lots of links).

Installing Pyke

System Requirements and installing Pyke.

Modifying Pyke

Which source code repository to use. And the other tools that you'll need run the units tests, and rebuild the html documentation.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/about_pyke/what_is_pyke.html0000644000175000017500000002154511346504626021056 0ustar lambylamby What is Pyke?
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

What is Pyke?

Pyke integrates a form of Logic Programming into Python by providing a knowledge engine that can:

  • Do both forward-chaining (data driven) and backward-chaining (goal directed) inferencing.
    • Pyke may be embedded into any Python program.
  • Automatically generate Python programs by assembling individual Python functions into complete call graphs.
    • This is done through a unique design where the individual Python functions are attached to backward-chaining rules.
    • Unlike other approaches to code reuse (e.g. Object-oriented programming, Zope adapters, generic functions), this allows the inference engine to ensure that all of the function's requirements are completely satisfied, by examining the entire call graph down to the leaves, before any of the functions are executed.
    • This is an optional feature. You don't need to use it if you just want the inferencing capability by itself.

The Knowledge Engine Supports:

  • Multiple fact bases, each with its own list of facts.
  • Both forward-chaining rules and backward-chaining rules.
  • Multiple rule bases, each with its own list of forward-chaining and/or backward-chaining rules.
  • Rule base inheritance -- activating the derived rule base includes the rules from the parent rule base.
  • The inference rules are compiled into Python functions, allowing Python code snippets to be used within the rules. This greatly enhances the expressiveness of the rules.

Automatic Program Generation:

  • Calls the generated Python programs plans.
  • Plans may be run multiple times without needing to rerun the inference rules.
  • Plans may be pickled and cached to disk to be used by other programs or in later runs of the same program.
  • Only one small Pyke module is required to run the plans.

Potential Pyke Applications:

  • Complicated decision making applications.
  • The back-end (code generation and optimization) of compilers. Pyke is used as the back-end of its own compiler that translates rules into Python code.
  • Automatic SQL statement generation.
  • Automatic HTML generation and automatic HTML template processing.
  • Automatic program builder to reuse a common set of functions for many different specific situations. This could also easily incorporate a new custom function into a much larger program, where the use of the custom function might influence the choice of other standard functions in other parts of the program.
  • The control module for a web framework tool.
  • A high-level planner to automatically distribute the modules of a large system over several computers in a distributed system to meet specific performance and capacity goals. This could be used to automatically scale the same system code from a small one program, one computer system to much larger distributed systems to meet a wide range of performance goals.
  • Diagnosis systems, including automated customer service systems.
  • Program or library customization for specific uses.
  • In addition to being able to build programs, Pyke can instantiate, configure and interconnect a network of objects to meet a specific need or situation.

More:

Cooking Python Functions

Explanation of how Pyke "cooks" Python functions.

What is Pyke?

An overview of Pyke's features.

Steps to Using Pyke

A brief list of the steps involved in programming in Pyke (with lots of links).

Installing Pyke

System Requirements and installing Pyke.

Modifying Pyke

Which source code repository to use. And the other tools that you'll need run the units tests, and rebuild the html documentation.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/about_pyke/installing_pyke.html0000644000175000017500000003323211365362362021560 0ustar lambylamby Installing Pyke
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Installing Pyke

Licensing

This software is licensed under the MIT license:

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

System Requirements

Pyke is 100% Python, so it should run on any platform supported by Python. So all you'll need is Python 2.5, 2.6 or 3.1.

Other Required Packages

No other packages are required to develop, run and distribute an application using Pyke. But there are package requirements to do the following additional things:

If you want to you also need minimum version
run the web_framework example HTMLTemplate 1.5
run the unit tests doctest-tools 1.0a3
rebuild the html documentation rest2web 0.5
docutils 0.4.1

If the docutils package is not part of your standard Python installation, there is probably a package for it in the package index for your Linux distribution.

All of the other packages can be installed as the administrator using pip or easy_install. For example:

# pip install HTMLTemplate

Installation

The source code for the latest release can be found on the Pyke project download page as pyke-<release>.zip (for Python2) and pyke3-<release>.zip (for Python3). After unzipping these, go into the directory and run:

$ python setup.py build

And then as administrator, run:

# python setup.py install

The sources include a complete copy of the project directory, including the documentation, unit tests, and examples.

If you want to clone the source code repository to contribute to the project development, or to use the latest developer version, read Modifying Pyke.

Run the Examples

There are several examples that are contained in the source directory. Each example is in it's own subdirectory under the examples subdirectory, and each has it's own README.txt file that explains how to run it.

The web_framework example requires the HTMLTemplate package, version 1.5 or later. This can be installed as administrator with pip or easy_install:

# pip install HTMLTemplate

See also Examples.

Viewing the HTML Documentation

This HTML documentation may be viewed directly from your hard drive. The HTML files are in the doc/html directory. Start with doc/html/index.html.

Repository Directory Structure

You'll see the following directories.

  • doc
    • the html directory has all of the HTML documentation ready to browse off of your hard drive. Start with doc/html/index.html.
    • the source directory has all of the sources that were used to generated the HTML documentation. See Rebuilding the HTML Documentation.
    • the examples directory just has a copy of the examples used by the .txt files in the source directory so that the doctests will work on the source directory. You should be able to skip this unless you change an example in one of the source files.
    • cheatsheets are a collection of text files with notes on various tools used by Pyke, and processes used to maintain Pyke.
  • examples
    • There are several examples. Start with family_relations. Look at the README.txt file for each example to see how to run it. See also, Examples.
  • experimental
    • This is a catch-all directory for various ideas that have been tried, but that have not been incorporated into Pyke. You can safely skip over this directory...
  • pyke
    • This is the top-level Python package directory for the Python sources. This needs to be installed into a directory on your PYTHONPATH. The sources for the compilers are in the krb_compiler subdirectory, which is expected to be a subpackage of pyke.
  • Test
    • This is where the unit test scripts are stored. These use Python's doctest package. Each test file has a .tst suffix.
    • See Running Unit Tests.

More:

Cooking Python Functions

Explanation of how Pyke "cooks" Python functions.

What is Pyke?

An overview of Pyke's features.

Steps to Using Pyke

A brief list of the steps involved in programming in Pyke (with lots of links).

Installing Pyke

System Requirements and installing Pyke.

Modifying Pyke

Which source code repository to use. And the other tools that you'll need run the units tests, and rebuild the html documentation.

Page last modified Thu, Mar 11 2010.
./pyke-1.1.1/doc/html/about_pyke/cooking_functions.html0000644000175000017500000002622011365362362022104 0ustar lambylamby Cooking Python Functions
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Cooking Python Functions

"Cooking" a Python function means customizing it. And you customize it by cooking certain parameter values into it as constant values.

Cooking a single parameter value

First you define the function that you want to cook with an extra parameter at the start:

>>> def foo(cooked, standard):
...     print "foo called with cooked: %s, standard: %s" % \
...               (cooked, standard)

Now you can call this function with two parameters:

>>> foo('a', 'b')
foo called with cooked: a, standard: b

But your real intention is that it appear to be a function taking one parameter, with the first parameter cooked in.

This is done with the partial class of the functools module in the standard Python library.

>>> from functools import partial

And then using partial to cook the first parameter:

>>> cooked1 = partial(foo, 'cooked_value1')

Now cooked_foo is a function that takes one parameter:

>>> cooked1('value1')
foo called with cooked: cooked_value1, standard: value1
>>> cooked1('value2')
foo called with cooked: cooked_value1, standard: value2

And you can make other cooked functions from foo with other cooked values:

>>> cooked2 = partial(foo, 'cooked_value2')
>>> cooked2('value1')
foo called with cooked: cooked_value2, standard: value1
>>> cooked2('value2')
foo called with cooked: cooked_value2, standard: value2

And you can still use the first cooked function, so now you have two functions for the price of one!

>>> cooked1('value3')
foo called with cooked: cooked_value1, standard: value3
>>> cooked1('value4')
foo called with cooked: cooked_value1, standard: value4
>>> cooked2('value5')
foo called with cooked: cooked_value2, standard: value5
>>> cooked2('value6')
foo called with cooked: cooked_value2, standard: value6

And you can keep going with this to make as many functions as you care to from your single starting function.

Cooking a Function Call Graph

This same technique can be used to cook a function call graph, by making the subordinate function a cooked parameter:

>>> def bar(child_fun, a):
...     print "bar called with:", a
...     return child_fun(a)

And now you can cook which function bar calls the same way you cook any other parameter:

>>> bar_float = partial(bar, float)
>>> bar_float('123')
bar called with: 123
123.0
>>> bar_min = partial(bar, min)
>>> bar_min((3,2,5))
bar called with: (3, 2, 5)
2

And, of course, you can use cooked functions as these subordinate functions too:

>>> bar_cooked1 = partial(bar, cooked1)
>>> bar_cooked1('abc')
bar called with: abc
foo called with cooked: cooked_value1, standard: abc

Which means that you can create function call graphs to any depth:

>>> bar_bar_min = partial(bar, bar_min)
>>> bar_bar_min((3,2,5))
bar called with: (3, 2, 5)
bar called with: (3, 2, 5)
2

Cooking Several Parameters

In general, you may want to cook several values for each function. Some of these values may specify which subordinate functions to call, others may just fix certain constant values for the function.

Pyke does this using a single extra parameter called context, which is a read-only dictionary. It can then prepare this dictionary with as many values as it needs and then cook the whole dictionary into the function using partial.

Pyke translates each individual access to a cooked parameter into a dictionary lookup on context that looks up that parameter name:

context['parameter_name']

The Need for Pyke

Now that you understand how Pyke cooks Python functions, you should be able to understand how this technique can achieve the "order of magnitude" improvements to Adaptability/Customization, Performance and Code Reuse discussed on the About Pyke page.

You should also now see the need for a tool like Pyke to assemble all of these functions to fit specific situations and use cases.

Note

Pyke calls a customized function call graph a plan. Plans are explained later, after you've been introduced to Logic Programming in Pyke.

And, finally, you should start to get a sense for how "programming in the large" with Pyke dovetails with "programming in the small" with Python.

More:

Cooking Python Functions

Explanation of how Pyke "cooks" Python functions.

What is Pyke?

An overview of Pyke's features.

Steps to Using Pyke

A brief list of the steps involved in programming in Pyke (with lots of links).

Installing Pyke

System Requirements and installing Pyke.

Modifying Pyke

Which source code repository to use. And the other tools that you'll need run the units tests, and rebuild the html documentation.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/using_pyke/0000755000175000017500000000000011425360453015502 5ustar lambylamby./pyke-1.1.1/doc/html/using_pyke/index.html0000644000175000017500000003010111365362360017474 0ustar lambylamby Using Pyke
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Using Pyke

This describes how to call Pyke from your Python program.

Getting Started

The simplest use of Pyke involves three steps:

Create an engine object.

>>> from pyke import knowledge_engine
>>> my_engine = knowledge_engine.engine(__file__)

This step compiles the Pyke source files, if out of date, and loads the knowledge bases.

There are three kinds of Pyke source files:

  1. .kfb files define fact bases, which are compiled into .fbc pickle files.
  2. .krb files define rule bases, which are compiled into 1 to 3 .py Python source files.
  3. .kqb files define question bases, which are compiled into .qbc pickle files.

See Creating an Inference Engine to control where the compiled files are written, load knowledge bases from multiple directories, distribute your application without your knowledge base files, or distribute using egg files.

Activate rule bases.

>>> my_engine.activate('bc_related')

You may activate one rule base for each rule base category. Simply pass multiple arguments to activate.

Note

Even if you only have one rule base, you must still activate it.

This is when the forward-chaining rules are run.

Prove goal.

>>> my_engine.prove_1_goal('bc_related.father_son(bruce, $son, ())')
({'son': 'david'}, None)

The goal might be met by simply matching an already known fact, or through the use of backward-chaining rules.

Then if you want to prove another goal, you can just repeat the last step. In this case, the forward-chaining rules are only run once and all goals operate against the same set of known facts.

>>> my_engine.prove_1_goal('bc_related.father_son(thomas, $grandson, (grand))')
({'grandson': 'david'}, None)

See Proving Goals to pass different arguments into goals, compile the goal statements once in advance, and to retrieve multiple answers for a goal.

Dynamically Asserting Facts

To dynamically assert facts within your Python program, a new step is added:

Create the engine object:

>>> my_engine = knowledge_engine.engine(__file__)

Assert facts.

>>> my_engine.assert_('family2', 'son_of', ('spike_the_dog', 'david'))

These facts must be asserted prior to activating the rule bases so that they are available to the forward-chaining rules. This example shows asserting case specific facts that are deleted before running the next case (as shown in the next section, below). But you can also assert universal facts that apply to all cases. See Asserting New Facts for more information.

After asserting your facts, activate your rule bases and prove your goal as before:

>>> my_engine.activate('bc_related')
>>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))')
({'grandson': 'spike_the_dog'}, None)

Using Different Facts for Different Cases

But if you want to prove goals against different sets of facts or using different rule bases, you need to reset the Pyke engine:

Only need this once:

>>> my_engine = knowledge_engine.engine(__file__)

First case, as before:

>>> my_engine.assert_('family2', 'son_of', ('spike_the_dog', 'david'))
>>> my_engine.activate('bc_related')
>>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))')
({'grandson': 'spike_the_dog'}, None)

Reset the Pyke engine.

>>> my_engine.reset()

This erases all of the case specific facts that you asserted in step 2, as well as all of the facts asserted by the forward-chaining rules.

It also deactivates all of the rule bases, so you'll need to call activate again after asserting your facts.

Second case:

>>> my_engine.assert_('family2', 'son_of', ('felix_the_cat', 'david'))
>>> my_engine.activate('bc_related')
>>> my_engine.prove_1_goal('bc_related.father_son(bruce, $grandson, (grand))')
({'grandson': 'felix_the_cat'}, None)

More:

Creating an Inference Engine Object

How to create a Pyke inference engine object.

Asserting New Facts

How to dynamically assert new facts from your Python program.

Proving Goals

Using Pyke's API to prove goals from your Python program.

Other Functions

Other miscellaneous functions available that you might be interested in, such as tracing rules and smart error tracebacks that show lines from your .krb files.

Page last modified Wed, Mar 10 2010.
./pyke-1.1.1/doc/html/using_pyke/adding_facts.html0000644000175000017500000002047111365362360021004 0ustar lambylamby Asserting New Facts
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Asserting New Facts

some_engine.add_universal_fact(kb_name, fact_name, arguments)

The add_universal_fact function is called once per fact. These facts are never deleted and apply to all cases.

Alternatively, you can place universal facts in a .kfb file so that they are loaded automatically.

>>> my_engine.add_universal_fact('family', 'son_of', ('bruce', 'thomas'))

Multiple facts with the same name are allowed.

>>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce'))

But duplicate facts (with the same arguments) are silently ignored.

>>> my_engine.add_universal_fact('family', 'son_of', ('david', 'bruce'))
>>> my_engine.get_kb('family').dump_universal_facts()
son_of('bruce', 'thomas')
son_of('david', 'bruce')

These facts are accessed as kb_name.fact_name(arguments) within the .krb files.

some_engine.assert_(kb_name, fact_name, arguments)

Call assert_ for each starting fact for this case. Like universal facts, you may have multiple facts with the same name so long as they have different arguments.

>>> my_engine.assert_('family', 'son_of', ('michael', 'bruce'))
>>> my_engine.assert_('family', 'son_of', ('fred', 'thomas'))
>>> my_engine.assert_('family', 'son_of', ('fred', 'thomas'))

Duplicates with universal facts are also ignored.

>>> my_engine.assert_('family', 'son_of', ('bruce', 'thomas'))
>>> my_engine.get_kb('family').dump_specific_facts()
son_of('michael', 'bruce')
son_of('fred', 'thomas')
>>> my_engine.get_kb('family').dump_universal_facts()
son_of('bruce', 'thomas')
son_of('david', 'bruce')

There is no difference within the .krb files of how universal facts verses case specific facts are used. The only difference between the two types of facts is that the case specific facts are deleted when a reset is done.

>>> my_engine.reset()
>>> my_engine.get_kb('family').dump_specific_facts()
>>> my_engine.get_kb('family').dump_universal_facts()
son_of('bruce', 'thomas')
son_of('david', 'bruce')

More:

Creating an Inference Engine Object

How to create a Pyke inference engine object.

Asserting New Facts

How to dynamically assert new facts from your Python program.

Proving Goals

Using Pyke's API to prove goals from your Python program.

Other Functions

Other miscellaneous functions available that you might be interested in, such as tracing rules and smart error tracebacks that show lines from your .krb files.

Page last modified Mon, Mar 08 2010.
./pyke-1.1.1/doc/html/using_pyke/creating_engine.html0000644000175000017500000003060011365362360021512 0ustar lambylamby Creating an Inference Engine Object
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Creating an Inference Engine Object

The engine object is your gateway into Pyke. Each engine object manages multiple knowledge bases related to accomplishing some task.

You may create multiple Pyke engines, each with it's own knowledge bases to accomplish different disconnected tasks.

When you create a Pyke engine object, Pyke scans for Pyke .kfb, .krb and .kqb source files and compiles these into .fbc pickle files, Python .py source files and .qbc pickle files, respectively.

Each time a Pyke engine object is created it checks the file modification times of the Pyke source files to see whether they need to be recompiled. If you change a Pyke source file, you may create a new Pyke engine to compile the changes and run with the new knowledge bases without having to restart your application.

Pyke also lets you zip these compiled files into Python eggs and can load the files from the egg. By including the compiled files in your application's distribution, you don't need to include your Pyke source files if you don't want to.

Once you have an engine object; generally, all of the Pyke functions that you need are provided directly by this object:

knowledge_engine.engine(*paths, **kws)

Pyke recursively searches for Pyke source files (.kfb files, .krb files, and .kqb files) starting at each source directory indicated in paths and places all of the compiled files in the associated target packages. This causes all of the knowledge bases to be loaded and made ready to activate.

Pyke source files may be spread out over multiple directories and may be compiled into one or more target packages. Multiple target packages would be used when more than one application wants to share a set of knowledge bases, perhaps adding some of its own knowledge that it compiles into its own target package.

Each path argument specifies a Pyke source directory and an optional target package. The source directories do not have to be Python package directories, but the target packages do.

The target package defaults to .compiled_krb. The leading dot (.) indicates that the compiled_krb directory will be subordinate to the lowest Python package directory on the path to the Pyke source directory. This uses Python's relative import notation, so multiple dots go up to higher directories (one per dot). If the target package does not start with a dot, it is taken to be an absolute package path and will be located using Python's sys.path like a normal Python import.

The Pyke source directory may be specified as a path (a string) or by passing a Python module. If a module is passed, its __file__ attribute is used. If the path points to a file, rather than a directory, the final filename is discarded. In the simple case, when the Pyke source files are in the same directory as the module creating the engine object, you can just pass __file__ as the sole argument.

>>> from pyke import knowledge_engine
>>> my_engine = knowledge_engine.engine(__file__)

Passing a package instead, this example could also be written:

>>> import doc.examples
>>> my_engine = knowledge_engine.engine(doc.examples)

or, you can pass a module within the desired package:

>>> from doc.examples import some_module
>>> my_engine = knowledge_engine.engine(some_module)

In the all three cases, the final filename is stripped from the value of the module's __file__ attribute to get the directory that the package is in. This directory will then be recursively searched for Pyke source files.

If you change some of your Pyke source files, you can create a new engine object to compile and reload the generated Python modules without restarting your program. But note that you'll need to rerun the add_universal_fact calls that you made (a reason to use .kfb files instead).

All of the compiled Python .py source files and .fbc/.qbc pickle files generated from each source directory are placed, by default, in a compiled_krb target package. You may specify a different target package for any source directory by passing that source directory along with the target package name as a 2-tuple. Thus, specifying the default target package explicitly would look like:

>>> my_engine = knowledge_engine.engine((__file__, '.compiled_krb'))

You may specify the same target package for multiple source directories.

The last component of the target package will be created automatically if it does not already exist.

Note

You will probably want to add compiled_krb (or whatever you've chosen to call it) to your source code repository's list of files to ignore.

If you want to distribute your application without the knowledge bases, you can use the 2-tuple notation with None as the source directory. In this case, all of the Pyke source files must already be compiled, and Pyke will simply load these files. Also, the target package must be specified in absolute form (with no leading dots).

Finally, there are four optional keyword arguments that you may also pass to the engine constructor. These are all booleans that default to True:

  • load_fb -- load fact bases
  • load_fc -- load forward-chaining rules
  • load_bc -- load backward-chaining rules and
  • load_qb -- load question bases

These parameters must be passed as keyword parameters and let you selectively load the various kinds of compiled files.

More:

Creating an Inference Engine Object

How to create a Pyke inference engine object.

Asserting New Facts

How to dynamically assert new facts from your Python program.

Proving Goals

Using Pyke's API to prove goals from your Python program.

Other Functions

Other miscellaneous functions available that you might be interested in, such as tracing rules and smart error tracebacks that show lines from your .krb files.

Page last modified Mon, Mar 29 2010.
./pyke-1.1.1/doc/html/using_pyke/other_functions.html0000644000175000017500000003102011365362360021577 0ustar lambylamby Other Functions
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Other Functions

Running and Pickling Plans

Once you've obtained a plan from prove_goal or prove_1_goal, you just call it like a normal Python function. The arguments required are simply those specified, if any, in the taking clause of the rule proving the top-level goal.

You may call the plan function any number of times. You may even pickle the plan for later use. But the plans are constructed out of functools.partial functions, that need to be registered with copy_reg if you are running Python 2.x:

>>> import copy_reg
>>> import functools
>>> copy_reg.pickle(functools.partial,
...                 lambda p: (functools.partial, (p.func,) + p.args))

No special code is required to unpickle a plan. Just unpickle and call it. (Unpickling the plan only imports one small Pyke module to be able to run the plan).

Tracing Rules

Individual rules may be traced to aid in debugging. The trace function takes two arguments: the rule base name, and the name of the rule to trace:

>>> my_engine.trace('bc_related0', 'grand_father_son')
>>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)')
bc_related0.grand_father_son('thomas', 'david', '$depth')
bc_related0.grand_father_son succeeded with ('thomas', 'david', ('grand',))
({'depth': ('grand',)}, None)

This can be done either before or after rule base activation and will remain in effect until you call untrace:

>>> my_engine.untrace('bc_related0', 'grand_father_son')
>>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)')
({'depth': ('grand',)}, None)

Krb_traceback

A handy traceback module is provided to convert Python functions, lines and line numbers to the .krb file rule names, lines and line numbers in a Python traceback. This makes it much easier to read the tracebacks that occur during proofs.

The krb_traceback module has exactly the same functions as the standard Python traceback module, but they convert the generated Python function information into .krb file information. They also delete the intervening Python functions between subgoal proofs.

>>> import sys
>>> from pyke import knowledge_engine
>>> from pyke import krb_traceback
>>>
>>> my_engine = knowledge_engine.engine(__file__)
>>> my_engine.activate('error_test')
>>> try:                                            # doctest: +ELLIPSIS
...     my_engine.prove_1_goal('error_test.goal()')
... except:
...     krb_traceback.print_exc(None, sys.stdout)   # sys.stdout needed for doctest
Traceback (most recent call last):
  File "<doctest other_functions.txt[19]>", line 2, in <module>
    my_engine.prove_1_goal('error_test.goal()')
  File "...knowledge_engine.py", line 366, in prove_1_goal
    return goal.compile(goal_str).prove_1(self, **args)
  File "...goal.py", line 47, in prove_1
    return iter(it).next()
  File "...rule_base.py", line 50, in next
    return self.iterator.next()
  File "...knowledge_engine.py", line 41, in from_iterable
    for x in iterable: yield x
  File "...knowledge_engine.py", line 41, in from_iterable
    for x in iterable: yield x
  File "...error_test.krb", line 26, in rule1
    goal2()
  File "...error_test.krb", line 31, in rule2
    goal3()
  File "...error_test.krb", line 36, in rule3
    goal4()
  File "...error_test.krb", line 41, in rule4
    check $bar > 0
  File "...contexts.py", line 231, in lookup_data
    raise KeyError("$%s not bound" % var_name)
KeyError: '$bar not bound'

Miscellaneous

There are a few more functions that may be useful in special situations.

some_engine.add_case_specific_fact(kb_name, fact_name, args)
This is an alternate to the assert_ function.
some_engine.get_kb(kb_name)
Finds and returns the knowledge base by the name kb_name. Raises KeyError if not found. Note that for rule bases, this returns the active rule base where kb_name is the rule base category name. Thus, not all rule bases are accessible through this call.
some_engine.get_rb(rb_name)
Finds and returns the rule base by the name rb_name. Raises KeyError if not found. This works for any rule base, whether it is active or not.
some_engine.print_stats([f = sys.stdout])
Prints a brief set of statistics for each knowledge base to file f. These are reset by the reset function. This will show how many facts were asserted, and counts of how many forward-chaining rules were fired and rerun, as well as counts of how many backward-chaining goals were tried, and how many backward-chaining rules matched, succeeded and failed. Note that one backward-chaining rule may succeed many times through backtracking.

More:

Creating an Inference Engine Object

How to create a Pyke inference engine object.

Asserting New Facts

How to dynamically assert new facts from your Python program.

Proving Goals

Using Pyke's API to prove goals from your Python program.

Other Functions

Other miscellaneous functions available that you might be interested in, such as tracing rules and smart error tracebacks that show lines from your .krb files.

Page last modified Fri, Mar 12 2010.
./pyke-1.1.1/doc/html/using_pyke/proving_goals.html0000644000175000017500000002316411365362360021251 0ustar lambylamby Proving Goals
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Proving Goals

Though Pyke has the capability to return multiple answers to a single goal, often you just want the first answer:

some_engine.prove_1_goal(goal, **args)

goal is a Pyke goal (as a string). This may include pattern variables (which start with a '$').

>>> my_engine.prove_1_goal('bc_related0.father_son(thomas, david, $depth)')
({'depth': ('grand',)}, None)

Returns the first proof found as a 2-tuple: a dict of bindings for the pattern variables, and a plan. The plan is None if no plan was generated; otherwise, it is a Python function as described here.

Args must be specified as keyword arguments and are set as the value of the corresponding pattern variable.

>>> vars, plan = \
...   my_engine.prove_1_goal('bc_related0.father_son($father, $son, $depth)',
...                          father='thomas',
...                          son='david')
>>> sorted(vars.items(), key=lambda item: item[0])
[('depth', ('grand',)), ('father', 'thomas'), ('son', 'david')]

Prove_1_goal raises pyke.knowledge_engine.CanNotProve if no proof is found:

>>> my_engine.prove_1_goal('bc_related0.father_son(thomas, bogus, $depth)')
Traceback (most recent call last):
    ...
CanNotProve: Can not prove bc_related0.father_son(thomas, bogus, $depth)
some_engine.prove_goal(goal, **args)

This returns a context manager for a generator yielding 2-tuples, as above. Unlike prove_1_goal it does not raise an exception if no proof is found:

>>> from __future__ import with_statement
>>> with my_engine.prove_goal(
...        'bc_related0.father_son(thomas, $son, $depth)') as gen:
...     for vars, plan in gen:
...         print vars['son'], vars['depth']
bruce ()
david ('grand',)

Like prove_1_goal, above, pattern variables in the goal may be specified with keyword arguments:

>>> with my_engine.prove_goal(
...        'bc_related0.father_son($father, $son, $depth)',
...        father='thomas') as gen:
...     for vars, plan in gen:
...         print vars['son'], vars['depth']
bruce ()
david ('grand',)

Compiling Goals at Program Startup

Similar to Python's regular expression library, re, you may compile your goal statements once at program startup:

>>> from pyke import goal
>>> my_goal = goal.compile('bc_related0.father_son($father, $son, $depth)')

Then use my_goal.prove_1 and my_goal.prove as many times as you'd like:

>>> vars, plan = my_goal.prove_1(my_engine, father='thomas', son='david')
>>> sorted(vars.items(), key=lambda item: item[0])
[('depth', ('grand',)), ('father', 'thomas'), ('son', 'david')]
>>> with my_goal.prove(my_engine, father='thomas') as gen:
...     for vars, plan in gen:
...         print vars['son'], vars['depth']
bruce ()
david ('grand',)

More:

Creating an Inference Engine Object

How to create a Pyke inference engine object.

Asserting New Facts

How to dynamically assert new facts from your Python program.

Proving Goals

Using Pyke's API to prove goals from your Python program.

Other Functions

Other miscellaneous functions available that you might be interested in, such as tracing rules and smart error tracebacks that show lines from your .krb files.

Page last modified Tue, Mar 09 2010.
./pyke-1.1.1/doc/html/knowledge_bases/0000755000175000017500000000000011425360453016461 5ustar lambylamby./pyke-1.1.1/doc/html/knowledge_bases/index.html0000644000175000017500000002207011365362362020463 0ustar lambylamby Knowledge Bases
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Knowledge Bases

Knowledge is organized into named repositories called knowledge bases. A knowledge base is like a directory for files on disk, except that knowledge bases may not be nested. Therefore, the entities within a knowledge base always have a two-level name: knowledge_base_name.knowledge_entity_name.

Here are some examples of facts you might see in a web server application:

header.cookie('session=123456789;remember=the_alamo')
cookie.session(123456789)
cookie.remember(the_alamo)
header.accept_encoding(gzip)
header.accept_encoding(deflate)
request.path('/my/site.html')
request.path_segment(0, my)
request.path_segment(1, 'site.html')
request.path_segment(-2, my)
request.path_segment(-1, 'site.html')

Note that three different knowledge bases (all fact bases) are shown here named header, cookie, and request; each with multiple facts.

The second part of the two-part name is the name of the knowledge entity. You can think of knowledge entities as statement types or topics. So:

request:is the name of the knowledge base.
request.path_segment:
 is the name of the knowledge entity, or statement topic.
request.path_segment(-1, 'site.html'):
 is a specific statement about the topic of request.path_segments.

What do Knowledge Bases Do?

Ultimately Pyke is interested in proving statements, or answering the question "Is statement X true"? There are several different types of knowledge bases. Each type of knowledge base represents a different way of doing this:

  • Those that contain simple lists of statements of fact (as you see in the example above) are called fact bases.
    • These determine whether a statement is true by simply checking to see if that statement is in their list of known facts.
  • Those that contain if-then rules are called rule bases.
    • These determine whether a statement is true by running their if-then rules to try to construct a proof for that statement.
    • Rule bases may include both forward-chaining and backward-chaining rules.
  • It is also possible to create other kinds of knowledge bases that determine the truth of statements in other ways. Pyke provides two of these:
    • The question base which just poses the statement to an end user as a question.
    • The special knowledge base which has several special one-off knowledge entities to do different things like run a command on the underlying system and examine its output and/or exit status.
      • There is only has one instance of this knowledge base -- called special.

Note

All knowledge bases share the same name space. So no two of them, regardless of their type, may have the same name.

More:

Fact Bases

Explanation of facts and fact bases.

Rule Bases

Explanation of rule bases, overview of .krb files and how these files are compiled and loaded into your Python program.

Question Bases

Explanation of question bases and .kqb files.

Special

Explanation of the special knowledge base.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/knowledge_bases/special.html0000644000175000017500000003554111365362362021003 0ustar lambylamby Special
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Special

There is only one instance of this 'special' knowledge base, called special.

The special knowledge base is a collection of miscellaneous helper knowledge entities that determine whether a statement is true or not in various interesting ways.

Thus, each entity in this knowledge base is a Python function that does something "special" when run.

The special functions are:

Claim_goal

The claim_goal function has no arguments:

special.claim_goal()

This acts like the Prolog cut operator.

In general there are multiple rules that might be used to try to prove any goal. They are each tried in the order that they appear in the .krb file. If one rule fails, the next rule is tried. The goal itself doesn't fail until all of the rules for it have failed.

Example

Suppose I want to translate a number, N, into the phrase "N dogs". I could use the following rules:

one_dog
    use n_dogs(1, '1 dog')

n_dogs
    use n_dogs($n, $phrase)
    when
        $phrase = "%d dogs" % $n

The problem here is that both rules might be used when n is 1, but the second rule isn't appropriate in this case. Special.claim_goal() may be used to fix this, as follows:

one_dog
    use n_dogs(1, '1 dog')
    when
        special.claim_goal()

n_dogs
    use n_dogs($n, $phrase)
    when
        $phrase = "%d dogs" % $n

The special.claim_goal() prevents the second rule from being used when n is 1.

Explanation

When a rule executes special.claim_goal() in its when clause, none of the rest of the rules will be tried for that goal. Thus, when special.claim_goal() is backtracked over, the goal fails immediately without trying any more rules for it.

This ends up acting like an "else". You place it in the when clause after the premises that show that this rule must be the correct one to use. Then the subsequent rules will only be tried if these premises fail, such that special.claim_goal() is never executed.

This means that you don't need to add extra premises in each subsequent rule to make sure that these premises have not occurred.

Without the special.claim_goal() in the prior example, you would have to write:

one_dog
    use n_dogs(1, '1 dog')

n_dogs
    use n_dogs($n, $phrase)
    when
        check $n != 1
        $phrase = "%d dogs" % $n

This is a simple example where it is easy to add the check in the second rule. But in general, it can be difficult to check for prior conditions, especially when many rules are involved that each has its own condition.

Running Commands

The remaining three functions deal with running programs (commands) on the host computer that is running your Pyke program. Their difference is in what kind of output they provide from the command.

These functions all use the subprocess.Popen function from the standard Python library.

Thus, each of these functions accept these three parameters that are passed on to subprocess.Popen:

  • The $command parameter (required).
    • This is a tuple indicating the program to run along with its command line arguments, such as (ls, '-l').
  • The $cwd parameter (optional).
    • This specifies the current working directory to run the command in.
    • If omitted or None the current working directory is not changed.
  • The $stdin parameter (optional).
    • This is a string that is fed to the command as its stdin.
      • If the command expects multiple lines of input, this string must include embedded newlines (e.g., 'line 1\nline 2\n').
    • If omitted or None, no stdin is provided to the command.

All of these functions fail on backtracking.

Check_command

special.check_command($command [, $cwd [, $stdin]])

Succeeds if the command returns a zero exit status. Fails otherwise. Any output from the command to stdout or stderr is unavailable.

>>> from pyke import knowledge_engine
>>> engine = knowledge_engine.engine()
>>> engine.prove_1_goal('special.check_command((true))')
({}, None)
>>> engine.prove_1_goal('special.check_command((false))')
Traceback (most recent call last):
    ...
CanNotProve: Can not prove special.check_command((false))

Command

special.command($stdout, $command [, $cwd [, $stdin]])

This just looks at the stdout of the command. Any output from the command to stderr is unavailable.

The $stdout is a tuple of lines with the trailing newlines removed.

This raises subprocess.CalledProcessError if the command returns a non-zero exit status.

>>> from __future__ import with_statement
>>> from pyke import pattern, contexts
>>> def run_command(entity, command, cwd=None, stdin=None):
...     with engine.prove_goal(
...            'special.%s($output, $command, $cwd, $stdin)' % entity,
...            command=command,
...            cwd=cwd,
...            stdin=stdin) \
...       as gen:
...         for vars, no_plan in gen:
...             print vars['output']
>>> run_command('command', ('echo', 'hi', 'mom'))
('hi mom',)
>>> run_command('command', ('ls',))   # doctest: +NORMALIZE_WHITESPACE
('fact_bases.txt', 'index.txt', 'links', 'question_bases.txt',
 'rule_bases.txt', 'special.txt')
>>> run_command('command', ('ls', '-l', 'links')) # doctest: +ELLIPSIS
('-rw-r--r-- 1 ... links',)
>>> run_command('command', ('tail', '-n', '5', 'template.txt', '-'),
...             '..',   # cwd (doc/source)
...             'stdin: line 1\nstdin: line 2\nstdin: line 3\n')
...     # doctest: +NORMALIZE_WHITESPACE
('==> template.txt <==',
 '   } catch(err) {}',
 '  </script>',
 '</body>',
 '</html>',
 '',
 '',
 '==> standard input <==',
 'stdin: line 1',
 'stdin: line 2',
 'stdin: line 3')
>>> run_command('command', ('false',))
Traceback (most recent call last):
    ...
CalledProcessError: Command 'false' returned non-zero exit status 1

General_command

special.general_command($output, $command [, $cwd [, $stdin]])

This is the fully general form that gives you all output from the command.

The $output is a three tuple: (exit_status, stdout, stderr). Both stdout and stderr are single strings (with embedded newlines).

>>> run_command('general_command', ('echo', 'hi', 'mom'))
(0, 'hi mom\n', '')
>>> run_command('general_command', ('cat', 'foobar'))
(1, '', 'cat: foobar: No such file or directory\n')
>>> run_command('general_command', ('tail', '-n', '5', '../../r2w.ini', 'foobar'))
...     # doctest: +NORMALIZE_WHITESPACE
(1,
 "==> ../../r2w.ini <==\ntarget_directory = 'html'\nmacros =
     ''\n\n[uservalues]\nversion = '0.2'\n",
 "tail: cannot open `foobar' for reading: No such file or directory\n")

More:

Fact Bases

Explanation of facts and fact bases.

Rule Bases

Explanation of rule bases, overview of .krb files and how these files are compiled and loaded into your Python program.

Question Bases

Explanation of question bases and .kqb files.

Special

Explanation of the special knowledge base.

Page last modified Fri, Mar 05 2010.
./pyke-1.1.1/doc/html/knowledge_bases/rule_bases.html0000644000175000017500000004502311365362362021503 0ustar lambylamby Rule Bases
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Rule Bases

Rule bases are collections of rules. Rule bases are created by writing a knowledge rule base (.krb) file with your favorite text editor.

A single rule base may contain both forward-chaining and backward-chaining rules.

The forward-chaining rules are run automatically when the rule base is activated to assert new statements of fact. Thus, forward-chaining rules are not directly used to determine whether any particular statement is true.

But backward-chaining rules are directly used to determine whether a particular statement is true. Thus, when a rule base name (or, more properly, a rule base category name, explained below) is used as the knowledge base name in a statement, it refers to the backward-chaining rules within that rule base.

Note

Pyke runs all forward-chaining rules before running any backward-chaining rules.

Thus, using rule base names as the knowledge base name in statements within a forward-chaining rule is prohibited, as this would cause backward-chaining rules to run in order to process the forward-chaining rule.

Why Multiple Rule Bases?

There are two reasons to have more than one rule base (i.e., more than one .krb file):

  1. To divide a large set of rules into human manageable units.

    In this case, you want Pyke to use all of the rule bases combined.

    For example, you may have rules governing the automatic generation of SQL statements, and other rules governing the generation of HTML documents. You want to keep these rules in different rule bases to make them more manageable.

  2. To enable your Python program to choose between different rule bases that are tailored to different specific situations.

    For example, some of the rules governing the automatic generation of SQL statements may vary depending upon the target database (e.g., mysql, postgresql, oracle).

    In this case, you want to be able to tell Pyke which of several rule bases to use on each invocation, depending upon which target database you will be accessing. You would never use more than one of these rule bases at the same time, so these rule bases are mutually exclusive.

These two goals are met by three capabilities:

Rule Base Categories

All rule bases are grouped into categories. Each rule base category only gets to have one active rule base.

Thus, you place rule bases that you want to have active simultaneously into different rule base categories; and place rule bases that are mutually exclusive with each other (e.g., the mysql, postgresql and oracle rule bases) into the same rule base category.

Each rule base category has a unique name. In the example above you would want two rule base categories: database and html_generation.

The rule base category name is used as the knowledge base name for statements within rules of one rule base category that want to refer to rules within another rule base category. For example, rule bases in the html_generation category would need to use database.insert(...) to refer to the insert rules in the database category.

Rule Base Inheritance

The rule bases within the same category share rules amongst themselves through rule base inheritance.

Rule bases use single inheritance to inherit the rules from one other rule base. This can go on to any depth. Both forward-chaining and backward-chaining rules are inherited.

This allows mutually exclusive rule bases to share common rules in a parent rule base without having to duplicate these rules amongst themselves.

Here is the definition, then, of a rule base category:

Each root rule base (through rule base inheritance) defines a unique rule base category. All rule bases derived (directly or indirectly) from that root rule base are in the same rule base category.

The name of the rule base category is simply the name of its root rule base.

So, our database and html_generation example would look like this:

../images/rule_base_categories.png

Rule Base Categories

We have one root rule base called database and it has derived rule bases called mysql, postgresql and oracle. And a second root rule base called html_generation with firefox and internet_explorer.

The two root rule bases define two rule base categories:

  • database, which includes:
    • database
    • mysql
    • postgresql
    • oracle
  • html_generation, which includes:
    • html_generation
    • firefox
    • internet_explorer

Note

The .krb files for these rule bases may be placed anywhere you want them within your Pyke source directory structure -- in other words, your directory structure is not required to match your rule base inheritance structure.

Only one rule base from each rule base category may be active at any point in time.

Within each of these rule bases, if the knowledge base name is omitted from a statement within a backward-chaining rule, it defaults to the rule base category of that rule base, not the rule base itself. Thus, insert(...) within postgresql would mean database.insert(...), and make_tag(...) within firefox would mean html_generation.make_tag(...).

Important

Note that referring to a rule base category (either explicitly or implicitly) always refers to the active rule base within that category. This may not be the rule base with that name (the root rule base), or even the rule base making implicit use of the rule base category. For example, insert(...) within postgresql will end up referring to insert rules within the oracle rule base when oracle is the active rule base.

All rules referring to foreign rule base categories must explicitly use the rule base category name. For example, to refer to the insert rule for databases, within the html_generation category, you would have to say database.insert(...).

In this way, all of the rules will work no matter which rule base is active within each rule base category.

Rule Inheritance

There is an important difference between how backward-chaining rule inheritance works within Pyke rule bases and how method inheritance works within Python classes:

  • When a derived class in Python defines a method by the same name as a method in its parent class, the derived method overrides the parent method. I.e., only the derived method is used when a call is made to it.

  • In contrast, when a derived rule base in Pyke defines backward-chaining rules for a goal that also has backward-chaining rules defined for it in the parent rule base, the derived rule extends the set of rules that may be used to try to prove this goal. All of the derived rules will be tried first. If all of these fail, then the parent rules are tried.

    If you don't want the parent rules to be used for a particular goal, you must list that goal name in the without clause of the extending statement at the beginning of the derived rule base.

Note

All forward-chaining rules in the parent rule base are always included in the derived rule base. The without clause only applies to backward-chaining rules.

Rule Base Activation

Loading rule bases only makes them available for use. It does not automatically activate any of them. This must be done explicitly by your Python program. Your program may decide to activate different rule bases in different situations.

Additionally, forward-chaining rules may be used to activate more specific rule bases, based upon their inferencing. But once a rule base has been activated for a rule base category, only children of the currently active rule base may be activated from that point on. Because these children inherit the rules of the currently active rule base; activating child rule bases only adds new rules, and doesn't take any away. Thus, any forward-chaining rule run during the activation of the first rule base are not invalidated by activating the second rule base.

In our database example, your program could activate the root database rule base and let the forward-chaining rules within the database rule base figure out which derived rule base to activate depending on the particular database in use at the time the program is run.

More:

Fact Bases

Explanation of facts and fact bases.

Rule Bases

Explanation of rule bases, overview of .krb files and how these files are compiled and loaded into your Python program.

Question Bases

Explanation of question bases and .kqb files.

Special

Explanation of the special knowledge base.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/knowledge_bases/fact_bases.html0000644000175000017500000001712211365362362021450 0ustar lambylamby Fact Bases
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Fact Bases

When statements of fact are stored in Pyke, they are stored in fact bases. Pyke allows you to have as many fact bases as you like to help you organize your facts. The fact bases are created automatically, as needed, as new facts are asserted.

Facts

Think of a fact as a simple statement. It has a name and a set of arguments. The arguments may be:

  • strings
    • proper identifiers don't need quotes: Fred is the same as 'Fred'
  • numbers
  • None, True or False
  • tuples of any of these (including nested tuples)
    • singleton tuples don't require a comma: (1) is the same as (1,)

Duplicate facts are not allowed. An attempt to assert a fact that already exists is silently ignored. But note that to be a duplicate, all of the arguments must be the same!

Currently facts are thought to be immutable, meaning that they may not be changed or retracted. That's why dictionaries, lists and user-defined objects are not recommended as arguments.

Case Specific Facts

Most facts are case specific facts. This means that they will be deleted when an engine reset is done to prepare for another run of the inference engine. Case specific facts are asserted through either:

some_engine.assert_(kb_name, fact_name, arguments)
some_engine.add_case_specific_fact(kb_name, fact_name, arguments)

They may also be asserted by forward-chaining rules.

Universal Facts

Universal facts are never deleted (specifically, when a reset is done). You can specify universal facts in a .kfb file, or add universal facts by calling:

some_engine.add_universal_fact(kb_name, fact_name, arguments)

Typically, all universal facts are added once at program startup.

More:

Fact Bases

Explanation of facts and fact bases.

Rule Bases

Explanation of rule bases, overview of .krb files and how these files are compiled and loaded into your Python program.

Question Bases

Explanation of question bases and .kqb files.

Special

Explanation of the special knowledge base.

Page last modified Mon, Oct 27 2008.
./pyke-1.1.1/doc/html/knowledge_bases/question_bases.html0000644000175000017500000002234211365362362022402 0ustar lambylamby Question Bases
 
Please Make a Donation:
Support This Project

Hosted by:
Get Python Knowledge Engine (PyKE) at SourceForge.net. Fast, secure and Free Open Source software downloads

Question Bases

Question bases contain questions for end users. These questions can be of various types (e.g., yes/no, multiple_choice). The questions may be parametrized, with the parameter values substituted into the question text when the question is asked. In this case, different parameter values are treated as different question statements.

The answers to all questions are automatically remembered so that if multiple rules ask the same question, the end user only sees it once. These answers are erased when an engine.reset is done.

Finally, questions may have reviews attached to them to automatically display different canned messages (with parameter substitution) depending on the end user's answer.

KQB files

Each question base is defined by a .kqb file. The name of each question base is the filename of the .kqb file (with the .kqb suffix removed). This must be a legal Python identifier.

These .kqb files are compiled and loaded automatically when you create your knowledge_engine.engine object.

The .kqb file contains all of the information about the question needed to ask the question, validate the answer, and output the appropriate review text.

The .kqb file also specifies which parameter contains the answer. All parameters, except this one, must be bound to values before testing the question.

Example

In writing a program to diagnose car problems, you might have a user_question question base with questions like:

engine_starts($ans)
    Can you start the engine?
    ---
    $ans = yn

mileage($ans)
    How many miles are on the car?
    ---
    $ans = integer(1-999999)
    200000- ! Wow, that's a lot of miles!

noise_from($location, $ans)
    Do you hear a noise from $location?
    ---
    $ans = yn

These would look like the following statements within rules:

user_question.engine_starts(True)
user_question.mileage($mileage)
user_question.noise_from('under the hood', False)

Presenting Questions to Your End Users

Pyke provides two modules to actually present a question to your end user:

  • ask_tty

    This presents the question on stdout and reads stdin for the answer.

  • ask_wx

    This presents the question in a dialog box for use with wxpython.

You may write your own module to present questions to end users. Look at the modules provided by Pyke for guidance.

To ask a question, Pyke looks for an ask_module attribute on:

  1. the question_base object, then
  2. the knowledge_engine.engine object

If the knowledge_engine.engine object does not have an ask_module attribute, ask_tty is imported (by default) and stored there.

Here's an example of setting this attribute:

>>> from pyke import knowledge_engine
>>> from pyke import ask_wx

>>> engine = knowledge_engine.engine(__file__)
>>> engine.ask_module = ask_wx

More:

Fact Bases

Explanation of facts and fact bases.

Rule Bases

Explanation of rule bases, overview of .krb files and how these files are compiled and loaded into your Python program.

Question Bases

Explanation of question bases and .kqb files.

Special

Explanation of the special knowledge base.

Page last modified Fri, Mar 05 2010.
./pyke-1.1.1/doc/todo0000644000175000017500000000024611346504626013257 0ustar lambylamby- remove ':' option after rule names in .krb files. - add ability to use pattern variables for kb_name and fact_name in assertions - add error recovery to krbparser ./pyke-1.1.1/doc/r2w.ini0000644000175000017500000000052211346504626013577 0ustar lambylamby# r2w.ini # these values are all left at the default: pause = False #log_file = 'log.txt' log_file = '' DEBUG = False compare_directory = '' skiperrors = False promote_headers = True # I have edited these values for this site: psyco = False start_directory = 'source' target_directory = 'html' macros = '' [uservalues] version = '0.2' ./pyke-1.1.1/doc/PyKE.mm0000644000175000017500000010266111346504626013536 0ustar lambylamby ./pyke-1.1.1/doc/examples/0000755000175000017500000000000011425360453014177 5ustar lambylamby./pyke-1.1.1/doc/examples/bc_related0.krb0000644000175000017500000000442711346504626017056 0ustar lambylamby# $Id: bc_related0.krb 56035209fc8e 2010-03-08 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. direct_father_son use father_son($father, $son, ()) when family.son_of($son, $father) grand_father_son use father_son($grand_father, $grand_son, (grand)) when father_son($father, $grand_son, ()) father_son($grand_father, $father, ()) great_grand_father_son use father_son($gg_father, $gg_son, (great, $prefix1, *$rest_prefixes)) when father_son($father, $gg_son, ()) father_son($gg_father, $father, ($prefix1, *$rest_prefixes)) brothers use brothers($brother1, $brother2) when father_son($father, $brother1, ()) father_son($father, $brother2, ()) check $brother1 != $brother2 uncle_nephew use uncle_nephew($uncle, $nephew, $prefix) when brothers($uncle, $father) father_son($father, $nephew, $prefix1) $prefix = ('great',) * len($prefix1) cousins use cousins($cousin1, $cousin2, $distance, $removed) when uncle_nephew($uncle, $cousin1, $prefix1) father_son($uncle, $cousin2, $prefix2) $distance = min(len($prefixes1), len($prefixes2)) + 1 $removed = abs(len($prefixes1) - len($prefixes2)) ./pyke-1.1.1/doc/examples/__init__.py0000644000175000017500000000000011346504626016302 0ustar lambylamby./pyke-1.1.1/doc/examples/fc_related.krb0000644000175000017500000000337611346504626017004 0ustar lambylamby# $Id: fc_related.krb 9f7068449a4b 2010-03-08 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. direct_father_son foreach family1.son_of($son, $father) assert family1.father_son($father, $son, ()) grand_father_son foreach family1.father_son($father, $grand_son, ()) family1.father_son($grand_father, $father, ()) assert family1.father_son($grand_father, $grand_son, (grand)) great_grand_father_son foreach family1.father_son($parent, $gg_child, ()) family1.father_son($gg_parent, $parent, ($prefix1, *$rest_prefixes)) assert family1.father_son($gg_parent, $gg_child, (great, $prefix1, *$rest_prefixes)) ./pyke-1.1.1/doc/examples/bc_related.krb0000644000175000017500000000442711346504626016776 0ustar lambylamby# $Id: bc_related.krb 9f7068449a4b 2010-03-08 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. direct_father_son use father_son($father, $son, ()) when family2.son_of($son, $father) grand_father_son use father_son($grand_father, $grand_son, (grand)) when father_son($father, $grand_son, ()) father_son($grand_father, $father, ()) great_grand_father_son use father_son($gg_father, $gg_son, (great, $prefix1, *$rest_prefixes)) when father_son($father, $gg_son, ()) father_son($gg_father, $father, ($prefix1, *$rest_prefixes)) brothers use brothers($brother1, $brother2) when father_son($father, $brother1, ()) father_son($father, $brother2, ()) check $brother1 != $brother2 uncle_nephew use uncle_nephew($uncle, $nephew, $prefix) when brothers($uncle, $father) father_son($father, $nephew, $prefix1) $prefix = ('great',) * len($prefix1) cousins use cousins($cousin1, $cousin2, $distance, $removed) when uncle_nephew($uncle, $cousin1, $prefix1) father_son($uncle, $cousin2, $prefix2) $distance = min(len($prefixes1), len($prefixes2)) + 1 $removed = abs(len($prefixes1) - len($prefixes2)) ./pyke-1.1.1/doc/examples/user_questions.kqb0000644000175000017500000000726411346504626020003 0ustar lambylamby# $Id: user_questions.kqb e37ceeb677f2 2010-03-03 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ate($meal, $ans) Did you eat $meal? --- $ans = yn hours_since_last_meal($ans) How many hours has it been since you last ate? --- $ans = integer(0-48) miles_to($dest, $ans) How many miles did you travel to get to $dest? --- $ans = number(0.1-3000) price($object, $price) What did you pay for $object? --- $price = float users_name($name) What's your name? - Please don't enter a fictitious (screen) name. --- $name = string(2-40) state_code($state) Enter your two digit state code. --- $state = string('uppercase'[uppercase]/[A-Z][A-Z]/) age($years) How old are you? --- $years = integer(1-130) age_category($period_of_life) How old are you? --- $period_of_life = integer(child=1-12 | teenager=13-19 | young_adult=20-35 | middle_age=35-64 | elder=65-130) stupid_question($ans) Can you answer a question that is several lines long? --- $ans = yn True ! Correct! This is true because the sky is blue! False ! Nope! Remember that the sky is blue! wood($ans) How much wood would a woodchuck chuck if a woodchuck could chuck wood? --- $ans = integer(0-100) -10 ! more than that! 10-20 ! bingo! 21- ! I guess they're not as strong as you think ... another_question($arg1, $arg2, $ans) question text with $arg1 stuff in it. on multiple lines - possibly indented - for who knows what reason... - maybe for $arg2? --- $ans = select_1 1: prompt for this selection with $arg2 in it too which can span multiple lines - and be indented ... ! Nope! Remember that the sky is blue! 2: next prompt ! =1 # same review as 1: 3: pick me! pick me!!! ! Correct! You certainly know about $arg1! yep, multiple review lines too... - and indented... problems($list) Which of these problems are you experiencing? - select all that apply --- $list = select_n boot: The system won't boot. os: I hate Windows! internet: I can't connect to the internet. slow: The system is running too slow. ouch: Help! I've fallen and I can't get up! freeze: The system freezes or does not respond to input. printer: The printer doesn't work. senile: What's email?./pyke-1.1.1/doc/examples/family1.kfb0000644000175000017500000000233711346504626016236 0ustar lambylamby# $Id: family1.kfb 9f7068449a4b 2010-03-08 mtnyogi $ # # Copyright © 2010 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. son_of(david, bruce) son_of(bruce, thomas) son_of(thomas, frederik) son_of(frederik, hiram) ./pyke-1.1.1/doc/examples/some_module.py0000644000175000017500000000000011346504626017053 0ustar lambylamby./pyke-1.1.1/doc/examples/error_test.krb0000644000175000017500000000251411346504626017075 0ustar lambylamby# $Id: error_test.krb 7a5f9c2f420f 2008-07-26 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. rule1 use goal() when goal2() rule2 use goal2() when goal3() rule3 use goal3() when goal4() rule4 use goal4() when check $bar > 0 ./pyke-1.1.1/doc/examples/plan_example.krb0000644000175000017500000000464511346504626017361 0ustar lambylamby# $Id: plan_example.krb 2bb500de1268 2008-09-24 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. transfer1 use transfer($from_acct, $to_acct) taking (amount) when withdraw($from_acct) $$(amount) deposit($to_acct) $$(amount) transfer2 use transfer($from_acct, $to_acct) taking (amount) when transfer_ach($from_acct, $to_acct) $$(amount) withdraw use withdraw(($who, $acct_type)) taking (amount) with print "withdraw", amount, "from", $who, $acct_type deposit use deposit(($who, $acct_type)) taking (amount) with print "deposit", amount, "to", $who, $acct_type transfer_ach1 use transfer_ach($from_acct, ($bank, $who, $acct_type)) taking (amount) when withdraw($from_acct) $$(amount) deposit((central_accts, ach_send_acct)) $$(amount) with print "send", amount, "to bank", $bank, "acct", $who, $acct_type transfer_ach2 use transfer_ach($from_acct, $to_acct) taking (amount) when get_ach($from_acct) $$(amount) withdraw((central_accts, ach_recv_acct)) $$(amount) deposit($to_acct) $$(amount) get_ach use get_ach(($bank, $who, $acct_type)) taking (amount) with print "get", amount, "from bank", $bank, "acct", $who, $acct_type ./pyke-1.1.1/doc/examples/family2.kfb0000644000175000017500000000232711346504626016236 0ustar lambylamby# $Id: family2.kfb 9f7068449a4b 2010-03-08 mtnyogi $ # # Copyright © 2010 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. son_of(tim, thomas) son_of(fred, thomas) son_of(bruce, thomas) son_of(david, bruce) ./pyke-1.1.1/make_release0000755000175000017500000001657611346504626014202 0ustar lambylamby#!/bin/bash # make_release release_number usage() { echo "usage: make_release release_number" >&2 exit 2 } [ $# -eq 1 ] || usage set -e RELEASE_1=release_1 PYKE=pyke PRE_2TO3_R1=pre_2to3_r1 PRE_2TO3=pre_2to3 TMPFILE=/tmp/make_release.$$ release_number="$1" start_repo=`pwd` # conditional commit -- only if "hg status" reports changes! # all args passed to "hg commit" do_commit() { hg status > $TMPFILE if [ -s $TMPFILE ] then #echo hg commit "$@" hg commit "$@" fi rm -f $TMPFILE } # Raise error if "hg status" reports any changes! # no args check_status() { hg status > $TMPFILE if [ -s $TMPFILE ] then echo "ERROR: Uncommitted files:" >&2 cat $TMPFILE >&2 rm -f $TMPFILE return 1 fi rm -f $TMPFILE } # Raise error if "hg heads" reports multiple heads! # optional -q arg to nix the error message to stderr check_heads() { hg heads --template '{desc|firstline}\n' > $TMPFILE if [ `wc -l $TMPFILE | cut -f1 '-d '` -gt 1 ] then if [ x"$1" != x-q ] then echo "ERROR: Multiple Heads:" >&2 cat $TMPFILE >&2 fi rm -f $TMPFILE return 1 fi rm -f $TMPFILE } # Run "hg fetch" and return error if no merge/commit done # 1 arg, passed to "hg pull -u" do_fetch() { hg pull -u "$1" if check_heads -q then return 1 else hg merge check_heads hg commit -m "Automated merge with $1" fi } echo echo "*********************************************************************" echo " Deleting .pyc files" echo "*********************************************************************" echo find . -name '*.pyc' -exec rm {} + echo echo "*********************************************************************" echo " Rebuilding compiler_bc.py" echo "*********************************************************************" echo dir=pyke/krb_compiler/compiled_krb if [ ! -d "$dir" ] then mkdir "$dir" fi python <=0.5"], # 'examples': ["HTMLTemplate>=1.5"], # 'tests': ["doctest-tools>=1.0a3"], #}, #zip_safe = True, # Metadata for upload to PyPI author = "Bruce Frederiksen", author_email = "dangyogi@gmail.com", description = "Python Knowledge Engine and Automatic Python Program Generator", license = "MIT License", keywords = "expert system forward backward chaining backtracking plans", url = "http://sourceforge.net/projects/pyke", long_description = """ Both forward-chaining and backward-chaining rules (which may include python code) are compiled into python. Can also automatically assemble python programs out of python functions which are attached to backward-chaining rules. """, download_url = "http://downloads.sourceforge.net/pyke/pyke-1.1.1.zip", classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Software Development :: Code Generators", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: Log Analysis", ], ) ./pyke-1.1.1/RELEASE_NOTES-1.txt0000644000175000017500000002373511365361620014555 0ustar lambylamby1.1.1 RELEASE NOTES Apr 26, 2010 ================================ This release has several bug fixes, and implements one small feature request. INCOMPATIBILITIES WITH 1.1 RELEASE: - The run.py modules in the examples were renamed to driver.py so as not to conflict with the "run" module in IDLE. FEATURE ENHANCEMENTS: - Feature Request #2991566: Automatically create fact bases mentioned in fc rules BUGS FIXED: The following bugs have been fixed: - Bug #2991564: IndentationError: unindent does not match any outer indentat - this fixed an obscure bug in forward-chaining rules with a forall clause that has a require clause ending with a python premise. - Bug #2991558: AttributeError: 'module' object has no attribute 'flags' - this fixed a problem running Pyke on Python 2.5 - Bug #2990998: "run" module also appears in IDLE tool. - it's not clear whether this fixed any problems or not w.r.t. IDLE, but just in case... - Also note that a bug has been reported against IDLE which prevents using the __file__ idiom with the Run -> Run Module (F5) command from the IDLE editor window. See http://bugs.python.org/issue8515 for the status of this bug and for a patch that was submitted. - Bug #2990993: Pyke goes into infinite loop on Windows - this fixed a problem running Pyke on Windows 1.1 RELEASE NOTES Mar 11, 2010 ============================== This release has a whole new interface from your Python program into Pyke. This was a result of attending the mython sprint at PyCon 2010, and thanks go to Jon Riehl of the mython project (see mython.org) for giving me the idea for this. The two big changes are: 1. Redid the engine constructor to make it easier to specify where your Pyke source files are. Now you can generally just do knowledge_engine.engine(__file__) without worrying about your PYTHONPATH. 2. Redid the engine.prove methods to take a goal statement as a string in Pyke syntax. This lets you use fully general patterns without having to create the Pyke pattern objects or create Pyke contexts. The Using Pyke section of the documentation has been completely re-written. Nothing in the .kfb, .krb or .kqb files has changed. INCOMPATIBILITIES WITH 1.0.4 RELEASE: - knowledge_engine.engine constructor path argument has changed. See Using Pyke => Creating an Engine web page. - engine.prove_1, engine.prove_n and engine.prove have been deprecated in favor of new engine.prove_goal and engine.prove_1_goal methods. See the Using Pyke => Proving Goals page. - You now need doctest-tools version 1.0a3 to run the unit tests. - Renamed all example test.py files to run.py (to not interfere with Python's test package). FEATURE ENHANCEMENTS: - Added new engine.prove_goal and engine.prove_1_goal to replace the engine.prove_1, engine.prove_n and engine.prove methods, which are now deprecated. - Also can compile goal statements at module load time with new goal.compile (much like re.compile). - Redid how paths to Pyke source files are specified in engine constructor. Now you can generally just call knowledge_engine.engine(__file__) and not have to worry about what's on your PYTHONPATH. - Moved example .tst files into Test/examples so that they are not cluttering up the examples directories. - Added blank lines between methods, deleted doctest calls (now using doctest-tools instead). - Rewrote the Using Pyke section of the documentation. BUGS FIXED: The following bugs have been fixed: - Fixed bug #2913426: KFB Breaks Without Trailing Newline This was also a problem with KRB files. - Fixed bug #2968963: installation creates extraneous pyke dir. 1.0.4 RELEASE NOTES Nov 4, 2009 =============================== This release has one bug fix in it. The big new things are: 1. The release packaging and installation have been redone. There is now only one source package. This includes the HTML documentation and examples. Use the standard "python setup.py install" to install it. 2. There is now a separate Pyke source zip file for Python 3. Please report bugs to http://sourceforge.net/projects/pyke to: Support => Bugs (under Project Tracker) INCOMPATIBILITIES WITH 1.0.3 RELEASE: - none. But Pyke will want to recompile your knowledge base (.krb, .kfb, .kqb) files when you install this release (as usual). This release introduces a new compiler_version to eliminate doing this when nothing in the compile process has changed in the release. FEATURE ENHANCEMENTS: - Changed the release files from tarball to zip format to make it easier for Windows users. - Eliminated the separate tarballs for the HTML documentation and examples. These are now included in the source zip file. - Eliminated the dependency on easy_install (setuptools) for installation. You can still use easy_install, but I no longer release separate egg files. Use the source zip file instead. - Added a separate source zip file for the version of Pyke that runs on Python 3. - Changed the unit test harness to use doctest-tools (see http://code.google.com/p/doctest-tools/). Now the top-level test script is called "testpyke" (was "testall"). - Combined RELEASE_NOTES for separate releases into one file. (Was separate files for each release). - Added a .txt suffix to all README files. - Renamed "copyright_license" to "LICENSE". - Added "About Pyke" -> "Modifying Pyke" page to HTML documentation. - Added a separate compiler_version to avoid unnecessarily recompiling the knowledge bases (.krb, .kfb, .kqb files) when a new Pyke release is installed with no compiler changes in that release. BUGS FIXED: The following bugs have been fixed: - Fixed bug #2881969: "copyright_license" missing from 1.0.3 tarball 1.0.3 RELEASE NOTES Oct 19, 2009 ================================ This release has two bug fixes in it. The big thing is that the source code repository has been moved from Subversion to Mercurial. This is the first release under Mercurial. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 1.0.2 RELEASE: - The source code repository has been moved from subversion to mercurial to make it easier for people to contribute to the project. If you have the source code checked out under subversion, and want to stay abreast of developments or contribute your changes back to the project, you will need to switch to Mercurial. FEATURE ENHANCEMENTS: - Moved the source code repository from Subversion to Mercurial! - Did some enhancements to examples/web_framework/web_framework.tst to better report server errors. - Added examples/web_framework/director.html as another example html template. - Upgraded to 3.3 version of PLY. BUGS FIXED: The following bugs have been fixed: - Fixed bug #2881934: py2exe doesn't work with Pyke - Applied patch #2787699: hash function returns void 1.0.2 RELEASE NOTES Apr 21, 2009 ================================ This fixes a few more bugs in Pyke and has several very minor enhancements. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 1.0.1 RELEASE: FEATURE ENHANCEMENTS: - Added zip_safe to setup.py so that the Pyke egg isn't unzipped when it is installed. - Added mention in the using_pyke.txt document that Pyke supports source package directories in zipped egg files. - Upgraded to 3.2 version of PLY. - Upgraded to 0.6c9 version ez_setup.py. BUGS FIXED: The following bugs have been fixed: - Fixed bug 2729315: IndexError: list index out of range. - Fixed an error in the logic_programming/rules/backward_chaining.txt documentation file (reported by Carlo, aka Mr.SpOOn). 1.0.1 RELEASE NOTES Apr 2, 2009 ================================ This fixes a few bugs in Pyke and cleans up the error reporting in the web_framework test script. Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 1.0 RELEASE: FEATURE ENHANCEMENTS: - Changed the test script for the web_framework example to better report errors from the server process. - Misc minor code changes for Python3.0 support. BUGS FIXED: The following bugs have been fixed: - Fixed bug 2709888: Strange behavior with two iterable. - Fixed an error in the examples/web_framework/README file. - Fixed testdocs bug which required wxPython to be installed to run the test (even though the test doesn't use wxPython). 1.0 RELEASE NOTES Mar 18, 2009 ================================ This fixes two bugs in Pyke and is Pyke's first full release version! Please report bugs to http://sourceforge.net/projects/pyke at either: forum => help or tracker => bugs INCOMPATIBILITIES WITH 0.7 RELEASE: FEATURE ENHANCEMENTS: Added the following features: - Changed the .krb parser to iterate, rather than recurse, on the list of rules in the file so that it doesn't hit the recursion limit on large .krb files. - Now including a copy of PLY with the Pyke sources so that I can release the compiled pyke/krb_compiler/*_tables.py files with Pyke and not have PLY try to recompile them (which generally gets a permission violation because normal users can't write the Python's site-packages directory). - Added the option to pass any module within the package to identify the source package to knowledge_engine.engine(). BUGS FIXED: The following bugs have been fixed: - Fixed bug 2569021: string literals don't work in .kfb files. - Added mention of 'engine' variable within the rules documentation to fix bug 2588191. ./pyke-1.1.1/examples/0000755000175000017500000000000011425360453013432 5ustar lambylamby./pyke-1.1.1/examples/learn_pyke/0000755000175000017500000000000011425360453015563 5ustar lambylamby./pyke-1.1.1/examples/learn_pyke/pattern_matching.krb0000644000175000017500000001463311346504626021625 0ustar lambylamby# pattern_matching.krb #knows_pattern_matching_already # use knows_pattern_matching() # when # prior_knowledge() # python # print "OK, here's some pointers specific to pyke:" # print # print "In pyke, pattern variables are preceded by a dollar sign " \ # "('$'). e.g.: $foo" # print "Pattern variables starting with an underscore are " \ # "anonymous. e.g.: $_foo" # print "Identifiers without a dollar sign are treated like string " \ # "literals.\n" \ # " This just saves having to type the quotes." # print "Singleton tuples do not require a comma." # print "A tuple pattern ending in '*$foo' matches $foo to the " \ # "rest of the tuple." # knows_pat_master() # #prior_knowledge1 # use prior_knowledge() # when # questions.knows_prolog(True) # #prior_knowledge2 # use prior_knowledge() # when # questions.knows_ai(True) # #knows_pattern_matching # use knows_pattern_matching() # when # notany # prior_knowledge() # python engine.get_ke('questions', 'pat_master').reset() # knows_pat_master() knows_pattern_matching use knows_pattern_matching() when python engine.get_ke('questions', 'pat_master').reset() knows_pat_master() knows_pattern_matching_2 use knows_pattern_matching() when learned_pattern_matching() knows_pat_master use knows_pat_master() when questions.pat_master(13) learned_pattern_matching use learned_pattern_matching() when taught_pattern_matching() knows_pattern_matching() taught_pattern_matching_1_2_4_16 use taught_pattern_matching() when questions.pat_master($ans) check $ans in (1, 2, 4, 16) knows_pattern_variable_syntax() knows_patterns() knows_how_patterns_match() knows_pattern_variable_scope() taught_pattern_matching_3_5_6_9_15 use taught_pattern_matching() when questions.pat_master($ans) check $ans in (3, 5, 6, 9, 15) knows_tuple_patterns() taught_pattern_matching_7_8_10 use taught_pattern_matching() when questions.pat_master($ans) check $ans in (7, 8, 10) knows_rest_variable() taught_pattern_matching_11_12 use taught_pattern_matching() when questions.pat_master($ans) check $ans in (11, 12) knows_pattern_variable_scope() taught_pattern_matching_14 use taught_pattern_matching() when questions.pat_master($ans) check $ans in (14,) knows_how_patterns_match() knows_pattern_variable_scope() knows_pattern_variable_syntax use knows_pattern_variable_syntax() when questions.pat_var_syntax($ans) #reset_if_wrong(pat_var_syntax, 2, $ans) reset_if_wrong__right use reset_if_wrong($_, $right_ans, $right_ans) when special.claim_goal() reset_if_wrong__wrong use reset_if_wrong($question, $_, $_) when python engine.get_ke('questions', $question).reset() knows_patterns use knows_patterns() when knows_literal_patterns() knows_pattern_variables() knows_tuple_patterns() knows_literal_patterns use knows_literal_patterns() when ask_until_correct(pat_literal, 6) ask_until_correct__ask use ask_until_correct($question, $right_ans) when questions.$question($right_ans) special.claim_goal() ask_until_correct__try_again use ask_until_correct($question, $right_ans) when python engine.get_ke('questions', $question).reset() python print "Try again:" ask_until_correct($question, $right_ans) knows_pattern_variables use knows_pattern_variables() when knows_pattern_variable_syntax() knows_pattern_variable_scope() knows_pattern_variable_scope use knows_pattern_variable_scope() when ask_until_correct(multiple_matching, 4) questions.anonymous_syntax($_) ask_until_correct(anonymous_matching, 3) knows_how_patterns_match use knows_how_patterns_match() when questions.pattern_scope($ans) post_process_pattern_scope($ans) ask_until_correct(anonymous_matching, 3) post_process_pattern_scope_1 use post_process_pattern_scope(1) when knows_literal_patterns() knows_pattern_variable_syntax() python engine.get_ke('questions', 'pattern_scope').reset() knows_how_patterns_match() post_process_pattern_scope_2 use post_process_pattern_scope(2) when knows_pattern_variable_scope() python engine.get_ke('questions', 'pattern_scope').reset() knows_how_patterns_match() post_process_pattern_scope_3 use post_process_pattern_scope(3) post_process_pattern_scope_4 use post_process_pattern_scope(4) when knows_pattern_variable_scope() questions.same_var_different_rules($_) python engine.get_ke('questions', 'pattern_scope').reset() knows_how_patterns_match() post_process_pattern_scope_5 use post_process_pattern_scope(5) when questions.same_var_different_rules($_) python engine.get_ke('questions', 'pattern_scope').reset() knows_how_patterns_match() post_process_pattern_scope_6 use post_process_pattern_scope(6) when knows_patterns() knows_pattern_variable_scope() python engine.get_ke('questions', 'pattern_scope').reset() knows_how_patterns_match() knows_rest_variable use knows_rest_variable() when questions.rest_pattern_variable_syntax($_) knows_rest_match() knows_rest_match use knows_rest_match() when questions.rest_match($ans) post_process_rest_match($ans) post_process_rest_match_1 use post_process_rest_match(1) when python engine.get_ke('questions', 'rest_match').reset() knows_rest_match() post_process_rest_match_4_5 use post_process_rest_match($ans) when check $ans in (4, 5) special.claim_goal() python engine.get_ke('questions', 'rest_match').reset() knows_rest_variable() post_process_rest_match_correct use post_process_rest_match($ans) when check $ans in (2, 3) knows_tuple_patterns use knows_tuple_patterns() when questions.tuple_pattern_syntax($_) knows_rest_variable() ./pyke-1.1.1/examples/learn_pyke/README.txt0000644000175000017500000000054111354261606017262 0ustar lambylambyThis is an experimental Computer Based Training (CBT) program to teach the user Pyke. (Currently, it only teaches patterns and pattern matching). This uses question bases. Even though the example is rather rough and incomplete it is left here as an example of question bases... To run this: $ python >>> import driver >>> driver.run() ./pyke-1.1.1/examples/learn_pyke/questions.kqb0000644000175000017500000002470011346504626020323 0ustar lambylamby# questions.kqb knows_prolog($ans) Do you have some familiarity with the programming language prolog? --- $ans = yn knows_ai($ans) Do you have some familiarity with artificial intelligence or expert systems? --- $ans = yn generic_yn($question, $ans) $question? --- $ans = yn pat_master($ans) Assume that the following two patterns are contained in different rules and that none of the pattern variables are initially bound to values: pattern 1: ((ho, $$_, ($$a, $$a)), ($$a, $$a, $$b), ($$a, *$$b)) pattern 2: ($$a, $$a, $$x) If the two patterns are matched together, what will $$x be bound to? --- $ans = select_1 1: (a, b) ! Incorrect: Neither of the symbols 'a' nor 'b' appear in either pattern. 2: $$a ! Incorrect: Pattern variable '$$a' is bound to a value. 3: ho ! Incorrect: $$x is bound to a tuple. 4: ($$a, *$$b) ! Incorrect: Both pattern variables '$$a' and '$$b' are bound to values. 5: (ho, *$$b) ! Incorrect: Pattern variable '$$b' is bound to a value. 6: (ho, *($$a, $$a)) ! Incorrect: The '*' is part of the pattern syntax and is not seen in the bound value. 7: (ho, ($$a, $$a)) !=2 8: (ho, $$a, $$a) !=2 9: (ho, *(ho, ho)) !=6 10: (ho, (ho, ho)) ! Incorrect: The '*' in '*$$b' means "the rest of the tuple" is '$$b'. 11: (ho, $$_, (ho, ho)) ! Incorrect: The '$$a' in pattern 1 is a different pattern variable than the '$$a' in pattern 2. 12: (ho, ho, (ho, ho)) !=11 13: (ho, ho, ho) ! Correct! matching Pattern 1: (ho, $$_, ($$a, $$a)) to Pattern 2: $$a binds Pattern 2: $$a to Pattern 1: (ho, $$_, (ho, ho)) matching Pattern 1: ($$a, $$a, $$b) to Pattern 2: $$a, which is bound to Pattern 1: (ho, $$_, ($$a, $$a)) binds Pattern 1: $$a to ho, and Pattern 1: $$b to Pattern 1: ($$a, $$a) which expands to (ho, ho) matching Pattern 1: ($$a, *$$b) to Pattern 2: $$x binds Pattern 2: $$x to Pattern 1: ($$a, *$$b) which expands to (ho, ho, ho) 14: nothing, the two patterns don't match ! Incorrect: The patterns do match! 15: nothing, pattern 1 is not a legal pattern ! Incorrect: Pattern 1 is a legal pattern. 16: I don't have a clue... pat_var_syntax($ans) A pattern variable matches any value (including other pattern variables). What is the syntax for a pattern variable? --- $ans = select_1 1: any legal identifier not within quotes is a pattern variable ! Incorrect: A legal identifier not within quotes is treated the same as if it were in quotes. This just saves you the trouble of typing the quotes. A pattern variable is any identifier preceded by a '$$'. 2: a '$$' in front of any legal identifier ! Correct: Pattern variables are preceded by a '$$'. 3: a '*' in front of any legal identifier ! Incorrect: Pattern variables must be preceded by a '$$'. pat_literal($ans) Pattern literals are patterns that only match a single constant value. Which of these is NOT a pattern literal? --- $ans = select_1 1: None ! Incorrect: Simple python values, like None, are pattern literals. 2: 33 ! Incorrect: Simple python values, like 33 and 3.14159, are pattern literals. 3: True ! Incorrect: Simple python values, like True and False, are pattern literals. 4: "hi mom!" ! Incorrect: Simple python values, like strings, are pattern literals. 5: fred ! Incorrect: Legal identifiers are strings that don't require quotes and are pattern literals just like quoted strings are. 6: $$fred ! Correct: Simple python values are pattern literals that only match themselves. multiple_matching($ans) What value matches the pattern: ($$a, $$a)? --- $ans = select_1 1: 44 ! Incorrect: A tuple pattern only matches another tuple. 2: a !=1 3: (a, b) ! Incorrect: Both '$$a' pattern variables must match the same value. 4: (b, b) ! Correct: Both '$$a' pattern variables must always match the same value, in this case: b 5: (a, a, b) ! Incorrect: A tuple pattern only matches another tuple of the same length. anonymous_syntax($ans) Anonymous pattern variables act like "don't care" patterns. What is the syntax for an anonymous pattern variable? --- $ans = select_1 1: $$anonymous ! Incorrect: An anonymous pattern variable is any pattern variable whose name starts with an underscore ('_'). 2: _anonymous !=1 3: Using a pattern variable name that starts with an underscore ('_'), like: $$_anonymous. ! Correct! rest_pattern_variable_syntax($ans) "Rest" pattern variables are used at the end of a tuple pattern to match the rest of the tuple. What is the syntax for a "rest" pattern variable? --- $ans = select_1 1: $$rest ! Incorrect: A "rest" pattern variable is any pattern variable preceded by an asterisk ('*'). 2: _rest !=1 3: Preceding a pattern variable with an asterisk ('*'), like: *$$foo. ! Correct! tuple_pattern_syntax($ans) What is the syntax for a tuple pattern? --- $ans = select_1 1: A series of patterns enclosed in "tuple(" ... ")". ! Incorrect: a tuple pattern is a series of patterns enclosed in parenthesis. 2: A series of patterns enclosed in parenthesis. ! Correct! anonymous_matching($ans) What value matches the pattern: ($$_a, $$_a)? --- $ans = select_1 1: 44 ! Incorrect: a tuple pattern only matches another tuple. 2: a !=1 3: (a, b) ! Correct: pattern variable names that begin with an '_' are anonymous and are not constrained to match the same value. They serve as a "don't care" pattern and their name just documents that value's function. 4: (a, a, b) ! Incorrect: a tuple pattern only matches another tuple of the same length. 5: none of the above ! Incorrect: pattern variable names that begin with an '_' are anonymous and are not constrained to match the same value. They serve as a "don't care" pattern and their name just documents that value's function. pattern_scope($ans) For each answer, assume that the two patterns are contained in different rules. Which set of patterns match each other? --- $ans = select_1 1: Pattern 1: a Pattern 2: 44 ! Incorrect: an identifier is treated as a string, and "a" does not match 44. 2: Pattern 1: ($$a, $$a, 3) Pattern 2: (1, 2, 3) ! Incorrect: both '$$a' pattern variables in pattern 1 must match the same value. 3: Pattern 1: ($$a, $$a, 3) Pattern 2: (1, 1, $$a) ! Correct: All of the pattern variables with the same name within the same rule must always match the same value. But pattern variables with the same name within different rules may match different values. Thus all of the $$a pattern variables in Pattern 1 must match the same value, but this may be a different value than the $$a in Pattern 2. 4: Pattern 1: ($$a, $$a, 3) Pattern 2: (1, $$b, $$b) ! Incorrect: All pattern variables with the same name in the same rule must match the same value. Here's the sequence of how these patterns are matched: 1. The first $$a in Pattern 1 is matched to the 1 in Pattern 2. This sets the $$a in Pattern 1 to 1. 2. The second $$a in Pattern 1 is matched to the first $$b in Pattern 2. This sets $$b in Pattern 2 to 1, since that is what $$a in Pattern 1 is set to. 3. The 3 in Pattern 1 is matched to the second $$b in Pattern 2. This tries to match 3 to 1, since $$b in Pattern 2 is 1. This is where the match fails! 5: None of these match. ! Incorrect: One set of patterns do match! 6: I don't have a clue. rest_match($ans) After matching the following two patterns, what is $$c set to? pattern 1: ($$a, $$b, *$$c) pattern 2: (1, 2, 3) --- $ans = select_1 1: 3 ! Incorrect: a "rest" pattern variable is always set to a tuple. 2: (3) ! Correct! (Note that a comma is not required for singleton tuples in PyKE). 3: (3,) ! Correct, but answer 2 is slightly better because a comma is not required for singleton tuples in pyke. 4: nothing, the two patterns don't match ! Incorrect: these two patterns do match! 5: nothing, pattern 1 is not a legal pattern ! Incorrect: pattern 1 is a legal pattern. same_var_different_rules($ans) Assume that the following two patterns are in different rules: Pattern 1: ($$a, 2) Pattern 2: (1, $$a) Do these patterns match? --- $ans = yn True ! Correct! The $$a in Pattern 1 is in a different rule than the $$a in Pattern 2, so they are not required to match the same value. False! Incorrect: The $$a in Pattern 1 is in a different rule than the $$a in Pattern 2, so they are not required to match the same value. #$$x matching $$y means that both $$x and $$y have to mean the same thing from then # on #pattern matching to select what to do #pattern matching to generate values ./pyke-1.1.1/examples/learn_pyke/driver.py0000644000175000017500000000054311354262256017435 0ustar lambylamby# driver.py import sys from pyke import knowledge_engine, krb_traceback engine = knowledge_engine.engine(__file__) def run(): engine.reset() try: engine.activate('pattern_matching') engine.prove_1_goal('pattern_matching.knows_pattern_matching()') except StandardError: krb_traceback.print_exc() sys.exit(1) ./pyke-1.1.1/examples/learn_pyke/testall.config0000644000175000017500000000002311346504626020421 0ustar lambylambyexclude README.txt ./pyke-1.1.1/examples/knapsack/0000755000175000017500000000000011425360453015225 5ustar lambylamby./pyke-1.1.1/examples/knapsack/README.txt0000644000175000017500000000213311354261576016731 0ustar lambylambyThis is the knapsack example taken from: http://www.ise.gmu.edu/~duminda/classes/fall03/set3.ppt starting on page 19 knapsack.krb This uses backward-chaining to solve the knapsack problem (see link above). The top-level goal is: legal_knapsack($Pantry, $Capacity, $Knapsack) $Pantry is a tuple of food items. Each food item is: (name, weight, calories). $Capacity is the maximum weight capacity of the knapsack. $Knapsack is a subset of $Pantry whose total weight is <= $Capacity. driver.py run(pantry, capacity) Uses the legal_knapsack goal to enumerate all of the possible knapsacks within the stated capacity. Returns the total_calories and knapsack of the answer with most calories. The final example on page 36 at the site above would be: >>> import driver >>> driver.run((('bread',4,9200), ... ('pasta',2,4500), ... ('peanutButter',1,6700), ... ('babyFood',3,6900)), ... 4) (13600, (('peanutButter', 1, 6700), ('babyFood', 3, 6900))) ./pyke-1.1.1/examples/knapsack/driver.py0000644000175000017500000000141211354262246017072 0ustar lambylamby#!/usr/bin/python # driver.py from __future__ import with_statement from pyke import knowledge_engine from pyke import krb_traceback def run(pantry, capacity): engine = knowledge_engine.engine(__file__) engine.activate('knapsack') max = 0 ans = None with engine.prove_goal( 'knapsack.legal_knapsack($pantry, $capacity, $knapsack)', pantry=pantry, capacity=capacity) \ as gen: for vars, no_plan in gen: knapsack = vars['knapsack'] calories = sum(map(lambda x: x[2], knapsack)) if calories > max: max = calories ans = knapsack return max, ans if __name__ == "__main__": import sys print run(eval(sys.argv[1]), int(sys.argv[2])) ./pyke-1.1.1/examples/knapsack/knapsack.krb0000644000175000017500000000171111346504626017524 0ustar lambylamby# knapsack.krb # # Taken from http://www.ise.gmu.edu/~duminda/classes/fall03/set3.ppt # pages 19-36. The final looping on all answers to find the best one is done # in python (in test.py). subseq_done use subseq((), ()) subseq_1 use subseq(($Item, *$RestX), ($Item, *$RestY)) when subseq($RestX, $RestY) subseq_2 use subseq($X, ($_, *$RestY)) when subseq($X, $RestY) # This was an intermediate rule that's not used in the final solution. knapsack_decision use knapsack_decision($Pantry, $Capacity, $Goal, $Knapsack) when subseq($Knapsack, $Pantry) check sum(map(lambda x: x[1], $Knapsack)) <= $Capacity check sum(map(lambda x: x[2], $Knapsack)) >= $Goal # We can maximize this in the calling python code. See test.py legal_knapsack use legal_knapsack($Pantry, $Capacity, $Knapsack) when subseq($Knapsack, $Pantry) check sum(map(lambda x: x[1], $Knapsack)) <= $Capacity ./pyke-1.1.1/examples/forall/0000755000175000017500000000000011425360453014711 5ustar lambylamby./pyke-1.1.1/examples/forall/family.kfb0000644000175000017500000000041211346504626016657 0ustar lambylamby# family.kfb child_of(arthur2, arthur1, bertha_o) child_of(helen, arthur1, bertha_o) child_of(roberta, arthur1, bertha_o) child_of(gladis, john, bertha_c) child_of(sarah_r, john, bertha_c) child_of(alice, marshall1, bertha_c) child_of(edmond, marshall1, bertha_c) ./pyke-1.1.1/examples/forall/bc_forall.krb0000644000175000017500000000054311346504626017342 0ustar lambylamby# bc_forall.krb test1 use no_step_siblings($child) when family.child_of($child, $father, $mother) forall family.child_of($_, $father, $mother2) require check $mother == $mother2 forall family.child_of($_, $father2, $mother) require check $father == $father2 ./pyke-1.1.1/examples/forall/README.txt0000644000175000017500000000131211354261570016405 0ustar lambylambyThis is a small example of the 'forall'/'require' clause used to verify that all of the elements a list meet some requirement. This example is done in both forward-chaining and backward-chaining rules. The forward-chaining and backward-chaining rules are in two different .krb files showing examples of use of the 'forall'/'require' clause in both cases. These rules find all people who have no step brothers or sisters. >>> import driver # uses fc_forall.krb >>> driver.fc_test() arthur2 has no step brothers or sisters helen has no step brothers or sisters roberta has no step brothers or sisters # uses bc_forall.krb >>> driver.bc_test() arthur2 helen roberta ./pyke-1.1.1/examples/forall/fc_forall.krb0000644000175000017500000000061711346504626017350 0ustar lambylamby# fc_forall.krb test1 foreach family.child_of($child, $father, $mother) forall family.child_of($_, $father, $mother2) require check $mother == $mother2 forall family.child_of($_, $father2, $mother) require check $father == $father2 assert python print $child, "has no step brothers or sisters" ./pyke-1.1.1/examples/forall/driver.py0000644000175000017500000000114611354262234016557 0ustar lambylamby# driver.py from __future__ import with_statement import sys from pyke import knowledge_engine from pyke import krb_traceback engine = knowledge_engine.engine(__file__) def fc_test(): engine.reset() try: engine.activate('fc_forall') except: krb_traceback.print_exc() sys.exit(1) def bc_test(): engine.reset() try: engine.activate('bc_forall') with engine.prove_goal('bc_forall.no_step_siblings($child)') as gen: for vars, plan in gen: print vars['child'] except: krb_traceback.print_exc() sys.exit(1) ./pyke-1.1.1/examples/web_framework/0000755000175000017500000000000011425360453016264 5ustar lambylamby./pyke-1.1.1/examples/web_framework/TurboGears2/0000755000175000017500000000000011425360453020423 5ustar lambylamby./pyke-1.1.1/examples/web_framework/TurboGears2/siege.urls.tg0000644000175000017500000000062111346504626023042 0ustar lambylamby# These are all of the legal urls: http://localhost:8080/movie/1 http://localhost:8080/movie/2 http://localhost:8080/movie/3 http://localhost:8080/movie/4 http://localhost:8080/movie/5 http://localhost:8080/movie/6 http://localhost:8080/movie2/1 http://localhost:8080/movie2/2 http://localhost:8080/movie2/3 http://localhost:8080/movie2/4 http://localhost:8080/movie2/5 http://localhost:8080/movie2/6 ./pyke-1.1.1/examples/web_framework/TurboGears2/siege.log0000644000175000017500000000145611346504626022234 0ustar lambylamby Date & Time, Trans, Elap Time, Data Trans, Resp Time, Trans Rate, Throughput, Concurrent, OKAY, Failed 2008-08-06 19:09:00, 734, 9.68, 0, 0.03, 75.83, 0.00, 1.99, 734, 0 2008-08-06 19:10:46, 7570, 9.57, 3, 0.00, 791.01, 0.31, 1.98, 7570, 0 2008-08-06 19:13:29, 13079, 10.20, 6, 0.00, 1282.25, 0.59, 1.97, 13079, 0 These tests were run on a laptop computer with 2 cores and running MySQL and siege on the same computer as the web server. The first line is TurboGears 2.0 running single threaded. The second line is Pyke running single threaded (simple_server). The third line is Pyke running preforked with 2 processes (preforked_server). ./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/0000755000175000017500000000000011425360453022157 5ustar lambylamby./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/model/0000755000175000017500000000000011425360453023257 5ustar lambylamby./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/model/__init__.py0000644000175000017500000000663611346504626025407 0ustar lambylamby"""The application's model objects""" from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy import MetaData # Bruce added: from sqlalchemy import Column, MetaData, Table, types from sqlalchemy.orm import mapper # Global session manager. DBSession() returns the session object # appropriate for the current web request. DBSession = scoped_session(sessionmaker(autoflush=True, autocommit=False)) # Global metadata. If you have multiple databases with overlapping table # names, you'll need a metadata for each database. metadata = MetaData() ##### # Generally you will not want to define your table's mappers, and data objects # here in __init__ but will want to create modules them in the model directory # and import them at the bottom of this file. # ###### def init_model(engine): """Call me before using any of the tables or classes in the model.""" # If you are using reflection to introspect your database and create # table objects for you, your tables must be defined and mapped inside # the init_model function, so that the engine is available if you # use the model outside tg2, you need to make sure this is called before # you use the model. # # See the following example: #global t_reflected #t_reflected = Table("Reflected", metadata, # autoload=True, autoload_with=engine) global t_movie, t_catalog, t_director, t_genre, t_movie_director_link t_catalog = Table("catalog", metadata, autoload=True, autoload_with=engine) t_director = Table("director", metadata, autoload=True, autoload_with=engine) t_genre = Table("genre", metadata, autoload=True, autoload_with=engine) t_movie = Table("movie", metadata, autoload=True, autoload_with=engine) t_movie_director_link = Table("movie_director_link", metadata, autoload=True, autoload_with=engine) #mapper(Reflected, t_reflected) mapper(Movie, t_movie) mapper(Catalog, t_catalog) mapper(Director, t_director) mapper(Genre, t_genre) mapper(MovieDirectorLink, t_movie_director_link) # Import your model modules here. class Movie(object): pass # using 'paster shell', to load the entire environment into an interactive # shell # ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', # '__hash__', '__init__', '__module__', '__new__', '__reduce__', # '__reduce_ex__', # '__repr__', '__setattr__', '__str__', '__weakref__', '_class_state', # 'c', 'genre_id', 'id', 'length', 'title', 'year'] # conveniance function def getDirectorLinks(self): if (self.id): return DBSession.query(MovieDirectorLink).filter(MovieDirectorLink.movie_id==self.id) class Catalog(object): pass # [ ..., 'dvd_number', 'movie_id', 'selection_number'] class Director(object): pass # [..., 'director_name', 'id'] # conveniance function def getMovieLinks(self): if (self.id): return DBSession.query(MovieDirectorLink).filter(MovieDirectorLink.director_id==self.id) class Genre(object): pass # [..., 'genre_name', 'id'] class MovieDirectorLink(object): def getMovie(self): if (self.movie_id): return DBSession.query(Movie).filter(Movie.id==self.movie_id)[0] def getDirector(self): if (self.director_id): return DBSession.query(Director).filter(Director.id==self.director_id)[0] # [ ..., 'billing', 'director_id', 'movie_id'] ./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/templates/0000755000175000017500000000000011425360453024155 5ustar lambylamby./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/templates/movie.html0000644000175000017500000000124711346504626026172 0ustar lambylamby Your title goes here

Movie Title

Year

1998

Length

1:33

Directors

  1. Director
./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/templates/movie2.html0000644000175000017500000000155011346504626026251 0ustar lambylamby Your title goes here

Movie Title

Year

1998

Length

1:33

Directors

  1. Director

DVD List:

  1. 100(1)
./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/controllers/0000755000175000017500000000000011425360453024525 5ustar lambylamby./pyke-1.1.1/examples/web_framework/TurboGears2/tg2movie/controllers/root.py0000644000175000017500000000207411346504626026071 0ustar lambylamby"""Main Controller""" from tg2movie.lib.base import BaseController from tg import expose, flash from pylons.i18n import ugettext as _ #from tg import redirect, validate #from tg2movie.model import DBSession, metadata #from dbsprockets.dbmechanic.frameworks.tg2 import DBMechanic #from dbsprockets.saprovider import SAProvider from tg2movie.model import DBSession from tg2movie.model import Movie, Director, MovieDirectorLink, Catalog class RootController(BaseController): #admin = DBMechanic(SAProvider(metadata), '/admin') @expose('tg2movie.templates.index') def index(self): return dict(page='index') @expose('tg2movie.templates.about') def about(self): return dict(page='about') @expose('tg2movie.templates.movie') def movie(self, id): return dict(m=DBSession.query(Movie).filter(Movie.id==id)[0]) @expose('tg2movie.templates.movie2') def movie2(self, id): return dict(m=DBSession.query(Movie).filter(Movie.id==id)[0], catalog=DBSession.query(Catalog).filter(Catalog.movie_id==id)[:]) ./pyke-1.1.1/examples/web_framework/TurboGears2/README.txt0000644000175000017500000000425311346504626022131 0ustar lambylambyThis was originally run on TurboGears2-1.9.7a3dev_r5053. Since TurboGears 2 is still evolving, you may have to tweak this at step 4 if you use a different version... 1. Follow the directions to install TurboGears on: http://www.turbogears.org/2.0/docs/main/DownloadInstall.html 2. If you created a virtual environment, make sure you also install MySQL-python. (I just made a symbolic link from /usr/lib/python2.5/site-packages/MySQLdb /usr/lib/python2.5/site-packages/_mysql_exceptions.py /usr/lib/python2.5/site-packages/_mysql_exceptions.pyc /usr/lib/python2.5/site-packages/_mysql.so to the virtual environment's lib/python2.5/site-packages directory). 3. In the directory that you want to contain the TurboGears application (for example, your virtual environment, tg2env, if you followed the steps outlined by TurboGears): $ paster quickstart --sqlalchemy Enter project name: tg2movie Enter package name [tg2movie]: Do you need Identity (usernames/passwords) in this project? [no] This will create a directory called "tg2movie". 4. Copy the 2 html templates from this directory into the new tg2movie/tg2movie/templates directory. Modify the 3 new files below using the corresponding files from this directory: tg2movie/development.ini tg2movie/tg2movie/controllers/root.py tg2movie/tg2movie/model/__init__.py 5. Start TurboGears. From the top-level "tg2movie" directory: $ paster serve development.ini This starts the TurboGears server listening on port 8080 (which is in the development.ini file if you want to change it). 6. From your browser goto: http://localhost:8080/movie/1 http://localhost:8080/movie/2 http://localhost:8080/movie/3 http://localhost:8080/movie/4 http://localhost:8080/movie/5 http://localhost:8080/movie/6 http://localhost:8080/movie2/1 http://localhost:8080/movie2/2 http://localhost:8080/movie2/3 http://localhost:8080/movie2/4 http://localhost:8080/movie2/5 http://localhost:8080/movie2/6 7. Hit Ctrl-C to kill TurboGears when you're done. ./pyke-1.1.1/examples/web_framework/TurboGears2/development.ini0000644000175000017500000000537111346504626023460 0ustar lambylamby# # tg2movie - Pylons development environment configuration # # The %(here)s variable will be replaced with the parent directory of this file # # This file is for deployment specific config options -- other configuration # that is always required for the app is done in the config directory, # and generally should not be modified by end users. [DEFAULT] debug = false # Uncomment and replace with the address which should receive any error reports #email_to = you@yourdomain.com smtp_server = localhost error_email_from = paste@localhost [server:main] use = egg:Paste#http host = 0.0.0.0 port = 8080 [app:main] use = egg:tg2movie full_stack = true #lang = ru cache_dir = %(here)s/data beaker.session.key = tg2movie beaker.session.secret = somesecret # If you'd like to fine-tune the individual locations of the cache data dirs # for the Cache data, or the Session saves, un-comment the desired settings # here: #beaker.cache.data_dir = %(here)s/data/cache #beaker.session.data_dir = %(here)s/data/sessions # pick the form for your database # %(here) may include a ':' character on Windows environments; this can # invalidate the URI when specifying a SQLite db via path name # sqlalchemy.url=postgres://username:password:port@hostname/databasename sqlalchemy.url=mysql://movie_user:user_pw@localhost:3306/movie_db # If you have sqlite, here's a simple default to get you started # in development #sqlalchemy.url = sqlite:///%(here)s/devdata.db sqlalchemy.echo = false sqlalchemy.echo_pool = false #sqlalchemy.pool_recycle = 3600 # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* # Debug mode will enable the interactive debugging tool, allowing ANYONE to # execute malicious code after an exception is raised. set debug = false # Logging configuration # Add additional loggers, handlers, formatters here # Uses python's logging config file format # http://docs.python.org/lib/logging-config-fileformat.html [loggers] keys = root, tg2movie, sqlalchemy [handlers] keys = console [formatters] keys = generic # If you create additional loggers, add them as a key to [loggers] [logger_root] level = ERROR handlers = console [logger_tg2movie] level = ERROR handlers = qualname = tg2movie [logger_sqlalchemy] level = ERROR handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) # If you create additional handlers, add them as a key to [handlers] [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic # If you create additional formatters, add them as a key to [formatters] [formatter_generic] format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S ./pyke-1.1.1/examples/web_framework/director.html0000644000175000017500000000041411346504626020770 0ustar lambylamby Director

Bob

Movies

  1. Title
./pyke-1.1.1/examples/web_framework/__init__.py0000644000175000017500000000000011346504626020367 0ustar lambylamby./pyke-1.1.1/examples/web_framework/profile_server.py0000644000175000017500000000067211346504626021675 0ustar lambylamby# profile_server.py import cProfile import simple_server def run(port=8080, logging=False, trace_sql=False, db_engine='sqlite3'): cProfile.runctx( 'simple_server.run(port=%d, logging=%s, trace_sql=%s, db_engine=%r)' % (port, logging, trace_sql, db_engine), globals(), locals(), 'profile.out') def stats(): import pstats p = pstats.Stats('profile.out') p.sort_stats('time') p.print_stats(20) ./pyke-1.1.1/examples/web_framework/siege.urls0000644000175000017500000000102511346504626020271 0ustar lambylamby# These are all of the legal urls: http://localhost:8080/movie/1/movie.html http://localhost:8080/movie/2/movie.html http://localhost:8080/movie/3/movie.html http://localhost:8080/movie/4/movie.html http://localhost:8080/movie/5/movie.html http://localhost:8080/movie/6/movie.html http://localhost:8080/movie/1/movie2.html http://localhost:8080/movie/2/movie2.html http://localhost:8080/movie/3/movie2.html http://localhost:8080/movie/4/movie2.html http://localhost:8080/movie/5/movie2.html http://localhost:8080/movie/6/movie2.html ./pyke-1.1.1/examples/web_framework/wsgi_app.py0000644000175000017500000001375211346504626020463 0ustar lambylamby# $Id: wsgi_app.py 9f7068449a4b 2010-03-08 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys import os, os.path from pyke import knowledge_engine, krb_traceback import sqlgen # Possibly interesting values: # CONTENT_LENGTH: # CONTENT_TYPE: application/x-www-form-urlencoded # PATH_INFO: /hello/mom/and/dad.html # QUERY_STRING: this=value&that=too # REMOTE_ADDR: 127.0.0.1 # REQUEST_METHOD: GET # SCRIPT_NAME: # wsgi.errors: # wsgi.file_wrapper: # wsgi.multiprocess: False # wsgi.multithread: True # wsgi.run_once: False class trace_cursor(object): def __init__(self, cursor): self.cursor = cursor def execute(self, command, parameters=None): sys.stderr.write("\ncursor.execute got:\n") sys.stderr.write(command + '\n') if parameters: sys.stderr.write("with: %s\n" % str(parameters)) return self.cursor.execute(command, parameters) def __getattr__(self, attr): return getattr(self.cursor, attr) def init(db_connection, trace_sql=False): global Engine, Db_connection, Db_cursor Engine = knowledge_engine.engine(sqlgen, __file__) Db_connection = db_connection Db_cursor = db_connection.cursor() if trace_sql: Db_cursor = trace_cursor(Db_cursor) return Engine Debug = 0 Web_framework_dir = os.path.dirname(__file__) def gen_plan(environ, starting_tables, template_name): Engine.reset() def add_fact(fb_name, env_var): fact_name = env_var.split('.')[-1].lower() value = environ.get(env_var) if value is not None and value != '': if Debug: print "asserting %s.%s(%s) from %s" % \ (fb_name, fact_name, value, env_var) Engine.add_case_specific_fact(fb_name, fact_name, (value,)) elif Debug: print "skipping %s.%s: got %s from %s" % \ (fb_name, fact_name, value, env_var) return value def add_http(env_var): fact_name = env_var[5:].lower() value = environ.get(env_var) if value is not None and value != '': if Debug: print "asserting header.%s(%s) from %s" % \ (fact_name, value, env_var) Engine.add_case_specific_fact("header", fact_name, (value,)) elif Debug: print "skipping header.%s: got %s from %s" % \ (fact_name, value, env_var) return value add_fact("header", "CONTENT_TYPE") add_fact("request", "REQUEST_METHOD") add_fact("request", "PATH_INFO") add_fact("request", "SCRIPT_NAME") add_fact("request", "QUERY_STRING") add_fact("request", "REMOTE_ADDR") add_fact("wsgi", "wsgi.multiprocess") add_fact("wsgi", "wsgi.multithread") add_fact("wsgi", "wsgi.run_once") for env_var in environ.iterkeys(): if env_var.startswith('HTTP_'): add_http(env_var) if Debug > 1: for key, value in environ.iteritems(): print "environ: %s = %s" % (key, value) length = int(environ.get("CONTENT_LENGTH") or '0') if length: request_file = environ['wsgi.input'] Engine.add_case_specific_fact("request", "body", (request_file.read(length),)) Engine.activate('database', 'web') try: no_vars, plan = \ Engine.prove_1_goal('web.process($starting_tables, $template_name)', starting_tables=starting_tables, template_name=template_name) except: traceback = krb_traceback.format_exc(100) return None, traceback return plan, None Plans_cache = {} def wsgi_app(environ, start_response): global Plans_cache # Parse the path: components = environ["PATH_INFO"].lstrip('/').split('/') template_name = os.path.join(Web_framework_dir, components[-1]) starting_tables = [] starting_keys = {} for i in range(0, len(components) - 1, 2): starting_tables.append(components[i]) starting_keys[components[i]] = int(components[i + 1]) # Convert to tuple so that it can be used as a dict key and sort so # different orders compare equal. starting_tables = tuple(sorted(starting_tables)) template_mtime = os.stat(template_name).st_mtime mtime, plan = Plans_cache.get((starting_tables, template_name), (None, None)) if mtime is None or mtime < template_mtime: print "get_plan(..., %s, %s)" % (starting_tables, template_name) plan, traceback = gen_plan(environ, starting_tables, template_name) if plan is None: Db_connection.rollback() start_response('500 Server Error', [('Content-Type', 'text/plain')]) return traceback Plans_cache[starting_tables, template_name] = template_mtime, plan status, headers, document = plan(Db_connection, Db_cursor, starting_keys) start_response(status, headers) return document ./pyke-1.1.1/examples/web_framework/movie.html0000644000175000017500000000060411346504626020275 0ustar lambylamby Movie Title

Movie Title

Year

1998

Length

1:33

Directors

  1. Director
./pyke-1.1.1/examples/web_framework/preforked_server.py0000644000175000017500000000772211346504626022221 0ustar lambylamby# $Id: preforked_server.py c5bb15998025 2010-03-03 dangyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys import os, os.path import signal import functools import wsgiref.simple_server import wsgi_app def kill(pids, signum, frame): sys.stderr.write("preforked_server(%d) caught SIGINT\n" % os.getpid()) sys.stderr.write("preforked_server(%d) self.pids is %s\n" % (os.getpid(), str(pids))) for pid in pids: os.kill(pid, signal.SIGTERM) sys.exit(1) class RequestHandlerNoLogging(wsgiref.simple_server.WSGIRequestHandler): def log_request(self, code='-', size='-'): pass class server(wsgiref.simple_server.WSGIServer): def __init__(self, server_address, rq_handler_class, num_processes, trace_sql, db_engine): self.num_processes = num_processes self.trace_sql = trace_sql self.db_engine = db_engine wsgiref.simple_server.WSGIServer.__init__(self, server_address, rq_handler_class) def init_wsgi(self): if self.db_engine.lower() == 'sqlite3': import sqlite3 as db import sqlgen.load_sqlite3_schema as load_schema db_connection = \ db.connect(os.path.join(os.path.dirname(load_schema.__file__), 'sqlite3.db')) elif self.db_engine.lower() == 'mysql': import MySQLdb as db import sqlgen.load_mysql_schema as load_schema db_connection = db.connect(user="movie_user", passwd="user_pw", db="movie_db") else: raise ValueError("prefork_server.init_wsgi: " "unrecognized db_engine: " + self.db_engine) load_schema.load_schema(wsgi_app.init(db_connection, self.trace_sql), db, db_connection) def name(self): return "prefork_server(%d)" % self.num_processes def server_activate(self): wsgiref.simple_server.WSGIServer.server_activate(self) pids = [] for i in xrange(self.num_processes - 1): pid = os.fork() if pid == 0: self.init_wsgi() break pids.append(pid) else: # only run by parent process self.init_wsgi() signal.signal(signal.SIGINT, functools.partial(kill, pids)) def run(num_processes = 2, port = 8080, logging = False, trace_sql = False, db_engine = 'sqlite3'): server_address = ('', port) httpd = server(server_address, wsgiref.simple_server.WSGIRequestHandler if logging else RequestHandlerNoLogging, num_processes, trace_sql, db_engine) httpd.set_app(wsgi_app.wsgi_app) print "Server running..." httpd.serve_forever() if __name__ == "__main__": run() ./pyke-1.1.1/examples/web_framework/README.txt0000644000175000017500000001163511346504626017774 0ustar lambylambyThis example automatically generates programs to render HTMLTemplates (http://py-templates.sourceforge.net/htmltemplate/index.html). You can use pip or easy_install to install the HTMLTemplate package. It uses the sqlgen example on the back-end to generate and run SQL statements. You'll need to first set up the sqlgen example database before using this example. Check there for the movie_db schema. This is also an example of using multiple rule bases (its own rule base, and the sqlgen/database.krb rule base). This example also caches the plans returned from pyke so it is extremely fast. In fact, in siege tests compared to this same example running in TurboGears 2.0, this web framework is a full 10 times faster than TurboGears! To run this example, you need to be in the examples directory, then: >>> from web_framework import simple_server >>> simple_server.run() Then point your browser at: http://localhost:8080/movie/1/movie.html The "movie/1" says that you're starting with a unique row (id of 1) in the "movie" table. The "movie.html" is the name of the HTMLTemplate file you want to use. You may specify multiple unique starting tables by pairing the table name with the id value: "/movie/1/genre/2/movie.html", though this doesn't make much sense with the example templates... web.krb This uses backward-chaining to build a program to fill an HTMLTemplate. The top-level goal is: process($starting_tables, $template_name) taking (db_cursor, starting_keys) The plan returned from this goal returns a three tuple: http_status, header_list, html_document wsgi_app.py The WSGI front-end that drives pyke and executes the resulting plans. This parses the path from the WSGI environ and creates a plan for the combination of starting_tables and template_file, if one hasn't already been created by an earlier request, or if the template_file has been modified since the last plan for it was created. To create the plan, it establishes some of the interesting values in its environ as facts, then does the "process" goal above. The plan returned is run with the specific key to produce the WSGI output. To echo all sql statements to stderr, set the TRACE_SQL environment variable to something other than False prior to executing python. simple_server.py This is just a simple driver to run the wsgi_app as a local http server (default port 8080). The 'run' function takes two optional arguments: port=8080 and logging=True. Pass logging=False to disable logging requests to stderr (e.g., for performance measurement). test.py This is a test driver to let you type goals in and run them to debug the rules. Test functions: init() Creates a pyke engine and calls load_mysql_schema.load_schema. run() Loops on "goal: " prompt. Type a goal, or trace/untrace rule_name. Empty string terminates the loop. When the plan is returned, it enters a loop prompting for a python expression to run the plan (the plan itself is in a variable called "plan", and "Db_cursor" is a variable containing a database cursor). For example: "plan(Db_cursor, {'movie': 1})". An empty line terminates the plan loop. movie.html movie2.html Two very simple html templates that you can play with. siege.urls A list of all combinations of movies 1-6 with movie.html and movie2.html for the siege program. This measures the performance of the web server: $ siege -c 2 -f siege.urls profile_server.py Invokes the simple_server under cProfile. Use siege to stimulate the server, then type ctl-C to interrupt it: Terminal 1 Terminal 2 -------------------------- -------------------------- $ python >>> from examples.web_framework import profile_server >>> profile_server.run() $ siege -c 1 -f siege.urls ctl-C >>> profile_server.stats() The 'run' function takes two optional arguments: port=8080 and logging=False. Pass logging=True to enable logging requests to stderr. preforked_server.py This creates the web server socket, then forks the process to get N long running processes all accepting connections from the same socket. Each process will connect independently to the database server when they receive their first request. Use ctl-C to kill all of the processes. This will fully utilize multiple processors to achieve higher throughputs: $ python >>> from examples.web_framework import preforked_server >>> preforked_server.run(2) # 2 is the number of processes ctl-C The 'run' function takes three optional arguments: num_processes=2, port=8080 and logging=False. ./pyke-1.1.1/examples/web_framework/simple_server.py0000644000175000017500000000514511346504626021526 0ustar lambylamby# $Id: simple_server.py c5bb15998025 2010-03-03 dangyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os.path import wsgi_app import wsgiref.simple_server class RequestHandlerNoLogging(wsgiref.simple_server.WSGIRequestHandler): def log_request(self, code='-', size='-'): pass def init(trace_sql = False, db_engine = 'sqlite3'): if db_engine.lower() == 'sqlite3': import sqlite3 as db import sqlgen.load_sqlite3_schema as load_schema db_connection = \ db.connect(os.path.join(os.path.dirname(load_schema.__file__), 'sqlite3.db')) elif db_engine.lower() == 'mysql': import MySQLdb as db import sqlgen.load_mysql_schema as load_schema db_connection = db.connect(user="movie_user", passwd="user_pw", db="movie_db") else: raise ValueError("simple_server.init: unrecognized db_engine: " + db_engine) load_schema.load_schema(wsgi_app.init(db_connection, trace_sql), db, db_connection) def run(port = 8080, logging = True, trace_sql = False, db_engine = 'sqlite3'): init(trace_sql, db_engine) server_address = ('', port) httpd = wsgiref.simple_server.WSGIServer( server_address, wsgiref.simple_server.WSGIRequestHandler if logging else RequestHandlerNoLogging) httpd.set_app(wsgi_app.wsgi_app) print "Server running..." httpd.serve_forever() if __name__ == "__main__": run() ./pyke-1.1.1/examples/web_framework/test.py0000644000175000017500000000371211346504626017624 0ustar lambylamby# $Id: test.py c5bb15998025 2010-03-03 dangyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from pyke import test from sqlgen import load_mysql_schema def init(): test.init(('.', '../sqlgen')) def init_fn(engine): global Db_connection, Db_cursor import MySQLdb as db Db_connection = db.connect(user="movie_user", passwd="user_pw", db="movie_db") Db_cursor = Db_connection.cursor() load_mysql_schema.load_schema(engine, Db_connection) engine.add_universal_fact('request', 'request_method', ('GET',)) engine.add_universal_fact('request', 'path_info', ('/movie/1/movie.html',)) engine.add_universal_fact('request', 'script_info', ('',)) def run(): test.run(('web', 'database'), init_fn = init_fn, plan_globals = globals()) def doc_test(): import doctest import sys sys.exit(doctest.testmod()[0]) if __name__ == "__main__": doc_test() ./pyke-1.1.1/examples/web_framework/movie2.html0000644000175000017500000000105411346504626020357 0ustar lambylamby Movie Title

Movie Title

Year:

1998

Length:

1:33

Directors:

  1. Director

DVD List:

  1. 100(1)
./pyke-1.1.1/examples/web_framework/testall.config0000644000175000017500000000003711346504626021127 0ustar lambylambyexclude TurboGears2 README.txt ./pyke-1.1.1/examples/web_framework/web.krb0000644000175000017500000002246111346504626017552 0ustar lambylamby# $Id: web.krb fe1a2365d4bf 2008-12-27 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # process($starting_tables, $template_name) # taking (db_connection, db_cursor, starting_keys) # # This is the top-level goal called by the wsgi application. # Input goal arguments: # $starting_tables - a tuple of table_names that keys will be # provided for when the plan is called (e.g., # ('movie',)). # $template_name - the filename of the template file to use # (e.g., 'movie1.html'). # plan arguments: # db_connection - the database connection object (to commit or # rollback). # db_cursor - an open cursor to the database. # starting_keys - a dict mapping the table_names given in # $starting_tables to their key values (e.g., # {'movie': 4}). # plan return: # The plan returns the 3-tuple that can be directly returned from the # wsgi function. process_retrieval use process($starting_tables, $template_name) taking (db_connection, db_cursor, starting_keys) when !format_retrieval($starting_tables, $template_name, $needed_data) \ step -1 return $$(db_connection, data) !database.get_data($starting_tables, $needed_data) data = $$(db_cursor, starting_keys) # # format_retrieval($starting_tables, $template_name, $needed_data) # taking (data) # # This creates a plan to render the indicated template. # # Input goal arguments: # $starting_tables - a tuple of table_names that keys will be # provided for (e.g., ('movie',)). # $template_name - the filename of the template file to use # (e.g., 'movie1.html'). # Output goal arguments: # $needed_data - a descriptor of the data that will be needed # by the plan. This is ready for input into # the sqlgen/database.get_data goal. # plan arguments: # db_connection - the database connection object (to commit or # rollback). # data - the dict returned by the # sqlgen/database.get_data plan containing the # values of the $needed_data. # plan return: # The plan returns the 3-tuple that can be directly returned from the # wsgi function. format_retrieval use format_retrieval($starting_tables, $template_name, $needed_data) taking (db_connection, data) when $template = get_template($template_name) $elements = structure($template) !render_elements($elements, $needed_data) as $render_fn with db_connection.commit() return '200 OK', [('Content-Type', 'text/html')], \ ($template.render($render_fn, data),) # # render_elements($elements, $needed_data) taking (template, data) # # This creates a plan to render the elements within an HTMLTemplate. # Input goal arguments: # $elements - tuple of element names, which are each # two-tuples of the form: (element_type, name). # Output goal arguments: # $needed_data - a descriptor of the data that will be needed # by the plan. This is ready for input into # the sqlgen/database.get_data goal. # plan arguments: # template - the HTMLTemplate object. # data - the dict returned by the # sqlgen/database.get_data plan containing the # values of the $needed_data. # plan return: # The plan returns the rendered html as a string. render_elements use render_elements($elements, $needed_data) taking (template, data) when python needed_data = [] python render_fns = [] forall $element in $elements require render_element1($element, $needed_data1) as $render_fn python needed_data1 = $needed_data1 if needed_data1 and needed_data1 not in needed_data: needed_data.append(needed_data1) render_fns.append($render_fn) $needed_data = tuple(needed_data) $render_fns = tuple(render_fns) with for render_fn in $render_fns: render_fn(template, data) # # render_element1($element, $needed_data1) taking (template, data) # # This creates a plan to render a single element within an HTMLTemplate. # Input goal arguments: # $element - a single template element as a two-tuple of # the form: (element_type, name). # Output goal arguments: # $needed_data1 - an individual descriptor item of the data # that will be needed by the plan, or None if # no data is needed. This may describe an # individual (unique) data item or a multi-row # set of items. This is ready for use in the # top-level descriptor to the # sqlgen/database.get_data goal. # plan arguments: # template - the HTMLTemplate object. # data - the dict returned by the # sqlgen/database.get_data plan containing the # value(s) of the $needed_data1. # plan return: # None - the plan modifies the HTMLTemplate object in # place and does not return anything. render_element1_del use render_element1((del, $_), None) taking (template, data) with pass render_element1_sep use render_element1((sep, $_), None) taking (template, data) with pass render_element1_con use render_element1((con, $name), $name) taking (template, data) when check $name.find('.') == -1 special.claim_goal() with getattr(template, $name).content = str(data[$name]) render_element1_con_dot use render_element1((con, $name), $real_name) taking (template, data) when ($real_name, $index) = $name.split('.') check $index.isdigit() special.claim_goal() with getattr(template, $name).content = str(data[$real_name]) render_element1_rep use render_element1((rep, $name, *$children), ($name, (), *$child_data)) taking (template, data) when !render_elements($children, $child_data) as $detail_fun with getattr(template, $name).repeat($detail_fun, data[$name]) bc_extras import sys import StringIO import HTMLTemplate def renderFun(template, render_fn, data): render_fn(template, data) def get_template(template_name): f = open(template_name) try: return HTMLTemplate.Template(renderFun, f.read()) finally: f.close() # Kludge! HTMLTemplate needs methods to query the structure... def structure(template): try: # This was stdout in earlier releases of HTMLTemplate! stderr_save = sys.stderr sys.stderr = StringIO.StringIO() template.structure() lines = sys.stderr.getvalue().split('\n') finally: sys.stderr.close() sys.stderr = stderr_save return get_info(lines, '\t')[1] def get_info(lines, prefix, start = 0): ''' Returns next_index, structure. ''' ans = [] while start < len(lines): line = lines[start] if line and not line.startswith('---') and line != 'tem:': if not line.startswith(prefix): break if len(line) > len(prefix) and line[len(prefix)] == '\t': start, children = get_info(lines, prefix + '\t', start) ans[-1] = tuple(ans[-1]) + children continue else: ans.append(tuple(line.strip().split(':'))) start += 1 #print "get_info -> %d, %s" % (start, tuple(ans)) return start, tuple(ans) ./pyke-1.1.1/examples/findall/0000755000175000017500000000000011425360453015043 5ustar lambylamby./pyke-1.1.1/examples/findall/family.kfb0000644000175000017500000000032511346504626017014 0ustar lambylamby# family.kfb child_of(egon, anton, hilde) child_of(ralf, anton, hilde) child_of(hilde, stefan, brigitte) child_of(diethelm, stefan, brigitte) child_of(harald, diethelm, gitte) child_of(claudia, diethelm, gitte) ./pyke-1.1.1/examples/findall/fc_findall.krb0000644000175000017500000000274411346504626017637 0ustar lambylamby# fc_findall.krb collect_siblings foreach family.child_of($child, $father, $mother) python siblings = [] forall family.child_of($sibling, $father, $mother) check $sibling != $child python siblings.append($sibling) $siblings = tuple(siblings) assert family.siblings_of($child, $siblings) collect_cousins foreach family.child_of($child, $father, $mother) python cousins = [] forall family.siblings_of($father, $father_siblings) forall $father_sibling in $father_siblings family.child_of($cousin, $father_sibling, $_) python cousins.append($cousin) forall $father_sibling in $father_siblings family.child_of($cousin, $_, $father_sibling) python cousins.append($cousin) forall family.siblings_of($mother, $mother_siblings) forall $mother_sibling in $mother_siblings family.child_of($cousin, $_, $mother_sibling) python cousins.append($cousin) forall $mother_sibling in $mother_siblings family.child_of($cousin, $mother_sibling, $_) python cousins.append($cousin) $cousins = tuple(cousins) assert python print "%s has %s as cousins" % ($child, $cousins) ./pyke-1.1.1/examples/findall/README.txt0000644000175000017500000000200511354261564016542 0ustar lambylambyThis is a small example of the 'forall' clause used to gather a list of answers in both forward-chaining and backward-chaining rules. The forward-chaining and backward-chaining rules are in two different .krb files showing examples of use of the 'forall' clause in both cases. Rather than finding individual siblings and cousins, these rules find all siblings and all cousins and assert them in a single fact (as a tuple). >>> import driver # uses fc_findall.krb >>> driver.fc_test() egon has ('harald', 'claudia') as cousins ralf has ('harald', 'claudia') as cousins hilde has () as cousins diethelm has () as cousins harald has ('egon', 'ralf') as cousins claudia has ('egon', 'ralf') as cousins # uses bc_findall.krb >>> driver.bc_test() egon has ('harald', 'claudia') as cousins ralf has ('harald', 'claudia') as cousins hilde has () as cousins diethelm has () as cousins harald has ('egon', 'ralf') as cousins claudia has ('egon', 'ralf') as cousins ./pyke-1.1.1/examples/findall/driver.py0000644000175000017500000000124511354262232016707 0ustar lambylamby# driver.py from __future__ import with_statement import sys from pyke import knowledge_engine from pyke import krb_traceback engine = knowledge_engine.engine(__file__) def fc_test(): engine.reset() try: engine.activate('fc_findall') except: krb_traceback.print_exc() sys.exit(1) def bc_test(): engine.reset() try: engine.activate('bc_findall') with engine.prove_goal('bc_findall.cousins_of($child, $cousins)') \ as gen: for vars, plan in gen: print "%s has %s as cousins" % (vars['child'], vars['cousins']) except: krb_traceback.print_exc() sys.exit(1) ./pyke-1.1.1/examples/findall/bc_findall.krb0000644000175000017500000000260411346504626017626 0ustar lambylamby# bc_findall.krb collect_siblings use siblings_of($child, $siblings) when family.child_of($child, $father, $mother) python siblings = [] forall family.child_of($sibling, $father, $mother) check $sibling != $child python siblings.append($sibling) $siblings = tuple(siblings) collect_cousins use cousins_of($child, $cousins) when family.child_of($child, $father, $mother) python cousins = [] forall siblings_of($father, $father_siblings) forall $father_sibling in $father_siblings family.child_of($cousin, $father_sibling, $_) python cousins.append($cousin) forall $father_sibling in $father_siblings family.child_of($cousin, $_, $father_sibling) python cousins.append($cousin) forall siblings_of($mother, $mother_siblings) forall $mother_sibling in $mother_siblings family.child_of($cousin, $_, $mother_sibling) python cousins.append($cousin) forall $mother_sibling in $mother_siblings family.child_of($cousin, $mother_sibling, $_) python cousins.append($cousin) $cousins = tuple(cousins) ./pyke-1.1.1/examples/towers_of_hanoi/0000755000175000017500000000000011425360453016617 5ustar lambylamby./pyke-1.1.1/examples/towers_of_hanoi/towers_of_hanoi.krb0000644000175000017500000000341511346504626022513 0ustar lambylamby# towers_of_hanoi.krb # Solves the towers of hanoi puzzle through brute force but with the # restrictions of never repeating a board position and never moving the # same disc twice in a row. # 1 disc has 1 solution # 2 discs has 2 solutions # 3 discs has 12 solutions # 4 discs has 1872 solutions # # Sure enough, if the number of solutions for n discs is N, the number of # solutions for n+1 discs is: N**3 + N**2. solve use solve($n, $moves) when $disks = tuple(range($n)) solve2($disks, (), (), (), (), $disks, 1, (($disks, (), ())), $moves) solve2_done use solve2($a, $b, $c, $a, $b, $c, $_last_move, $_frozen_boards, ()) solve2_not_done use solve2($a1, $b1, $c1, $a2, $b2, $c2, $last_move, $frozen_boards, (($from, $to), *$moves)) when move($a1, $b1, $c1, $a, $b, $c, ($from, $to)) check $from != $last_move $freeze = ($a, $b, $c) check $freeze not in $frozen_boards solve2($a, $b, $c, $a2, $b2, $c2, $to, ($freeze, *$frozen_boards), $moves) move_01 use move(($a1, *$as), $b, $c, $as, ($a1, *$b), $c, (0, 1)) when ok($a1, $b) move_02 use move(($a1, *$as), $b, $c, $as, $b, ($a1, *$c), (0, 2)) when ok($a1, $c) move_10 use move($a, ($b1, *$bs), $c, ($b1, *$a), $bs, $c, (1, 0)) when ok($b1, $a) move_12 use move($a, ($b1, *$bs), $c, $a, $bs, ($b1, *$c), (1, 2)) when ok($b1, $c) move_20 use move($a, $b, ($c1, *$cs), ($c1, *$a), $b, $cs, (2, 0)) when ok($c1, $a) move_21 use move($a, $b, ($c1, *$cs), $a, ($c1, *$b), $cs, (2, 1)) when ok($c1, $b) ok_empty use ok($_disc, ()) ok_smaller use ok($disc, ($top, *$_rest)) when check $disc < $top ./pyke-1.1.1/examples/towers_of_hanoi/README.txt0000644000175000017500000000144111354261720020313 0ustar lambylambyThis solves the Towers of Hanoi puzzle through brute force but with the restrictions of never repeating a board position and never moving the same disc twice in a row. Running this, you'll see the following pattern: 1 disc has 1 solution 2 discs has 2 solutions 3 discs has 12 solutions 4 discs has 1872 solutions Sure enough, if the number of solutions for n discs is N, the number of solutions for n+1 discs is: N**3 + N**2. (I leave the explanation of why this is so to the reader! :-) This would mean that 5 discs has 6,563,711,232 solutions, but I haven't run this to verify the answer... >>> import driver >>> driver.test(2) # test takes the number of disks as an argument got 1: ((0, 1), (0, 2), (1, 2)) got 2: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) ./pyke-1.1.1/examples/towers_of_hanoi/towers2.krb0000644000175000017500000000264211346504626020734 0ustar lambylamby# towers2.krb # This is the same solution as towers_of_hanoi.krb, but with one 'move' rule # rather than 6 rules. solve use solve($n, $moves) when $disks = tuple(range($n)) solve2(($disks, (), ()), ((), (), $disks), 1, (($disks, (), ())), $moves) solve2_done use solve2(($a, $b, $c), ($a, $b, $c), $_last_move, $_frozen_boards, ()) solve2_not_done use solve2(($a1, $b1, $c1), ($a2, $b2, $c2), $last_move, $frozen_boards, (($from, $to), *$moves)) when move(($a1, $b1, $c1), ($a, $b, $c), ($from, $to)) check $from != $last_move $freeze = ($a, $b, $c) check $freeze not in $frozen_boards solve2(($a, $b, $c), ($a2, $b2, $c2), $to, ($freeze, *$frozen_boards), $moves) move use move($old_board, $new_board, ($from, $to)) when $from in range(3) check len($old_board[$from]) > 0 $to in range(3) check $from != $to $top = $old_board[$from][0] $to_pile = $old_board[$to] ok($top, $to_pile) $new_board = tuple((pile[1:] if i == $from else ($top,) + pile if i == $to else pile) for i, pile in enumerate($old_board)) ok_empty use ok($_disc, ()) ok_smaller use ok($disc, ($top, *$_rest)) when check $disc < $top ./pyke-1.1.1/examples/towers_of_hanoi/driver.py0000644000175000017500000000112111354262266020463 0ustar lambylamby# driver.py from __future__ import with_statement import sys from pyke import knowledge_engine from pyke import krb_traceback engine = knowledge_engine.engine(__file__) def test(num_disks): engine.reset() try: engine.activate('towers_of_hanoi') with engine.prove_goal('towers_of_hanoi.solve($num_disks, $moves)', num_disks=num_disks) \ as gen: for i, (vars, no_plan) in enumerate(gen): print "got %d:" % (i + 1), vars['moves'] except: krb_traceback.print_exc() sys.exit(1) ./pyke-1.1.1/examples/sqlgen/0000755000175000017500000000000011425360453014723 5ustar lambylamby./pyke-1.1.1/examples/sqlgen/load_tables0000755000175000017500000000257611346504626017140 0ustar lambylamby#!/bin/bash # $Id: load_tables c0848aaeaad7 2008-03-03 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. usage() { echo "usage: load_tables" >&2 exit 2 } [ $# -eq 0 ] || usage set -e mysqlimport --user=movie_admin --password=admin_pw --delete --local movie_db \ genre.txt movie.txt director.txt movie_director_link.txt catalog.txt ./pyke-1.1.1/examples/sqlgen/__init__.py0000644000175000017500000000000011346504626017026 0ustar lambylamby./pyke-1.1.1/examples/sqlgen/create_database.sql0000644000175000017500000000252111346504626020537 0ustar lambylamby# $Id: create_database.sql c0848aaeaad7 2008-03-03 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. create database if not exists movie_db; grant all on movie_db.* to movie_admin identified by 'admin_pw'; grant select, insert, update, delete on movie_db.* to movie_user identified by 'user_pw'; ./pyke-1.1.1/examples/sqlgen/Schema.dia0000644000175000017500000000476611346504626016623 0ustar lambylambyn8y õMӼJT4Y4fӳ6dhJ YI*y!%bB3J@m#_fO"=I|7Gg"$Ao__*mS7Wne3^~ A,G4|Yei~,wn7߷i"UnDI:{ok(Ys!ソT۳yCދnB${ג)Sdx{Oe"R5¶v~ ->Q0⩗8#.͚ͭ̚K͚ }ffu$VQ sb-̲C ޽4 .߼g)ZryaJUrܟCDS8Ӗ쫿Nௗ En`G1 nv%2V0G`&"EezV✴J?sdeq_G6?gap7|5d~RO~-n)ptx7V[p#뱍Ĺ5b.p|Dpjjj|PIYnl/%ۖ~gV7o.^d0է䨴"zYO[qX9D}h)ɫB7]:0V@_ǛÆmmwe(^P~#tpRvngzפxDO3)y'?NyMȡX"j/U \]8 "&Icyn5}5p.H 2UZN@]j!g<f]3- sVO6J;+%OXb!}J!V&EIuap aFa^#'i闠@aź8Jk. Ղ*Q]Y+0||5)XI[-aJy,b'X nG)b9p=l|6]©wFq{}fIAНtZ`nىG5z6bGމ>d%|yL9dي4uu&[3Y ֭WkX-A)P1eO1ӤC46:vQ[k_kxibI1@.DPWV hXZգ4;o=0g_Cl& .khWY.OAA@X\]$epI2K.H-iJw%+g FɚY$,\WGYN\_E&4)*0wU_UÈL,]-]u+O'LN=c ;YzLU<]ᙫvژsR(} |FVA&S(0JLj)QDR˹ajt*d.ӇKWKמtD,_'XN4}"ˇk"is[ؾt,_]Q\%(c9$B,(-lHʱc(VP ⶏp: J>_DzS?x./pyke-1.1.1/examples/sqlgen/catalog.txt0000644000175000017500000000007011346504626017077 0ustar lambylamby1 100 1 2 101 1 3 102 1 4 103 1 5 103 2 6 104 1 3 105 1 ./pyke-1.1.1/examples/sqlgen/sqlite3.db0000644000175000017500000003600011346504626016621 0ustar lambylambySQLite format 3@               ihggfed  i h g g f e d uQ<'Casino Royale2:11Bogus1:50"5Sleepless in Seattle1:45<iA Funny Thing Happened on the Way to the Forum1:39)The Terminator1:48-KIt's a Mad, Mad, Mad, Mad World3:12   Drama+Science Fiction Romance Comedy   Drama+Science Fiction Romance Comedy  339tablemovie_director_linkmovie_director_linkCREATE TABLE movie_director_link ( movie_id int not null references movie(id), director_id int not null references director(id), billing int not null default 1, primary key (movie_id, director_id) )EY3indexsqlite_autoindex_movie_director_link_1movie_director_linkf#tablecatalogcatalogCREATE TABLE catalog ( movie_id int not null references movie(id), dvd_number int not null, selection_number int not null default 1, primary key (movie_id, dvd_number, selection_number) )  @$ wtabledirectordirector CREATE TABLE director ( id int not null primary key, director_name varchar(100), unique (director_name) )/ Cindexsqlite_autoindex_director_2director/ Cindexsqlite_autoindex_director_1director-Aindexsqlite_autoindex_catalog_1catalog6KtablemoviemovieCREATE TABLE movie ( id int not null primary key, title varchar(100), genre_id int not null references genre (id), year year, length time ))=indexsqlite_autoindex_movie_1movieetablegenregenreCREATE TABLE genre ( id int not null primary key, genre_name varchar(100), unique (genre_name) ))=indexsqlite_autoindex_genre_1genre ) =indexsqlite_autoindex_genre_2genre 2p\H2 - Richard Talmadge ) Robert Parrish ) Joseph McGrath#John Huston!Ken Hughes Val Guest)Norman Jewison#Nora Ephron)Richard Lester'James Cameron)Stanley Kramer     =xe=R-Richard Talmadge )Robert Parrish )Joseph McGrath #John Huston!Ken Hughes Val Guest)Norman Jewison#Nora Ephron)Richard Lester'James Cameron)Stanley Kramer./pyke-1.1.1/examples/sqlgen/genre.txt0000644000175000017500000000005511346504626016570 0ustar lambylamby1 Comedy 2 Romance 3 Science Fiction 4 Drama ./pyke-1.1.1/examples/sqlgen/README.txt0000644000175000017500000001044311354261712016422 0ustar lambylambyNOTE: This example is only a proof-of-concept and is not intended for production use! This example generates SQL statements, given a tuple of column names. It was originally written for MySQL but now also supports Sqlite3. It is relatively easy to port to other database engines. The following files can be used to create and populate the database. A pre-populated Sqlite3 database is included in this directory as 'sqlite3.db'. create_database.sql Creates movie_db database and users: movie_admin/admin_pw and movie_user/user_pw. create_tables.sql # for MySQL create_tables.sqlite3 # for Sqlite3 movie id int auto_increment title varchar(100) genre_id int year year length time genre id int auto_increment genre_name varchar(100) director id int auto_increment director_name varchar(100) movie_director_link movie_id int director_id int billing int catalog movie_id int dvd_number int selection_number int default 1 drop_tables.sql This shell script loads 6 movies: load_tables # for MySQL load_sqlite3 # for Sqlite3 Loads the following files in the indicated order: genre.txt movie.txt director.txt movie_director_link.txt catalog.txt load_mysql_schema.py load_sqlite3_schema.py These provide one function which loads the "schema" fact base from the database connection provided: load_schema(pyke_engine, dbi_module, db_connection) All facts are asserted as universal facts so that they remain after an engine.reset() is done. The following facts are asserted: schema.paramstyle(paramstyle) # e.g.: format, qmark schema.column(table_name, col_name, type, null, key, default, extra) schema.primary_key(table_name, columns) schema.many_to_1(table_many, table_1, table_many_columns, table_1_columns) schema.links_to(depth, start_table, end_table, joins) database.krb This uses backward-chaining to build SQL statements and cook them into plans that execute them. The top-level goal is: get_data($starting_tables, $needed_data) \ taking(db_cursor, starting_keys) $starting_tables is a list of tables that you have id values for. The starting_keys parameter to the plan is a dictionary mapping these table names to id values that identify a unique row in that table. The $needed_data is a tuple of column_names and/or (multi-row-name, (options), column_name...). The plan will return a dictionary with keys from $needed_data and values from the database. Where the multi-row-name sub-tuple is used, the key in the top-level dictionary is multi-row-name (this can be anything, it doesn't match anything in the schema). Its value is a tuple of dictionaries with the indicated column_names as keys and values from the database. driver.py driver_sqlite3.py These have a debug "cursor" class that can be used instead of a real database cursor. The debug cursor class does not require a database connection and returns dummy data from any SELECT call. Import either driver (for mysql) or driver_sqlite3, for example: >>> import driver_sqlite3 Test functions: >>> driver_sqlite3.init() Creates a pyke engine and calls load_schema. >>> driver_sqlite3.run() Loops on "goal: " prompt. Type a goal, or trace/untrace rule_name. Empty string terminates the loop. Examples: goal: get_data((movie), (title, year, length)) goal: get_data((director), (director_name, (movies, (), title, year)) When the plan is run, it first runs it with the debug cursor, then enters a loop prompting for the starting_keys values. These should be entered space separated. An empty line terminates the plan loop. For example: ('movie',): 1 ./pyke-1.1.1/examples/sqlgen/create_tables.sql0000644000175000017500000000414411346504626020250 0ustar lambylamby# $Id: create_tables.sql c0848aaeaad7 2008-03-03 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. create table if not exists movie ( id int not null auto_increment primary key, title varchar(100), genre_id int not null references genre (id), year year, length time ) type=InnoDB; create table if not exists genre ( id int not null auto_increment primary key, genre_name varchar(100), unique (genre_name) ) type=InnoDB; create table if not exists director ( id int not null auto_increment primary key, director_name varchar(100), unique (director_name) ) type=InnoDB; create table if not exists movie_director_link ( movie_id int not null references movie(id), director_id int not null references director(id), billing int not null default 1, primary key (movie_id, director_id) ) type=InnoDB; create table if not exists catalog ( movie_id int not null references movie(id), dvd_number int not null, selection_number int not null default 1, primary key (movie_id, dvd_number, selection_number) ) type=InnoDB; ./pyke-1.1.1/examples/sqlgen/driver.py0000644000175000017500000000604411354262654016601 0ustar lambylamby# $Id: driver.py 6de8ee4e7d2d 2010-03-29 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import with_statement import contextlib from pyke import test import load_mysql_schema class cursor(object): rowcount = 1 # This is only check for unique queries... def __init__(self, width): self.width = width def execute(self, str, parameters=None): print "execute got:" print str if parameters: print "with:", parameters def fetchone(self, base = 44): return (base,) * self.width def fetchall(self): return tuple(self.fetchone(i) for i in range(1, 5)) def init(): global db import MySQLdb as db test.init() with contextlib.closing(db.connect(user="movie_user", passwd="user_pw", db="movie_db")) \ as conn: load_mysql_schema.load_schema(test.Engine, db, conn) def run_plan(globals, locals): plan = locals['plan'] args = locals['args'] starting_keys = dict(zip(args[0], range(1, len(args[0]) + 1))) print "executing the plan with debug database cursor" ans = plan(cursor(len(args[1])), starting_keys) print "plan returned:", ans while True: print data_values = raw_input("%s: " % str(args[0])).split() if not data_values: break starting_keys = dict(zip(args[0], data_values)) print "executing the plan with real database cursor" with contextlib.closing(db.connect(user="movie_user", passwd="user_pw", db="movie_db")) \ as conn: with contextlib.closing(conn.cursor()) as cur: ans = plan(cur, starting_keys) print "plan returned:", ans def run(): if not test.Did_init: init() test.run('database', fn_to_run_plan = run_plan) def doc_test(): import doctest import sys sys.exit(doctest.testmod()[0]) if __name__ == "__main__": doc_test() ./pyke-1.1.1/examples/sqlgen/drop_tables.sql0000644000175000017500000000244211346504626017750 0ustar lambylamby# $Id: drop_tables.sql c0848aaeaad7 2008-03-03 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. drop table if exists movie; drop table if exists genre; drop table if exists director; drop table if exists movie_director_link; drop table if exists catalog; ./pyke-1.1.1/examples/sqlgen/driver_sqlite3.py0000644000175000017500000000566211354262654020252 0ustar lambylamby# $Id: driver_sqlite3.py 6de8ee4e7d2d 2010-03-29 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import with_statement import os.path import contextlib import sqlite3 as db from pyke import test import load_sqlite3_schema Sqlgen_dir = os.path.dirname(load_sqlite3_schema.__file__) Sqlite3_db = os.path.join(Sqlgen_dir, "sqlite3.db") class cursor(object): rowcount = 1 # This is only check for unique queries... def __init__(self, width): self.width = width def execute(self, str, parameters=None): print "execute got:" print str if parameters: print "with:", parameters def fetchone(self, base = 44): return (base,) * self.width def fetchall(self): return tuple(self.fetchone(i) for i in range(1, 5)) def init(): test.init() with contextlib.closing(db.connect(Sqlite3_db)) as conn: load_sqlite3_schema.load_schema(test.Engine, db, conn) def run_plan(globals, locals): plan = locals['plan'] args = locals['args'] starting_keys = dict(zip(args[0], range(1, len(args[0]) + 1))) print "executing the plan with debug database cursor" ans = plan(cursor(len(args[1])), starting_keys) print "plan returned:", ans while True: print data_values = raw_input("%s: " % str(args[0])).split() if not data_values: break starting_keys = dict(zip(args[0], data_values)) print "executing the plan with real database cursor" with contextlib.closing(db.connect(Sqlite3_db)) as conn: with contextlib.closing(conn.cursor()) as cur: ans = plan(cur, starting_keys) print "plan returned:", ans def run(): if not test.Did_init: init() test.run('database', fn_to_run_plan = run_plan) def doc_test(): import doctest import sys sys.exit(doctest.testmod()[0]) if __name__ == "__main__": doc_test() ./pyke-1.1.1/examples/sqlgen/movie_director_link.txt0000644000175000017500000000010411346504626021512 0ustar lambylamby1 1 1 2 2 1 3 3 1 4 4 1 5 5 1 6 6 1 6 7 2 6 8 3 6 9 4 6 10 5 6 11 6 ./pyke-1.1.1/examples/sqlgen/create_tables.sqlite30000644000175000017500000000402411346504626021032 0ustar lambylamby-- $Id: create_tables.sqlite3 2825a90e9445 2008-12-25 mtnyogi $ -- -- Copyright © 2008 Bruce Frederiksen -- -- Permission is hereby granted, free of charge, to any person obtaining a copy -- of this software and associated documentation files (the "Software"), to deal -- in the Software without restriction, including without limitation the rights -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -- copies of the Software, and to permit persons to whom the Software is -- furnished to do so, subject to the following conditions: -- -- The above copyright notice and this permission notice shall be included in -- all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -- THE SOFTWARE. create table if not exists movie ( id int not null primary key, title varchar(100), genre_id int not null references genre (id), year year, length time ); create table if not exists genre ( id int not null primary key, genre_name varchar(100), unique (genre_name) ); create table if not exists director ( id int not null primary key, director_name varchar(100), unique (director_name) ); create table if not exists movie_director_link ( movie_id int not null references movie(id), director_id int not null references director(id), billing int not null default 1, primary key (movie_id, director_id) ); create table if not exists catalog ( movie_id int not null references movie(id), dvd_number int not null, selection_number int not null default 1, primary key (movie_id, dvd_number, selection_number) ); ./pyke-1.1.1/examples/sqlgen/database.krb0000644000175000017500000010101611346504626017172 0ustar lambylamby# $Id: database.krb f163995c2be9 2010-02-25 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # This logic adds inheritance to the database tables. This means that when # table A includes a foreign key to table B, all of the columns in table B are # inherited by table A and can be used as if they appeared directly in table A. # The logic will automatically add the necessary joins from table A to table B. # This inheritance goes on any number of levels, so columns inherited by # table B are also included in table A. # # This example is simplified in the following ways: # - it is assumed that the primary key to all tables is called 'id'. # - it is assumed that all foreign keys linking to table 'B' are called # 'B_id'. # - functions and operators are not supported (in the 'where' or 'select' # clause). # - thus no aggregation is possible; so no need for a "group by" clause # which would otherwise be added automatically. # - updates are not supported (insert, update, delete). # - column_name qualification is not supported to disambiguate between # multiple columns being inherited with the same name. # get_data($starting_tables, $needed_data) taking(db_cursor, starting_keys) # # This is the top-level goal to generate a plan to retrieve a set of data. # Input goal arguments: # $starting_tables - a tuple of table_names that keys will be # provided for when the plan is called (e.g., # ('movie',)). # $needed_data - a tuple of data descriptors describing the data # that should be returned (as a dict) from # executing the plan. Each data descriptor is one # of the following: # column_name # -- binds a single (ie, unique) value to # column_name # (name, (option...), needed_data...) # -- binds a tuple of dictionaries to name # -- an option is: # ('linked_to' table_name) # -- table_name is the "1" table in # the many_to_1 link # ('order_by' 'asc'|'desc'|column_name...) # -- needed_data can only be a column_name at # this point (no nested multirow queries). # plan arguments: # db_cursor - an open cursor to the database. # starting_keys - a dict mapping the table_names in # $starting_tables to their key values (e.g., # {'movie': 4}). # plan return: # The plan returns a dict mapping the names in $needed_data to their # values from the database. get_data use get_data($starting_tables, $needed_data) taking(db_cursor, starting_keys) when #python print "get_data: starting_tables: %s" % str($starting_tables) #python print "get_data: needed_data: %s" % str($needed_data) !parse_needed_data($starting_tables, $needed_data, $multi_plans, $unique_queries) !process_unique_queries($unique_queries) ans = {} $$(db_cursor, starting_keys, ans) with for row_name, plan in $multi_plans: ans[row_name] = plan(db_cursor, starting_keys) return ans # parse_needed_data($starting_tables, $needed_data, # $multi_plans, $unique_queries) # # This parses all of the $needed_data and produces two outputs: one for the # multi_row data, and the other for the unique data. # Input goal arguments: # $starting_tables - a tuple of table_names that keys will be # provided for when the plan is called (e.g., # ('movie',)). # $needed_data - a tuple of data descriptors describing the data # that should be returned (as a dict) from # executing the plan. Each data descriptor is one # of the following: # column_name # -- binds a single (ie, unique) value to # column_name # (name, (option...), needed_data...) # -- binds a tuple of dictionaries to name # -- an option is: # ('linked_to' table_name) # -- table_name is the "1" table in # the many_to_1 link # ('order_by' 'asc'|'desc'|column_name...) # -- needed_data can only be a column_name at # this point (no nested multirow queries). # Output goal arguments: # $multi_plans - A tuple of two-tuples; one for each request # for multi-row data. Each two-tuple has: # row_name # - the key to set in the top-level dict. # plan # - a function taking (db_cursor, # starting_keys) # $unique_queries - A tuple of three-tuples; one for each # request for a unique data value. Each # three-tuple has: # from clause # - a tuple starting with table_name_a # followed by: # (table_name_b, from_table, # from_columns, b_columns)... # select clause # - a column_name in the last table of the # from clause # name binding # - the key in the resulting dict # No plan is returned. parse_needed_data use parse_needed_data($starting_tables, $needed_data, $multi_plans, $unique_queries) when #python print "parse_needed_data(%s, %s, _, _)" % \ # ($starting_tables, $needed_data) # Note difference between multi_plans and $multi_plans! python multi_plans = [] python unique_queries = [] forall $needed_element in $needed_data require parse_needed_element($starting_tables, $needed_element, $multi_plan, $unique_queries1) python if $multi_plan: multi_plans.append($multi_plan) python if $unique_queries1: unique_queries.append($unique_queries1) $multi_plans = tuple(multi_plans) $unique_queries = tuple(unique_queries) # parse_needed_element($starting_tables, $needed_element, # $multi_plan, $unique_query) # # This processes one element in $needed_data. # Input goal arguments: # $starting_tables - a tuple of table_names that keys will be # provided for when the plan is called (e.g., # ('movie',)). # $needed_element - one of the following: # column_name # -- binds a single (ie, unique) value to # column_name # (name, (option...), needed_data...) # -- binds a tuple of dictionaries to name # -- an option is: # ('linked_to' table_name) # -- table_name is the "1" table in # the many_to_1 link # ('order_by' 'asc'|'desc'|column_name...) # -- needed_data can only be a column_name at # this point (no nested multirow queries). # Output goal arguments: # $multi_plan - a two-tuple: (row_name, plan), or None # row_name # - the key to set in the top-level dict. # plan # - a function taking (db_cursor, # starting_keys) # $unique_query - This element is a three-tuple or None: # from clause # - a tuple starting with table_name_a # followed by: # (table_name_b, from_table, # from_columns, b_columns)... # select clause # - a column_name in the last table of the # from clause # name binding # - the key in the resulting dict # No plan is returned. parse_needed_element_multi use parse_needed_element($starting_tables, ($row_name, $options, *$multi_data), ($row_name, $plan), None) when # Multi-row request. special.claim_goal() get_multi($starting_tables, $options, $multi_data, $plan) parse_needed_element_unique use parse_needed_element($starting_tables, $unique_data, None, $unique_out) when # Single-row (unique) request. get_unique($starting_tables, $unique_data, $unique_out) # get_unique($starting_tables, $column, $unique_query) # # This processes one column in $needed_data. # Input goal arguments: # $starting_tables - a tuple of table_names that keys will be # provided for when the plan is called (e.g., # ('movie',)). # $column - the desired column_name # Output goal arguments: # $unique_query - A three-tuple: # from clause # - a tuple starting with table_name_a # followed by: # (table_name_b, from_table, # from_columns, b_columns)... # select clause # - a column_name in the last table of the # from clause # name binding # - the key in the resulting dict # No plan is returned. get_unique_direct use get_unique($starting_tables, $column, (($table), $column, $column)) when # This handles the case where $column is directly in one of the # $starting_tables. lookup_column($column, $starting_tables, $table) special.claim_goal() # lookup_column sets $table to None if there were multiple matches... # (which is an error and should cause get_unique to fail). check $table get_unique_indirect use get_unique($starting_tables, $column, ($from_clause, $column, $column)) when # This handles the case where $column is inherited into one of the # $starting_tables. lookup_indirect(1, $column, $starting_tables, $from_clause) # lookup_column($column, $starting_tables, $table) # # This figures out which unique table within $starting_tables contains $column. # # This succeeds with None, after printing an error message, if $column appears # in more than one $table within $starting_tables. This is done so that the # invoking goal does not try to find inherited columns in this situation. # # It fails if none of the tables in $starting_tables contain $column. # # Input goal arguments: # $column - The desired column_name. # $starting_tables - A tuple of table_names. # Output goal arguments: # $table - The table_name of the table in $starting_tables # containing $column, or None. # No plan is returned. lookup_column use lookup_column($column, $starting_tables, $table) when # Find all of the tables in $starting_tables that have $column. find_columns($column, $starting_tables, $tables) unique_table($column, $tables, $table) # find_columns($column, $tables_in, $matching_tables) # # Produces a list of the table in $tables_in that directly contain $column. # Input goal arguments: # $column - The column_name to look for. # $tables_in - A tuple of table_names. # Output goal arguments: # $matching_tables - A tuple of the table_names in $tables_in that # directly contain $column. # No plan is returned. find_columns use find_columns($column, $tables_in, $matching_tables) when python matching_tables = [] forall $table in $tables_in schema.column($table, $column, $_, $_, $_, $_, $_) python matching_tables.append($table) $matching_tables = tuple(matching_tables) # unique_table($column, $table_list, $table) # # This checks to see if $table_list contains only one table. # # If so, it returns it. # If $table_list is empty, it fails. # If $table_list has more than one table, it prints an error message and # succeeds with $table set to None. # # Input goal arguments: # $column - The column_name to use in the error message. # $table_list - A tuple of table_names. # Output goal arguments: # $table - The table_name of the only table in $table_list, # or None. # No plan is returned. unique_table_success use unique_table($_, ($table), $table) when # A single table was found: Success! special.claim_goal() unique_table_not_found use unique_table($_, (), $_) when # No tables were found: cause the unique_table goal to fail! special.claim_goal() check False unique_table_dups use unique_table($column, $tables, None) when # Multiple tables were found! # Print error message: python print "duplicate column %s, found in %s" % ($column, $tables) # Then succeed with None to flag the problem... # lookup_indirect($depth, $column, $starting_tables, $from_clause) # # This implements a depth first search of the tables inherited by the tables # in $starting_tables looking for $column. It calls itself recursively to # search at ever greater $depth values (starting with 1). # # It will fail if the $column can't be found, or if the $column exists in more # than one inherited table at the same $depth when first encountered. (It's # OK if the first encounter is unique, but there are more $columns at a # greater depth). # # No plan is returned. lookup_indirect use lookup_indirect($depth, $column, $starting_tables, $from_clause) when #python print "lookup_indirect: " # "find_at_depth(%s, %s, %s, _, _)" % # ($depth, $column, $starting_tables) !find_at_depth($depth, $column, $starting_tables, $got_depth, $from_clauses) #python print "lookup_indirect: " # "lookup_indirect2(%s, %s, %s, %s, %s, _)" % # ($depth, $column, $starting_tables, $got_depth, # $tables) lookup_indirect2($depth, $column, $starting_tables, $got_depth, $from_clauses, $from_clause) # lookup_indirect2($depth, $column, $starting_tables, $got_depth, $from_clauses, # $from_clause) # # This finishes the job for lookup_indirect after find_at_depth has looked for # $column at $depth. $got_depth is a bool indicating whether there were any # inherited tables at $depth. This is how lookup_indirect2 knows when to stop # searching at greater depths. # # No plan is returned. lookup_indirect2_success # $column found in one table. Success! use lookup_indirect2($_depth, $_column, $_starting_tables, $_got_depth, ($from_clause), $from_clause) when # Found it! special.claim_goal() lookup_indirect2_next_depth # $column not found, but there are still tables at this $depth. # Try the next higher $depth... use lookup_indirect2($depth, $column, $starting_tables, True, (), $from_clause) when # $column not found, but the inherited tables haven't hit $depth yet! special.claim_goal() $depth2 = $depth + 1 lookup_indirect($depth2, $column, $starting_tables, $from_clause) lookup_indirect2_dups # We're using $got_depth of True here so that we don't match empty $froms. # We can do this because $got_depth must be True if there are any $froms. # If $got_depth is False, let lookup_indirect2 fail silently... use lookup_indirect2($_depth, $column, $_starting_tables, True, $froms, $_) when python print "duplicate tables for %s: %s" % \ ($column, tuple(from_[0] for from_ in $froms)) check False # find_at_depth($depth, $column, $starting_tables, $got_depth, $from_clauses) # # This looks for $column at an inheritance depth of $depth from each table in # $starting_tables. It returns $got_depth of True if any tables were found at # this depth and the $from_clauses of all the matches. # # No plan is returned. find_at_depth use find_at_depth($depth, $column, $starting_tables, $got_depth, $from_clauses) when python got_depth = False python from_clauses = [] forall $table in $starting_tables require ($got_depth1, $from) = find_at_depth1(engine, $depth, $column, $table) python got_depth = got_depth or $got_depth1 python from_clauses.extend($from) $got_depth = got_depth $from_clauses = tuple(from_clauses) # process_unique_queries($unique_queries) taking(db_cursor, starting_keys, dict) # # This groups queries from a common root table into one sql select statement # to minimize the number of sql statements issued to the database. It does # this by: # 1. Taking the first item in $unique_queries # 2. Going through the rest of the items in $unique_queries sorting out # those that can be combined and those that can't. # 3. For all of the combined queries it generates a single sql select # statement. # 4. Then it repeats the process on those queries that can't be combined # until no queries remain. # # It returns a plan that will execute these sql statements and populate a dict # with the answers. process_unique_queries_done # All done! use process_unique_queries(()) taking(db_cursor, starting_keys, dict) with pass process_unique_queries_step use process_unique_queries((($from, $select, $key), *$rest)) taking(db_cursor, starting_keys, dict) when $select2 = "%s.%s" % (($from[0] if len($from) == 1 else $from[-1][0]), $select) #python print "process_unique_queries_step: " \ # "combine_queries(%s, %s, _, _)" % \ # (($from, ($select2,), ($key,)), $rest) !combine_queries(($from, ($select2), ($key)), $rest, $combined_query, $uncombined_queries) #python print "process_unique_queries_step: combined_query is: %s" % \ # str($combined_query) !run_query($combined_query) $$(db_cursor, starting_keys, dict) !process_unique_queries($uncombined_queries) $$(db_cursor, starting_keys, dict) # combine_queries($unique_query, $rest_unique_queries, # $combined_query, $uncombined_queries) # # This combines $unique_query with as many queries in $rest_unique_queries as # it can. The combined query is returned in $combined_query and the # uncombined queries are returned as a tuple in $uncombined_queries. # # No plan is returned. combine_queries_done_n # All done! use combine_queries($query, (), $query, ()) combine_queries_match_n # The next query in $rest_unique_queries can be combined! use combine_queries((($table1, *$joins1), $select_columns1, $dict_keys1), ((($table1, *$joins2), $select_column2, $dict_key2), *$rest), $combined_query, $uncombined_queries) when special.claim_goal() #python print "merge_joins(%s, %s, $merged_join)" % # ($joins1, $joins2) !merge_joins($joins1, $joins2, $merged_join) $select2 = "%s.%s" % (($table1 if not $joins2 else $joins2[-1][0]), $select_column2) !combine_queries((($table1, *$merged_join), ($select2, *$select_columns1), ($dict_key2, *$dict_keys1)), $rest, $combined_query, $uncombined_queries) combine_queries_match # The next query in $rest_unique_queries can not be combined... use combine_queries($query1, ($uncombined_query1, *$rest), $combined_query, ($uncombined_query1, *$uncombined_queries)) when !combine_queries($query1, $rest, $combined_query, $uncombined_queries) # merge_joins($joins1, $joins2, $merged_join) # # Merges two join clauses into one. It does this by determining how many of # the leading tables match (if any) and then appending the unmatched tail of # the second join clause to the end of the first join clause. # # This goal should never fail. # # Examples: # merge_joins((table1, table2, table3), (table1, table2, table4), # (table1, table2, table3, table4)) # # merge_joins((table1, table2), (table3, table4), # (table1, table2, table3, table4)) # # merge_joins((table1, table2, table3), (table1, table2), # (table1, table2, table3)) # # No plan is returned. merge_joins_match # Merge joins with the same first table by merging the rest of the joins # in each clause. use merge_joins(($first_join, *$rest_joins1), ($first_join, *$rest_joins2), ($first_join, *$merged_joins)) when special.claim_goal() merge_joins($rest_joins1, $rest_joins2, $merged_joins) merge_joins_no_match # Merge joins with different first tables by concatenating the join # clauses. use merge_joins($joins1, $joins2, $merged_joins) when $merged_joins = $joins1 + $joins2 # run_query($combined_query) taking(db_cursor, starting_keys, dict) # # This constructs a sql select statement for $combined_query and returns a # plan to run that select statement and populate a dict with the results. run_query use run_query((($table, *$joins), $select_columns, $dict_keys)) taking(db_cursor, starting_keys, dict) when #python print "make_from_clause(%s, %s, $from_clause)" % \ # ($table, $joins) !make_from_clause($table, $joins, $from_clause) command_parameters($param, id, $param_fn) $sql_command = "select %s\n from %s\n where %s.id = %s" % \ (', '.join($select_columns), $from_clause, $table, $param) with db_cursor.execute($sql_command, $param_fn(starting_keys[$table])) if db_cursor.rowcount != -1 and db_cursor.rowcount is not None: assert db_cursor.rowcount == 1, \ 'expected unique row, got %d rows from "%s"' % \ (db_cursor.rowcount, $sql_command) dict.update(zip($dict_keys, db_cursor.fetchone())) else: row = db_cursor.fetchone() assert row is not None, \ 'expected unique row, got 0 rows from "%s"' % ($sql_command,) dict.update(zip($dict_keys, row)) assert db_cursor.fetchone() is None, \ 'expected unique row, got >1 rows from "%s"' % ($sql_command,) # make_from_clause($table, $joins, $from_clause) # # Creates the $from_clause for a sql select statement (as a string) starting # with $table and including the $joins. # # No plan is returned. make_from_clause use make_from_clause($table, $joins, $from_clause) when python from_clause = $table forall $join in $joins require ($table2, $from_table, $from_cols, $to_cols) = $join python from_clause += "\n inner join %s on (%s)" % \ ($table2, ' and '.join("%s.%s = %s.%s" % ($from_table, from_col, $table2, to_col) for from_col, to_col in zip($from_cols, $to_cols))) $from_clause = from_clause # get_multi($starting_tables, $options, $multi_data, $plan) # # This creates a $plan for retrieving the multi-row $multi_data starting from # $starting_tables with any indicated $options. # Input goal arguments: # $starting_tables - a tuple of table_names that keys will be # provided for when the plan is called (e.g., # ('movie',)). # $options - a tuple of options. Currently, only two options # are supported: # ('linked_to', table_name) # ('order_by', 'asc'|'desc'|column_name...) # $multi_data - can only be a tuple of column_names at this point # (no nested multirow queries). # Output goal arguments: # $plan - a function taking (db_cursor, starting_keys) that # will return a tuple of dicts (one per row in the # select result). # # No plan is attached to the goal. Instead, it is returned in $plan. get_multi use get_multi($starting_tables, $options, $multi_data, $plan) when $linked_to = dict($options).get('linked_to') $order_by = dict($options).get('order_by') get_multi2($starting_tables, $multi_data, $linked_to, $order_by, $plan) # get_multi2($starting_tables, $multi_data, $linked_to, $order_by, $plan) # # This is a continuation of the get_multi goal (above) with the options pulled # out separately. # # No plan is attached to the goal. Instead, it is returned in $plan. get_multi2_no_linked_to use get_multi2($starting_tables, $multi_data, None, $order_by, $plan) when special.claim_goal() # Try each $unique_table in $starting_tables to find one that # find_multi_from_unique likes. # FIX: This should check for duplicates (ambiguous specification)! $unique_table in $starting_tables find_multi_from_unique($unique_table, $multi_data, $order_by, $plan) get_multi2_linked_to use get_multi2($starting_tables, $multi_data, $linked_to, $order_by, $plan) when # FIX: This should allow $linked_to to be an inherited table # Or should we allow specifying the multi-row table rather than # (in addition to?) the linked_to table? check $linked_to in $starting_tables find_multi_from_unique($linked_to, $multi_data, $order_by, $plan) # find_multi_from_unique($unique_table, $multi_data, $order_by, $plan) # # Finds a many-to-1 relationship between some table X (as the "many" end of # the relationship) and $unique_table (as the "1" end of the relationship) and # then creates a plan to retrieve the columns specified in $multi_data from # that table X. # # No plan is attached to the goal. Instead, it is returned in $plan. find_multi_from_unique use find_multi_from_unique($unique_table, $multi_data, $order_by, $plan) when # FIX: This should check for duplicate possibilities (duplicate # $multi_tables)! schema.many_to_1($multi_table, $unique_table, ($multi_link_column), $_) parse_needed_data(($multi_table), $multi_data, (), $unique_queries) process_multi_queries($unique_queries, $combined_query) run_multi_query($unique_table, $combined_query, $multi_link_column, $order_by) as $plan # process_multi_queries($unique_queries, $combined_query) # # This combines the $unique_queries into a single $combined_query. # # No plan is returned. process_multi_queries_none use process_multi_queries((), ()) process_multi_queries_some use process_multi_queries((($from, $select, $key), *$rest), $combined_query) when $select2 = "%s.%s" % (($from[0] if len($from) == 1 else $from[-1][0]), $select) combine_queries(($from, ($select2), ($key)), $rest, $combined_query, ()) # run_multi_query($unique_table, $combined_query, $multi_link_column, $order_by) # taking (db_cursor, starting_keys) # # This creates a sql select statement to retrieve multi-row data. The first # table in $combined_query must link to $unique_table by $multi_link_column to # generate the multiple rows. # # It generates a plan to execute this sql select statement and create a tuple # of dicts, one per row in the select response. # # The plan returns a tuple of dictionaries run_multi_query use run_multi_query($unique_table, (($table1, *$joins1), $select_columns, $dict_keys), $multi_link_column, $order_by) \ taking (db_cursor, starting_keys) when !make_from_clause($table1, $joins1, $from_clause) command_parameters($param, id, $param_fn) # FIX: Add order_by $sql_command = "select %s\n from %s\n where %s.%s = %s" % \ (', '.join($select_columns), $from_clause, $table1, $multi_link_column, $param) with db_cursor.execute($sql_command, $param_fn(starting_keys[$unique_table])) return tuple(dict(zip($dict_keys, row)) for row in db_cursor.fetchall()) format_params use command_parameters('%s', $_param_name, $param_fn) when schema.paramstyle(format) $param_fn = lambda param, prev_params = (): prev_params + (param,) qmark_params use command_parameters('?', $_param_name, $param_fn) when schema.paramstyle(qmark) $param_fn = lambda param, prev_params = (): prev_params + (param,) bc_extras from pyke import goal links_to = \ goal.compile('schema.links_to($depth, $table, $to_table, $joins)') column_lookup = \ goal.compile('schema.column($to_table, $column, $_, $_, $_, $_, $_)') def find_at_depth1(engine, depth, column, table): got_depth = False ans_joins = [] with links_to.prove(engine, depth=depth, table=table) as gen1: for vars, bogus_plan1 in gen1: to_table, joins = vars['to_table'], vars['joins'] got_depth = True with column_lookup.prove(engine, to_table=to_table, column=column) \ as gen2: for bogus_vars, bogus_plan1 in gen2: ans_joins.append((table,) + joins) return got_depth, tuple(ans_joins) ./pyke-1.1.1/examples/sqlgen/load_sqlite30000755000175000017500000000257211346504626017246 0ustar lambylamby#!/bin/bash # $Id: load_sqlite3 2825a90e9445 2008-12-25 mtnyogi $ # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. usage() { echo "usage: load_sqlite3" >&2 exit 2 } [ $# -eq 0 ] || usage set -e for table in genre movie director movie_director_link catalog do sqlite3 sqlite3.db <<-! .separator "\\t" .import $table.txt $table ! done ./pyke-1.1.1/examples/sqlgen/testall.config0000644000175000017500000000002311346504626017561 0ustar lambylambyexclude README.txt ./pyke-1.1.1/examples/sqlgen/load_mysql_schema.py0000644000175000017500000001232011346504626020763 0ustar lambylamby# $Id: load_mysql_schema.py f163995c2be9 2010-02-25 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ''' This module loads universal facts representing a schema into a pyke engine. All facts are put in the "schema" fact base. It adds five kinds of facts: paramstyle(style) column(table_name, col_name, type, null, key, default, extra) primary_key(table_name, columns) many_to_1(table_many, table_1, table_many_columns, table_1_columns) links_to(depth, start_table, end_table, joins) The many_to_1 facts are determined by column names ending with "_id". The part before "_id" is taken as the table name being referred to with a primary key of ('id'). This module only exports one function: load_schema, which you'd call once at startup after you've created your pyke engine. ''' from __future__ import with_statement import contextlib from pyke import goal debug = False def load_schema(engine, dbi_module, connection): _add_fact(engine, "paramstyle", (dbi_module.paramstyle,)) with contextlib.closing(connection.cursor()) as table_cursor: table_cursor.execute("show tables") with contextlib.closing(connection.cursor()) as column_cursor: for name, in table_cursor.fetchall(): _load_table(engine, column_cursor, name) depth = 1 while _links_to(engine, depth): depth += 1 def _load_table(engine, cursor, table_name): # This doesn't allow sql parameters! cursor.execute("show columns from " + table_name) for col_name, type, null, key, default, extra in cursor.fetchall(): _create_column(engine, table_name, col_name, type, null.upper() == 'YES', key, default, extra) def _create_column(engine, table_name, col_name, type, null, key, default, extra): #null = null.upper() == 'YES' if not key: key = None if not default: default = None if not extra: extra = None _add_fact(engine, "column", (table_name, col_name, type, null, key, default, extra)) if col_name == 'id': _add_fact(engine, "primary_key", (table_name, (col_name,))) if col_name.endswith('_id'): to_table = col_name[:-3] _add_fact(engine, "many_to_1", (table_name, to_table, (col_name,), ('id',))) many_to_1 = goal.compile( 'schema.many_to_1($from_table, $to_table, $from_columns, $to_columns)') links_to = goal.compile( 'schema.links_to($depth, $from_table, $to_table, $joins)') many_to_1_to = goal.compile( 'schema.many_to_1($to_table, $end_table, $to_columns, $end_columns)') def _links_to(engine, depth): ans = False if depth == 1: with many_to_1.prove(engine) as gen1: for vars, bogus_plan in gen1: from_table, to_table, from_columns, to_columns = \ vars['from_table'], vars['to_table'], vars['from_columns'], \ vars['to_columns'] _add_fact(engine, "links_to", (1, from_table, to_table, ((to_table, from_table, from_columns, to_columns),))) ans = True return ans with links_to.prove(engine, depth=depth - 1) as gen2: for vars, bogus_plan1 in gen2: from_table, to_table, joins = \ vars['from_table'], vars['to_table'], vars['joins'] with many_to_1_to.prove(engine, to_table=to_table) as gen3: for vars, bogus_plan2 in gen3: end_table, to_columns, end_columns = \ vars['end_table'], vars['to_columns'], vars['end_columns'] if end_table != from_table and \ not any(end_table == join_clause[0] for join_clause in joins): _add_fact(engine, "links_to", (depth, from_table, end_table, joins + ((end_table, to_table, to_columns, end_columns),))) ans = True return ans def _add_fact(engine, fact, args): if debug: print "schema", fact, args engine.add_universal_fact("schema", fact, args) ./pyke-1.1.1/examples/sqlgen/director.txt0000644000175000017500000000025711346504626017307 0ustar lambylamby1 Stanley Kramer 2 James Cameron 3 Richard Lester 4 Nora Ephron 5 Norman Jewison 6 Val Guest 7 Ken Hughes 8 John Huston 9 Joseph McGrath 10 Robert Parrish 11 Richard Talmadge ./pyke-1.1.1/examples/sqlgen/Schema.png0000644000175000017500000003605611346504626016647 0ustar lambylambyPNG  IHDR#ւmsBITO pHYs IDATx{XWфpY-޵m*]zJ)Xç˷m&|~ZZ5O<~Nۼys@@wRRJ2W"=zO":u ߟ! BU.^8qD2UYY>o$+W|DRRRB}xO? BPL2%''g% d^^VVVYY+ZsŊo.++tBrcӦMr099Y.755gZ[[jkke2Yii@ 8{,~̙3"hԩ}` ??_TdW9sF ?m 4 #T*MSWWZ3,,l׮]7n vXlfPPgg'A6+vuuuPPݻR)^|={m (w 3[[[[q#?0ojVW 5 Ǐ =rA<r1cƐ Im.kIWWP(6ܒ zV}&sݻ|I ?ݭ{{{>oj[HH5I'NH$A677[C֠ ȥKڵkΝIII[CHH%xV^ojK&Z /:kK>5R/288u:B(333##^^v-)) RR4''GTHR)B(--LKK݊6IIIk׮%{>eт̲! \tիWɧ;"##݋ںuرchAm۶ ΞVMxد NWTT6}RVmZ.Azz:DӅFDDzic0VSSSoߎ(%~* A+N<p2|` 0r+0:K|p.uɓ&M~؍+**mk]ŋZ>"c7KF?~ŋG~\ݕ[}ݔ+VBCK=q8}EDDϜ9I5X-,K[XTyVg/6UvfF5553!T]]m[>{.99O>!k9Oj^ӧO;w'111==l%VKʰ"e͔g|w!P(jkk~aSo4 J}Ǐ{xx?~!txS=B=n/X?mhzwttJWb qСgyԸCFfO8l2 7nܸxb .]z!@Pk .^wmh(V0~h*ZX ΀:dj\SCcQپHJ%}}}U*UDDTBr9w_y<^{{;a>)ՆJ-,k<8A\nj\SC)]hϟOĂ n0ֿ>,+V|l}JaE& ibbbLki+..^`Bh…qqqv3iΞ=Kͭ~iIѰϞs(m*ZR0QXLyV gv׮]e3z3o ѕEA\rQmCZ-4iG}4c Ars3SM QZuԖa;`An汜[ %7r8sRsأT+au=J]k^^. BeP2W`p8v xCE&rÓ'OƺGDD߿TA)xaC1VXwWn:uܹs~m JxyyyyEEVE5UsrXeee.]2!9?dVf5c}_q522ܸd*~|QQdRdGԨ*L?,,l׮]7n :?R vk*l( hzk/uqTtMSm*R=ۯ$X^^0~#Gꐺ9YnM X`MRT---V㒩V*ϭ+犳nwttz DbCr}jV5A1V[%%SeJ)ykCbciJJJn޼I_~:nZ ( ,KmtٳgKhKTJ)2q:6d+ZQQ1c o111</662!>T+ݚ+jj8xȭ8QZՀV5uҰ 5y[y[y[y[y[y[y[y[y[y[y[y[y[y[y[y[y[yV8hcun Ck_sFD;v@566&&&73..NPr8ܠ]ߟ! BUrZ%III \ϯ !P( Ŕ)SrrrBA:ֺZL/2l$FgӧOO4vobjro6::!TSS3o޼7o"BBB(P}}}tt4m~̓<O\paƍ* !pz=;ҮC\.BHzyyi4nnn[.k ',YҢ{{{iӟubqcc#n744brL&mܚzꖖlZvwwdV"7񩩩mS`R4''GT*lT/_|ݺuJ-_s뫯:yiӦM0!22!tС 6;~;rϞ=ojl֭bxĉ!!!:^ °|m$vFS#:>>r8;v~;f͚ 6$&&;5&&f`˖-5pI3UVV666+WTThZ[Ɗ+ n߾]VVv%3c-((hhhhjja|jp\x[>??ԌS]]MLB !I>q \.T[[AAAwJxCvEv^UUb\WW555Ԯ>&$$ Ɲ @uttJxC}8 u&!:.?Cmܸqvu r|p||V2Lt: zQ0 Ɲڅaj5nwvvZ-u\D;T*~?OCn@ .8|nJ999JRPdggKRPZZځJ^tCnٔիW WWG4>y'8Ư t@ j5A:.444""Bn銊ܦO^ZZj~tZ}v5iw0??+**f̘BCC|Md5m۶OSSSI6C3C~bcc!G7fggD;wuֱcOqejb ЮF;e,|3!Q5dunuFQ %o^0}rfޑp_8o.&8ͭp]L>ppMwo^z-B/mL8Ld~)I---lǩo~v5k˗َ0 x"hlaZ}nem//vϙ蠠 vBCC0D.}}}q{Jxg&F5 y[y[yΔ[njrq[:ѕbhL!AhZvS,eS s:88b$`V8o8,ȭ<ȭ<ȭ<ȭ<'˭oVs Ĺ~;pXN[     ,Nr+Va9qnVrܪR]nyNc-,pnp8vd?xff&˭QQQ1A Ǜ ù׿s='Nxkzĉ/ƍ˖- A.]nyXX?b?'  ^Ν;׮];Qp(<o``j̼[! z=<O{VCxyyi4 f}O@*(JB-JBiii8x`ZZVZڵkIII6GO5&&&++pX02Lt:^BbbB ZNOO ==]VBCC#""z=:(< 뭍vFqmV/ȭ<ȭ<ȭ<ȭ<ȭ<ȭ<ȭ<ȭ<ȭsLٵkWXXdz0T>c;::p[(JSؘ8n8OOϸ8BaSd6~hm]+O!uF/v؁BP(ZYpb___Drb???Dr)7F[JL&z*. ]|-Y***|rEE\.nLVYYYVVf~ӧO;w'111==줼B=Z*۷Ϝ93gSP( Ŕ)Srrr v X5Ưbq{WZ޿C@kmmd/Ab3~qMr099Y.755]1O,JBppp]]nԠinUVިSONxxxUUnb[U588#":::BJEN)BժP{gT[^!44tpp ^xᣏ>0JIDATh\[m~WZ[wC=$$TAvuuuPPa6K=n;;; gd+[OLo=r\V\.3f BߪJ#ZR7|tÇtZm&?M@iwsU3L;EV}hIsJks,e2n GaC;M x~ᡇ4ޒ%KZZZz}oo/1"]X5Q(dwUӾ7!b|2OlϭIIIk׮U* ";;~h8,\Q__j]`0˗/_n]GGR$/< igϞ7msϙOV{yydUV1yVMkdhqav}WZ?+zꖖ+%Jsrr!R|2O&VSSSoߎ&uֱc"˩#tp77ӧߪk**99iqq, Sm>p^o>cǎEEExȽ{Ͽ6_ji_#SGCNzMwhG&}}}}E"Ν;WJV @V #/ǭUVƽ_iܺgϞ^zɮ!y+ E8|p/aYfMWWW{{ wb;fkVL<9::Z lٲ9FxD??5k{a8.q=YYYv=#FW#?5Y]8oAnAnAnAnAnAnAnAnAn7z5=dkHԟoa1B}===,Q !c΅^ɸ7o[n]tC9^c8 1+..nܸA$뷿ȌK\Vܹsy7sL-ilʔ)#?3=J"o' CCCaKKKssP(deXw$B[S &p̙~-&į#~~~,D{=CW*1^OJ钓NG 5.X <<Zmjjj?Kq-]^.%5N؋!@nEɇ!!!|MPP!*jnmoo~민 Kq[7x|vZ!z;wΆX,f;(uw}kjjAO"'l,[oP(?~ǸqƏv,F=^ gٳSNe7QΝ;SN%K1M4ʕ+^^^Fbbb㇛7o.**b7$G3z |db7nɓ'!0y)Vmm-q | X===ٍzє#ޟ9%eGG}4++|o_b10$ mF>LMM,F5SN=vww> 0T*z'Nr {-h+rkjj _O>$Xy<އ~]|>ze~̠V\d $Vz~6cƌ9|0A_[Ҝ={ ./駟؎Ak?N[/QZ1cYb922~shz#GEN}ܹs===d'ZԆ;:N(&&&~qqq|&ppYW)..H$G-..H$N+gddBPjժ/N8ϧ ADEEUVV@t7oNJJRT \K$=ٵkWXX>hh4"hǎx[Cvb?.nkг`>sLˤK.7qww7 ykppp}}=nQëvXPRxB}|G챧 nMxq)Pvv;w8vll,^!'''>>^./X 77wƌO~7f""ݲe󛛛oݺE؆ qBގG!$JJ>_z饅 655uuueggFl]@=3*^g?S?| uZn߾=&.!Ax<.rO9_pW4\Hjs̹u=viӨlWW~ !I>#\.T[[AAAwJxCvEhA6qK3$$ڧ{8~ ʭƧ/۷㏦z# w@Cn;֠ f-ȭ0J6mZGGB\rgrr2^NGmy<^WWP(!/Zqƹ2m\.VcTn5^!4a„<3{=r|f`.[/_nݺRI~<333##^^v-))ކPP?xӧO4i˖-iӦQ+!ɒ bX$=c>|p||<&e2V'1)sh~C͏b>APϏ?H\=JJNN (..vss#BL>켕vCC^^Ǟ0888sLՔH$h܄B!-Z~z ovEi4*7뭸sdϼ 677wwwY h7ڷ͛111EEEf$?"Fݸqʀo~zdd]H~c{~g}/_>dnU@ j t:]hhhDD[CGNvoJg}fggD;wuVR33[ hkkH$E``Ԃ+))u 7nύ7o)emGL[[ۭ[p+,,ld8;8;mmml0"V= k r: 50r+p2111p8>;օߧ_~%1ԩS:6 b1;&Yȕ+WF'{ݱcڵk/8;{WV[E"̚;w.[|ɂ; 䤾kέT&L Έ,fmw>0a'[X׋Cص܌ײa_"0Sp8v xk4\H P9n[w۶mX,iiiVo߾ww3g!VSRRȘ=tVN=afyCvhjllLLL7ng\\B@-Вͅr077?ZWWW[[+JKKٳggΜDSN5賨rcӦMcW*2իgΜav-鼼BF^PPPUUUQQԄ n0K/AÏp*++^y=}szzznLVYYI孥.COaÆ\P/ZhE{WͯP(rŋq[l?~ss[RSSp<2NTT*MmHk]s=gΜygw9p'۱x+ %I=]0j=Je~ePgg'ATls\BPPPmm-nWWW޽[*%˗/߳gmxx8yOvXljt횚!pj^+vN!j&򐐐Std--w֭4Vͯ itttJxd]WW7d|> QrSoUn##,yhmWT*ߟo$ȭ$7rZ-nk4% ;;;;::|||7x\.$˭`fs+mv6r.hLo<> w߆j><󹕑 r|O=22'LxM6n޼9m4|I$9B7mW.\xGƎp|~GG^d[Y;(X,nll톆X,{Ç>|8>>x@Lju:A=YM .X9m䁁74%EhjdbLdr[:sd{mbK,jii|dfHҜRP(R)B(--LKK*333##^^v-))(IIIk׮%Gav6򔔔իW=?"YSYvB?p֭P*999Voo,Z6_ظq#*ޚ_ׄyaaE4MUULu@]N]3xaٲeodAIpM4ܪt@ :.444""Bn銊ܦO^ZZj~tZ}!p vnji#D;wknݺߧ, 6 mۆ?MMM5L>TT8b77718VkުJ;GG%7cǎEEExȽ{Z[-Y[]ޮ_i~ʭ6*RY2G.ru՝mmm/^n,{[fMWWW{{  #Q.111'O[lj[;Cۡ9{'#KIl寸K~VVVVVmێ:9KXk0         ۷oaPKK .++@NF@ p }}}]r0V02!#%5p8QTcFV`[ǎ[PPl{N8188xuR'Nd;CO $+MIENDB`./pyke-1.1.1/examples/sqlgen/load_sqlite3_schema.py0000644000175000017500000001247611346504626021216 0ustar lambylamby# $Id: load_sqlite3_schema.py f163995c2be9 2010-02-25 mtnyogi $ # coding=utf-8 # # Copyright © 2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ''' This module loads universal facts representing a schema into a pyke engine. All facts are put in the "schema" fact base. It adds five kinds of facts: paramstyle(style) column(table_name, col_name, type, null, key, default, extra) primary_key(table_name, columns) many_to_1(table_many, table_1, table_many_columns, table_1_columns) links_to(depth, start_table, end_table, joins) The many_to_1 facts are determined by column names ending with "_id". The part before "_id" is taken as the table name being referred to with a primary key of ('id'). This module only exports one function: load_schema, which you'd call once at startup after you've created your pyke engine. ''' from __future__ import with_statement import contextlib from pyke import goal debug = False def load_schema(engine, dbi_module, connection): _add_fact(engine, "paramstyle", (dbi_module.paramstyle,)) with contextlib.closing(connection.cursor()) as table_cursor: table_cursor.execute("""select name from sqlite_master where type='table' """) with contextlib.closing(connection.cursor()) as column_cursor: for name, in table_cursor.fetchall(): _load_table(engine, column_cursor, name) depth = 1 while _links_to(engine, depth): depth += 1 def _load_table(engine, cursor, table_name): # This doesn't allow sql parameters! cursor.execute("pragma table_info(%s)" % table_name) for col_num, col_name, type, null_flag, default, key in cursor.fetchall(): _create_column(engine, table_name, col_name, type, null_flag != 99, key, default, None) def _create_column(engine, table_name, col_name, type, null, key, default, extra): #null = null.upper() == 'YES' if not key: key = None if not default: default = None if not extra: extra = None _add_fact(engine, "column", (table_name, col_name, type, null, key, default, extra)) if col_name == 'id': _add_fact(engine, "primary_key", (table_name, (col_name,))) if col_name.endswith('_id'): to_table = col_name[:-3] _add_fact(engine, "many_to_1", (table_name, to_table, (col_name,), ('id',))) many_to_1 = goal.compile( 'schema.many_to_1($from_table, $to_table, $from_columns, $to_columns)') links_to = goal.compile( 'schema.links_to($depth, $from_table, $to_table, $joins)') many_to_1_to = goal.compile( 'schema.many_to_1($to_table, $end_table, $to_columns, $end_columns)') def _links_to(engine, depth): ans = False if depth == 1: with many_to_1.prove(engine) as gen1: for vars, bogus_plan in gen1: from_table, to_table, from_columns, to_columns = \ vars['from_table'], vars['to_table'], vars['from_columns'], \ vars['to_columns'] _add_fact(engine, "links_to", (1, from_table, to_table, ((to_table, from_table, from_columns, to_columns),))) ans = True return ans with links_to.prove(engine, depth=depth - 1) as gen2: for vars, bogus_plan1 in gen2: from_table, to_table, joins = \ vars['from_table'], vars['to_table'], vars['joins'] with many_to_1_to.prove(engine, to_table=to_table) as gen3: for vars, bogus_plan2 in gen3: end_table, to_columns, end_columns = \ vars['end_table'], vars['to_columns'], vars['end_columns'] if end_table != from_table and \ not any(end_table == join_clause[0] for join_clause in joins): _add_fact(engine, "links_to", (depth, from_table, end_table, joins + ((end_table, to_table, to_columns, end_columns),))) ans = True return ans def _add_fact(engine, fact, args): if debug: print "schema", fact, args engine.add_universal_fact("schema", fact, args) ./pyke-1.1.1/examples/sqlgen/movie.txt0000644000175000017500000000033311346504626016606 0ustar lambylamby1 It's a Mad, Mad, Mad, Mad World 1 1963 3:12 2 The Terminator 3 1984 1:48 3 A Funny Thing Happened on the Way to the Forum 1 1966 1:39 4 Sleepless in Seattle 2 1993 1:45 5 Bogus 4 1996 1:50 6 Casino Royale 2 1967 2:11 ./pyke-1.1.1/examples/family_relations/0000755000175000017500000000000011425360453016773 5ustar lambylamby./pyke-1.1.1/examples/family_relations/example.krb0000644000175000017500000002222611346504626021136 0ustar lambylamby# $Id: example.krb 2bb500de1268 2008-09-24 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Since all other relationships depend on child_parent and sibling # relationships, we're just going to go ahead and assert all child_parent and # sibling relationships with forward-chaining rules. If we use # backward-chaining rules for these, we would have to re-run the rules each # time the facts are needed for the same people. This avoids that. # # Establish child_parent relationships: son_of foreach family.son_of($child, $father, $mother) assert family.child_parent($child, $father, father, son) family.child_parent($child, $mother, mother, son) daughter_of foreach family.daughter_of($child, $father, $mother) assert family.child_parent($child, $father, father, daughter) family.child_parent($child, $mother, mother, daughter) # Establish sibling relationships: brothers foreach family.son_of($brother1, $father, $mother) family.son_of($brother2, $father, $mother) check $brother1 != $brother2 assert family.siblings($brother1, $brother2, brother, brother) sisters foreach family.daughter_of($sister1, $father, $mother) family.daughter_of($sister2, $father, $mother) check $sister1 != $sister2 assert family.siblings($sister1, $sister2, sister, sister) brother_and_sister foreach family.son_of($brother, $father, $mother) family.daughter_of($sister, $father, $mother) assert family.siblings($brother, $sister, sister, brother) family.siblings($sister, $brother, brother, sister) facts_for_bc_rules # With no "foreach" clause, this rule will always be triggered just once. # These facts could also be universal facts. # They translate, for example the first fact means that my parent's # "brother" "as an aunt or uncle" (as_au) is my "uncle". # The last fact means that my "daughter" "as a niece or nephew" (as_nn) # is my sibling's "niece". assert family.as_au(brother, uncle) family.as_au(sister, aunt) family.as_nn(son, nephew) family.as_nn(daughter, niece) niece_or_nephew_and_aunt_or_uncle use nn_au($nn, $au, $greats, $au_type, $nn_type) when child_parent($nn, $parent, $depth, $_, $child_type) family.siblings($parent, $au, $sibling_type, $_) family.as_au($sibling_type, $au_type) family.as_nn($child_type, $nn_type) $greats = ('great',) * len($depth) # Note that these are example.child_parent (the subgoal), which are different # than family.child_parent (that facts). These include an extra argument to # handle ('grand',), ('great', 'grand'), etc. parent_and_child use child_parent($child, $parent, (), $parent_type, $child_type) when family.child_parent($child, $parent, $parent_type, $child_type) grand_parent_and_child # Note that a comma is not required (but is allowed) for singleton tuples # in .krb files; in this case, "(grand)". use child_parent($child, $grand_parent, (grand), $parent_type, $child_type) when family.child_parent($child, $parent, $_, $child_type) family.child_parent($parent, $grand_parent, $parent_type, $_) great_grand_parent_and_child use child_parent($child, $grand_parent, (great, $a, *$b), $parent_type, $child_type) when family.child_parent($child, $grand_child, $_, $child_type) # We use "($a, *$b)" in the next premise so that it won't match (). child_parent($grand_child, $grand_parent, ($a, *$b), $parent_type, $_) great_niece_or_nephew_and_aunt_or_uncle use nn_au($younger, $elder, (great, *$greats), $au_type, $nn_type) when family.child_parent($younger, $parent, $_, $younger_type) nn_au($parent, $elder, $greats, $au_type, $_) family.as_nn($younger_type, $nn_type) first_cousins use cousins($cousin1, $cousin2, 1) when family.child_parent($cousin1, $sibling1, $_, $_) family.siblings($sibling1, $sibling2, $_, $_) family.child_parent($cousin2, $sibling2, $_, $_) nth_cousins use cousins($next_cousin1, $next_cousin2, $next_n) when family.child_parent($next_cousin1, $cousin1, $_, $_) cousins($cousin1, $cousin2, $n) family.child_parent($next_cousin2, $cousin2, $_, $_) $next_n = $n + 1 how_related_child_parent use how_related($person1, $person2) when child_parent($person1, $person2, $prefix, $p2_type, $p1_type) add_prefix($prefix) return $$($p1_type, $p2_type) how_related_parent_child use how_related($person1, $person2) when # Note that for how_related(Fixed_name, $variable) that this # subgoal is run "in reverse": # child_parent($variable, Fixed_name, ...) # This is very inefficient the way the following rules were written: # grand_parent_and_child # and great_grand_parent_and_child # It is left as an exercise for the reader to determine how to improve # these rules. Here's a way to check whether a pattern variable is # bound (only 'variable_name' changes with different variables). This # only checks the top-level binding. It does not check whether # subordinate variables in tuples are bound: # check context.is_bound(contexts.variable('variable_name')) child_parent($person2, $person1, $prefix, $p1_type, $p2_type) add_prefix($prefix) return $$($p1_type, $p2_type) how_related_siblings use how_related($person1, $person2) when family.siblings($person1, $person2, $p2_type, $p1_type) with return $p1_type + ', ' + $p2_type how_related_nn_au use how_related($person1, $person2) when nn_au($person1, $person2, $prefix, $p2_type, $p1_type) add_prefix($prefix) return $$($p1_type, $p2_type) how_related_au_nn use how_related($person1, $person2) when # Here is another case where how_related(Fixed_name, $variable) is # very inefficient because of the way the # great_niece_or_nephew_and_aunt_or_uncle rule is written. nn_au($person2, $person1, $prefix, $p1_type, $p2_type) add_prefix($prefix) return $$($p1_type, $p2_type) how_related_cousins use how_related($cousin1, $cousin2) when cousins($cousin1, $cousin2, $n) nth_cousin($n) return $$() how_related_removed_cousins use how_related($removed_cousin1, $cousin2) when child_parent($removed_cousin1, $cousin1, $grand, $_, $_) cousins($cousin1, $cousin2, $n) nth_cousin($n) nth_cousin = $$() # Note that this is assigning to a standard # python variable, NOT a pattern variable! $r1 = len($grand) + 1 # While this is assigning to a pattern variable. with return "%s, %d removed" % (nth_cousin, $r1) # Use of both variables. how_related_cousins_removed use how_related($cousin1, $removed_cousin2) when cousins($cousin1, $cousin2, $n) child_parent($removed_cousin2, $cousin2, $grand, $_, $_) nth_cousin($n) nth_cousin = $$() $r1 = len($grand) + 1 with return "%s, %d removed" % (nth_cousin, $r1) # The nth_cousin goal codes the numbers: 1 is "1st", 2 is "2nd", etc. nth_cousin_th use nth_cousin($n) when check $n % 10 not in (1, 2, 3) or 10 < $n % 100 < 20 special.claim_goal() with return "%dth cousins" % $n nth_cousin_1 use nth_cousin($n) when check $n % 10 == 1 # except 11, which is caught above special.claim_goal() with return "%dst cousins" % $n nth_cousin_2 use nth_cousin($n) when check $n % 10 == 2 # except 12, which is caught above special.claim_goal() with return "%dnd cousins" % $n nth_cousin_3 use nth_cousin($n) when check $n % 10 == 3 # except 13, which is caught above special.claim_goal() with return "%drd cousins" % $n # The add_prefix goal adds ('great', 'grand') prefixes passed to the goal to # the two plan function arguments (x and y) to make a string (when the plan is # run). add_empty_prefix # Empty prefix just uses x and y. use add_prefix(()) taking (x, y) when special.claim_goal() with return x + ', ' + y add_prefix use add_prefix($prefix) taking (x, y) with pre = ' '.join($prefix) + ' ' return pre + x + ', ' + pre + y ./pyke-1.1.1/examples/family_relations/family.kfb0000644000175000017500000001057711346504626020756 0ustar lambylamby# $Id: family.kfb 1053a76196fb 2008-08-10 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # family.daughter_of(daughter, father, mother) # family.son_of(son, father, mother) son_of(arthur2, arthur1, bertha_o) daughter_of(helen, arthur1, bertha_o) daughter_of(roberta, arthur1, bertha_o) daughter_of(gladis, john, bertha_c) daughter_of(sarah_r, john, bertha_c) daughter_of(alice, marshall1, bertha_c) son_of(edmond, marshall1, bertha_c) daughter_of(kathleen, marshall1, bertha_c) daughter_of(darleen, marshall1, bertha_c) daughter_of(birdie, marshall1, bertha_c) son_of(marshall2, marshall1, bertha_c) daughter_of(donna, david_r, sarah_r) son_of(david_r2, david_r, sarah_r) daughter_of(shirley, david_r, sarah_r) daughter_of(sarah_a, val, donna) daughter_of(heidi_m, brian, shirley) daughter_of(holly, brian, shirley) daughter_of(anne, chuck, alice) daughter_of(carol, chuck, alice) son_of(matt, ralph, anne) daughter_of(nanette, arthur2, kathleen) son_of(arthur3, arthur2, kathleen) daughter_of(sue, arthur2, kathleen) son_of(ed, arthur2, kathleen) daughter_of(marilyn, arthur2, kathleen) son_of(david_b, arthur2, kathleen) daughter_of(m_helen, arthur2, kathleen) daughter_of(beverly, michael_b, nanette) daughter_of(dianna, michael_b, nanette) daughter_of(teresa, michael_b, nanette) son_of(nick, paul, nanette) daughter_of(katrina, paul, nanette) son_of(albert2, albert1, dianna) son_of(jack, albert1, dianna) daughter_of(samantha, mike_g, dianna) daughter_of(tiffany, chad, teresa) son_of(tyler, chad, teresa) daughter_of(anita, arthur3, julie) son_of(justin, arthur3, julie) son_of(brian_d, arthur3, julie) daughter_of(debbie, arthur3, julie) son_of(arthur4, arthur3, lisa) daughter_of(rachael, arthur3, lisa) daughter_of(heidi_j, steve, sue) daughter_of(sarah_k, al, sue) daughter_of(dominique, arthur_x, heidi_j) son_of(jacob, frankie, sarah_k) son_of(justin_m, gary, sarah_k) son_of(keith, ed, ann) daughter_of(barbara, ed, ann) son_of(ramon, ed, ann) daughter_of(mary_k, ed, michelle) son_of(m_thomas, bruce, marilyn) son_of(david_a, bruce, marilyn) daughter_of(jackie, david_b, colleen) son_of(corey, david_b, colleen) daughter_of(andrea, david_b, colleen) son_of(alex, greg, jackie) daughter_of(amanda, michael_k, m_helen) daughter_of(tammy, michael_k, m_helen) daughter_of(crystal, michael_k, m_helen) daughter_of(joyce, frederik, mary) daughter_of(phyllis, frederik, mary) son_of(thomas, frederik, mary) son_of(david_c, chris, joyce) daughter_of(dee, chris, joyce) son_of(danny, chris, joyce) daughter_of(norma, allen, ismay) son_of(john_w, allen, ismay) son_of(bill, allen, ismay) son_of(chuck_w, allen, ismay) daughter_of(jonni, john_w, cleo) daughter_of(lorri, john_w, cleo) son_of(mitch, john_w, cleo) daughter_of(charli, joseph, jonni) son_of(steve_w, bill, elvina) son_of(jim, bill, elvina) daughter_of(jeri, bill, elvina) daughter_of(annette, bill, elvina) daughter_of(helen_w, bill, elvina) daughter_of(mary_w, bill, elvina) daughter_of(jamie, jim, sandy_w) son_of(jimjim, jim, sandy_w) son_of(johnjohn, jim, sandy_w) son_of(david_w, bob, annette) daughter_of(jessica, bob, annette) daughter_of(bridget, bob, annette) daughter_of(victoria, brian_w, helen_w) son_of(brian2, brian_w, helen_w) son_of(bruce, thomas, norma) son_of(fred_a, thomas, norma) son_of(tim, thomas, norma) daughter_of(vicki, thomas, norma) daughter_of(jill, thomas, norma) ./pyke-1.1.1/examples/family_relations/fc_example.krb0000644000175000017500000002017311346504626021605 0ustar lambylamby# $Id: fc_example.krb 2bb500de1268 2008-09-24 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Since all other relationships depend on child_parent and sibling # relationships, we're just going to go ahead and assert all child_parent and # sibling relationships with forward-chaining rules. If we use # backward-chaining rules for these, we would have to re-run the rules each # time the facts are needed for the same people. This avoids that. # # Establish child_parent relationships: son_of foreach family.son_of($child, $father, $mother) assert family.child_parent($child, $father, father, son) family.child_parent($child, $mother, mother, son) daughter_of foreach family.daughter_of($child, $father, $mother) assert family.child_parent($child, $father, father, daughter) family.child_parent($child, $mother, mother, daughter) # Establish sibling relationships: brothers foreach family.son_of($brother1, $father, $mother) family.son_of($brother2, $father, $mother) check $brother1 != $brother2 assert family.siblings($brother1, $brother2, brother, brother) sisters foreach family.daughter_of($sister1, $father, $mother) family.daughter_of($sister2, $father, $mother) check $sister1 != $sister2 assert family.siblings($sister1, $sister2, sister, sister) brother_and_sister foreach family.son_of($brother, $father, $mother) family.daughter_of($sister, $father, $mother) assert family.siblings($brother, $sister, sister, brother) family.siblings($sister, $brother, brother, sister) facts_for_bc_rules # With no "foreach" clause, this rule will always be triggered just once. # These facts could also be universal facts. # They translate, for example the first fact means that my parent's # "brother" "as an aunt or uncle" (as_au) is my "uncle". # The last fact means that my "daughter" "as a niece or nephew" (as_nn) # is my sibling's "niece". assert family.as_au(brother, uncle) family.as_au(sister, aunt) family.as_nn(son, nephew) family.as_nn(daughter, niece) niece_or_nephew_and_aunt_or_uncle foreach family.child_parent($nn, $parent, $depth, $_, $child_type) family.siblings($parent, $au, $sibling_type, $_) family.as_au($sibling_type, $au_type) family.as_nn($child_type, $nn_type) $greats = ('great',) * len($depth) assert family.nn_au($nn, $au, $greats, $au_type, $nn_type) # Note that these family.child_parent have an extra argument to handle # ('grand',), ('great', 'grand'), etc. parent_and_child foreach family.child_parent($child, $parent, $parent_type, $child_type) assert family.child_parent($child, $parent, (), $parent_type, $child_type) grand_parent_and_child foreach family.child_parent($child, $parent, $_, $child_type) family.child_parent($parent, $grand_parent, $parent_type, $_) assert # Note that a comma is not required (but is allowed) for singleton # tuples in .krb files; in this case, "(grand)". family.child_parent($child, $grand_parent, (grand), $parent_type, $child_type) great_grand_parent_and_child foreach family.child_parent($child, $grand_child, $_, $child_type) # We use "($a, *$b)" in the next premise so that it won't match (). family.child_parent($grand_child, $grand_parent, ($a, *$b), $parent_type, $_) assert family.child_parent($child, $grand_parent, (great, $a, *$b), $parent_type, $child_type) first_cousins foreach family.child_parent($cousin1, $sibling1, $_, $_) family.siblings($sibling1, $sibling2, $_, $_) family.child_parent($cousin2, $sibling2, $_, $_) assert family.cousins($cousin1, $cousin2, 1) nth_cousins foreach family.child_parent($next_cousin1, $cousin1, $_, $_) family.cousins($cousin1, $cousin2, $n) family.child_parent($next_cousin2, $cousin2, $_, $_) $next_n = $n + 1 assert family.cousins($next_cousin1, $next_cousin2, $next_n) how_related_child_parent foreach family.child_parent($person1, $person2, $prefix, $p2_type, $p1_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) assert family.how_related($person1, $person2, $relationship) how_related_parent_child foreach # Note that for how_related(Fixed_name, $variable) that this # subgoal is run "in reverse": # child_parent($variable, Fixed_name, ...) # This is very inefficient the way the following rules were written: # grand_parent_and_child # and great_grand_parent_and_child # It is left as an exercise for the reader to determine how to improve # these rules. Here's a way to check whether a pattern variable is # bound (only 'variable_name' changes with different variables). This # only checks the top-level binding. It does not check whether # subordinate variables in tuples are bound: # check context.is_bound(contexts.variable('variable_name')) family.child_parent($person2, $person1, $prefix, $p1_type, $p2_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) assert family.how_related($person1, $person2, $relationship) how_related_siblings foreach family.siblings($person1, $person2, $p2_type, $p1_type) assert family.how_related($person1, $person2, ($p1_type, $p2_type)) how_related_nn_au foreach family.nn_au($person1, $person2, $prefix, $p2_type, $p1_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) assert family.how_related($person1, $person2, $relationship) how_related_au_nn foreach # Here is another case where how_related(Fixed_name, $variable) is # very inefficient because of the way the # great_niece_or_nephew_and_aunt_or_uncle rule is written. family.nn_au($person2, $person1, $prefix, $p1_type, $p2_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) assert family.how_related($person1, $person2, $relationship) how_related_cousins foreach family.cousins($cousin1, $cousin2, $n) $nth = nth($n) assert family.how_related($cousin1, $cousin2, ($nth, cousins)) how_related_removed_cousins foreach family.child_parent($removed_cousin1, $cousin1, $grand, $_, $_) family.cousins($cousin1, $cousin2, $n) $nth = nth($n) $r1 = len($grand) + 1 assert family.how_related($removed_cousin1, $cousin2, ($nth, cousins, $r1, removed)) how_related_cousins_removed foreach family.cousins($cousin1, $cousin2, $n) family.child_parent($removed_cousin2, $cousin2, $grand, $_, $_) $nth = nth($n) $r1 = len($grand) + 1 assert family.how_related($cousin1, $removed_cousin2, ($nth, cousins, $r1, removed)) fc_extras def nth(n): if n % 10 not in (1, 2, 3) or 10 < n % 100 < 20: return "%dth" % n if n % 10 == 1: return "%dst" % n if n % 10 == 2: return "%dnd" % n if n % 10 == 3: return "%drd" % n def add_prefix(prefix, x, y): if not prefix: return (x, y) return (prefix + (x,), prefix + (y,)) ./pyke-1.1.1/examples/family_relations/README.txt0000644000175000017500000001067011354261556020502 0ustar lambylambyThis example determines the relationships between people. family.py This has the primary data used by the rest of the rules. This data is established as universal facts so that it remains after an engine.reset() is done. fc_example.krb Forward-chaining example. This only uses forward-chaining rules, which means that all possible relationships are determined when the rule base is activated. bc_example.krb Backward-chaining example. This only uses backward-chaining rules, though these rules are not very efficient. bc2_example.krb Backward-chaining example. This also only uses backward-chaining rules, but a few rule optimizations have been made which results in this rule base running 100 times faster than bc_example.krb. example.krb Combined rule base. This has some forward-chaining rules, some (unoptimized) backward-chaining rules, and produces plans that return the relationship when run. (This is a poor use of plans, but demonstrates the syntax and underlying principles). driver.py Driver program. Read this code to see how to call Pyke! Run: >>> import driver # All of the *test functions below default to 'bruce', but I won't use # that here because it generates a lot of output! # uses fc_example.krb >>> driver.fc_test('michael_k') # doctest: +ELLIPSIS doing proof michael_k, amanda are ('father', 'daughter') michael_k, tammy are ('father', 'daughter') michael_k, crystal are ('father', 'daughter') done family: 9 fact names, 94 universal facts, 6920 case_specific facts fc_example: 20 fc_rules, 6772 triggered, 892 rerun fc_example: 0 bc_rules, 0 goals, 0 rules matched 0 successes, 0 failures fc time ..., ... asserts/sec # uses bc_example.krb >>> driver.bc_test('gary') # doctest: +ELLIPSIS doing proof gary, justin_m are ('father', 'son') done bc_example: 0 fc_rules, 0 triggered, 0 rerun bc_example: 26 bc_rules, 5034 goals, 17901 rules matched 3563 successes, 17901 failures family: 9 fact names, 94 universal facts, 0 case_specific facts bc time ..., ... goals/sec # uses bc2_example.krb >>> driver.bc2_test('chad') # doctest: +ELLIPSIS doing proof chad, tyler are ('father', 'son') chad, tiffany are ('father', 'daughter') done bc2_example: 0 fc_rules, 0 triggered, 0 rerun bc2_example: 29 bc_rules, 35 goals, 140 rules matched 10 successes, 140 failures family: 9 fact names, 94 universal facts, 0 case_specific facts bc time ..., ... goals/sec # uses example.krb >>> driver.test('paul') # doctest: +ELLIPSIS doing proof paul, nick are father, son paul, katrina are father, daughter done example: 6 fc_rules, 262 triggered, 0 rerun example: 21 bc_rules, 2828 goals, 6221 rules matched 1414 successes, 6221 failures family: 9 fact names, 94 universal facts, 422 case_specific facts fc time ..., ... asserts/sec bc time ..., ... goals/sec total time ... # this has three parameters that all default to None: # person1, person2 and relationship >>> driver.general(person1='bruce', # uses bc2_example.krb ... relationship=('father', 'son')) # doctest: +ELLIPSIS doing proof bruce, m_thomas are ('father', 'son') bruce, david_a are ('father', 'son') done bc2_example: 0 fc_rules, 0 triggered, 0 rerun bc2_example: 29 bc_rules, 105 goals, 390 rules matched 82 successes, 390 failures family: 9 fact names, 94 universal facts, 0 case_specific facts bc time ... goals/sec The last function uses the bc2_example rule base. You can pass whatever combinations of values you like. If you want to specify person1 and/or person2, pass their name; otherwise any person will match. For the relationship, pass a tuple. Use '$var_name' strings in the tuple for pattern variables. You can also have nested tuples (as some of the relationships are nested tuples). ./pyke-1.1.1/examples/family_relations/bc2_example.krb0000644000175000017500000002053011346504626021660 0ustar lambylamby# $Id: bc2_example.krb 2bb500de1268 2008-09-24 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. father_son use child_parent($child, $father, father, son) when family.son_of($child, $father, $mother) mother_son use child_parent($child, $mother, mother, son) when family.son_of($child, $father, $mother) father_daughter use child_parent($child, $father, father, daughter) when family.daughter_of($child, $father, $mother) mother_daughter use child_parent($child, $mother, mother, daughter) when family.daughter_of($child, $father, $mother) # Establish sibling relationships: brothers use siblings($brother1, $brother2, brother, brother) when family.son_of($brother1, $father, $mother) family.son_of($brother2, $father, $mother) check $brother1 != $brother2 sisters use siblings($sister1, $sister2, sister, sister) when family.daughter_of($sister1, $father, $mother) family.daughter_of($sister2, $father, $mother) check $sister1 != $sister2 brother_sister use siblings($sister, $brother, brother, sister) when family.daughter_of($sister, $father, $mother) family.son_of($brother, $father, $mother) sister_brother use siblings($brother, $sister, sister, brother) when family.son_of($brother, $father, $mother) family.daughter_of($sister, $father, $mother) as_au_brother_uncle use as_au(brother, uncle) as_au_sister_aunt use as_au(sister, aunt) as_nn_son_nephew use as_nn(son, nephew) as_nn_daughter_niece use as_nn(daughter, niece) niece_or_nephew_and_aunt_or_uncle_1 use nn_au($nn, $au, $greats, $au_type, $nn_type) when check context.is_bound(contexts.variable('nn')) child_parent($nn, $parent, $depth, $_, $child_type) siblings($parent, $au, $sibling_type, $_) as_au($sibling_type, $au_type) as_nn($child_type, $nn_type) $greats = ('great',) * len($depth) niece_or_nephew_and_aunt_or_uncle_2 use nn_au($nn, $au, $greats, $au_type, $nn_type) when check not context.is_bound(contexts.variable('nn')) siblings($au, $parent, $_, $sibling_type) child_parent($nn, $parent, $depth, $_, $child_type) as_au($sibling_type, $au_type) as_nn($child_type, $nn_type) $greats = ('great',) * len($depth) # Note that these child_parent have an extra argument to handle # ('grand',), ('great', 'grand'), etc. parent_and_child use child_parent($child, $parent, (), $parent_type, $child_type) when child_parent($child, $parent, $parent_type, $child_type) grand_parent_and_child_1 # Note that a comma is not required (but is allowed) for singleton # tuples in .krb files; in this case, "(grand)". use child_parent($child, $grand_parent, (grand), $parent_type, $child_type) when check context.is_bound(contexts.variable('child')) child_parent($child, $parent, $_, $child_type) child_parent($parent, $grand_parent, $parent_type, $_) grand_parent_and_child_2 # Note that a comma is not required (but is allowed) for singleton # tuples in .krb files; in this case, "(grand)". use child_parent($child, $grand_parent, (grand), $parent_type, $child_type) when check not context.is_bound(contexts.variable('child')) child_parent($parent, $grand_parent, $parent_type, $_) child_parent($child, $parent, $_, $child_type) great_grand_parent_and_child_1 use child_parent($child, $grand_parent, (great, $a, *$b), $parent_type, $child_type) when check context.is_bound(contexts.variable('child')) child_parent($child, $grand_child, $_, $child_type) # We use "($a, *$b)" in the next premise so that it won't match (). child_parent($grand_child, $grand_parent, ($a, *$b), $parent_type, $_) great_grand_parent_and_child_2 use child_parent($child, $grand_parent, (great, $a, *$b), $parent_type, $child_type) when check not context.is_bound(contexts.variable('child')) child_parent($parent, $grand_parent, $parent_type, $_) # We use "($a, *$b)" in the next premise so that it won't match (). child_parent($child, $parent, ($a, *$b), $_, $child_type) first_cousins use cousins($cousin1, $cousin2, 1) when child_parent($cousin1, $sibling1, $_, $_) siblings($sibling1, $sibling2, $_, $_) child_parent($cousin2, $sibling2, $_, $_) nth_cousins use cousins($next_cousin1, $next_cousin2, $next_n) when child_parent($next_cousin1, $cousin1, $_, $_) cousins($cousin1, $cousin2, $n) child_parent($next_cousin2, $cousin2, $_, $_) $next_n = $n + 1 how_related_child_parent use how_related($person1, $person2, $relationship) when child_parent($person1, $person2, $prefix, $p2_type, $p1_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_parent_child use how_related($person1, $person2, $relationship) when # Note that for how_related(Fixed_name, $variable) that this # subgoal is run "in reverse": # child_parent($variable, Fixed_name, ...) # This is very inefficient the way the following rules were written: # grand_parent_and_child # and great_grand_parent_and_child # It is left as an exercise for the reader to determine how to improve # these rules. Here's a way to check whether a pattern variable is # bound (only 'variable_name' changes with different variables). This # only checks the top-level binding. It does not check whether # subordinate variables in tuples are bound: # check context.is_bound(contexts.variable('variable_name')) child_parent($person2, $person1, $prefix, $p1_type, $p2_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_siblings use how_related($person1, $person2, ($p1_type, $p2_type)) when siblings($person1, $person2, $p2_type, $p1_type) how_related_nn_au use how_related($person1, $person2, $relationship) when nn_au($person1, $person2, $prefix, $p2_type, $p1_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_au_nn use how_related($person1, $person2, $relationship) when # Here is another case where how_related(Fixed_name, $variable) is # very inefficient because of the way the # great_niece_or_nephew_and_aunt_or_uncle rule is written. nn_au($person2, $person1, $prefix, $p1_type, $p2_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_cousins use how_related($cousin1, $cousin2, ($nth, cousins)) when cousins($cousin1, $cousin2, $n) $nth = nth($n) how_related_removed_cousins use how_related($removed_cousin1, $cousin2, ($nth, cousins, $r1, removed)) when child_parent($removed_cousin1, $cousin1, $grand, $_, $_) cousins($cousin1, $cousin2, $n) $nth = nth($n) $r1 = len($grand) + 1 how_related_cousins_removed use how_related($cousin1, $removed_cousin2, ($nth, cousins, $r1, removed)) when cousins($cousin1, $cousin2, $n) child_parent($removed_cousin2, $cousin2, $grand, $_, $_) $nth = nth($n) $r1 = len($grand) + 1 bc_extras def nth(n): if n % 10 not in (1, 2, 3) or 10 < n % 100 < 20: return "%dth" % n if n % 10 == 1: return "%dst" % n if n % 10 == 2: return "%dnd" % n if n % 10 == 3: return "%drd" % n def add_prefix(prefix, x, y): if not prefix: return (x, y) return (prefix + (x,), prefix + (y,)) ./pyke-1.1.1/examples/family_relations/driver.py0000644000175000017500000001756011354651516020655 0ustar lambylamby# $Id: driver.py 6de8ee4e7d2d 2010-03-29 mtnyogi $ # coding=utf-8 # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ''' This example shows how people are related. The primary data (facts) that are used to figure everything out are in family.kfb. There are four independent rule bases that all do the same thing. The fc_example rule base only uses forward-chaining rules. The bc_example rule base only uses backward-chaining rules. The bc2_example rule base also only uses backward-chaining rules, but with a few optimizations that make it run 100 times faster than bc_example. And the example rule base uses all three (though it's a poor use of plans). Once the pyke engine is created, all the rule bases loaded and all the primary data established as universal facts; there are five functions that can be used to run each of the three rule bases: fc_test, bc_test, bc2_test, test and general. ''' from __future__ import with_statement import contextlib import sys import time from pyke import knowledge_engine, krb_traceback, goal # Compile and load .krb files in same directory that I'm in (recursively). engine = knowledge_engine.engine(__file__) fc_goal = goal.compile('family.how_related($person1, $person2, $relationship)') def fc_test(person1 = 'bruce'): ''' This function runs the forward-chaining example (fc_example.krb). ''' engine.reset() # Allows us to run tests multiple times. start_time = time.time() engine.activate('fc_example') # Runs all applicable forward-chaining rules. fc_end_time = time.time() fc_time = fc_end_time - start_time print "doing proof" with fc_goal.prove(engine, person1=person1) as gen: for vars, plan in gen: print "%s, %s are %s" % \ (person1, vars['person2'], vars['relationship']) prove_time = time.time() - fc_end_time print print "done" engine.print_stats() print "fc time %.2f, %.0f asserts/sec" % \ (fc_time, engine.get_kb('family').get_stats()[2] / fc_time) def bc_test(person1 = 'bruce'): engine.reset() # Allows us to run tests multiple times. start_time = time.time() engine.activate('bc_example') fc_end_time = time.time() fc_time = fc_end_time - start_time print "doing proof" try: with engine.prove_goal( 'bc_example.how_related($person1, $person2, $relationship)', person1=person1) \ as gen: for vars, plan in gen: print "%s, %s are %s" % \ (person1, vars['person2'], vars['relationship']) except StandardError: # This converts stack frames of generated python functions back to the # .krb file. krb_traceback.print_exc() sys.exit(1) prove_time = time.time() - fc_end_time print print "done" engine.print_stats() print "bc time %.2f, %.0f goals/sec" % \ (prove_time, engine.get_kb('bc_example').num_prove_calls / prove_time) def bc2_test(person1 = 'bruce'): engine.reset() # Allows us to run tests multiple times. start_time = time.time() engine.activate('bc2_example') fc_end_time = time.time() fc_time = fc_end_time - start_time print "doing proof" try: with engine.prove_goal( 'bc2_example.how_related($person1, $person2, $relationship)', person1=person1) \ as gen: for vars, plan in gen: print "%s, %s are %s" % \ (person1, vars['person2'], vars['relationship']) except StandardError: # This converts stack frames of generated python functions back to the # .krb file. krb_traceback.print_exc() sys.exit(1) prove_time = time.time() - fc_end_time print print "done" engine.print_stats() print "bc time %.2f, %.0f goals/sec" % \ (prove_time, engine.get_kb('bc2_example').num_prove_calls / prove_time) def test(person1 = 'bruce'): engine.reset() # Allows us to run tests multiple times. # Also runs all applicable forward-chaining rules. start_time = time.time() engine.activate('example') fc_end_time = time.time() fc_time = fc_end_time - start_time print "doing proof" try: # In this case, the relationship is returned when you run the plan. with engine.prove_goal( 'example.how_related($person1, $person2)', person1=person1) \ as gen: for vars, plan in gen: print "%s, %s are %s" % (person1, vars['person2'], plan()) except StandardError: # This converts stack frames of generated python functions back to the # .krb file. krb_traceback.print_exc() sys.exit(1) prove_time = time.time() - fc_end_time print print "done" engine.print_stats() print "fc time %.2f, %.0f asserts/sec" % \ (fc_time, engine.get_kb('family').get_stats()[2] / fc_time) print "bc time %.2f, %.0f goals/sec" % \ (prove_time, engine.get_kb('example').num_prove_calls / prove_time) print "total time %.2f" % (fc_time + prove_time) # Need some extra goodies for general()... from pyke import contexts, pattern def general(person1 = None, person2 = None, relationship = None): engine.reset() # Allows us to run tests multiple times. start_time = time.time() engine.activate('bc2_example') # same rule base as bc2_test() fc_end_time = time.time() fc_time = fc_end_time - start_time print "doing proof" args = {} if person1: args['person1'] = person1 if person2: args['person2'] = person2 if relationship: args['relationship'] = relationship try: with engine.prove_goal( 'bc2_example.how_related($person1, $person2, $relationship)', **args ) as gen: for vars, plan in gen: print "%s, %s are %s" % (vars['person1'], vars['person2'], vars['relationship']) except StandardError: # This converts stack frames of generated python functions back to the # .krb file. krb_traceback.print_exc() sys.exit(1) prove_time = time.time() - fc_end_time print print "done" engine.print_stats() print "bc time %.2f, %.0f goals/sec" % \ (prove_time, engine.get_kb('bc2_example').num_prove_calls / prove_time) import types def make_pattern(x): if isinstance(x, types.StringTypes): if x[0] == '$': return contexts.variable(x[1:]) return pattern.pattern_literal(x) if isinstance(x, (tuple, list)): return pattern.pattern_tuple(tuple(make_pattern(element) for element in x)) return pattern.pattern_literal(x) ./pyke-1.1.1/examples/family_relations/bc_example.krb0000644000175000017500000001602011346504626021575 0ustar lambylamby# $Id: bc_example.krb 2bb500de1268 2008-09-24 mtnyogi $ # # Copyright © 2007-2008 Bruce Frederiksen # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. father_son use child_parent($child, $father, father, son) when family.son_of($child, $father, $mother) mother_son use child_parent($child, $mother, mother, son) when family.son_of($child, $father, $mother) father_daughter use child_parent($child, $father, father, daughter) when family.daughter_of($child, $father, $mother) mother_daughter use child_parent($child, $mother, mother, daughter) when family.daughter_of($child, $father, $mother) # Establish sibling relationships: brothers use siblings($brother1, $brother2, brother, brother) when family.son_of($brother1, $father, $mother) family.son_of($brother2, $father, $mother) check $brother1 != $brother2 sisters use siblings($sister1, $sister2, sister, sister) when family.daughter_of($sister1, $father, $mother) family.daughter_of($sister2, $father, $mother) check $sister1 != $sister2 brother_sister use siblings($sister, $brother, brother, sister) when family.daughter_of($sister, $father, $mother) family.son_of($brother, $father, $mother) sister_brother use siblings($brother, $sister, sister, brother) when family.son_of($brother, $father, $mother) family.daughter_of($sister, $father, $mother) as_au_brother_uncle use as_au(brother, uncle) as_au_sister_aunt use as_au(sister, aunt) as_nn_son_nephew use as_nn(son, nephew) as_nn_daughter_niece use as_nn(daughter, niece) niece_or_nephew_and_aunt_or_uncle use nn_au($nn, $au, $greats, $au_type, $nn_type) when child_parent($nn, $parent, $depth, $_, $child_type) siblings($parent, $au, $sibling_type, $_) as_au($sibling_type, $au_type) as_nn($child_type, $nn_type) $greats = ('great',) * len($depth) # Note that these child_parent have an extra argument to handle # ('grand',), ('great', 'grand'), etc. parent_and_child use child_parent($child, $parent, (), $parent_type, $child_type) when child_parent($child, $parent, $parent_type, $child_type) grand_parent_and_child # Note that a comma is not required (but is allowed) for singleton # tuples in .krb files; in this case, "(grand)". use child_parent($child, $grand_parent, (grand), $parent_type, $child_type) when child_parent($child, $parent, $_, $child_type) child_parent($parent, $grand_parent, $parent_type, $_) great_grand_parent_and_child use child_parent($child, $grand_parent, (great, $a, *$b), $parent_type, $child_type) when child_parent($child, $grand_child, $_, $child_type) # We use "($a, *$b)" in the next premise so that it won't match (). child_parent($grand_child, $grand_parent, ($a, *$b), $parent_type, $_) first_cousins use cousins($cousin1, $cousin2, 1) when child_parent($cousin1, $sibling1, $_, $_) siblings($sibling1, $sibling2, $_, $_) child_parent($cousin2, $sibling2, $_, $_) nth_cousins use cousins($next_cousin1, $next_cousin2, $next_n) when child_parent($next_cousin1, $cousin1, $_, $_) cousins($cousin1, $cousin2, $n) child_parent($next_cousin2, $cousin2, $_, $_) $next_n = $n + 1 how_related_child_parent use how_related($person1, $person2, $relationship) when child_parent($person1, $person2, $prefix, $p2_type, $p1_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_parent_child use how_related($person1, $person2, $relationship) when # Note that for how_related(Fixed_name, $variable) that this # subgoal is run "in reverse": # child_parent($variable, Fixed_name, ...) # This is very inefficient the way the following rules were written: # grand_parent_and_child # and great_grand_parent_and_child # It is left as an exercise for the reader to determine how to improve # these rules. Here's a way to check whether a pattern variable is # bound (only 'variable_name' changes with different variables). This # only checks the top-level binding. It does not check whether # subordinate variables in tuples are bound: # check context.is_bound(contexts.variable('variable_name')) child_parent($person2, $person1, $prefix, $p1_type, $p2_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_siblings use how_related($person1, $person2, ($p1_type, $p2_type)) when siblings($person1, $person2, $p2_type, $p1_type) how_related_nn_au use how_related($person1, $person2, $relationship) when nn_au($person1, $person2, $prefix, $p2_type, $p1_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_au_nn use how_related($person1, $person2, $relationship) when # Here is another case where how_related(Fixed_name, $variable) is # very inefficient because of the way the # great_niece_or_nephew_and_aunt_or_uncle rule is written. nn_au($person2, $person1, $prefix, $p1_type, $p2_type) $relationship = add_prefix($prefix, $p1_type, $p2_type) how_related_cousins use how_related($cousin1, $cousin2, ($nth, cousins)) when cousins($cousin1, $cousin2, $n) $nth = nth($n) how_related_removed_cousins use how_related($removed_cousin1, $cousin2, ($nth, cousins, $r1, removed)) when child_parent($removed_cousin1, $cousin1, $grand, $_, $_) cousins($cousin1, $cousin2, $n) $nth = nth($n) $r1 = len($grand) + 1 how_related_cousins_removed use how_related($cousin1, $removed_cousin2, ($nth, cousins, $r1, removed)) when cousins($cousin1, $cousin2, $n) child_parent($removed_cousin2, $cousin2, $grand, $_, $_) $nth = nth($n) $r1 = len($grand) + 1 bc_extras def nth(n): if n % 10 not in (1, 2, 3) or 10 < n % 100 < 20: return "%dth" % n if n % 10 == 1: return "%dst" % n if n % 10 == 2: return "%dnd" % n if n % 10 == 3: return "%drd" % n def add_prefix(prefix, x, y): if not prefix: return (x, y) return (prefix + (x,), prefix + (y,)) ./pyke-1.1.1/examples/testall.config0000644000175000017500000000002211346504626016267 0ustar lambylambyexclude-suffix py ./pyke-1.1.1/examples/notany/0000755000175000017500000000000011425360453014742 5ustar lambylamby./pyke-1.1.1/examples/notany/bc_notany.krb0000644000175000017500000000400111346504626017415 0ustar lambylamby# bc_notany.krb brothers use siblings($brother1, $brother2, brother, brother) when family.son_of($brother1, $father, $mother) family.son_of($brother2, $father, $mother) check $brother1 != $brother2 sisters use siblings($sister1, $sister2, sister, sister) when family.daughter_of($sister1, $father, $mother) family.daughter_of($sister2, $father, $mother) check $sister1 != $sister2 brother_sister use siblings($sister, $brother, brother, sister) when family.daughter_of($sister, $father, $mother) family.son_of($brother, $father, $mother) sister_brother use siblings($brother, $sister, sister, brother) when family.son_of($brother, $father, $mother) family.daughter_of($sister, $father, $mother) no_parent use child_with_no_parent($child) when notany family.daughter_of($child, $_, $_) notany family.son_of($child, $_, $_) no_uncle_1 use child_with_no_uncle($child) when family.son_of($child, $father, $mother) notany siblings($father, $uncle1, $gender, $_) check $gender == 'brother' notany siblings($mother, $uncle2, $gender, $_) check $gender == 'brother' no_uncle_2 use child_with_no_uncle($child) when family.daughter_of($child, $father, $mother) notany siblings($father, $uncle1, $gender, $_) check $gender == 'brother' notany siblings($mother, $uncle2, $gender, $_) check $gender == 'brother' no_aunt_1 use child_with_no_aunt($child) when family.son_of($child, $father, $mother) notany siblings($father, $aunt1, sister, $_) notany siblings($mother, $aunt2, sister, $_) no_aunt_2 use child_with_no_aunt($child) when family.daughter_of($child, $father, $mother) notany siblings($father, $aunt1, sister, $_) notany siblings($mother, $aunt2, sister, $_) ./pyke-1.1.1/examples/notany/fc_notany.krb0000644000175000017500000000422011346504626017424 0ustar lambylamby# fc_notany.krb brothers foreach family.son_of($brother1, $father, $mother) family.son_of($brother2, $father, $mother) check $brother1 != $brother2 assert family.siblings($brother1, $brother2, brother, brother) sisters foreach family.daughter_of($sister1, $father, $mother) family.daughter_of($sister2, $father, $mother) check $sister1 != $sister2 assert family.siblings($sister1, $sister2, sister, sister) brother_sister foreach family.daughter_of($sister, $father, $mother) family.son_of($brother, $father, $mother) assert family.siblings($sister, $brother, brother, sister) sister_brother foreach family.son_of($brother, $father, $mother) family.daughter_of($sister, $father, $mother) assert family.siblings($brother, $sister, sister, brother) no_uncle_1 foreach family.son_of($child, $father, $mother) notany family.siblings($father, $uncle1, $gender1, $_) check $gender1 == 'brother' notany family.siblings($mother, $uncle2, $gender2, $_) check $gender2 == 'brother' assert python print $child, "has no uncle" no_uncle_2 foreach family.daughter_of($child, $father, $mother) notany family.siblings($father, $uncle1, $gender, $_) check $gender == 'brother' notany family.siblings($mother, $uncle2, $gender, $_) check $gender == 'brother' assert python print $child, "has no uncle" no_aunt_1 foreach family.son_of($child, $father, $mother) notany family.siblings($father, $aunt1, sister, $_) notany family.siblings($mother, $aunt2, sister, $_) assert python print $child, "has no aunt" no_aunt_2 foreach family.daughter_of($child, $father, $mother) notany family.siblings($father, $aunt1, sister, $_) notany family.siblings($mother, $aunt2, sister, $_) assert python print $child, "has no aunt" ./pyke-1.1.1/examples/notany/family.kfb0000644000175000017500000000032411346504626016712 0ustar lambylamby# family.kfb son_of(egon, anton, brigitte) son_of(ralf, anton, brigitte) son_of(anton, johann, maria) daughter_of(elisabeth, johann, maria) daughter_of(karin, karl, margit) daughter_of(sabine, karl, margit) ./pyke-1.1.1/examples/notany/README.txt0000644000175000017500000000175611354261612016447 0ustar lambylambyThis is a small example of the 'notany' clause used to verify that none of the elements of a list meet a certain requirement. This is done in both forward-chaining and backward-chaining rules. The forward-chaining and backward-chaining rules are in two different .krb files showing examples of use of the 'notany' clause in both cases. These rules find all people who have no aunts all people with no uncles. >>> import driver # uses fc_notany.krb >>> driver.fc_test() egon has no uncle ralf has no uncle anton has no uncle elisabeth has no uncle karin has no uncle sabine has no uncle anton has no aunt elisabeth has no aunt karin has no aunt sabine has no aunt # uses bc_notany.krb >>> driver.bc_test() anton has no aunt elisabeth has no aunt karin has no aunt sabine has no aunt egon has no uncle ralf has no uncle anton has no uncle elisabeth has no uncle karin has no uncle sabine has no uncle ./pyke-1.1.1/examples/notany/driver.py0000644000175000017500000000201611354262262016606 0ustar lambylamby# driver.py from __future__ import with_statement import sys from pyke import knowledge_engine from pyke import krb_traceback engine = knowledge_engine.engine(__file__) def fc_test(): engine.reset() try: engine.activate('fc_notany') except: krb_traceback.print_exc() sys.exit(1) def bc_test(): engine.reset() try: engine.activate('bc_notany') #with engine.prove_goal( # 'bc_notany.siblings($sibling1, $sibling2, $_, $_)') \ # as gen1: # for vars, plan in gen1: # print "siblings:", vars['sibling1'], vars['sibling2'] with engine.prove_goal('bc_notany.child_with_no_aunt($child)') as gen2: for vars, plan in gen2: print vars['child'], "has no aunt" with engine.prove_goal('bc_notany.child_with_no_uncle($child)') as gen3: for vars, plan in gen3: print vars['child'], "has no uncle" except: krb_traceback.print_exc() sys.exit(1) ./pyke-1.1.1/Test/0000755000175000017500000000000011425360453012533 5ustar lambylamby./pyke-1.1.1/Test/first/0000755000175000017500000000000011425360453013662 5ustar lambylamby./pyke-1.1.1/Test/first/__init__.py0000644000175000017500000000000011346504626015765 0ustar lambylamby./pyke-1.1.1/Test/first/test.krb0000644000175000017500000000113711346504626015347 0ustar lambylamby# test.krb test1 foreach family.son_of($child, $father, $mother) first family.son_of($brother, $father, $mother) check $child != $brother assert family.brothers($child, $brother) test2 foreach first family.cousins($a, $c) family.son_of($a, $father, $mother) assert family.nephew_of($c, $father, $mother) test3 foreach family.brothers($a, $b) first family.son_of($c1, $a, $_) first family.son_of($c2, $b, $_) assert family.cousins($c1, $c2) ./pyke-1.1.1/Test/first/test.tst0000644000175000017500000000165211346504626015405 0ustar lambylamby# test.tst >>> from Test import test >>> import Test.first >>> from Test.first import family >>> test.init(family.__file__) >>> family.init(test.Engine) son_of('art', 'art2', 'nana') son_of('artie', 'art', 'kathleen') son_of('ed', 'art', 'kathleen') son_of('david', 'art', 'kathleen') son_of('justin', 'artie', 'julie') son_of('arthur', 'artie', 'lisa') son_of('keith', 'ed', 'ann') son_of('ramon', 'ed', 'ann') son_of('cody', 'david', 'colleen') >>> test.Engine.get_kb('family').dump_specific_facts() >>> test.fc_test('test') >>> test.Engine.get_kb('family').dump_specific_facts() brothers('artie', 'ed') brothers('ed', 'artie') brothers('david', 'artie') brothers('keith', 'ramon') brothers('ramon', 'keith') cousins('justin', 'keith') cousins('keith', 'justin') cousins('cody', 'justin') nephew_of('keith', 'artie', 'julie') ./pyke-1.1.1/Test/first/family.py0000644000175000017500000000073311346504626015524 0ustar lambylamby# family.py def init(engine): def son_of(son, father, mother): engine.add_universal_fact('family', 'son_of', (son, father, mother)) son_of('art', 'art2', 'nana') son_of('artie', 'art', 'kathleen') son_of('ed', 'art', 'kathleen') son_of('david', 'art', 'kathleen') son_of('justin', 'artie', 'julie') son_of('arthur', 'artie', 'lisa') son_of('keith', 'ed', 'ann') son_of('ramon', 'ed', 'ann') son_of('cody', 'david', 'colleen') ./pyke-1.1.1/Test/__init__.py0000644000175000017500000000000011346504626014636 0ustar lambylamby./pyke-1.1.1/Test/test.py0000644000175000017500000000335111346504626014072 0ustar lambylamby# test.py from __future__ import with_statement import sys import types from pyke import knowledge_engine from pyke import krb_traceback def init(*paths): global Engine Engine = knowledge_engine.engine(*paths) def add_fact(fb, name, *args): Engine.add_universal_fact(fb, name, args) def tmp_fact(fb, name, *args): Engine.add_case_specific_fact(fb, name, args) def fc_test(*rb_names): Engine.reset() try: for rb_name in rb_names: Engine.activate(rb_name) except: krb_traceback.print_exc() sys.exit(1) def bc_test(rb_names, goal, num_return, args = (), rb_name = None, plan_args = None, plan_kws = None): Engine.reset() try: if isinstance(rb_names, types.StringTypes): rb_names = (rb_names,) for rb_name in rb_names: Engine.activate(rb_name) if rb_name is None: rb_name = rb_names[0] with Engine.prove_n(rb_name, goal, args, num_return) as gen: for ret_args, plan in gen: first = True for ret_arg in ret_args: if first: first = False else: print ",", print ret_arg, print if plan_args is None and plan_kws is None: assert plan is None, "unexpected plan" else: assert plan is not None, "expected plan" print "plan:" if plan_args is None: ans = plan(**plan_kws) elif plan_kws is None: ans = plan(*plan_args) else: ans = plan(*plan_args, **plan_vars) if ans is not None: print ans except: krb_traceback.print_exc() sys.exit(1) ./pyke-1.1.1/Test/pyketest.py0000644000175000017500000000442611346504626014767 0ustar lambylamby# pyketest.py from __future__ import with_statement import types import unittest from pyke import knowledge_engine from pyke import krb_traceback def mk_engine(*paths): if isinstance(self.paths, types.StringTypes): self.paths = (self.paths,) return knowledge_engine.engine(*paths) class pyketest(unittest.TestCase): # Need to set: engine tmp_facts = () # sequence of (fb_name, fact_name, fact_arg...) rb_names_to_activate = ('test',) def activate(self): for tmp_fact in self.tmp_facts: fb = tmp_fact[0] name = tmp_fact[1] args = tmp_fact[2:] self.tmp_fact(fb, name, *args) if hasattr(self, 'add_tmp_facts'): self.add_tmp_facts() self.engine.activate(*self.rb_names_to_activate) def add_fact(fb, name, *args): self.engine.add_universal_fact(fb, name, args) def tmp_fact(fb, name, *args): self.engine.add_case_specific_fact(fb, name, args) def tearDown(self): self.engine.reset() class fc_tests(pyketest): def runTest(self): self.activate() class bc_tests(pyketest): rb_name = None # defaults to self.rb_names_to_activate[0] # Need to set: goal, num_return plan_args = None plan_kws = None def __init__(self, methodName = 'runTest'): super(bc_tests, self).__init__(methodName) if self.rb_name is None: self.rb_name = self.rb_names_to_activate[0] def setUp(self): try: self.activate() except: krb_traceback.print_exc() raise def bc_test(self, *args): ans = [] with self.engine.prove_n(self.rb_name, self.goal, args, self.num_return) as gen: for ret_args, plan in gen: if plan_args is None and plan_kws is None: self.assert_(plan is None, "unexpected plan") ans.append(ret_args) else: self.assert_(plan is not None, "expected plan") if plan_args is None: plan_ans = plan(**plan_kws) elif plan_kws is None: plan_ans = plan(*plan_args) else: plan_ans = plan(*plan_args, **plan_vars) ans.append((ret_args, plan_ans)) return ans ./pyke-1.1.1/Test/backup/0000755000175000017500000000000011425360453014000 5ustar lambylamby./pyke-1.1.1/Test/backup/__init__.py0000644000175000017500000000000011346504626016103 0ustar lambylamby./pyke-1.1.1/Test/backup/backup.tst0000644000175000017500000000023511354260504015776 0ustar lambylamby# backup.tst >>> from Test.backup import driver >>> driver.run() Traceback (most recent call last): ... KeyError: '$ans not bound' ./pyke-1.1.1/Test/backup/driver.py0000644000175000017500000000032711354260560015646 0ustar lambylamby# driver.py from pyke import knowledge_engine def run(): engine = knowledge_engine.engine(__file__) engine.activate('backup') vars, plan = engine.prove_1_goal('backup.top($ans)') print vars['ans'] ./pyke-1.1.1/Test/backup/backup.krb0000644000175000017500000000043411346504626015752 0ustar lambylamby# backup.krb top_a use top($a) when a($a) top_b use top($_) a # creates context and binds local $a to top $a use a($a) when c($a) special.claim_goal() check False c # creates context and binds top $a to 44 use c(44) ./pyke-1.1.1/Test/Misc/0000755000175000017500000000000011425360453013426 5ustar lambylamby./pyke-1.1.1/Test/Misc/generator_exception_propagation.py0000644000175000017500000000611211346504626022453 0ustar lambylamby# generator_exception_propagation.py import sys class Backup(Exception): pass class watch(object): def __init__(self, name, gen, *gen_args): self.name = name print "%s.__init__()" % self.name self.iterator = gen(*gen_args) def __iter__(self): print "%s.__iter__()" % self.name self.iterator_iter = iter(self.iterator) return self def __del__(self): print "%s.__del__()" % self.name del self.iterator def next(self): try: ans = self.iterator_iter.next() except Exception, e: print "%s.next() => %s: %s" % \ (self.name, e.__class__.__name__, str(e)) raise print "%s.next() => %s" % (self.name, str(ans)) return ans def throw(self, value=None, traceback=None): try: ans = self.iterator_iter.throw(value, traceback) except Exception, e: print "%s.throw(%s, %s) => %s: %s" % \ (self.name, str(value), "traceback" if traceback else None, e.__class__.__name__, str(e)) raise print "%s.throw(%s, %s) => %s" % \ (self.name, str(value), "traceback" if traceback else None, str(ans)) return ans def close(self): print "%s.close()" % self.name self.iterator_iter.close() def gen1(name, gen, *args): try: try: for i in gen(*args): yield i except Exception, e: print "gen1(%s) => %s: %s" % (name, e.__class__.__name__, str(e)) raise finally: print "gen1(%s) done" % name def gen2(name, gen1, gen1_args, gen2, gen2_args): try: try: for i in gen1(*gen1_args): for j in gen2(*gen2_args): yield i, j except Exception, e: print "gen2(%s) => %s: %s" % (name, e.__class__.__name__, str(e)) raise finally: print "gen2(%s) done" % name def gen_exception(name, gen, *args): try: try: for i in gen(*args): if i == 2: print "gen_exception(%s): raising exception" % name raise Backup("gen_exception(%s): hello bob" % name) yield i except Exception, e: print "gen_exception(%s) => %s: %s" % \ (name, e.__class__.__name__, str(e)) raise finally: print "gen_exception(%s) done" % name def make_gen(): return watch("top_gen", gen2, "top_gen", watch, ("first_gen", gen1, "first_gen", range, 1, 5), watch, ("second_gen", gen1, "second_gen", watch, "gen_exception", gen_exception, "gen_exception", watch, "range", range, 1, 5)) def test(): for i, x in enumerate(make_gen()): print "got", x if i == 3: print "test: raising exception" raise Backup("test: hello bob") ./pyke-1.1.1/Test/Misc/generator_close.py0000644000175000017500000000244211346504626017161 0ustar lambylamby# generator_close.py import sys class Backup(Exception): pass def gen1(name, n): try: try: for i in range(n): yield i except Exception, e: print "gen1(%s) => %s: %s" % (name, e.__class__.__name__, str(e)) raise finally: print "gen1(%s) done" % name def gen2(name): try: try: for i in gen1("first", 5): for j in gen_exception("second"): yield i, j except Exception, e: print "gen2(%s) => %s: %s" % (name, e.__class__.__name__, str(e)) raise finally: print "gen2(%s) done" % name def gen_exception(name): try: try: for i in gen1("inner", 5): if i == 2: print "gen_exception(%s): raising exception" % name raise Backup("gen_exception(%s): hello bob" % name) yield i except Exception, e: print "gen_exception(%s) => %s: %s" % \ (name, e.__class__.__name__, str(e)) raise finally: print "gen_exception(%s) done" % name def test(): for i, x in enumerate(gen2("top")): print "got", x if i == 3: print "test: raising exception" raise Backup("test: hello bob") ./pyke-1.1.1/Test/Misc/generator_test.py0000644000175000017500000000171311346504626017033 0ustar lambylamby# foo.py def gen2(n): try: for x in range(n): try: yield x finally: print "gen2 inner finally" except Exception, e: print "gen2 caught", repr(e) finally: print "gen2 finally" print "gen2 done" def gen(n): try: for x in gen2(n): try: yield x finally: print "gen inner finally" except Exception, e: print "gen caught", repr(e) finally: print "gen finally" print "gen done" def test1(): try: for x in gen(2): print "test in for, doing: assert False" raise ValueError("this is a test") print "test after for" finally: print "test finally" def test2(): try: for x in gen(2): print "test in for, doing: break" break print "test after for" finally: print "test finally" ./pyke-1.1.1/Test/CanNotProve/0000755000175000017500000000000011425360453014731 5ustar lambylamby./pyke-1.1.1/Test/CanNotProve/__init__.py0000644000175000017500000000000011346504626017034 0ustar lambylamby./pyke-1.1.1/Test/CanNotProve/CanNotProve2.tst0000644000175000017500000000173011346504626017752 0ustar lambylamby# CanNotProve2.tst We're going to create a zipped egg file with a compiled CanNotProve example in it and see if pyke can load its compiled files from the zip file. First, create the egg (in Test/dist) using Test/setup.py: >>> import os >>> os.chdir('..') # up to Test directory >>> import contextlib >>> import zipfile >>> with contextlib.closing(zipfile.PyZipFile('CanNotProve.egg', 'w')) as z: ... z.writepy('CanNotProve') ... z.write('CanNotProve/compiled_krb/facts.fbc') Now, move up another level (so that 'CanNotProve' is not a subdirectory) >>> os.chdir('..') # up to root source directory Add the egg to the python path: >>> import sys >>> sys.path.insert(0, 'Test/CanNotProve.egg') Now try the test! >>> from CanNotProve import test >>> test.__loader__ # doctest: +ELLIPSIS >>> test.dotests() And finally, delete the egg file: >>> os.remove('Test/CanNotProve.egg') ./pyke-1.1.1/Test/CanNotProve/CanNotProve.tst0000644000175000017500000000012511346504626017665 0ustar lambylamby# CanNotProve.tst >>> from Test.CanNotProve import test >>> test.dotests() ./pyke-1.1.1/Test/CanNotProve/test.py0000644000175000017500000000124711346504626016272 0ustar lambylamby# test.py from pyke import knowledge_engine Engine = None def test(kb, ke, arg): global Engine if Engine is None: Engine = knowledge_engine.engine(__file__) Engine.reset() Engine.activate('rules') try: Engine.prove_1(kb, ke, (arg,), 0) except knowledge_engine.CanNotProve: return raise AssertionError("test: expected CanNotProve exception") def dotests(): test('facts', 'fact1', 2) test('facts', 'fact2', 2) test('rules', 'rule1', 2) test('rules', 'rule2', 2) Engine.reset() Engine.activate('rules') vars, no_plan = Engine.prove_1_goal('facts.fact3($ans)') assert vars['ans'] == 'hi\nthere' ./pyke-1.1.1/Test/CanNotProve/facts.kfb0000644000175000017500000000005011346504626016514 0ustar lambylamby# facts.kfb fact1(1) fact3('hi\nthere')./pyke-1.1.1/Test/CanNotProve/rules.krb0000644000175000017500000000017711346504626016574 0ustar lambylamby# rules.krb rule1 use rule1($x) when facts.fact1($x) rule2 use rule2($x) when facts.fact2($x)./pyke-1.1.1/Test/examples/0000755000175000017500000000000011425360453014351 5ustar lambylamby./pyke-1.1.1/Test/examples/forall.tst0000644000175000017500000000073511354260640016367 0ustar lambylamby# forall.tst >>> import sys >>> import pyke >>> import os >>> new_path = os.path.join(os.path.dirname(os.path.dirname(pyke.__file__)), ... 'examples/forall') >>> sys.path.append(new_path) >>> import driver >>> driver.fc_test() arthur2 has no step brothers or sisters helen has no step brothers or sisters roberta has no step brothers or sisters >>> driver.bc_test() arthur2 helen roberta ./pyke-1.1.1/Test/examples/findall.tst0000644000175000017500000000142611354260634016522 0ustar lambylamby# findall.tst >>> import sys >>> import pyke >>> import os >>> new_path = os.path.join(os.path.dirname(os.path.dirname(pyke.__file__)), ... 'examples/findall') >>> sys.path.append(new_path) >>> import driver >>> driver.fc_test() egon has ('harald', 'claudia') as cousins ralf has ('harald', 'claudia') as cousins hilde has () as cousins diethelm has () as cousins harald has ('egon', 'ralf') as cousins claudia has ('egon', 'ralf') as cousins >>> driver.bc_test() egon has ('harald', 'claudia') as cousins ralf has ('harald', 'claudia') as cousins hilde has () as cousins diethelm has () as cousins harald has ('egon', 'ralf') as cousins claudia has ('egon', 'ralf') as cousins ./pyke-1.1.1/Test/examples/__init__.py0000644000175000017500000000000011346504626016454 0ustar lambylamby./pyke-1.1.1/Test/examples/knapsack.tst0000644000175000017500000000077711354260646016717 0ustar lambylamby# knapsack.tst >>> import sys >>> import pyke >>> import os >>> new_path = os.path.join(os.path.dirname(os.path.dirname(pyke.__file__)), ... 'examples/knapsack') >>> sys.path.append(new_path) >>> import driver >>> driver.run((('bread', 4, 9200), ... ('pasta', 2, 4500), ... ('peanutButter', 1, 6700), ... ('babyFood', 3, 6900)), ... 4) (13600, (('peanutButter', 1, 6700), ('babyFood', 3, 6900))) ./pyke-1.1.1/Test/examples/web_framework.tst0000644000175000017500000001171011346504626017743 0ustar lambylamby# web_framework.tst >>> import sys >>> import pyke >>> import os >>> source_dir = os.path.dirname(os.path.dirname(pyke.__file__)) >>> new_path = os.path.join(source_dir, 'examples') >>> sys.path.append(new_path) First, fire up the server: >>> import sys >>> import os >>> import signal >>> import time >>> import subprocess >>> server = subprocess.Popen((sys.executable, "-u", "-"), ... stdin=subprocess.PIPE, ... stdout=subprocess.PIPE, ... stderr=subprocess.STDOUT, ... close_fds=True, env=os.environ) >>> server.stdin.write(r''' ... import sys ... sys.path.append(%r) ... sys.path.append(%r) ... from web_framework import simple_server ... simple_server.run() ... ''' % (new_path, source_dir)) >>> server.stdin.close() >>> def readline(): ... global server_error_msg ... if server.poll() is not None: ... msg = server.stdout.read() ... if msg: ... server_error_msg = '\n'.join(('server: ' + line) ... for line in msg.split('\n')) ... sys.stdout.write(server_error_msg) ... sys.stdout.write( ... "Server terminated prematurely with returncode %r\n" % \ ... (server.returncode,)) ... return ... line = server.stdout.readline() ... print >> sys.stderr, line, ... return line >>> while True: ... line = readline() ... if not line.startswith('writing ['): ... break >>> line 'Server running...\n' >>> time.sleep(0.8) Then interact with it: >>> import urllib >>> def get(path): ... f = urllib.urlopen("http://localhost:8080/" + path) ... #print >> sys.stderr, "info:", tuple(sorted(f.info().keys())) ... try: ... return f.read() # int(f.info()['content-length'])) ... finally: ... f.close() >>> print get("movie/1/movie.html") It's a Mad, Mad, Mad, Mad World

It's a Mad, Mad, Mad, Mad World

Year

1963

Length

3:12

Directors

  1. Stanley Kramer
>>> readline() # doctest: +ELLIPSIS "get_plan(..., ('movie',), ...examples/web_framework/movie.html)\n" >>> readline() # doctest: +ELLIPSIS 'localhost - - [...] "GET /movie/1/movie.html HTTP/1.0" 200 302\n' >>> print get("movie/3/movie.html") A Funny Thing Happened on the Way to the Forum

A Funny Thing Happened on the Way to the Forum

Year

1966

Length

1:39

Directors

  1. Richard Lester
>>> readline() # doctest: +ELLIPSIS 'localhost - - [...] "GET /movie/3/movie.html HTTP/1.0" 200 332\n' >>> print get("movie/3/movie2.html") A Funny Thing Happened on the Way to the Forum

A Funny Thing Happened on the Way to the Forum

Year:

1966

Length:

1:39

Directors:

  1. Richard Lester

DVD List:

  1. 102(1)
  2. 105(1)
>>> readline() # doctest: +ELLIPSIS "get_plan(..., ('movie',), ...examples/web_framework/movie2.html)\n" >>> readline() # doctest: +ELLIPSIS 'localhost - - [...] "GET /movie/3/movie2.html HTTP/1.0" 200 429\n' >>> print get("movie/6/movie2.html") Casino Royale

Casino Royale

Year:

1967

Length:

2:11

Directors:

  1. Val Guest
  2. Ken Hughes
  3. John Huston
  4. Joseph McGrath
  5. Robert Parrish
  6. Richard Talmadge

DVD List:

  1. 104(1)
>>> readline() # doctest: +ELLIPSIS 'localhost - - [...] "GET /movie/6/movie2.html HTTP/1.0" 200 485\n' Kill server: >>> time.sleep(0.4) >>> os.kill(server.pid, signal.SIGTERM) >>> server.wait() -15 ./pyke-1.1.1/Test/examples/family_relations.tst0000644000175000017500000002457411354260630020457 0ustar lambylamby# family_relations.tst >>> import sys >>> import pyke >>> import os >>> new_path = os.path.join(os.path.dirname(os.path.dirname(pyke.__file__)), ... 'examples/family_relations') >>> sys.path.append(new_path) >>> import driver >>> driver.fc_test() # doctest: +ELLIPSIS doing proof bruce, thomas are ('son', 'father') bruce, norma are ('son', 'mother') bruce, frederik are (('grand', 'son'), ('grand', 'father')) bruce, mary are (('grand', 'son'), ('grand', 'mother')) bruce, allen are (('grand', 'son'), ('grand', 'father')) bruce, ismay are (('grand', 'son'), ('grand', 'mother')) bruce, m_thomas are ('father', 'son') bruce, david_a are ('father', 'son') bruce, fred_a are ('brother', 'brother') bruce, tim are ('brother', 'brother') bruce, vicki are ('brother', 'sister') bruce, jill are ('brother', 'sister') bruce, joyce are ('nephew', 'aunt') bruce, phyllis are ('nephew', 'aunt') bruce, john_w are ('nephew', 'uncle') bruce, bill are ('nephew', 'uncle') bruce, chuck_w are ('nephew', 'uncle') bruce, david_c are ('1st', 'cousins') bruce, danny are ('1st', 'cousins') bruce, dee are ('1st', 'cousins') bruce, mitch are ('1st', 'cousins') bruce, jonni are ('1st', 'cousins') bruce, lorri are ('1st', 'cousins') bruce, steve_w are ('1st', 'cousins') bruce, jim are ('1st', 'cousins') bruce, jeri are ('1st', 'cousins') bruce, annette are ('1st', 'cousins') bruce, helen_w are ('1st', 'cousins') bruce, mary_w are ('1st', 'cousins') bruce, charli are ('1st', 'cousins', 1, 'removed') bruce, jimjim are ('1st', 'cousins', 1, 'removed') bruce, johnjohn are ('1st', 'cousins', 1, 'removed') bruce, jamie are ('1st', 'cousins', 1, 'removed') bruce, david_w are ('1st', 'cousins', 1, 'removed') bruce, jessica are ('1st', 'cousins', 1, 'removed') bruce, bridget are ('1st', 'cousins', 1, 'removed') bruce, brian2 are ('1st', 'cousins', 1, 'removed') bruce, victoria are ('1st', 'cousins', 1, 'removed') done family: 9 fact names, 94 universal facts, 6920 case_specific facts fc_example: 20 fc_rules, 6772 triggered, 892 rerun fc_example: 0 bc_rules, 0 goals, 0 rules matched 0 successes, 0 failures fc time ..., ... asserts/sec >>> driver.bc_test() # doctest: +ELLIPSIS doing proof bruce, thomas are ('son', 'father') bruce, norma are ('son', 'mother') bruce, frederik are (('grand', 'son'), ('grand', 'father')) bruce, mary are (('grand', 'son'), ('grand', 'mother')) bruce, allen are (('grand', 'son'), ('grand', 'father')) bruce, ismay are (('grand', 'son'), ('grand', 'mother')) bruce, m_thomas are ('father', 'son') bruce, david_a are ('father', 'son') bruce, fred_a are ('brother', 'brother') bruce, tim are ('brother', 'brother') bruce, vicki are ('brother', 'sister') bruce, jill are ('brother', 'sister') bruce, joyce are ('nephew', 'aunt') bruce, phyllis are ('nephew', 'aunt') bruce, john_w are ('nephew', 'uncle') bruce, bill are ('nephew', 'uncle') bruce, chuck_w are ('nephew', 'uncle') bruce, david_c are ('1st', 'cousins') bruce, danny are ('1st', 'cousins') bruce, dee are ('1st', 'cousins') bruce, mitch are ('1st', 'cousins') bruce, jonni are ('1st', 'cousins') bruce, lorri are ('1st', 'cousins') bruce, steve_w are ('1st', 'cousins') bruce, jim are ('1st', 'cousins') bruce, jeri are ('1st', 'cousins') bruce, annette are ('1st', 'cousins') bruce, helen_w are ('1st', 'cousins') bruce, mary_w are ('1st', 'cousins') bruce, charli are ('1st', 'cousins', 1, 'removed') bruce, jimjim are ('1st', 'cousins', 1, 'removed') bruce, johnjohn are ('1st', 'cousins', 1, 'removed') bruce, jamie are ('1st', 'cousins', 1, 'removed') bruce, david_w are ('1st', 'cousins', 1, 'removed') bruce, jessica are ('1st', 'cousins', 1, 'removed') bruce, bridget are ('1st', 'cousins', 1, 'removed') bruce, brian2 are ('1st', 'cousins', 1, 'removed') bruce, victoria are ('1st', 'cousins', 1, 'removed') done bc_example: 0 fc_rules, 0 triggered, 0 rerun bc_example: 26 bc_rules, 31950 goals, 112099 rules matched 17150 successes, 112099 failures family: 9 fact names, 94 universal facts, 0 case_specific facts bc time ..., ... goals/sec >>> driver.bc2_test() # doctest: +ELLIPSIS doing proof bruce, thomas are ('son', 'father') bruce, norma are ('son', 'mother') bruce, frederik are (('grand', 'son'), ('grand', 'father')) bruce, mary are (('grand', 'son'), ('grand', 'mother')) bruce, allen are (('grand', 'son'), ('grand', 'father')) bruce, ismay are (('grand', 'son'), ('grand', 'mother')) bruce, m_thomas are ('father', 'son') bruce, david_a are ('father', 'son') bruce, fred_a are ('brother', 'brother') bruce, tim are ('brother', 'brother') bruce, vicki are ('brother', 'sister') bruce, jill are ('brother', 'sister') bruce, joyce are ('nephew', 'aunt') bruce, phyllis are ('nephew', 'aunt') bruce, john_w are ('nephew', 'uncle') bruce, bill are ('nephew', 'uncle') bruce, chuck_w are ('nephew', 'uncle') bruce, david_c are ('1st', 'cousins') bruce, danny are ('1st', 'cousins') bruce, dee are ('1st', 'cousins') bruce, mitch are ('1st', 'cousins') bruce, jonni are ('1st', 'cousins') bruce, lorri are ('1st', 'cousins') bruce, steve_w are ('1st', 'cousins') bruce, jim are ('1st', 'cousins') bruce, jeri are ('1st', 'cousins') bruce, annette are ('1st', 'cousins') bruce, helen_w are ('1st', 'cousins') bruce, mary_w are ('1st', 'cousins') bruce, charli are ('1st', 'cousins', 1, 'removed') bruce, jimjim are ('1st', 'cousins', 1, 'removed') bruce, johnjohn are ('1st', 'cousins', 1, 'removed') bruce, jamie are ('1st', 'cousins', 1, 'removed') bruce, david_w are ('1st', 'cousins', 1, 'removed') bruce, jessica are ('1st', 'cousins', 1, 'removed') bruce, bridget are ('1st', 'cousins', 1, 'removed') bruce, brian2 are ('1st', 'cousins', 1, 'removed') bruce, victoria are ('1st', 'cousins', 1, 'removed') done bc2_example: 0 fc_rules, 0 triggered, 0 rerun bc2_example: 29 bc_rules, 315 goals, 1202 rules matched 272 successes, 1202 failures family: 9 fact names, 94 universal facts, 0 case_specific facts bc time ..., ... goals/sec >>> driver.test() # doctest: +ELLIPSIS doing proof bruce, thomas are son, father bruce, norma are son, mother bruce, frederik are grand son, grand father bruce, mary are grand son, grand mother bruce, allen are grand son, grand father bruce, ismay are grand son, grand mother bruce, m_thomas are father, son bruce, david_a are father, son bruce, fred_a are brother, brother bruce, tim are brother, brother bruce, vicki are brother, sister bruce, jill are brother, sister bruce, joyce are nephew, aunt bruce, phyllis are nephew, aunt bruce, john_w are nephew, uncle bruce, bill are nephew, uncle bruce, chuck_w are nephew, uncle bruce, david_c are 1st cousins bruce, danny are 1st cousins bruce, dee are 1st cousins bruce, mitch are 1st cousins bruce, jonni are 1st cousins bruce, lorri are 1st cousins bruce, steve_w are 1st cousins bruce, jim are 1st cousins bruce, jeri are 1st cousins bruce, annette are 1st cousins bruce, helen_w are 1st cousins bruce, mary_w are 1st cousins bruce, charli are 1st cousins, 1 removed bruce, jimjim are 1st cousins, 1 removed bruce, johnjohn are 1st cousins, 1 removed bruce, jamie are 1st cousins, 1 removed bruce, david_w are 1st cousins, 1 removed bruce, jessica are 1st cousins, 1 removed bruce, bridget are 1st cousins, 1 removed bruce, brian2 are 1st cousins, 1 removed bruce, victoria are 1st cousins, 1 removed done example: 6 fc_rules, 262 triggered, 0 rerun example: 21 bc_rules, 9600 goals, 19772 rules matched 1542 successes, 19772 failures family: 9 fact names, 94 universal facts, 422 case_specific facts fc time ..., ... asserts/sec bc time ..., ... goals/sec total time ... >>> driver.general('bruce') # doctest: +ELLIPSIS doing proof bruce, thomas are ('son', 'father') bruce, norma are ('son', 'mother') bruce, frederik are (('grand', 'son'), ('grand', 'father')) bruce, mary are (('grand', 'son'), ('grand', 'mother')) bruce, allen are (('grand', 'son'), ('grand', 'father')) bruce, ismay are (('grand', 'son'), ('grand', 'mother')) bruce, m_thomas are ('father', 'son') bruce, david_a are ('father', 'son') bruce, fred_a are ('brother', 'brother') bruce, tim are ('brother', 'brother') bruce, vicki are ('brother', 'sister') bruce, jill are ('brother', 'sister') bruce, joyce are ('nephew', 'aunt') bruce, phyllis are ('nephew', 'aunt') bruce, john_w are ('nephew', 'uncle') bruce, bill are ('nephew', 'uncle') bruce, chuck_w are ('nephew', 'uncle') bruce, david_c are ('1st', 'cousins') bruce, danny are ('1st', 'cousins') bruce, dee are ('1st', 'cousins') bruce, mitch are ('1st', 'cousins') bruce, jonni are ('1st', 'cousins') bruce, lorri are ('1st', 'cousins') bruce, steve_w are ('1st', 'cousins') bruce, jim are ('1st', 'cousins') bruce, jeri are ('1st', 'cousins') bruce, annette are ('1st', 'cousins') bruce, helen_w are ('1st', 'cousins') bruce, mary_w are ('1st', 'cousins') bruce, charli are ('1st', 'cousins', 1, 'removed') bruce, jimjim are ('1st', 'cousins', 1, 'removed') bruce, johnjohn are ('1st', 'cousins', 1, 'removed') bruce, jamie are ('1st', 'cousins', 1, 'removed') bruce, david_w are ('1st', 'cousins', 1, 'removed') bruce, jessica are ('1st', 'cousins', 1, 'removed') bruce, bridget are ('1st', 'cousins', 1, 'removed') bruce, brian2 are ('1st', 'cousins', 1, 'removed') bruce, victoria are ('1st', 'cousins', 1, 'removed') done bc2_example: 0 fc_rules, 0 triggered, 0 rerun bc2_example: 29 bc_rules, 315 goals, 1202 rules matched 272 successes, 1202 failures family: 9 fact names, 94 universal facts, 0 case_specific facts bc time ..., ... goals/sec ./pyke-1.1.1/Test/examples/notany.tst0000644000175000017500000000141411354260660016415 0ustar lambylamby# notany.tst >>> import sys >>> import pyke >>> import os >>> new_path = os.path.join(os.path.dirname(os.path.dirname(pyke.__file__)), ... 'examples/notany') >>> sys.path.append(new_path) >>> import driver >>> driver.fc_test() egon has no uncle ralf has no uncle anton has no uncle elisabeth has no uncle karin has no uncle sabine has no uncle anton has no aunt elisabeth has no aunt karin has no aunt sabine has no aunt >>> driver.bc_test() anton has no aunt elisabeth has no aunt karin has no aunt sabine has no aunt egon has no uncle ralf has no uncle anton has no uncle elisabeth has no uncle karin has no uncle sabine has no uncle ./pyke-1.1.1/Test/examples/towers_of_hanoi.tst0000644000175000017500000000337711354260664020310 0ustar lambylamby# towers_of_hanoi.tst >>> import sys >>> import pyke >>> import os >>> new_path = os.path.join(os.path.dirname(os.path.dirname(pyke.__file__)), ... 'examples/towers_of_hanoi') >>> sys.path.append(new_path) >>> import driver >>> driver.test(1) got 1: ((0, 2),) >>> driver.test(2) got 1: ((0, 1), (0, 2), (1, 2)) got 2: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) >>> driver.test(3) got 1: ((0, 1), (0, 2), (1, 0), (2, 1), (0, 1), (0, 2), (1, 0), (1, 2), (0, 2)) got 2: ((0, 1), (0, 2), (1, 0), (2, 1), (0, 1), (0, 2), (1, 2), (1, 0), (2, 1), (0, 2), (1, 2)) got 3: ((0, 1), (0, 2), (1, 2), (0, 1), (2, 0), (2, 1), (0, 2), (1, 0), (2, 0), (1, 2), (0, 1), (0, 2), (1, 2)) got 4: ((0, 1), (0, 2), (1, 2), (0, 1), (2, 0), (2, 1), (0, 2), (1, 0), (2, 0), (1, 2), (0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) got 5: ((0, 1), (0, 2), (1, 2), (0, 1), (2, 1), (2, 0), (1, 0), (1, 2), (0, 1), (0, 2), (1, 2)) got 6: ((0, 1), (0, 2), (1, 2), (0, 1), (2, 1), (2, 0), (1, 0), (1, 2), (0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) got 7: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2), (0, 1), (2, 0), (2, 1), (0, 2), (1, 0), (2, 0), (1, 2), (0, 1), (0, 2), (1, 2)) got 8: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2), (0, 1), (2, 0), (2, 1), (0, 2), (1, 0), (2, 0), (1, 2), (0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) got 9: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2), (0, 1), (2, 1), (2, 0), (1, 0), (1, 2), (0, 1), (0, 2), (1, 2)) got 10: ((0, 2), (0, 1), (2, 0), (1, 2), (0, 2), (0, 1), (2, 1), (2, 0), (1, 0), (1, 2), (0, 2), (0, 1), (2, 0), (1, 2), (0, 2)) got 11: ((0, 2), (0, 1), (2, 1), (0, 2), (1, 0), (1, 2), (0, 2)) got 12: ((0, 2), (0, 1), (2, 1), (0, 2), (1, 2), (1, 0), (2, 1), (0, 2), (1, 2)) ./pyke-1.1.1/Test/examples/learn_pyke.tst0000644000175000017500000000702611354260654017246 0ustar lambylamby# learn_pyke.tst >>> import sys >>> import pyke >>> import os >>> new_path = os.path.join(os.path.dirname(os.path.dirname(pyke.__file__)), ... 'examples/learn_pyke') >>> sys.path.append(new_path) >>> import driver >>> from StringIO import StringIO >>> import sys >>> sys.stdin = StringIO('8\n2\n2\n13\n') >>> driver.run() ______________________________________________________________________________ Assume that the following two patterns are contained in different rules and that none of the pattern variables are initially bound to values: pattern 1: ((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b)) pattern 2: ($a, $a, $x) If the two patterns are matched together, what will $x be bound to? 1. (a, b) 2. $a 3. ho 4. ($a, *$b) 5. (ho, *$b) 6. (ho, *($a, $a)) 7. (ho, ($a, $a)) 8. (ho, $a, $a) 9. (ho, *(ho, ho)) 10. (ho, (ho, ho)) 11. (ho, $_, (ho, ho)) 12. (ho, ho, (ho, ho)) 13. (ho, ho, ho) 14. nothing, the two patterns don't match 15. nothing, pattern 1 is not a legal pattern 16. I don't have a clue... ? [1-16] Incorrect: Pattern variable '$a' is bound to a value. ______________________________________________________________________________ "Rest" pattern variables are used at the end of a tuple pattern to match the rest of the tuple. What is the syntax for a "rest" pattern variable? 1. $rest 2. _rest 3. Preceding a pattern variable with an asterisk ('*'), like: *$foo. ? [1-3] Incorrect: A "rest" pattern variable is any pattern variable preceded by an asterisk ('*'). ______________________________________________________________________________ After matching the following two patterns, what is $c set to? pattern 1: ($a, $b, *$c) pattern 2: (1, 2, 3) 1. 3 2. (3) 3. (3,) 4. nothing, the two patterns don't match 5. nothing, pattern 1 is not a legal pattern ? [1-5] Correct! (Note that a comma is not required for singleton tuples in PyKE). ______________________________________________________________________________ Assume that the following two patterns are contained in different rules and that none of the pattern variables are initially bound to values: pattern 1: ((ho, $_, ($a, $a)), ($a, $a, $b), ($a, *$b)) pattern 2: ($a, $a, $x) If the two patterns are matched together, what will $x be bound to? 1. (a, b) 2. $a 3. ho 4. ($a, *$b) 5. (ho, *$b) 6. (ho, *($a, $a)) 7. (ho, ($a, $a)) 8. (ho, $a, $a) 9. (ho, *(ho, ho)) 10. (ho, (ho, ho)) 11. (ho, $_, (ho, ho)) 12. (ho, ho, (ho, ho)) 13. (ho, ho, ho) 14. nothing, the two patterns don't match 15. nothing, pattern 1 is not a legal pattern 16. I don't have a clue... ? [1-16] Correct! matching Pattern 1: (ho, $_, ($a, $a)) to Pattern 2: $a binds Pattern 2: $a to Pattern 1: (ho, $_, (ho, ho)) matching Pattern 1: ($a, $a, $b) to Pattern 2: $a, which is bound to Pattern 1: (ho, $_, ($a, $a)) binds Pattern 1: $a to ho, and Pattern 1: $b to Pattern 1: ($a, $a) which expands to (ho, ho) matching Pattern 1: ($a, *$b) to Pattern 2: $x binds Pattern 2: $x to Pattern 1: ($a, *$b) which expands to (ho, ho, ho)