LoginSignup
6
6

More than 5 years have passed since last update.

CoffeeScriptでProlog的なことをやってみた

Posted at

テスト前日なので、

Rubyで作るProlog処理系 (1/2):CodeZine - http://codezine.jp/article/detail/461

を見てPrologインタープリタ的なのを作って、FizzBuzzしてみた。

prolog.coffee
isSymbol = (t)-> t is t.toUpperCase?()

class Pred
  constructor: (@name) ->
    @defs = []
  is: (args...) ->
    new Goal(@, args)

pred = (x) -> new Pred(x)

class Goal
  constructor: (@pred, @args) ->
  si: (rhs...) ->
    @pred.defs.push([@, list(rhs...)])
  calls: (callback) ->
    @pred.defs.push([@, callback])

class Cons
  constructor: (car, cdr) ->
    [@[0], @[1]] = [car, cdr]

cons = (car, cdr) -> new Cons car, cdr

list = (xs...) -> xs.reduceRight (ls, x) ->
  cons x, ls
, null

class Env
  constructor: ->
    @table = {}
  put: (x, pair) ->
    @table[x] = pair
  get: (x) -> @table[x]
  del: (x) -> delete @table[x]
  clear: ->
    @table = {}
  dereference: (t) ->
    env = @
    while isSymbol t
      p = env.get t
      break unless p
      [t, env] = p
    [t, env]
  item: (t) ->
    [t, env] = @dereference t
    return switch
      when t instanceof Goal then new Goal t.pred, env.item(t.args)
      when t instanceof Cons then cons env.item(t[0], t[1])
      when t instanceof Array then t.map (e) -> env.item(e)
      else t

_unify = (x, xEnv, y, yEnv, trail, tmpEnv) ->
  loop
    if isSymbol x
      xp = xEnv.get x
      unless xp
        [y, yEnv] = yEnv.dereference y
        unless x is y and xEnv is yEnv
          xEnv.put x, [y, yEnv]
          trail.push([x, xEnv]) unless xEnv is tmpEnv
        return true
      else
        [x, xEnv] = xp
        [x, xEnv] = xEnv.dereference x
    else if isSymbol y
      [x, xEnv, y, yEnv] = [y, yEnv, x, xEnv]
    else
      break
  if x instanceof Goal and y instanceof Goal
    return false unless x.pred is y.pred
    [x, y] = [x.args, y.args]
  if x instanceof Array and y instanceof Array
    return false unless x.length is y.length
    for i in [0 ... x.length]
      return false unless _unify(x[i], xEnv, y[i], yEnv, trail, tmpEnv)
    return true
  else
    return x is y

resolve = (goals...) ->
  cb = goals.pop()
  env = new Env
  _resolveBody(list(goals...), env, [false], -> cb env)

_resolveBody = (body, env, cut, cb) ->
  if body is null then cb()
  else
    [goal, rest] = body
    if goal is 'CUT'
      _resolveBody(rest, env, cut, cb)
      cut[0] = true
    else
      dEnv = new Env
      dCut = [false]
      for [dHead, dBody] in goal.pred.defs
        break if dCut[0] or cut[0]
        trail = []
        if _unify(goal, env, dHead, dEnv, trail, dEnv)
          if typeof dBody is 'function'
            if dBody(new CallbackEnv(dEnv, trail))
              _resolveBody(rest, env, cut, cb)
          else
            _resolveBody(dBody, dEnv, dCut, ->
              _resolveBody(rest, env, cut, cb)
              dCut[0] ||= cut[0]
            )
        for [x, xEnv] in trail
          xEnv.del(x)

class CallbackEnv
  constructor: (@env, @trail) ->
  item: (t) -> @env.item(t)
  unify: (t, u) -> _unify(t, @env, u, @env, @trail, @env)

fizzbuzz = pred 'fizzbuzz'
assign = pred 'assign'
show = pred 'show'
eq = pred 'eq'
add = pred 'add'
mod = pred 'mod'
divable = pred 'divable'
print = pred 'print'

runFizzbuzz = pred 'runFizzbuzz'
runFizzbuzz2 = pred 'runFizzbuzz2'

assign.is('A', 'B').calls (env) ->
  env.unify('A', 'B')

show.is('A', 'B').calls (env) ->
  env.unify('A', env.item('B').toString?())

eq.is('A', 'B').calls (env) ->
  env.item('A') is env.item('B')

mod.is('A', 'B', 'X').calls (env) ->
  env.unify('X', env.item('A') % env.item('B'))

add.is('A', 'B', 'X').calls (env) ->
  env.unify('X', env.item('A') + env.item('B'))

print.is('X').calls (env) ->
  console.log env.item('X')
  true

divable.is('A', 'B').si mod.is('A', 'B', 'X'), eq.is('X', 0)

fizzbuzz.is('I', 'fizzbuzzhoge').si divable.is('I', 3), divable.is('I', 5), divable.is('I', 8), 'CUT'
fizzbuzz.is('I', 'buzzhoge').si divable.is('I', 5), divable.is('I', 8), 'CUT'
fizzbuzz.is('I', 'fizzhoge').si divable.is('I', 3), divable.is('I', 8), 'CUT'
fizzbuzz.is('I', 'fizzbuzz').si divable.is('I', 3), divable.is('I', 5), 'CUT'
fizzbuzz.is('I', 'hoge').si divable.is('I', 8), 'CUT'
fizzbuzz.is('I', 'fizz').si divable.is('I', 3), 'CUT'
fizzbuzz.is('I', 'buzz').si divable.is('I', 5), 'CUT'
fizzbuzz.is('I', 'I').si()

runFizzbuzz.is(101).si 'CUT'
runFizzbuzz.is('I').si fizzbuzz.is('I', 'S'), print.is('S'), add.is('I', 1, 'I1'), runFizzbuzz.is('I1')

runFizzbuzz2.is(200, '_').si 'CUT'
runFizzbuzz2.is('I', 'X').si fizzbuzz.is('I', 'X')
runFizzbuzz2.is('I', 'X').si add.is('I', 1, 'I1'), runFizzbuzz2.is('I1', 'X')

resolve runFizzbuzz2.is(1, 'X'), (env) ->
  console.log env.item('X')

特に解説等はしません。
なぜならテスト前日だから。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6