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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7e5de431546d46c5f4998818e1360e10e5dab6497e0988e783e626393b35cc7
4
- data.tar.gz: 4cd776d265de52483fe89e239041ee001ba21856c339fb067b661437c6cbfd25
3
+ metadata.gz: dc30b39fd062308b8cbae9bcaeca09c2b4921ff888c42ba00d100c2a045b39bf
4
+ data.tar.gz: 11adee909aee233bae48fe9ea7d1c33b25ba35ef33c1fabe6526e399e7920d81
5
5
  SHA512:
6
- metadata.gz: a8ab63130414fe7d8ae8775c4a0f11199c76024952541b2820deb8e607361ca26cb07bcec64fc2a2df8e5790a5042caaa1598a0705d0271d6ac4dca4cd9497de
7
- data.tar.gz: 58d09609f7888ee559bb626219f7be13576016aa40dff3992c12413a84d3113841e748f7896aa93d204dcceb2b5f0e40528e828bc30b930e7743ccb134efaf1f
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
- ?- assert(person(john,anderson)).
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
- [1] pry(Upl):1> fact = Term.functor :person, :john, :anderson
54
+ [2] pry(main)> fact = Upl::Term.functor :person, :john, :anderson
55
55
  => person/2(john,anderson)
56
- [2] pry(Upl):1> Runtime.eval Term.functor :assert, fact
56
+ [3] pry(main)> Upl.assertz fact
57
57
  => true
58
- [3] pry(Upl):1> Array query 'person(A,B)'
58
+ [4] pry(main)> Array Upl.query 'person(A,B)'
59
59
  => [{:A=>john, :B=>anderson}]
60
- [4] pry(Upl):1> Runtime.eval Term.functor :retract, fact
60
+ [5] pry(main)> Upl.retract fact
61
61
  => true
62
- [5] pry(Upl):1> Array query 'person(A,B)'
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
- [1] pry(Upl):1> fact = Term.functor :person, :john, :anderson, (o = Object.new)
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
- [2] pry(Upl):1> Runtime.eval Term.functor :assert, fact
74
+ [3] pry(main)> Upl.assertz fact
75
75
  => true
76
- [3] pry(Upl):1> ha, = Array query 'person(A,B,C)'
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
- [4] pry(Upl):1> ha[:C].equal? o
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::Runtime.eval Upl::Term.functor :assert, fact
91
+ Upl.assertz fact
92
92
 
93
93
  fact2 = Upl::Term.functor :person, :thomas, :paine, (thing2 = Object.new)
94
- Upl::Runtime.eval Upl::Term.functor :assert, fact2
94
+ Upl.assertz fact2
95
95
 
96
96
  # Note that both facts are in the result
97
- query_term, query_vars = Upl::Runtime.term_vars 'person(A,B,C)'
98
- Array Upl::Runtime.term_vars_query query_term, query_vars
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, query_vars = Upl::Runtime.term_vars 'person(A,B,C)'
104
- Upl::Extern.PL_unify query_vars.last.args.to_a.last, thing2.to_term
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, query_vars
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, eval current_prolog_flag(version_data,swi(M,I,P,E)). Major, mInor, Patch, Extra[]
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
- # TODO not used
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 -q --tty=false --nosignals]
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
- # first must be Term.new otherwise Term unhooks the term_t pointer
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, qvars
88
+ def self.term_vars_query qterm, qvars_hash
74
89
  raise "not a term" unless Term === qterm
75
- return enum_for __method__, qterm, qvars unless block_given?
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
- terms_ptr = Extern.PL_new_term_refs qterm.arity
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, terms_ptr
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 = qvars.each_with_object Hash.new do |name_var,ha|
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[name.atom.to_sym] = Tree.of_term var_term_t
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
- def self.list_to_ary lst, &elt_converter
139
- rv = []
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
- while Extern::PL_get_nil(lst) != 1 # not end of list
142
- res = Extern::PL_get_list \
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
- break unless res == 1
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
- rv << (elt_converter.call head)
150
- lst = rst
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
- rv
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
- # simple query with predicate / arity
157
- def self.squery predicate_str, arity
158
- return enum_for :squery, predicate_str, arity unless block_given?
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 = Extern.PL_new_term_refs arity
164
- query_id_p = Extern.PL_open_query Extern::NULL, 0, p_predicate, answer_lst
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
- res = Extern.PL_next_solution query_id_p
168
- break if res == 0
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
- raise "can't do strings yet"
11
- # PL_chars_to_term term_or_string
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 to_term method
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,i|
44
- Extern::PL_unify (arg_terms+i), arg.to_term
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" unless rv == 1
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.new self) end
100
- def to_ruby; tree end
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 args
98
+ def each
106
99
  return enum_for :args unless block_given?
107
100
 
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
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
- Extern::PL_get_arg idx+1, term_t, (subterm = Extern.PL_new_term_ref)
121
- rv = Extern.PL_unify subterm, val_term_t
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.args.map do |arg|
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 term
31
- case term.term_type
30
+ def self.term_to_ruby term_t
31
+ case term_t.term_type
32
32
  when Extern::PL_VARIABLE
33
- Variable.copy term
33
+ Variable.copy term_t
34
34
 
35
35
  when Extern::PL_ATOM
36
- atom = Atom.of_term 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
- when Extern::PL_INTEGER
44
- Extern.PL_get_integer term, (int_ptr = Fiddle::Pointer[0].ref)
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 term, (str_ptr = Fiddle::Pointer[0].ref), (len_ptr = Fiddle::Pointer[0].ref)
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 term
67
+ Tree.new term_t
59
68
 
60
69
  when Extern::PL_LIST_PAIR
61
- list_to_ary term
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
- while Extern::PL_get_nil(lst) != 1 # not end of list
70
- res = Extern::PL_get_list \
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
- break unless res == 1
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 || Extern.PL_new_term_ref
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
- new
22
+ Extern.PL_new_term_ref
22
23
  end
23
24
 
24
25
  def to_s; _string end
data/lib/upl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Upl
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
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 st, &blk
14
- term, vars = Runtime::term_vars st
15
- Runtime::term_vars_query term, vars, &blk
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::eval %Q{["#{p.realpath.to_s}"]}
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.1
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-13 00:00:00.000000000 Z
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