upl 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|