1 | // do NOT initialize in static field |
2 | static final class Prolog { |
3 | boolean upperCaseVariables = false; // true for SNL, false for NL |
4 | long varCount; |
5 | boolean showStuff; |
6 | new L<Entry> stack; |
7 | Trail sofar = null; |
8 | new L<Clause> program; |
9 | new L<L<Clause>> programByArity; |
10 | long steps; |
11 | L<S> log; |
12 | int maxLogSize = 1000; // lines |
13 | int maxLevel = 10000; // max stack size |
14 | int timeLimit = 20; // 20 seconds |
15 | long startTime; |
16 | boolean allowEval = true; |
17 | int defaultRewriteMax = 100; |
18 | |
19 | // name of theory to memorize stuff to |
20 | S outTheory; |
21 | |
22 | // stats |
23 | int maxLevelSeen; // maximum stack level reached during computation |
24 | long topUnifications, exceptions; |
25 | |
26 | static interface INative { |
27 | public boolean yo(Prolog p, Lisp term); |
28 | } |
29 | |
30 | // CENTRAL method for comparing terms |
31 | // change for modifying case-sensitivity. |
32 | boolean headeq(S a, S b) { |
33 | ret isQuoted (a) ? eq(a, b) : eqic(a, b); |
34 | } |
35 | |
36 | static class Var extends Lisp { |
37 | long id; |
38 | Lisp instance; |
39 | |
40 | *(S name) { |
41 | super(name); |
42 | instance = this; |
43 | } |
44 | |
45 | *(long id) { |
46 | super("___"); |
47 | this.id = id; |
48 | instance = this; |
49 | } |
50 | |
51 | void reset() { instance = this; } |
52 | |
53 | public String toString() { |
54 | if (instance != this) |
55 | ret instance.toString(); |
56 | ret prettyName(); |
57 | } |
58 | |
59 | S prettyName() { |
60 | ret isUserVar() ? getName() : "_" + id; |
61 | } |
62 | |
63 | boolean isUserVar() { |
64 | ret id == 0; |
65 | } |
66 | |
67 | S getName() { |
68 | ret head; |
69 | } |
70 | |
71 | Lisp getValue() { |
72 | Lisp l = instance; |
73 | while (l instanceof Var) { |
74 | Var v = cast l; |
75 | if (v.instance == v) |
76 | ret v; |
77 | l = v.instance; |
78 | } |
79 | ret l; |
80 | } |
81 | } |
82 | |
83 | static class LCollector extends Lisp { |
84 | new L<Lisp> solutions; |
85 | |
86 | *() { super("___"); } |
87 | |
88 | public S toString() { |
89 | ret "<collector>"; |
90 | } |
91 | } |
92 | |
93 | static class Clause { |
94 | Lisp head; |
95 | S loadedFrom; // theory name |
96 | boolean used; |
97 | |
98 | // Either body or nat will be set (or none) |
99 | INative nat; |
100 | Goal body; |
101 | |
102 | *(Lisp *head, INative *nat) {} |
103 | *(Lisp *head, Goal *body) {} |
104 | *(Lisp *head, Goal *body, INative *nat) {} |
105 | *(Lisp *head) {} |
106 | |
107 | Clause copy(Prolog p) { |
108 | ret new Clause(p.copy(head), body == null ? null : body.copy(p), nat); |
109 | } |
110 | |
111 | public String toString() { |
112 | //ret head + " :- " + (body == null ? "true" : body); |
113 | ret nat != null ? head + " :- native" : |
114 | (body == null ? head.toString() : head + " :- " + body); |
115 | } |
116 | |
117 | // fails if clause is a rule |
118 | Lisp getStatement() { |
119 | if (nat != null || body != null) fail("Not a statement: " + this); |
120 | ret head; |
121 | } |
122 | } |
123 | |
124 | class Trail { |
125 | Var tcar; |
126 | Trail tcdr; |
127 | |
128 | *(Var *tcar, Trail *tcdr) {} |
129 | } |
130 | |
131 | Trail Trail_Note() { return sofar; } |
132 | void Trail_Set(Var x, Lisp value) { |
133 | sofar = new Trail(x, sofar); |
134 | x.instance = value; |
135 | /*if (showStuff) |
136 | log(indent(level()) + " Push " + x.prettyName() + " (" + x + ")");*/ |
137 | } |
138 | void Trail_Undo(Trail whereto) { |
139 | for (; sofar != whereto; sofar = sofar.tcdr) { |
140 | /*if (showStuff) |
141 | log(indent(level()) + " Resetting variable " + sofar.tcar.prettyName() + " (" + sofar.tcar + ")");*/ |
142 | sofar.tcar.reset(); |
143 | } |
144 | } |
145 | |
146 | static class TermVarMapping { |
147 | new L<Var> vars; |
148 | |
149 | *(L<Var> *vars) {} |
150 | *(Var... vars) { this.vars.addAll(asList(vars)); } |
151 | |
152 | void showanswer() { |
153 | print("TRUE."); |
154 | for (Var v : vars) |
155 | print(" " + v.prettyName() + " = " + v); |
156 | } |
157 | } |
158 | |
159 | static class Goal { |
160 | Lisp car; |
161 | Goal cdr; |
162 | |
163 | *(Lisp *car, Goal *cdr) {} |
164 | *(Lisp *car) {} |
165 | |
166 | public String toString() { |
167 | ret cdr == null ? car.toString() : car + "; " + cdr; |
168 | } |
169 | |
170 | Goal copy(Prolog p) { |
171 | return new Goal(p.copy(car), |
172 | cdr == null ? null : cdr.copy(p)); |
173 | } |
174 | |
175 | Goal append(Goal l) { |
176 | return new Goal(car, cdr == null ? l : cdr.append(l)); |
177 | } |
178 | |
179 | } // class Goal |
180 | |
181 | boolean unifyOrRollback(Lisp a, Lisp b) { |
182 | Trail t = Trail_Note(); |
183 | if (unify(a, b)) ret true; |
184 | Trail_Undo(t); |
185 | ret false; |
186 | } |
187 | |
188 | boolean unify(Lisp thiz, Lisp t) { |
189 | if (thiz == null) fail("thiz=null"); |
190 | if (t == null) fail("t=null"); |
191 | |
192 | if (thiz instanceof Var) { // TermVar::unify |
193 | Var v = cast thiz; |
194 | if (v.instance != v) |
195 | return unify(v.instance, t); |
196 | Trail_Set(v, t); |
197 | return true; |
198 | } |
199 | |
200 | // TermCons::unify |
201 | return unify2(t, thiz); |
202 | } |
203 | |
204 | boolean unify2(Lisp thiz, Lisp t) { |
205 | if (thiz instanceof Var) |
206 | return unify(thiz, t); |
207 | |
208 | int arity = thiz.size(); |
209 | if (!headeq(thiz.head, t.head) || arity != t.size()) |
210 | return false; |
211 | for (int i = 0; i < arity; i++) |
212 | if (!unify(thiz.get(i), t.get(i))) |
213 | return false; |
214 | return true; |
215 | } |
216 | |
217 | Lisp copy(Lisp thiz) { |
218 | if (thiz instanceof Var) { |
219 | Var v = cast thiz; |
220 | if (v.instance == v) { |
221 | Trail_Set(v, newVar()); |
222 | } |
223 | return v.instance; |
224 | } |
225 | |
226 | // copy2 (copy non-var) |
227 | Lisp l = new Lisp(thiz.head); |
228 | for (Lisp arg : thiz) |
229 | l.add(copy(arg)); |
230 | ret l; |
231 | } |
232 | |
233 | Var newVar() { |
234 | ret new Var(++varCount); |
235 | } |
236 | |
237 | Var newVar(S name) { |
238 | ret new Var(name); |
239 | } |
240 | |
241 | Clause clause(Lisp head, Goal body) { |
242 | ret prologify(new Clause(head, body)); |
243 | } |
244 | |
245 | // primary processor for freshly parsed rules |
246 | |
247 | Clause clause(Lisp rule) { |
248 | L<Lisp> ops = snlSplitOps(rule); |
249 | /*if (showStuff) |
250 | log("clause(Lisp): " + rule + " => " + structure(ops)); */ |
251 | |
252 | // expand rule shortcuts (say/memorize/...) |
253 | |
254 | for (int i = 0; i < l(ops)-1; i++) |
255 | if (ops.get(i).is("memorize *")) |
256 | ops.set(i, lisp("and *", lisp("[]", "memorize", ops.get(i)))); |
257 | |
258 | if (!empty(ops) && last(ops).is("say *")) |
259 | ops.set(l(ops)-1, lisp("then *", lisp("[]", "say", last(ops).get(0)))); |
260 | |
261 | rule = empty(ops) ? rule : snlJoinOps(ops); |
262 | |
263 | if (allowEval) |
264 | rule = new EvalTransform().evalTransformRule(rule); |
265 | |
266 | ops = snlSplitOps(rule); |
267 | if (!empty(ops) && last(ops).is("then *")) { |
268 | Lisp head = last(ops).get(0); |
269 | Goal goal = null; |
270 | |
271 | // TODO: check the actual words (if/and/...) |
272 | for (int i = l(ops)-2; i >= 0; i--) |
273 | goal = new Goal(ops.get(i).get(0), goal); |
274 | |
275 | ret clause(head, goal); |
276 | } else |
277 | ret clause(rule, (Lisp) null); |
278 | } |
279 | |
280 | Clause clause(Lisp head, Lisp body) { |
281 | ret clause(head, body == null ? null : new Goal(body)); |
282 | } |
283 | |
284 | Lisp prologify(Lisp term) { |
285 | ret prologify(term, new HashMap); |
286 | } |
287 | |
288 | Clause prologify(Clause c) { |
289 | new HashMap vars; |
290 | c = new Clause( |
291 | prologify(c.head, vars), |
292 | prologify(c.body, vars), |
293 | c.nat); |
294 | /*if (showStuff) |
295 | log("Clause made: " + structure_seen(c));*/ |
296 | ret c; |
297 | } |
298 | |
299 | Goal prologify(Goal goal, Map<S, Var> vars) { |
300 | if (goal == null) ret null; |
301 | ret new Goal( |
302 | prologify(goal.car, vars), |
303 | prologify(goal.cdr, vars)); |
304 | } |
305 | |
306 | // Note: only for un-prologified |
307 | boolean isVar(Lisp term) { |
308 | ret upperCaseVariables ? snlIsVar(term) : nlIsVar(term); |
309 | } |
310 | |
311 | // for prologified (e.g. in native clauses) |
312 | boolean isLiveVar(Lisp term) { |
313 | ret term instanceof Var; |
314 | } |
315 | |
316 | Lisp prologify(Lisp term, Map<S, Var> vars) { |
317 | if (term == null) ret null; |
318 | if (isVar(term)) { |
319 | Var v = vars.get(term.head); |
320 | if (v == null) |
321 | vars.put(term.head, v = newVar(term.head)); |
322 | ret v; |
323 | } else { |
324 | Lisp l = new Lisp(term.head); |
325 | for (Lisp arg : term) |
326 | l.add(prologify(arg, vars)); |
327 | ret l; |
328 | } |
329 | } |
330 | |
331 | L<Var> findVars(Goal g) { |
332 | new IdentityHashMap<Var, Boolean> map; |
333 | while (g != null) { |
334 | findVars(g.car, map); |
335 | g = g.cdr; |
336 | } |
337 | ret asList(map.keySet()); |
338 | } |
339 | |
340 | L<Var> findVars(Lisp term) { |
341 | new IdentityHashMap<Var, Boolean> map; |
342 | findVars(term, map); |
343 | ret asList(map.keySet()); |
344 | } |
345 | |
346 | void findVars(Lisp term, IdentityHashMap<Var, Boolean> map) { |
347 | if (term instanceof Var) |
348 | map.put((Var) term, Boolean.TRUE); |
349 | else |
350 | for (Lisp arg : term) |
351 | findVars(arg, map); |
352 | } |
353 | |
354 | static Map<S, Var> makeVarMap(L<Var> vars) { |
355 | new HashMap<S, Var> map; |
356 | for (Var v : vars) |
357 | map.put(v.getName(), v); |
358 | ret map; |
359 | } |
360 | |
361 | new L<Clause> emptyProgram; |
362 | |
363 | // Executor stack entry |
364 | class Entry { |
365 | Goal goal; |
366 | L<Clause> program; // full program or filtered by arity |
367 | int programIdx = 0; |
368 | Trail trail; |
369 | boolean trailSet; |
370 | |
371 | *(Goal *goal) { |
372 | Lisp car = resolve1(goal.car); |
373 | if (car instanceof Var) { |
374 | if (showStuff) |
375 | log("Weird: Goal is variable: " + car); |
376 | program = Prolog.this.program; |
377 | } else { |
378 | int arity = car.size(); |
379 | program = arity >= l(programByArity) ? emptyProgram : programByArity.get(arity); |
380 | if (showStuff) |
381 | log(indent(level()) + "Goal arity " + arity + ": " + render(car) + ", have " + n2(program, "fact") + " of this arity"); |
382 | } |
383 | } |
384 | } |
385 | |
386 | void start(S goal) { |
387 | start(nlParse(goal)); |
388 | } |
389 | |
390 | void start(Lisp goal) { |
391 | start(new Goal(prologify(goal))); |
392 | } |
393 | |
394 | S render(Lisp term) { |
395 | ret nlUnparse(resolve(term)); |
396 | } |
397 | |
398 | S render(Goal goal) { |
399 | ret goal == null ? "-" : render(goal.car); |
400 | } |
401 | |
402 | S render(Clause c) { |
403 | ret c == null ? "-" : render(c.head); |
404 | } |
405 | |
406 | // warning: doesn't prologify the goal |
407 | void start(Goal goal) { |
408 | if (showStuff) |
409 | log("Starting on goal: " + render(goal)); |
410 | steps = 0; |
411 | stack = new L; |
412 | Trail_Undo(null); |
413 | stackAdd(new Entry(goal)); |
414 | startTime = sysNow(); |
415 | } |
416 | |
417 | void log(S s) { |
418 | if (log != null) { |
419 | if (l(log) == maxLogSize) { |
420 | log.add("[Log overflow]"); |
421 | showStuff = false; |
422 | } |
423 | else if (l(log) < maxLogSize) |
424 | log.add(s); |
425 | } else if (showStuff) |
426 | print("prolog: " + s); |
427 | } |
428 | |
429 | int level() { |
430 | ret l(stack)-1; |
431 | } |
432 | |
433 | boolean done() { |
434 | boolean result = empty(stack); |
435 | /*if (showStuff && result) |
436 | log("Done with goal!");*/ |
437 | ret result; |
438 | } |
439 | |
440 | boolean gnext(Goal g) { |
441 | Goal gdash = g.cdr; |
442 | if (gdash == null) { |
443 | if (showStuff) |
444 | log("gnext true"); |
445 | ret true; |
446 | } else { |
447 | stackAdd(new Entry(gdash)); |
448 | ret false; |
449 | } |
450 | } |
451 | |
452 | void stackPop() { |
453 | Entry e = popLast(stack); |
454 | if (e.trailSet) |
455 | Trail_Undo(e.trail); |
456 | } |
457 | |
458 | void backToCutPoint(int n) { |
459 | if (showStuff) |
460 | log("back to cut point " + n); |
461 | while (l(stack) >= n) { |
462 | if (showStuff) |
463 | log("cut: dropping " + structure(last(stack).goal)); |
464 | stackPop(); |
465 | } |
466 | } |
467 | |
468 | boolean step() { |
469 | ping(); |
470 | if (done()) fail("done!"); // safety |
471 | if (sysNow() >= startTime+timeLimit*1000) |
472 | fail("time limit reached: " + timeLimit + " s"); |
473 | |
474 | ++steps; |
475 | Entry e = last(stack); |
476 | |
477 | if (e.trailSet) { |
478 | Trail_Undo(e.trail); |
479 | e.trailSet = false; |
480 | } |
481 | |
482 | e.trail = Trail_Note(); |
483 | e.trailSet = true; |
484 | |
485 | // cut operator - suceeds first time |
486 | if (e.goal.car.is("!", 1)) { |
487 | if (showStuff) |
488 | log("cut " + e.programIdx + ". " + structure(e.goal)); |
489 | if (e.programIdx == -1) { |
490 | ++e.programIdx; |
491 | ret gnext(e.goal); |
492 | } else if (e.programIdx == 0) { |
493 | ++e.programIdx; |
494 | // fails 2nd time and cuts |
495 | //e.goal.car.head = "false"; // super-hack :D |
496 | backToCutPoint(parseInt(e.goal.car.get(0).raw())); |
497 | ret false; |
498 | } else { |
499 | stackPop(); |
500 | ret false; |
501 | } |
502 | } |
503 | |
504 | if (e.programIdx >= l(e.program)) { // program loop ends |
505 | removeLast(stack); |
506 | ret false; |
507 | } |
508 | |
509 | // now in program loop - get next clause to try |
510 | |
511 | Clause cc = e.program.get(e.programIdx); |
512 | ++e.programIdx; |
513 | |
514 | // copy the clause; synchronize on it because it may come |
515 | // from a shared Prolog interpreter |
516 | |
517 | Clause c; |
518 | synchronized(cc) { |
519 | c = cc.copy(this); |
520 | Trail_Undo(e.trail); |
521 | } |
522 | |
523 | S text = null; |
524 | if (showStuff) |
525 | //text = " Goal: " + e.goal + ". Got clause: " + c; |
526 | text = "Got clause: " + render(c); |
527 | ++topUnifications; |
528 | if (unify(e.goal.car, c.head)) { |
529 | cc.used = true; // mark clause used |
530 | if (showStuff) { |
531 | log(indent(level()) + text); |
532 | log(indent(level()) + " Clause unifies to: " + render(c)); |
533 | } |
534 | Goal gdash; |
535 | if (c.nat != null) { |
536 | if (showStuff) |
537 | log(indent(level()) + " Clause is native."); |
538 | if (!c.nat.yo(this, c.head)) { |
539 | if (showStuff) |
540 | log(indent(level()) + "Native clause fails"); |
541 | ret false; |
542 | } |
543 | gdash = e.goal.cdr; |
544 | } else |
545 | gdash = c.body == null ? e.goal.cdr : resolveCut(c.body).append(e.goal.cdr); |
546 | if (showStuff) |
547 | log(indent(level()) + " gdash: " + render(gdash)); |
548 | |
549 | if (gdash == null) { |
550 | if (showStuff) |
551 | log("SUCCESS!"); |
552 | ret true; |
553 | } else { |
554 | Entry e2 = new Entry(gdash); |
555 | /*if (showStuff) |
556 | log(indent(level()) + "New goal: " + render(gdash));*/ |
557 | stackAdd(e2); |
558 | ret false; |
559 | } |
560 | } /*else |
561 | if (showStuff) |
562 | log(indent(level()) + "No match for clause.");*/ |
563 | |
564 | ret false; |
565 | } |
566 | |
567 | // resolve cut in clause body |
568 | Goal resolveCut(Goal g) { |
569 | if (g.car.is("!", 0)) |
570 | ret fixCut(g); |
571 | if (g.cdr == null) |
572 | ret g; |
573 | ret new Goal(g.car, resolveCut(g.cdr)); |
574 | } |
575 | |
576 | // note stack length in cut |
577 | Goal fixCut(Goal g) { |
578 | ret new Goal(lisp("!", lstr(stack)), g.cdr); // max. one cut per clause |
579 | } |
580 | |
581 | void stackAdd(Entry e) { |
582 | stack.add(e); |
583 | int n = l(stack); |
584 | if (n > maxLevel) fail("Maximum stack level reached: " + n); |
585 | if (n > maxLevelSeen) maxLevelSeen = n; |
586 | } |
587 | |
588 | void addClause(S text) { |
589 | addClause(nlParse(text)); |
590 | } |
591 | |
592 | void addClauses(L<Lisp> l) { |
593 | for (Lisp clause : unnull(l)) |
594 | addClause(clause); |
595 | } |
596 | |
597 | // synonym of addClause |
598 | void addStatement(S text) { |
599 | addClause(text); |
600 | } |
601 | |
602 | void addClause(Lisp c) { |
603 | addClause(clause(c)); |
604 | } |
605 | |
606 | void addClause(Lisp c, S name) { |
607 | addClause(clause(c), name); |
608 | } |
609 | |
610 | void addClause(Clause c) { |
611 | addClause(c, null); |
612 | } |
613 | |
614 | void addClause(Clause c, S name) { |
615 | c.loadedFrom = name; |
616 | program.add(c); |
617 | if (c.head instanceof Var) { |
618 | if (showStuff) |
619 | log("WARNING: Clause is variable, will not be executed right now: " + c); |
620 | ret; |
621 | } |
622 | int arity = c.head.size(); |
623 | growArityList(arity); |
624 | programByArity.get(arity).add(c); |
625 | } |
626 | |
627 | void growArityList(int arity) { |
628 | while (arity >= l(programByArity)) |
629 | programByArity.add(new L); |
630 | } |
631 | |
632 | boolean canSolve(Lisp goal) { |
633 | ret canSolve(new Goal(prologify(goal))); |
634 | } |
635 | |
636 | boolean canSolve(S goal) { |
637 | ret canSolve(nlParse(goal)); |
638 | } |
639 | |
640 | boolean canSolve(Goal goal) { |
641 | start(goal); |
642 | while (!done()) |
643 | if (step()) |
644 | ret true; |
645 | ret false; |
646 | } |
647 | |
648 | S solveAsText(S goal, S var) { |
649 | Map<S, Lisp> solution = solve(goal); |
650 | ret solution == null ? null : nlUnparse(solution.get(addPrefix("$", var))); |
651 | } |
652 | |
653 | // return variable map or null if unsolved |
654 | Map<S, Lisp> solve(Lisp goal) { |
655 | start(goal); |
656 | |
657 | ret nextSolution(); |
658 | } |
659 | |
660 | Map<S, Lisp> solve(S text) { |
661 | ret solve(nlParse(text)); |
662 | } |
663 | |
664 | Map<S, Lisp> getUserVarMap() { |
665 | Goal g = stack.get(0).goal; |
666 | if (showStuff) |
667 | log("UserVarMap goal: " + g); |
668 | new HashMap<S, Lisp> map; |
669 | for (Var v : findVars(g)) |
670 | if (v.isUserVar()) { |
671 | Lisp term = resolve(v); |
672 | boolean ok = !(term instanceof Var) || (term != v && ((Var) term).isUserVar()); |
673 | if (showStuff) |
674 | log("UserVarMap var " + v + " ok: " + ok); |
675 | if (ok) |
676 | map.put(v.getName(), term); |
677 | } |
678 | ret map; |
679 | } |
680 | |
681 | Map<S, Lisp> nextSolution() { |
682 | if (showStuff) |
683 | log("nextSolution"); |
684 | int n = 0; |
685 | while (!done()) { |
686 | ++n; |
687 | if (step()) { |
688 | if (showStuff) |
689 | log(" solution found in step " + n); |
690 | ret getUserVarMap(); |
691 | } |
692 | } |
693 | if (showStuff) |
694 | log("\nNo solution"); |
695 | ret null; |
696 | } |
697 | |
698 | void code(S text) { |
699 | addTheory(text, "ad hoc"); |
700 | } |
701 | |
702 | void addTheory(S text, S name) { |
703 | for (Clause c : parseProgram(text)) |
704 | addClause(c, name); |
705 | } |
706 | |
707 | void addTheory(S text, Lisp tree) { |
708 | addTheory(text, tree, null); |
709 | } |
710 | |
711 | void addTheory(S text, Lisp tree, S name) { |
712 | for (Clause c : parseProgram(text, tree)) |
713 | addClause(c, name); |
714 | } |
715 | |
716 | L<Clause> parseProgram(S text) { |
717 | ret parseProgram(text, nlParse(text)); |
718 | } |
719 | |
720 | L<Clause> parseProgram(S text, Lisp tree) { |
721 | new L<Clause> l; |
722 | if (nlIsMultipleStatements(text)) |
723 | for (Lisp part : tree) |
724 | l.add(clause(part)); |
725 | else |
726 | l.add(clause(tree)); |
727 | ret l; |
728 | } |
729 | |
730 | L<Lisp> parseStatements(S text) { |
731 | ret map(func(Clause c) { c.getStatement() }, parseProgram(text)); |
732 | } |
733 | |
734 | // Resolve top-level only |
735 | Lisp resolve1(Lisp term) { |
736 | if (term instanceof Var) |
737 | ret ((Var) term).getValue(); |
738 | ret term; |
739 | } |
740 | |
741 | // resolve all variables |
742 | Lisp resolve(Lisp term) { |
743 | term = resolve1(term); |
744 | |
745 | // smart recurse |
746 | for (int i = 0; i < term.size(); i++) { |
747 | Lisp l = term.get(i); |
748 | Lisp r = resolve(l); |
749 | if (l != r) { |
750 | Lisp x = new Lisp(term.head); |
751 | for (int j = 0; j < i; j++) |
752 | x.add(term.get(j)); |
753 | x.add(r); |
754 | for (i++; i < term.size(); i++) |
755 | x.add(resolve(term.get(i))); |
756 | ret x; |
757 | } |
758 | } |
759 | |
760 | ret term; |
761 | } |
762 | |
763 | // looks for a bodyless rule in the program that matches the term |
764 | // todo: eqic |
765 | boolean containsStatement(Lisp term) { |
766 | for (Clause c : program) |
767 | if (c.body == null && c.nat == null && eq(c.head, term)) |
768 | ret true; |
769 | ret false; |
770 | } |
771 | |
772 | // closed == contains no variables |
773 | boolean isClosedTerm(Lisp term) { |
774 | if (term instanceof Var) |
775 | ret false; |
776 | else |
777 | for (Lisp arg : term) |
778 | if (!isClosedTerm(arg)) |
779 | ret false; |
780 | ret true; |
781 | } |
782 | |
783 | void addNative(BaseNative n) { |
784 | addClause(prologify(new Clause(nlParse(n.pat), n)), "nat"); |
785 | } |
786 | |
787 | void addNatives(BaseNative... l) { |
788 | for (BaseNative n : l) |
789 | addNative(n); |
790 | } |
791 | |
792 | void addNatives(L<BaseNative> l) { |
793 | for (BaseNative n : l) |
794 | addNative(n); |
795 | } |
796 | |
797 | L<Lisp> getStackTerms() { |
798 | new L<Lisp> l; |
799 | for (Entry e : stack) |
800 | l.add(e.goal.car); |
801 | ret l; |
802 | } |
803 | |
804 | void logOn() { |
805 | log = new L; |
806 | showStuff = true; |
807 | } |
808 | |
809 | static abstract class BaseNative implements INative { |
810 | S pat; |
811 | Prolog p; |
812 | Map<S, Lisp> m; |
813 | |
814 | *(S *pat) {} |
815 | |
816 | abstract boolean yo(); |
817 | |
818 | public boolean yo(Prolog p, Lisp term) { |
819 | m = new HashMap; |
820 | this.p = p; |
821 | try { |
822 | // Terms are always resolved for native code! |
823 | ret nlMatch(pat, p.resolve(term), m) && yo(); |
824 | } catch (Exception e) { |
825 | ++p.exceptions; |
826 | if (p.showStuff) |
827 | p.log("Exception in native class " + getClass().getName() + ": " + getStackTrace(e)); |
828 | ret false; |
829 | } finally { |
830 | this.p = null; |
831 | } |
832 | } |
833 | |
834 | boolean unify(S var, Lisp term) { |
835 | ret set(var, term); |
836 | } |
837 | |
838 | boolean set(S var, Lisp term) { |
839 | Lisp v = m.get(var); |
840 | if (v == null) |
841 | fail("Variable " + var + " not found"); |
842 | ret p.unify(v, term); |
843 | } |
844 | |
845 | boolean unifyForeign(IdentityHashMap<Lisp, S> varMap, Map<S, Lisp> solution) { |
846 | if (p.showStuff) |
847 | p.log("unifyForeign with: " + structure(solution)); |
848 | for (Lisp v : varMap.keySet()) { |
849 | S name = varMap.get(v); |
850 | Lisp value = solution.get(name); |
851 | if (value == null) continue; |
852 | if (!p.unify(v, escapeVariables(value))) { |
853 | ret false; // rollback? |
854 | } |
855 | } |
856 | ret true; |
857 | } |
858 | |
859 | // fills varMap |
860 | Lisp exportVars(Lisp tree, IdentityHashMap<Lisp, S> varMap) { |
861 | if (tree instanceof Var) { |
862 | S name = varMap.get(tree); |
863 | if (name == null) { |
864 | name = "$_" + (varMap.size()+1); |
865 | varMap.put(tree, name); |
866 | } |
867 | ret lisp(name); |
868 | } |
869 | |
870 | Lisp lisp = new Lisp(tree.head); |
871 | for (Lisp sub : tree) |
872 | lisp.add(exportVars(sub, varMap)); |
873 | ret lisp; |
874 | } |
875 | |
876 | // var = without $ |
877 | Lisp get(S var) { |
878 | ret m.get(var); |
879 | } |
880 | |
881 | S unq(S var) { |
882 | ret m.get(var).unq(); |
883 | } |
884 | } // BaseNative |
885 | |
886 | // Prolog constructor |
887 | *() { |
888 | addClause(lisp("true")); |
889 | addBaseNatives(); |
890 | } |
891 | |
892 | *(Prolog p) { |
893 | copyProgramFrom(p); |
894 | } |
895 | |
896 | static class State { |
897 | Trail trail; |
898 | L<Entry> stack; |
899 | } |
900 | |
901 | State saveState() { |
902 | new State s; |
903 | s.trail = sofar; |
904 | s.stack = stack; |
905 | sofar = null; |
906 | ret s; |
907 | } |
908 | |
909 | void restoreState(State s) { |
910 | sofar = s.trail; |
911 | stack = s.stack; |
912 | } |
913 | |
914 | // auto-rewrite with all clauses in DB |
915 | L<Lisp> rewrite() { |
916 | ret rewriteWith(program, defaultRewriteMax); |
917 | } |
918 | |
919 | L<Lisp> rewrite(int max) { |
920 | ret rewriteWith(program, max); |
921 | } |
922 | |
923 | // Note: rewriteTheory is not searched for facts (only rules) |
924 | L<Lisp> rewriteWith(Prolog rewriteTheory) { |
925 | ret rewriteWith(rewriteTheory.program()); |
926 | } |
927 | |
928 | L<Lisp> rewriteWith(L<Clause> rewriteTheory) { |
929 | ret rewriteWith(rewriteTheory, defaultRewriteMax); |
930 | } |
931 | |
932 | L<Lisp> rewriteWith(L<Clause> rewriteTheory, int max) { |
933 | new L<Lisp> l; |
934 | rewriteWithInto(rewriteTheory, limitedListCollector(l, max)); |
935 | ret l; |
936 | } |
937 | |
938 | void rewriteInto(Collector<Lisp> collector) { |
939 | rewriteWithInto(program, collector); |
940 | } |
941 | |
942 | void rewriteWithInto(L<Clause> rewriteTheory, Collector<Lisp> collector) { |
943 | if (collector.full()) ret; |
944 | for (Prolog.Clause clause : rewriteTheory) { |
945 | if (clause.body == null) continue; // need conditions to rewrite anything |
946 | |
947 | // usual safe clause copying |
948 | |
949 | Trail t = Trail_Note(); |
950 | Clause c; |
951 | synchronized(clause) { |
952 | c = clause.copy(this); |
953 | Trail_Undo(t); |
954 | } |
955 | |
956 | State state = saveState(); |
957 | try { |
958 | start(c.body); |
959 | while (!done()) { |
960 | if (step()) { |
961 | Lisp term = resolve(c.head); |
962 | if (!isClosedTerm(term)) { |
963 | if (showStuff) |
964 | log("Not a closed term, skipping: " + term); |
965 | continue; |
966 | } |
967 | |
968 | if (containsStatement(term)) { |
969 | if (showStuff) |
970 | log("Statement exists, skipping: " + term); |
971 | continue; |
972 | } |
973 | |
974 | if (collector.contains(term)) |
975 | continue; |
976 | |
977 | if (showStuff) |
978 | log("Found new statement: " + term); |
979 | collector.add(term); |
980 | if (collector.full()) { |
981 | log("Collector full"); |
982 | break; |
983 | } |
984 | } |
985 | } |
986 | } finally { |
987 | restoreState(state); |
988 | } |
989 | } |
990 | } |
991 | |
992 | static class NewCollector extends BaseNative { |
993 | *() { super("$x = new collector"); } |
994 | |
995 | boolean yo() { |
996 | ret set("x", new LCollector); |
997 | } |
998 | } |
999 | |
1000 | class Save extends BaseNative { |
1001 | *() { super("saveTo($x, $collector)"); } |
1002 | |
1003 | boolean yo() { |
1004 | LCollector collector = (LCollector) get("collector"); |
1005 | Lisp x = get("x"); |
1006 | collector.solutions.add(resolve(x)); |
1007 | ret true; |
1008 | } |
1009 | } |
1010 | |
1011 | static class Retrieve extends BaseNative { |
1012 | *() { super("$x = retrieve($collector)"); } |
1013 | |
1014 | boolean yo() { |
1015 | print("Retrieve: collector = " + get("collector")); |
1016 | LCollector collector = (LCollector) get("collector"); |
1017 | ret set("x", nlList(collector.solutions)); |
1018 | } |
1019 | } |
1020 | |
1021 | void addBaseNatives() { |
1022 | addNatives(new NewCollector, new Save, new Retrieve); |
1023 | } |
1024 | |
1025 | static Lisp escapeVariables(Lisp tree) { |
1026 | if (tree instanceof Var) { |
1027 | S name = ((Var) tree).prettyName(); |
1028 | ret lisp("[]", "var", name); |
1029 | } |
1030 | |
1031 | Lisp lisp = new Lisp(tree.head); |
1032 | for (Lisp sub : tree) |
1033 | lisp.add(escapeVariables(sub)); |
1034 | ret lisp; |
1035 | } |
1036 | |
1037 | boolean addClauseIfNew(Lisp clause, S name) { |
1038 | if (containsStatement(clause)) ret false; |
1039 | addClause(clause, name); |
1040 | ret true; |
1041 | } |
1042 | |
1043 | L<Clause> getUsedClauses() { |
1044 | new L<Clause> l; |
1045 | for (Clause c : program) |
1046 | if (c.used) |
1047 | l.add(c); |
1048 | ret l; |
1049 | } |
1050 | |
1051 | L<S> getUsedTheories() { |
1052 | new TreeSet<S> theories; |
1053 | for (Clause c : getUsedClauses()) |
1054 | theories.add(or(c.loadedFrom, "?")); |
1055 | ret asList(theories); |
1056 | } |
1057 | |
1058 | void think(Lisp x) { |
1059 | addClauseIfNew(x, "thought"); // TODO: vars and stuff? |
1060 | } |
1061 | |
1062 | L<Lisp> getMemorizedStatements() { |
1063 | new L<Lisp> l; |
1064 | for (Clause c : program) |
1065 | if (c.body == null && c.nat == null && c.head.is("[]", 2) && c.head.get(0).is("memorized")) |
1066 | l.add(c.head); |
1067 | ret l; |
1068 | } |
1069 | |
1070 | S showClause(Clause c) { |
1071 | ret nlUnparse(c.head) + (c.body == null ? "" : " :- ..."); |
1072 | } |
1073 | |
1074 | S showProgram() { |
1075 | new L<S> l; |
1076 | for (Clause c : program) |
1077 | l.add(showClause(c)); |
1078 | ret n(l(program), "statement") + " " + slackSnippet(joinLines(l )); |
1079 | } |
1080 | |
1081 | void copyProgramFrom(Prolog p) { |
1082 | program.addAll(p.program); |
1083 | growArityList(l(p.programByArity)); |
1084 | for (int i = 0; i < l(p.programByArity); i++) |
1085 | programByArity.get(i).addAll(p.programByArity.get(i)); |
1086 | } |
1087 | |
1088 | void addProgram(L<Clause> clauses, S loadedFrom) { |
1089 | for (Clause c : clauses) |
1090 | addClause(c, loadedFrom); |
1091 | } |
1092 | |
1093 | void addClauses(S text) { |
1094 | addTheory(text, fsI(programID())); |
1095 | } |
1096 | |
1097 | L<S> statementsAsText() { |
1098 | ret statementsAsText(0); |
1099 | } |
1100 | |
1101 | L<S> statementsAsText(int startingFromIndex) { |
1102 | new L<S> l; |
1103 | for (int i = startingFromIndex; i < l(program); i++) { |
1104 | Clause c = program.get(i); |
1105 | if (c.nat == null) { |
1106 | if (c.body != null) |
1107 | l.add("[if ? then " + nlUnparse(c.head, false) + "]"); // TODO |
1108 | else |
1109 | l.add(nlUnparse(c.head, false)); |
1110 | } |
1111 | } |
1112 | ret l; |
1113 | } |
1114 | |
1115 | int mark() { |
1116 | ret l(program); |
1117 | } |
1118 | |
1119 | L<Clause> program() { ret program; } |
1120 | |
1121 | L<Lisp> rewriteAndSave() { |
1122 | L<Lisp> l = rewrite(); |
1123 | addClauses(l); |
1124 | ret l; |
1125 | } |
1126 | } // class Prolog |
Began life as a copy of #1002868
download show line numbers debug dex old transpilations
Travelled to 14 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1002884 |
Snippet name: | class Prolog with shared clauses (LIVE) |
Eternal ID of this version: | #1002884/13 |
Text MD5: | 8b1ba8ab8d3ec6f37ccefe88809e7462 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2018-05-02 09:53:58 |
Source code size: | 27122 bytes / 1126 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 691 / 1929 |
Version history: | 12 change(s) |
Referenced in: | [show references] |