Module: Upl::Foreign

Defined in:
lib/upl/foreign.rb

Overview

Register a foreign predicate in prolog

see https://www.swi-prolog.org/pldoc/man?section=modes

for meanings of det, semidet, nondet

Class Method Summary collapse

Class Method Details

.predicatesObject



7
8
9
# File 'lib/upl/foreign.rb', line 7

def self.predicates
  @predicates ||= Hash.new
end

.register_semidet(name, arity = nil, mod: nil, &blk) ⇒ Object

name is the predicate name as called prolog mod is the prolog module name arity will be figured out from the blk unless you specify it blk will be called with Term and Tree instances, depending on ground-ness of the term on the prolog end of the call



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/upl/foreign.rb', line 16

def self.register_semidet name, arity = nil, mod: nil, &blk
  arity ||= blk.arity
  arg_types = arity.times.map{Fiddle::TYPE_VOIDP}
  ruby_pred = Fiddle::Closure::BlockCaller.new Fiddle::TYPE_INT, arg_types do |*args|
    # convert args to Upl::Tree or Upl::Variable instances
    ruby_args = args.map do |term_t|
      case term_t.term_type
      when Extern::PL_VARIABLE
        # TODO how to make sure this variable does not outlive the swipl frame?
        Upl::Variable.new term_t
      else
        Upl::Tree.of_term term_t
      end
    end

    # now do the call and convert to swipl
    case (rv = blk.call *ruby_args)
    when true;            Upl::Extern::TRUE
    when false, NilClass; Upl::Extern::FALSE

    # Upl::Extern::(TRUE, FALSE)
    when 0, 1;            rv
    else                  Upl::Extern::TRUE
    end

  # yes, really catch all exceptions because this is the language boundary
  rescue Exception => ex
    # pass the actual exception object through prolog
    # ultimately gets handled by Runtime.query and friends.
    term = Upl::Term :ruby_error, ex
    Extern::PL_raise_exception term.to_term_t
  end

  mod_ptr = Fiddle::Pointer[mod&.to_s || 0]

  fn = Fiddle::Function.new ruby_pred, arg_types, Fiddle::TYPE_INT

  # int PL_register_foreign_in_module(char *mod, char *name, int arity, foreign_t (*f)(), int flags, ...)
  # flags == 0 for semidet, ie only one 'output' value and no need for retry
  # https://www.swi-prolog.org/pldoc/doc_for?object=c(%27PL_register_foreign_in_module%27)
  rv = Upl::Extern.PL_register_foreign_in_module \
    mod_ptr,
    name.to_s,
    arity,
    fn,
    (flags=0)

  rv == 1 or raise "can't register ruby predicate #{name}/#{arity}"

  # NOTE you have to keep ruby_pred and fn around somewhere, otherwise they
  # get garbage collected, and then the callback segfaults.
  predicates[[name,arity].join('/').to_sym] = ruby_pred, fn
end