upl 0.0.2 → 0.0.3

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: dc30b39fd062308b8cbae9bcaeca09c2b4921ff888c42ba00d100c2a045b39bf
4
- data.tar.gz: 11adee909aee233bae48fe9ea7d1c33b25ba35ef33c1fabe6526e399e7920d81
3
+ metadata.gz: 74895ae80e02af92445e7d4fb47413dbb67b0996ced75014b250c18bba5bb444
4
+ data.tar.gz: 7288b2b9b91bb4a7127ff05341ccb053d5a159e7dcdd70537d4d6a21cfc186bb
5
5
  SHA512:
6
- metadata.gz: 5cc115b7e58995a480463b226df08d68fa54ee9a441d64b47efbc13546f184d3f4ee91fc14881f4f6df5773c2dd6b7f8f5268d86db2a7009bac036afa564b462
7
- data.tar.gz: cb405495fee3c55030f520c3be3c6305b8158848aa77c9d597233bf15a76c82b352d675d4277fc75acaec9c87d4e6ed21a977e79290301ec5882f500f86385c8
6
+ metadata.gz: 516ebd68a80b61d75d12d1ca91833409cce8bc0d84d3d44346e07fc7b444c70f3d9d11eaca42cdbe39ff78f28e06fcc15af53c0659fa29ece17e316364e23706
7
+ data.tar.gz: ded90962d4080696c29e87cf83ec3a429e8feca7b693b01ae2b2b2596fa7980a6ea0d44af876c014fff8b8f371bf27b3810dd8f2515bf5f157ccc9efb4e7cf5a
data/Gemfile CHANGED
@@ -4,3 +4,6 @@ git_source(:github) {|repo_name| "https://github.com/djellemah/upl" }
4
4
 
5
5
  # Specify your gem's dependencies in upl.gemspec
6
6
  gemspec
7
+
8
+ gem 'pry-stack_explorer'
9
+ gem 'pry-byebug'
@@ -0,0 +1,3 @@
1
+ == 0.0.3
2
+ * rectify README errors
3
+ * specs to handle variable unification from variables from top-level queries.
data/README.md CHANGED
@@ -17,7 +17,7 @@ Query a built-in predicate, with a full expression:
17
17
  ``` ruby
18
18
  [1] pry(main)> enum = Upl.query 'current_prolog_flag(K,V), member(K,[home,executable,shared_object_extension])'
19
19
  => #<Enumerator: ...>
20
- [12] pry(main) enum.to_a
20
+ [2] pry(main) enum.to_a
21
21
  => [{:K=>home, :V=>/usr/lib64/swipl-7.7.18},
22
22
  {:K=>executable, :V=>/usr/local/rvm/rubies/ruby-2.6.0-preview2/bin/ruby},
23
23
  {:K=>shared_object_extension, :V=>so}]
@@ -51,15 +51,15 @@ false.
51
51
  And in Upl:
52
52
 
53
53
  ``` ruby
54
- [2] pry(main)> fact = Upl::Term.functor :person, :john, :anderson
54
+ [1] pry(main)> fact = Upl::Term.functor :person, :john, :anderson
55
55
  => person/2(john,anderson)
56
- [3] pry(main)> Upl.assertz fact
56
+ [2] pry(main)> Upl.assertz fact
57
57
  => true
58
- [4] pry(main)> Array Upl.query 'person(A,B)'
58
+ [3] pry(main)> Array Upl.query 'person(A,B)'
59
59
  => [{:A=>john, :B=>anderson}]
60
- [5] pry(main)> Upl.retract fact
60
+ [4] pry(main)> Upl.retract fact
61
61
  => true
62
- [6] pry(main)> Array Upl.query 'person(A,B)'
62
+ [5] 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
- [2] pry(main)> fact = Upl::Term.functor :person, :john, :anderson, (o = Object.new)
72
+ [1] pry(main)> fact = Upl::Term.functor :person, :john, :anderson, (o = Object.new)
73
73
  => person/3(john,anderson,#<Object:0x0000563346a08e38 @_upl_atom=439429>)
74
- [3] pry(main)> Upl.assertz fact
74
+ [2] pry(main)> Upl.assertz fact
75
75
  => true
76
- [4] pry(main)> ha, = Array Upl.query 'person(A,B,C)'
76
+ [3] 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
- [5] pry(main)> ha[:C].equal? o
80
+ [4] pry(main)> ha[:C].equal? o
81
81
  => true
82
82
  ```
83
83
 
@@ -87,28 +87,40 @@ much wisdom. Hurhur. And at least one extra instance variable.
87
87
  And now, the pièce de résistance - using an object as an input term:
88
88
 
89
89
  ``` ruby
90
- fact = Upl::Term.functor :person, :james, :madison, (o = Object.new)
90
+ fact = Upl::Term.functor :person, :james, :madison, (o = Object.new)
91
91
  Upl.assertz fact
92
92
 
93
93
  fact2 = Upl::Term.functor :person, :thomas, :paine, (thing2 = Object.new)
94
94
  Upl.assertz fact2
95
95
 
96
- # Note that both facts are in the result
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
- =>[{:A=>james, :B=>madison, :C=>#<Object:0x0000563f56e35580 @_upl_atom=439429>},
100
- {:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}]
96
+ # Note that both facts are in the result and the values for C are different
97
+ query_term, query_vars = Upl::Runtime.term_vars 'person(A,B,C)'
98
+ Array Upl::Runtime.term_vars_query query_term, query_vars
99
+ =>[
100
+ {:A=>james, :B=>madison, :C=>#<Object:0x0000563f56e35580 @_upl_atom=439429>},
101
+ {:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}
102
+ ]
101
103
 
102
- # Unify C with thing2. This needs a nicer api :-\
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
104
+ # Unify C with thing2
105
+ query_term, query_vars = Upl::Runtime.term_vars 'person(A,B,C)'
106
+ query_vars.C = thing2
105
107
 
106
108
  # ... and we get the correct result
107
109
  # Note that the first fact is not in the result.
108
- Array Upl::Runtime.term_vars_query query_term, query_hash
110
+ Array Upl::Runtime.term_vars_query query_term, query_vars
109
111
  => [{:A=>thomas, :B=>paine, :C=>#<Object:0x0000563f56d2b5b8 @_upl_atom=439813>}]
110
112
  ```
111
113
 
114
+ ### Ruby Predicates
115
+
116
+ You can define predicates in ruby.
117
+ ``` ruby
118
+
119
+
120
+ ```
121
+
122
+ So you can (theoretically) define a query in prolog that searches a ruby object graph.
123
+
112
124
  ## Disclaimer
113
125
 
114
126
  This is in-development code. I use it for some things other than just playing with. It might be useful for you too.
data/lib/upl.rb CHANGED
@@ -4,41 +4,47 @@ require_relative 'upl/version'
4
4
  require_relative 'upl/extern'
5
5
  require_relative 'upl/term'
6
6
  require_relative 'upl/variable'
7
+ require_relative 'upl/variables'
7
8
  require_relative 'upl/atom'
8
9
  require_relative 'upl/runtime'
9
10
  require_relative 'upl/dict'
10
11
  require_relative 'upl/tree'
11
12
  require_relative 'upl/inter'
12
13
  require_relative 'upl/term_vector'
14
+ require_relative 'upl/foreign'
13
15
 
14
16
  module Upl
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
17
+ module_function def query string_or_term, vars = nil, &blk
18
+ if string_or_term.is_a?(Term) && vars
19
+ Runtime.term_vars_query string_or_term, vars
22
20
  else
23
- raise "dunno about #{string_or_term.inspect}"
21
+ case string_or_term
22
+ when Term
23
+ Runtime.query string_or_term
24
+ when String
25
+ term, vars = Runtime.term_vars string_or_term
26
+ Runtime.term_vars_query term, vars, &blk
27
+ else
28
+ raise "dunno about #{string_or_term.inspect}"
29
+ end
24
30
  end
25
31
  end
26
32
 
27
- def self.consult filename
33
+ module_function def consult filename
28
34
  p = Pathname filename
29
35
  Runtime::call %Q{["#{p.realpath.to_s}"]}
30
36
  end
31
37
 
32
- def self.asserta term
38
+ module_function def asserta term
33
39
  Runtime.call Term.functor :asserta, term
34
40
  end
35
41
 
36
- def self.assertz term
42
+ module_function def assertz term
37
43
  Runtime.call Term.functor :assertz, term
38
44
  end
39
45
 
40
46
  # behaves as if run under once, cos of the way call works
41
- def self.retract term
47
+ module_function def retract term
42
48
  Runtime.call Term.functor :retract, term
43
49
  end
44
50
 
@@ -57,7 +63,7 @@ module Upl
57
63
  #
58
64
  # Upl.query Term :current_prolog_flag, Variable.new, Variable.new
59
65
  #
60
- def self.Term name, *args
66
+ module_function def Term name, *args
61
67
  Term.functor name, *args
62
68
  end
63
69
  end
@@ -1,35 +1,74 @@
1
1
  module Upl
2
2
  class Atom
3
- def initialize( atom_ref )
4
- @atom_ptr = atom_ref.ptr
5
- atom_chars_ptr = ::Upl::Extern::PL_atom_chars @atom_ptr
6
- @_symbol = atom_chars_ptr.to_s.to_sym
3
+ # NOTE these are atom_t, NOT term_t and NOT Fiddle::Pointer to atom_t
4
+ def initialize( atom_t )
5
+ @atom_t = atom_t
6
+
7
+ # pretty much all other methods need chars, so just do it now.
8
+ @chars = (::Upl::Extern::PL_atom_chars @atom_t).to_s.freeze
7
9
  end
8
10
 
9
- # drop the term immediately, and just keep the atom pointer
11
+ # drop the term immediately, and just keep the atom value
10
12
  def self.of_term( term_t )
11
- rv = Extern::PL_get_atom term_t, (atom_ref = Fiddle::Pointer.new(0).ref)
12
- raise "can't get atom from term" unless rv == 1
13
- new atom_ref
13
+ rv = Extern::PL_get_atom term_t, (atom_t = Fiddle::Pointer[0].ref)
14
+ rv == 1 or raise "can't get atom from term"
15
+ new atom_t.ptr
16
+ end
17
+
18
+ attr_reader :atom_t
19
+
20
+ # NOTE this returns an atom_t for obj.object_id embedded in an atom
21
+ def self.t_of_ruby obj
22
+ Upl::Extern.PL_new_atom "ruby-#{obj.object_id.to_s}"
14
23
  end
15
24
 
16
- attr_reader :atom_ptr
25
+ # return the object_id embedded in the atom, or nil if it's not an embedded
26
+ # object_id
27
+ def to_obj_id
28
+ if instance_variable_defined? :@to_obj_id
29
+ @to_obj_id
30
+ else
31
+ @to_obj_id =
32
+ if @chars =~ /^ruby-(\d+)/
33
+ $1.to_i
34
+ end
35
+ end
36
+ end
37
+
38
+ # return the ruby object associated with the object_id embedded in the atom
39
+ def to_ruby
40
+ if to_obj_id
41
+ ObjectSpace._id2ref to_obj_id
42
+ else
43
+ case _sym = to_sym
44
+ when :false; false
45
+ when :true; true
46
+ else _sym
47
+ end
48
+ end
49
+ rescue RangeError
50
+ # object with the given obj_id no longer exists in ruby, so just return
51
+ # the symbol with the embedded object_id.
52
+ to_sym
53
+ end
17
54
 
18
55
  def == rhs
19
- to_sym == rhs.to_sym
56
+ atom_t == rhs.atom_t
20
57
  end
21
58
 
22
59
  def to_sym
23
- @_symbol or raise "no symbol for atom"
60
+ @chars.to_sym
24
61
  end
25
62
 
26
63
  def to_s
27
- @_string ||= to_sym.to_s
64
+ @chars
28
65
  end
29
66
 
30
67
  def inspect; to_sym end
31
68
 
32
69
  def pretty_print pp
70
+ pp.text atom_t
71
+ pp.text '-'
33
72
  pp.text to_s
34
73
  end
35
74
  end
@@ -54,6 +54,9 @@ module Upl
54
54
  # terms
55
55
  extern 'predicate_t PL_predicate(const char *name, int arity, const char *module)'
56
56
 
57
+ TRUE = (1)
58
+ FALSE = (0)
59
+
57
60
  ##############
58
61
  # querying and getting results
59
62
 
@@ -118,6 +121,17 @@ module Upl
118
121
  PL_NOT_A_LIST = (43)
119
122
  PL_DICT = (44)
120
123
 
124
+ # Foreign predicate flags
125
+ PL_FA_NOTRACE = (0x01) # foreign cannot be traced
126
+ # PL_FA_TRANSPARENT = (0x02) # foreign is module transparent. Deprecated.
127
+ PL_FA_NONDETERMINISTIC = (0x04) # foreign is non-deterministic
128
+ PL_FA_VARARGS = (0x08) # call using t0, ac, ctx
129
+ PL_FA_CREF = (0x10) # Internal: has clause-reference
130
+ PL_FA_ISO = (0x20) # Internal: ISO core predicate
131
+ PL_FA_META = (0x40) # Additional meta-argument spec
132
+
133
+ extern 'int PL_register_foreign_in_module(char *mod, char *name, int arity, foreign_t (*f)(), int flags, ...)'
134
+
121
135
  module Convert
122
136
  REP_UTF8 = 0x1000
123
137
  BUF_MALLOC = 0x0200
@@ -197,6 +211,10 @@ module Upl
197
211
  extern 'int PL_put_variable(term_t t)'
198
212
  extern 'int PL_put_functor(term_t t, functor_t functor)'
199
213
  extern 'int PL_put_term(term_t t1, term_t t2)' # Make t1 point to the same term as t2.
214
+ extern 'int PL_put_integer(term_t t, long i)'
215
+ extern 'int PL_put_int64(term_t t, int64_t i)'
216
+
217
+ extern 'int PL_put_string_nchars(term_t t, size_t len, const char *chars)'
200
218
 
201
219
  extern 'int PL_cons_functor_v(term_t h, functor_t fd, term_t a0)'
202
220
 
@@ -243,5 +261,8 @@ module Upl
243
261
  extern 'int PL_wchars_to_term(const pl_wchar_t *chars, term_t term)'
244
262
 
245
263
  extern 'void PL_unregister_atom(atom_t a)'
264
+
265
+ # signature is actually PL_agc_hook_t, not void*
266
+ extern 'void* PL_agc_hook(void*)'
246
267
  end
247
268
  end
@@ -0,0 +1,55 @@
1
+ module Upl
2
+ # Register a foreign predicate in prolog
3
+ # Upl::Extern.PL_register_foreign_in_module(char *mod, char *name, int arity, foreign_t (*f)(), int flags, ...)
4
+
5
+ # create the foreign predicate as a class in ruby
6
+ #
7
+ # closure = Class.new(Fiddle::Closure) {
8
+ # def call
9
+ # 10
10
+ # end
11
+ # }.new(Fiddle::TYPE_INT, [])
12
+ #
13
+ # func = Fiddle::Function.new(closure, [], Fiddle::TYPE_INT)
14
+
15
+ # create the foreign predicate as a block in ruby
16
+ #
17
+ # new(ctype, args, abi = Fiddle::Function::DEFAULT, &block)
18
+ # cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
19
+ # one
20
+ # end
21
+ #
22
+ # func = Function.new(cb, [TYPE_INT], TYPE_INT)
23
+
24
+ module Foreign
25
+ def self.predicates
26
+ @predicates ||= Hash.new
27
+ end
28
+
29
+ def self.register_semidet name, arity = nil, module: nil, &blk
30
+ arity ||= blk.arity
31
+ arg_types = arity.times.map{Fiddle::TYPE_VOIDP}
32
+ ruby_pred = Fiddle::Closure::BlockCaller.new Fiddle::TYPE_INT, arg_types do |*args|
33
+ case (rv = blk.call *args)
34
+ when true; Upl::Extern::TRUE
35
+ when false, NilClass; Upl::Extern::FALSE
36
+ when 0, 1; rv
37
+ else Upl::Extern::TRUE
38
+ end
39
+ rescue
40
+ # TODO raise an exception here, otherwise errors get lost in an empty result set.
41
+ Upl::Extern::FALSE
42
+ end
43
+
44
+ module_name = Fiddle::Pointer[module_name&.to_s || 0]
45
+
46
+ fn = Fiddle::Function.new ruby_pred, arg_types, Fiddle::TYPE_INT
47
+ rv = Upl::Extern.PL_register_foreign_in_module module_name, name.to_s, arity, fn, 0
48
+ rv == 1 or raise "can't register ruby predicate #{name}/#{arity}"
49
+
50
+ # NOTE you have to keep ruby_pred and fn around somewhere, otherwise they
51
+ # get garbage collected, and then the callback segfaults.
52
+ predicates[[name,arity].join('/').to_sym] = ruby_pred, fn
53
+ end
54
+ end
55
+ end
@@ -1,3 +1,6 @@
1
+ require_relative 'extern'
2
+ require_relative 'foreign'
3
+
1
4
  module Upl
2
5
  module Inter
3
6
  # Try Term, then Fiddle::Pointer, then to_term_t.
@@ -13,6 +16,19 @@ module Upl
13
16
  end
14
17
  end
15
18
 
19
+ # call any method on any object, from prolog
20
+ def self.register_mcall_predicate
21
+ Upl::Foreign.register_semidet :mcall do |obj_term_t,meth_term_t,v_term_t|
22
+ obj = Upl::Tree.of_term obj_term_t
23
+ meth = Upl::Tree.of_term meth_term_t
24
+ v = obj.send meth
25
+
26
+ Upl::Extern::PL_unify v_term_t, v.to_term_t
27
+ end
28
+ end
29
+
30
+ register_mcall_predicate
31
+
16
32
  # lst_term is a Term, or a Fiddle::Pointer to term_t
17
33
  # yield term_t items of the lst_term
18
34
  def self.each_of_list lst_term, &blk
@@ -31,6 +47,57 @@ module Upl
31
47
  lst_term = rst_t
32
48
  end
33
49
  end
50
+
51
+ # keep track of object_id atoms that were assigned in prolog, and prolog now
52
+ # wants to garbage collect them.
53
+ class Agc
54
+ def initialize
55
+ @id_objects = {}
56
+ end
57
+
58
+ def self.instance
59
+ @instance ||= new
60
+ end
61
+
62
+ def register obj
63
+ @id_objects[obj.object_id] = obj
64
+ end
65
+
66
+ def deregister obj
67
+ @id_objects.delete obj.object_id
68
+ end
69
+ end
70
+
71
+ module_function def attach_atom_hook
72
+ @atom_hook = atom_hook = Fiddle::Closure::BlockCaller.new Fiddle::TYPE_INT, [Fiddle::TYPE_VOIDP] do |atom_t|
73
+ atom = Upl::Atom.new atom_t
74
+ p atom_t: atom_t.to_i, atom: atom
75
+ if atom.to_obj_id
76
+ obj = atom.to_ruby
77
+ p obj: obj, dereg: (Agc.instance.deregister obj)
78
+ end
79
+
80
+ # FALSE here will prevent garbage collection
81
+ Upl::Extern::TRUE
82
+ end
83
+
84
+ # NOTENOTE this must NOT be garbage-collected, otherwise the callback to it will fail.
85
+ @atom_hook_fn = Fiddle::Function.new atom_hook, atom_hook.args, atom_hook.ctype
86
+
87
+ # returns old fn ptr
88
+ Upl::Extern.PL_agc_hook @atom_hook_fn
89
+ end
90
+
91
+ def register_mcall_predicate
92
+ Upl::Foreign.register_semidet :mcall do |obj_term_t,meth_term_t,v_term_t|
93
+ obj = Upl::Tree.of_term obj_term_t
94
+ meth = Upl::Tree.of_term meth_term_t
95
+ v = obj.send meth
96
+
97
+ Upl::Extern::PL_unify v_term_t, v.to_term_t
98
+ end
99
+ end
100
+
34
101
  end
35
102
  end
36
103
 
@@ -70,12 +137,16 @@ protected
70
137
  end
71
138
 
72
139
  def _upl_atomize
73
- # see also PL_agc_hook for hooking into the swipl GC
74
- ObjectSpace.define_finalizer self do |this_obj|
75
- # TODO PL_unregister_atom? Finalizer?
76
- Upl::Extern.PL_unregister_atom this_obj.instance_variable_get :@_upl_atom
77
- end
78
- Upl::Extern.PL_new_atom "ruby-#{object_id.to_s}"
140
+ # TODO see also PL_agc_hook for hooking into the swipl GC
141
+ atom_t = Upl::Atom.t_of_ruby self
142
+ ObjectSpace.define_finalizer self, &self.class._upl_finalizer_blk(atom_t)
143
+ atom_t
144
+ end
145
+
146
+ # Have to put this in a separate method, otherwise the finalizer block's
147
+ # binding holds onto the obj it's trying to finalize.
148
+ def self._upl_finalizer_blk atom_t
149
+ proc do |objid| Upl::Extern.PL_unregister_atom atom_t end
79
150
  end
80
151
  end
81
152
 
@@ -84,3 +155,19 @@ class Symbol
84
155
  Upl::Extern.PL_new_atom to_s
85
156
  end
86
157
  end
158
+
159
+ class Integer
160
+ def to_term_t
161
+ rv = Upl::Extern.PL_put_int64 (term_t = Upl::Extern.PL_new_term_ref), self
162
+ rv == 1 or raise "can't convert #{self} to term. Maybe too big."
163
+ term_t
164
+ end
165
+ end
166
+
167
+ class String
168
+ def to_term_t
169
+ rv = Upl::Extern.PL_put_string_nchars (term_t = Upl::Extern.PL_new_term_ref), length, Fiddle::Pointer[self]
170
+ rv == 1 or raise "can't convert #{self} to term"
171
+ term_t
172
+ end
173
+ end
@@ -9,7 +9,7 @@ class Fiddle::Pointer
9
9
 
10
10
  def type_string
11
11
  type_int = term_type
12
- ::Upl::Extern.constants.find{|c| (::Upl::Extern.const_get c) == type_int}
12
+ ::Upl::Extern.constants.find{|c| (::Upl::Extern.const_get c) == type_int} || type_int
13
13
  end
14
14
  end
15
15
 
@@ -32,15 +32,23 @@ module Upl
32
32
  rv == 1 # don't raise
33
33
  end
34
34
 
35
+ def self.ruby_free_fn
36
+ @ruby_free_fn ||= Fiddle::Function.new Fiddle::RUBY_FREE, [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID
37
+ end
38
+
39
+ def self.swipl_free_fn
40
+ @swipl_free_fn ||= Fiddle::Function.new Extern['PL_free'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID
41
+ end
42
+
35
43
  def self.init
36
44
  # set up no output so we don't get swipl command line interfering in ruby
37
45
  # TODO exception handling should not kick off a prolog terminal
38
- # TODO see gem-swipl for more useful stuff here
46
+ # TODO from gem-swipl args = [ @swipl_lib, "-tty", "-q", "-t", "true", "-g", "true", "--nodebug", "--nosignals" ]
39
47
  args = %w[upl --tty=false --signals=false --debug=false --quiet=true]
40
48
 
41
49
  # convert args to char **
42
50
  ptr_size = Extern.sizeof 'char*'
43
- arg_ptrs = Ptr.malloc(ptr_size * args.size)
51
+ arg_ptrs = Ptr.malloc ptr_size * args.size, ruby_free_fn
44
52
  args.each_with_index do |rg,i|
45
53
  (arg_ptrs + i*ptr_size)[0,ptr_size] = Ptr[rg].ref
46
54
  end
@@ -70,13 +78,13 @@ module Upl
70
78
  (predicate 'atom_to_term', 3),
71
79
  (args = TermVector[st.to_sym, nil, nil]).terms
72
80
 
73
- vars_hash = Inter.each_of_list(args[2]).map do |term_t|
81
+ vars = Inter.each_of_list(args[2]).each_with_object Variables.new do |term_t, vars|
74
82
  # each of these is =(Atom,variable), and we want Atom => variable
75
83
  t = Term.new term_t
76
- [t.first.atom.to_sym, t.last]
77
- end.to_h
84
+ vars.store t.first.atom.to_sym, (Variable.new t.last.term_t, name: t.first.atom.to_sym)
85
+ end
78
86
 
79
- return args[1], vars_hash
87
+ return args[1], vars
80
88
  end
81
89
 
82
90
  def self.unify( term_a, term_b )
@@ -85,6 +93,7 @@ module Upl
85
93
  end
86
94
 
87
95
  # do a query for the given term and vars, as parsed by term_vars
96
+ # qvars_hash is a hash of :VariableName => Term(PL_VARIABLE)
88
97
  def self.term_vars_query qterm, qvars_hash
89
98
  raise "not a term" unless Term === qterm
90
99
  return enum_for __method__, qterm, qvars_hash unless block_given?
@@ -105,10 +114,9 @@ module Upl
105
114
  break if res == 0
106
115
 
107
116
  hash = qvars_hash.each_with_object Hash.new do |(name_sym,var),ha|
108
- # term_t will be invalidated by the next call to PL_next_solution,
109
- # so we need to construct a ruby tree of the value term
110
- val = ha[name_sym] = var.to_ruby
111
- # binding.pry if val.to_sym == :query_debug_settings rescue false
117
+ # var will be invalidated by the next call to PL_next_solution,
118
+ # so we need to construct a ruby tree copy of the value term.
119
+ ha[name_sym] = var.to_ruby
112
120
  end
113
121
 
114
122
  yield hash
@@ -151,7 +159,7 @@ module Upl
151
159
 
152
160
  def self.query term
153
161
  raise "not a Term" unless Term === term
154
- return enum_for :query_term, term unless block_given?
162
+ return enum_for :query, term unless block_given?
155
163
 
156
164
  answer_lst = TermVector.new term.arity do |idx| term[idx] end
157
165
  query_id_p = Extern.PL_open_query Extern::NULL, 0, term.to_predicate, answer_lst.terms
@@ -56,7 +56,7 @@ module Upl
56
56
  rv == 1 or raise "can't populate term"
57
57
 
58
58
  @arity = int_ptr.ptr.to_i
59
- @atom = Atom.new atom_ptr
59
+ @atom = Atom.new atom_ptr.ptr
60
60
 
61
61
  self
62
62
  end
@@ -84,7 +84,7 @@ module Upl
84
84
  end
85
85
 
86
86
  def to_functor
87
- Extern::PL_new_functor atom.atom_ptr, arity
87
+ Extern::PL_new_functor atom.atom_t, arity
88
88
  end
89
89
 
90
90
  def to_predicate
@@ -94,7 +94,6 @@ module Upl
94
94
  def tree; @tree || (Tree.of_term term_t) end
95
95
  alias to_ruby tree
96
96
 
97
- # TODO leaning hard towards each with Enumerable
98
97
  def each
99
98
  return enum_for :args unless block_given?
100
99
 
@@ -27,18 +27,15 @@ module Upl
27
27
  term_to_ruby term_t
28
28
  end
29
29
 
30
+ def to_ruby; self end
31
+
30
32
  def self.term_to_ruby term_t
31
33
  case term_t.term_type
32
34
  when Extern::PL_VARIABLE
33
35
  Variable.copy term_t
34
36
 
35
37
  when Extern::PL_ATOM
36
- atom = Atom.of_term term_t
37
- if atom.to_s =~ /^ruby-(\d+)/
38
- ObjectSpace._id2ref $1.to_i
39
- else
40
- atom.to_sym
41
- end
38
+ Atom.of_term(term_t).to_ruby
42
39
 
43
40
  # I think integers > 63 bits can be fetched with PL_get_mpz
44
41
  # Other than PL_INTEGER, most of these seem to be unused?
@@ -56,7 +53,7 @@ module Upl
56
53
  when Extern::PL_STRING
57
54
  rv = Extern.PL_get_string term_t, (str_ptr = Fiddle::Pointer[0].ref), (len_ptr = Fiddle::Pointer[0].ref)
58
55
  value_ptr = Fiddle::Pointer.new str_ptr.ptr, len_ptr.ptr.to_i
59
- value_ptr.to_s
56
+ value_ptr.to_s[0,len_ptr.ptr.to_i]
60
57
 
61
58
  when Extern::PL_NIL
62
59
  # TODO maybe this should be [] - see what happens when term_vars has no vars
@@ -67,13 +64,13 @@ module Upl
67
64
  Tree.new term_t
68
65
 
69
66
  when Extern::PL_LIST_PAIR
70
- Inter.each_of_list(term_t).to_a
67
+ Inter.each_of_list(term_t).map{|term_t| Term.new(term_t).to_ruby}
71
68
 
72
69
  when Extern::PL_DICT
73
70
  Dict.of_term term_t
74
71
 
75
72
  else
76
- :NotImplemented
73
+ :"#{term_t.type_string} NotImplemented"
77
74
 
78
75
  end
79
76
  end
@@ -81,7 +78,7 @@ module Upl
81
78
  def arity; args.size end
82
79
 
83
80
  def pretty_print(pp)
84
- unless atom == :','
81
+ unless atom.to_sym == :','
85
82
  pp.text atom.to_s
86
83
  if arity > 0
87
84
  pp.text ?/
@@ -1,13 +1,17 @@
1
1
  module Upl
2
2
  # Really this is just an empty term.
3
3
  class Variable
4
- def initialize term_t = nil
4
+ def initialize term_t = nil, name: nil
5
5
  @term_t = term_t || self.class.to_term
6
+ @name = name
6
7
  end
7
8
 
8
- attr_reader :term_t
9
+ attr_reader :term_t, :name
9
10
  alias to_term_t term_t
10
11
 
12
+ # create a ruby represetation of the term_t
13
+ def to_ruby; Tree.of_term term_t end
14
+
11
15
  def self.copy term_t
12
16
  inst = new term_t
13
17
 
@@ -22,15 +26,21 @@ module Upl
22
26
  Extern.PL_new_term_ref
23
27
  end
24
28
 
29
+ def self.[]( *names )
30
+ vars = names.map{|name| new name: name}
31
+ if vars.size == 1 then vars.first else vars end
32
+ end
33
+
25
34
  def to_s; _string end
26
35
 
27
36
  def _string
28
37
  @_string ||= begin
29
38
  Extern::PL_get_chars \
30
39
  term_t,
31
- (str_ref = Runtime::Ptr[''].ref),
40
+ (str_ref = Runtime::Ptr[0].ref),
32
41
  Extern::Convert::CVT_VARIABLE | Extern::Convert::REP_UTF8 | Extern::Convert::BUF_MALLOC # | Extern::CVT_ALL
33
42
 
43
+ str_ref.ptr.free = Runtime.swipl_free_fn
34
44
  str_ref.ptr.to_s
35
45
  end
36
46
  end
@@ -55,6 +65,10 @@ module Upl
55
65
  if attributed?
56
66
  attribute.pretty_print pp
57
67
  else
68
+ if name
69
+ pp.text name
70
+ pp.text '='
71
+ end
58
72
  pp.text to_s
59
73
  end
60
74
  end
@@ -0,0 +1,42 @@
1
+ module Upl
2
+ # Storage from variables, where setting one just calls unify on the underlying terms.
3
+ # Cos it's hard to hang unify on a single variable.
4
+ class Variables < Hash
5
+ def initialize *names
6
+ super
7
+ names.each do |name|
8
+ self.store name, Variable.new
9
+ end
10
+ end
11
+
12
+ # calls unify, so you can't set a given variable more than once.
13
+ def []=( name, term )
14
+ Extern::PL_unify self[name.to_sym].to_term_t, term.to_term_t
15
+ end
16
+
17
+ def method_missing meth, *args
18
+ name = meth.to_s
19
+
20
+ the_method =
21
+ if name.chomp! '='
22
+ # set the value
23
+ :'[]='
24
+ else
25
+ # fetch the value
26
+ :'[]'
27
+ end
28
+
29
+ var_name = name.to_sym
30
+
31
+ if has_key? var_name
32
+ send the_method, var_name, *args
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def pretty_print pp
39
+ transform_values{|v| v.to_ruby}.pretty_print pp
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,3 @@
1
1
  module Upl
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -0,0 +1,33 @@
1
+ def register_test_predicate
2
+ Upl::Foreign.register_semidet :upl_block do |term_t0, term_t1|
3
+ fterm = (Upl::Tree.of_term term_t0)
4
+ p foreign: fterm
5
+
6
+ if Symbol === fterm then
7
+ Upl::Extern::PL_unify term_t1, :there.to_term_t
8
+ end
9
+ end
10
+
11
+ Array Upl.query "upl_block(hello,A)"
12
+ end
13
+
14
+ =begin
15
+ include UPL
16
+ vars = Variables.new :V
17
+ term = Term :mcall, (o = Object.new), :to_s, vars.V
18
+ def o.to_s; "This is from Ruby, with Love :-D"; end
19
+ Array Runtime.term_vars_query term, vars
20
+ => [{:V=>"This is from Ruby, with Love :-D"}]
21
+
22
+ mcall(+Object, +Method, -Result)
23
+ =end
24
+
25
+ def doit
26
+ fact = Upl::Term.functor :person, :john, :anderson, Object.new
27
+ Upl.assertz fact
28
+ vs = Array Upl.query 'person(A,B,C)'
29
+ p vs
30
+ 1000.times{Array Upl.query 'current_prolog_flag(K,V)'}
31
+ Upl.retract fact
32
+ Upl::Runtime.call Upl::Term :garbage_collect_atoms
33
+ end
@@ -30,9 +30,9 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
32
 
33
- spec.add_development_dependency 'bundler', '~> 1.16'
34
- spec.add_development_dependency 'rake', '~> 10.0'
35
- spec.add_development_dependency 'rspec', '~> 3.0'
33
+ spec.add_development_dependency 'bundler', '>= 1.16'
34
+ spec.add_development_dependency 'rake', '>= 10.0'
35
+ spec.add_development_dependency 'rspec', '>= 3.0'
36
36
  spec.add_dependency 'pry'
37
37
  spec.add_dependency 'fiddle'
38
38
  end
metadata CHANGED
@@ -1,55 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: upl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
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-17 00:00:00.000000000 Z
11
+ date: 2019-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.16'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.16'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '3.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
55
  - !ruby/object:Gem::Dependency
@@ -91,6 +91,7 @@ files:
91
91
  - ".rspec"
92
92
  - ".travis.yml"
93
93
  - Gemfile
94
+ - History.txt
94
95
  - LICENSE.txt
95
96
  - README.md
96
97
  - Rakefile
@@ -100,6 +101,7 @@ files:
100
101
  - lib/upl/atom.rb
101
102
  - lib/upl/dict.rb
102
103
  - lib/upl/extern.rb
104
+ - lib/upl/foreign.rb
103
105
  - lib/upl/functor.rb
104
106
  - lib/upl/inter.rb
105
107
  - lib/upl/runtime.rb
@@ -107,7 +109,9 @@ files:
107
109
  - lib/upl/term_vector.rb
108
110
  - lib/upl/tree.rb
109
111
  - lib/upl/variable.rb
112
+ - lib/upl/variables.rb
110
113
  - lib/upl/version.rb
114
+ - scratch/register_predicate.rb
111
115
  - upl.gemspec
112
116
  homepage: https://github.com/djellemah/upl
113
117
  licenses:
@@ -129,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
133
  - !ruby/object:Gem::Version
130
134
  version: '0'
131
135
  requirements: []
132
- rubyforge_project:
133
- rubygems_version: 2.7.7
136
+ rubygems_version: 3.0.6
134
137
  signing_key:
135
138
  specification_version: 4
136
139
  summary: Access SWI-Prolog engine from ruby