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 +4 -4
- data/Gemfile +3 -0
- data/History.txt +3 -0
- data/README.md +32 -20
- data/lib/upl.rb +19 -13
- data/lib/upl/atom.rb +51 -12
- data/lib/upl/extern.rb +21 -0
- data/lib/upl/foreign.rb +55 -0
- data/lib/upl/inter.rb +93 -6
- data/lib/upl/runtime.rb +20 -12
- data/lib/upl/term.rb +2 -3
- data/lib/upl/tree.rb +7 -10
- data/lib/upl/variable.rb +17 -3
- data/lib/upl/variables.rb +42 -0
- data/lib/upl/version.rb +1 -1
- data/scratch/register_predicate.rb +33 -0
- data/upl.gemspec +3 -3
- metadata +13 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74895ae80e02af92445e7d4fb47413dbb67b0996ced75014b250c18bba5bb444
|
4
|
+
data.tar.gz: 7288b2b9b91bb4a7127ff05341ccb053d5a159e7dcdd70537d4d6a21cfc186bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 516ebd68a80b61d75d12d1ca91833409cce8bc0d84d3d44346e07fc7b444c70f3d9d11eaca42cdbe39ff78f28e06fcc15af53c0659fa29ece17e316364e23706
|
7
|
+
data.tar.gz: ded90962d4080696c29e87cf83ec3a429e8feca7b693b01ae2b2b2596fa7980a6ea0d44af876c014fff8b8f371bf27b3810dd8f2515bf5f157ccc9efb4e7cf5a
|
data/Gemfile
CHANGED
data/History.txt
ADDED
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
|
-
[
|
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
|
-
[
|
54
|
+
[1] pry(main)> fact = Upl::Term.functor :person, :john, :anderson
|
55
55
|
=> person/2(john,anderson)
|
56
|
-
[
|
56
|
+
[2] pry(main)> Upl.assertz fact
|
57
57
|
=> true
|
58
|
-
[
|
58
|
+
[3] pry(main)> Array Upl.query 'person(A,B)'
|
59
59
|
=> [{:A=>john, :B=>anderson}]
|
60
|
-
[
|
60
|
+
[4] pry(main)> Upl.retract fact
|
61
61
|
=> true
|
62
|
-
[
|
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
|
-
[
|
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
|
-
[
|
74
|
+
[2] pry(main)> Upl.assertz fact
|
75
75
|
=> true
|
76
|
-
[
|
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
|
-
[
|
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 =
|
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,
|
98
|
-
Array Upl::Runtime.term_vars_query query_term,
|
99
|
-
=>[
|
100
|
-
|
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
|
103
|
-
query_term,
|
104
|
-
|
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,
|
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
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
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
|
38
|
+
module_function def asserta term
|
33
39
|
Runtime.call Term.functor :asserta, term
|
34
40
|
end
|
35
41
|
|
36
|
-
def
|
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
|
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
|
66
|
+
module_function def Term name, *args
|
61
67
|
Term.functor name, *args
|
62
68
|
end
|
63
69
|
end
|
data/lib/upl/atom.rb
CHANGED
@@ -1,35 +1,74 @@
|
|
1
1
|
module Upl
|
2
2
|
class Atom
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
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, (
|
12
|
-
raise "can't get atom from term"
|
13
|
-
new
|
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
|
-
|
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
|
-
|
56
|
+
atom_t == rhs.atom_t
|
20
57
|
end
|
21
58
|
|
22
59
|
def to_sym
|
23
|
-
@
|
60
|
+
@chars.to_sym
|
24
61
|
end
|
25
62
|
|
26
63
|
def to_s
|
27
|
-
@
|
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
|
data/lib/upl/extern.rb
CHANGED
@@ -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
|
data/lib/upl/foreign.rb
ADDED
@@ -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
|
data/lib/upl/inter.rb
CHANGED
@@ -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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
data/lib/upl/runtime.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
-
|
77
|
-
end
|
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],
|
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
|
-
#
|
109
|
-
# so we need to construct a ruby tree of the value term
|
110
|
-
|
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 :
|
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
|
data/lib/upl/term.rb
CHANGED
@@ -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.
|
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
|
|
data/lib/upl/tree.rb
CHANGED
@@ -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
|
-
|
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).
|
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 ?/
|
data/lib/upl/variable.rb
CHANGED
@@ -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[
|
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
|
data/lib/upl/version.rb
CHANGED
@@ -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
|
data/upl.gemspec
CHANGED
@@ -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', '
|
34
|
-
spec.add_development_dependency 'rake', '
|
35
|
-
spec.add_development_dependency 'rspec', '
|
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.
|
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:
|
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
|
-
|
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
|