├── README ├── example.html ├── interpreter.js ├── logic_programming.js └── streams.js /README: -------------------------------------------------------------------------------- 1 | This library is a JavaScript adaptation of the logic programming system 2 | from the book "Structure and Interpretation of Computer Programs", 3 | Section 4.4: http://mitpress.mit.edu/sicp/full-text/book/book.html 4 | 5 | A simple command line interpreter is provided. It requires the Rhino 6 | JavaScript engine and can be run in the command line with: 7 | 8 | jrunscript interpreter.js 9 | 10 | There is also an entirely browser based version: 11 | 12 | example.html 13 | 14 | 15 | Programs written for the logic programming system are composed of a sequence 16 | of elements called terms. There are 3 different kinds of terms: 17 | 18 | 1) Atoms: these are general purpose names with no inherent meaning. Atoms 19 | are strings that start with lowercase letters and can contain lowercase 20 | letters, uppercase letters and underscores. Example atoms include: 21 | natural, fooBar, foo_bar. 22 | 23 | 2) Variables: resemble variables in logic in that they are placeholders for 24 | arbitrary terms. They are denoted by strings that start with an uppercase 25 | letter and can contain lowercase letters, uppercase letters and 26 | underscores. Example Variables include: X, Y, Foo, Foo_bar. 27 | 28 | 3) Compound Terms: are composed from an atom called a functor and a number of 29 | arguments, which are again terms. Compound terms are written as a functor 30 | followed by a comma-separated list of argument terms, which is contained 31 | in parentheses. 32 | 33 | Top level compound terms can be of 1 of 3 types determined by the value of 34 | their functor: 35 | 36 | 1) fact - a compound term of arity 1, where the first argument is a term 37 | to be added to the system's database. Example facts include: 38 | fact(likes(fred, wilma)), fact(plus(two, two, four)). 39 | 40 | 2) rule - a compound term of arity 2, where the first argument is the head 41 | of the rule and the second argument is the body of the rule. Example 42 | rules include: rule(natural(succ(X)), natural(X)), 43 | rule(likes(X, Y), and(likes(X, Z), likes(Z, Y)). 44 | 45 | 3) query - a compound term of arity 1, where the first argument is a goal 46 | term that the system must try to resolve, by finding appropriate values 47 | for the variables. Example queries include: 48 | query(likes(fred, X)), query(natural(succ(succ(zero)))). 49 | 50 | The system supports 3 special forms for logic operations which are represented 51 | by compound terms with functors: 52 | 53 | 1) and - a compound term of arity n, which dictates that all the arguments 54 | have to simultaneously resolve for the whole term to resolve successfully. 55 | 56 | 2) or - a compound term of arity n, which dictates that at least one of the 57 | arguments must successfully resolve for the whole term to resolve 58 | successfully. 59 | 60 | 3) not - a compound term of arity 1, which dictates that at the term resolves 61 | successfully if the argument can not resolve. 62 | 63 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 |

JavaScript logic programming

15 | 16 | 17 | 46 | 118 | 119 |
18 |
19 | 42 | 43 |
44 | 45 |
47 |

48 | Programs written for the logic programming system are 49 | composed of a sequence of elements called terms. There 50 | are 3 different kinds of terms: 51 |

52 |
    53 |
  1. 54 | Atoms: these are general purpose names with no 55 | inherent meaning. Atoms are strings that start with lowercase 56 | letters and can contain lowercase letters, uppercase letters and 57 | underscores. Example atoms include: natural, fooBar, foo_bar. 58 |
  2. 59 |
  3. 60 | Variables: resemble variables in logic in that 61 | they are placeholders for arbitrary terms. They are denoted by 62 | strings that start with an uppercase letter and can contain 63 | lowercase letters, uppercase letters and underscores. 64 | Example Variables include: X, Y, Foo, Foo_bar. 65 |
  4. 66 |
  5. 67 | Compound Terms: are composed from an atom called a 68 | functor and a number of arguments, which are again terms. Compound 69 | terms are written as a functor followed by a comma-separated list 70 | of argument terms, which is contained in parentheses. 71 |
  6. 72 |
73 |

74 | Top level compound terms can be of 1 of 3 types determined by the 75 | value of their functor: 76 |

77 |
    78 |
  1. 79 | fact - a compound term of arity 1, where the first 80 | argument is a term to be added to the system's database. Example 81 | facts include: fact(likes(fred, wilma)), fact(plus(two, two, four)). 82 |
  2. 83 |
  3. 84 | rule - a compound term of arity 2, where the first 85 | argument is the head of the rule and the second argument is the 86 | body of the rule. Example rules include: 87 | rule(natural(succ(X)), natural(X)), 88 | rule(likes(X, Y), and(likes(X, Z), likes(Z, Y)). 89 |
  4. 90 |
  5. 91 | query - a compound term of arity 1, where the first 92 | argument is a goal term that the system must try to resolve, by 93 | finding appropriate values for the variables. Example queries 94 | include: query(likes(fred, X)), query(natural(succ(succ(zero)))). 95 |
  6. 96 |
97 |

98 | The system supports 3 special forms for logic operations which are 99 | represented by compound terms with functors: 100 |

101 |
    102 |
  1. 103 | and - a compound term of arity n, which dictates 104 | that all the arguments have to simultaniously resolve for the 105 | whole term to resolve successfully. 106 |
  2. 107 |
  3. 108 | or - a compound term of arity n, which dictates 109 | that at least one of the arguments must successfully resolve for 110 | the whole term to resolve successfully. 111 |
  4. 112 |
  5. 113 | not - a compound term of arity 1, which dictates 114 | that at the term resolves successfully if the argument can not resolve. 115 |
  6. 116 |
117 |
120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /interpreter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 Ivan Vladimirov Ivanov (ivan.vladimirov.ivanov@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included 12 | * in all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | * OTHER DEALINGS IN THE SOFTWARE. 21 | * 22 | * 23 | * 24 | * This module implements a simple command line interface to the logic 25 | * programming system. 26 | * 27 | * 28 | * Example usage: 29 | * jrunscript interpreter.js 30 | * 31 | * >> fact(natural(zero)) 32 | * >> rule(natural(s(X)), natural(X)) 33 | * >> query(natural(X)) 34 | * X = zero 35 | * X = s(zero) 36 | * X = s(s(zero)) 37 | * ... 38 | * ... 39 | * >> quit 40 | * 41 | * 42 | * @author Ivan Vladimirov Ivanov (ivan.vladimirov.ivanov@gmail.com) 43 | */ 44 | 45 | interpreter = {}; 46 | 47 | interpreter.isFact = function(value) { 48 | if (lP.isArray(value) && value.length > 0 && value[0] === 'fact') { 49 | return true; 50 | } 51 | return false; 52 | }; 53 | 54 | interpreter.isRule = function(value) { 55 | if (lP.isArray(value) && value.length > 0 && value[0] === 'rule') { 56 | return true; 57 | } 58 | return false; 59 | }; 60 | 61 | interpreter.isQuery = function(value) { 62 | if (lP.isArray(value) && value.length > 0 && value[0] === 'query') { 63 | return true; 64 | } 65 | return false; 66 | }; 67 | 68 | interpreter.REPL = function() { 69 | var db; 70 | var scanner; 71 | var pattern; 72 | var line; 73 | 74 | java.lang.System.out.println( 75 | "This is the logic programming system's command line interpreter!"); 76 | java.lang.System.out.println(); 77 | java.lang.System.out.println( 78 | 'You can enter 3 types of commands:'); 79 | java.lang.System.out.println( 80 | ' 1) facts: fact(natural(zero))'); 81 | java.lang.System.out.println( 82 | ' 2) rules: rule(natural(succ(X)), natural(X))'); 83 | java.lang.System.out.println( 84 | ' 3) queries: query(natural(X))'); 85 | java.lang.System.out.println(); 86 | java.lang.System.out.println( 87 | "To quit the interpreter enter the 'quit' command"); 88 | java.lang.System.out.println(); 89 | 90 | db = new lP.Database(); 91 | scanner = new java.util.Scanner(java.lang.System['in']); 92 | 93 | while (true) { 94 | java.lang.System.out.print('>> '); 95 | line = (new String(scanner.nextLine())).toString(); 96 | if (line === 'quit') break; 97 | 98 | pattern = (new lP.Parser()).parseTerms(line)[0]; 99 | if (interpreter.isFact(pattern)) { 100 | db.addAssertion(new lP.Assertion(pattern[1])); 101 | java.lang.System.out.println('Fact added to database.'); 102 | } 103 | else if (interpreter.isRule(pattern)) { 104 | db.addRule(new lP.Rule(pattern[1], pattern[2])); 105 | java.lang.System.out.println('Rule added to database.'); 106 | } 107 | else if (interpreter.isQuery(pattern)) { 108 | // At most 10 results are computed. 109 | lP.qeval( 110 | db, 111 | pattern[1], 112 | streams.singletonStream(lP.EMPTY_FRAME)).take(10).forEach( 113 | function(frame) { 114 | var j; 115 | var vars = lP.extractVariables(pattern[1]); 116 | java.lang.System.out.println('Result:'); 117 | for (j = 0; j < vars.length; j++) { 118 | java.lang.System.out.print(vars[j].substring(1) + ' = '); 119 | java.lang.System.out.println(lP.serializeTerm( 120 | lP.instantiate(vars[j], frame))); 121 | } 122 | }); 123 | } 124 | else { 125 | java.lang.System.out.println('Invalid command!'); 126 | } 127 | } 128 | }; 129 | 130 | load('logic_programming.js'); 131 | load('streams.js'); 132 | interpreter.REPL(); 133 | 134 | -------------------------------------------------------------------------------- /logic_programming.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 Ivan Vladimirov Ivanov (ivan.vladimirov.ivanov@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included 12 | * in all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | * OTHER DEALINGS IN THE SOFTWARE. 21 | * 22 | * 23 | * 24 | * This library is a JavaScript adaptation of the logic programming system 25 | * from the book "Structure and Interpretation of Computer Programs", 26 | * Section 4.4: http://mitpress.mit.edu/sicp/full-text/book/book.html 27 | * 28 | * @author Ivan Vladimirov Ivanov (ivan.vladimirov.ivanov@gmail.com) 29 | */ 30 | 31 | var lP = {}; 32 | 33 | //############################################################################# 34 | // Definitions of primary datastructures: 35 | // * Frame - a linked-list like data structure of name-value pairs. 36 | // * Assertion - models an assertion/fact in the database. 37 | // * Rule - models a rule in the database. 38 | // * Database - a collections of assertions and rules. 39 | //############################################################################# 40 | 41 | lP.Frame = function(name, value, frame) { 42 | this.name = name; 43 | this.value = value; 44 | this.frame = frame; 45 | }; 46 | 47 | lP.Frame.prototype = { 48 | lookup: function(name) { 49 | if (name === this.name) return this.value; 50 | if (this.frame) return this.frame.lookup(name); 51 | return false; 52 | }, 53 | 54 | toString: function() { 55 | var result = ''; 56 | var currentFrame = this; 57 | 58 | while (currentFrame) { 59 | result += (currentFrame.name + ': ' + 60 | lP.serializeTree(currentFrame.value) + '; '); 61 | currentFrame = currentFrame.frame; 62 | } 63 | return result; 64 | } 65 | }; 66 | 67 | lP.EMPTY_FRAME = new lP.Frame('', '', false); 68 | 69 | lP.Assertion = function(assertion) { 70 | this.assertion = assertion; 71 | }; 72 | 73 | lP.Assertion.prototype = { 74 | checkAssertion: function(pattern, frame) { 75 | var matchResult = lP.patternMatch(pattern, this.assertion, frame); 76 | if (matchResult) return streams.singletonStream(matchResult); 77 | return streams.EMPTY_STREAM; 78 | }, 79 | 80 | toString: function() { 81 | return 'assertion: ' + lP.serializeTree(this.assertion); 82 | } 83 | }; 84 | 85 | lP.Rule = function(conclusion, body) { 86 | this.conclusion = conclusion; 87 | this.body = body; 88 | }; 89 | 90 | lP.Rule.idCount = 0; 91 | 92 | lP.Rule.prototype = { 93 | renameVariables: function() { 94 | lP.Rule.idCount++; 95 | return new lP.Rule( 96 | lP.renameVariables(this.conclusion, lP.Rule.idCount), 97 | lP.renameVariables(this.body, lP.Rule.idCount)); 98 | }, 99 | 100 | applyRule: function(db, pattern, frame) { 101 | var cleanRule = this.renameVariables(); 102 | var match = lP.unifyMatch(pattern, cleanRule.conclusion, frame); 103 | if (match) return lP.qeval( 104 | db, cleanRule.body, streams.singletonStream(match)); 105 | return streams.EMPTY_STREAM; 106 | }, 107 | 108 | toString: function() { 109 | return ('rule: ' + lP.serializeTree(this.conclusion) + 110 | ' <- ' + lP.serializeTree(this.body)); 111 | } 112 | }; 113 | 114 | lP.Database = function() { 115 | this.assertions = streams.EMPTY_STREAM; 116 | this.rules = streams.EMPTY_STREAM; 117 | }; 118 | 119 | lP.Database.prototype = { 120 | addAssertion: function(assertion) { 121 | var oldAssertions = this.assertions; 122 | this.assertions = this.assertions.append( 123 | streams.singletonStream(assertion)); 124 | }, 125 | 126 | addRule: function(rule) { 127 | var oldRules = this.rules; 128 | this.rules = this.rules.append(streams.singletonStream(rule)); 129 | }, 130 | 131 | fetchAssertions: function(pattern, frame) { 132 | //TODO: optimize 133 | return this.assertions; 134 | }, 135 | 136 | fetchRules: function(pattern, frame) { 137 | //TODO: optimize 138 | return this.rules; 139 | }, 140 | 141 | findAssertions: function(pattern, frame) { 142 | return this.fetchAssertions(pattern, frame).flatmapDelayed( 143 | function(a) { return a.checkAssertion(pattern, frame); }); 144 | }, 145 | 146 | applyRules: function(pattern, frame) { 147 | var that = this; 148 | return this.fetchRules(pattern, frame).flatmapDelayed( 149 | function(r) { return r.applyRule(that, pattern, frame); }); 150 | }, 151 | 152 | toString: function() { 153 | return ( 154 | 'Assertions: \n' + 155 | this.assertions.reduce( 156 | function(result, a) { return result + a + '\n'; }, 157 | '') + 158 | 'Rules: \n' + 159 | this.rules.reduce( 160 | function(result, r) { return result + r + '\n'; }, 161 | '')); 162 | } 163 | }; 164 | 165 | lP.renameVariables = function(tree, id) { 166 | if (lP.isVariable(tree)) return tree + '-' + id; 167 | if (lP.isArray(tree)) { 168 | return lP.map(function(x) { return lP.renameVariables(x, id); }, tree); 169 | } 170 | return tree; 171 | }; 172 | 173 | //############################################################################# 174 | // Pattern matching and unification 175 | // -------------------------------- 176 | // 177 | // Both pattern matching and unification are applied to heterogeneous 178 | // JavaScript arrays, which contain a mix of strings and other heterogeneous 179 | // arrays. If a string begins with a '?' then it represents a variable, 180 | // otherwise it represents an atom. Example patterns include: 181 | // 182 | // 1) ['foo', '?x'] 183 | // 2) ['foo', ['bar', 'baz']] 184 | // 3) ['?y', ['bar', 'baz']] 185 | // 186 | // The idea of pattern matching and unification is to find appropriate 187 | // bindings for variables so as to equate two patterns. Pattern matching 188 | // is simpler since only one of its input patterns may contain variables. 189 | // Unification, on the other hand, allows both patterns to contain variables. 190 | // 191 | // Pattern matching is implemented by the function 'lP.patternMatch' which 192 | // takes two patterns ('pattern' and 'data'), where only the first one may 193 | // contain variables, and a frame of (variable, pattern) bindings and returns 194 | // a new frame with the extra bindings needed to equate the two patterns or 195 | // 'false' if equating is impossible. For example if we pattern match the 196 | // first two examples with an empty frame we would get the new binding: 197 | // 198 | // '?x' = ['bar', 'baz'] 199 | // 200 | // Unification is implemented by the function 'lP.unifyMatch' which 201 | // takes two patterns ('pattern1' and 'pattern2'), where both may contain 202 | // variables, and a frame of (variable, pattern) bindings and returns a new 203 | // frame with the extra bindings needed to equate the two patterns or 'false' 204 | // if equating is impossible. For example if we unify the first and the last 205 | // example with an empty frame we would get the new bindings: 206 | // 207 | // '?x': ['bar', 'baz'] 208 | // '?y': 'foo' 209 | 210 | //############################################################################# 211 | 212 | lP.extendIfConsistent = function(variable, data, frame) { 213 | var value = frame.lookup(variable); 214 | if (value) return lP.patternMatch(value, data, frame); 215 | return new lP.Frame(variable, data, frame); 216 | }; 217 | 218 | lP.patternMatch = function(pattern, data, frame) { 219 | var i; 220 | 221 | if (frame === false) return false; 222 | if (pattern === data) return frame; 223 | if (lP.isVariable(pattern)) { 224 | return lP.extendIfConsistent(pattern, data, frame); 225 | } 226 | if (lP.isArray(pattern) && lP.isArray(data)) { 227 | if (pattern.length !== data.length) return false; 228 | for (i = 0; i < pattern.length; i++) { 229 | frame = lP.patternMatch(pattern[i], data[i], frame); 230 | } 231 | return frame; 232 | } 233 | return false; 234 | }; 235 | 236 | lP.dependsOn = function(tree, variable, frame) { 237 | var i; 238 | var frameValue; 239 | 240 | if (lP.isVariable(tree)) { 241 | if (tree === variable) return true; 242 | frameValue = frame.lookup(tree); 243 | if (frameValue) return lP.dependsOn(frameValue, variable, frame); 244 | return false; 245 | } 246 | if (lP.isArray(tree)) { 247 | for (i = 0; i < tree.length; i++) { 248 | if (lP.dependsOn(tree[i], variable, frame)) return true; 249 | } 250 | } 251 | return false; 252 | }; 253 | 254 | lP.extendIfPossible = function(variable, value, frame) { 255 | var frameValue1 = frame.lookup(variable); 256 | var frameValue2; 257 | 258 | if (frameValue1) return lP.unifyMatch(frameValue1, value, frame); 259 | if (lP.isVariable(value)) { 260 | frameValue2 = frame.lookup(value); 261 | if (frameValue2) return lP.unifyMatch(frameValue2, variable, frame); 262 | return new lP.Frame(variable, value, frame); 263 | } 264 | if (lP.dependsOn(value, variable, frame)) return false; 265 | return new lP.Frame(variable, value, frame); 266 | }; 267 | 268 | lP.unifyMatch = function(pattern1, pattern2, frame) { 269 | var i; 270 | 271 | if (frame === false) return false; 272 | if (pattern1 === pattern2) return frame; 273 | if (lP.isVariable(pattern1)) { 274 | return lP.extendIfPossible(pattern1, pattern2, frame); 275 | } 276 | if (lP.isVariable(pattern2)) { 277 | return lP.extendIfPossible(pattern2, pattern1, frame); 278 | } 279 | if (lP.isArray(pattern1) && lP.isArray(pattern2)) { 280 | if (pattern1.length !== pattern2.length) return false; 281 | for (i = 0; i < pattern1.length; i++) { 282 | frame = lP.unifyMatch(pattern1[i], pattern2[i], frame); 283 | } 284 | return frame; 285 | } 286 | return false; 287 | }; 288 | 289 | lP.instantiate = function(pattern, frame) { 290 | var value; 291 | 292 | if (lP.isVariable(pattern)) { 293 | value = frame.lookup(pattern); 294 | if (value) return lP.instantiate(value, frame); 295 | return pattern; 296 | } 297 | if (lP.isArray(pattern)) { 298 | return lP.map(function(x) { return lP.instantiate(x, frame); }, pattern); 299 | } 300 | return pattern; 301 | }; 302 | 303 | //############################################################################# 304 | // Evaluation of queries - logic inference 305 | //############################################################################# 306 | 307 | lP.qeval = function(db, pattern, frameStream) { 308 | if (lP.isAnd(pattern)) return lP.andQuery(db, pattern, frameStream); 309 | if (lP.isOr(pattern)) return lP.orQuery(db, pattern, frameStream); 310 | if (lP.isNot(pattern)) return lP.notQuery(db, pattern, frameStream); 311 | return lP.simpleQuery(db, pattern, frameStream); 312 | }; 313 | 314 | lP.simpleQuery = function(db, pattern, frameStream) { 315 | return frameStream.flatmapDelayed( 316 | function(frame) { 317 | return db.findAssertions(pattern, frame).appendDelayed( 318 | function() { return db.applyRules(pattern, frame); }); 319 | }); 320 | }; 321 | 322 | lP.andQuery = function(db, pattern, frameStream) { 323 | var i; 324 | var result = frameStream; 325 | for (i = 1; i < pattern.length; i++) { 326 | result = lP.qeval(db, pattern[i], result); 327 | } 328 | return result; 329 | }; 330 | 331 | lP.orQuery = function(db, pattern, frameStream) { 332 | var i; 333 | var result = streams.EMPTY_STREAM; 334 | for (i = 1; i < pattern.length; i++) { 335 | result = result.interleaveDelayed( 336 | function() { return lP.qeval(db, pattern[i], frameStream); }); 337 | } 338 | return result; 339 | }; 340 | 341 | lP.notQuery = function(db, pattern, frameStream) { 342 | return frameStream.flatmapDelayed( 343 | function(frame) { 344 | var result = lP.qeval(db, pattern[1], streams.singletonStream(frame)); 345 | if (result.isEmpty()) return streams.singletonStream(frame); 346 | return streams.EMPTY_STREAM; 347 | }); 348 | }; 349 | 350 | //############################################################################# 351 | // Serialization logic 352 | //############################################################################# 353 | 354 | lP.serializeTree = function(tree) { 355 | if (lP.isArray(tree)) { 356 | return '[' + lP.map(lP.serializeTree, tree).join(', ') + ']'; 357 | } 358 | return '' + tree; 359 | }; 360 | 361 | lP.serializeTerm = function(term) { 362 | if (lP.isArray(term)) { 363 | var i; 364 | var result = ''; 365 | result += term[0] + '('; 366 | for (i = 1; i < term.length; i++) { 367 | result += lP.serializeTerm(term[i]); 368 | if (i + 1 < term.length) result += ','; 369 | } 370 | result += ')'; 371 | return result; 372 | } 373 | return term; 374 | }; 375 | 376 | //############################################################################# 377 | // Parsing logic 378 | //############################################################################# 379 | 380 | lP.Parser = function() { 381 | }; 382 | 383 | lP.Parser.prototype = { 384 | parseTerms: function(rawString) { 385 | var result = []; 386 | var pos = 0; 387 | rawString = rawString.replace(/[^a-zA-Z_(,)]/g, ''); 388 | while (pos < rawString.length) { 389 | var term = this.parseTerm(rawString, pos); 390 | if (term[1] === -1) return -1; 391 | result.push(term[0]); 392 | pos = term[1]; 393 | } 394 | 395 | return result; 396 | }, 397 | 398 | parseTerm: function(rawString, pos) { 399 | var term; 400 | 401 | term = this.parseCompoundTerm(rawString, pos); 402 | if (term[1] !== -1) return term; 403 | 404 | term = this.parseAtom(rawString, pos); 405 | if (term[1] !== -1) return term; 406 | 407 | term = this.parseVariable(rawString, pos); 408 | if (term[1] !== -1) return term; 409 | 410 | return [[], -1]; 411 | }, 412 | 413 | parseCompoundTerm: function(rawString, pos) { 414 | var terms = []; 415 | var functor = this.parseAtom(rawString, pos); 416 | if (functor[1] === -1) return [[], -1]; 417 | 418 | pos = functor[1]; 419 | if (rawString.charAt(pos++) !== '(') return [[], -1]; 420 | 421 | var result = [functor[0]]; 422 | while (true) { 423 | var term = this.parseTerm(rawString, pos); 424 | if (term[1] === -1) return [[], -1]; 425 | pos = term[1]; 426 | if (rawString.charAt(pos) === ')') { 427 | pos++; 428 | result.push(term[0]); 429 | break; 430 | } 431 | if (rawString.charAt(pos) === ',') { 432 | pos++; 433 | result.push(term[0]); 434 | continue; 435 | } 436 | return [[], -1]; 437 | } 438 | 439 | return [result, pos]; 440 | }, 441 | 442 | parseAtom: function(rawString, pos) { 443 | var name = ''; 444 | while (pos < rawString.length) { 445 | var ch = rawString.charCodeAt(pos++); 446 | if (ch >= 65 && ch <= 90) { // 'A' - 'Z' 447 | if (0 === name.length) return [[], -1]; 448 | name += String.fromCharCode(ch); 449 | } 450 | else if (ch >= 97 && ch <= 122) { // 'a' - 'z' 451 | name += String.fromCharCode(ch); 452 | } 453 | else if (ch === 95) { // '_' 454 | if (0 === name.length) return [[], -1]; 455 | name += String.fromCharCode(ch); 456 | } 457 | else { 458 | pos--; 459 | break; 460 | } 461 | } 462 | 463 | return (name.length > 0) ? [name, pos] : [[], -1]; 464 | }, 465 | 466 | parseVariable: function(rawString, pos) { 467 | var name = ''; 468 | while (pos < rawString.length) { 469 | var ch = rawString.charCodeAt(pos++); 470 | if (ch >= 65 && ch <= 90) { // 'A' - 'Z' 471 | name += String.fromCharCode(ch); 472 | } 473 | else if (ch >= 97 && ch <= 122) { // 'a' - 'z' 474 | if (0 === name.length) return [[], -1]; 475 | name += String.fromCharCode(ch); 476 | } 477 | else if (ch === 95) { // '_' 478 | if (0 === name.length) return [[], -1]; 479 | name += String.fromCharCode(ch); 480 | } 481 | else { 482 | pos--; 483 | break; 484 | } 485 | } 486 | 487 | return (name.length > 0) ? ['?' + name, pos] : [[], -1]; 488 | } 489 | }; 490 | 491 | //############################################################################# 492 | // Utility functions 493 | //############################################################################# 494 | 495 | lP.isArray = function(value) { 496 | if (Object.prototype.toString.call(value) === '[object Array]') return true; 497 | return false; 498 | }; 499 | 500 | lP.isString = function(value) { 501 | if (typeof value === 'string') return true; 502 | return false; 503 | }; 504 | 505 | lP.isVariable = function(value) { 506 | if (lP.isString(value) && value.indexOf('?') === 0) return true; 507 | return false; 508 | }; 509 | 510 | lP.isAnd = function(value) { 511 | if (lP.isArray(value) && value.length > 0 && value[0] === 'and') return true; 512 | return false; 513 | }; 514 | 515 | lP.isOr = function(value) { 516 | if (lP.isArray(value) && value.length > 0 && value[0] === 'or') return true; 517 | return false; 518 | }; 519 | 520 | lP.isNot = function(value) { 521 | if (lP.isArray(value) && value.length > 0 && value[0] === 'not') return true; 522 | return false; 523 | }; 524 | 525 | lP.map = function(func, arr) { 526 | var i; 527 | var result = []; 528 | for (i = 0; i < arr.length; i++) { 529 | result.push(func(arr[i])); 530 | } 531 | return result; 532 | }; 533 | 534 | lP.uniqueValues = function(arr) { 535 | var i; 536 | var result = new Array(); 537 | arr.sort(); 538 | for (i = 0; i < arr.length; i++) { 539 | if (i == 0 || arr[i] != arr[i - 1]) { 540 | result.push(arr[i]); 541 | } 542 | } 543 | 544 | return result; 545 | }; 546 | 547 | lP.extractVariables = function(tree) { 548 | var i; 549 | var vars; 550 | var result = new Array(); 551 | 552 | if (lP.isVariable(tree)) { 553 | result.push(tree); 554 | } 555 | else if (lP.isArray(tree)) { 556 | for (i = 0; i < tree.length; i++) { 557 | vars = lP.extractVariables(tree[i]); 558 | result = result.concat(vars); 559 | } 560 | } 561 | 562 | return lP.uniqueValues(result); 563 | }; 564 | 565 | //############################################################################# 566 | // Simplified interface 567 | //############################################################################# 568 | 569 | lP.solve = function(program, maxSolutions) { 570 | var terms = (new lP.Parser()).parseTerms(program); 571 | if (terms === -1) { 572 | return 'Invalid Program Syntax'; 573 | } 574 | else { 575 | var i; 576 | var db = new lP.Database(); 577 | var result = ''; 578 | for (i = 0; i < terms.length; i++) { 579 | if (terms[i][0] === 'fact') { 580 | db.addAssertion(new lP.Assertion(terms[i][1])); 581 | } 582 | else if (terms[i][0] === 'rule') { 583 | db.addRule(new lP.Rule(terms[i][1], terms[i][2])); 584 | } 585 | else if (terms[i][0] === 'query') { 586 | var resultStream = lP.qeval( 587 | db, 588 | terms[i][1], 589 | streams.singletonStream(lP.EMPTY_FRAME)); 590 | if (result.length > 0) result += '\n############################\n\n'; 591 | if (resultStream.isEmpty()) result += 'No Solution\n'; 592 | else result += 'Has a solution\n'; 593 | resultStream.take(maxSolutions).forEach( 594 | function(frame) { 595 | var j; 596 | var vars = lP.extractVariables(terms[i][1]); 597 | result += 'Variables:\n'; 598 | for (j = 0; j < vars.length; j++) { 599 | result += vars[j].substring(1) + ' = ' + 600 | lP.serializeTerm(lP.instantiate(vars[j], frame)) + '\n'; 601 | } 602 | }); 603 | } 604 | } 605 | return result; 606 | } 607 | }; 608 | 609 | -------------------------------------------------------------------------------- /streams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 Ivan Vladimirov Ivanov (ivan.vladimirov.ivanov@gmail.com) 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included 12 | * in all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20 | * OTHER DEALINGS IN THE SOFTWARE. 21 | * 22 | * 23 | * 24 | * A library for lazy lists - streams. 25 | * 26 | * @author Ivan Vladimirov Ivanov (ivan.vladimirov.ivanov@gmail.com) 27 | */ 28 | 29 | var streams = {}; 30 | 31 | streams.Stream = function(car, delayedCdr) { 32 | this.car = car; 33 | this.delayedCdr = delayedCdr; 34 | this.isCdrComputed = false; 35 | }; 36 | 37 | streams.EMPTY_STREAM = new streams.Stream('empty stream', 'empty stream'); 38 | 39 | streams.Stream.prototype = { 40 | streamCar: function() { 41 | return this.car; 42 | }, 43 | 44 | streamCdr: function() { 45 | if (this.isCdrComputed) return this.cdr; 46 | this.cdr = this.delayedCdr(); 47 | this.isCdrComputed = true; 48 | return this.cdr; 49 | }, 50 | 51 | isEmpty: function() { 52 | return (this.car === 'empty stream' && this.delayedCdr === 'empty stream'); 53 | }, 54 | 55 | forEach: function(func) { 56 | current = this; 57 | while (!current.isEmpty()) { 58 | func(current.streamCar()); 59 | current = current.streamCdr(); 60 | } 61 | }, 62 | 63 | map: function(func) { 64 | var that = this; 65 | if (this.isEmpty()) return streams.EMPTY_STREAM; 66 | return new streams.Stream( 67 | func(this.streamCar()), 68 | function() { return that.streamCdr().map(func); }); 69 | }, 70 | 71 | filter: function(pred) { 72 | var that = this; 73 | if (this.isEmpty()) return streams.EMPTY_STREAM; 74 | if (pred(this.streamCar())) { 75 | return new streams.Stream( 76 | this.streamCar(), 77 | function() { return that.streamCdr().filter(pred); }); 78 | } 79 | return this.streamCdr().filter(pred); 80 | }, 81 | 82 | reduce: function(func, initialValue) { 83 | var result = initialValue; 84 | var current = this; 85 | while (!current.isEmpty()) { 86 | result = func(result, current.streamCar()); 87 | current = current.streamCdr(); 88 | } 89 | return result; 90 | }, 91 | 92 | interleave: function(s) { 93 | var that = this; 94 | if (this.isEmpty()) return s; 95 | return new streams.Stream( 96 | this.streamCar(), 97 | function() { return s.interleave(that.streamCdr()); }); 98 | }, 99 | 100 | merge: function(s) { 101 | var car1; 102 | var car2; 103 | var that = this; 104 | 105 | if (s.isEmpty()) return this; 106 | if (this.isEmpty()) return s; 107 | 108 | car1 = this.streamCar(); 109 | car2 = s.streamCar(); 110 | if (car1 < car2) { 111 | return new streams.Stream( 112 | car1, 113 | function() { return that.streamCdr().merge(s); }); 114 | } 115 | if (car1 > car2) { 116 | return new streams.Stream( 117 | car2, 118 | function() { return that.merge(s.streamCdr()); }); 119 | } 120 | 121 | return new streams.Stream( 122 | car1, 123 | function() { return that.streamCdr().merge(s.streamCdr()); }); 124 | }, 125 | 126 | scale: function(scalar) { 127 | return this.map(function(num) { return scalar * num; }); 128 | }, 129 | 130 | take: function(n) { 131 | var that = this; 132 | if (n === 0) return streams.EMPTY_STREAM; 133 | if (this.isEmpty()) return streams.EMPTY_STREAM; 134 | if (n === 1) return streams.singletonStream(this.streamCar()); 135 | return new streams.Stream( 136 | this.streamCar(), 137 | function() { return that.streamCdr().take(n - 1); }); 138 | }, 139 | 140 | append: function(s) { 141 | var that = this; 142 | if (this.isEmpty()) return s; 143 | return new streams.Stream( 144 | this.streamCar(), 145 | function() { return that.streamCdr().append(s); }); 146 | }, 147 | 148 | appendDelayed: function(s) { 149 | var that = this; 150 | if (this.isEmpty()) return s(); 151 | return new streams.Stream( 152 | this.streamCar(), 153 | function() { return that.streamCdr().appendDelayed(s); }); 154 | }, 155 | 156 | interleaveDelayed: function(s) { 157 | var that = this; 158 | if (this.isEmpty()) return s(); 159 | return new streams.Stream( 160 | this.streamCar(), 161 | function() { 162 | return s().interleaveDelayed(function() { 163 | return that.streamCdr(); 164 | }); 165 | }); 166 | }, 167 | 168 | flattenDelayed: function() { 169 | var that = this; 170 | if (this.isEmpty()) return streams.EMPTY_STREAM; 171 | return this.streamCar().interleaveDelayed( 172 | function() { return that.streamCdr().flattenDelayed(); }); 173 | }, 174 | 175 | flatmapDelayed: function(func) { 176 | return this.map(func).flattenDelayed(); 177 | } 178 | }; 179 | 180 | streams.EMPTY_STREAM = new streams.Stream('empty stream', 'empty stream'); 181 | 182 | streams.singletonStream = function(value) { 183 | return new streams.Stream( 184 | value, function() { return streams.EMPTY_STREAM; }); 185 | }; 186 | 187 | streams.range = function(a, b) { 188 | if (a > b) return streams.EMPTY_STREAM; 189 | return new streams.Stream(a, function() { return streams.range(a + 1, b); }); 190 | }; 191 | 192 | --------------------------------------------------------------------------------