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 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