├── .gitignore ├── .project ├── .pydevproject ├── .settings ├── org.deved.antlride.core.prefs └── org.eclipse.core.resources.prefs ├── License.txt ├── README.rst ├── README.txt ├── images ├── action.jpg ├── agendaGroup.jpg ├── attributeActrion.jpg ├── attributeStmt.jpg ├── classConstraint.jpg ├── condition.jpg ├── forgetAction.jpg ├── gittip.png ├── haltAction.jpg ├── learnAction.jpg ├── modifyAction.jpg ├── ruleStmt.jpg ├── simpleStmt.jpg ├── then.jpg └── when.jpg ├── intellect ├── Callable.py ├── IO.py ├── Intellect.py ├── Node.py ├── PolicyLexer.py ├── PolicyTokenSource.py ├── __init__.py ├── examples │ ├── __init__.py │ ├── bahBahBlackSheep │ │ ├── BagOfWool.py │ │ ├── BlackSheep.py │ │ ├── BuyOrder.py │ │ ├── Example.py │ │ ├── __init__.py │ │ └── rulesets │ │ │ ├── __init__.py │ │ │ └── example.policy │ └── testing │ │ ├── ClassA.py │ │ ├── ClassCandD.py │ │ ├── ExerciseGrammar.py │ │ ├── ExerciseIntellect.py │ │ ├── Test.py │ │ ├── __init__.py │ │ ├── rulesets │ │ ├── __init__.py │ │ ├── test_a.policy │ │ ├── test_b.policy │ │ ├── test_c.policy │ │ ├── test_d.policy │ │ ├── test_e.policy │ │ └── test_f.policy │ │ └── subModule │ │ ├── ClassB.py │ │ └── __init__.py ├── grammar │ ├── Policy.g │ ├── Policy.tokens │ ├── PolicyLexer.py │ ├── PolicyParser.py │ └── __init__.py └── reflection.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | 29 | /Intellect.egg-info 30 | /dist -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Intellect 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.dltk.core.scriptbuilder 10 | 11 | 12 | 13 | 14 | org.python.pydev.PyDevBuilder 15 | 16 | 17 | 18 | 19 | 20 | org.python.pydev.pythonNature 21 | org.deved.antlride.core.nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Default 6 | python 2.7 7 | 8 | /Intellect 9 | 10 | 11 | -------------------------------------------------------------------------------- /.settings/org.deved.antlride.core.prefs: -------------------------------------------------------------------------------- 1 | antlr_core_builder_Xconversiontimeout=1000 2 | antlr_core_builder_Xdfaverbose=false 3 | antlr_core_builder_Xm=4 4 | antlr_core_builder_Xmaxdfaedges=65534 5 | antlr_core_builder_Xnocollapse=false 6 | antlr_core_builder_Xnomergestopstates=false 7 | antlr_core_builder_Xnoprune=false 8 | antlr_core_builder_dfa=false 9 | antlr_core_builder_include_stacktrace_on_internal_errors=false 10 | antlr_core_builder_max_number_of_problems_reported_per_grammar=25 11 | antlr_core_builder_nfa=false 12 | antlr_core_builder_report=false 13 | antlr_core_builder_runtime=3.1.3 14 | antlr_core_code_generator_XdbgST=false 15 | antlr_core_code_generator_append_java_package_to_out_folder=false 16 | antlr_core_code_generator_debug=false 17 | antlr_core_code_generator_max_memory=0 18 | antlr_core_code_generator_out_folder=antlr-generated 19 | antlr_core_code_generator_out_option=g 20 | antlr_core_code_generator_profile=false 21 | antlr_core_code_generator_trace=false 22 | antlr_core_code_generator_x_max_switch_case_labels=300 23 | antlr_core_code_generator_x_min_switch_alts=3 24 | antlr_core_mark_generated_resources_as_derived=true 25 | eclipse.preferences.version=1 26 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//intellect/Callable.py=utf-8 3 | encoding//intellect/IO.py=utf-8 4 | encoding//intellect/Intellect.py=utf-8 5 | encoding//intellect/Node.py=utf-8 6 | encoding//intellect/PolicyLexer.py=utf-8 7 | encoding//intellect/PolicyTokenSource.py=utf-8 8 | encoding//intellect/examples/bahBahBlackSheep/Example.py=utf-8 9 | encoding//intellect/examples/testing/ExerciseGrammar.py=utf-8 10 | encoding//intellect/examples/testing/ExerciseIntellect.py=utf-8 11 | encoding/setup.py=utf-8 12 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, The MITRE Corporation 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 3. All advertising materials mentioning features or use of this software 12 | must display the following acknowledgement: 13 | This product includes software developed by the author. 14 | 4. Neither the name of the author nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Intellect 2 | ========= 3 | 4 | :Info: Intellect is a Domain-specific language and Rules Engine for Python. 5 | 6 | :Author: Michael Joseph Walsh 7 | 8 | 1. What is Intellect 9 | -------------------- 10 | 11 | Intellect is a DSL ("Domain-Specific Language") and Rule Engine for Python 12 | I authored for expressing policies to orchestrate and control a dynamic 13 | network defense cyber-security platform being researched in The 14 | MITRE Corporation's Innovation Program. 15 | 16 | The rules engine provides an intellect, a form of artificial intelligence, 17 | a faculty of reasoning and understanding objectively over a working memory. 18 | The memory retains knowledge relevant to the system, and a set of rules 19 | authored in the DSL that describe a necessary behavior to achieve some 20 | goal. Each rule has an optional condition, and a suite of one or more 21 | actions. These actions either further direct the behavior of the system, 22 | and/or further inform the system. The engine starts with some facts, 23 | truths known about past or present circumstances, and uses rules to infer 24 | more facts. These facts fire more rules, that infer more facts and so 25 | on. 26 | 27 | For the platform in the Innovation Program, the network defender uses 28 | the DSL to confer policy, how the platform is to respond to network 29 | events mounted over covert network channels, but there are no direct 30 | ties written into the language nor the rule engine to cyber security 31 | and thus the system in its entirety can be more broadly used in 32 | other domains. 33 | 34 | 2. TODOS 35 | -------- 36 | 37 | There are number of improvements I would like to work for future releases: 38 | 39 | * Add Support for Multiple Rule Conditions. 40 | * Move to an ANTLR4 based parser. The ANTLR Runtime for Python dependency was left behind with the the release of ANTL4. Luckily, n ANTLR 4 runtime for both Python 2 and 3 target is being tackled by Eric Vergnaud and is taking `shape `_. The last I looked all execParser and execLexer tests passed. Woot! 41 | * Support Python 3. 42 | 43 | Please help support these efforts. 44 | 45 | .. image:: https://github.com/nemonik/Intellect/raw/master/images/gittip.png 46 | :target: https://www.gittip.com/nemonik/ 47 | 48 | 3. Intellect In The News 49 | --------------------- 50 | 51 | The September 2013 issue, Volume 37 of Elsevier's "Computers and Security" contains a journal article entitled "`Active cyber defense with denial and deception: A cyber-wargame experiment `_" describing a computer network security use case for Intellect. 52 | 53 | 4. Installation 54 | --------------- 55 | 56 | * To install via `setuptools `_ use ``easy_install -U Intellect`` 57 | * To install via `pip `_ use ``pip install Intellect`` 58 | * To install via `pypm `_ use ``pypm install intellect`` 59 | * Or download the latest source from `Master `_ or the most recent tagged release `Tagged Releases `_, unpack, and run ``python setup.py install`` 60 | 61 | 5. Dependencies 62 | --------------- 63 | 64 | * `ANTLR3 Python Runtime `_ that will contrain you to Python 2.x. In the past I've noted here that "Python 3 at present is not supported, because ANTLR3 appears not to support Python 3." Well, that's not exactly true, there is a Python3 runtime, I just wasn't aware of it nor have I worked with it. It can be found here `ANTLR3 Python3 Runtime `_ 65 | * Python itself, if you don't already have it. I've tested the code on Python 2.7.1 and 2.7.2., but will likely work on any and all Python 2.x versions. 66 | 67 | 6. Source Code Contributions 68 | ---------------------------- 69 | 70 | The source code is available under the BSD 4-clause license. If you have ideas, 71 | code, bug reports, or fixes you would like to contribute please do so. 72 | 73 | Bugs and feature requests can be filed at `Github `_. 74 | 75 | 7. Background 76 | ------------- 77 | 78 | Many production rule system implementations have been open-sourced, such as 79 | JBoss Drools, Rools, Jess, Lisa, et cetera. If you're familiar with the 80 | Drools syntax, Intellect's syntax should look familiar. (I'm not saying it 81 | is based on it, because it is not entirely, but I found as I was working 82 | the syntax I would check with Drools and if made sense to push in the 83 | direction of Drools, this is what I did.) The aforementioned implementations 84 | are available for other languages for expressing production rules, but it is 85 | my belief that Python is under-represented, and as such it was my thought the 86 | language and rule engine could benefit from being open sourced, and so I put 87 | a request in. 88 | 89 | The MITRE Corporation granted release August 4, 2011. 90 | 91 | Thus, releasing the domain-specific language (DSL) and Rule Engine to Open 92 | Source in the hopes doing so will extend its use and increase its chances 93 | for possible adoption, while at the same time mature the project with more 94 | interested eyeballs being placed on it. 95 | 96 | Starting out, it was initially assumed the aforementioned platform would 97 | be integrated with the best Open Source rules engine available for 98 | Python as there are countless implementation for Ruby, Java, and Perl, 99 | but surprisingly I found none fitting the project's needs. This led to 100 | the thought of inventing one; simply typing the keywords "python rules 101 | engine" into Google though will return to you the advice "to not invent 102 | yet another rules language", but instead you are advised to "just write 103 | your rules in Python, import them, and execute them." The basis for this 104 | advice can be coalesced down to doing so otherwise does not fit with the 105 | "Python Philosophy." At the time, I did not believe this to be true, nor 106 | fully contextualized, and yet admittedly, I had not yet authored a line 107 | of Python code (Yes, you're looking at my first Python program. So, 108 | please give me a break.) nor used ANTLR3 prior to this effort. Looking 109 | back, I firmly believe the act of inventing a rules engine and abstracting it 110 | behind a nomenclature that describes and illuminates a specific domain is 111 | the best way for in case of aforementioned platform the network defender 112 | to think about the problem. Like I said though the DSL and rules engine 113 | could be used for anything needing a "production rule system". 114 | 115 | As there were no rules engines available for Python fitting the platforms 116 | needs, a policy language and naive forward chaining rules engine were built 117 | from scratch. The policy language's grammar is based on a subset of Python 118 | language syntax. The policy DSL is parsed and lexed with the help of the 119 | ANTLR3 Parse Generator and Runtime for Python. 120 | 121 | 122 | 8. Facts (Data being reasoned over) 123 | ----------------------------------- 124 | 125 | The interpreter, the rules engine, and the remainder of the code such as 126 | objects for conferring discrete network conditions, referred to as "facts", 127 | are also authored in Python. Python's approach to the object-oriented programming 128 | paradigm, where objects consist of data fields and methods, did not easily 129 | lend itself to describing "facts". Because the data fields of a Python object 130 | referred to syntactically as "attributes" can and often are set on an 131 | instance of a class, they will not exist prior to a class's instantiation. 132 | In order for a rules engine to work, it must be able to fully introspect an 133 | object instance representing a condition. This proves to be very difficult 134 | unless the property decorator with its two attributes, "getter" and "setter", 135 | introduced in Python 2.6, are adopted and formally used for authoring these objects. 136 | Coincidentally, the use of the "Getter/Setter Pattern" used frequently in 137 | Java is singularly frowned upon in the Python developer community with the 138 | cheer of "Python is not Java". 139 | 140 | So, you will need to author your facts as Python object's who attributes 141 | are formally denoted as properties like so for the attributes you would like to 142 | reason over:: 143 | 144 | class ClassA(object): 145 | ''' 146 | An example fact 147 | ''' 148 | 149 | def __init__(self, property0 = None, property1 = None): 150 | ''' 151 | ClassA initializer 152 | ''' 153 | self._property0 = property0 154 | 155 | @property 156 | def property0(self): 157 | return self._property0 158 | 159 | @property0.setter 160 | def property0(self, value): 161 | self._property0 = value 162 | 163 | 9. The Policy DSL 164 | ----------------- 165 | 166 | Example with policy files can be found at the path `intellect/examples `_. 167 | Policy files must follow the Policy grammar as define in `intellect/grammar/Policy.g `_. 168 | The rest of this section documents the grammar of policy domain-specific language. 169 | 170 | 9.1 Import Statements (``ImportStmts``) 171 | --------------------------------------- 172 | 173 | Import statements basically follow Python's with a few limitations. For 174 | example, The wild card form of import is not supported for the reasons 175 | elaborated `here `_ 176 | and follow the Python 2.7.2 grammar. ``ImportStmt`` statements exist only at the same 177 | level of ``ruleStmt`` statements as per the grammar, and are typically at the top of a 178 | policy file, but are not limited to. In fact, if you break up your policy across several 179 | files the last imported as class or module wins as the one being named. 180 | 181 | .. _9.2: 182 | 183 | 9.2 Attribute Statements (``attribute``) 184 | ---------------------------------------- 185 | 186 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/attributeStmt.jpg 187 | 188 | The syntax diagram for a ``attributeStmt``. 189 | 190 | ``attributeStmt`` statements are expressions used to create policy attributes, a form of 191 | globals, that are accessible from rules. 192 | 193 | For example, a policy could be written:: 194 | 195 | import logging 196 | 197 | first_sum = 0 198 | second_sum = 0 199 | 200 | rule "set both first_sum and second_sum to 1": 201 | agenda-group "test_d" 202 | then: 203 | attribute (first_sum, second_sum) = (1,1) 204 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 205 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 206 | 207 | rule "add 2": 208 | agenda-group "test_d" 209 | then: 210 | attribute first_sum += 2 211 | attribute second_sum += 2 212 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 213 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 214 | 215 | rule "add 3": 216 | agenda-group "test_d" 217 | then: 218 | attribute first_sum += 3 219 | attribute second_sum += 3 220 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 221 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 222 | 223 | rule "add 4": 224 | agenda-group "test_d" 225 | then: 226 | attribute first_sum += 4 227 | attribute second_sum += 4 228 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 229 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 230 | halt 231 | 232 | rule "should never get here": 233 | agenda-group "test_d" 234 | then: 235 | log("Then how did I get here?", "example", logging.DEBUG) 236 | 237 | containing the two ``atributeStmt`` statements:: 238 | 239 | first_sum = 0 240 | second_sum = 0 241 | 242 | The following rules will increment these two attributes using ``attributeAction`` 243 | statements. 244 | 245 | Code to exercise this policy would look like so:: 246 | 247 | class MyIntellect(Intellect): 248 | pass 249 | 250 | if __name__ == "__main__": 251 | 252 | # set up logging for the example 253 | logger = logging.getLogger('example') 254 | logger.setLevel(logging.DEBUG) 255 | 256 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 257 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 258 | logger.addHandler(consoleHandler) 259 | 260 | myIntellect = MyIntellect() 261 | 262 | policy_d = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_d.policy")) 263 | 264 | myIntellect.reason(["test_d"]) 265 | 266 | and the logging output from the execution of the above would be:: 267 | 268 | 2011-10-04 23:56:51,681 example DEBUG __main__.MyIntellect :: first_sum is 1 269 | 2011-10-04 23:56:51,682 example DEBUG __main__.MyIntellect :: second_sum is 1 270 | 2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: first_sum is 3 271 | 2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: second_sum is 3 272 | 2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: first_sum is 6 273 | 2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: second_sum is 6 274 | 2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: first_sum is 10 275 | 2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: second_sum is 10 276 | 277 | See section 9.3.3.1.2_ ``attributeAction`` for another example. 278 | 279 | 9.3 Rule Statements (``ruleStmt``) 280 | ---------------------------------- 281 | 282 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/ruleStmt.jpg 283 | 284 | The syntax diagram for a ``ruleStmt``. 285 | 286 | A rule statement at its simplest looks like so:: 287 | 288 | rule "print": 289 | then: 290 | print("hello world!!!!") 291 | 292 | The rule ``"print"`` will always activate and output ``hello world!!!!`` to the 293 | ``sys.stdout``. 294 | 295 | A rule will always have an identifier (``id``) in either a ``NAME`` or ``STRING`` 296 | token form following Python's naming and ``String`` conventions. 297 | 298 | Generally, a rule will have both a ``when`` portion containing the condition 299 | of the rule, as of now a ``ruleCondition``, and an ``action`` described by the 300 | ``then`` portion. The ``action`` can be thought of in Python-terms as having more 301 | specifically a suite of one ore more actions. 302 | 303 | Depending on the evaluation of ``condition``, facts in knowledge will be matched 304 | and then operated over in the action of the rule. 305 | 306 | Such as in the rule ``"delete those that don't match"``, all facts in knowledge 307 | of type ``ClassD`` who's ``property1`` value is either a ``1`` or ``2`` or ``3`` 308 | will be deleted in action of the rule. 309 | 310 | :: 311 | 312 | from intellect.testing.ClassCandD import ClassD 313 | 314 | rule "delete those that don't match": 315 | when: 316 | not $bar := ClassD(property1 in [1,2,3]) 317 | then: 318 | delete $bar 319 | 320 | 9.3.1 ``agenda-group`` rule property 321 | ------------------------------------ 322 | 323 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/agendaGroup.jpg 324 | 325 | The syntax diagram for a ``agendaGroup``. 326 | 327 | Optionally, a rule may have an ``agenda-group`` property that allows it to be 328 | grouped in to agenda groups, and fired on an agenda. 329 | 330 | See sections 9.2_ ``attribute`` and 9.3.3.1.2_ ``attributeAction`` for examples 331 | of the use of this property. 332 | 333 | 9.3.2 When 334 | ---------- 335 | 336 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/when.jpg 337 | 338 | The syntax diagram for a ``when``. 339 | 340 | If present in rule, it defines the condition on which the rule will be activated. 341 | 342 | 9.3.2.1 Rule Condition (``condition``) 343 | -------------------------------------- 344 | 345 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/condition.jpg 346 | 347 | The syntax diagram for a ``condition``. 348 | 349 | A rule may have an optional condition, a boolean evaluation, on the state of objects 350 | in knowledge defined by a Class Constraint (``classConstraint``), and may be 351 | optionally prepended with ``exists`` as follows:: 352 | 353 | rule rule_c: 354 | when: 355 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 356 | then: 357 | print( "matches" + " exist" ) 358 | a = 1 359 | b = 2 360 | c = a + b 361 | print(c) 362 | test.helloworld() 363 | # call MyIntellect's bar method as it is decorated as callable 364 | bar() 365 | 366 | and thus the action will be called once if there are any object in memory matching 367 | the condition. The action statements ``modify`` and ``delete`` may not be used in 368 | the action if ``exists`` prepends the ``classContraint``. 369 | 370 | Currently, the DSL only supports a single ``classConstraint``, but work is ongoing 371 | to support more than one. 372 | 373 | 9.3.2.1.1 A Class Constraint (``classConstraint``) 374 | -------------------------------------------------- 375 | 376 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/classConstraint.jpg 377 | 378 | The syntax diagram for a ``classConsraint``. 379 | 380 | A ``classContraint`` defines how an objects in knowledge will be matched. It defines an 381 | ``OBJECTBINDING``, the Python name of the object's class and the optional ``constraint`` 382 | by which objects will be matched in knowledge. 383 | 384 | The ``OBJECTBINDING`` is a ``NAME`` token following Python's naming convention prepended 385 | with a dollar-sign (``$``). 386 | 387 | As in the case of the Rule Condition example:: 388 | 389 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 390 | 391 | 392 | ``$classB`` is the ``OBJECTBINDING`` that binds the matches of facts of type 393 | ``ClassB`` in knowledge matching the ``constraint``. 394 | 395 | An ``OBJECTBINDING`` can be further used in the action of the rule, but not in the 396 | case where the ``condition`` is prepended with ``exists`` as in the example. 397 | 398 | 9.3.2.1.2 A Constraint 399 | ---------------------- 400 | 401 | A ``constraint`` follows the same basic ``and``, ``or``, and ``not`` grammar that Python 402 | follows. 403 | 404 | As in the case of the Rule Condition example:: 405 | 406 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 407 | 408 | All ``ClassB`` type facts are matched in knowledge that have ``property1`` attributes 409 | that ``startwith`` ``apple``, and ``property2`` attributes greater than ``5`` before 410 | evaluated in hand with ``exist`` statement. More on the rest of the constraint follows 411 | in the sections below. 412 | 413 | 9.3.2.1.2.1 Using Regular Expressions 414 | ------------------------------------- 415 | 416 | You can also use regular expressions in constraint by simply importing the 417 | regular expression library straight from Python and then using like so as 418 | in the case of the Rule Condition example:: 419 | 420 | $classB := ClassB( re.search(r"\bapple\b", property1)!=None and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 421 | 422 | The regular expression ``r"\bapple\b"`` search is performed on ``property1`` of 423 | objects of type ``ClassB`` in knowledge. 424 | 425 | 9.3.2.1.2.2 Using Methods 426 | ------------------------- 427 | 428 | To rewrite a complicated ``constraint``: 429 | ```````````````````````````````````````` 430 | 431 | If you are writing a very complicated ``constraint`` consider moving the 432 | evaluation necessary for the ``constraint`` into a method of fact being 433 | reasoned over to increase readability. 434 | 435 | As in the case of the Rule Condition example, it could be rewritten to:: 436 | 437 | $classB := ClassB(property1ContainsTheStrApple() and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 438 | 439 | If you were to add the method to ClassB:: 440 | 441 | def property1ContainsTheStrApple() 442 | return re.search(r"\bapple\b", property1) != None 443 | 444 | Of a class and/or instance: 445 | ``````````````````````````` 446 | 447 | This example, also demonstrates how the ``test`` module function ``greaterThanTen`` 448 | can be messaged the instance's ``property2`` attribute and the function's return 449 | evaluated, and a call to the instance's ``aMethod`` method can be evaluated for 450 | a return of ``"a"``. 451 | 452 | 9.3.3 Then 453 | ---------- 454 | 455 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/then.jpg 456 | 457 | The syntax diagram for a ``then``. 458 | 459 | Is used to define the suite of one-or-more ``action`` statements to be called 460 | firing the rule, when the rule is said to be activated. 461 | 462 | 9.3.3.1 Rule Action (Suite of Actions) 463 | -------------------------------------- 464 | 465 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/action.jpg 466 | 467 | The syntax diagram for an ``action``. 468 | 469 | Rules may have a suite of one or more actions used in process of doing something, 470 | typically to achieve an aim. 471 | 472 | 9.3.3.1.1 Simple Statements (``simpleStmt``) 473 | -------------------------------------------- 474 | 475 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/simpleStmt.jpg 476 | 477 | The syntax diagram for a ``simpleStmt``. 478 | 479 | ``simpleStmts`` are supported actions of a rule, and so one can do the following:: 480 | 481 | rule rule_c: 482 | when: 483 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 484 | then: 485 | print("matches" + " exist") 486 | a = 1 487 | b = 2 488 | c = a + b 489 | print(c) 490 | test.helloworld() 491 | bar() 492 | 493 | The ``simpleStmt`` in the action will be executed if any facts in knowledge 494 | exist matching the condition. 495 | 496 | To keep the policy files from turning into just another Python script you 497 | will want to keep as little code out of the suite of actions and thus the policy 498 | file was possible... You will want to focus on using ``modify``, ``delete``, 499 | ``insert``, ``halt`` before heavily using large amounts of simple statements. This 500 | is why ``action`` supports a limited Python grammar. ``if``, ``for``, ``while`` etc 501 | are not supported, only Python's ``expressionStmt`` statements are supported. 502 | 503 | .. _9.3.3.1.2: 504 | 505 | 9.3.3.1.2 ``attributeAction`` 506 | ----------------------------- 507 | 508 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/attributeStmt.jpg 509 | 510 | The syntax diagram for a ``attributeStmt``. 511 | 512 | ``attributeAction`` actions are used to create, delete, or modify a policy 513 | attribute. 514 | 515 | For example:: 516 | 517 | i = 0 518 | 519 | rule rule_e: 520 | agenda-group "1" 521 | then: 522 | attribute i = i + 1 523 | print i 524 | 525 | rule rule_f: 526 | agenda-group "2" 527 | then: 528 | attribute i = i + 1 529 | print i 530 | 531 | rule rule_g: 532 | agenda-group "3" 533 | then: 534 | attribute i = i + 1 535 | print i 536 | 537 | rule rule_h: 538 | agenda-group "4" 539 | then: 540 | # the 'i' variable is scoped to then portion of the rule 541 | i = 0 542 | print i 543 | 544 | rule rule_i: 545 | agenda-group "5" 546 | then: 547 | attribute i += 1 548 | print i 549 | # the 'i' variable is scoped to then portion of the rule 550 | i = 0 551 | 552 | rule rule_j: 553 | agenda-group "6" 554 | then: 555 | attribute i += 1 556 | print i 557 | 558 | If the rules engine is instructed to reason seeking to activate 559 | rules on agenda in the order describe by the Python list 560 | ``["1", "2", "3", "4", "5", "6"]`` like so:: 561 | 562 | class MyIntellect(Intellect): 563 | pass 564 | 565 | if __name__ == "__main__": 566 | 567 | myIntellect = MyIntellect() 568 | 569 | policy_c = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_c.policy")) 570 | 571 | myIntellect.reason(["1", "2", "3", "4", "5", "6"]) 572 | 573 | The following output will result:: 574 | 575 | 1 576 | 2 577 | 3 578 | 0 579 | 4 580 | 5 581 | 582 | When firing ``rule_e`` the policy attribute ``i`` will be incremented by a value 583 | of ``1``, and print ``1``, same with ``rule_f`` and ``rule_g``, but ``rule_h`` 584 | prints 0. The reason for this is the ``i`` variable is scoped to ``then`` portion 585 | of the rule. ``Rule_i`` further illustrates scoping: the policy attribute ``i`` 586 | is further incremented by ``1`` and is printed, and then a variable ``i`` scoped to 587 | ``then`` portion of the rule initialized to ``0``, but this has no impact on 588 | the policy attribute ``i`` for when ``rule_j`` action is executed firing the rule 589 | the value of ``6`` is printed. 590 | 591 | 9.3.3.1.3 ``learn`` action 592 | -------------------------- 593 | 594 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/learnAction.jpg 595 | :scale: 50 % 596 | 597 | The syntax diagram for a ``learnAction``. 598 | 599 | A rule entitled ``"Time to buy new sheep?"`` might look like the following:: 600 | 601 | rule "Time to buy new sheep?": 602 | when: 603 | $buyOrder := BuyOrder( ) 604 | then: 605 | print( "Buying a new sheep." ) 606 | modify $buyOrder: 607 | count = $buyOrder.count - 1 608 | learn BlackSheep() 609 | 610 | The rule above illustrates the use of a ``learn`` action to learn/insert 611 | a ``BlackSheep`` fact. The same rule can also be written as the following 612 | using ``insert``:: 613 | 614 | rule "Time to buy new sheep?": 615 | when: 616 | $buyOrder := BuyOrder( ) 617 | then: 618 | print( "Buying a new sheep." ) 619 | modify $buyOrder: 620 | count = $buyOrder.count - 1 621 | insert BlackSheep() 622 | 623 | 9.3.3.1.4 ``forget`` action 624 | --------------------------- 625 | 626 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/forgetAction.jpg 627 | 628 | The syntax diagram for a ``forgetAction``. 629 | 630 | 631 | A rule entitled ``"Remove empty buy orders"`` might look like the following:: 632 | 633 | rule "Remove empty buy orders": 634 | when: 635 | $buyOrder := BuyOrder( count == 0 ) 636 | then: 637 | forget $buyOrder 638 | 639 | 640 | The rule above illustrates the use of a ``forget`` action to forget/delete 641 | each match returned by the rule's condition. The same rule can also be written 642 | as the following using ``delete``:: 643 | 644 | rule "Remove empty buy orders": 645 | when: 646 | $buyOrder := BuyOrder( count == 0 ) 647 | then: 648 | delete $buyOrder 649 | 650 | Note: cannot be used in conjunction with ``exists``. 651 | 652 | 9.3.3.1.5 ``modify`` action 653 | --------------------------- 654 | 655 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/modifyAction.jpg 656 | 657 | The syntax diagram for a ``modifyAction``. 658 | 659 | The following rule:: 660 | 661 | rule "Time to buy new sheep?": 662 | when: 663 | $buyOrder := BuyOrder( ) 664 | then: 665 | print( "Buying a new sheep." ) 666 | modify $buyOrder: 667 | count = $buyOrder.count - 1 668 | learn BlackSheep() 669 | 670 | 671 | illustrates the use of a ``modify`` action to modify each ``BuyOrder`` match 672 | returned by the rule's condition. Cannot be used in conjunction with ``exists`` 673 | rule conditions. The ``modify`` action can also be used to chain rules, what 674 | you do is modify the fact (toggle a boolean property, set a property's value, 675 | etc) and then use this property to evaluate in the proceeding rule. 676 | 677 | 678 | 9.3.3.1.6 ``halt`` action 679 | ------------------------- 680 | 681 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/haltAction.jpg 682 | 683 | The syntax diagram for a ``haltAction``. 684 | 685 | The following rule:: 686 | 687 | rule "End policy": 688 | then: 689 | log("Finished reasoning over policy.", "example", logging.DEBUG) 690 | halt 691 | 692 | illustrates the use of a ``halt`` action to tell the rules engine to halt 693 | reasoning over the policy. 694 | 695 | 10. Creating and using a Rules Engine with a single policy 696 | --------------------------------------------------------- 697 | 698 | At its simplest a rules engine can be created and used like so:: 699 | 700 | import sys, logging 701 | 702 | from intellect.Intellect import Intellect 703 | from intellect.Intellect import Callable 704 | 705 | # set up logging 706 | logging.basicConfig(level=logging.DEBUG, 707 | format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s', stream=sys.stdout) 708 | 709 | intellect = Intellect() 710 | 711 | policy_a = intellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy")) 712 | 713 | intellect.reason() 714 | 715 | intellect.forget_all() 716 | 717 | 718 | It may be preferable for you to sub-class ``intellect.Intellect.Intellect`` class in 719 | order to add ``@Callable`` decorated methods that will in turn permit these methods 720 | to be called from the action of the rule. 721 | 722 | For example, ``MyIntellect`` is created to sub-class ``Intellect``:: 723 | 724 | import sys, logging 725 | 726 | from intellect.Intellect import Intellect 727 | from intellect.Intellect import Callable 728 | 729 | class MyIntellect(Intellect): 730 | 731 | @Callable 732 | def bar(self): 733 | self.log(logging.DEBUG, ">>>>>>>>>>>>>> called MyIntellect's bar method as it was decorated as callable.") 734 | 735 | if __name__ == "__main__": 736 | 737 | # set up logging 738 | logging.basicConfig(level=logging.DEBUG, 739 | format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s', 740 | #filename="rules.log") 741 | stream=sys.stdout) 742 | 743 | print "*"*80 744 | print """create an instance of MyIntellect extending Intellect, create some facts, and exercise the MyIntellect's ability to learn and forget""" 745 | print "*"*80 746 | 747 | myIntellect = MyIntellect() 748 | 749 | policy_a = myIntellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy")) 750 | 751 | myIntellect.reason() 752 | 753 | myIntellect.forget_all() 754 | 755 | 756 | The policy could then be authored, where the ``MyIntellect`` class's ``bar`` method 757 | is called for matches to the rule condition, like so:: 758 | 759 | from intellect.testing.subModule.ClassB import ClassB 760 | import intellect.testing.Test as Test 761 | import logging 762 | 763 | fruits_of_interest = ["apple", "grape", "mellon", "pear"] 764 | count = 5 765 | 766 | rule rule_a: 767 | agenda-group test_a 768 | when: 769 | $classB := ClassB( property1 in fruits_of_interest and property2>count ) 770 | then: 771 | # mark the 'ClassB' matches in memory as modified 772 | modify $classB: 773 | property1 = $classB.property1 + " pie" 774 | modified = True 775 | # increment the match's 'property2' value by 1000 776 | property2 = $classB.property2 + 1000 777 | 778 | attribute count = $classB.property2 779 | print "count = {0}".format( count ) 780 | 781 | # call MyIntellect's bar method as it is decorated as callable 782 | bar() 783 | log(logging.DEBUG, "rule_a fired") 784 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Intellect 2 | ========= 3 | 4 | :Info: Intellect is a Domain-specific language and Rules Engine for Python. 5 | 6 | :Author: Michael Joseph Walsh 7 | 8 | 1. What is Intellect 9 | -------------------- 10 | 11 | Intellect is a DSL ("Domain-Specific Language") and Rule Engine for Python 12 | I authored for expressing policies to orchestrate and control a dynamic 13 | network defense cyber-security platform being researched in The 14 | MITRE Corporation's Innovation Program. 15 | 16 | The rules engine provides an intellect, a form of artificial intelligence, 17 | a faculty of reasoning and understanding objectively over a working memory. 18 | The memory retains knowledge relevant to the system, and a set of rules 19 | authored in the DSL that describe a necessary behavior to achieve some 20 | goal. Each rule has an optional condition, and a suite of one or more 21 | actions. These actions either further direct the behavior of the system, 22 | and/or further inform the system. The engine starts with some facts, 23 | truths known about past or present circumstances, and uses rules to infer 24 | more facts. These facts fire more rules, that infer more facts and so 25 | on. 26 | 27 | For the platform in the Innovation Program, the network defender uses 28 | the DSL to confer policy, how the platform is to respond to network 29 | events mounted over covert network channels, but there are no direct 30 | ties written into the language nor the rule engine to cyber security 31 | and thus the system in its entirety can be more broadly used in 32 | other domains. 33 | 34 | 2. TODOS 35 | -------- 36 | 37 | There are number of improvements I would like to work for future releases: 38 | 39 | * Add Support for Multiple Rule Conditions. 40 | * The ANTLR Runtime for Python dependency was left behind with the the release of ANTL4, I would like to port the present Runtime for Java to Python and use. 41 | * Support Python 3. 42 | 43 | Please help support these efforts. 44 | 45 | .. image:: https://github.com/nemonik/Intellect/raw/master/images/gittip.png 46 | :target: https://www.gittip.com/nemonik/ 47 | 48 | 3. Intellect In The News 49 | --------------------- 50 | 51 | The September 2013 issue, Volume 37 of Elsevier's "Computers and Security" contains a journal article entitled "`Active cyber defense with denial and deception: A cyber-wargame experiment `_" describing a computer network security use case for Intellect. 52 | 53 | 4. Installation 54 | --------------- 55 | 56 | * To install via `setuptools `_ use ``easy_install -U Intellect`` 57 | * To install via `pip `_ use ``pip install Intellect`` 58 | * To install via `pypm `_ use ``pypm install intellect`` 59 | * Or download the latest source from `Master `_ or the most recent tagged release `Tagged Releases `_, unpack, and run ``python setup.py install`` 60 | 61 | 5. Dependencies 62 | --------------- 63 | 64 | * ANTLR3 Python Runtime 3.1.3 (Will contrain you to Python 2.x.) 65 | * Python itself, if you don't already have it. I've tested the code on Python 2.7.1 and 2.7.2., but will likely work on any and all Python 2.x versions. Python 3 at prestent is not supported, because ANTLR3 appears not to support Python 3. 66 | 67 | 6. Source Code Contributions 68 | ---------------------------- 69 | 70 | The source code is available under the BSD 4-clause license. If you have ideas, 71 | code, bug reports, or fixes you would like to contribute please do so. 72 | 73 | Bugs and feature requests can be filed at `Github `_. 74 | 75 | 7. Background 76 | ------------- 77 | 78 | Many production rule system implementations have been open-sourced, such as 79 | JBoss Drools, Rools, Jess, Lisa, et cetera. If you're familiar with the 80 | Drools syntax, Intellect's syntax should look familiar. (I'm not saying it 81 | is based on it, because it is not entirely, but I found as I was working 82 | the syntax I would check with Drools and if made sense to push in the 83 | direction of Drools, this is what I did.) The aforementioned implementations 84 | are available for other languages for expressing production rules, but it is 85 | my belief that Python is under-represented, and as such it was my thought the 86 | language and rule engine could benefit from being open sourced, and so I put 87 | a request in. 88 | 89 | The MITRE Corporation granted release August 4, 2011. 90 | 91 | Thus, releasing the domain-specific language (DSL) and Rule Engine to Open 92 | Source in the hopes doing so will extend its use and increase its chances 93 | for possible adoption, while at the same time mature the project with more 94 | interested eyeballs being placed on it. 95 | 96 | Starting out, it was initially assumed the aforementioned platform would 97 | be integrated with the best Open Source rules engine available for 98 | Python as there are countless implementation for Ruby, Java, and Perl, 99 | but surprisingly I found none fitting the project's needs. This led to 100 | the thought of inventing one; simply typing the keywords "python rules 101 | engine" into Google though will return to you the advice "to not invent 102 | yet another rules language", but instead you are advised to "just write 103 | your rules in Python, import them, and execute them." The basis for this 104 | advice can be coalesced down to doing so otherwise does not fit with the 105 | "Python Philosophy." At the time, I did not believe this to be true, nor 106 | fully contextualized, and yet admittedly, I had not yet authored a line 107 | of Python code (Yes, you're looking at my first Python program. So, 108 | please give me a break.) nor used ANTLR3 prior to this effort. Looking 109 | back, I firmly believe the act of inventing a rules engine and abstracting it 110 | behind a nomenclature that describes and illuminates a specific domain is 111 | the best way for in case of aforementioned platform the network defender 112 | to think about the problem. Like I said though the DSL and rules engine 113 | could be used for anything needing a "production rule system". 114 | 115 | As there were no rules engines available for Python fitting the platforms 116 | needs, a policy language and naive forward chaining rules engine were built 117 | from scratch. The policy language's grammar is based on a subset of Python 118 | language syntax. The policy DSL is parsed and lexed with the help of the 119 | ANTLR3 Parse Generator and Runtime for Python. 120 | 121 | 122 | 8. Facts (Data being reasoned over) 123 | ----------------------------------- 124 | 125 | The interpreter, the rules engine, and the remainder of the code such as 126 | objects for conferring discrete network conditions, referred to as "facts", 127 | are also authored in Python. Python's approach to the object-oriented programming 128 | paradigm, where objects consist of data fields and methods, did not easily 129 | lend itself to describing "facts". Because the data fields of a Python object 130 | referred to syntactically as "attributes" can and often are set on an 131 | instance of a class, they will not exist prior to a class's instantiation. 132 | In order for a rules engine to work, it must be able to fully introspect an 133 | object instance representing a condition. This proves to be very difficult 134 | unless the property decorator with its two attributes, "getter" and "setter", 135 | introduced in Python 2.6, are adopted and formally used for authoring these objects. 136 | Coincidentally, the use of the "Getter/Setter Pattern" used frequently in 137 | Java is singularly frowned upon in the Python developer community with the 138 | cheer of "Python is not Java". 139 | 140 | So, you will need to author your facts as Python object's who attributes 141 | are formally denoted as properties like so for the attributes you would like to 142 | reason over:: 143 | 144 | class ClassA(object): 145 | ''' 146 | An example fact 147 | ''' 148 | 149 | def __init__(self, property0 = None, property1 = None): 150 | ''' 151 | ClassA initializer 152 | ''' 153 | self._property0 = property0 154 | 155 | @property 156 | def property0(self): 157 | return self._property0 158 | 159 | @property0.setter 160 | def property0(self, value): 161 | self._property0 = value 162 | 163 | 9. The Policy DSL 164 | ----------------- 165 | 166 | Example with policy files can be found at the path `intellect/examples `_. 167 | Policy files must follow the Policy grammar as define in `intellect/grammar/Policy.g `_. 168 | The rest of this section documents the grammar of policy domain-specific language. 169 | 170 | 9.1 Import Statements (``ImportStmts``) 171 | --------------------------------------- 172 | 173 | Import statements basically follow Python's with a few limitations. For 174 | example, The wild card form of import is not supported for the reasons 175 | elaborated `here `_ 176 | and follow the Python 2.7.2 grammar. ``ImportStmt`` statements exist only at the same 177 | level of ``ruleStmt`` statements as per the grammar, and are typically at the top of a 178 | policy file, but are not limited to. In fact, if you break up your policy across several 179 | files the last imported as class or module wins as the one being named. 180 | 181 | .. _9.2: 182 | 183 | 9.2 Attribute Statements (``attribute``) 184 | ---------------------------------------- 185 | 186 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/attributeStmt.jpg 187 | 188 | The syntax diagram for a ``attributeStmt``. 189 | 190 | ``attributeStmt`` statements are expressions used to create policy attributes, a form of 191 | globals, that are accessible from rules. 192 | 193 | For example, a policy could be written:: 194 | 195 | import logging 196 | 197 | first_sum = 0 198 | second_sum = 0 199 | 200 | rule "set both first_sum and second_sum to 1": 201 | agenda-group "test_d" 202 | then: 203 | attribute (first_sum, second_sum) = (1,1) 204 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 205 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 206 | 207 | rule "add 2": 208 | agenda-group "test_d" 209 | then: 210 | attribute first_sum += 2 211 | attribute second_sum += 2 212 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 213 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 214 | 215 | rule "add 3": 216 | agenda-group "test_d" 217 | then: 218 | attribute first_sum += 3 219 | attribute second_sum += 3 220 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 221 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 222 | 223 | rule "add 4": 224 | agenda-group "test_d" 225 | then: 226 | attribute first_sum += 4 227 | attribute second_sum += 4 228 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 229 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 230 | halt 231 | 232 | rule "should never get here": 233 | agenda-group "test_d" 234 | then: 235 | log("Then how did I get here?", "example", logging.DEBUG) 236 | 237 | containing the two ``atributeStmt`` statements:: 238 | 239 | first_sum = 0 240 | second_sum = 0 241 | 242 | The following rules will increment these two attributes using ``attributeAction`` 243 | statements. 244 | 245 | Code to exercise this policy would look like so:: 246 | 247 | class MyIntellect(Intellect): 248 | pass 249 | 250 | if __name__ == "__main__": 251 | 252 | # set up logging for the example 253 | logger = logging.getLogger('example') 254 | logger.setLevel(logging.DEBUG) 255 | 256 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 257 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 258 | logger.addHandler(consoleHandler) 259 | 260 | myIntellect = MyIntellect() 261 | 262 | policy_d = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_d.policy")) 263 | 264 | myIntellect.reason(["test_d"]) 265 | 266 | and the logging output from the execution of the above would be:: 267 | 268 | 2011-10-04 23:56:51,681 example DEBUG __main__.MyIntellect :: first_sum is 1 269 | 2011-10-04 23:56:51,682 example DEBUG __main__.MyIntellect :: second_sum is 1 270 | 2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: first_sum is 3 271 | 2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: second_sum is 3 272 | 2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: first_sum is 6 273 | 2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: second_sum is 6 274 | 2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: first_sum is 10 275 | 2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: second_sum is 10 276 | 277 | See section 9.3.3.1.2_ ``attributeAction`` for another example. 278 | 279 | 9.3 Rule Statements (``ruleStmt``) 280 | ---------------------------------- 281 | 282 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/ruleStmt.jpg 283 | 284 | The syntax diagram for a ``ruleStmt``. 285 | 286 | A rule statement at its simplest looks like so:: 287 | 288 | rule "print": 289 | then: 290 | print("hello world!!!!") 291 | 292 | The rule ``"print"`` will always activate and output ``hello world!!!!`` to the 293 | ``sys.stdout``. 294 | 295 | A rule will always have an identifier (``id``) in either a ``NAME`` or ``STRING`` 296 | token form following Python's naming and ``String`` conventions. 297 | 298 | Generally, a rule will have both a ``when`` portion containing the condition 299 | of the rule, as of now a ``ruleCondition``, and an ``action`` described by the 300 | ``then`` portion. The ``action`` can be thought of in Python-terms as having more 301 | specifically a suite of one ore more actions. 302 | 303 | Depending on the evaluation of ``condition``, facts in knowledge will be matched 304 | and then operated over in the action of the rule. 305 | 306 | Such as in the rule ``"delete those that don't match"``, all facts in knowledge 307 | of type ``ClassD`` who's ``property1`` value is either a ``1`` or ``2`` or ``3`` 308 | will be deleted in action of the rule. 309 | 310 | :: 311 | 312 | from intellect.testing.ClassCandD import ClassD 313 | 314 | rule "delete those that don't match": 315 | when: 316 | not $bar := ClassD(property1 in [1,2,3]) 317 | then: 318 | delete $bar 319 | 320 | 9.3.1 ``agenda-group`` rule property 321 | ------------------------------------ 322 | 323 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/agendaGroup.jpg 324 | 325 | The syntax diagram for a ``agendaGroup``. 326 | 327 | Optionally, a rule may have an ``agenda-group`` property that allows it to be 328 | grouped in to agenda groups, and fired on an agenda. 329 | 330 | See sections 9.2_ ``attribute`` and 9.3.3.1.2_ ``attributeAction`` for examples 331 | of the use of this property. 332 | 333 | 9.3.2 When 334 | ---------- 335 | 336 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/when.jpg 337 | 338 | The syntax diagram for a ``when``. 339 | 340 | If present in rule, it defines the condition on which the rule will be activated. 341 | 342 | 9.3.2.1 Rule Condition (``condition``) 343 | -------------------------------------- 344 | 345 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/condition.jpg 346 | 347 | The syntax diagram for a ``condition``. 348 | 349 | A rule may have an optional condition, a boolean evaluation, on the state of objects 350 | in knowledge defined by a Class Constraint (``classConstraint``), and may be 351 | optionally prepended with ``exists`` as follows:: 352 | 353 | rule rule_c: 354 | when: 355 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 356 | then: 357 | print( "matches" + " exist" ) 358 | a = 1 359 | b = 2 360 | c = a + b 361 | print(c) 362 | test.helloworld() 363 | # call MyIntellect's bar method as it is decorated as callable 364 | bar() 365 | 366 | and thus the action will be called once if there are any object in memory matching 367 | the condition. The action statements ``modify`` and ``delete`` may not be used in 368 | the action if ``exists`` prepends the ``classContraint``. 369 | 370 | Currently, the DSL only supports a single ``classConstraint``, but work is ongoing 371 | to support more than one. 372 | 373 | 9.3.2.1.1 A Class Constraint (``classConstraint``) 374 | -------------------------------------------------- 375 | 376 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/classConstraint.jpg 377 | 378 | The syntax diagram for a ``classConsraint``. 379 | 380 | A ``classContraint`` defines how an objects in knowledge will be matched. It defines an 381 | ``OBJECTBINDING``, the Python name of the object's class and the optional ``constraint`` 382 | by which objects will be matched in knowledge. 383 | 384 | The ``OBJECTBINDING`` is a ``NAME`` token following Python's naming convention prepended 385 | with a dollar-sign (``$``). 386 | 387 | As in the case of the Rule Condition example:: 388 | 389 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 390 | 391 | 392 | ``$classB`` is the ``OBJECTBINDING`` that binds the matches of facts of type 393 | ``ClassB`` in knowledge matching the ``constraint``. 394 | 395 | An ``OBJECTBINDING`` can be further used in the action of the rule, but not in the 396 | case where the ``condition`` is prepended with ``exists`` as in the example. 397 | 398 | 9.3.2.1.2 A Constraint 399 | ---------------------- 400 | 401 | A ``constraint`` follows the same basic ``and``, ``or``, and ``not`` grammar that Python 402 | follows. 403 | 404 | As in the case of the Rule Condition example:: 405 | 406 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 407 | 408 | All ``ClassB`` type facts are matched in knowledge that have ``property1`` attributes 409 | that ``startwith`` ``apple``, and ``property2`` attributes greater than ``5`` before 410 | evaluated in hand with ``exist`` statement. More on the rest of the constraint follows 411 | in the sections below. 412 | 413 | 9.3.2.1.2.1 Using Regular Expressions 414 | ------------------------------------- 415 | 416 | You can also use regular expressions in constraint by simply importing the 417 | regular expression library straight from Python and then using like so as 418 | in the case of the Rule Condition example:: 419 | 420 | $classB := ClassB( re.search(r"\bapple\b", property1)!=None and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 421 | 422 | The regular expression ``r"\bapple\b"`` search is performed on ``property1`` of 423 | objects of type ``ClassB`` in knowledge. 424 | 425 | 9.3.2.1.2.2 Using Methods 426 | ------------------------- 427 | 428 | To rewrite a complicated ``constraint``: 429 | ```````````````````````````````````````` 430 | 431 | If you are writing a very complicated ``constraint`` consider moving the 432 | evaluation necessary for the ``constraint`` into a method of fact being 433 | reasoned over to increase readability. 434 | 435 | As in the case of the Rule Condition example, it could be rewritten to:: 436 | 437 | $classB := ClassB(property1ContainsTheStrApple() and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 438 | 439 | If you were to add the method to ClassB:: 440 | 441 | def property1ContainsTheStrApple() 442 | return re.search(r"\bapple\b", property1) != None 443 | 444 | Of a class and/or instance: 445 | ``````````````````````````` 446 | 447 | This example, also demonstrates how the ``test`` module function ``greaterThanTen`` 448 | can be messaged the instance's ``property2`` attribute and the function's return 449 | evaluated, and a call to the instance's ``aMethod`` method can be evaluated for 450 | a return of ``"a"``. 451 | 452 | 9.3.3 Then 453 | ---------- 454 | 455 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/then.jpg 456 | 457 | The syntax diagram for a ``then``. 458 | 459 | Is used to define the suite of one-or-more ``action`` statements to be called 460 | firing the rule, when the rule is said to be activated. 461 | 462 | 9.3.3.1 Rule Action (Suite of Actions) 463 | -------------------------------------- 464 | 465 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/action.jpg 466 | 467 | The syntax diagram for an ``action``. 468 | 469 | Rules may have a suite of one or more actions used in process of doing something, 470 | typically to achieve an aim. 471 | 472 | 9.3.3.1.1 Simple Statements (``simpleStmt``) 473 | -------------------------------------------- 474 | 475 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/simpleStmt.jpg 476 | 477 | The syntax diagram for a ``simpleStmt``. 478 | 479 | ``simpleStmts`` are supported actions of a rule, and so one can do the following:: 480 | 481 | rule rule_c: 482 | when: 483 | exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 484 | then: 485 | print("matches" + " exist") 486 | a = 1 487 | b = 2 488 | c = a + b 489 | print(c) 490 | test.helloworld() 491 | bar() 492 | 493 | The ``simpleStmt`` in the action will be executed if any facts in knowledge 494 | exist matching the condition. 495 | 496 | To keep the policy files from turning into just another Python script you 497 | will want to keep as little code out of the suite of actions and thus the policy 498 | file was possible... You will want to focus on using ``modify``, ``delete``, 499 | ``insert``, ``halt`` before heavily using large amounts of simple statements. This 500 | is why ``action`` supports a limited Python grammar. ``if``, ``for``, ``while`` etc 501 | are not supported, only Python's ``expressionStmt`` statements are supported. 502 | 503 | .. _9.3.3.1.2: 504 | 505 | 9.3.3.1.2 ``attributeAction`` 506 | ----------------------------- 507 | 508 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/attributeStmt.jpg 509 | 510 | The syntax diagram for a ``attributeStmt``. 511 | 512 | ``attributeAction`` actions are used to create, delete, or modify a policy 513 | attribute. 514 | 515 | For example:: 516 | 517 | i = 0 518 | 519 | rule rule_e: 520 | agenda-group "1" 521 | then: 522 | attribute i = i + 1 523 | print i 524 | 525 | rule rule_f: 526 | agenda-group "2" 527 | then: 528 | attribute i = i + 1 529 | print i 530 | 531 | rule rule_g: 532 | agenda-group "3" 533 | then: 534 | attribute i = i + 1 535 | print i 536 | 537 | rule rule_h: 538 | agenda-group "4" 539 | then: 540 | # the 'i' variable is scoped to then portion of the rule 541 | i = 0 542 | print i 543 | 544 | rule rule_i: 545 | agenda-group "5" 546 | then: 547 | attribute i += 1 548 | print i 549 | # the 'i' variable is scoped to then portion of the rule 550 | i = 0 551 | 552 | rule rule_j: 553 | agenda-group "6" 554 | then: 555 | attribute i += 1 556 | print i 557 | 558 | If the rules engine is instructed to reason seeking to activate 559 | rules on agenda in the order describe by the Python list 560 | ``["1", "2", "3", "4", "5", "6"]`` like so:: 561 | 562 | class MyIntellect(Intellect): 563 | pass 564 | 565 | if __name__ == "__main__": 566 | 567 | myIntellect = MyIntellect() 568 | 569 | policy_c = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_c.policy")) 570 | 571 | myIntellect.reason(["1", "2", "3", "4", "5", "6"]) 572 | 573 | The following output will result:: 574 | 575 | 1 576 | 2 577 | 3 578 | 0 579 | 4 580 | 5 581 | 582 | When firing ``rule_e`` the policy attribute ``i`` will be incremented by a value 583 | of ``1``, and print ``1``, same with ``rule_f`` and ``rule_g``, but ``rule_h`` 584 | prints 0. The reason for this is the ``i`` variable is scoped to ``then`` portion 585 | of the rule. ``Rule_i`` further illustrates scoping: the policy attribute ``i`` 586 | is further incremented by ``1`` and is printed, and then a variable ``i`` scoped to 587 | ``then`` portion of the rule initialized to ``0``, but this has no impact on 588 | the policy attribute ``i`` for when ``rule_j`` action is executed firing the rule 589 | the value of ``6`` is printed. 590 | 591 | 9.3.3.1.3 ``learn`` action 592 | -------------------------- 593 | 594 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/learnAction.jpg 595 | :scale: 50 % 596 | 597 | The syntax diagram for a ``learnAction``. 598 | 599 | A rule entitled ``"Time to buy new sheep?"`` might look like the following:: 600 | 601 | rule "Time to buy new sheep?": 602 | when: 603 | $buyOrder := BuyOrder( ) 604 | then: 605 | print( "Buying a new sheep." ) 606 | modify $buyOrder: 607 | count = $buyOrder.count - 1 608 | learn BlackSheep() 609 | 610 | The rule above illustrates the use of a ``learn`` action to learn/insert 611 | a ``BlackSheep`` fact. The same rule can also be written as the following 612 | using ``insert``:: 613 | 614 | rule "Time to buy new sheep?": 615 | when: 616 | $buyOrder := BuyOrder( ) 617 | then: 618 | print( "Buying a new sheep." ) 619 | modify $buyOrder: 620 | count = $buyOrder.count - 1 621 | insert BlackSheep() 622 | 623 | 9.3.3.1.4 ``forget`` action 624 | --------------------------- 625 | 626 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/forgetAction.jpg 627 | 628 | The syntax diagram for a ``forgetAction``. 629 | 630 | 631 | A rule entitled ``"Remove empty buy orders"`` might look like the following:: 632 | 633 | rule "Remove empty buy orders": 634 | when: 635 | $buyOrder := BuyOrder( count == 0 ) 636 | then: 637 | forget $buyOrder 638 | 639 | 640 | The rule above illustrates the use of a ``forget`` action to forget/delete 641 | each match returned by the rule's condition. The same rule can also be written 642 | as the following using ``delete``:: 643 | 644 | rule "Remove empty buy orders": 645 | when: 646 | $buyOrder := BuyOrder( count == 0 ) 647 | then: 648 | delete $buyOrder 649 | 650 | Note: cannot be used in conjunction with ``exists``. 651 | 652 | 9.3.3.1.5 ``modify`` action 653 | --------------------------- 654 | 655 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/modifyAction.jpg 656 | 657 | The syntax diagram for a ``modifyAction``. 658 | 659 | The following rule:: 660 | 661 | rule "Time to buy new sheep?": 662 | when: 663 | $buyOrder := BuyOrder( ) 664 | then: 665 | print( "Buying a new sheep." ) 666 | modify $buyOrder: 667 | count = $buyOrder.count - 1 668 | learn BlackSheep() 669 | 670 | 671 | illustrates the use of a ``modify`` action to modify each ``BuyOrder`` match 672 | returned by the rule's condition. Cannot be used in conjunction with ``exists`` 673 | rule conditions. The ``modify`` action can also be used to chain rules, what 674 | you do is modify the fact (toggle a boolean property, set a property's value, 675 | etc) and then use this property to evaluate in the proceeding rule. 676 | 677 | 678 | 9.3.3.1.6 ``halt`` action 679 | ------------------------- 680 | 681 | .. figure:: https://github.com/nemonik/Intellect/raw/master/images/haltAction.jpg 682 | 683 | The syntax diagram for a ``haltAction``. 684 | 685 | The following rule:: 686 | 687 | rule "End policy": 688 | then: 689 | log("Finished reasoning over policy.", "example", logging.DEBUG) 690 | halt 691 | 692 | illustrates the use of a ``halt`` action to tell the rules engine to halt 693 | reasoning over the policy. 694 | 695 | 10. Creating and using a Rules Engine with a single policy 696 | --------------------------------------------------------- 697 | 698 | At its simplest a rules engine can be created and used like so:: 699 | 700 | import sys, logging 701 | 702 | from intellect.Intellect import Intellect 703 | from intellect.Intellect import Callable 704 | 705 | # set up logging 706 | logging.basicConfig(level=logging.DEBUG, 707 | format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s', stream=sys.stdout) 708 | 709 | intellect = Intellect() 710 | 711 | policy_a = intellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy")) 712 | 713 | intellect.reason() 714 | 715 | intellect.forget_all() 716 | 717 | 718 | It may be preferable for you to sub-class ``intellect.Intellect.Intellect`` class in 719 | order to add ``@Callable`` decorated methods that will in turn permit these methods 720 | to be called from the action of the rule. 721 | 722 | For example, ``MyIntellect`` is created to sub-class ``Intellect``:: 723 | 724 | import sys, logging 725 | 726 | from intellect.Intellect import Intellect 727 | from intellect.Intellect import Callable 728 | 729 | class MyIntellect(Intellect): 730 | 731 | @Callable 732 | def bar(self): 733 | self.log(logging.DEBUG, ">>>>>>>>>>>>>> called MyIntellect's bar method as it was decorated as callable.") 734 | 735 | if __name__ == "__main__": 736 | 737 | # set up logging 738 | logging.basicConfig(level=logging.DEBUG, 739 | format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s', 740 | #filename="rules.log") 741 | stream=sys.stdout) 742 | 743 | print "*"*80 744 | print """create an instance of MyIntellect extending Intellect, create some facts, and exercise the MyIntellect's ability to learn and forget""" 745 | print "*"*80 746 | 747 | myIntellect = MyIntellect() 748 | 749 | policy_a = myIntellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy")) 750 | 751 | myIntellect.reason() 752 | 753 | myIntellect.forget_all() 754 | 755 | 756 | The policy could then be authored, where the ``MyIntellect`` class's ``bar`` method 757 | is called for matches to the rule condition, like so:: 758 | 759 | from intellect.testing.subModule.ClassB import ClassB 760 | import intellect.testing.Test as Test 761 | import logging 762 | 763 | fruits_of_interest = ["apple", "grape", "mellon", "pear"] 764 | count = 5 765 | 766 | rule rule_a: 767 | agenda-group test_a 768 | when: 769 | $classB := ClassB( property1 in fruits_of_interest and property2>count ) 770 | then: 771 | # mark the 'ClassB' matches in memory as modified 772 | modify $classB: 773 | property1 = $classB.property1 + " pie" 774 | modified = True 775 | # increment the match's 'property2' value by 1000 776 | property2 = $classB.property2 + 1000 777 | 778 | attribute count = $classB.property2 779 | print "count = {0}".format( count ) 780 | 781 | # call MyIntellect's bar method as it is decorated as callable 782 | bar() 783 | log(logging.DEBUG, "rule_a fired") 784 | -------------------------------------------------------------------------------- /images/action.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/action.jpg -------------------------------------------------------------------------------- /images/agendaGroup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/agendaGroup.jpg -------------------------------------------------------------------------------- /images/attributeActrion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/attributeActrion.jpg -------------------------------------------------------------------------------- /images/attributeStmt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/attributeStmt.jpg -------------------------------------------------------------------------------- /images/classConstraint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/classConstraint.jpg -------------------------------------------------------------------------------- /images/condition.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/condition.jpg -------------------------------------------------------------------------------- /images/forgetAction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/forgetAction.jpg -------------------------------------------------------------------------------- /images/gittip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/gittip.png -------------------------------------------------------------------------------- /images/haltAction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/haltAction.jpg -------------------------------------------------------------------------------- /images/learnAction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/learnAction.jpg -------------------------------------------------------------------------------- /images/modifyAction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/modifyAction.jpg -------------------------------------------------------------------------------- /images/ruleStmt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/ruleStmt.jpg -------------------------------------------------------------------------------- /images/simpleStmt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/simpleStmt.jpg -------------------------------------------------------------------------------- /images/then.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/then.jpg -------------------------------------------------------------------------------- /images/when.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemonik/Intellect/4fdc9eb00f874900a4353333e1026cf7f4d1dba7/images/when.jpg -------------------------------------------------------------------------------- /intellect/Callable.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # coding=utf-8 3 | ''' 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | ''' 32 | 33 | 34 | class Callable(object): 35 | 36 | ''' 37 | A decorator used to confer to Policy object what 38 | methods are callable. 39 | ''' 40 | 41 | def __init__(self, method): 42 | ''' 43 | Callable Initializer 44 | ''' 45 | 46 | self.__method = method 47 | 48 | def __call__(self, *args): 49 | ''' 50 | Wrapper to the callable method 51 | ''' 52 | 53 | return self.__method(*args) 54 | 55 | def __get__(self, obj, obj_type): 56 | """ 57 | PFM to get self of method decorated 58 | """ 59 | 60 | if obj is None: 61 | return self 62 | 63 | new_method = self.__method.__get__(obj, obj_type) 64 | 65 | return self.__class__(new_method) 66 | -------------------------------------------------------------------------------- /intellect/IO.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # coding=utf-8 3 | """ 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | import os 34 | import sys 35 | import StringIO 36 | 37 | 38 | class RedirectStdError(object): 39 | 40 | def __init__(self): 41 | self._stderr = StringIO.StringIO() 42 | 43 | def __enter__(self): 44 | self.save_stderr = sys.stderr 45 | self.save_stderr.flush() 46 | sys.stderr = self._stderr 47 | 48 | return sys.stderr 49 | 50 | def __exit__( 51 | self, 52 | exc_type, 53 | exc_value, 54 | traceback, 55 | ): 56 | self._stderr.flush() 57 | sys.stderr = self.save_stderr 58 | 59 | 60 | class RedirectStdOut(object): 61 | 62 | def __init__(self): 63 | self._stdout = StringIO.StringIO() 64 | 65 | def __enter__(self): 66 | self.save_stdout = sys.stdout 67 | self.save_stdout.flush() 68 | sys.stdout = self._stdout 69 | 70 | return sys.stdout 71 | 72 | def __exit__( 73 | self, 74 | exc_type, 75 | exc_value, 76 | traceback, 77 | ): 78 | self._stdout.flush() 79 | sys.stdout = self.save_stdout 80 | -------------------------------------------------------------------------------- /intellect/Intellect.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # coding=utf-8 3 | """ 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | import logging 34 | import sys 35 | import urllib 36 | import urllib2 37 | import os 38 | import errno 39 | 40 | from urlparse import urlparse 41 | 42 | from antlr3 import CommonTokenStream, ANTLRStringStream 43 | 44 | from intellect.grammar.PolicyParser import PolicyParser 45 | from intellect.PolicyLexer import PolicyLexer 46 | from intellect.Node import Policy 47 | from intellect.Node import File 48 | from intellect.PolicyTokenSource import PolicyTokenSource 49 | from intellect.Callable import Callable 50 | from intellect.IO import RedirectStdError 51 | 52 | 53 | 54 | class Intellect(object): 55 | ''' 56 | Rules engine. 57 | ''' 58 | 59 | 60 | def __init__(self): 61 | ''' 62 | Intellect initializer. 63 | ''' 64 | 65 | # initialize list to hold learned objects 66 | self.knowledge = [] 67 | 68 | # initialize the hold the combined sum of the policy files 69 | # as a single policy. 70 | self.policy = Policy() 71 | 72 | 73 | @property 74 | def knowledge(self): 75 | ''' 76 | Returns the intellect's knowledge either a list of objects or an 77 | empty list. 78 | ''' 79 | return self._knowledge 80 | 81 | 82 | @knowledge.setter 83 | def knowledge(self, one_or_more_objects): 84 | ''' 85 | Keeper of knowledge. 86 | 87 | Holds facts aka objects in a list. 88 | 89 | Args: 90 | one_or_more_objects: Either a single facts or policy file or 91 | a list of facts and/or policy files. 92 | ''' 93 | 94 | try: 95 | self.knowledge 96 | except AttributeError: 97 | self._knowledge = [] 98 | 99 | if not isinstance(one_or_more_objects, list): 100 | self.knowledge.append(one_or_more_objects) 101 | elif one_or_more_objects != []: 102 | self.knowledge.extend(one_or_more_objects) 103 | 104 | 105 | @property 106 | def policy(self): 107 | ''' 108 | Getter for the intellect's Policy object. 109 | ''' 110 | return self._policy 111 | 112 | 113 | @policy.setter 114 | def policy(self, value): 115 | ''' 116 | Setter for the intellect Policy object 117 | 118 | Args: 119 | value: a Policy object 120 | ''' 121 | self._policy = value 122 | 123 | 124 | @staticmethod 125 | def local_file_uri(file_path): 126 | ''' 127 | Helper/Utility method to take file system paths and return a file URI for use 128 | with learn, and learn_policy methods 129 | 130 | Args: 131 | file_path: The file path to the policy 132 | 133 | Returns: 134 | an equivalent file URI 135 | ''' 136 | if os.path.isfile(file_path): 137 | try: 138 | with open(file_path): 139 | pass 140 | 141 | except IOError: 142 | # Permission denied, cannot read file. 143 | raise IOError(errno.EACCES, "Permission denied to policy locate at: {0}".format(file_path)) 144 | 145 | else: 146 | # Cannot find file. 147 | raise IOError(errno.ENOENT, "Cannot find policy located at: {0}".format(file_path)) 148 | 149 | full_path = urllib.pathname2url(os.path.abspath(file_path)) 150 | if file_path.startswith("file://"): 151 | return full_path 152 | 153 | else: 154 | return "file://" + full_path 155 | 156 | 157 | @staticmethod 158 | def policy_from(urlstring): 159 | ''' 160 | Helper/Utility method to retrieve a policy from a URL 161 | 162 | Uses proxies from environment. 163 | 164 | Args: 165 | urlstring: The URL to the policy file. 166 | 167 | Returns: 168 | The text of the policy. 169 | ''' 170 | 171 | response = urllib2.urlopen(urlstring) 172 | response.headers['content-type'] = 'text/plain; charset=utf8' 173 | content = response.read() 174 | return content 175 | 176 | 177 | def learn(self, identifier): 178 | ''' 179 | Learns an object fact, or learns a policy by messaging learn_policy 180 | method with the 'identifier' as a policy URL or the text of the policy, 181 | if the identifier is a string. 182 | 183 | Args: 184 | fact: an object or policy file/string to learn. Typically objects have 185 | annotated properties and/or methods that return values. 186 | 187 | Raises: 188 | ValueError: The fact or policy already exists in knowledge. 189 | TypeError: Raised when parameter 'identifier' is a NoneType. 190 | ''' 191 | if identifier: 192 | if isinstance(identifier, basestring): 193 | return self.learn_policy(identifier) 194 | elif self.knowledge.count(identifier) == 0: 195 | self.knowledge.append(identifier) 196 | self.log("Learned: {0}:{1}".format(type(identifier), identifier.__dict__)) 197 | else: 198 | raise ValueError("{0}:{1} already exists in knowledge.".format(type(identifier), identifier.__dict__)) 199 | else: 200 | raise TypeError("parameter 'identifier' cannot be a NoneType.") 201 | 202 | 203 | def learn_policy(self, identifier): 204 | ''' 205 | Learn a policy file. 206 | 207 | Args: 208 | identifier: a string, either a URL to a policy file or the text of the policy itself. 209 | Keep in mind a policy can be comprised of more than one policy file (a file containing 210 | valid policy DSL) or string containing policy DSL. This way you break your rule set, 211 | imports, and policy attributes across any number of files. See reason-method for more. 212 | 213 | 214 | Returns: 215 | The resulting File Node. 216 | 217 | Raises: 218 | ValueError: if the policy already exists in knowledge. 219 | TypeError: if parameter 'identifier' is a NoneType, or is not a string representing 220 | either a file path to a policy or the text of the policy itself. 221 | ''' 222 | 223 | is_file = False 224 | 225 | if identifier: 226 | if isinstance(identifier, basestring): 227 | if urlparse(identifier).scheme: 228 | 229 | # Treat 'identifier' as an URL 230 | self.log("Learning policy from URL: {0}".format(identifier)) 231 | stream = ANTLRStringStream(Intellect.policy_from(identifier)) 232 | is_file = True 233 | else: 234 | 235 | #Treat 'identifier' as policy string 236 | self.log("Learning policy from string") 237 | stream = ANTLRStringStream(identifier) 238 | 239 | lexer = PolicyLexer(stream) 240 | tokens = CommonTokenStream(lexer) 241 | tokens.discardOffChannelTokens = True 242 | indented_source = PolicyTokenSource(tokens) 243 | tokens = CommonTokenStream(indented_source) 244 | parser = PolicyParser(tokens) 245 | 246 | with RedirectStdError() as stderr: 247 | try: 248 | # ANTL3 may raise an exception, and doing so the stderror 249 | # will not be printed hiding the underlying problem. GRRR!!!! 250 | file_node = parser.file() 251 | except Exception as e: 252 | if stderr.getvalue().rstrip() != "": 253 | trace = sys.exc_info()[2] 254 | raise Exception(stderr.getvalue().rstrip()), None, trace 255 | else: 256 | raise e 257 | 258 | # The ANTLR3 Recognizer class prints a number of ANTLR3 Exceptions to 259 | # stderr vice throwing an exception, because it will try to recover and 260 | # continue parsing. 261 | # 262 | # In the case of NoViableAltException, I've chosen to raise an 263 | # exception. 264 | # 265 | # Otherwise, all the other error message that Recognizer writes to 266 | # stderr will be returned for the benefit of the policy author. 267 | if stderr.getvalue().rstrip() != "": 268 | # check for stderror msg indicating an NoViableAltException occured. 269 | # if did raise an exception with the stderror message. 270 | if "no viable alternative at input" in stderr.getvalue().rstrip(): 271 | raise Exception("Error parsing policy: {0}\n{1}".format(identifier, stderr.getvalue().rstrip())) 272 | else: 273 | print >> sys.stderr, stderr.getvalue().rstrip() 274 | 275 | # set path attribute 276 | file_node.path = identifier if is_file else None 277 | 278 | # associate the path to all descendants 279 | file_node.set_file_on_descendants(file_node, file_node) 280 | 281 | try: 282 | # determine if the policy already exists in knowledge 283 | self.policy.files.index(file_node) 284 | raise ValueError("Policy already exists in knowledge: {0}".format(identifier)) 285 | except: 286 | pass 287 | 288 | # store add the policy file to the policy 289 | self.policy.append_child(file_node) 290 | 291 | self.log("learned a policy file") 292 | 293 | return file_node 294 | 295 | else: 296 | raise TypeError("parameter 'identifier' must be a string, either a file path to a policy or the text of the policy itself") 297 | 298 | else: 299 | raise TypeError("parameter 'identifier' cannot be a NoneType.") 300 | 301 | 302 | def learn_fact(self, identifier): 303 | ''' 304 | Wrapper for 'learn' method 305 | ''' 306 | self.learn(identifier) 307 | 308 | 309 | def forget(self, identifier): 310 | ''' 311 | Forgets an id() of a fact or policy, or a string representing the path 312 | of a policy, or a fact. 313 | 314 | Args: 315 | identifier: is an id() of a fact or policy, or a string 316 | representing the URL of a policy, or a fact. 317 | 318 | Raises: 319 | ValueError: Raised when parameter 'identifier' has the right 320 | type but an inappropriate value. 321 | TypeError: Raised when parameter 'identifier' is a NoneType. 322 | ''' 323 | if identifier: 324 | if isinstance(identifier, int): 325 | # remove the fact from the knowledge 326 | for index, fact in enumerate(self.knowledge): 327 | if identifier == id(fact): 328 | self.log("Forgetting fact with id: {0} of type: {1} from knowledge. fact.__dict__: {2}".format(identifier, type(fact), fact.__dict__)) 329 | self.knowledge.remove(fact) 330 | return 331 | # fact doesn't exist in memory, attempt to remove a policy file/String 332 | # from knowledge with this identifier 333 | for index, policy_file in self.policy.files: 334 | if identifier == id(policy_file): 335 | self.log("Forgetting policy loaded from file path : {0}".format(identifier.path)) 336 | self.policy.files.remove(policy_file) 337 | return 338 | # neither fact nor policy so raise an exception 339 | raise ValueError("Fact with id: {0} is not in knowledge".format(identifier)) 340 | elif isinstance(identifier, basestring): 341 | # remove the policy policy file from knowledge 342 | try: 343 | for fileIndex, policy_file in enumerate(self.policy.files): 344 | if policy_file.path == identifier: 345 | self.policy.files.pop(fileIndex) 346 | 347 | self.log("Forgetting policy loaded from file path : {0}".format(identifier)) 348 | except KeyError: 349 | raise ValueError("Policy for file path: {0} is not in knowledge".format(identifier)) 350 | elif isinstance(identifier, File): 351 | try: 352 | index = self.policy.files.index(identifier) 353 | self.policy.files.pop(index) 354 | self.log("Forgetting policy loaded from file path : {0}".format(identifier.path)) 355 | except: 356 | raise ValueError("Policy: {0} not in knowledge".format(identifier.path)) 357 | else: 358 | try: 359 | self.knowledge.remove(identifier) 360 | self.log("Forgetting fact: {0}".format(identifier)) 361 | except: 362 | raise ValueError("Fact: {0} is not in knowledge".format(identifier)) 363 | else: 364 | raise TypeError("Parameter 'identifier' cannot be a NoneType.") 365 | 366 | 367 | def forget_fact(self, identifier): 368 | ''' 369 | Wrapper for 'forget' method 370 | 371 | Args: 372 | identifier: is the id() of the fact, or the fact itself to forget 373 | 374 | Raises: 375 | See forget-method 'raises'. 376 | ''' 377 | self.forget(identifier) 378 | 379 | 380 | def forget_policy(self, identifier): 381 | ''' 382 | Wrapper for 'forget' method 383 | 384 | Args: 385 | identifier: is the either the path to the policy to forget, or 386 | the Policy object itself. 387 | 388 | Raises: 389 | TypeError: Raised when 'identifier' is not a basestring or Policy 390 | object. 391 | Also, see forget-method 'raises'. 392 | ''' 393 | if isinstance(identifier, (basestring, File)): 394 | self.forget(identifier) 395 | else: 396 | raise TypeError("Parameter 'identifier': {0} was neither a path to the policy to forget, or a Policy object.".format(identifier)) 397 | 398 | 399 | def forget_all(self): 400 | ''' 401 | Forgets all facts in knowledge and the present policy 402 | ''' 403 | self.knowledge = [] 404 | self.policy = Policy() 405 | 406 | self.log("forgot all") 407 | 408 | 409 | def reason(self, agenda=None): 410 | ''' 411 | Reasons across the facts in knowledge applying the policy. 412 | 413 | Args: 414 | agenda: is either the default of None or a list of agenda-group identifiers. 415 | If a rule is created with no agenda group attribute then the group will 416 | be associated with "MAIN" agenda group. If the 'agenda' attribute remains 417 | the default of None, then only the "MAIN" agenda group will fire. 418 | 419 | rule "flood the torpedo tubes": 420 | agenda group "firing sequence" 421 | when: 422 | ... 423 | then: 424 | ... 425 | 426 | So, in the scenario above an agenda may look like: 427 | 428 | agenda = ["targeting sequence", "firing sequence", "after firing sequence" ] 429 | 430 | First, all the rules associated with the "targeting sequence" agenda 431 | group will fire, then those associated with the "firing sequence" 432 | group... 433 | 434 | Note, any cumulative changes that occur to policy attributes are 435 | passed onto individual agenda groups. 436 | 437 | Remember, whatever is loaded last wins in terms of imports. At present rule 438 | names are not evaluated. So, it doesn't matter to the interpreter if you have 439 | two or more rules named the same, each will be evaluated. Attributes and import 440 | statement are evaluated top to bottom. Imports are evaluated first, then 441 | attributes, then rule statements. 442 | 443 | Raises: 444 | Any exceptions raised by the combined policy as it is evaluated will 445 | be raised here. 446 | ''' 447 | 448 | #update the policy with the present Intellect 449 | self.policy.intellect = self 450 | 451 | # eval the policy using the described agenda 452 | self.policy.eval(agenda) 453 | 454 | 455 | @Callable 456 | def log(self, msg, name="intellect", level=logging.DEBUG): 457 | ''' 458 | Logs at the 'level' for the messaged 'msg' 459 | 460 | Args: 461 | name: the name of the logger 462 | level: must be either logging.DEBUG, logging.INFO, logging.WARNING, 463 | logging.ERROR, logging.CRITICAL 464 | msg: message string 465 | 466 | Raises: 467 | ValueError: Raised when it receives an 'level' that is not either 468 | logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, or 469 | logging.CRITICAL. 470 | ''' 471 | 472 | if level not in [logging.DEBUG, logging.INFO, logging.WARNING, 473 | logging.ERROR, logging.CRITICAL]: 474 | raise ValueError("A value of '{0}' for 'level' is invalid, must be either logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL".format(level)) 475 | 476 | logging.getLogger(name).log(level, "{0}.{1} :: {2}".format(self.__class__.__module__, self.__class__.__name__, msg)) 477 | -------------------------------------------------------------------------------- /intellect/PolicyLexer.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # coding=utf-8 3 | """ 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | from intellect.grammar.PolicyLexer import PolicyLexer as AutoGeneratedLexer 34 | 35 | 36 | class PolicyLexer(AutoGeneratedLexer): 37 | 38 | def nextToken(self): 39 | self.startPosition = self.getCharPositionInLine() 40 | return AutoGeneratedLexer.nextToken(self) 41 | 42 | -------------------------------------------------------------------------------- /intellect/PolicyTokenSource.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # coding=utf-8 3 | """ 4 | Copyright (c) 2004 Terence Parr and Loring Craymer 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 3. The name of the author may not be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | """ 29 | 30 | from antlr3.tokens import ClassicToken, EOF 31 | from antlr3.recognizers import TokenSource 32 | 33 | from intellect.grammar.PolicyParser import INDENT, DEDENT 34 | from intellect.grammar.PolicyLexer import NEWLINE, LEADING_WS 35 | 36 | 37 | class PolicyTokenSource(TokenSource): 38 | 39 | """ 40 | Python does not explicitly provide begin and end nesting signals. 41 | Rather, the indentation level indicates when you begin and end. 42 | This is an interesting lexical problem because multiple DEDENT 43 | tokens should be sent to the parser sometimes without a corresponding 44 | input symbol! Consider the following example: 45 | 46 | a=1 47 | if a>1: 48 | print a 49 | b=3 50 | 51 | Here the "b" token on the left edge signals that a DEDENT is needed 52 | after the "print a \n" and before the "b". The sequence should be 53 | 54 | ... 1 COLON NEWLINE INDENT PRINT a NEWLINE DEDENT b ASSIGN 3 ... 55 | 56 | For more examples, see the big comment at the bottom of this file. 57 | 58 | This TokenStream normally just passes tokens through to the parser. 59 | Upon NEWLINE token from the lexer, however, an INDENT or DEDENT token 60 | may need to be sent to the parser. The NEWLINE is the trigger for 61 | this class to do it's job. NEWLINE is saved and then the first token 62 | of the next line is examined. If non-leading-whitespace token, 63 | then check against stack for indent vs dedent. If LEADING_WS, then 64 | the column of the next non-whitespace token will dictate indent vs 65 | dedent. The column of the next real token is number of spaces 66 | in the LEADING_WS token + 1 (to move past the whitespace). The 67 | lexer grammar must set the text of the LEADING_WS token to be 68 | the proper number of spaces (and do tab conversion etc...). 69 | 70 | A stack of column numbers is tracked and used to detect changes 71 | in indent level from one token to the next. 72 | 73 | A queue of tokens is built up to hold multiple DEDENT tokens that 74 | are generated. Before asking the lexer for another token via 75 | nextToken(), the queue is flushed first one token at a time. 76 | 77 | Terence Parr and Loring Craymer 78 | February 2004 79 | """ 80 | 81 | FIRST_CHAR_POSITION = 0 82 | 83 | def __init__(self, stream): 84 | 85 | # The stack of indent levels (column numbers) 86 | # "state" of indent level is FIRST_CHAR_POSITION 87 | 88 | self.indentStack = [self.FIRST_CHAR_POSITION] 89 | 90 | # The queue of tokens 91 | 92 | self.tokens = [] 93 | 94 | # We pull real tokens from this lexer 95 | 96 | self.stream = stream 97 | 98 | self.lastTokenAddedIndex = -1 99 | 100 | def nextToken(self): 101 | """ 102 | From http://www.python.org/doc/2.2.3/ref/indentation.html 103 | 104 | Before the first line of the file is read, a single zero is 105 | pushed on the stack; this will never be popped off again. The 106 | numbers pushed on the stack will always be strictly increasing 107 | from bottom to top. At the beginning of each logical line, the 108 | line's indentation level is compared to the top of the 109 | stack. If it is equal, nothing happens. If it is larger, it is 110 | pushed on the stack, and one INDENT token is generated. If it 111 | is smaller, it must be one of the numbers occurring on the 112 | stack; all numbers on the stack that are larger are popped 113 | off, and for each number popped off a DEDENT token is 114 | generated. At the end of the file, a DEDENT token is generated 115 | for each number remaining on the stack that is larger than 116 | zero. 117 | 118 | I use char position in line 0..n-1 instead. 119 | 120 | The DEDENTS possibly needed at EOF are gracefully handled by forcing 121 | EOF to have char pos 0 even though with UNIX it's hard to get EOF 122 | at a non left edge. 123 | """ 124 | 125 | # if something in queue, just remove and return it 126 | 127 | if len(self.tokens) > 0: 128 | t = self.tokens.pop(0) 129 | return t 130 | 131 | self.insertImaginaryIndentDedentTokens() 132 | 133 | return self.nextToken() 134 | 135 | def insertImaginaryIndentDedentTokens(self): 136 | t = self.stream.LT(1) 137 | self.stream.consume() 138 | 139 | # if not a NEWLINE, doesn't signal indent/dedent work; just enqueue 140 | 141 | if t.getType() != NEWLINE: 142 | hiddenTokens = \ 143 | self.stream.getTokens(self.lastTokenAddedIndex + 1, 144 | t.getTokenIndex() - 1) 145 | 146 | if hiddenTokens is not None: 147 | self.tokens.extend(hiddenTokens) 148 | 149 | self.lastTokenAddedIndex = t.getTokenIndex() 150 | self.tokens.append(t) 151 | return 152 | 153 | # save NEWLINE in the queue 154 | # print "found newline: "+str(t)+" stack is "+self.stackString() 155 | 156 | hiddenTokens = self.stream.getTokens(self.lastTokenAddedIndex 157 | + 1, t.getTokenIndex() - 1) 158 | 159 | if hiddenTokens is not None: 160 | self.tokens.extend(hiddenTokens) 161 | 162 | self.lastTokenAddedIndex = t.getTokenIndex() 163 | self.tokens.append(t) 164 | 165 | # grab first token of next line 166 | 167 | t = self.stream.LT(1) 168 | self.stream.consume() 169 | 170 | # handle hidden tokens 171 | 172 | hiddenTokens = self.stream.getTokens(self.lastTokenAddedIndex 173 | + 1, t.getTokenIndex() - 1) 174 | 175 | if hiddenTokens is not None: 176 | self.tokens.extend(hiddenTokens) 177 | 178 | self.lastTokenAddedIndex = t.getTokenIndex() 179 | 180 | # compute cpos as the char pos of next non-WS token in line 181 | 182 | cpos = t.getCharPositionInLine() # column dictates indent/dedent 183 | 184 | if t.getType() == EOF: 185 | cpos = -1 # pretend EOF always happens at left edge 186 | elif t.getType() == LEADING_WS: 187 | cpos = len(t.getText()) 188 | 189 | # print "next token is: "+str(t) 190 | 191 | # compare to last indent level 192 | 193 | lastIndent = self.indentStack[-1] 194 | 195 | # print "cpos, lastIndent = "+str(cpos)+", "+str(lastIndent) 196 | 197 | if cpos > lastIndent: # they indented; track and gen INDENT 198 | self.indentStack.append(cpos) 199 | 200 | # print self.indentStack 201 | # print "push("+str(cpos)+"): "+self.stackString() 202 | 203 | indent = ClassicToken(INDENT, '') 204 | indent.setCharPositionInLine(t.getCharPositionInLine()) 205 | indent.setLine(t.getLine()) 206 | self.tokens.append(indent) 207 | elif cpos < lastIndent: 208 | 209 | # they dedented 210 | # how far back did we dedent? 211 | 212 | prevIndex = self.findPreviousIndent(cpos) 213 | 214 | # print "dedented; prevIndex of cpos="+str(cpos)+" is "+str(prevIndex) 215 | 216 | # generate DEDENTs for each indent level we backed up over 217 | 218 | while len(self.indentStack) > prevIndex + 1: 219 | dedent = ClassicToken(DEDENT, '') 220 | dedent.setCharPositionInLine(t.getCharPositionInLine()) 221 | dedent.setLine(t.getLine()) 222 | self.tokens.append(dedent) 223 | 224 | self.indentStack.pop(-1) # pop those off indent level 225 | 226 | # print self.indentStack 227 | 228 | if t.getType() != LEADING_WS: # discard WS 229 | self.tokens.append(t) 230 | 231 | # T O K E N S T A C K M E T H O D S 232 | 233 | def findPreviousIndent(self, i): 234 | ''' 235 | Return the index on stack of previous indent level == i else -1 236 | ''' 237 | 238 | for (j, pos) in reversed(list(enumerate(self.indentStack))): 239 | if pos == i: 240 | return j 241 | 242 | return self.FIRST_CHAR_POSITION 243 | 244 | def stackString(self): 245 | return ' '.join([str(i) for i in reversed(self.indentStack)]) 246 | -------------------------------------------------------------------------------- /intellect/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # coding=utf-8 3 | """ 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | __author__ = "Michael Joseph Walsh" 34 | 35 | VERSION = (1, 4, 9, 0) 36 | -------------------------------------------------------------------------------- /intellect/examples/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ -------------------------------------------------------------------------------- /intellect/examples/bahBahBlackSheep/BagOfWool.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, Michael Joseph Walsh. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ 30 | 31 | ''' 32 | Created on Aug 29, 2011 33 | 34 | @author: Michael Joseph Walsh 35 | ''' 36 | 37 | 38 | class BagOfWool(object): 39 | ''' 40 | Used to signify a bag of wool 41 | ''' 42 | 43 | 44 | def __init__(self): 45 | ''' 46 | BagsOfWool Initializer 47 | ''' 48 | -------------------------------------------------------------------------------- /intellect/examples/bahBahBlackSheep/BlackSheep.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, Michael Jospeh Walsh. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ 30 | 31 | ''' 32 | Created on Aug 18, 2011 33 | 34 | @author: Michael Joseph Walsh 35 | ''' 36 | 37 | import logging, time 38 | import thread, random 39 | from threading import Lock 40 | 41 | from intellect.examples.bahBahBlackSheep.BagOfWool import BagOfWool 42 | 43 | def grow_wool(sheep): 44 | while True: 45 | 46 | time.sleep(random.randint(2, 5)) 47 | 48 | logging.getLogger("example").debug("{0}: Grew a bag of wool.".format(sheep.name)) 49 | sheep.bags_of_wool.append(BagOfWool()) 50 | 51 | if len(sheep.bags_of_wool) == 3: 52 | logging.getLogger("example").debug("{0}: Waiting around for retirement.".format(sheep.name)) 53 | break 54 | 55 | class BlackSheep(): 56 | ''' 57 | Used to signify a black sheep 58 | ''' 59 | 60 | number = 0 61 | 62 | def __init__(self): 63 | ''' 64 | BlackSheep Initializer 65 | ''' 66 | self.bags_of_wool = [] 67 | BlackSheep.number = BlackSheep.number + 1 68 | self.name = "Sheep #{0}".format(BlackSheep.number) 69 | 70 | logging.getLogger("example").debug("Creating {0}.".format(self.name)) 71 | 72 | self.lock = Lock() 73 | thread.start_new_thread(grow_wool, (self,)) 74 | 75 | 76 | @property 77 | def name(self): 78 | return self._name 79 | 80 | @name.setter 81 | def name(self, value): 82 | self._name = value 83 | 84 | @property 85 | def bags_of_wool(self): 86 | return self._bags_of_wool 87 | 88 | @bags_of_wool.setter 89 | def bags_of_wool(self, value): 90 | self.lock.acquire() 91 | self._bags_of_wool = value 92 | self.lock.release() 93 | 94 | 95 | if __name__ == '__main__': 96 | sheep = BlackSheep() 97 | 98 | while True: 99 | time.sleep(5) 100 | print len(sheep.bags_of_wool) 101 | 102 | if len(sheep.bags_of_wool) == 3: 103 | break 104 | -------------------------------------------------------------------------------- /intellect/examples/bahBahBlackSheep/BuyOrder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, Michael Joseph Walsh. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ 30 | 31 | ''' 32 | Created on Aug 18, 2011 33 | 34 | @author: Michael Joseph Walsh 35 | ''' 36 | 37 | import logging 38 | 39 | class BuyOrder(object): 40 | ''' 41 | Used to signify a buy order 42 | ''' 43 | 44 | def __init__(self, count = 1): 45 | ''' 46 | BuyOrder Initializer 47 | ''' 48 | logging.getLogger("example").debug("Creating buy order for {0} sheep.".format(count)) 49 | 50 | self.count = count 51 | 52 | @property 53 | def count(self): 54 | return self._count 55 | 56 | @count.setter 57 | def count(self, value): 58 | self._count = value -------------------------------------------------------------------------------- /intellect/examples/bahBahBlackSheep/Example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Copyright (c) 2011, Michael Joseph Walsh. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | ''' 34 | Created on Aug 17, 2011 35 | 36 | @author: Michael Joseph Walsh 37 | ''' 38 | 39 | import sys, logging, time, random 40 | 41 | from intellect.Intellect import Intellect 42 | 43 | from intellect.examples.bahBahBlackSheep.BuyOrder import BuyOrder 44 | 45 | class MyIntellect(Intellect): 46 | pass 47 | 48 | 49 | if __name__ == '__main__': 50 | 51 | # tune down logging inside Intellect 52 | logger = logging.getLogger('intellect') 53 | logger.setLevel(logging.ERROR) 54 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 55 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 56 | logger.addHandler(consoleHandler) 57 | 58 | # set up logging for the example 59 | logger = logging.getLogger('example') 60 | logger.setLevel(logging.DEBUG) 61 | 62 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 63 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 64 | logger.addHandler(consoleHandler) 65 | 66 | logging.getLogger("example").debug("Creating reasoning engine.") 67 | myIntellect = MyIntellect() 68 | 69 | logging.getLogger("example").debug("Asking the engine to learn my policy.") 70 | policy = myIntellect.learn(myIntellect.local_file_uri("./rulesets/example.policy")) 71 | 72 | #print myIntellect.policy.str_tree("semantic model:") 73 | 74 | max_buy_orders_to_start = input('Provide the maximum number possible buy orders to start with: ') 75 | 76 | buy_order_to_start = random.randint(1, max_buy_orders_to_start) 77 | 78 | logging.getLogger("example").debug("Asking the engine to learn a BuyOrder for {0} sheep.".format(buy_order_to_start)) 79 | myIntellect.learn(BuyOrder(buy_order_to_start)) 80 | 81 | myIntellect.reason() 82 | 83 | while True: 84 | logging.getLogger("example").debug("{0} in knowledge.".format(myIntellect.knowledge)) 85 | time.sleep(5) 86 | logging.getLogger("example").debug("Messaging reasoning engine to reason.") 87 | myIntellect.reason() 88 | -------------------------------------------------------------------------------- /intellect/examples/bahBahBlackSheep/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, Michael Joseph Walsh. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ -------------------------------------------------------------------------------- /intellect/examples/bahBahBlackSheep/rulesets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ -------------------------------------------------------------------------------- /intellect/examples/bahBahBlackSheep/rulesets/example.policy: -------------------------------------------------------------------------------- 1 | from intellect.examples.bahBahBlackSheep.BlackSheep import BlackSheep 2 | from intellect.examples.bahBahBlackSheep.BuyOrder import BuyOrder 3 | import logging 4 | import random 5 | 6 | ''' 7 | Ruleset based on the nursery rhyme... 8 | ''' 9 | 10 | rule "Start policy": 11 | then: 12 | log("Policy is being reasoned over.", "example", logging.DEBUG) 13 | 14 | rule "Baa, baa, black sheep, Have you any wool?": 15 | when: 16 | exists BlackSheep() 17 | then: 18 | print( "Me: Baa, baa, black sheep, Have you any wool?" ) 19 | 20 | rule "Have you no bags full?": 21 | when: 22 | $sheep := BlackSheep( len(bags_of_wool) == 0 ) 23 | then: 24 | print( "Sheep {0}: No, sir, No, sir.".format( $sheep.name ) ) 25 | 26 | rule "At least 1 bag full?": 27 | when: 28 | $sheep := BlackSheep( len(bags_of_wool) >= 1 ) 29 | then: 30 | print( "{0}: Yes, sir, yes, sir,".format( $sheep.name ) ) 31 | print( "{0}: {1} bags full".format( $sheep.name, len($sheep.bags_of_wool) ) ) 32 | print( "{0}: One for my master,".format( $sheep.name ) ) 33 | 34 | rule "At least 2 bags full?": 35 | when: 36 | $sheep := BlackSheep( len(bags_of_wool) >= 2 ) 37 | then: 38 | print("{0}: And one for my dame,".format( $sheep.name ) ) 39 | 40 | rule "3 bags full?": 41 | when: 42 | $sheep := BlackSheep( len(bags_of_wool) == 3 ) 43 | then: 44 | print( "{0}: and one for the little boy".format( $sheep.name ) ) 45 | print( "{0}: Who lives down the lane.".format( $sheep.name ) ) 46 | 47 | rule "Retire the sheep and insert a buy order": 48 | when: 49 | $sheep := BlackSheep( len(bags_of_wool) == 3 ) 50 | then: 51 | print( "Retiring sheep {0}.".format( $sheep.name ) ) 52 | forget $sheep 53 | insert BuyOrder(random.randint(1, 1)) 54 | 55 | rule "Time to buy new sheep?": 56 | when: 57 | $buyOrder := BuyOrder( ) 58 | then: 59 | modify $buyOrder: 60 | count = $buyOrder.count - 1 61 | learn BlackSheep() 62 | 63 | rule "Remove empty buy orders": 64 | when: 65 | $buyOrder := BuyOrder( count == 0 ) 66 | then: 67 | forget $buyOrder 68 | 69 | rule "End policy": 70 | then: 71 | log("Finished reasoning over policy.", "example", logging.DEBUG) 72 | halt 73 | -------------------------------------------------------------------------------- /intellect/examples/testing/ClassA.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ 30 | 31 | """ 32 | ClassA 33 | 34 | Description: Intellect test fact 35 | 36 | Initial Version: Feb 2, 2011 37 | 38 | @author: Michael Joseph Walsh 39 | """ 40 | 41 | class ClassA(object): 42 | ''' 43 | An example fact 44 | ''' 45 | 46 | globalInClassA_1 = "a global" 47 | globalInClassA_2 = "another global" 48 | 49 | def __init__(self, property0 = None, property1 = None): 50 | ''' 51 | ClassA initializer 52 | ''' 53 | self.attribute1 = "attribute1's value" 54 | self.__hiddenAttribute1 = "super secret hidden attribute. nah!" 55 | 56 | self.property0 = property0 57 | self.property1 = property1 58 | 59 | print "created an instance of ClassA" 60 | 61 | @property 62 | def property0(self): 63 | return self._property0 64 | 65 | @property0.setter 66 | def property0(self, value): 67 | self._property0 = value 68 | 69 | @property 70 | def property1(self): 71 | return self._property1 72 | 73 | @property1.setter 74 | def property1(self, value): 75 | self._property1 = value 76 | 77 | def someMethod(self): 78 | print("someMethod called") 79 | 80 | @staticmethod 81 | def classAStaticMethod(self): 82 | print("classAStaticMethod called") 83 | 84 | def classASomeOtherMethod(self): 85 | print("classASomeOtherMethd called") 86 | 87 | def __classAHiddenMethod(self): 88 | print("classAHiddenMethod called") -------------------------------------------------------------------------------- /intellect/examples/testing/ClassCandD.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ 30 | 31 | """ 32 | ClassCandD 33 | 34 | Description: Intellect test facts 35 | 36 | Initial Version: Feb 2, 2011 37 | 38 | @author: Michael Joseph Walsh 39 | """ 40 | 41 | class ClassC(object): 42 | ''' 43 | An example fact. 44 | ''' 45 | 46 | globalInClassC="a global only in ClassC" 47 | 48 | def __init__(self, property1 = None, property2 = None): 49 | ''' 50 | ClassC initializer 51 | ''' 52 | self.property1 = property1 53 | self.property2 = property2 54 | 55 | def someMethod(self): 56 | print("someMethod called") 57 | 58 | @property 59 | def property1(self): 60 | return self._property1 61 | 62 | @property1.setter 63 | def property1(self, value): 64 | self._property1 = value 65 | 66 | @property1.deleter 67 | def property1(self): 68 | del self._property1 69 | 70 | @property 71 | def property2(self): 72 | return self._property2 73 | 74 | @property2.setter 75 | def property2(self, value): 76 | self._property2 = value 77 | 78 | @property2.deleter 79 | def property2(self): 80 | del self._property2 81 | 82 | 83 | class ClassD(object): 84 | ''' 85 | An example fact. 86 | ''' 87 | 88 | globalInClassD="a global only in ClassD" 89 | 90 | def __init__(self, property1 = None): 91 | ''' 92 | ClassD initializer 93 | ''' 94 | self.property1 = property1 95 | 96 | def someMethod(self): 97 | print("someMethod called") 98 | 99 | @property 100 | def property1(self): 101 | return self._property1 102 | 103 | @property1.setter 104 | def property1(self, value): 105 | self._property1 = value 106 | 107 | @property1.deleter 108 | def property1(self): 109 | del self._property1 -------------------------------------------------------------------------------- /intellect/examples/testing/ExerciseGrammar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | """ 34 | ExerciseIntellect 35 | 36 | Description: Exercises the Intellect grammar 37 | 38 | Initial Version: Dec 29, 2011 39 | 40 | @author: Michael Joseph Walsh 41 | """ 42 | import sys, traceback, logging 43 | 44 | from intellect.Intellect import Intellect 45 | from intellect.examples.testing.ClassA import ClassA 46 | 47 | if __name__ == "__main__": 48 | 49 | # tune down logging inside Intellect 50 | logger = logging.getLogger('intellect') 51 | logger.setLevel(logging.DEBUG) # change this to ERROR for less output 52 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 53 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 54 | logger.addHandler(consoleHandler) 55 | 56 | # set up logging for the example 57 | logger = logging.getLogger('example') 58 | logger.setLevel(logging.DEBUG) 59 | 60 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 61 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 62 | logger.addHandler(consoleHandler) 63 | 64 | print "*"*80 65 | print """create an instance of MyIntellect extending Intellect, create some facts, and exercise the grammar""" 66 | print "*"*80 67 | 68 | try: 69 | myIntellect = Intellect() 70 | 71 | policy_a = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_f.policy")) 72 | 73 | myIntellect.learn(ClassA(property1="apple")) 74 | #myIntellect.learn(ClassA( property1="pear")) 75 | #myIntellect.learn(ClassA( property1="grape")) 76 | 77 | #logger.debug("reasoning over policy w/ objects in memory") 78 | 79 | myIntellect.reason() 80 | except Exception as e: 81 | traceback.print_exc(limit=sys.getrecursionlimit(), file=sys.stdout) 82 | -------------------------------------------------------------------------------- /intellect/examples/testing/ExerciseIntellect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | """ 34 | ExerciseIntellect 35 | 36 | Description: Exercises the Intellect object 37 | 38 | Initial Version: Feb 9, 2011 39 | 40 | @author: Michael Joseph Walsh 41 | """ 42 | 43 | import sys, traceback, logging 44 | 45 | from intellect.Intellect import Intellect 46 | from intellect.Intellect import Callable 47 | 48 | 49 | class MyIntellect(Intellect): 50 | 51 | @Callable 52 | def bar(self): 53 | self.log(">>>>>>>>>>>>>> called MyIntellect's bar method as it was decorated as callable.") 54 | 55 | 56 | if __name__ == "__main__": 57 | 58 | try: 59 | # tune down logging inside Intellect 60 | logger = logging.getLogger('intellect') 61 | logger.setLevel(logging.DEBUG) # change this to ERROR for less output 62 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 63 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 64 | logger.addHandler(consoleHandler) 65 | 66 | # set up logging for the example 67 | logger = logging.getLogger('example') 68 | logger.setLevel(logging.DEBUG) 69 | 70 | consoleHandler = logging.StreamHandler(stream=sys.stdout) 71 | consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) 72 | logger.addHandler(consoleHandler) 73 | 74 | print "*"*80 75 | print """create an instance of MyIntellect extending Intellect, create some facts, and exercise the MyIntellect's ability to learn and forget""" 76 | print "*"*80 77 | 78 | myIntellect = MyIntellect() 79 | 80 | try: 81 | policy_bogus = myIntellect.learn(Intellect.local_file_uri("./rulesets/doesnt_exist.policy")) 82 | except IOError as e: 83 | exc_type, exc_value, exc_traceback = sys.exc_info() 84 | traceback.print_exception(exc_type, exc_value, exc_traceback) 85 | 86 | policy_a = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_a.policy")) 87 | policy_d = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_d.policy")) 88 | 89 | myIntellect.reason(["test_d", "test_a"]) 90 | 91 | myIntellect.forget_all() 92 | 93 | from intellect.examples.testing.subModule.ClassB import ClassB 94 | 95 | # keep an identifier (b1) around to a ClassB, to demonstrate that a rule 96 | # can modify the object and the change is reflected in b1 97 | b = ClassB(property1="apple", property2=11) 98 | myIntellect.learn(b) 99 | 100 | b = ClassB(property1="pear", property2=11) 101 | myIntellect.learn(b) 102 | 103 | # learn policy as a string 104 | policy_a = myIntellect.learn(""" 105 | from intellect.examples.testing.subModule.ClassB import ClassB 106 | import intellect.examples.testing.Test as Test 107 | import logging 108 | 109 | fruits_of_interest = ["apple", "grape", "mellon", "pear"] 110 | count = 5 111 | 112 | rule rule_a: 113 | agenda-group test_a 114 | when: 115 | $classB := ClassB( property1 in fruits_of_interest and property2>count ) 116 | then: 117 | # mark the 'ClassB' matches in memory as modified 118 | modify $classB: 119 | property1 = $classB.property1 + " pie" 120 | modified = True 121 | # increment the matche's 'property2' value by 1000 122 | property2 = $classB.property2 + 1000 123 | attribute count = $classB.property2 124 | print "count = {0}".format( count ) 125 | # call MyIntellect's bar method as it is decorated as callable 126 | bar() 127 | log("rule_a fired") 128 | 129 | rule rule_b: 130 | agenda-group test_a 131 | then: 132 | print "count = {0}".format( count ) 133 | insert ClassB("water melon") 134 | # call MyIntellect's bar method as it is decorated as callable 135 | bar() 136 | log("rule_b fired") 137 | 138 | rule rule_c: 139 | # on the MAIN agenda-group 140 | then: 141 | log("rule_c fired") 142 | 143 | rule rule_d: 144 | agenda-group test_a 145 | then: 146 | attribute foo = "foo bar" 147 | """) 148 | 149 | policy_b = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_b.policy")) 150 | #print policy.str_tree() 151 | #print str(policy_a) 152 | 153 | for policy_file_paths in myIntellect.policy.file_paths: 154 | print "----------------- path: {0}".format(policy_file_paths) 155 | 156 | myIntellect.forget(policy_b) 157 | 158 | for policy_file_paths in myIntellect.policy.file_paths: 159 | print "----------------- path: {0}".format(policy_file_paths) 160 | 161 | policy_b = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_b.policy")) 162 | 163 | for policy_file_paths in myIntellect.policy.file_paths: 164 | print "----------------- path: {0}".format(policy_file_paths) 165 | 166 | myIntellect.forget(Intellect.local_file_uri("./rulesets/test_b.policy")) 167 | 168 | for policy_file_paths in myIntellect.policy.file_paths: 169 | print "----------------- path: {0}".format(policy_file_paths) 170 | 171 | policy_b = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_b.policy")) 172 | 173 | for policy_file_paths in myIntellect.policy.file_paths: 174 | print "----------------- path: {0}".format(policy_file_paths) 175 | 176 | print "*"*80 177 | print "message MyIntellect to reason over the facts in knowledge" 178 | print "*"*80 179 | 180 | myIntellect.reason(["test_a"]) 181 | 182 | print "*"*80 183 | print "facts in knowledge after applying policies" 184 | print "*"*80 185 | 186 | print myIntellect.knowledge 187 | 188 | for fact in myIntellect.knowledge: 189 | print "type: {0}, __dict__: {1}".format(type(fact), fact.__dict__) 190 | 191 | print "*"*80 192 | print "forget all, learn a policy from a string" 193 | print "*"*80 194 | 195 | myIntellect.forget_all() 196 | 197 | policy = myIntellect.learn("""rule rule_inline: 198 | then: 199 | a = 1 200 | b = 2 201 | c = a + b 202 | print("{0} + {1} = {2}".format(a, b, c)) 203 | """) 204 | 205 | myIntellect.reason() 206 | 207 | print "*"*80 208 | print 'forget all, learn test_c.policy, reason over ["1", "2", "3", "4", "5", "6"]' 209 | print "*"*80 210 | 211 | myIntellect.forget_all() 212 | 213 | policy_c = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_c.policy")) 214 | 215 | myIntellect.reason(["1", "2", "3", "4", "5", "6"]) 216 | 217 | myIntellect.forget_all() 218 | 219 | try: 220 | policy_bogus = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_e.policy")) 221 | except Exception as e: 222 | print >> sys.stderr, "./rulesets/test_e.policy doesn't contain any actual policy text. exception trace follows:" 223 | exc_type, exc_value, exc_traceback = sys.exc_info() 224 | traceback.print_exception(exc_type, exc_value, exc_traceback) 225 | except Exception as e: 226 | traceback.print_exc(limit=sys.getrecursionlimit(), file=sys.stdout) 227 | -------------------------------------------------------------------------------- /intellect/examples/testing/Test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ 30 | 31 | """ 32 | test 33 | 34 | Description: Test module 35 | 36 | Initial Version: Feb 23, 2011 37 | 38 | @author: Michael Joseph Walsh 39 | """ 40 | def helloworld(): 41 | """ 42 | Returns "hello world" annd prints "returning 'hello world'" to the 43 | sys.stdout 44 | """ 45 | print "returning 'hello world'" 46 | return "hello world" 47 | 48 | def greaterThanTen(n): 49 | """ 50 | Returns True if 'n' is greater than 10 51 | """ 52 | return n>10 53 | 54 | 55 | class MyClass(object): 56 | 57 | def __init__(self): 58 | self._globals = {} 59 | 60 | @property 61 | def globals(self): 62 | return self._globals 63 | 64 | @globals.setter 65 | def globals(self, value): 66 | self._globals = value 67 | 68 | 69 | a = MyClass() 70 | 71 | locals = {} 72 | 73 | exec("a = 1" ,a.globals, locals) 74 | 75 | print "globals = {0}".format([g for g in a.globals if not g.startswith("__")]) 76 | print "locals = {0}".format(locals) 77 | 78 | exec("a += 1", a.globals, locals) 79 | 80 | print "globals = {0}".format([g for g in a.globals if not g.startswith("__")]) 81 | print "locals = {0}".format(locals) 82 | 83 | a.globals["b"] = 5 84 | 85 | print "globals = {0}".format([g for g in a.globals if not g.startswith("__")]) 86 | print "locals = {0}".format(locals) 87 | 88 | exec("global b;b += 1", a.globals, locals) -------------------------------------------------------------------------------- /intellect/examples/testing/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ -------------------------------------------------------------------------------- /intellect/examples/testing/rulesets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ -------------------------------------------------------------------------------- /intellect/examples/testing/rulesets/test_a.policy: -------------------------------------------------------------------------------- 1 | from intellect.examples.testing.subModule.ClassB import ClassB 2 | import intellect.examples.testing.Test as Test 3 | import logging 4 | 5 | fruits_of_interest = ["apple", "grape", "mellon", "pear"] 6 | count = 5 7 | 8 | rule rule_a: 9 | agenda-group test_a 10 | when: 11 | $classB := ClassB( property1 in fruits_of_interest and property2>count ) 12 | then: 13 | # mark the 'ClassB' matches in memory as modified 14 | modify $classB: 15 | property1 = $classB.property1 + " pie" 16 | modified = True 17 | # increment the matche's 'property2' value by 1000 18 | property2 = $classB.property2 + 1000 19 | attribute count = $classB.property2 20 | print "count = {0}".format( count ) 21 | # call MyIntellect's bar method as it is decorated as callable 22 | bar() 23 | log("rule_a fired") 24 | 25 | rule rule_b: 26 | agenda-group test_a 27 | then: 28 | print "count = {0}".format( count ) 29 | insert ClassB("water melon") 30 | # call MyIntellect's bar method as it is decorated as callable 31 | bar() 32 | log("rule_b fired") 33 | 34 | rule rule_c: 35 | # on the MAIN agenda-group 36 | then: 37 | log("rule_c fired") 38 | 39 | rule rule_d: 40 | agenda-group test_a 41 | then: 42 | attribute foo = "foo bar" 43 | -------------------------------------------------------------------------------- /intellect/examples/testing/rulesets/test_b.policy: -------------------------------------------------------------------------------- 1 | from intellect.examples.testing.subModule.ClassB import ClassB 2 | 3 | rule rule_e: 4 | agenda-group test_a 5 | then: 6 | print foo 7 | 8 | rule rule_f: 9 | agenda-group test_a 10 | then: 11 | halt 12 | 13 | rule rule_g: 14 | agenda-group test_a 15 | then: 16 | halt 17 | 18 | -------------------------------------------------------------------------------- /intellect/examples/testing/rulesets/test_c.policy: -------------------------------------------------------------------------------- 1 | i = 0 2 | 3 | rule rule_e: 4 | agenda-group "1" 5 | then: 6 | attribute i = i + 1 7 | print i 8 | halt 9 | 10 | rule rule_f: 11 | agenda-group "2" 12 | then: 13 | attribute i = i + 1 14 | print i 15 | 16 | rule rule_g: 17 | agenda-group "3" 18 | then: 19 | attribute i = i + 1 20 | print i 21 | 22 | rule rule_h: 23 | agenda-group "4" 24 | then: 25 | # the 'i' variable is scoped to then portion of the rule 26 | i = 0 27 | print i 28 | 29 | rule rule_i: 30 | agenda-group "5" 31 | then: 32 | attribute i += 1 33 | print i 34 | # the 'i' variable is scoped to then portion of the rule 35 | i = 0 36 | 37 | rule rule_j: 38 | agenda-group "6" 39 | then: 40 | attribute i += 1 41 | print i 42 | -------------------------------------------------------------------------------- /intellect/examples/testing/rulesets/test_d.policy: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | first_sum = 0 4 | second_sum = 0 5 | 6 | rule "set both first_sum and second_sum to 1": 7 | agenda-group "test_d" 8 | then: 9 | attribute (first_sum, second_sum) = (1,1) 10 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 11 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 12 | 13 | rule "add 2": 14 | agenda-group "test_d" 15 | then: 16 | attribute first_sum += 2 17 | attribute second_sum += 2 18 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 19 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 20 | 21 | rule "add 3": 22 | agenda-group "test_d" 23 | then: 24 | attribute first_sum += 3 25 | attribute second_sum += 3 26 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 27 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 28 | 29 | rule "add 4": 30 | agenda-group "test_d" 31 | then: 32 | attribute first_sum += 4 33 | attribute second_sum += 4 34 | log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) 35 | log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) 36 | halt 37 | 38 | rule "should never get here": 39 | agenda-group "test_d" 40 | then: 41 | log("Then how did I get here?", "example", logging.DEBUG) 42 | -------------------------------------------------------------------------------- /intellect/examples/testing/rulesets/test_e.policy: -------------------------------------------------------------------------------- 1 | The limerick* packs laughs anatomical 2 | *(pronounced "lim'rick" to preserve meter) 3 | In space that is quite economical, 4 | But the good ones I've seen 5 | So seldom are clean, 6 | And the clean ones so seldom are comical. -------------------------------------------------------------------------------- /intellect/examples/testing/rulesets/test_f.policy: -------------------------------------------------------------------------------- 1 | from intellect.examples.testing.ClassA import ClassA 2 | 3 | rule rule_a: 4 | when: 5 | $classA := ClassA( property1 == "apple" ) 6 | then: 7 | log("rule_a fired") 8 | -------------------------------------------------------------------------------- /intellect/examples/testing/subModule/ClassB.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ 30 | 31 | """ 32 | ClassB 33 | 34 | Description: Intellect test fact 35 | 36 | Initial Version: Feb 2, 2011 37 | 38 | @author: Michael Joseph Walsh 39 | """ 40 | 41 | from intellect.examples.testing.ClassA import ClassA 42 | 43 | class ClassB(ClassA): 44 | ''' 45 | An example fact 46 | ''' 47 | 48 | globalInClassB="a global only in class B" 49 | 50 | def __init__(self, property0 = None, property1 = None, property2 = None): 51 | ''' 52 | ClassB initializer 53 | ''' 54 | self.attribute2 = "attribute2's value" 55 | self.__hiddenAttribute2 = "another super secret hidden attribute. nah!" 56 | 57 | self.property0 = property0 58 | self.property1 = property1 59 | self.property2 = property2 60 | self.modified = False 61 | 62 | print "created an instance of ClassB" 63 | 64 | @property 65 | def property2(self): 66 | return self._property2 67 | 68 | @property2.setter 69 | def property2(self, value): 70 | print "setting property2 to {0}".format(value) 71 | self._property2 = value 72 | 73 | @property 74 | def modified(self): 75 | return self._modified 76 | 77 | @modified.setter 78 | def modified(self, value): 79 | print "setting modified to {0}".format(value) 80 | self._modified = value 81 | 82 | def aMethod(self): 83 | return "a" 84 | 85 | def trueValue(self): 86 | return True 87 | 88 | @staticmethod 89 | def classBStaticMethod(self): 90 | # and here 91 | print("classBStatciMethod called") 92 | 93 | def classBSomeOtherMethod(self): 94 | # and here 95 | print("classBSomeOtherMethod called") 96 | 97 | def __classBHiddenMethod(self): 98 | # something goes here 99 | print("classBHiddenMethod called") -------------------------------------------------------------------------------- /intellect/examples/testing/subModule/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ -------------------------------------------------------------------------------- /intellect/grammar/Policy.g: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | /** 31 | * @author: Michael Joseph Walsh 32 | * 33 | * A policy grammar derived from the ANTLR3 Python 2.3.3 Grammar authored by 34 | * Terence Parr and Loring Craymer. 35 | */ 36 | 37 | grammar Policy; 38 | 39 | options { 40 | language = Python; 41 | } 42 | 43 | tokens { 44 | INDENT; 45 | DEDENT; 46 | } 47 | 48 | @header { 49 | from intellect.Node import * 50 | } 51 | 52 | @lexer::init { 53 | self.implicitLineJoiningLevel = 0 54 | self.startPosition = -1 55 | } 56 | 57 | // 58 | // P A R S E R R U L E S 59 | // 60 | 61 | file returns [object] // returns a policy File object 62 | @init{ $object = File() } 63 | : ( NEWLINE | statement { $object.append_child( $statement.object ) } )+ 64 | | EOF 65 | ; 66 | 67 | statement returns [object] // returns a Statment object 68 | : importStmt { $object = Statement( $importStmt.object, $importStmt.object.line, $importStmt.object.column ) } 69 | | attributeStmt { $object = Statement( $attributeStmt.object ) } 70 | | ruleStmt { $object = Statement( $ruleStmt.object, $ruleStmt.object.line, $ruleStmt.object.column ) } 71 | ; 72 | 73 | importStmt returns [object] // returns a ImportStmt object 74 | : importName { $object = ImportStmt( children = $importName.object, line = $importName.object.line ) } 75 | | importFrom { $object = ImportStmt( children = $importFrom.object, line = $importFrom.object.line ) } 76 | ; 77 | 78 | importName returns [object] // returns an ImportName object 79 | : IMPORT dottedAsNames { $object = ImportName( children = [$IMPORT.text, $dottedAsNames.object], line = $IMPORT.getLine() ) } NEWLINE 80 | ; 81 | 82 | importFrom returns [object] // returns an ImportFrom object 83 | : FROM { $object = ImportFrom( children = $FROM.text, line = $FROM.getLine() ) } dottedName { $object.append_child( $dottedName.object ) } IMPORT { $object.append_child( $IMPORT.text ) } 84 | ( importAsNames1=importAsNames { object.append_child( $importAsNames1.object ) } 85 | | LPAREN importAsNames2=importAsNames RPAREN { $object.append_children( [$LPAREN.text, $importAsNames2.object, $RPAREN.text] ) } 86 | ) NEWLINE 87 | ; 88 | 89 | importAsNames returns [object] 90 | : importAsName1=importAsName { $object=ImportAsNames( $importAsName1.object ) } 91 | ( COMMA importAsName2=importAsName { $object.append_children( [",", $importAsName2.object] ) } )* ( COMMA { $object.append_child( "," ) } )? 92 | ; 93 | 94 | importAsName returns [object] 95 | : name1=NAME { $object=ImportAsName( $name1.text ) } ( AS name2=NAME { $object.append_children( [$AS.text, $name2.text] ) } )? 96 | ; 97 | 98 | dottedAsNames returns [object] 99 | : dottedAsName1=dottedAsName { $object = DottedAsNames( $dottedAsName1.object ) } 100 | ( COMMA dottedAsName2=dottedAsName { object.append_children( [$COMMA.text, $dottedAsName2.object] ) } )* 101 | ; 102 | 103 | dottedAsName returns [object] 104 | : dottedName { $object = DottedAsName( $dottedName.object ) } ( AS NAME { $object.append_children( [$AS.text, $NAME.text] ) } )? 105 | ; 106 | 107 | dottedName returns [object] // returns a DottedName object 108 | : name1=NAME { $object = DottedName( $name1.text ) } 109 | ( DOT name2=NAME { $object.append_children( [$DOT.text, $name2.text] ) } )* 110 | ; 111 | 112 | attributeStmt returns [object] // returns an AttributeStmt object. 113 | : expressionStmt { $object = AttributeStmt( $expressionStmt.object ) } 114 | ; 115 | 116 | ruleStmt returns [object] // returns a RuleStmt object. 117 | : RULE id COLON NEWLINE { $object = RuleStmt( [ $RULE.text, $id.object, $COLON.text ], $RULE.getLine(), $RULE.getCharPositionInLine() ) } 118 | INDENT ( ruleAttribute { $object.append_child( $ruleAttribute.object ) } )* 119 | ( when { $object.append_child( $when.object ) } )? 120 | then { $object.append_child( $then.object ) } DEDENT 121 | ; 122 | 123 | id returns [object] // returns an Id object 124 | : NAME { $object = Id( $NAME.text ) } 125 | | STRING { $object = Id( $STRING.text ) } 126 | ; 127 | 128 | ruleAttribute returns [object] // returns a RuleAttribute object 129 | : agendaGroup { $object = RuleAttribute( $agendaGroup.object, $agendaGroup.object.line, $agendaGroup.object.column ) } 130 | ; 131 | 132 | agendaGroup returns [object] // returns a RuleGroup 133 | : AGENDAGROUP id NEWLINE { $object = AgendaGroup( [ $AGENDAGROUP.text, $id.object ], $AGENDAGROUP.getLine(), $AGENDAGROUP.getCharPositionInLine() ) } 134 | ; 135 | 136 | when returns [object] //returns a When object 137 | : WHEN COLON NEWLINE { $object = When( [$WHEN.text, $COLON.text], $WHEN.getLine(), $WHEN.getCharPositionInLine() ) } 138 | INDENT ( ruleCondition { $object.append_child( $ruleCondition.object ) } )? DEDENT 139 | ; 140 | 141 | then returns [object] //return a Then object 142 | : THEN COLON NEWLINE { $object = Then( [$THEN.text, $COLON.text], $THEN.getLine(), $THEN.getCharPositionInLine() ) } 143 | INDENT ( action { $object.append_child( $action.object ) } )+ DEDENT 144 | ; 145 | 146 | ruleCondition returns [object] // returns a NotCondition object 147 | : notCondition NEWLINE { $object = RuleCondition($notCondition.object) } 148 | ; 149 | 150 | notCondition returns [object] // returns a NotCondition object 151 | @init{ $object = NotCondition() } 152 | : ( NOT { $object.append_child( $NOT.text ) } )* condition { $object.append_child( $condition.object ) } 153 | ; 154 | 155 | condition returns [object] // returns a Condition object 156 | @init{ $object = Condition() } 157 | : ( EXISTS { $object.append_child( $EXISTS.text ) } )? classConstraint { $object.append_child( $classConstraint.object ) } 158 | ; 159 | 160 | classConstraint returns [object] // returns ClassConstraint object 161 | @init{ $object = ClassConstraint() } 162 | : ( OBJECTBINDING ASSIGNEQUAL { $object.append_children( [ $OBJECTBINDING.text, $ASSIGNEQUAL.text] ) } )? 163 | NAME LPAREN { $object.append_children( [$NAME.text, $LPAREN.text] ); } ( constraint { $object.append_child( $constraint.object ) } )? RPAREN { $object.append_child( $RPAREN.text ) } 164 | ; 165 | 166 | action returns [object] // returns an Action object 167 | : simpleStmt { $object = Action( $simpleStmt.object, $simpleStmt.object.line, $simpleStmt.object.column ) } 168 | | attributeAction { $object = Action( $attributeAction.object, $attributeAction.object.line, $attributeAction.object.column ) } 169 | | learnAction { $object = Action( $learnAction.object, $learnAction.object.line, $learnAction.object.column ) } 170 | | forgetAction { $object = Action( $forgetAction.object, $forgetAction.object.line, $forgetAction.object.column ) } 171 | | modifyAction { $object = Action( $modifyAction.object, $modifyAction.object.line, $modifyAction.object.column ) } 172 | | haltAction { $object = Action( $haltAction.object, $haltAction.object.line, $haltAction.object.column ) } 173 | ; 174 | 175 | simpleStmt returns [object] // returns a SimpleStmt object 176 | : expressionStmt { $object = SimpleStmt( $expressionStmt.object ) } 177 | | printStmt { $object = SimpleStmt( $printStmt.object, $printStmt.object.line, $printStmt.object.column ) } 178 | ; 179 | 180 | attributeAction returns [object] // returns a AttributeAction object 181 | : ATTRIBUTE expressionStmt { $object = AttributeAction( [ $ATTRIBUTE.text, $expressionStmt.object ] , $ATTRIBUTE.getLine(), $ATTRIBUTE.getCharPositionInLine() ) } 182 | ; 183 | 184 | printStmt returns [object] // returns a PrintStmt object 185 | : PRINT { $object = PrintStmt( $PRINT.text, $PRINT.getLine(), $PRINT.getCharPositionInLine() ) } 186 | ( comparisonList1=comparisonList { $object.append_child( $comparisonList1.object ) } 187 | | RIGHTSHIFT comparisonList2=comparisonList { object.append_children( [$RIGHTSHIFT.text, $comparisonList2.object] ) } )? NEWLINE 188 | ; 189 | 190 | forgetAction returns [object] // returns a ForgetAction object 191 | : ( FORGET { $object = ForgetAction( $FORGET.text, $FORGET.getLine(), $FORGET.getCharPositionInLine() ) } 192 | | DELETE { $object = ForgetAction( $DELETE.text, $DELETE.getLine(), $DELETE.getCharPositionInLine() ) } ) 193 | OBJECTBINDING { $object.append_child( $OBJECTBINDING.text ) } NEWLINE 194 | ; 195 | 196 | learnAction returns [object] // returns an LearnActions object 197 | : ( LEARN { $object = LearnAction( $LEARN.text, $LEARN.getLine(), $LEARN.getCharPositionInLine() ) } 198 | | INSERT { $object = LearnAction( $INSERT.text, $INSERT.getLine(), $INSERT.getCharPositionInLine() ) } ) 199 | NAME LPAREN { $object.append_children( [$NAME.text, $LPAREN.text] ) } 200 | ( argumentList { $object.append_child( $argumentList.object ) } )? RPAREN { $object.append_child( $RPAREN.text ) } NEWLINE 201 | ; 202 | 203 | modifyAction returns [object] // returns a ModifyAction object 204 | : MODIFY OBJECTBINDING COLON NEWLINE { $object = ModifyAction( [$MODIFY.text, $OBJECTBINDING.text, $COLON.text], $MODIFY.getLine(), $MODIFY.getCharPositionInLine() ) } 205 | INDENT ( propertyAssignment { $object.append_child( $propertyAssignment.object ) } )+ DEDENT 206 | ; 207 | 208 | haltAction returns [object] // returns a HaltAction object 209 | : HALT { $object = HaltAction( $HALT.text, $HALT.getLine(), $HALT.getCharPositionInLine() ) } NEWLINE 210 | ; 211 | 212 | propertyAssignment returns [object] // returns a PropertyAssignment object 213 | : NAME assignment constraint {$object = PropertyAssignment( [$NAME.text, $assignment.object, $constraint.object] ) } NEWLINE 214 | ; 215 | 216 | expressionStmt returns [object] // returns a ExpressionStmt object 217 | : comparisonList1=comparisonList { $object = ExpressionStmt( $comparisonList1.object ) } 218 | ( assignment comparisonList2=comparisonList { $object.append_children( [$assignment.object, $comparisonList2.object] ) } )? NEWLINE 219 | ; 220 | 221 | assignment returns [object] // returns a Assign object 222 | : ASSIGN { $object = Assignment( $ASSIGN.text ) } 223 | | PLUSEQUAL { $object = Assignment( $PLUSEQUAL.text ) } 224 | | MINUSEQUAL { $object = Assignment( $MINUSEQUAL.text ) } 225 | | STAREQUAL { $object = Assignment( $STAREQUAL.text ) } 226 | | SLASHEQUAL { $object = Assignment( $SLASHEQUAL.text ) } 227 | | PERCENTEQUAL { $object = Assignment( $PERCENTEQUAL.text ) } 228 | | AMPEREQUAL { $object = Assignment( $AMPEREQUAL.text ) } 229 | | VBAREQUAL { $object = Assignment( $VBAREQUAL.text ) } 230 | | CIRCUMFLEXEQUAL { $object = Assignment( $CIRCUMFLEXEQUAL.text ) } 231 | | LEFTSHIFTEQUAL { $object = Assignment( $LEFTSHIFTEQUAL.text ) } 232 | | RIGHTSHIFTEQUAL { $object = Assignment( $RIGHTSHIFTEQUAL.text ) } 233 | | DOUBLESTAREQUAL { $object = Assignment( $DOUBLESTAREQUAL.text ) } 234 | | DOUBLESLASHEQUAL { $object = Assignment( $DOUBLESLASHEQUAL.text ) } 235 | ; 236 | 237 | constraint returns [object] // returns a Contraint object 238 | : orConstraint { $object = Constraint( $orConstraint.object ) } 239 | ; 240 | 241 | orConstraint returns [object] // returns an OrContraint object 242 | : constraint1=andConstraint { $object = OrConstraint( $constraint1.object ) } 243 | ( OR constraint2=andConstraint { $object.append_children( [$OR.text, $constraint2.object] ) } )* 244 | ; 245 | 246 | andConstraint returns [object] // returns an AndConstraint object 247 | : constraint1=notConstraint {$object = AndConstraint( $constraint1.object )} 248 | ( AND constraint2=notConstraint { $object.append_children( [$AND.text, $constraint2.object] ) } )* 249 | ; 250 | 251 | notConstraint returns [object] // returns a NotConstraint object 252 | @init{ $object = NotConstraint() } 253 | : ( NOT { $object.append_child( $NOT.text ) } )* comparison { $object.append_child( $comparison.object ) } 254 | ; 255 | 256 | comparison returns [object] // returns a Comparison object 257 | : expression1=expression { $object = Comparison( $expression1.object ) } 258 | ( comparisonOperation expression2=expression { $object.append_children( [ $comparisonOperation.object, $expression2.object] ) } )* 259 | ; 260 | 261 | comparisonOperation returns [object] // returns a ComparisonOperation object 262 | : ( LESS { $object = ComparisonOperation( $LESS.text ) } 263 | | GREATER { $object = ComparisonOperation( $GREATER.text ) } 264 | | EQUAL { $object = ComparisonOperation( $EQUAL.text ) } 265 | | GREATEREQUAL { $object = ComparisonOperation( $GREATEREQUAL.text ) } 266 | | LESSEQUAL { $object = ComparisonOperation( $LESSEQUAL.text ) } 267 | | ALT_NOTEQUAL { $object = ComparisonOperation( $ALT_NOTEQUAL.text ) } 268 | | NOTEQUAL { $object = ComparisonOperation( $NOTEQUAL.text ) } 269 | | IN { $object = ComparisonOperation( "in" ) } 270 | | NOT IN { $object = ComparisonOperation( "not in" ) } 271 | | IS { $object = ComparisonOperation( "is" ) } 272 | | IS NOT { $object = ComparisonOperation( "is not" ) } ) 273 | ; 274 | 275 | expression returns [object] // returns Exrpession object 276 | : bitwiseOrExpr { $object = Expression( $bitwiseOrExpr.object ) } 277 | ; 278 | 279 | bitwiseOrExpr returns [object] // returns a BitwiseOrExpr object 280 | : expr1=bitwiseXorExpr { $object = BitwiseOrExpr( $expr1.object ) } 281 | ( VBAR expr2=bitwiseXorExpr { $object.append_children( [$VBAR.text, $expr2.object] ) } )* 282 | ; 283 | 284 | bitwiseXorExpr returns [object] // returns a BitwiseXorExpr object 285 | : expr1=bitwiseAndExpr { $object = BitwiseXorExpr( $expr1.object ) } 286 | ( CIRCUMFLEX expr2=bitwiseAndExpr { $object.append_children( [$CIRCUMFLEX.text, $expr2.object] ) } )* 287 | ; 288 | 289 | bitwiseAndExpr returns [object] // returns a BitwiseAndExpr object 290 | : expr1=shiftExpr { $object = BitwiseAndExpr( $expr1.object ) } 291 | ( AMPER expr2=shiftExpr { $object.append_children( [$AMPER.text, $expr2.object] ) } )* 292 | ; 293 | 294 | shiftExpr returns [object] // returns a ShiftExpr object 295 | : expr1=arithExpr { $object = ShiftExpr( $expr1.object ) } 296 | ( ( LEFTSHIFT { $object.append_child( $LEFTSHIFT.text ) } | RIGHTSHIFT { $object.append_child( $RIGHTSHIFT.text ) } ) 297 | expr2=arithExpr { $object.append_child( $expr2.object ) } )* 298 | ; 299 | 300 | arithExpr returns [object] // returns a ArithExpr object 301 | : term1=term { $object = ArithExpr( $term1.object ) } 302 | ( ( PLUS { $object.append_child( $PLUS.text ) } | MINUS { $object.append_child( $MINUS.text ) } ) 303 | term2=term { $object.append_child( $term2.object ) } )* 304 | ; 305 | 306 | term returns [object] // returns a Term object 307 | : factor1=factor {$object = Term( $factor1.object ) } 308 | ( (STAR { $object.append_child( $STAR.text ) } | SLASH { $object.append_child( $SLASH.text ) } 309 | | PERCENT { $object.append_child( $PERCENT.text ) } | DOUBLESLASH { $object.append_child( $DOUBLESLASH.text ) } ) 310 | factor2=factor { $object.append_child( $factor2.object ) } )* 311 | ; 312 | 313 | factor returns [object] // returns a Factor object 314 | : PLUS factor1=factor { $object = Factor( [$PLUS.text, $factor1.object] ) } 315 | | MINUS factor2=factor { $object = Factor( [$MINUS.text, $factor2.object] ) } 316 | | TILDE factor3=factor { $object = Factor( [$TILDE.text, $factor3.object] ) } 317 | | power { $object = Factor( $power.object ) } 318 | ; 319 | 320 | power returns [object] // returns a Power object 321 | : atom { $object = Power( $atom.object ) } 322 | ( trailer { $object.append_child( $trailer.object ) } )* 323 | (options {greedy=true;}:DOUBLESTAR factor { $object.append_children( [$DOUBLESTAR.text, $factor.object] ) } )? 324 | ; 325 | 326 | atom returns [object] // returns a Atom object 327 | : LPAREN {$object = Atom( $LPAREN.text ) } 328 | ( comparisonList1=comparisonList { $object.append_child( $comparisonList1.object ) } )? 329 | RPAREN { $object.append_child( $RPAREN.text ) } 330 | | LBRACK {$object = Atom( $LBRACK.text ) } 331 | ( listmaker { $object.append_child( $listmaker.object ) } )? 332 | RBRACK { $object.append_child( $RBRACK.text ) } 333 | | LCURLY {$object = Atom( $LCURLY.text ) } 334 | ( dictmaker { $object.append_child( $dictmaker.object ) } )? 335 | RCURLY { $object.append_child( $RCURLY.text ) } 336 | // | BACKQUOTE comparisonList2=comparisonList BACKQUOTE { $object = Atom( ["`" , $comparisonList2.object, "`"] ) } Removed, used repr(contraint) instead 337 | | NAME {$object = Atom( $NAME.text ) } 338 | | OBJECTBINDING {$object = Atom( $OBJECTBINDING.text ) } 339 | | INT {$object = Atom( $INT.text ) } 340 | | LONGINT {$object = Atom( $LONGINT.text ) } 341 | | FLOAT {$object = Atom( $FLOAT.text ) } 342 | | COMPLEX {$object = Atom( $COMPLEX.text ) } 343 | | {$object = Atom() } ( STRING { $object.append_child( $STRING.text ) } )+ 344 | ; 345 | 346 | listmaker returns [object] // returns a ListMaker object 347 | : constraint1=constraint {$object = ListMaker( $constraint1.object ) } 348 | (options {greedy=true;}:COMMA constraint2=constraint { $object.append_children( [ ",", $constraint2.object] ) } )* 349 | ( COMMA { $object.append_child( "," ) } )? 350 | ; 351 | 352 | comparisonList returns [object] // returns a ComparisonList object 353 | : constraint1=constraint { $object = ComparisonList( $constraint1.object ) } 354 | (options {k=2;}:COMMA constraint2=constraint { $object.append_children( [",", $constraint2.object] ) } )* 355 | ( COMMA { $object.append_child( "," ) } )? 356 | ; 357 | 358 | trailer returns [object] // returns a Trailer object 359 | : LPAREN {$object = Trailer( $LPAREN.text ) } (argumentList { $object.append_child( $argumentList.object ) } )? RPAREN { $object.append_child( $RPAREN.text ) } 360 | | LBRACK {$object = Trailer( $LBRACK.text ) } (constraint { $object.append_child( $constraint.object ) } )? RBRACK { $object.append_child( $RBRACK.text ) } 361 | | DOT NAME { $object = Trailer( [$DOT.text, $NAME.text] ) } 362 | ; 363 | 364 | expressionList returns [object] // returns an ExpressionList object 365 | : expression1=expression { $object = ExpressionList( $expression1.object ) } 366 | ( options {k=2;}: COMMA expression2=expression { $object.append_children( [ ",", $expression2.object ] ) } )* 367 | ( COMMA { $object.append_child( "," ) } )? 368 | ; 369 | 370 | dictmaker returns [object] // returns a DictMaker object 371 | : constraint1=constraint { $object = DictMaker( $constraint1.object ) } 372 | COLON constraint2=constraint { $object.append_children( [":", $constraint2.object] ) } 373 | (options {k=2;}: COMMA constraint3=constraint COLON constraint4=constraint { $object.append_children( [",", $constraint3.object, ":", $constraint4.object] ) } )* 374 | ( COMMA { $object.append_child( "," ) } )? 375 | ; 376 | 377 | argumentList returns [object] // returns an ArgumentList object 378 | : constraint1=constraint { $object = ArgumentList( $constraint1.object ) } 379 | ( COMMA constraint2=constraint { $object.append_children( [",", $constraint2.object] ) } )* 380 | ( COMMA { $object.append_child( "," ) } )? 381 | ; 382 | 383 | // 384 | // L E X E R 385 | // 386 | 387 | LPAREN 388 | : '(' {self.implicitLineJoiningLevel += 1} 389 | ; 390 | 391 | RPAREN 392 | : ')' {self.implicitLineJoiningLevel -= 1} 393 | ; 394 | 395 | LBRACK 396 | : '[' {self.implicitLineJoiningLevel += 1} 397 | ; 398 | 399 | RBRACK 400 | : ']' {self.implicitLineJoiningLevel -= 1} 401 | ; 402 | 403 | LCURLY 404 | : '{' {self.implicitLineJoiningLevel += 1} 405 | ; 406 | 407 | RCURLY 408 | : '}' {self.implicitLineJoiningLevel -= 1} 409 | ; 410 | 411 | COLON 412 | : ':' 413 | ; 414 | 415 | COMMA 416 | : ',' 417 | ; 418 | 419 | DOT 420 | : '.' 421 | ; 422 | 423 | SEMI 424 | : ';' 425 | ; 426 | 427 | PLUS 428 | : '+' 429 | ; 430 | 431 | MINUS 432 | : '-' 433 | ; 434 | 435 | STAR 436 | : '*' 437 | ; 438 | 439 | DOLLAR 440 | : '$' 441 | ; 442 | 443 | SLASH 444 | : '/' 445 | ; 446 | 447 | VBAR 448 | : '|' 449 | ; 450 | 451 | AMPER 452 | : '&' 453 | ; 454 | 455 | LESS 456 | : '<' 457 | ; 458 | 459 | GREATER 460 | : '>' 461 | ; 462 | 463 | ASSIGN 464 | : '=' 465 | ; 466 | 467 | PERCENT 468 | : '%' 469 | ; 470 | 471 | BACKQUOTE 472 | : '`' 473 | ; 474 | 475 | CIRCUMFLEX 476 | : '^' 477 | ; 478 | 479 | TILDE 480 | : '~' 481 | ; 482 | 483 | EQUAL 484 | : '==' 485 | ; 486 | 487 | ASSIGNEQUAL 488 | : ':=' 489 | ; 490 | 491 | NOTEQUAL 492 | : '!=' 493 | ; 494 | 495 | ALT_NOTEQUAL 496 | : '<>' 497 | ; 498 | 499 | LESSEQUAL 500 | : '<=' 501 | ; 502 | 503 | LEFTSHIFT 504 | : '<<' 505 | ; 506 | 507 | GREATEREQUAL 508 | : '>=' 509 | ; 510 | 511 | RIGHTSHIFT 512 | : '>>' 513 | ; 514 | 515 | PLUSEQUAL 516 | : '+=' 517 | ; 518 | 519 | MINUSEQUAL 520 | : '-=' 521 | ; 522 | 523 | DOUBLESTAR 524 | : '**' 525 | ; 526 | 527 | STAREQUAL 528 | : '*=' 529 | ; 530 | 531 | DOUBLESLASH 532 | : '//' 533 | ; 534 | 535 | SLASHEQUAL 536 | : '/=' 537 | ; 538 | 539 | VBAREQUAL 540 | : '|=' 541 | ; 542 | 543 | PERCENTEQUAL 544 | : '%=' 545 | ; 546 | 547 | AMPEREQUAL 548 | : '&=' 549 | ; 550 | 551 | CIRCUMFLEXEQUAL 552 | : '^=' 553 | ; 554 | 555 | LEFTSHIFTEQUAL 556 | : '<<=' 557 | ; 558 | 559 | RIGHTSHIFTEQUAL 560 | : '>>=' 561 | ; 562 | 563 | DOUBLESTAREQUAL 564 | : '**=' 565 | ; 566 | 567 | DOUBLESLASHEQUAL 568 | : '//=' 569 | ; 570 | 571 | GLOBAL 572 | : 'global' 573 | ; 574 | 575 | ATTRIBUTE 576 | : 'attribute' 577 | ; 578 | 579 | RULE 580 | : 'rule' 581 | ; 582 | 583 | AGENDAGROUP 584 | : 'agenda-group' 585 | ; 586 | 587 | WHEN 588 | : 'when' 589 | ; 590 | 591 | EXISTS 592 | : 'exists' 593 | ; 594 | 595 | THEN 596 | : 'then' 597 | ; 598 | 599 | MODIFY 600 | : 'modify' 601 | ; 602 | 603 | INSERT 604 | : 'insert' 605 | ; 606 | 607 | LEARN 608 | : 'learn' 609 | ; 610 | 611 | DELETE 612 | : 'delete' 613 | ; 614 | 615 | FORGET 616 | : 'forget' 617 | ; 618 | 619 | HALT 620 | : 'halt' 621 | ; 622 | 623 | PRINT 624 | : 'print' 625 | ; 626 | 627 | IMPORT 628 | : 'import' 629 | ; 630 | 631 | FROM 632 | : 'from' 633 | ; 634 | 635 | AND 636 | : 'and' 637 | ; 638 | 639 | OR 640 | : 'or' 641 | ; 642 | 643 | IN 644 | : 'in' 645 | ; 646 | 647 | IS 648 | : 'is' 649 | ; 650 | 651 | AS 652 | : 'as' 653 | ; 654 | 655 | NOT 656 | : 'not' 657 | ; 658 | 659 | fragment 660 | LETTER 661 | : ('a'..'z' | 'A'..'Z') 662 | ; 663 | 664 | fragment 665 | DIGIT 666 | : ('0'.. '9') 667 | ; 668 | 669 | FLOAT 670 | : '.' DIGIT+ (EXPONENT)? 671 | | DIGIT+ '.' EXPONENT 672 | | DIGIT+ ('.' (DIGIT+ (EXPONENT)?)? | EXPONENT) 673 | ; 674 | 675 | LONGINT 676 | : INT ('l'|'L') 677 | ; 678 | 679 | fragment 680 | EXPONENT 681 | : ('e' | 'E') ( '+' | '-' )? DIGIT+ 682 | ; 683 | 684 | INT 685 | : '0' ('x' | 'X') ( DIGIT | 'a' .. 'f' | 'A' .. 'F' )+ 686 | | '0' DIGIT* 687 | | '1'..'9' DIGIT* 688 | ; 689 | 690 | COMPLEX 691 | : INT ('j'|'J') 692 | | FLOAT ('j'|'J') 693 | ; 694 | 695 | NAME 696 | : ( LETTER | '_') ( LETTER | '_' | DIGIT )* 697 | ; 698 | 699 | OBJECTBINDING 700 | : DOLLAR NAME 701 | ; 702 | 703 | STRING 704 | : ('r'|'u'|'ur')? 705 | ( '\'\'\'' (options {greedy=false;}:TRIAPOS)* '\'\'\'' 706 | | '"""' (options {greedy=false;}:TRIQUOTE)* '"""' 707 | | '"' (ESC|~('\\'|'\n'|'"'))* '"' 708 | | '\'' (ESC|~('\\'|'\n'|'\''))* '\'' 709 | ) 710 | ; 711 | 712 | fragment 713 | TRIQUOTE 714 | : '"'? '"'? (ESC|~('\\'|'"'))+ 715 | ; 716 | 717 | fragment 718 | TRIAPOS 719 | : '\''? '\''? (ESC|~('\\'|'\''))+ 720 | ; 721 | 722 | fragment 723 | ESC 724 | : '\\' . 725 | ; 726 | 727 | CONTINUED_LINE 728 | : '\\' ( '\r' )? '\n' ( ' ' | '\t' )* { $channel=HIDDEN } 729 | ( newline=NEWLINE { self.emit( ClassicToken( NEWLINE, newline.text ) ) } 730 | | 731 | ) 732 | ; 733 | 734 | NEWLINE 735 | : ( ( '\u000C' )? ( '\r' )? '\n' )+ { 736 | if self.startPosition == 0 or self.implicitLineJoiningLevel > 0: 737 | $channel=HIDDEN 738 | } 739 | ; 740 | 741 | WS 742 | : { self.startPosition > 0 }? => ( ' ' | '\t' )+ { $channel=HIDDEN } 743 | ; 744 | 745 | LEADING_WS 746 | @init { spaces = 0 } 747 | : { self.startPosition == 0 }? => 748 | ( {self.implicitLineJoiningLevel > 0}? ( ' ' | '\t' )+ { $channel=HIDDEN } 749 | | ( ' ' { spaces += 1 } 750 | | '\t' { 751 | spaces += 8 752 | spaces -= (spaces \% 8) 753 | } 754 | )+ { self.emit(ClassicToken(LEADING_WS, ' '*spaces)) } 755 | ( ('\r')? '\n' { 756 | if self._state.token is not None: 757 | self._state.token.setChannel(99) 758 | else: 759 | $channel=HIDDEN 760 | } 761 | )* 762 | ) 763 | ; 764 | 765 | COMMENT 766 | @init { $channel=HIDDEN } 767 | : { self.startPosition == 0 }? => (' '|'\t')* '#' (~'\n')* '\n'+ 768 | | { self.startPosition > 0 }? => '#' (~'\n')* 769 | ; 770 | -------------------------------------------------------------------------------- /intellect/grammar/Policy.tokens: -------------------------------------------------------------------------------- 1 | SLASHEQUAL=38 2 | BACKQUOTE=80 3 | EXPONENT=84 4 | STAR=64 5 | CIRCUMFLEXEQUAL=42 6 | LETTER=82 7 | TRIAPOS=85 8 | GREATEREQUAL=52 9 | COMPLEX=77 10 | ASSIGNEQUAL=24 11 | NOT=21 12 | NOTEQUAL=55 13 | LEADING_WS=90 14 | MINUSEQUAL=36 15 | VBAR=58 16 | RPAREN=10 17 | IMPORT=7 18 | NAME=12 19 | GREATER=50 20 | INSERT=31 21 | DOUBLESTAREQUAL=45 22 | LESS=49 23 | COMMENT=91 24 | RBRACK=71 25 | RULE=15 26 | LCURLY=72 27 | INT=74 28 | DELETE=29 29 | RIGHTSHIFT=27 30 | DOUBLESLASHEQUAL=46 31 | WS=89 32 | VBAREQUAL=41 33 | OR=47 34 | LONGINT=75 35 | FORGET=28 36 | FROM=8 37 | PERCENTEQUAL=39 38 | LESSEQUAL=53 39 | DOLLAR=79 40 | MODIFY=32 41 | DOUBLESLASH=67 42 | LBRACK=70 43 | CONTINUED_LINE=88 44 | OBJECTBINDING=23 45 | DOUBLESTAR=69 46 | HALT=33 47 | ESC=87 48 | ATTRIBUTE=25 49 | DEDENT=5 50 | FLOAT=76 51 | RIGHTSHIFTEQUAL=44 52 | AND=48 53 | LEARN=30 54 | INDENT=4 55 | LPAREN=9 56 | PLUSEQUAL=35 57 | AS=13 58 | SLASH=65 59 | THEN=20 60 | IN=56 61 | COMMA=11 62 | IS=57 63 | AMPER=60 64 | EQUAL=51 65 | TILDE=68 66 | LEFTSHIFTEQUAL=43 67 | LEFTSHIFT=61 68 | PLUS=62 69 | EXISTS=22 70 | DIGIT=83 71 | DOT=14 72 | AGENDAGROUP=18 73 | PERCENT=66 74 | MINUS=63 75 | SEMI=78 76 | PRINT=26 77 | COLON=16 78 | TRIQUOTE=86 79 | AMPEREQUAL=40 80 | NEWLINE=6 81 | WHEN=19 82 | RCURLY=73 83 | ASSIGN=34 84 | GLOBAL=81 85 | STAREQUAL=37 86 | CIRCUMFLEX=59 87 | STRING=17 88 | ALT_NOTEQUAL=54 89 | -------------------------------------------------------------------------------- /intellect/grammar/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2011, The MITRE Corporation. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. All advertising materials mentioning features or use of this software 13 | must display the following acknowledgement: 14 | This product includes software developed by the author. 15 | 4. Neither the name of the author nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | """ -------------------------------------------------------------------------------- /intellect/reflection.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # coding=utf-8 3 | """ 4 | Copyright (c) 2011, The MITRE Corporation. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | """ 34 | reflection 35 | 36 | Description: Functions for performing introspection 37 | 38 | Initial Version: Feb 1, 2011 39 | 40 | @author: Michael Joseph Walsh 41 | """ 42 | 43 | import inspect, os, sys, types, logging 44 | 45 | FUNCTION = "function" 46 | BUILTIN_FUNCTION = "built-in function" 47 | INSTANCE_METHOD = "instance method" 48 | CLASS_METHOD = classmethod 49 | STATIC_METHOD = staticmethod 50 | PROPERTY = property 51 | DATA = "data" 52 | 53 | def log(msg, name="intellect", level=logging.DEBUG): 54 | ''' 55 | Logs at the 'level' for the messaged 'msg' 56 | 57 | Args: 58 | name: the name of the logger 59 | level: must be either logging.DEBUG, logging.INFO, logging.WARNING, 60 | logging.ERROR, logging.CRITICAL 61 | msg: message string 62 | ''' 63 | 64 | if level not in [logging.DEBUG, logging.INFO, logging.WARNING, 65 | logging.ERROR, logging.CRITICAL]: 66 | raise ValueError("'level' must be either logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL") 67 | 68 | logging.getLogger(name).log(level, "{0} :: {1}".format(__name__, msg)) 69 | 70 | 71 | def is_method(klazz, name): 72 | ''' 73 | does this class have this method? 74 | ''' 75 | return (inspect_class_for_attribute(klazz, name)[0] in [INSTANCE_METHOD, CLASS_METHOD, STATIC_METHOD]) 76 | 77 | 78 | def has_attribute(klazz, name): 79 | ''' 80 | does this class have this attribute in other words does it have this 81 | (instance/class/static) method, property, or global? 82 | ''' 83 | return (inspect_class_for_attribute(klazz, name)) != (None, None, None) 84 | 85 | 86 | def is_instance_method(klazz, name): 87 | ''' 88 | does this class have this method? 89 | ''' 90 | return (inspect(klazz, name)[0] is INSTANCE_METHOD) 91 | 92 | 93 | def is_class_method(klazz, name): 94 | ''' 95 | does this class have this class method? 96 | ''' 97 | return (inspect_class_for_attribute(klazz, name)[0] is CLASS_METHOD) 98 | 99 | 100 | def is_static_method(klazz, name): 101 | ''' 102 | does this class have this static method? 103 | ''' 104 | return (inspect_class_for_attribute(klazz, name)[0] is STATIC_METHOD) 105 | 106 | 107 | def is_property(klazz, name): 108 | ''' 109 | does this class have this property? 110 | ''' 111 | return (inspect_class_for_attribute(klazz, name)[0] is PROPERTY) 112 | 113 | 114 | def is_data(klazz, name): 115 | ''' 116 | does this class have this global or attribute? 117 | ''' 118 | return (inspect_class_for_attribute(object, name)[0] is DATA) 119 | 120 | 121 | def inspect_class_for_attribute(klazz, name): 122 | ''' 123 | For a given class inspects it for the existence of named instance/class/static method, 124 | property, and data element (global or attribute) attribute; and returns in tuple form 125 | (kind, obj, homeClass) 126 | ''' 127 | 128 | if inspect.isclass(klazz): 129 | if name in dir(klazz): 130 | if name in klazz.__dict__: 131 | obj = klazz.__dict__[name] 132 | else: 133 | obj = getattr(klazz, name) 134 | 135 | homeClass = getattr(obj, "__objclass__", None) 136 | 137 | if homeClass is None: 138 | for base in inspect.getmro(klazz): 139 | if name in base.__dict__: 140 | homeClass = base 141 | break 142 | 143 | if ((homeClass is not None) and (name in homeClass.__dict__)): 144 | obj = homeClass.__dict__[name] 145 | obj_via_getattr = getattr(klazz, name) 146 | 147 | if isinstance(obj, staticmethod): 148 | kind = staticmethod 149 | elif isinstance(obj, classmethod): 150 | kind = CLASS_METHOD 151 | elif isinstance(obj, property): 152 | kind = PROPERTY 153 | elif (inspect.ismethod(obj_via_getattr) or 154 | inspect.ismethoddescriptor(obj_via_getattr)): 155 | kind = INSTANCE_METHOD 156 | else: 157 | kind = DATA 158 | 159 | return (kind, obj, homeClass) 160 | else: 161 | return (None, None, None) 162 | else: 163 | raise TypeError("parameter 'klazz' must a class object, not an instance of a class.") 164 | 165 | 166 | def inspect_module_for_attribute(module, name): 167 | ''' 168 | For a given module inspects it for the existence of named built-in, 169 | functions, and data attributes; and returns in tuple form 170 | (kind, obj) 171 | ''' 172 | if inspect.ismodule(module): 173 | 174 | names = dir(module) 175 | 176 | if name in names: 177 | 178 | if name in module.__dict__: 179 | obj = module.__dict__[name] 180 | else: 181 | obj = getattr(module, name) 182 | 183 | if isinstance(obj, types.FunctionType): 184 | kind = FUNCTION 185 | elif isinstance(obj, types.BuiltinFunctionType): 186 | kind = BUILTIN_FUNCTION 187 | else: 188 | kind = DATA 189 | 190 | return (kind, obj) 191 | else: 192 | return (None, None, None) 193 | else: 194 | raise TypeError("parameter 'module' must be a Module, not an instance of a class, nor a Class.") 195 | 196 | 197 | def for_class_list(klazz, type): 198 | ''' 199 | A wrapper to for_object_list-method for evaluating just Class-type objects 200 | ''' 201 | if inspect.isclass(klazz): 202 | return for_object_list(klazz, type) 203 | else: 204 | raise TypeError("parameter 'klazz' was not a Class, but an instance object.") 205 | 206 | 207 | def for_object_list(object, type): 208 | ''' 209 | For a given object inspects it for the existence of named instance/class/static method, 210 | property, and data element (global or attribute) attribute; and returns in tuple form 211 | (kind, obj, homeClass) 212 | ''' 213 | value = [] 214 | names = dir(object) 215 | 216 | for name in names: 217 | if name in object.__dict__: 218 | obj = object.__dict__[name] 219 | else: 220 | obj = getattr(object, name) 221 | 222 | homeClass = getattr(obj, "__objclass__", None) 223 | 224 | if homeClass is None: 225 | for base in inspect.getmro(object): 226 | if name in base.__dict__: 227 | homeClass = base 228 | break 229 | 230 | if ((homeClass is not None) and (name in homeClass.__dict__)): 231 | obj = homeClass.__dict__[name] 232 | obj_via_getattr = getattr(object, name) 233 | 234 | if isinstance(obj, staticmethod): 235 | kind = staticmethod 236 | elif isinstance(obj, classmethod): 237 | kind = CLASS_METHOD 238 | elif isinstance(obj, property): 239 | kind = PROPERTY 240 | elif (inspect.ismethod(obj_via_getattr) or 241 | inspect.ismethoddescriptor(obj_via_getattr)): 242 | kind = INSTANCE_METHOD 243 | else: 244 | kind = DATA 245 | 246 | print name, kind, obj 247 | 248 | if (kind is type): 249 | value.append(name) 250 | 251 | return value 252 | 253 | 254 | def for_module_list(module, type): 255 | ''' 256 | For a given module list either the built-in or data attributes. 257 | 258 | Example: 259 | 260 | for_module_list(__builtins__, BUILTIN) 261 | 262 | Will return a list of all the Python interpreter built-in functions 263 | 264 | ''' 265 | 266 | value = [] 267 | 268 | if inspect.ismodule(module): 269 | names = dir(module) 270 | 271 | for name in names: 272 | 273 | if name in module.__dict__: 274 | obj = module.__dict__[name] 275 | else: 276 | obj = getattr(module, name) 277 | 278 | if isinstance(obj, types.FunctionType): 279 | kind = FUNCTION 280 | elif isinstance(obj, types.BuiltinFunctionType): 281 | kind = BUILTIN_FUNCTION 282 | else: 283 | kind = DATA 284 | 285 | if (kind is type): 286 | value.append(name) 287 | 288 | return value 289 | 290 | 291 | def class_from_string(className, policy): 292 | """ 293 | Returns class object 294 | 295 | Args: 296 | className: A string holding either the class identifier or local name. 297 | policy: Policy providing ImportFrom objects to inspect. 298 | 299 | Raises: 300 | SyntaxError, if the class wasn't declared in a fromImport statement in the policy file 301 | """ 302 | 303 | identifier = "" 304 | dottedName = "" 305 | 306 | importFromClass = class_from_str("intellect.Node.ImportFrom") 307 | 308 | importFroms = [importStmt.importStmt for importStmt in policy.importStmts if isinstance(importStmt.importStmt, importFromClass)] 309 | 310 | if not importFroms: 311 | raise SyntaxError("{0} was not declared in a importFrom statement in the policy file: '{1}'".format(className, policy.path)) 312 | else: 313 | for importFrom in reversed(importFroms): 314 | for importAsName in reversed(importFrom.importAsNames.importAsNames): 315 | if importAsName.localName is not None and importAsName.localName == className: 316 | # finding a match matching the localname for the className, 317 | # hold the class's dottedName and identifier for later use 318 | matchedImportFrom = importFrom # used for raising SyntaxError 319 | dottedName = importFrom.dottedName 320 | identifier = importAsName.identifier 321 | break 322 | elif importAsName.localName is None and importAsName.identifier == className: 323 | # finding a match matching the localname for the className, 324 | # hold the class's dottedName and identifier for later use 325 | matchedImportFrom = importFrom # used for raising SyntaxError 326 | dottedName = importFrom.dottedName 327 | identifier = importAsName.identifier 328 | break 329 | 330 | if identifier: 331 | break 332 | 333 | if not identifier: 334 | # the className was not imported, raise a SyntaxError 335 | # TODO: include the line to assist the policy author 336 | raise SyntaxError("{0} was not declared in a fromImport statement in the policy file: '{1}'".format(className, policy.path)) 337 | else: 338 | # otherwise return the class 339 | 340 | try: 341 | module = __import__(str(dottedName), globals(), locals(), [identifier]) 342 | except ImportError as detail: 343 | raise SyntaxError("{0} at line: {1} in policy file: {2}".format(detail, matchedImportFrom.line, policy.path)) 344 | 345 | try: 346 | klazz = getattr(module, identifier) 347 | except AttributeError: 348 | raise SyntaxError("'{0}' does not exist in module imported from at line: {1} in policy file: '{2}'".format(identifier, matchedImportFrom.line, policy.path)) 349 | 350 | # return the class 351 | log("returning {0} for '{1}'".format(klazz, className)) 352 | 353 | return klazz 354 | 355 | 356 | def module_from_string(moduleName, policy): 357 | """ 358 | Returns module object 359 | 360 | Args: 361 | module: A string holding either the module identifier or local name. 362 | policy: Policy providing ImportFrom objects to inspect. 363 | 364 | Raises: 365 | SyntaxError, if the module wasn't declared in a importName statement in the policy file 366 | """ 367 | 368 | dottedName = "" 369 | 370 | importNameClass = class_from_str("intellect.Node.ImportName") 371 | 372 | importNames = [importStmt.importStmt for importStmt in policy.importStmts if isinstance(importStmt.importStmt, importNameClass)] 373 | 374 | if not importNames: 375 | raise SyntaxError("{0} was not declared in a importName statement in the policy file: '{1}'".format(moduleName, policy.path)) 376 | else: 377 | for importName in importNames: 378 | 379 | # first check all the localNames 380 | for dottedAsName in importName.dottedAsNames.dottedAsNames: 381 | if dottedAsName.localName is not None and dottedAsName.localName == moduleName: 382 | # finding a match matching the localname for the moduleName, 383 | # hold the module's dottedName for later use 384 | matchedImportName = importName # used for raising SyntaxError 385 | dottedName = dottedAsName.dottedName 386 | break 387 | elif dottedAsName.dottedName == moduleName: 388 | # finding a matching identifier, 389 | # hold the class's dottedName and identifier for later use 390 | matchedImportName = importName # used for raising PolicyException 391 | dottedName = moduleName 392 | break 393 | 394 | if dottedName: 395 | break 396 | 397 | if not dottedName: 398 | # the className was not imported, raise a SyntaxError 399 | # TODO: include the line to assist the policy author 400 | raise SyntaxError("{0} was not declared in a importName statement in the policy file: '{1}'".format(moduleName, policy.path)) 401 | else: 402 | # otherwise return the module 403 | try: 404 | # return the package object 405 | module = __import__(str(dottedName)) 406 | # then dynamically load the module from the package 407 | components = str(dottedName).split('.') 408 | for comp in components[1:]: 409 | module = getattr(module, comp) 410 | 411 | except ImportError as detail: 412 | raise SyntaxError("{0} at line: {1} in policy file: '{2}'".format(detail, matchedImportName.line, policy.path)) 413 | 414 | # returning the module, not the package... 415 | log("returning a {0}".format(module)) 416 | 417 | return module 418 | 419 | 420 | def module_from_str(name): 421 | ''' 422 | Returns a Module object from dottedName.identifier str such as 423 | 'intellect.reflection'. 424 | ''' 425 | module = sys.modules[name] 426 | 427 | log("returning {0} for {1}".format(module, name)) 428 | return module 429 | 430 | 431 | def class_from_str(name): 432 | ''' 433 | Returns a Class object from dottedName.identifier str such as 434 | 'intellect.Intellect.Intellect'. 435 | ''' 436 | 437 | dottedName, identifier = name.rsplit('.', 1) 438 | module = __import__(str(dottedName), globals(), locals(), [identifier]) 439 | klazz = getattr(module, identifier) 440 | 441 | log("returning {0} for {1}".format(klazz, name)) 442 | return klazz 443 | 444 | 445 | def is_instance(instance, klazz): 446 | ''' 447 | If the python interpreter is running a module as the main program, 448 | instances of the classes define in the same module will be instances 449 | of the scope (__main__) instead of what is expected. 450 | 451 | So, more work is needed to determine if the instance is of type klazz 452 | 453 | Returns True or False 454 | 455 | Args: 456 | instance: An object instance to introspect 457 | klazz: a class object. 458 | ''' 459 | 460 | log("instance = {0}".format(instance)) 461 | log("klazz = {0}".format(klazz)) 462 | 463 | value = isinstance(instance, klazz) 464 | 465 | log("value = {0}".format(value)) 466 | 467 | if (not value): 468 | path = sys.modules[instance.__module__].__file__.rsplit(".", 1)[0] 469 | pathComponents = path.split(os.sep) 470 | moduleName = pathComponents[len(pathComponents) - 1] 471 | 472 | del pathComponents[len(pathComponents) - 1:] 473 | 474 | path = "".join(path.rsplit(moduleName, 1)).rstrip(os.sep) 475 | 476 | while True: 477 | if os.path.exists(path + os.sep + "__init__.py"): 478 | log("{0} has a __init__.py file".format(path)) 479 | moduleComponent = pathComponents[len(pathComponents) - 1] 480 | moduleName = moduleComponent + "." + moduleName 481 | path = "".join(path.rsplit(moduleComponent, 1)).rstrip(os.sep) 482 | 483 | del pathComponents[len(pathComponents) - 1:] 484 | else: 485 | log("{0} doesn't have a __init__.py file".format(path)) 486 | break 487 | 488 | value = ((moduleName + '.' + instance.__class__.__name__) == (klazz.__module__ + '.' + klazz.__name__)) 489 | 490 | log("value = {0}".format(value)) 491 | 492 | return value 493 | 494 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | Copyright (c) 2011, Michael Joseph Walsh 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. All advertising materials mentioning features or use of this software 15 | must display the following acknowledgement: 16 | This product includes software developed by the author. 17 | 4. Neither the name of the author nor the 18 | names of its contributors may be used to endorse or promote products 19 | derived from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 25 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | ''' 32 | 33 | import os 34 | from setuptools import setup, find_packages 35 | 36 | DESCRIPTION = 'A Domain-specific language and Rules Engine for Python' 37 | 38 | if os.path.exists("README.rst"): 39 | long_description = open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.rst')).read() 40 | else: 41 | long_description = "See http://pypi.python.org/pypi/Intellect" 42 | 43 | def get_version(version_tuple): 44 | version = '%s.%s' % (version_tuple[0], version_tuple[1]) 45 | if version_tuple[2]: 46 | version = '%s.%s' % (version, version_tuple[2]) 47 | if version_tuple[3]: 48 | version = '%s.%s' % (version, version_tuple[3]) 49 | return version 50 | 51 | def fullsplit(path, result=None): 52 | """ 53 | Split a pathname into components (the opposite of os.path.join) in a 54 | platform-neutral way. 55 | """ 56 | if result is None: 57 | result = [] 58 | head, tail = os.path.split(path) 59 | if head == '': 60 | return [tail] + result 61 | if head == path: 62 | return result 63 | return fullsplit(head, [tail] + result) 64 | 65 | init = os.path.join(os.path.dirname(__file__), 'intellect', '__init__.py') 66 | version_line = filter(lambda l: l.startswith('VERSION'), open(init))[0] 67 | VERSION = get_version(eval(version_line.split('=')[-1])) 68 | print VERSION 69 | 70 | CLASSIFIERS = [ 71 | 'Development Status :: 4 - Beta', 72 | 'Intended Audience :: Developers', 73 | 'License :: OSI Approved :: BSD License', 74 | 'Operating System :: OS Independent', 75 | 'Programming Language :: Python :: 2.6', 76 | 'Programming Language :: Python :: 2.7', 77 | "Topic :: Software Development :: Libraries :: Python Modules" 78 | ] 79 | 80 | packages, data_files = [], [] 81 | root_dir = os.path.dirname(__file__) 82 | if root_dir != '': 83 | os.chdir(root_dir) 84 | 85 | intellect_dir = 'intellect' 86 | 87 | for dirpath, dirnames, filenames in os.walk(intellect_dir): 88 | for i, dirname in enumerate(dirnames): 89 | if dirname.startswith('.'): del dirnames[i] 90 | if '__init__.py' in filenames: 91 | packages.append('.'.join(fullsplit(dirpath))) 92 | elif filenames: 93 | data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) 94 | 95 | setup(name="Intellect", 96 | version=VERSION, 97 | description=DESCRIPTION, 98 | long_description=long_description, 99 | author='Michael Joseph Walsh', 100 | author_email='github.com{nospam}nemonik.com', 101 | url='http://github.com/nemonik/Intellect', 102 | classifiers=CLASSIFIERS, 103 | keywords='intellect rules engine dsl policy', 104 | license='BSD, 4-clause license', 105 | packages=packages, 106 | package_data={'': ['*.g', '*.tokens', '*.policy']}, 107 | include_package_data=True, 108 | install_requires=['antlr_python_runtime>=3.1.3']) 109 | --------------------------------------------------------------------------------