upl 0.0.2 → 0.0.3

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