mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
546 lines
11 KiB
Text
546 lines
11 KiB
Text
#
|
|
# 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
|