ruby/test/racc/assets/intp.y

547 строки
11 KiB
Plaintext

#
# intp
#
class Intp::Parser
prechigh
nonassoc UMINUS
left '*' '/'
left '+' '-'
nonassoc EQ
preclow
rule
program : stmt_list
{
result = RootNode.new( val[0] )
}
stmt_list :
{
result = []
}
| stmt_list stmt EOL
{
result.push val[1]
}
| stmt_list EOL
stmt : expr
| assign
| IDENT realprim
{
result = FuncallNode.new( @fname, val[0][0],
val[0][1], [val[1]] )
}
| if_stmt
| while_stmt
| defun
if_stmt : IF stmt THEN EOL stmt_list else_stmt END
{
result = IfNode.new( @fname, val[0][0],
val[1], val[4], val[5] )
}
else_stmt : ELSE EOL stmt_list
{
result = val[2]
}
|
{
result = nil
}
while_stmt: WHILE stmt DO EOL stmt_list END
{
result = WhileNode.new(@fname, val[0][0],
val[1], val[4])
}
defun : DEF IDENT param EOL stmt_list END
{
result = DefNode.new(@fname, val[0][0], val[1][1],
Function.new(@fname, val[0][0], val[2], val[4]))
}
param : '(' name_list ')'
{
result = val[1]
}
| '(' ')'
{
result = []
}
|
{
result = []
}
name_list : IDENT
{
result = [ val[0][1] ]
}
| name_list ',' IDENT
{
result.push val[2][1]
}
assign : IDENT '=' expr
{
result = AssignNode.new(@fname, val[0][0], val[0][1], val[2])
}
expr : expr '+' expr
{
result = FuncallNode.new(@fname, val[0].lineno, '+', [val[0], val[2]])
}
| expr '-' expr
{
result = FuncallNode.new(@fname, val[0].lineno, '-', [val[0], val[2]])
}
| expr '*' expr
{
result = FuncallNode.new(@fname, val[0].lineno, '*', [val[0], val[2]])
}
| expr '/' expr
{
result = FuncallNode.new(@fname, val[0].lineno,
'/', [val[0], val[2]])
}
| expr EQ expr
{
result = FuncallNode.new(@fname, val[0].lineno, '==', [val[0], val[2]])
}
| primary
primary : realprim
| '(' expr ')'
{
result = val[1]
}
| '-' expr =UMINUS
{
result = FuncallNode.new(@fname, val[0][0], '-@', [val[1]])
}
realprim : IDENT
{
result = VarRefNode.new(@fname, val[0][0],
val[0][1])
}
| NUMBER
{
result = LiteralNode.new(@fname, *val[0])
}
| STRING
{
result = StringNode.new(@fname, *val[0])
}
| TRUE
{
result = LiteralNode.new(@fname, *val[0])
}
| FALSE
{
result = LiteralNode.new(@fname, *val[0])
}
| NIL
{
result = LiteralNode.new(@fname, *val[0])
}
| funcall
funcall : IDENT '(' args ')'
{
result = FuncallNode.new(@fname, val[0][0], val[0][1], val[2])
}
| IDENT '(' ')'
{
result = FuncallNode.new(@fname, val[0][0], val[0][1], [])
}
args : expr
{
result = val
}
| args ',' expr
{
result.push val[2]
}
end
---- header
#
# intp/parser.rb
#
---- inner
def initialize
@scope = {}
end
RESERVED = {
'if' => :IF,
'else' => :ELSE,
'while' => :WHILE,
'then' => :THEN,
'do' => :DO,
'def' => :DEF,
'true' => :TRUE,
'false' => :FALSE,
'nil' => :NIL,
'end' => :END
}
RESERVED_V = {
'true' => true,
'false' => false,
'nil' => nil
}
def parse(f, fname)
@q = []
@fname = fname
lineno = 1
f.each do |line|
line.strip!
until line.empty?
case line
when /\A\s+/, /\A\#.*/
;
when /\A[a-zA-Z_]\w*/
word = $&
@q.push [(RESERVED[word] || :IDENT),
[lineno, RESERVED_V.key?(word) ? RESERVED_V[word] : word.intern]]
when /\A\d+/
@q.push [:NUMBER, [lineno, $&.to_i]]
when /\A"(?:[^"\\]+|\\.)*"/, /\A'(?:[^'\\]+|\\.)*'/
@q.push [:STRING, [lineno, eval($&)]]
when /\A==/
@q.push [:EQ, [lineno, '==']]
when /\A./
@q.push [$&, [lineno, $&]]
else
raise RuntimeError, 'must not happen'
end
line = $'
end
@q.push [:EOL, [lineno, nil]]
lineno += 1
end
@q.push [false, '$']
do_parse
end
def next_token
@q.shift
end
def on_error(t, v, values)
if v
line = v[0]
v = v[1]
else
line = 'last'
end
raise Racc::ParseError, "#{@fname}:#{line}: syntax error on #{v.inspect}"
end
---- footer
# intp/node.rb
module Intp
class IntpError < StandardError; end
class IntpArgumentError < IntpError; end
class Core
def initialize
@ftab = {}
@obj = Object.new
@stack = []
@stack.push Frame.new '(toplevel)'
end
def frame
@stack[-1]
end
def define_function(fname, node)
raise IntpError, "function #{fname} defined twice" if @ftab.key?(fname)
@ftab[fname] = node
end
def call_function_or(fname, args)
call_intp_function_or(fname, args) {
call_ruby_toplevel_or(fname, args) {
yield
}
}
end
def call_intp_function_or(fname, args)
if func = @ftab[fname]
frame = Frame.new(fname)
@stack.push frame
func.call self, frame, args
@stack.pop
else
yield
end
end
def call_ruby_toplevel_or(fname, args)
if @obj.respond_to? fname, true
@obj.send fname, *args
else
yield
end
end
end
class Frame
def initialize(fname)
@fname = fname
@lvars = {}
end
attr :fname
def lvar?(name)
@lvars.key? name
end
def [](key)
@lvars[key]
end
def []=(key, val)
@lvars[key] = val
end
end
class Node
def initialize(fname, lineno)
@filename = fname
@lineno = lineno
end
attr_reader :filename
attr_reader :lineno
def exec_list(intp, nodes)
v = nil
nodes.each {|i| v = i.evaluate(intp) }
v
end
def intp_error!(msg)
raise IntpError, "in #{filename}:#{lineno}: #{msg}"
end
def inspect
"#{self.class.name}/#{lineno}"
end
end
class RootNode < Node
def initialize(tree)
super nil, nil
@tree = tree
end
def evaluate
exec_list Core.new, @tree
end
end
class DefNode < Node
def initialize(file, lineno, fname, func)
super file, lineno
@funcname = fname
@funcobj = func
end
def evaluate(intp)
intp.define_function @funcname, @funcobj
end
end
class FuncallNode < Node
def initialize(file, lineno, func, args)
super file, lineno
@funcname = func
@args = args
end
def evaluate(intp)
args = @args.map {|i| i.evaluate intp }
begin
intp.call_intp_function_or(@funcname, args) {
if args.empty? or not args[0].respond_to?(@funcname)
intp.call_ruby_toplevel_or(@funcname, args) {
intp_error! "undefined function #{@funcname.id2name}"
}
else
recv = args.shift
recv.send @funcname, *args
end
}
rescue IntpArgumentError, ArgumentError
intp_error! $!.message
end
end
end
class Function < Node
def initialize(file, lineno, params, body)
super file, lineno
@params = params
@body = body
end
def call(intp, frame, args)
unless args.size == @params.size
raise IntpArgumentError,
"wrong # of arg for #{frame.fname}() (#{args.size} for #{@params.size})"
end
args.each_with_index do |v,i|
frame[@params[i]] = v
end
exec_list intp, @body
end
end
class IfNode < Node
def initialize(fname, lineno, cond, tstmt, fstmt)
super fname, lineno
@condition = cond
@tstmt = tstmt
@fstmt = fstmt
end
def evaluate(intp)
if @condition.evaluate(intp)
exec_list intp, @tstmt
else
exec_list intp, @fstmt if @fstmt
end
end
end
class WhileNode < Node
def initialize(fname, lineno, cond, body)
super fname, lineno
@condition = cond
@body = body
end
def evaluate(intp)
while @condition.evaluate(intp)
exec_list intp, @body
end
end
end
class AssignNode < Node
def initialize(fname, lineno, vname, val)
super fname, lineno
@vname = vname
@val = val
end
def evaluate(intp)
intp.frame[@vname] = @val.evaluate(intp)
end
end
class VarRefNode < Node
def initialize(fname, lineno, vname)
super fname, lineno
@vname = vname
end
def evaluate(intp)
if intp.frame.lvar?(@vname)
intp.frame[@vname]
else
intp.call_function_or(@vname, []) {
intp_error! "unknown method or local variable #{@vname.id2name}"
}
end
end
end
class StringNode < Node
def initialize(fname, lineno, str)
super fname, lineno
@val = str
end
def evaluate(intp)
@val.dup
end
end
class LiteralNode < Node
def initialize(fname, lineno, val)
super fname, lineno
@val = val
end
def evaluate(intp)
@val
end
end
end # module Intp
begin
tree = nil
fname = 'src.intp'
File.open(fname) {|f|
tree = Intp::Parser.new.parse(f, fname)
}
tree.evaluate
rescue Racc::ParseError, Intp::IntpError, Errno::ENOENT
raise ####
$stderr.puts "#{File.basename $0}: #{$!}"
exit 1
end