module Upl::Runtime

Constants

Ptr

TODO move this to inter, or maybe a refinement

Public Class Methods

call(st_or_term) click to toggle source
# 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
init() click to toggle source
# 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, mod: nil, flags: nil) { |query_id_p| ... } click to toggle source

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

# File lib/upl/runtime.rb, line 122
def self.open_query qterm, 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
  args = TermVector.new qterm.arity do |idx| qterm[idx] end

  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, module_name = 0) click to toggle source
# File lib/upl/runtime.rb, line 77
def self.predicate name, arity, module_name = 0
  Extern.PL_predicate Ptr[name.to_s], arity, Fiddle::Pointer[module_name]
end
query(qterm, qvars_hash = nil) { |result_map[]| ... } click to toggle source

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 qterm. TODO much duplication between this and .query below

# File lib/upl/runtime.rb, line 155
def self.query qterm, qvars_hash = nil
  raise "not a term" unless Term === qterm
  return enum_for __method__,  qterm, qvars_hash unless block_given?

  result_map =
  if qvars_hash
    lambda do
      # construct map of given variable names to their values
      qvars_hash.each_with_object Hash.new do |(name_sym,var),ha|
        ha[name_sym] = var.to_ruby
      end
    end
  else
    # no variable names provided so just get the values
    ->{ qterm.map{|term_t| Tree.of_term term_t} }
  end

  open_query qterm do |query_id_p|
    loop do
      case (status = 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, Extern::ExtStatus::LAST
        # 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 immediately.
        yield result_map[]

      else
        raise "unknown PL_next_solution status #{status}"
      end
    end
  end
end
raise_prolog_or_ruby(query_id_p) click to toggle source
# File lib/upl/runtime.rb, line 137
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
term_vars(st) click to toggle source

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

# File lib/upl/runtime.rb, line 102
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
unify( term_a, term_b ) click to toggle source
# File lib/upl/runtime.rb, line 81
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() { |fid_t| ... } click to toggle source

blk takes a fid_t

# File lib/upl/runtime.rb, line 87
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