オリジナルの仕様だと「関数定義の箇所で必ず改行しないといけない(逆に他の箇所では改行できない)」「文字数が凄く多くなる」という難点があったので、文字種を増やして少し文法を改変してみた。
書いたはいいけど、どう処分すればいいのかわからない・・・。
#
# 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