『Rubyを256倍使うための本 無道編』の 13.defun/intp.y を移植

関数定義可能。変数スコープあり。コードを理解できていないので、意味(動作)から推測(IntpFrame クラスはなし)

class Node:
    def exec_list(self, nodes):
        for x in nodes:
            x.evaluate()

class RootNode(Node):
    def __init__(self, tree):
        self.tree = tree
    def evaluate(self):
        self.exec_list(self.tree)

class DefNode(Node):
    def __init__(self, ftab, funcname, funcobj):
        self.ftab     = ftab
        self.funcname = funcname
        self.funcobj  = funcobj
    def evaluate(self):
        self.ftab[self.funcname] = self.funcobj

class FuncallNode(Node):
    def __init__(self, ftab, func, args):
        self.ftab = ftab
        self.func = func
        self.args = args
    def evaluate(self):
        args = map(lambda x: x.evaluate(), self.args)
        
        if self.ftab.has_key(self.func):
            self.ftab[self.func].call(args)
        elif self.func in ["+", "-", "*", "/"]:
            cmd = "%s%s%s" % (args[0], self.func, args[1])
            return eval(cmd)
        else:
            import string
            cmd_args     = string.join([repr(x) for x in args], ",")
            cmd          = self.func + "(" + cmd_args + ")"
            cmd_no_paren = self.func + " " + cmd_args
            
            try:
                return eval(cmd)
            except SyntaxError:
                exec(cmd_no_paren)

class Function(Node):
    def __init__(self, vtable, fname, params, body):
        self.vtable = vtable
        self.fname  = fname
        self.params = params
        self.body   = body
    def call(self, args):
        size1 = len(args)
        size2 = len(self.params)
        if size1 != size2:
            print "wrong # of arg for %s() (%d for %d)" % (self.fname, size1, size2)
            sys.exit(1)
        vtable = {}
        i = 0
        for x in args:
            vtable[self.params[i]] = x
            i += 1
        self.vtable.append(vtable)
        self.exec_list(self.body)
        self.vtable.pop()

class IfNode(Node):
    def __init__(self, condition, tstmt, fstmt):
        self.condition = condition
        self.tstmt     = tstmt
        self.fstmt     = fstmt
    def evaluate(self):
        if self.condition.evaluate():
            self.exec_list(self.tstmt)
        else:
            self.exec_list(self.fstmt)

class WhileNode(Node):
    def __init__(self, condition, body):
        self.condition = condition
        self.body      = body
    def evaluate(self):
        while self.condition.evaluate():
            self.exec_list(self.body)

class AssignNode(Node):
    def __init__(self, vtable, vname, val):
        self.vtable = vtable
        self.vname  = vname
        self.val    = val
    def evaluate(self):
        if len(self.vtable) < 1:
            vtable = { self.vname : self.val.evaluate() }
            self.vtable.append(vtable)
        else:
            vtable = self.vtable[-1]
            vtable[self.vname] = self.val.evaluate()

class VarRefNode(Node):
    def __init__(self, vtable, ftab, vname):
        self.vtable = vtable
        self.ftab   = ftab
        self.vname  = vname
    def evaluate(self):
        vtable = self.vtable[-1]
        
        if vtable.has_key(self.vname):
            return vtable[self.vname]
        elif self.ftab.has_key(self.vname):
            self.ftab[self.vname].call([])
        else:
            try:
                return eval(vtable[self.vname])
            except SyntaxError:
                print "unknown method or local variable %s" % self.vname

class StringNode(Node):
    def __init__(self, val):
        self.val = val
    def evaluate(self):
        return self.val

class LiteralNode(Node):
    def __init__(self, val):
        self.val = val
    def evaluate(self):
        return self.val

#!/usr/bin/env python

import sys
from math import *
from m20090514 import *

import ply.lex as lex

tokens = (
  'NUMBER',
  'STRING',
  'IDENT',
  'EOL',

  'IF',
  'THEN',
  'ELSE',
  'WHILE',
  'DO',
  'END',
  'DEF',
)

literals = "(),=+-*/"

reserved = {
  'if'    : 'IF',
  'then'  : 'THEN',
  'else'  : 'ELSE',
  'while' : 'WHILE',
  'do'    : 'DO',
  'end'   : 'END',
  'def'   : 'DEF',
}

def t_NUMBER(t):
    r'\d+'
    try:
        t.value = int(t.value)
    except ValueError:
        print "Integer value too large", t.value
        t.value = 0
    return t

def t_STRING(t):
    r""""(?:[^"\\]+|\\.)*"|'(?:[^'\\]+|\\.)*'"""
    t.value = eval(t.value)
    return t

def t_EOL(t):
    r'\n'
    return t

def t_IDENT(t):
    r'[a-zA-Z_]\w*'
    t.type = reserved.get(t.value, 'IDENT')
    return t

t_ignore = " \t"

def t_error(t):
    print "Illegal character '%s'" % t.value[0]
    t.lexer.skip(1)

lex.lex()


import ply.yacc as yacc

precedence = (
    ('left',  '+', '-'),
    ('left',  '*', '/'),
    ('right', 'UMINUS'),
    )

def p_program_stmt(p):
    "program : stmt_list"
    p[0] = RootNode(p[1])

def p_stmt_list_empty(p):
    "stmt_list : "
    p[0] = []

def p_stmt_list_stmt(p):
    "stmt_list : stmt_list stmt EOL"
    global lineno
    lineno += 1
    p[1].append(p[2])
    p[0] = p[1]

def p_stmt_list(p):
    "stmt_list : stmt_list EOL"
    global lineno
    lineno += 1
    p[0] = p[1]

def p_stmt(p):
    """stmt : expr
            | assign
            | if_stmt
            | while_stmt
            | defun"""
    p[0] = p[1]

def p_stmt_IDENT(p):
    "stmt : IDENT realprim"
    global ftab
    p[0] = FuncallNode(ftab, p[1], [p[2]])

def p_if_stmt(p):
    "if_stmt : IF stmt THEN EOL stmt_list else_stmt END"
    p[0] = IfNode(p[2], p[5], p[6])

def p_else_stmt(p):
    "else_stmt : ELSE EOL stmt_list"
    p[0] = p[3]

def p_else_stmt_empty(p):
    "else_stmt : "
    p[0] = []

def p_while_stmt(p):
    "while_stmt : WHILE stmt DO EOL stmt_list END"
    p[0] = WhileNode(p[2], p[5])

def p_defun(p):
    "defun : DEF IDENT param EOL stmt_list END"
    global ftab
    global vtable
    p[0] = DefNode(ftab, p[2],
                   Function(vtable, p[2], p[3], p[5]))

def p_param_name_list(p):
    "param : '(' name_list ')'"
    p[0] = p[2]

def p_param(p):
    """param : '(' ')'
             | """
    p[0] = []

def p_name_list(p):
    "name_list : IDENT"
    p[0] = [ p[1] ]

def p_name_list2(p):
    "name_list : name_list ',' IDENT"
    p[1].append(p[3])
    p[0] = p[1]

def p_assign(p):
    "assign : IDENT '=' expr"
    global vtable
    p[0] = AssignNode(vtable, p[1], p[3])

def p_expr_plus(p):
    "expr : expr '+' expr"
    global ftab
    p[0] = FuncallNode(ftab, '+', [p[1], p[3]])

def p_expr_minus(p):
    "expr : expr '-' expr"
    global ftab
    p[0] = FuncallNode(ftab, '-', [p[1], p[3]])

def p_expr_mul(p):
    "expr : expr '*' expr"
    global ftab
    p[0] = FuncallNode(ftab, '*', [p[1], p[3]])

def p_expr_div(p):
    "expr : expr '/' expr"
    global ftab
    p[0] = FuncallNode(ftab, '/', [p[1], p[3]])

def p_expr_primary(p):
    "expr : primary"
    p[0] = p[1]

def p_primary(p):
    "primary : realprim"
    p[0] = p[1]

def p_primary_paren(p):
    "primary : '(' expr ')'"
    p[0] = p[2]

def p_primary_uminus(p):
    "primary : '-' primary %prec UMINUS"
    global ftab
    p[0] = FuncallNode(ftab, '-', [LiteralNode(0), p[2]])

def p_realprim_ident(p):
    "realprim : IDENT"
    global vtable
    global ftab
    p[0] = VarRefNode(vtable, ftab, p[1])

def p_realprim_number(p):
    "realprim : NUMBER"
    p[0] = LiteralNode(p[1])

def p_realprim_string(p):
    "realprim : STRING"
    p[0] = StringNode(p[1])

def p_realprim_funcall(p):
    "realprim : funcall"
    p[0] = p[1]

def p_funcall_args(p):
    "funcall : IDENT '(' args ')'"
    global ftab
    p[0] = FuncallNode(ftab, p[1], p[3])

def p_funcall_no_args(p):
    "funcall : IDENT '(' ')'"
    global ftab
    p[0] = FuncallNode(ftab, p[1], [])

def p_args1(p):
    "args : expr"
    p[0] = [p[1]]

def p_args2(p):
    "args : args ',' expr"
    p[1].append(p[3])
    p[0] = p[1]

def p_error(p):
    global lineno
    try:
        print "%s:%d Syntax error at '%s'" % ("-", lineno, p.value)
    except AttributeError:
        print "%s:%d Syntax error" % ("-", lineno)

yacc.yacc()

vtable = []
ftab   = {}
lineno = 1

def do_funcall(func, args):
    import string
    cmd_args     = string.join([repr(x) for x in args], ",")
    cmd          = func + "(" + cmd_args + ")"
    cmd_no_paren = func + " " + cmd_args
    
    try:
        return eval(cmd)
    except SyntaxError:
        exec(cmd_no_paren)

def do_assign(vname, val):
    global vtable
    vtable[vname] = val

def do_varref(vname):
    global vtable
    if vtable.has_key(vname):
        return vtable[vname]
    else:
        return eval(vname)

tree = yacc.parse(sys.stdin.read())
#print tree.tree
tree.evaluate()

で、

message = "funcall ok"
print(message)
print "\n"
print message
print "\n"
print('operator', ' ok', "\n")
print('1 + 1 = ', 1+1, "\n")
print('1 + 2 * -3 = ', 1 + 2 * -3, "\n")

if 1 then
  print "if ok\n"
else
  print "if not ok\n"
end

i = 1
while i do
  print "while ok\n"
  i = 0
end

def func
  print "defun ok\n"
end
func()
func

def argfunc(arg)
  print 'defun with arg: '
  print(arg, "\n")
end
argfunc('ok')
argfunc 'ok'

def argfunc2(arg1, arg2)
  print 'defun with arg: '
  print(arg1, " ", arg2, "\n")
end
argfunc2(1, 2)

x = 1
def func(x)
  print(x, "\n")
  x = 2
  print(x, "\n")
end
func(x)
print(x, "\n")

を入力すると、

funcall ok


funcall ok


operator  ok 

1 + 1 =  2 

1 + 2 * -3 =  -5 

if ok

while ok

defun ok

defun ok

defun with arg: 
ok 

defun with arg: 
ok 

defun with arg: 
1   2 

1 

2 

1