アットウィキロゴ
「Ruby/Hom2」の編集履歴(バックアップ)一覧に戻る

Ruby/Hom2 - (2011/06/06 (月) 18:40:25) のソース

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

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

Rubyソース … http://www42.atwiki.jp/memset?cmd=upload&act=open&pageid=96&file=grahom.rb

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

#codehighlight(ruby){{{
#
# 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
}}}