1 | !752 |
2 | |
3 | static final class Prolog { |
4 | boolean upperCaseVariables = false; // true for SNL, false for NL |
5 | long varCount; |
6 | boolean showStuff; |
7 | new L<Entry> stack; |
8 | Trail sofar = null; |
9 | new L<Clause> program; |
10 | new L<L<Clause>> programByArity; |
11 | long steps; |
12 | new L<Native> natives; |
13 | L<S> log; |
14 | int maxLogSize = 1000; // lines |
15 | int maxLevel = 10000; // max stack size |
16 | int timeLimit = 20; // 20 seconds |
17 | long startTime; |
18 | boolean allowEval = true; |
19 | |
20 | // stats |
21 | int maxLevelSeen; // maximum stack level reached during computation |
22 | long topUnifications, exceptions; |
23 | |
24 | static interface Native { |
25 | public boolean yo(Prolog p, Lisp term); |
26 | } |
27 | |
28 | boolean headeq(S a, S b) { |
29 | ret isQuoted (a) ? eq(a, b) : eqic(a, b); |
30 | } |
31 | |
32 | static class Var extends Lisp { |
33 | long id; |
34 | Lisp instance; |
35 | |
36 | *(S name) { |
37 | super(name); |
38 | instance = this; |
39 | } |
40 | |
41 | *(long id) { |
42 | super("___"); |
43 | this.id = id; |
44 | instance = this; |
45 | } |
46 | |
47 | void reset() { instance = this; } |
48 | |
49 | public String toString() { |
50 | if (instance != this) |
51 | ret instance.toString(); |
52 | ret isUserVar() ? getName() : "_" + id; |
53 | } |
54 | |
55 | boolean isUserVar() { |
56 | ret id == 0; |
57 | } |
58 | |
59 | S getName() { |
60 | ret head; |
61 | } |
62 | |
63 | Lisp getValue() { |
64 | Lisp l = instance; |
65 | while (l instanceof Var) { |
66 | Var v = cast l; |
67 | if (v.instance == v) |
68 | ret v; |
69 | l = v.instance; |
70 | } |
71 | ret l; |
72 | } |
73 | } |
74 | |
75 | class Clause { |
76 | Lisp head; |
77 | |
78 | // Either body or nat will be set (or none) |
79 | Native nat; |
80 | Goal body; |
81 | |
82 | *(Lisp *head, Native *nat) {} |
83 | *(Lisp *head, Goal *body) {} |
84 | *(Lisp *head, Goal *body, Native *nat) {} |
85 | *(Lisp *head) {} |
86 | |
87 | Clause copy() { |
88 | ret new Clause(Prolog.this.copy(head), body == null ? null : body.copy(), nat); |
89 | } |
90 | |
91 | public String toString() { |
92 | //ret head + " :- " + (body == null ? "true" : body); |
93 | ret nat != null ? head + " :- native" : |
94 | (body == null ? head.toString() : head + " :- " + body); |
95 | } |
96 | } |
97 | |
98 | class Trail { |
99 | Var tcar; |
100 | Trail tcdr; |
101 | |
102 | *(Var *tcar, Trail *tcdr) {} |
103 | } |
104 | |
105 | Trail Trail_Note() { return sofar; } |
106 | void Trail_Push(Var x) { sofar = new Trail(x, sofar); } |
107 | void Trail_Undo(Trail whereto) { |
108 | for (; sofar != whereto; sofar = sofar.tcdr) |
109 | sofar.tcar.reset(); |
110 | } |
111 | |
112 | static class TermVarMapping { |
113 | new L<Var> vars; |
114 | |
115 | *(L<Var> *vars) {} |
116 | *(Var... vars) { this.vars.addAll(asList(vars)); } |
117 | |
118 | void showanswer() { |
119 | print("TRUE."); |
120 | for (Var v : vars) |
121 | print(" " + v.getName() + " = " + v); |
122 | } |
123 | } |
124 | |
125 | class Goal { |
126 | Lisp car; |
127 | Goal cdr; |
128 | |
129 | *(Lisp *car, Goal *cdr) {} |
130 | *(Lisp *car) {} |
131 | |
132 | public String toString() { |
133 | ret cdr == null ? car.toString() : car + "; " + cdr; |
134 | } |
135 | |
136 | Goal copy() { |
137 | return new Goal(Prolog.this.copy(car), |
138 | cdr == null ? null : cdr.copy()); |
139 | } |
140 | |
141 | Goal append(Goal l) { |
142 | return new Goal(car, cdr == null ? l : cdr.append(l)); |
143 | } |
144 | |
145 | } // class Goal |
146 | |
147 | boolean unifyOrRollback(Lisp a, Lisp b) { |
148 | Trail t = Trail_Note(); |
149 | if (unify(a, b)) ret true; |
150 | Trail_Undo(t); |
151 | ret false; |
152 | } |
153 | |
154 | boolean unify(Lisp thiz, Lisp t) { |
155 | if (thiz == null) fail("thiz=null"); |
156 | if (t == null) fail("t=null"); |
157 | |
158 | if (thiz instanceof Var) { // TermVar::unify |
159 | Var v = cast thiz; |
160 | if (v.instance != v) |
161 | return unify(v.instance, t); |
162 | Trail_Push(v); |
163 | v.instance = t; |
164 | return true; |
165 | } |
166 | |
167 | // TermCons::unify |
168 | return unify2(t, thiz); |
169 | } |
170 | |
171 | boolean unify2(Lisp thiz, Lisp t) { |
172 | if (thiz instanceof Var) |
173 | return unify(thiz, t); |
174 | |
175 | int arity = thiz.size(); |
176 | if (!headeq(thiz.head, t.head) || arity != t.size()) |
177 | return false; |
178 | for (int i = 0; i < arity; i++) |
179 | if (!unify(thiz.get(i), t.get(i))) |
180 | return false; |
181 | return true; |
182 | } |
183 | |
184 | Lisp copy(Lisp thiz) { |
185 | if (thiz instanceof Var) { |
186 | Var v = cast thiz; |
187 | if (v.instance == v) { |
188 | Trail_Push(v); |
189 | v.instance = newVar(); |
190 | } |
191 | return v.instance; |
192 | } |
193 | |
194 | // copy2 (copy non-var) |
195 | Lisp l = new Lisp(thiz.head); |
196 | for (Lisp arg : thiz) |
197 | l.add(copy(arg)); |
198 | ret l; |
199 | } |
200 | |
201 | Var newVar() { |
202 | ret new Var(++varCount); |
203 | } |
204 | |
205 | Var newVar(S name) { |
206 | ret new Var(name); |
207 | } |
208 | |
209 | Clause clause(Lisp head, Goal body) { |
210 | ret prologify(new Clause(head, body)); |
211 | } |
212 | |
213 | // primarey processor for freshly parsed rules |
214 | |
215 | Clause clause(Lisp rule) { |
216 | if (allowEval) |
217 | rule = new EvalTransform().evalTransformRule(rule); |
218 | |
219 | L<Lisp> ops = snlSplitOps(rule); |
220 | /*if (showStuff) |
221 | log("clause(Lisp): " + rule + " => " + structure(ops)); */ |
222 | |
223 | if (!empty(ops) && last(ops).is("say *")) |
224 | ops.set(l(ops)-1, lisp("then *", lisp("[]", "say", last(ops).get(0)))); |
225 | |
226 | if (!empty(ops) && last(ops).is("then *")) { |
227 | Lisp head = last(ops).get(0); |
228 | Goal goal = null; |
229 | |
230 | // TODO: check the actual words (if/and/...) |
231 | for (int i = l(ops)-2; i >= 0; i--) |
232 | goal = new Goal(ops.get(i).get(0), goal); |
233 | |
234 | ret clause(head, goal); |
235 | } else |
236 | ret clause(rule, (Lisp) null); |
237 | } |
238 | |
239 | Clause clause(Lisp head, Lisp body) { |
240 | ret clause(head, body == null ? null : new Goal(body)); |
241 | } |
242 | |
243 | Lisp prologify(Lisp term) { |
244 | ret prologify(term, new HashMap); |
245 | } |
246 | |
247 | Clause prologify(Clause c) { |
248 | new HashMap vars; |
249 | c = new Clause( |
250 | prologify(c.head, vars), |
251 | prologify(c.body, vars), |
252 | c.nat); |
253 | /*if (showStuff) |
254 | log("Clause made: " + structure_seen(c));*/ |
255 | ret c; |
256 | } |
257 | |
258 | Goal prologify(Goal goal, Map<S, Var> vars) { |
259 | if (goal == null) ret null; |
260 | ret new Goal( |
261 | prologify(goal.car, vars), |
262 | prologify(goal.cdr, vars)); |
263 | } |
264 | |
265 | boolean isVar(Lisp term) { |
266 | ret upperCaseVariables ? snlIsVar(term) : nlIsVar(term); |
267 | } |
268 | |
269 | Lisp prologify(Lisp term, Map<S, Var> vars) { |
270 | if (term == null) ret null; |
271 | if (isVar(term)) { |
272 | Var v = vars.get(term.head); |
273 | if (v == null) |
274 | vars.put(term.head, v = newVar(term.head)); |
275 | ret v; |
276 | } else { |
277 | Lisp l = new Lisp(term.head); |
278 | for (Lisp arg : term) |
279 | l.add(prologify(arg, vars)); |
280 | ret l; |
281 | } |
282 | } |
283 | |
284 | L<Var> findVars(Goal g) { |
285 | new IdentityHashMap<Var, Boolean> map; |
286 | while (g != null) { |
287 | findVars(g.car, map); |
288 | g = g.cdr; |
289 | } |
290 | ret asList(map.keySet()); |
291 | } |
292 | |
293 | L<Var> findVars(Lisp term) { |
294 | new IdentityHashMap<Var, Boolean> map; |
295 | findVars(term, map); |
296 | ret asList(map.keySet()); |
297 | } |
298 | |
299 | void findVars(Lisp term, IdentityHashMap<Var, Boolean> map) { |
300 | if (term instanceof Var) |
301 | map.put((Var) term, Boolean.TRUE); |
302 | else |
303 | for (Lisp arg : term) |
304 | findVars(arg, map); |
305 | } |
306 | |
307 | static Map<S, Var> makeVarMap(L<Var> vars) { |
308 | new HashMap<S, Var> map; |
309 | for (Var v : vars) |
310 | map.put(v.getName(), v); |
311 | ret map; |
312 | } |
313 | |
314 | new L<Clause> emptyProgram; |
315 | |
316 | // Executor stack entry |
317 | class Entry { |
318 | Goal goal; |
319 | L<Clause> program; // full program or filtered by arity |
320 | int programIdx = -1; // -1 is natives |
321 | Trail trail; |
322 | boolean trailSet; |
323 | |
324 | *(Goal *goal) { |
325 | Lisp car = resolve1(goal.car); |
326 | if (car instanceof Var) { |
327 | if (showStuff) |
328 | log("Weird: Goal is variable: " + car); |
329 | program = Prolog.this.program; |
330 | } else { |
331 | int arity = car.size(); |
332 | if (showStuff) |
333 | log("Goal arity " + arity + ": " + car); |
334 | program = arity >= l(programByArity) ? emptyProgram : programByArity.get(arity); |
335 | } |
336 | } |
337 | } |
338 | |
339 | void start(Lisp goal) { |
340 | start(new Goal(prologify(goal))); |
341 | } |
342 | |
343 | // warning: doesn't prologify the goal |
344 | void start(Goal goal) { |
345 | if (showStuff) |
346 | log("Starting on goal: " + structure_seen(goal)); |
347 | steps = 0; |
348 | stack = new L; |
349 | Trail_Undo(null); |
350 | stackAdd(new Entry(goal)); |
351 | startTime = now(); |
352 | } |
353 | |
354 | void log(S s) { |
355 | if (log != null) { |
356 | if (l(log) == maxLogSize) |
357 | log.add("[Log overflow]"); |
358 | else if (l(log) < maxLogSize) |
359 | log.add(s); |
360 | } else if (showStuff) |
361 | print("prolog: " + s); |
362 | } |
363 | |
364 | int level() { |
365 | ret l(stack)-1; |
366 | } |
367 | |
368 | boolean done() { |
369 | boolean result = empty(stack); |
370 | if (showStuff && result) |
371 | log("Done with goal!"); |
372 | ret result; |
373 | } |
374 | |
375 | boolean gnext(Goal g) { |
376 | Goal gdash = g.cdr; |
377 | if (gdash == null) { |
378 | if (showStuff) |
379 | log("gnext true"); |
380 | ret true; |
381 | } else { |
382 | stackAdd(new Entry(gdash)); |
383 | ret false; |
384 | } |
385 | } |
386 | |
387 | void stackPop() { |
388 | Entry e = popLast(stack); |
389 | if (e.trailSet) |
390 | Trail_Undo(e.trail); |
391 | } |
392 | |
393 | void backToCutPoint(int n) { |
394 | if (showStuff) |
395 | log("back to cut point " + n); |
396 | while (l(stack) >= n) { |
397 | if (showStuff) |
398 | log("cut: dropping " + structure(last(stack).goal)); |
399 | stackPop(); |
400 | } |
401 | } |
402 | |
403 | boolean step() { |
404 | if (done()) fail("done!"); // safety |
405 | if (now() >= startTime+timeLimit*1000) |
406 | fail("time limit reached: " + timeLimit + " s"); |
407 | |
408 | ++steps; |
409 | Entry e = last(stack); |
410 | |
411 | if (e.trailSet) { |
412 | Trail_Undo(e.trail); |
413 | e.trailSet = false; |
414 | } |
415 | |
416 | e.trail = Trail_Note(); |
417 | e.trailSet = true; |
418 | |
419 | // cut operator - suceeds first time |
420 | if (e.goal.car.is("!", 1)) { |
421 | if (showStuff) |
422 | log("cut " + e.programIdx + ". " + structure(e.goal)); |
423 | if (e.programIdx == -1) { |
424 | ++e.programIdx; |
425 | ret gnext(e.goal); |
426 | } else if (e.programIdx == 0) { |
427 | ++e.programIdx; |
428 | // fails 2nd time and cuts |
429 | //e.goal.car.head = "false"; // super-hack :D |
430 | backToCutPoint(parseInt(e.goal.car.get(0).raw())); |
431 | ret false; |
432 | } else { |
433 | stackPop(); |
434 | ret false; |
435 | } |
436 | } |
437 | |
438 | if (e.programIdx >= l(e.program)) { // program loop ends |
439 | removeLast(stack); |
440 | ret false; |
441 | } |
442 | |
443 | if (e.programIdx == -1) { |
444 | ++e.programIdx; |
445 | |
446 | // try native functions |
447 | if (goNative(e.goal.car)) { |
448 | if (showStuff) |
449 | log(indent(level()) + "native!"); |
450 | |
451 | ret gnext(e.goal); |
452 | } |
453 | |
454 | ret false; |
455 | } |
456 | |
457 | // now in program loop |
458 | |
459 | Clause c = e.program.get(e.programIdx).copy(); |
460 | ++e.programIdx; |
461 | Trail_Undo(e.trail); |
462 | S text = null; |
463 | if (showStuff) |
464 | text = " Goal: " + e.goal + ". Got clause: " + c; |
465 | ++topUnifications; |
466 | if (unify(e.goal.car, c.head)) { |
467 | if (showStuff) { |
468 | log(indent(level()) + text); |
469 | log(indent(level()) + " Clause unifies to: " + c); |
470 | } |
471 | Goal gdash; |
472 | if (c.nat != null) { |
473 | if (showStuff) |
474 | log(indent(level()) + " Clause is native."); |
475 | if (!c.nat.yo(this, c.head)) { |
476 | if (showStuff) |
477 | log(indent(level()) + "Native clause fails"); |
478 | ret false; |
479 | } |
480 | gdash = e.goal.cdr; |
481 | } else |
482 | gdash = c.body == null ? e.goal.cdr : resolveCut(c.body).append(e.goal.cdr); |
483 | if (showStuff) |
484 | log(indent(level()) + " gdash: " + gdash); |
485 | |
486 | if (gdash == null) { |
487 | if (showStuff) |
488 | log("SUCCESS!"); |
489 | ret true; |
490 | } else { |
491 | Entry e2 = new Entry(gdash); |
492 | if (showStuff) |
493 | log(indent(level()) + "New goal: " + gdash); |
494 | stackAdd(e2); |
495 | ret false; |
496 | } |
497 | } /*else |
498 | if (showStuff) |
499 | log(indent(level()) + "No match for clause.");*/ |
500 | |
501 | ret false; |
502 | } |
503 | |
504 | // resolve cut in clause body |
505 | Goal resolveCut(Goal g) { |
506 | if (g.car.is("!", 0)) |
507 | ret fixCut(g); |
508 | if (g.cdr == null) |
509 | ret g; |
510 | ret new Goal(g.car, resolveCut(g.cdr)); |
511 | } |
512 | |
513 | // note stack length in cut |
514 | Goal fixCut(Goal g) { |
515 | ret new Goal(lisp("!", lstr(stack)), g.cdr); // max. one cut per clause |
516 | } |
517 | |
518 | void stackAdd(Entry e) { |
519 | stack.add(e); |
520 | int n = l(stack); |
521 | if (n > maxLevel) fail("Maximum stack level reached: " + n); |
522 | if (n > maxLevelSeen) maxLevelSeen = n; |
523 | } |
524 | |
525 | void addClause(S text) { |
526 | addClause(nlParse(text)); |
527 | } |
528 | |
529 | void addClause(Lisp c) { |
530 | addClause(clause(c)); |
531 | } |
532 | |
533 | void addClause(Clause c) { |
534 | program.add(c); |
535 | int arity = c.head.size(); |
536 | while (arity >= l(programByArity)) |
537 | programByArity.add(new L); |
538 | programByArity.get(arity).add(c); |
539 | } |
540 | |
541 | boolean canSolve(Lisp goal) { |
542 | ret canSolve(new Goal(prologify(goal))); |
543 | } |
544 | |
545 | boolean canSolve(Goal goal) { |
546 | start(goal); |
547 | while (!done()) |
548 | if (step()) |
549 | ret true; |
550 | ret false; |
551 | } |
552 | |
553 | // return variable map or null if unsolved |
554 | Map<S, Lisp> solve(Lisp goal) { |
555 | start(new Goal(prologify(goal))); |
556 | ret nextSolution(); |
557 | } |
558 | |
559 | Map<S, Lisp> solve(S text) { |
560 | ret solve(nlParse(text)); |
561 | } |
562 | |
563 | Map<S, Lisp> getUserVarMap() { |
564 | Goal g = stack.get(0).goal; |
565 | new HashMap<S, Lisp> map; |
566 | for (Var v : findVars(g)) |
567 | if (v.isUserVar()) |
568 | map.put(v.getName(), resolve(v)); |
569 | ret map; |
570 | } |
571 | |
572 | Map<S, Lisp> nextSolution() { |
573 | if (showStuff) |
574 | log("nextSolution"); |
575 | int n = 0; |
576 | while (!done()) { |
577 | ++n; |
578 | if (step()) { |
579 | if (showStuff) |
580 | log(" solution found in step " + n); |
581 | ret getUserVarMap(); |
582 | } |
583 | } |
584 | if (showStuff) |
585 | log("No solution"); |
586 | ret null; |
587 | } |
588 | |
589 | void addTheory(S text) { |
590 | for (Clause c : parseProgram(text)) |
591 | addClause(c); |
592 | } |
593 | |
594 | L<Clause> parseProgram(S text) { |
595 | new L<Clause> l; |
596 | Lisp tree = nlParse(text); |
597 | if (nlIsMultipleStatements(text)) |
598 | for (Lisp part : tree) |
599 | l.add(clause(part)); |
600 | else |
601 | l.add(clause(tree)); |
602 | ret l; |
603 | } |
604 | |
605 | boolean goNative(Lisp term) { |
606 | term = resolve(term); |
607 | |
608 | for (Native n : natives) { |
609 | Trail t = Trail_Note(); |
610 | boolean result; |
611 | try { |
612 | result = n.yo(this, term); |
613 | } catch (Exception e) { |
614 | Trail_Undo(t); |
615 | continue; |
616 | } |
617 | if (!result) { |
618 | Trail_Undo(t); |
619 | continue; |
620 | } |
621 | ret true; |
622 | } |
623 | |
624 | if (term.is("nativeTest", 0)) |
625 | ret true; |
626 | |
627 | if (term.is("isQuoted", 1)) { |
628 | Lisp x = term.get(0); |
629 | ret !(x instanceof Var) && x.isLeaf() && isQuoted(x.head); |
630 | } |
631 | |
632 | ret false; |
633 | } |
634 | |
635 | // Resolve top-level only |
636 | Lisp resolve1(Lisp term) { |
637 | if (term instanceof Var) |
638 | ret ((Var) term).getValue(); |
639 | ret term; |
640 | } |
641 | |
642 | // resolve all variables |
643 | Lisp resolve(Lisp term) { |
644 | term = resolve1(term); |
645 | |
646 | // smart recurse |
647 | for (int i = 0; i < term.size(); i++) { |
648 | Lisp l = term.get(i); |
649 | Lisp r = resolve(l); |
650 | if (l != r) { |
651 | Lisp x = new Lisp(term.head); |
652 | for (int j = 0; j < i; j++) |
653 | x.add(term.get(j)); |
654 | x.add(r); |
655 | for (i++; i < term.size(); i++) |
656 | x.add(resolve(term.get(i))); |
657 | ret x; |
658 | } |
659 | } |
660 | |
661 | ret term; |
662 | } |
663 | |
664 | // looks for a bodyless rule in the program that matches the term |
665 | // todo: eqic |
666 | boolean containsStatement(Lisp term) { |
667 | for (Clause c : program) |
668 | if (c.body == null && c.nat == null && eq(c.head, term)) |
669 | ret true; |
670 | ret false; |
671 | } |
672 | |
673 | // closed == contains no variables |
674 | boolean isClosedTerm(Lisp term) { |
675 | if (term instanceof Var) |
676 | ret false; |
677 | else |
678 | for (Lisp arg : term) |
679 | if (!isClosedTerm(arg)) |
680 | ret false; |
681 | ret true; |
682 | } |
683 | |
684 | void addNative(Native n) { |
685 | if (n instanceof BaseNative) |
686 | addNative(((BaseNative) n).pat, n); |
687 | else |
688 | natives.add(n); |
689 | } |
690 | |
691 | void addNatives(Native... l) { |
692 | for (Native n : l) |
693 | addNative(n); |
694 | } |
695 | |
696 | void addNatives(L<Native> l) { |
697 | for (Native n : l) |
698 | addNative(n); |
699 | } |
700 | |
701 | void addNative(S text, Native n) { |
702 | addClause(prologify(new Clause(nlParse(text), n))); |
703 | } |
704 | |
705 | L<Lisp> getStackTerms() { |
706 | new L<Lisp> l; |
707 | for (Entry e : stack) |
708 | l.add(e.goal.car); |
709 | ret l; |
710 | } |
711 | |
712 | void logOn() { |
713 | log = new L; |
714 | showStuff = true; |
715 | } |
716 | |
717 | static abstract class BaseNative implements Native { |
718 | S pat; |
719 | Prolog p; |
720 | Map<S, Lisp> m; |
721 | |
722 | *(S *pat) {} |
723 | |
724 | abstract boolean yo(); |
725 | |
726 | public boolean yo(Prolog p, Lisp term) { |
727 | m = new HashMap; |
728 | this.p = p; |
729 | try { |
730 | ret nlMatch(pat, p.resolve(term), m) && yo(); |
731 | } catch (Exception e) { |
732 | ++p.exceptions; |
733 | if (p.showStuff) |
734 | p.log("Exception in native: " + e); |
735 | ret false; |
736 | } finally { |
737 | this.p = null; |
738 | } |
739 | } |
740 | |
741 | boolean unify(S var, Lisp term) { |
742 | Lisp v = m.get(var); |
743 | if (v == null) |
744 | fail("Variable " + var + " not found"); |
745 | ret p.unify(v, term); |
746 | } |
747 | } // BaseNative |
748 | |
749 | // Prolog constructor |
750 | *() { |
751 | addClause(lisp("true")); |
752 | } |
753 | |
754 | L<Lisp> rewriteWith(L<Clause> rewriteTheory) { |
755 | new L<Lisp> result; |
756 | |
757 | for (Prolog.Clause clause : rewriteTheory) { |
758 | if (clause.body == null) continue; // need conditions to rewrite anything |
759 | |
760 | // todo: trail |
761 | Prolog.Clause c = clause.copy(); |
762 | start(c.body); |
763 | while (!done()) { |
764 | if (step()) { |
765 | Lisp term = resolve(c.head); |
766 | if (!isClosedTerm(term)) { |
767 | print("Not a closed term, skipping: " + term); |
768 | continue; |
769 | } |
770 | |
771 | if (containsStatement(term)) { |
772 | print("Statement exists, skipping: " + term); |
773 | continue; |
774 | } |
775 | |
776 | if (result.contains(term)) |
777 | continue; |
778 | |
779 | print("Found new statement: " + term); |
780 | result.add(term); |
781 | } |
782 | } |
783 | } |
784 | |
785 | ret result; |
786 | } |
787 | } // class Prolog |
Began life as a copy of #1002857
download show line numbers debug dex old transpilations
Travelled to 13 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1002862 |
Snippet name: | class Prolog with rewriting and eval (old) |
Eternal ID of this version: | #1002862/1 |
Text MD5: | bd68ccfd36dae3f9add95ea97749bc78 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2016-03-10 00:40:12 |
Source code size: | 18351 bytes / 787 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 606 / 558 |
Referenced in: | [show references] |