1 ! Copyright (C) 2019-2020 KUSUMOTO Norio.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays assocs classes classes.tuple combinators
4 combinators.short-circuit compiler.units continuations
5 formatting fry io kernel lexer lists locals make math multiline
6 namespaces parser prettyprint prettyprint.backend
7 prettyprint.config prettyprint.custom prettyprint.sections
8 quotations sequences sequences.deep sequences.generalizations
9 sets splitting strings vectors words words.symbol ;
13 SYMBOL: !! ! cut operator in prolog: !
14 SYMBOL: __ ! anonymous variable in prolog: _
15 SYMBOL: ;; ! disjunction, or in prolog: ;
16 SYMBOL: \+ ! negation in prolog: not, \+
21 TUPLE: logic-pred name defs ;
23 : <pred> ( name -- pred )
29 SINGLETON: NORMAL-LOGIC-VAR
30 SINGLETON: ANONYMOUSE-LOGIC-VAR
31 INSTANCE: NORMAL-LOGIC-VAR LOGIC-VAR
32 INSTANCE: ANONYMOUSE-LOGIC-VAR LOGIC-VAR
34 : logic-var? ( obj -- ? )
35 dup symbol? [ get LOGIC-VAR? ] [ drop f ] if ; inline
37 SYMBOLS: *trace?* *trace-depth* ;
39 : define-logic-var ( name -- )
43 [ NORMAL-LOGIC-VAR swap set-global ] tri ;
45 : define-logic-pred ( name -- )
49 [ [ name>> <pred> ] keep set-global ] tri ;
53 : trace ( -- ) t *trace?* set-global ;
55 : notrace ( -- ) f *trace?* set-global ;
57 SYNTAX: LOGIC-VAR: scan-token define-logic-var ;
59 SYNTAX: LOGIC-VARS: ";" [ define-logic-var ] each-token ;
61 SYNTAX: LOGIC-PRED: scan-token define-logic-pred ;
63 SYNTAX: LOGIC-PREDS: ";" [ define-logic-pred ] each-token ;
67 "!%" parse-multiline-string drop ;
71 TUPLE: logic-goal pred args ;
73 : called-args ( args -- args' )
74 [ dup callable? [ call( -- term ) ] when ] map ; inline
76 :: <goal> ( pred args -- goal )
77 pred get args called-args logic-goal boa ; inline
79 : def>goal ( goal-def -- goal ) unclip swap <goal> ;
81 : normalize ( goal-def/defs -- goal-defs )
84 [ ?first dup symbol? [ get logic-pred? ] [ drop f ] if ]
85 } 1|| [ 1array ] when ;
87 TUPLE: logic-env table ;
89 : <env> ( -- env ) logic-env new H{ } clone >>table ; inline
91 :: env-put ( x pair env -- ) pair x env table>> set-at ; inline
93 : env-get ( x env -- pair/f ) table>> at ; inline
95 : env-delete ( x env -- ) table>> delete-at ; inline
97 : env-clear ( env -- ) table>> clear-assoc ; inline
99 : dereference ( term env -- term' env' )
100 [ 2dup env-get [ 2nip first2 t ] [ f ] if* ] loop ; inline
106 { [ over logic-goal? ] [
107 [ [ pred>> ] [ args>> ] bi ] dip at <goal> t ] }
109 '[ tuple-slots [ _ at ] map ]
110 [ class-of slots>tuple ] bi t ] }
111 { [ over sequence? ] [
118 TUPLE: callback-env env trail ;
120 C: <callback-env> callback-env inline
122 M: callback-env at* env>> at* ; inline
124 TUPLE: cut-info cut? ;
126 C: <cut> cut-info inline
128 : cut? ( cut-info -- ? ) cut?>> ; inline
130 : set-info ( ? cut-info -- ) cut?<< ; inline
132 : set-info-if-f ( ? cut-info -- )
133 dup cut?>> [ 2drop ] [ cut?<< ] if ; inline
135 : 2each-until ( seq1 seq2 quot -- all-failed? ) 2 nfind 2drop f = ; inline
139 :: (unify*) ( x! x-env! y! y-env! trail tmp-env -- success? )
140 f :> ret-value! f :> ret?! f :> ret2?!
144 x x-env env-get :> xp
147 x x-env dereference x-env! x!
150 y y-env dereference y-env! y!
151 x y = x-env y-env eq? and [
152 x { y y-env } x-env env-put
154 { x x-env } trail push
161 x y x! y! x-env y-env x-env! y-env!
168 x y [ logic-goal? ] both? [
169 x pred>> y pred>> = [
170 x args>> x! y args>> y!
172 f ret-value! t ret2?!
177 { [ x y [ tuple? ] both? ] [
178 x y [ class-of ] same? [
179 x y [ tuple-slots ] bi@ [| x-item y-item |
180 x-item x-env y-item y-env trail tmp-env unify* not
182 ] [ f ] if ret-value! ] }
183 { [ x y [ sequence? ] both? ] [
184 x y [ class-of ] same? x y [ length ] same? and [
185 x y [| x-item y-item |
186 x-item x-env y-item y-env trail tmp-env unify* not
188 ] [ f ] if ret-value! ] }
195 :: unify* ( x x-env y y-env trail tmp-env -- success? )
196 *trace?* get-global :> trace?
199 *trace-depth* counter depth!
200 depth [ "\t" printf ] times
201 "Unification of " printf x-env x of pprint
202 " and " printf y pprint nl
204 x x-env y y-env trail tmp-env (unify*) :> success?
206 depth [ "\t" printf ] times
207 success? [ "==> Success\n" ] [ "==> Fail\n" ] if "%s\n" printf
208 *trace-depth* get-global 1 - *trace-depth* set-global
218 s-callable: s-callable/iter:
219 s-not-callable: s-not-callable/outer-iter: s-not-callable/inner-iter:
225 { state initial: s-start: }
227 first-goal rest-goals d-head d-body defs trail d-env d-cut
228 sub-resolver1 sub-resolver2 i loop-end
231 :: <resolver> ( body env cut -- resolver )
233 body >>body env >>env cut >>cut ; inline
235 GENERIC: next ( generator -- yield? )
237 M:: resolver-gen next ( resolver -- yield? )
243 resolver body>> empty? [
245 s-end: resolver state<<
247 s-not-empty: resolver state<<
250 resolver body>> unclip
251 [ resolver rest-goals<< ] [ resolver first-goal<< ] bi*
252 resolver first-goal>> !! = [ ! cut
253 s-cut: resolver state<<
255 s-not-cut: resolver state<<
258 resolver [ rest-goals>> ] [ env>> ] [ cut>> ] tri <resolver>
259 resolver sub-resolver1<<
260 s-cut/iter: resolver state<< ] }
262 resolver sub-resolver1>> next [
265 t resolver cut>> set-info
266 s-end: resolver state<<
269 resolver first-goal>> callable? [
270 resolver first-goal>> call( -- goal ) resolver first-goal<<
272 *trace?* get-global [
273 resolver first-goal>>
274 [ pred>> name>> "in: { %s " printf ]
275 [ args>> [ "%u " printf ] each "}\n" printf ] bi
277 <env> resolver d-env<<
278 f <cut> resolver d-cut<<
279 resolver first-goal>> pred>> defs>> dup resolver defs<<
280 length 1 - dup 0 >= [
283 s-defs-loop: resolver state<<
286 s-end: resolver state<<
289 resolver [ i>> ] [ defs>> ] bi nth
290 first2 [ resolver d-head<< ] [ resolver d-body<< ] bi*
291 resolver d-cut>> cut? resolver cut>> cut? or [
292 s-end: resolver state<<
294 V{ } clone resolver trail<<
303 resolver d-body>> callable? [
304 s-callable: resolver state<<
306 s-not-callable: resolver state<<
309 s-unify?-exit: resolver state<<
313 resolver [ d-env>> ] [ trail>> ] bi <callback-env>
314 resolver d-body>> call( cb-env -- ? ) [
315 resolver [ rest-goals>> ] [ env>> ] [ cut>> ] tri <resolver>
316 resolver sub-resolver1<<
317 s-callable/iter: resolver state<<
319 s-unify?-exit: resolver state<<
322 resolver sub-resolver1>> next [
325 s-unify?-exit: resolver state<<
328 resolver [ d-body>> ] [ d-env>> ] [ d-cut>> ] tri <resolver>
329 resolver sub-resolver1<<
330 s-not-callable/outer-iter: resolver state<< ] }
331 { s-not-callable/outer-iter: [
332 resolver sub-resolver1>> next [
333 resolver [ rest-goals>> ] [ env>> ] [ cut>> ] tri <resolver>
334 resolver sub-resolver2<<
335 s-not-callable/inner-iter: resolver state<<
337 s-unify?-exit: resolver state<<
339 { s-not-callable/inner-iter: [
340 resolver sub-resolver2>> next [
343 resolver cut>> cut? resolver d-cut>> set-info-if-f
344 s-not-callable/outer-iter: resolver state<<
347 resolver trail>> [ first2 env-delete ] each
348 resolver d-env>> env-clear
349 s-defs-loop-end: resolver state<< ] }
351 resolver [ i>> ] [ loop-end>> ] bi >= [
352 s-end: resolver state<<
354 resolver [ 1 + ] change-i drop
355 s-defs-loop: resolver state<<
358 t resolver return?<< ] }
360 resolver [ yield?>> ] [ return?>> ] bi or not
364 : split-body ( body -- bodies ) { ;; } split [ >array ] map ;
366 SYMBOL: *anonymouse-var-no*
368 : reset-anonymouse-var-no ( -- ) 0 *anonymouse-var-no* set-global ;
370 : proxy-var-for-'__' ( -- var-symbol )
372 *anonymouse-var-no* counter "ANON-%d_" sprintf
373 "logic.private" create-word dup dup
375 ANONYMOUSE-LOGIC-VAR swap set-global
376 ] with-compilation-unit ;
378 : replace-'__' ( before -- after )
380 { [ dup __ = ] [ drop proxy-var-for-'__' ] }
381 { [ dup sequence? ] [ [ replace-'__' ] map ] }
383 [ tuple-slots [ replace-'__' ] map ]
384 [ class-of slots>tuple ] bi ] }
388 : collect-logic-vars ( seq -- vars-array )
389 [ logic-var? ] deep-filter members ;
393 :: negation-goal ( goal -- negation-goal )
394 "failo_" <pred> :> f-pred
395 f-pred { } clone logic-goal boa :> f-goal
396 V{ { f-goal [ drop f ] } } f-pred defs<<
397 goal pred>> name>> "\\+%s_" sprintf <pred> :> negation-pred
398 negation-pred goal args>> clone logic-goal boa :> negation-goal
400 { negation-goal { goal !! f-goal } } ! \+P_ { P !! { failo_ } } rule
401 { negation-goal { } } ! \+P_ fact
402 } negation-pred defs<<
405 SYMBOLS: at-the-beginning at-the-end ;
407 :: (rule) ( head body pos -- )
408 reset-anonymouse-var-no
409 head replace-'__' def>goal :> head-goal
410 body replace-'__' normalize
411 split-body pos at-the-beginning = [ reverse ] when ! disjunction
413 head-goal swap 2array 1vector
415 pos at-the-end = [ swap ] when append!
422 { [ dup \+ = ] [ drop dummy-item t negation?! ] }
424 def>goal negation? [ negation-goal ] when
426 { [ dup callable? ] [
427 call( -- goal ) negation? [ negation-goal ] when
429 { [ dup [ t = ] [ f = ] bi or ] [
430 :> t/f! negation? [ t/f not t/f! ] when
431 t/f "trueo_" "failo_" ? <pred> :> t/f-pred
432 t/f-pred { } clone logic-goal boa :> t/f-goal
433 V{ { t/f-goal [ drop t/f ] } } t/f-pred defs<<
436 { [ dup !! = ] [ f negation?! ] } ! as '!!'
437 [ drop dummy-item f negation?! ]
439 ] map dummy-item swap remove :> body-goals
440 V{ { head-goal body-goals } }
442 pos at-the-end = [ swap ] when append!
447 : (fact) ( head pos -- ) { } clone swap (rule) ;
451 : rule ( head body -- ) at-the-end (rule) ;
453 : rule* ( head body -- ) at-the-beginning (rule) ;
455 : rules ( defs -- ) [ first2 rule ] each ;
457 : fact ( head -- ) at-the-end (fact) ;
459 : fact* ( head -- ) at-the-beginning (fact) ;
461 : facts ( defs -- ) [ fact ] each ;
463 :: callback ( head quot: ( callback-env -- ? ) -- )
464 head def>goal :> head-goal
466 { head-goal quot } suffix!
469 : callbacks ( defs -- ) [ first2 callback ] each ; inline
471 :: retract ( head-def -- )
472 head-def replace-'__' def>goal :> head-goal
473 head-goal pred>> defs>> :> defs
474 defs [ first <env> head-goal <env> V{ } clone <env> (unify*) ] find [
475 head-goal pred>> [ remove-nth! ] change-defs drop
478 :: retract-all ( head-def -- )
479 head-def replace-'__' def>goal :> head-goal
480 head-goal pred>> defs>> :> defs
482 first <env> head-goal <env> V{ } clone <env> (unify*)
483 ] reject! head-goal pred>> defs<< ;
485 : clear-pred ( pred -- ) get V{ } clone swap defs<< ;
487 :: unify ( cb-env x y -- success? )
489 x env y env cb-env trail>> env (unify*) ;
491 :: is ( quot: ( env -- value ) dist -- goal )
492 quot collect-logic-vars
493 dup dist swap member? [ dist suffix ] unless :> args
494 quot dist "[ %u %s is ]" sprintf <pred> :> is-pred
495 is-pred args logic-goal boa :> is-goal
499 [| env | env dist env quot call( env -- value ) unify ]
504 :: =:= ( quot: ( env -- n m ) -- goal )
505 quot collect-logic-vars :> args
506 quot "[ %u =:= ]" sprintf <pred> :> =:=-pred
507 =:=-pred args logic-goal boa :> =:=-goal
512 env quot call( env -- n m )
513 2dup [ number? ] both? [ = ] [ 2drop f ] if ]
518 :: =\= ( quot: ( env -- n m ) -- goal )
519 quot collect-logic-vars :> args
520 quot "[ %u =\\= ]" sprintf <pred> :> =\=-pred
521 =\=-pred args logic-goal boa :> =\=-goal
526 env quot call( env -- n m )
527 2dup [ number? ] both? [ = not ] [ 2drop f ] if ]
532 :: invoke ( quot: ( env -- ) -- goal )
533 quot collect-logic-vars :> args
534 quot "[ %u invoke ]" sprintf <pred> :> invoke-pred
535 invoke-pred args logic-goal boa :> invoke-goal
537 { invoke-goal [| env | env quot call( env -- ) t ] }
541 :: invoke* ( quot: ( env -- ? ) -- goal )
542 quot collect-logic-vars :> args
543 quot "[ %u invoke* ]" sprintf <pred> :> invoke*-pred
544 invoke*-pred args logic-goal boa :> invoke*-goal
546 { invoke*-goal [| env | env quot call( env -- ? ) ] }
547 } invoke*-pred defs<<
550 :: nquery ( goal-def/defs n/f -- bindings-array/success? )
551 *trace?* get-global :> trace?
554 V{ } clone :> bindings
556 goal-def/defs replace-'__' normalize [ def>goal ] map
558 <resolver> :> resolver
562 resolver env>> table>> keys [ get NORMAL-LOGIC-VAR? ] filter
563 [ dup env at ] H{ } map>assoc
564 trace? get-global [ dup [ "%u: %u\n" printf ] assoc-each ] when
569 n n/f >= [ return ] when
576 [ first keys empty? ]
577 } 1|| [ drop success? ] [ >array ] if ;
579 : query ( goal-def/defs -- bindings-array/success? ) f nquery ;
581 ! nquery has been modified to use generators created by finite
582 ! state machines to reduce stack consumption.
583 ! Since the processing algorithm of the code is difficult
584 ! to understand, the words no longer used are kept as private
585 ! words for verification.
589 : each-until ( seq quot -- ) find 2drop ; inline
591 :: resolve-body ( body env cut quot: ( -- ) -- )
595 body unclip :> ( rest-goals! first-goal! )
596 first-goal !! = [ ! cut
597 rest-goals env cut quot resolve-body
600 first-goal callable? [
601 first-goal call( -- goal ) first-goal!
603 *trace?* get-global [
605 [ pred>> name>> "in: { %s " printf ]
606 [ args>> [ "%u " printf ] each "}\n" printf ] bi
610 first-goal pred>> defs>> [
611 first2 :> ( d-head d-body )
612 first-goal d-head [ args>> length ] same? [
613 d-cut cut? cut cut? or [ t ] [
615 first-goal env d-head d-env trail d-env unify* [
617 d-env trail <callback-env> d-body call( cb-env -- ? ) [
618 rest-goals env cut quot resolve-body
622 rest-goals env cut quot resolve-body
623 cut cut? d-cut set-info-if-f
627 trail [ first2 env-delete ] each
636 :: (resolve) ( goal-def/defs quot: ( env -- ) -- )
637 goal-def/defs replace-'__' normalize [ def>goal ] map :> goals
639 goals env f <cut> [ env quot call( env -- ) ] resolve-body ;
641 : resolve ( goal-def/defs quot: ( env -- ) -- ) (resolve) ;
643 :: nquery/rec ( goal-def/defs n/f -- bindings-array/success? )
644 *trace?* get-global :> trace?
647 V{ } clone :> bindings
649 goal-def/defs normalize [| env |
650 env table>> keys [ get NORMAL-LOGIC-VAR? ] filter
651 [ dup env at ] H{ } map>assoc
652 trace? get-global [ dup [ "%u: %u\n" printf ] assoc-each ] when
657 n n/f >= [ return ] when
663 [ first keys empty? ]
664 } 1|| [ drop success? ] [ >array ] if ;
666 : query/rec ( goal-def/defs -- bindings-array/success? )
671 ! Built-in predicate definitions -----------------------------------------------------
676 (<) (>) (>=) (=<) (==) (\==) (=) (\=)
678 membero appendo lengtho listo ;
680 { trueo } [ drop t ] callback
682 { failo } [ drop f ] callback
685 <PRIVATE LOGIC-VARS: X Y Z ; PRIVATE>
687 { varo X } [ X of logic-var? ] callback
689 { nonvaro X } [ X of logic-var? not ] callback
693 [ X of ] [ Y of ] bi 2dup [ number? ] both? [ < ] [ 2drop f ] if
697 [ X of ] [ Y of ] bi 2dup [ number? ] both? [ > ] [ 2drop f ] if
701 [ X of ] [ Y of ] bi 2dup [ number? ] both? [ >= ] [ 2drop f ] if
705 [ X of ] [ Y of ] bi 2dup [ number? ] both? [ <= ] [ 2drop f ] if
708 { (==) X Y } [ [ X of ] [ Y of ] bi = ] callback
710 { (\==) X Y } [ [ X of ] [ Y of ] bi = not ] callback
712 { (=) X Y } [ dup [ X of ] [ Y of ] bi unify ] callback
715 clone [ clone ] change-env [ clone ] change-trail
716 dup [ X of ] [ Y of ] bi unify not
722 [ dup string? [ printf ] [ pprint ] if ] each
724 dup string? [ printf ] [ pprint ] if
730 [ dup string? [ printf ] [ pprint ] if ] each
732 dup string? [ printf ] [ pprint ] if
736 { nlo } [ drop nl t ] callback
739 <PRIVATE LOGIC-VARS: L L1 L2 L3 Head Tail N N1 ; PRIVATE>
741 { membero X L{ X . Tail } } fact
742 { membero X L{ Head . Tail } } { membero X Tail } rule
744 { appendo L{ } L L } fact
745 { appendo L{ X . L1 } L2 L{ X . L3 } } {
749 { lengtho L{ } 0 } fact
750 { lengtho L{ __ . Tail } N } {
752 [ [ N1 of 1 + ] N is ]
756 { listo L{ __ . __ } } fact