Module: Upl::Runtime

Defined in:
lib/upl/runtime.rb

Defined Under Namespace

Classes: PrologException

Constant Summary collapse

Ptr =

shortcuttery

Fiddle::Pointer

Class Method Summary collapse

Class Method Details

.call(st_or_term) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/upl/runtime.rb', line 37

def self.call st_or_term
  term =
  case st_or_term
  when String
    Term.new st_or_term
  when Term
    st_or_term
  else
    raise "dunno bout #{st_or_term}"
  end

  rv = Extern.PL_call term.term_t, Fiddle::NULL
  rv == 1 # don't raise
end

.initObject



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/upl/runtime.rb', line 52

def self.init
  # set up no output so we don't get swipl command line interfering in ruby
  # TODO exception handling should not kick off a prolog terminal
  # TODO from gem-swipl args = [ @swipl_lib, "-tty", "-q", "-t", "true", "-g", "true", "--nodebug", "--nosignals" ]
  args = %w[upl --tty=false --signals=false --debug=false --quiet=true]

  # convert args to char **
  # TODO Fiddle::SIZEOF_VOIDP would be faster
  ptr_size = Extern.sizeof 'char*'
  arg_ptrs = Ptr.malloc ptr_size * args.size, Extern::ruby_free_fn
  args.each_with_index do |rg,i|
    (arg_ptrs + i*ptr_size)[0,ptr_size] = Ptr[rg].ref
  end

  # call init
  rv = Extern.PL_initialise args.size, arg_ptrs
  rv == 1 or raise 'PL_initialise failed'

  # we really don't want the prolog console showing up in ruby.
  call 'set_prolog_flag(debug_on_error,false)'
end

.open_query(qterm, args, mod: nil, flags: nil, &blk) ⇒ Object

just to make sure the query handle pointer is properly closed TODO should be private, because args are gnarly



125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/upl/runtime.rb', line 125

def self.open_query qterm, args, mod: nil, flags: nil, &blk
  # This will need a string for the module, eventually
  # module is NULL, flags is 0
  mod ||= Fiddle::NULL
  flags ||= flags=Extern::Flags::PL_Q_EXT_STATUS | Extern::Flags::PL_Q_CATCH_EXCEPTION

  query_id_p = Extern.PL_open_query mod, flags, qterm.to_predicate, args.terms
  query_id_p != 0 or raise 'no space on environment stack, see SWI-Prolog docs for PL_open_query'

  yield query_id_p
ensure
  query_id_p&.to_i and Extern.PL_close_query query_id_p
end

.predicate(name, arity) ⇒ Object



80
81
82
# File 'lib/upl/runtime.rb', line 80

def self.predicate name, arity, module_name = 0
  Extern.PL_predicate Ptr[name.to_s], arity, Fiddle::Pointer[module_name]
end

.query(term) ⇒ Object

TODO much duplication between this and .term_vars_query Only used by the term branch of Upl.query



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/upl/runtime.rb', line 220

def self.query term
  raise "not a Term" unless Term === term
  return enum_for :query, term unless block_given?

  answer_lst = TermVector.new term.arity do |idx| term[idx] end

  open_query term, answer_lst do |query_id_p|
    loop do
      case Extern.PL_next_solution query_id_p
      when Extern::ExtStatus::FALSE
        break

      when Extern::ExtStatus::EXCEPTION
        raise_prolog_or_ruby query_id_p

      # when Extern::ExtStatus::TRUE
      # when Extern::ExtStatus::LAST
      else
        yield answer_lst.each_t.map{|term_t| Tree.of_term term_t}

      end
    end
  end
end

.raise_prolog_or_ruby(query_id_p) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/upl/runtime.rb', line 139

def self.raise_prolog_or_ruby query_id_p
  tree = Tree.of_term Extern::PL_exception(query_id_p)

  case tree.atom.to_ruby
  # special case for errors that originated inside a predicate
  # that was defined in ruby.
  when :ruby_error
    # re-raise the actual exception object from the predicate
    raise tree.args.first
  else
    raise PrologException, tree
  end
end

.squery(predicate_str, arity) ⇒ Object

Simple query with predicate / arity Returns an array of arrays. TODO remove, not really used



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/upl/runtime.rb', line 196

def self.squery predicate_str, arity
  return enum_for :squery, predicate_str, arity unless block_given?

  # bit of a hack because open_query wants to call to_predicate
  # and we have to construct that manually here because Upl::Term
  # is slightly ill-suited.
  qterm = Object.new
  qterm.define_singleton_method :to_predicate do
    p_functor = Extern::PL_new_functor predicate_str.to_sym.to_atom, arity
    Extern::PL_pred p_functor, Fiddle::NULL
  end

  args = TermVector.new arity
  open_query qterm, args do |query_id_p|
    loop do
      rv = Extern.PL_next_solution query_id_p
      break if rv == 0
      yield args.each_t.map{|term_t| Tree.of_term term_t}
    end
  end
end

.term_vars(st) ⇒ Object

Use prolog predicate to parse the string into a term (containing variables), along with its named variables as a hash of Name => _variable

TODO need to use read_term_from_atom(‘pred(A,B,C)’, Term, [variable_names(VarNames)]). remember Atom can also be a string for swipl



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/upl/runtime.rb', line 105

def self.term_vars st
  rv = Extern::PL_call_predicate \
    Fiddle::NULL, # module
    0, # flags, see PL_open_query
    (predicate 'atom_to_term', 3),
    # 3 variables, first one determined
    (args = TermVector[st.to_sym, nil, nil]).terms

  vars = Inter.each_of_list(args[2]).each_with_object Variables.new do |term_t, vars|
    # each of these is =(Atom,variable), and we want Atom => variable
    t = Term.new term_t
    vars.store t.first.atom.to_sym, (Variable.new t.last.term_t, name: t.first.atom.to_sym)
  end

  # return term, {name => var...}
  return args[1], vars
end

.term_vars_query(qterm, qvars_hash) ⇒ Object

do a query for the given term and vars, as parsed by term_vars qvars_hash is a hash of :VariableName => Term(PL_VARIABLE) and each variable is already bound in term. TODO much duplication between this and .query below



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/upl/runtime.rb', line 157

def self.term_vars_query qterm, qvars_hash
  raise "not a term" unless Term === qterm
  return enum_for __method__,  qterm, qvars_hash unless block_given?

  with_frame do |fid_t|
    # populate input values from qterm
    args = TermVector.new qterm.arity do |idx| qterm[idx] end
    open_query qterm, args do |query_id_p|
      loop do
        case Extern.PL_next_solution query_id_p
        when Extern::ExtStatus::FALSE
          break

        when Extern::ExtStatus::EXCEPTION
          raise_prolog_or_ruby query_id_p

        # when Extern::ExtStatus::TRUE
        # when Extern::ExtStatus::LAST
        else
          hash = qvars_hash.each_with_object Hash.new do |(name_sym,var),ha|
            # var will be invalidated by the next call to PL_next_solution,
            # so we need to construct a ruby tree copy of the value term.
            ha[name_sym] = var.to_ruby
          end

          yield hash
        end
      end
    end
  end
end

.unify(term_a, term_b) ⇒ Object



84
85
86
87
# File 'lib/upl/runtime.rb', line 84

def self.unify( term_a, term_b )
  rv = Extern::PL_unify term_a.term_t, term_a.term_t
  rv == 1 or raise "can't unify #{term_a} and #{term_b}"
end

.with_frame(&blk) ⇒ Object

blk takes a fid_t



90
91
92
93
94
95
96
97
98
# File 'lib/upl/runtime.rb', line 90

def self.with_frame &blk
  fid_t = Extern.PL_open_foreign_frame
  yield fid_t
ensure
  # discards term references, but keeps bindings
  # fid_t and Extern.PL_close_foreign_frame fid_t
  # same as close and also undo bindings
  fid_t and Extern.PL_discard_foreign_frame fid_t
end