upl 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -23
- data/lib/upl/dict.rb +61 -0
- data/lib/upl/extern.rb +6 -1
- data/lib/upl/inter.rb +42 -4
- data/lib/upl/runtime.rb +60 -73
- data/lib/upl/term.rb +34 -31
- data/lib/upl/term_vector.rb +67 -0
- data/lib/upl/tree.rb +24 -41
- data/lib/upl/variable.rb +3 -2
- data/lib/upl/version.rb +1 -1
- data/lib/upl.rb +45 -4
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc30b39fd062308b8cbae9bcaeca09c2b4921ff888c42ba00d100c2a045b39bf
|
4
|
+
data.tar.gz: 11adee909aee233bae48fe9ea7d1c33b25ba35ef33c1fabe6526e399e7920d81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cc115b7e58995a480463b226df08d68fa54ee9a441d64b47efbc13546f184d3f4ee91fc14881f4f6df5773c2dd6b7f8f5268d86db2a7009bac036afa564b462
|
7
|
+
data.tar.gz: cb405495fee3c55030f520c3be3c6305b8158848aa77c9d597233bf15a76c82b352d675d4277fc75acaec9c87d4e6ed21a977e79290301ec5882f500f86385c8
|
data/README.md
CHANGED
@@ -13,12 +13,6 @@ Also, do prolog-style queries on objects :-DD
|
|
13
13
|
|
14
14
|
### Queries
|
15
15
|
|
16
|
-
To read rules from a prolog file:
|
17
|
-
``` ruby
|
18
|
-
[1] pry(main)> Upl.consult '/home/yours/funky_data.pl'
|
19
|
-
=> true
|
20
|
-
```
|
21
|
-
|
22
16
|
Query a built-in predicate, with a full expression:
|
23
17
|
``` ruby
|
24
18
|
[1] pry(main)> enum = Upl.query 'current_prolog_flag(K,V), member(K,[home,executable,shared_object_extension])'
|
@@ -29,12 +23,18 @@ Query a built-in predicate, with a full expression:
|
|
29
23
|
{:K=>shared_object_extension, :V=>so}]
|
30
24
|
```
|
31
25
|
|
26
|
+
To read rules from a prolog file:
|
27
|
+
``` ruby
|
28
|
+
[1] pry(main)> Upl.consult '/home/yours/funky_data.pl'
|
29
|
+
=> true
|
30
|
+
```
|
31
|
+
|
32
32
|
### Facts
|
33
33
|
Also we want to be able to construct prolog-queryable facts from ruby objects.
|
34
34
|
In prolog:
|
35
35
|
|
36
36
|
``` prolog
|
37
|
-
?-
|
37
|
+
?- assertz(person(john,anderson)).
|
38
38
|
true.
|
39
39
|
|
40
40
|
?- person(A,B).
|
@@ -51,15 +51,15 @@ false.
|
|
51
51
|
And in Upl:
|
52
52
|
|
53
53
|
``` ruby
|
54
|
-
[
|
54
|
+
[2] pry(main)> fact = Upl::Term.functor :person, :john, :anderson
|
55
55
|
=> person/2(john,anderson)
|
56
|
-
[
|
56
|
+
[3] pry(main)> Upl.assertz fact
|
57
57
|
=> true
|
58
|
-
[
|
58
|
+
[4] pry(main)> Array Upl.query 'person(A,B)'
|
59
59
|
=> [{:A=>john, :B=>anderson}]
|
60
|
-
[
|
60
|
+
[5] pry(main)> Upl.retract fact
|
61
61
|
=> true
|
62
|
-
[
|
62
|
+
[6] pry(main)> Array Upl.query 'person(A,B)'
|
63
63
|
=> []
|
64
64
|
```
|
65
65
|
|
@@ -69,15 +69,15 @@ Also, with objects other than symbols. Obviously, this is a rabbit-hole of
|
|
69
69
|
Alician proportions. So, here we GOOOoooo...
|
70
70
|
|
71
71
|
``` ruby
|
72
|
-
[
|
72
|
+
[2] pry(main)> fact = Upl::Term.functor :person, :john, :anderson, (o = Object.new)
|
73
73
|
=> person/3(john,anderson,#<Object:0x0000563346a08e38 @_upl_atom=439429>)
|
74
|
-
[
|
74
|
+
[3] pry(main)> Upl.assertz fact
|
75
75
|
=> true
|
76
|
-
[
|
76
|
+
[4] pry(main)> ha, = Array Upl.query 'person(A,B,C)'
|
77
77
|
=> [{:A=>john,
|
78
78
|
:B=>anderson,
|
79
79
|
:C=>#<Object:0x0000563346a08e38 @_upl_atom=439429>}]
|
80
|
-
[
|
80
|
+
[5] pry(main)> ha[:C].equal? o
|
81
81
|
=> true
|
82
82
|
```
|
83
83
|
|
@@ -88,24 +88,24 @@ And now, the pièce de résistance - using an object as an input term:
|
|
88
88
|
|
89
89
|
``` ruby
|
90
90
|
fact = Upl::Term.functor :person, :james, :madison, (o = Object.new)
|
91
|
-
Upl
|
91
|
+
Upl.assertz fact
|
92
92
|
|
93
93
|
fact2 = Upl::Term.functor :person, :thomas, :paine, (thing2 = Object.new)
|
94
|
-
Upl
|
94
|
+
Upl.assertz fact2
|
95
95
|
|
96
96
|
# Note that both facts are in the result
|
97
|
-
query_term,
|
98
|
-
Array Upl::Runtime.term_vars_query query_term,
|
97
|
+
query_term, query_hash = Upl::Runtime.term_vars 'person(A,B,C)'
|
98
|
+
Array Upl::Runtime.term_vars_query query_term, query_hash
|
99
99
|
=>[{:A=>james, :B=>madison, :C=>#<Object:0x0000563f56e35580 @_upl_atom=439429>},
|
100
100
|
{:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}]
|
101
101
|
|
102
102
|
# Unify C with thing2. This needs a nicer api :-\
|
103
|
-
query_term,
|
104
|
-
Upl::Extern.PL_unify
|
103
|
+
query_term, query_hash = Upl::Runtime.term_vars 'person(A,B,C)'
|
104
|
+
Upl::Extern.PL_unify query_hash[:C].term_t, thing2.to_term_t
|
105
105
|
|
106
106
|
# ... and we get the correct result
|
107
107
|
# Note that the first fact is not in the result.
|
108
|
-
Array Upl::Runtime.term_vars_query query_term,
|
108
|
+
Array Upl::Runtime.term_vars_query query_term, query_hash
|
109
109
|
=> [{:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}]
|
110
110
|
```
|
111
111
|
|
data/lib/upl/dict.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Upl
|
2
|
+
class Dict < Hash
|
3
|
+
def initialize( tag, default_value = nil, &default_blk )
|
4
|
+
@tag = tag
|
5
|
+
super default_value, &default_blk
|
6
|
+
end
|
7
|
+
|
8
|
+
# fetch the tag for the dict
|
9
|
+
def self.dict_tag( dict_term_t )
|
10
|
+
args = TermVector[dict_term_t, nil]
|
11
|
+
|
12
|
+
# TODO need a better api here as well, and other places
|
13
|
+
# eg, need to check that args.size == predicate.arity
|
14
|
+
# otherwise segfaults and other weird stuff ensue
|
15
|
+
rv = Extern::PL_call_predicate \
|
16
|
+
Extern::NULL, # module
|
17
|
+
0, # flags, see PL_open_query
|
18
|
+
(Runtime.predicate 'is_dict', args.size),
|
19
|
+
args.terms
|
20
|
+
|
21
|
+
rv == 1 or raise "can't retrieve dict tag"
|
22
|
+
|
23
|
+
# now retrieve the variable's value
|
24
|
+
args.last.to_ruby
|
25
|
+
end
|
26
|
+
|
27
|
+
# copy dict_term_t into a ruby structure
|
28
|
+
def self.of_term( dict_term_t )
|
29
|
+
# Have to do a little hoop-jumping here. There are no c-level calls to
|
30
|
+
# access dicts, so we have to break them down with prolog predicates. But
|
31
|
+
# we can't process queries that have dicts in their results, otherwise we
|
32
|
+
# have an endless recursion.
|
33
|
+
|
34
|
+
query_term, query_hash = Runtime.term_vars 'get_dict(K,Dict,V)'
|
35
|
+
|
36
|
+
# So set the Dict value to dict_term_t above ...
|
37
|
+
query_term[1] = dict_term_t
|
38
|
+
# ...and remove it from the output variables
|
39
|
+
query_hash.delete :Dict
|
40
|
+
|
41
|
+
# now we have a result set with K,V values
|
42
|
+
en = Upl::Runtime.term_vars_query query_term, query_hash
|
43
|
+
|
44
|
+
# map to a hash-y thing
|
45
|
+
en.each_with_object Dict.new(dict_tag dict_term_t) do |row,values|
|
46
|
+
values[row[:K]] = row[:V]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :tag, :values
|
51
|
+
|
52
|
+
def == rhs
|
53
|
+
[tag,to_h] == [rhs.tag,rhs.to_h]
|
54
|
+
end
|
55
|
+
|
56
|
+
def pretty_print pp
|
57
|
+
tag.pretty_print pp
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/upl/extern.rb
CHANGED
@@ -196,12 +196,17 @@ module Upl
|
|
196
196
|
extern 'int PL_put_atom(term_t t, atom_t a)'
|
197
197
|
extern 'int PL_put_variable(term_t t)'
|
198
198
|
extern 'int PL_put_functor(term_t t, functor_t functor)'
|
199
|
+
extern 'int PL_put_term(term_t t1, term_t t2)' # Make t1 point to the same term as t2.
|
199
200
|
|
200
201
|
extern 'int PL_cons_functor_v(term_t h, functor_t fd, term_t a0)'
|
201
202
|
|
203
|
+
extern 'int PL_unify_arg(int index, term_t t, term_t a)' # set index-th arg of t to a
|
204
|
+
|
202
205
|
extern 'int PL_get_atom_chars(term_t t, char **a)'
|
203
206
|
extern 'int PL_get_string(term_t t, char **s, size_t *len)'
|
204
207
|
extern 'int PL_get_integer(term_t t, int *i)'
|
208
|
+
extern 'int PL_get_int64(term_t t, int64_t *i)'
|
209
|
+
extern 'int PL_get_float(term_t t, double *f)'
|
205
210
|
extern 'int PL_get_chars(term_t t, char **s, unsigned int flags)'
|
206
211
|
extern 'int PL_get_name_arity(term_t t, atom_t *name, int *arity)'
|
207
212
|
extern 'int PL_get_arg(int index, term_t t, term_t a)'
|
@@ -230,7 +235,7 @@ module Upl
|
|
230
235
|
####################
|
231
236
|
# looks like parsing of terms
|
232
237
|
# only >= 7.6.0
|
233
|
-
# get version,
|
238
|
+
# get version, call current_prolog_flag(version_data,swi(M,I,P,E)). Major, mInor, Patch, Extra[]
|
234
239
|
# PL_EXPORT(int) PL_put_term_from_chars(term_t t, int flags, size_t len, const char *s);
|
235
240
|
# extern 'int PL_put_term_from_chars(term_t t, int flags, size_t len, const char *s)'
|
236
241
|
|
data/lib/upl/inter.rb
CHANGED
@@ -1,4 +1,39 @@
|
|
1
|
-
|
1
|
+
module Upl
|
2
|
+
module Inter
|
3
|
+
# Try Term, then Fiddle::Pointer, then to_term_t.
|
4
|
+
# Return a term_t pointer
|
5
|
+
def self.term_t_of term_or_ptr
|
6
|
+
case term_or_ptr
|
7
|
+
when Term
|
8
|
+
term_or_ptr.term_t
|
9
|
+
when Fiddle::Pointer
|
10
|
+
term_or_ptr
|
11
|
+
else
|
12
|
+
term_or_ptr.to_term_t
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# lst_term is a Term, or a Fiddle::Pointer to term_t
|
17
|
+
# yield term_t items of the lst_term
|
18
|
+
def self.each_of_list lst_term, &blk
|
19
|
+
return enum_for __method__, lst_term unless block_given?
|
20
|
+
lst_term = Inter.term_t_of lst_term
|
21
|
+
|
22
|
+
while Extern::PL_get_nil(lst_term) != 1 # not end of list
|
23
|
+
res = Extern::PL_get_list \
|
24
|
+
lst_term,
|
25
|
+
(head_t = Extern.PL_new_term_ref),
|
26
|
+
(rst_t = Extern.PL_new_term_ref)
|
27
|
+
|
28
|
+
break unless res == 1
|
29
|
+
|
30
|
+
yield head_t
|
31
|
+
lst_term = rst_t
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
2
37
|
class Object
|
3
38
|
def to_atom
|
4
39
|
if frozen?
|
@@ -9,7 +44,13 @@ class Object
|
|
9
44
|
end
|
10
45
|
end
|
11
46
|
|
47
|
+
# return a Term object from to_term_t
|
12
48
|
def to_term
|
49
|
+
Upl::Term.new to_term_t
|
50
|
+
end
|
51
|
+
|
52
|
+
# return a term_t pointer
|
53
|
+
def to_term_t
|
13
54
|
if frozen?
|
14
55
|
# TODO must check instance variable here
|
15
56
|
_upl_termize
|
@@ -43,6 +84,3 @@ class Symbol
|
|
43
84
|
Upl::Extern.PL_new_atom to_s
|
44
85
|
end
|
45
86
|
end
|
46
|
-
|
47
|
-
module Inter
|
48
|
-
end
|
data/lib/upl/runtime.rb
CHANGED
@@ -17,11 +17,26 @@ module Upl
|
|
17
17
|
module Runtime
|
18
18
|
Ptr = Fiddle::Pointer
|
19
19
|
|
20
|
+
def self.call st_or_term
|
21
|
+
term =
|
22
|
+
case st_or_term
|
23
|
+
when String
|
24
|
+
Term.new st_or_term
|
25
|
+
when Term
|
26
|
+
st_or_term
|
27
|
+
else
|
28
|
+
raise "dunno bout #{st_or_term}"
|
29
|
+
end
|
30
|
+
|
31
|
+
rv = Extern.PL_call term.term_t, Extern::NULL
|
32
|
+
rv == 1 # don't raise
|
33
|
+
end
|
34
|
+
|
20
35
|
def self.init
|
21
36
|
# set up no output so we don't get swipl command line interfering in ruby
|
22
37
|
# TODO exception handling should not kick off a prolog terminal
|
23
38
|
# TODO see gem-swipl for more useful stuff here
|
24
|
-
args = %w[upl
|
39
|
+
args = %w[upl --tty=false --signals=false --debug=false --quiet=true]
|
25
40
|
|
26
41
|
# convert args to char **
|
27
42
|
ptr_size = Extern.sizeof 'char*'
|
@@ -33,6 +48,9 @@ module Upl
|
|
33
48
|
# call init
|
34
49
|
rv = Extern.PL_initialise args.size, arg_ptrs
|
35
50
|
rv == 1 or raise 'PL_initialise failed'
|
51
|
+
|
52
|
+
# we really don't want the prolog console showing up in ruby.
|
53
|
+
call 'set_prolog_flag(debug_on_error,false)'
|
36
54
|
end
|
37
55
|
|
38
56
|
# once_only. Should probably be a singleton or something.
|
@@ -42,26 +60,23 @@ module Upl
|
|
42
60
|
Extern.PL_predicate Fiddle::Pointer[name.to_s], arity, NULL
|
43
61
|
end
|
44
62
|
|
45
|
-
# Use prolog predicate to parse the string into a term with its named variables
|
63
|
+
# Use prolog predicate to parse the string into a term, with its named variables as a hash of Name => _variable
|
64
|
+
# TODO maybe use read_term_from_chars, or at least don't force the term to be an atom
|
65
|
+
# TODO need to use read_term_from_atom('retry(A,B,C)', Term, [variable_names(VarNames)]).
|
46
66
|
def self.term_vars st
|
47
|
-
# atom_to_term('your_pred(A,B,C,D)',Term,Options).
|
48
|
-
terms = Extern.PL_new_term_refs 3
|
49
|
-
atom, term, options = terms+0, terms+1, terms+2
|
50
|
-
|
51
|
-
Extern::PL_put_atom atom, (Extern::PL_new_atom Fiddle::Pointer[st])
|
52
|
-
Extern::PL_put_variable term
|
53
|
-
Extern::PL_put_variable options
|
54
|
-
|
55
|
-
# docs say to use read_term_from_atom/3, but it fails with uninstantiated variables for 7.7.18
|
56
67
|
rv = Extern::PL_call_predicate \
|
57
68
|
Extern::NULL, # module
|
58
69
|
0, # flags, see PL_open_query
|
59
70
|
(predicate 'atom_to_term', 3),
|
60
|
-
terms
|
71
|
+
(args = TermVector[st.to_sym, nil, nil]).terms
|
72
|
+
|
73
|
+
vars_hash = Inter.each_of_list(args[2]).map do |term_t|
|
74
|
+
# each of these is =(Atom,variable), and we want Atom => variable
|
75
|
+
t = Term.new term_t
|
76
|
+
[t.first.atom.to_sym, t.last]
|
77
|
+
end.to_h
|
61
78
|
|
62
|
-
|
63
|
-
# vars *must* be unhooked though ¯\_(ツ)_/¯
|
64
|
-
return (Term.new term), (list_to_ary options do |elt| Term.new elt end)
|
79
|
+
return args[1], vars_hash
|
65
80
|
end
|
66
81
|
|
67
82
|
def self.unify( term_a, term_b )
|
@@ -70,21 +85,18 @@ module Upl
|
|
70
85
|
end
|
71
86
|
|
72
87
|
# do a query for the given term and vars, as parsed by term_vars
|
73
|
-
def self.term_vars_query qterm,
|
88
|
+
def self.term_vars_query qterm, qvars_hash
|
74
89
|
raise "not a term" unless Term === qterm
|
75
|
-
return enum_for __method__, qterm,
|
90
|
+
return enum_for __method__, qterm, qvars_hash unless block_given?
|
76
91
|
|
77
92
|
fid_t = Extern.PL_open_foreign_frame
|
78
93
|
|
79
94
|
begin
|
80
|
-
# input values
|
81
|
-
|
82
|
-
qterm.args.each_with_index do |arg,idx|
|
83
|
-
Extern::PL_unify (terms_ptr+idx), arg
|
84
|
-
end
|
95
|
+
# populate input values from qterm
|
96
|
+
args = TermVector.new qterm.arity do |idx| qterm[idx] end
|
85
97
|
|
86
98
|
# module is NULL, flags is 0
|
87
|
-
query_id_p = Extern.PL_open_query Extern::NULL, 0, qterm.to_predicate,
|
99
|
+
query_id_p = Extern.PL_open_query Extern::NULL, 0, qterm.to_predicate, args.terms
|
88
100
|
query_id_p != 0 or raise 'no space on environment stack, see SWI-Prolog docs for PL_open_query'
|
89
101
|
|
90
102
|
loop do
|
@@ -92,13 +104,10 @@ module Upl
|
|
92
104
|
res = Extern.PL_next_solution query_id_p
|
93
105
|
break if res == 0
|
94
106
|
|
95
|
-
hash =
|
96
|
-
name_term_t, var_term_t = name_var.args.to_a
|
97
|
-
name = Term.new name_term_t
|
98
|
-
|
107
|
+
hash = qvars_hash.each_with_object Hash.new do |(name_sym,var),ha|
|
99
108
|
# term_t will be invalidated by the next call to PL_next_solution,
|
100
109
|
# so we need to construct a ruby tree of the value term
|
101
|
-
val = ha[
|
110
|
+
val = ha[name_sym] = var.to_ruby
|
102
111
|
# binding.pry if val.to_sym == :query_debug_settings rescue false
|
103
112
|
end
|
104
113
|
|
@@ -114,65 +123,43 @@ module Upl
|
|
114
123
|
fid_t and Extern.PL_close_foreign_frame fid_t
|
115
124
|
end
|
116
125
|
|
117
|
-
def self.eval st_or_term
|
118
|
-
p_term =
|
119
|
-
case st_or_term
|
120
|
-
when String
|
121
|
-
rv = Extern.PL_chars_to_term Fiddle::Pointer[st_or_term], (p_term = Extern.PL_new_term_ref)
|
122
|
-
raise "failure parsing term #{st_or_term}" unless rv == 1
|
123
|
-
p_term
|
124
|
-
when Term
|
125
|
-
st_or_term.term_t
|
126
|
-
else
|
127
|
-
raise "dunno bout #{st_or_term}"
|
128
|
-
end
|
129
|
-
|
130
|
-
rv = Extern.PL_call p_term, Extern::NULL
|
131
|
-
rv == 1 or raise "failure executing term #{st}"
|
132
|
-
end
|
133
|
-
|
134
126
|
def self.predicate name, arity
|
135
127
|
pred_p = Extern.PL_predicate Ptr[name.to_s], arity, Extern::NULL
|
136
128
|
end
|
137
129
|
|
138
|
-
|
139
|
-
|
130
|
+
# Simple query with predicate / arity
|
131
|
+
# Returns an array of arrays.
|
132
|
+
def self.squery predicate_str, arity
|
133
|
+
return enum_for :squery, predicate_str, arity unless block_given?
|
140
134
|
|
141
|
-
|
142
|
-
|
143
|
-
lst,
|
144
|
-
(head = Extern.PL_new_term_ref),
|
145
|
-
(rst = Extern.PL_new_term_ref)
|
135
|
+
p_functor = Extern::PL_new_functor predicate_str.to_sym.to_atom, arity
|
136
|
+
p_predicate = Extern::PL_pred p_functor, Extern::NULL
|
146
137
|
|
147
|
-
|
138
|
+
answer_lst = TermVector.new arity
|
139
|
+
query_id_p = Extern.PL_open_query Extern::NULL, 0, p_predicate, answer_lst.terms
|
148
140
|
|
149
|
-
|
150
|
-
|
141
|
+
loop do
|
142
|
+
rv = Extern.PL_next_solution query_id_p
|
143
|
+
break if rv == 0
|
144
|
+
yield answer_lst.each_t.map{|term_t| Tree.of_term term_t}
|
151
145
|
end
|
152
146
|
|
153
|
-
|
147
|
+
ensure
|
148
|
+
# NOTE this also gets called after enum_for
|
149
|
+
query_id_p&.to_i and Extern.PL_close_query query_id_p
|
154
150
|
end
|
155
151
|
|
156
|
-
|
157
|
-
|
158
|
-
return enum_for :
|
159
|
-
p_atom = Extern::PL_new_atom Fiddle::Pointer[predicate_str]
|
160
|
-
p_functor = Extern::PL_new_functor p_atom, arity
|
161
|
-
p_predicate = Extern::PL_pred p_functor, Extern::NULL
|
152
|
+
def self.query term
|
153
|
+
raise "not a Term" unless Term === term
|
154
|
+
return enum_for :query_term, term unless block_given?
|
162
155
|
|
163
|
-
answer_lst =
|
164
|
-
query_id_p = Extern.PL_open_query Extern::NULL, 0,
|
156
|
+
answer_lst = TermVector.new term.arity do |idx| term[idx] end
|
157
|
+
query_id_p = Extern.PL_open_query Extern::NULL, 0, term.to_predicate, answer_lst.terms
|
165
158
|
|
166
159
|
loop do
|
167
|
-
|
168
|
-
break if
|
169
|
-
|
170
|
-
answrs =
|
171
|
-
arity.times.map do |i|
|
172
|
-
term_to_ruby answer_lst+i
|
173
|
-
end
|
174
|
-
|
175
|
-
yield answrs
|
160
|
+
rv = Extern.PL_next_solution query_id_p
|
161
|
+
break if rv == 0
|
162
|
+
yield answer_lst.each_t.map{|term_t| Tree.of_term term_t}
|
176
163
|
end
|
177
164
|
|
178
165
|
ensure
|
data/lib/upl/term.rb
CHANGED
@@ -7,25 +7,21 @@ module Upl
|
|
7
7
|
def initialize term_or_string
|
8
8
|
case term_or_string
|
9
9
|
when String
|
10
|
-
|
11
|
-
|
10
|
+
@term_t = Extern.PL_new_term_ref
|
11
|
+
rv = Extern.PL_chars_to_term Fiddle::Pointer[term_or_string], @term_t
|
12
|
+
rv == 1 or raise "failure parsing term #{term_or_string}"
|
13
|
+
|
12
14
|
when Fiddle::Pointer
|
13
15
|
# assume this is a pointer to a term. Unsafe, but there's no choice really
|
14
16
|
@term_t = term_or_string
|
17
|
+
|
15
18
|
else
|
16
19
|
raise "can't handle #{term_or_string}"
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
20
23
|
attr_reader :term_t
|
21
|
-
|
22
|
-
def to_term; term_t end
|
23
|
-
|
24
|
-
# Make a copy of all the term information. Useful for passing in to queries, apparently.
|
25
|
-
def self.copy term_t
|
26
|
-
copy_term_t = Extern.PL_copy_term_ref term_t
|
27
|
-
new copy_term_t
|
28
|
-
end
|
24
|
+
alias to_term_t term_t
|
29
25
|
|
30
26
|
def self.of_atom atom
|
31
27
|
term_t = Extern.PL_new_term_ref
|
@@ -34,14 +30,14 @@ module Upl
|
|
34
30
|
term_t
|
35
31
|
end
|
36
32
|
|
37
|
-
# args are things that can be converted to term_t pointers using
|
33
|
+
# args are things that can be converted to term_t pointers using to_term_t method
|
38
34
|
def self.functor name, *args
|
39
35
|
# TODO maybe use a frame or something because this allocates quite a few sub-terms
|
40
36
|
functor_t = Extern.PL_new_functor name.to_sym.to_atom, args.size
|
41
37
|
|
42
38
|
arg_terms = Extern.PL_new_term_refs args.size
|
43
|
-
args.each_with_index do |arg,
|
44
|
-
Extern::PL_unify (arg_terms+
|
39
|
+
args.each_with_index do |arg,idx|
|
40
|
+
Extern::PL_unify (arg_terms+idx), arg.to_term_t
|
45
41
|
end
|
46
42
|
|
47
43
|
term_t = Extern.PL_new_term_ref
|
@@ -54,9 +50,10 @@ module Upl
|
|
54
50
|
def populate
|
55
51
|
int_ptr = Runtime::Ptr[0].ref
|
56
52
|
atom_ptr = Runtime::Ptr[0].ref
|
53
|
+
|
57
54
|
rv = Extern::PL_get_name_arity term_t, atom_ptr, int_ptr
|
58
55
|
# This happens when the term_t is not a PL_TERM (ie a compound)
|
59
|
-
raise "can't populate term"
|
56
|
+
rv == 1 or raise "can't populate term"
|
60
57
|
|
61
58
|
@arity = int_ptr.ptr.to_i
|
62
59
|
@atom = Atom.new atom_ptr
|
@@ -72,8 +69,6 @@ module Upl
|
|
72
69
|
[@atom, @arity] <=> [rhs.atom, rhs.arity]
|
73
70
|
end
|
74
71
|
|
75
|
-
# attr_reader :atom, :arity
|
76
|
-
|
77
72
|
def atom
|
78
73
|
@atom or begin
|
79
74
|
populate
|
@@ -96,29 +91,37 @@ module Upl
|
|
96
91
|
Extern::PL_pred to_functor, Extern::NULL
|
97
92
|
end
|
98
93
|
|
99
|
-
def tree; @tree || (Tree.
|
100
|
-
|
94
|
+
def tree; @tree || (Tree.of_term term_t) end
|
95
|
+
alias to_ruby tree
|
101
96
|
|
102
|
-
# Assume that term_t still has a value. Which means we have to copy all
|
103
|
-
# values before the underlying term_t is changed.
|
104
97
|
# TODO leaning hard towards each with Enumerable
|
105
|
-
def
|
98
|
+
def each
|
106
99
|
return enum_for :args unless block_given?
|
107
100
|
|
108
|
-
(1..arity).each do |
|
109
|
-
rv = Extern::PL_get_arg
|
110
|
-
|
111
|
-
|
112
|
-
else
|
113
|
-
puts "#{rv}: can't convert #{i} arg of #{atom}"
|
114
|
-
yield subterm
|
115
|
-
end
|
101
|
+
(1..arity).each do |idx|
|
102
|
+
rv = Extern::PL_get_arg idx, term_t, (subterm = Extern.PL_new_term_ref)
|
103
|
+
rv == 1 or raise "#{rv}: can't convert #{i} arg of #{atom}"
|
104
|
+
yield subterm
|
116
105
|
end
|
117
106
|
end
|
118
107
|
|
108
|
+
include Enumerable
|
109
|
+
|
110
|
+
def first; self[0] end
|
111
|
+
def last; self[arity-1] end
|
112
|
+
|
113
|
+
def [](idx)
|
114
|
+
# remember args for terms are 1-based
|
115
|
+
rv = Extern::PL_get_arg idx+1, term_t, (arg = Extern.PL_new_term_ref)
|
116
|
+
rv == 1 or raise "can't access term at #{idx}"
|
117
|
+
Term.new arg
|
118
|
+
end
|
119
|
+
|
120
|
+
# set term_t[idx] = val_term_t
|
121
|
+
# idx is zero-based, unlike the prolog calls
|
119
122
|
def []=( idx, val_term_t)
|
120
|
-
|
121
|
-
rv = Extern.
|
123
|
+
raise IndexError, "max index is #{arity-1}" if idx >= arity
|
124
|
+
rv = Extern.PL_unify_arg idx+1, term_t, val_term_t
|
122
125
|
rv == 1 or raise "can't set index #{idx}"
|
123
126
|
end
|
124
127
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'fiddle'
|
2
|
+
|
3
|
+
module Upl
|
4
|
+
# Create a c-array of terms using PL_new_term_refs. Methods on this class
|
5
|
+
# will return the term_t pointers wrapped in Term objects. If you want access
|
6
|
+
# to the underlying term_t pointers, use terms + idx, or the term_t method of
|
7
|
+
# the Term objects.
|
8
|
+
class TermVector
|
9
|
+
# args must all be convertible to term_t Fiddle::Pointers, via term_t_of.
|
10
|
+
#
|
11
|
+
# nil values are defaulted to Variable.new, but beware passing in the wrong
|
12
|
+
# number of arguments.
|
13
|
+
def self.[]( *args )
|
14
|
+
new args.size do |idx|
|
15
|
+
args[idx]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# similar to Array.new, but each value yielded from blk will be converted to
|
20
|
+
# term_t using term_t_of
|
21
|
+
def initialize size, &blk
|
22
|
+
@size = Integer size
|
23
|
+
@terms = Extern.PL_new_term_refs @size
|
24
|
+
|
25
|
+
if block_given?
|
26
|
+
@size.times do |idx|
|
27
|
+
termable = (yield idx) || Variable.new
|
28
|
+
term_t = Inter.term_t_of termable
|
29
|
+
# TODO not sure if Extern::PL_put_term should be available as a possibility here?
|
30
|
+
rv = Extern::PL_unify @terms+idx, term_t
|
31
|
+
rv == 1 or raise "can't set index #{idx} of term_vector to #{termable}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :size, :terms
|
37
|
+
|
38
|
+
def each_t
|
39
|
+
return enum_for :each_t unless block_given?
|
40
|
+
size.times.each do |idx| yield @terms+idx end
|
41
|
+
end
|
42
|
+
|
43
|
+
def each
|
44
|
+
return enum_for :each unless block_given?
|
45
|
+
size.times.each do |idx| yield Term.new @terms+idx end
|
46
|
+
end
|
47
|
+
|
48
|
+
include Enumerable
|
49
|
+
|
50
|
+
def first; Term.new @terms+0; end
|
51
|
+
def last; Term.new @terms+(size-1); end
|
52
|
+
|
53
|
+
def [](idx)
|
54
|
+
raise IndexError unless idx < @size
|
55
|
+
Term.new @terms+idx
|
56
|
+
end
|
57
|
+
|
58
|
+
def []=(idx, value)
|
59
|
+
raise IndexError unless idx < @size
|
60
|
+
Extern::PL_put_term @terms + idx, (Inter.term_t_of value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_a
|
64
|
+
size.times.map{|idx| @terms+idx}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/upl/tree.rb
CHANGED
@@ -13,7 +13,7 @@ module Upl
|
|
13
13
|
case term
|
14
14
|
when Term
|
15
15
|
@atom = term.atom
|
16
|
-
@args = term.
|
16
|
+
@args = term.map do |arg|
|
17
17
|
self.class.term_to_ruby arg
|
18
18
|
end
|
19
19
|
when Fiddle::Pointer
|
@@ -27,25 +27,34 @@ module Upl
|
|
27
27
|
term_to_ruby term_t
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.term_to_ruby
|
31
|
-
case
|
30
|
+
def self.term_to_ruby term_t
|
31
|
+
case term_t.term_type
|
32
32
|
when Extern::PL_VARIABLE
|
33
|
-
Variable.copy
|
33
|
+
Variable.copy term_t
|
34
34
|
|
35
35
|
when Extern::PL_ATOM
|
36
|
-
atom = Atom.of_term
|
36
|
+
atom = Atom.of_term term_t
|
37
37
|
if atom.to_s =~ /^ruby-(\d+)/
|
38
38
|
ObjectSpace._id2ref $1.to_i
|
39
39
|
else
|
40
|
-
atom
|
40
|
+
atom.to_sym
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
# I think integers > 63 bits can be fetched with PL_get_mpz
|
44
|
+
# Other than PL_INTEGER, most of these seem to be unused?
|
45
|
+
when Extern::PL_INTEGER, Extern::PL_LONG, Extern::PL_INT, Extern::PL_INT64, Extern::PL_SHORT
|
46
|
+
rv = Extern.PL_get_int64 term_t, (int_ptr = Fiddle::Pointer[0].ref)
|
47
|
+
rv == 1 or raise "Can't convert to int64. Maybe too large."
|
45
48
|
int_ptr.ptr.to_i
|
46
49
|
|
50
|
+
when Extern::PL_FLOAT
|
51
|
+
rv = Extern.PL_get_float term_t, (double_ptr = Fiddle::Pointer[0].ref)
|
52
|
+
rv == 1 or raise "Can't convert to double. Maybe too large."
|
53
|
+
bytes = double_ptr[0,8]
|
54
|
+
bytes.unpack('D').first
|
55
|
+
|
47
56
|
when Extern::PL_STRING
|
48
|
-
rv = Extern.PL_get_string
|
57
|
+
rv = Extern.PL_get_string term_t, (str_ptr = Fiddle::Pointer[0].ref), (len_ptr = Fiddle::Pointer[0].ref)
|
49
58
|
value_ptr = Fiddle::Pointer.new str_ptr.ptr, len_ptr.ptr.to_i
|
50
59
|
value_ptr.to_s
|
51
60
|
|
@@ -55,30 +64,18 @@ module Upl
|
|
55
64
|
nil
|
56
65
|
|
57
66
|
when Extern::PL_TERM
|
58
|
-
Tree.new
|
67
|
+
Tree.new term_t
|
59
68
|
|
60
69
|
when Extern::PL_LIST_PAIR
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.list_to_ary lst
|
67
|
-
rv = []
|
70
|
+
Inter.each_of_list(term_t).to_a
|
68
71
|
|
69
|
-
|
70
|
-
|
71
|
-
lst,
|
72
|
-
(head = Extern.PL_new_term_ref),
|
73
|
-
(rst = Extern.PL_new_term_ref)
|
72
|
+
when Extern::PL_DICT
|
73
|
+
Dict.of_term term_t
|
74
74
|
|
75
|
-
|
75
|
+
else
|
76
|
+
:NotImplemented
|
76
77
|
|
77
|
-
rv << (term_to_ruby head)
|
78
|
-
lst = rst
|
79
78
|
end
|
80
|
-
|
81
|
-
rv
|
82
79
|
end
|
83
80
|
|
84
81
|
def arity; args.size end
|
@@ -101,19 +98,5 @@ module Upl
|
|
101
98
|
end
|
102
99
|
end
|
103
100
|
end
|
104
|
-
|
105
|
-
protected
|
106
|
-
|
107
|
-
def populate_args count
|
108
|
-
(1..arity).each do |i|
|
109
|
-
rv = Extern::PL_get_arg i, term_t, (subterm = Extern.PL_new_term_ref)
|
110
|
-
if rv == 1
|
111
|
-
yield subterm
|
112
|
-
else
|
113
|
-
puts "#{rv}: can't convert #{i} arg of #{atom}"
|
114
|
-
yield subterm
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
101
|
end
|
119
102
|
end
|
data/lib/upl/variable.rb
CHANGED
@@ -2,10 +2,11 @@ module Upl
|
|
2
2
|
# Really this is just an empty term.
|
3
3
|
class Variable
|
4
4
|
def initialize term_t = nil
|
5
|
-
@term_t = term_t ||
|
5
|
+
@term_t = term_t || self.class.to_term
|
6
6
|
end
|
7
7
|
|
8
8
|
attr_reader :term_t
|
9
|
+
alias to_term_t term_t
|
9
10
|
|
10
11
|
def self.copy term_t
|
11
12
|
inst = new term_t
|
@@ -18,7 +19,7 @@ module Upl
|
|
18
19
|
|
19
20
|
# bit of a hack to create empty variables for a functor
|
20
21
|
def self.to_term
|
21
|
-
|
22
|
+
Extern.PL_new_term_ref
|
22
23
|
end
|
23
24
|
|
24
25
|
def to_s; _string end
|
data/lib/upl/version.rb
CHANGED
data/lib/upl.rb
CHANGED
@@ -6,17 +6,58 @@ require_relative 'upl/term'
|
|
6
6
|
require_relative 'upl/variable'
|
7
7
|
require_relative 'upl/atom'
|
8
8
|
require_relative 'upl/runtime'
|
9
|
+
require_relative 'upl/dict'
|
9
10
|
require_relative 'upl/tree'
|
10
11
|
require_relative 'upl/inter'
|
12
|
+
require_relative 'upl/term_vector'
|
11
13
|
|
12
14
|
module Upl
|
13
|
-
def self.query
|
14
|
-
|
15
|
-
|
15
|
+
def self.query string_or_term, &blk
|
16
|
+
case string_or_term
|
17
|
+
when Term
|
18
|
+
Runtime.query string_or_term
|
19
|
+
when String
|
20
|
+
term, vars = Runtime.term_vars string_or_term
|
21
|
+
Runtime.term_vars_query term, vars, &blk
|
22
|
+
else
|
23
|
+
raise "dunno about #{string_or_term.inspect}"
|
24
|
+
end
|
16
25
|
end
|
17
26
|
|
18
27
|
def self.consult filename
|
19
28
|
p = Pathname filename
|
20
|
-
Runtime::
|
29
|
+
Runtime::call %Q{["#{p.realpath.to_s}"]}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.asserta term
|
33
|
+
Runtime.call Term.functor :asserta, term
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.assertz term
|
37
|
+
Runtime.call Term.functor :assertz, term
|
38
|
+
end
|
39
|
+
|
40
|
+
# behaves as if run under once, cos of the way call works
|
41
|
+
def self.retract term
|
42
|
+
Runtime.call Term.functor :retract, term
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.listing
|
46
|
+
(Upl.query 'with_output_to(string(Buffer),listing)').first[:Buffer]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Nicer syntax for Term.functor. Construct a Term from a symbol and args that
|
50
|
+
# all respond to 'to_term_t'.
|
51
|
+
#
|
52
|
+
# In other words:
|
53
|
+
#
|
54
|
+
# Upl.query 'current_prolog_flag(A,B)'
|
55
|
+
#
|
56
|
+
# is moreorless the same as
|
57
|
+
#
|
58
|
+
# Upl.query Term :current_prolog_flag, Variable.new, Variable.new
|
59
|
+
#
|
60
|
+
def self.Term name, *args
|
61
|
+
Term.functor name, *args
|
21
62
|
end
|
22
63
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: upl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Anderson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-09-
|
11
|
+
date: 2018-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -98,11 +98,13 @@ files:
|
|
98
98
|
- bin/setup
|
99
99
|
- lib/upl.rb
|
100
100
|
- lib/upl/atom.rb
|
101
|
+
- lib/upl/dict.rb
|
101
102
|
- lib/upl/extern.rb
|
102
103
|
- lib/upl/functor.rb
|
103
104
|
- lib/upl/inter.rb
|
104
105
|
- lib/upl/runtime.rb
|
105
106
|
- lib/upl/term.rb
|
107
|
+
- lib/upl/term_vector.rb
|
106
108
|
- lib/upl/tree.rb
|
107
109
|
- lib/upl/variable.rb
|
108
110
|
- lib/upl/version.rb
|