【元ネタ】
プログラミング言語「ほむほむ」 - ゆろよろ日記
http://d.hatena.ne.jp/yuroyoro/20110601/1306908421

オリジナルの仕様だと「関数定義の箇所で必ず改行しないといけない(逆に他の箇所では改行できない)」「文字数が凄く多くなる」という難点があったので、文字種を増やして少し文法を改変してみた。


書いたはいいけど、どう処分すればいいのかわからない・・・。

#
# grahom.rb -- "w" の代わりに "ほむ" を使うGrassインタープリタ
#
 
#*
# 改行は無視する. 区切りには "改行以外の" 空白類を用いること.
#
# HOM(n) = "ほむ"×n
# HOM@(n) = "ほむ"×nの後ろに "っ" or "ー" を付けたもの
# EXT = "ぅ" or "!" or "?"
#
# HOM(m) HOM(n) -> App(m,n)     (Grassでいう WWW...Wwwww....w)
# HOM@(n) ... -> Abs(n, ...)    (Grassでいう www...w ...)
# EXT -> 関数定義の終了         (Grassでいう v )
#
# 例.
#  ほむほむほむ ほむ → WWWw
#  ほむっ ほむほむ ほむほむほむほむ → wWWwwww
#
# ただし, "っ" or "ー" によって連結された "ほむ"×nは
# 各項の長さの積と同じ数の "ほむ" として扱う.
#
# 例.
#  ほむほむっほむほむほむー = ほむほむほむほむほむほむー
#*
 
 
#1. Lexer
 
class Token
  attr_reader :value, :token_type
 
  class Hom < Token
    def initialize(line)
      @token_type = (line !~ /む$/) ? 'A' : 'B'
      @value = line.split(/[っー]/).inject(1){ |v, hm| v * hm.length / 'ほむ'.length }
    end
  end
 
  class Ext < Token; end
end
 
class Lexer
  HOM_PAT = /^\s*(((ほむ)+[っー]?)+)/
  EXT_PAT = /^\s*[ぅ!?]+/
 
  def initialize(line)
    @line = line.gsub(/([^ほむっーぅ!?\s]|\n)/, '')
    begin nexttoken end until (empty? || @current.token_type == 'A')
    raise "Error: 関数定義が存在しません" if empty?
  end
  attr_reader :current
 
  def empty?
    @current.token_type.nil? && @line.empty?
  end
 
  def skip(type)
    raise "Error: Appの文法に間違いがあります" unless @current.token_type == type
    ret = @current; nexttoken; ret
  end
 
  def nexttoken
    if @line.slice!(HOM_PAT)
      @current = Token::Hom.new($1)
    elsif @line.empty? || @line.slice!(EXT_PAT)
      @current = Token::Ext.new
    else
      raise "Error: 構文エラー"
    end
  end
end
 
 
#2. Instruction -- eval(runtime) を持つ
 
class App
  def initialize(m, n)
    @m, @n = m, n
  end
 
  def eval(runtime)
    op1, op2 = runtime.env[-@m], runtime.env[-@n]
    raise "Error: インデックスエラー (at #{inspect})" unless op1 && op2
    op1.apply(runtime, op2)
  end
 
  def inspect
    "App(#{@m},#{@n})"
  end
end
 
class Abs
  def initialize(code, arity = 1)
    @code = (arity == 1) ? code : [Abs.new(code, arity-1)]
  end
 
  def eval(runtime)
    runtime.env << CE.new(@code, runtime.env.dup)
  end
 
  def inspect
    "Abs(#{@code.inspect})"
  end
end
 
 
#3. Operator -- apply(runtime, arg) を持つ
 
class Op
  def inspect
    self.class.name.slice!(/[^:]+$/)
  end
 
  class In < Op
    def apply(runtime, arg)
      runtime.env << (ch = $stdin.getc) ? CHARS[ch] : arg
    end
  end
 
  class Out < Op
    def apply(runtime, arg)
      $stdout.putc(arg.char_code.chr)
      $stdout.flush
      runtime.env << arg
    end
  end
 
  class Succ < Op
    def apply(runtime, arg)
      runtime.env << CHARS[(arg.char_code + 1) & 255]
    end
  end
 
  class Char < Op
    def initialize(ch)
      @char_code = ch
    end
    attr_reader :char_code
 
    def apply(runtime, arg)
      runtime.env << (arg == self) ? CHURCH_TRUE : CHURCH_FALSE
    end
 
    def inspect
      "\"#{@char_code.chr}\""
    end
  end
 
  CHARS = (0..255).map{ |ch| Char.new(ch) }
end
 
class CE
  def initialize(c, e)
    @code, @env = c, e
  end
  attr_reader :code, :env
 
  def apply(runtime, arg)
    runtime.save_to_dump
    runtime.code = @code.dup
    runtime.env = (@env.dup << arg)
  end
 
  def inspect
    [@code, "ENV(#{@env.length})"].inspect
  end
end
 
CHURCH_TRUE = CE.new( [Abs.new( [App.new(3,2)] )], [CE.new([], [])] )
 
CHURCH_FALSE = CE.new( [Abs.new([])], [] )
 
 
#4. Runtime
 
class Runtime
  def initialize(c)
    @code = c
    @env = [Op::In.new, Op::CHARS[?w], Op::Succ.new, Op::Out.new]
    @dump = [ CE.new([], []), CE.new([App.new(1, 1)], []) ]
  end
  attr_accessor :code, :env
 
  def run
    while true
      if @code.size > 0
        @code.shift.eval(self)
      else
        break if @dump.empty?
        ret = @env[-1]
        ce = @dump.pop
        @code, @env = ce.code, (ce.env << ret)
      end
    end
    return @env[-1]
  end
 
  def save_to_dump
    @dump << CE.new(@code, @env.dup)
  end
end
 
 
#5. Main
 
def parse_abs(lex, arity)
  body = []
  while lex.current.token_type == 'B'
    body << App.new(lex.skip('B').value, lex.skip('B').value)
  end
  return Abs.new(body, arity)
end
 
def parse(lex, dest)
  until lex.empty?
    case lex.current.token_type
      when 'A' then dest << parse_abs(lex, lex.skip('A').value)
      when 'B' then dest << App.new(lex.skip('B').value, lex.skip('B').value)
      else lex.nexttoken
    end
  end
end
 
code = []
parse(Lexer.new($<.read), code)
r = Runtime.new(code).run
 
print "\nresult: " + r.inspect
 
最終更新:2011年06月06日 18:40
添付ファイル