# encoding=shiftjis

# 日本語で計算プログラムβ
# 2007.12.26 version 0.2
# copyright takayan

### インポート

import MeCab
import sys
import re
import math


### クラス宣言

# 引数エラークラス
class ParamError(Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return repr(self.value)


# 意味不明エラークラス
class Incoherent(Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return repr(self.value)


# コマンドクラス
class Command:
	def __init__(self,func,string):
		self.f = func
		temp   = string.split(',')
		self.s = temp[::-1]
		self.k = "func"


# ノードクラス
class Node:
	def __init__(self,value,kind):
		self.v = value
		self.k = kind
		self.n = 0


# ノードリストクラス
class ListNode:
	def __init__(self,value,kind):
		self.v = value
		self.k = kind
		self.n = 0


### 大域変数

# 基本述語リスト
primitives = []

# 前回の結果
gResult = 0


### 関数定義

# 引数チェック
def checkparam(param,num):
	if len(param) < num:
		raise ParamError,"引数の数が足りません:"+str(len(param))

	if len(param) > num:
		raise ParamError,"引数の数が多すぎます:"+str(len(param))

	for n in range(num):
		if ( param[n] == None ):
			raise ParamError,"値がありません"


# コマンド関数
def func_add(param):
	checkparam(param,2)
	p2 = int(param[1].v)
	p1 = int(param[0].v)
	return Node( p1+p2, 'ワード' )

def func_radd(param):
	checkparam(param,2)
	p2 = int(param[0].v)
	p1 = int(param[1].v)
	return Node( p1+p2, 'ワード' )

def func_sub(param):
	checkparam(param,2)
	p2 = int(param[1].v)
	p1 = int(param[0].v)
	return Node( p1-p2, 'ワード' )

def func_rsub(param):
	checkparam(param,2)
	p2 = int(param[0].v)
	p1 = int(param[1].v)
	return Node( p1-p2, 'ワード' )

def func_mul(param):
	checkparam(param,2)
	p2 = int(param[1].v)
	p1 = int(param[0].v)
	return Node( p1*p2, 'ワード' )

def func_rmul(param):
	checkparam(param,2)
	p2 = int(param[0].v)
	p1 = int(param[1].v)
	return Node( p1*p2, 'ワード' )

def func_div(param):
	checkparam(param,2)
	p2 = int(param[1].v)
	p1 = int(param[0].v)
	if p2 == 0:
		raise ParamError,"０では割れません"
	return Node( p1/p2, 'ワード' )

def func_rdiv(param):
	checkparam(param,2)
	p2 = int(param[0].v)
	p1 = int(param[1].v)
	if p2 == 0:
		raise ParamError,"０では割れません"
	return Node( p1/p2, 'ワード' )

def func_mod(param):
	checkparam(param,2)
	p2 = int(param[1].v)
	p1 = int(param[0].v)
	if p2 == 0:
		raise ParamError,"０では割れません"
	return Node( p1%p2, 'ワード' )

def func_rmod(param):
	checkparam(param,2)
	p2 = int(param[0].v)
	p1 = int(param[1].v)
	if p2 == 0:
		raise ParamError,"０では割れません"
	return Node( p1%p2, 'ワード' )

def func_cat(param):
	checkparam(param,2)
	p2 = param[1].v
	p1 = param[0].v
	return Node( p1+p2, 'ワード' )

def func_rcat(param):
	checkparam(param,2)
	p2 = param[0].v
	p1 = param[1].v
	return Node( p1+p2, 'ワード' )

def func_factorial(param):
	checkparam(param,1)
	p1 = int(param[0].v)
	result = 1
	for i in range(1,p1+1):
		result = result * i
	return Node( result, 'ワード' )

def func_value(param):
	checkparam(param,1)
	p1 = int(param[0].v)
	return Node( p1, 'ワード' )

def func_square(param):
	checkparam(param,1)
	p1 = int(param[0].v)
	return Node( p1*p1, 'ワード' )

def func_pi(param):
	checkparam(param,0)
	return Node( math.pi, 'ワード' )

def func_show(param):
	checkparam(param,1)
	print param[0].v
	return Node( 0, 'ワード' )

def func_result(param):
	checkparam(param,0)
	return Node( gResult, 'ワード' )

# コマンド定義
commands = (
	(func_pi, "パイ"),
	(func_value,"の,あたい"),
	(func_show,"を,ひょうじ"),
	(func_show,"を,ひょうじする"),
	(func_add, "に,を,たす"),
	(func_radd, "を,に,たす"),
	(func_add, "と,を,たす"),
	(func_sub,"から,を,ひく"),
	(func_rsub,"を,から,ひく"),
	(func_mul,"に,を,かける"),
	(func_rmul,"を,に,かける"),
	(func_mul,"と,を,かける"),
	(func_div,"を,で,わる"),
	(func_rdiv,"で,を,わる"),
	(func_mod,"を,で,わったあまり"),
	(func_rmod,"で,を,わったあまり"),
	(func_factorial,"の,かいじょう"),

	(func_pi, "π"),
	(func_value,"の,値"),
	(func_show,"を,表示"),
	(func_show,"を,表示する"),
	(func_add, "に,を,足す"),
	(func_radd, "を,に,足す"),
	(func_add, "と,を,足す"),
	(func_sub,"から,を,引く"),
	(func_rsub,"を,から,引く"),
	(func_mul,"に,を,掛ける"),
	(func_rmul,"を,に,掛ける"),
	(func_mul,"と,を,掛ける"),
	(func_div,"を,で,割る"),
	(func_rdiv,"で,を,割る"),
	(func_mod,"を,で,割った余り"),
	(func_rmod,"で,を,割った余り"),
	(func_factorial,"の,階乗"),

	(func_square,"の,自乗"),
	(func_square,"の,じじょう"),

	(func_cat,"に,を,くっつける"),
	(func_rcat,"を,に,くっつける"),
	(func_cat,"に,を,連結"),
	(func_rcat,"を,に,連結"),
	(func_cat,"に,を,れんけつ"),
	(func_rcat,"を,に,れんけつ"),

	(func_result, "その結果"),
	(func_result, "そのけっか"),
	(func_result, "その答え"),
	(func_result, "そのこたえ"),
	(func_result, "その値"),
	(func_result, "そのあたい"),
)


# コマンド要素
class CommandNode:
	def __init__(self,name,func,params):
		self.name   = name
		self.func   = func
		self.params = params


# 再帰的にコマンドを実行する
def evalCommandNode(node):

	if node == None:
		return None

	if node.func == None:
		return Node( node.name, 'ワード' )

	para = []
	for param in node.params:
		para.append( evalCommandNode( param ) )

	result =  node.func( para )

#	print "name:",node.name
#	print "func:",node.func
#	print "para:",para
#	print "途中結果:",result.v

	return result


# コマンドリストを実行する
def evalCommandList(data):
	global gResult

	for node in data:
		result = evalCommandNode( node )
		gResult = result.v
		print "結果:",result.v


# 命令を生成する
def makeCommand( nodes, counter ):

	node = nodes[counter]

	coms = []
	if node <> None:
		for p in primitives:
#			print "primitive:",p.s[0]
			if p.s[0] == node.v:
				coms.append(p)

	# 定義されていなければたぶん定数。変数はまだ未対応
	if coms == []:
		return  ( counter+1, CommandNode( node.v, None, [] ) )

	verbonly = 0
	only = 0
	keep = counter
	verb = None

	# すべての候補に対して確認する
	for p in coms:
		# カウンタを基準点に戻す
		counter = keep

		# 述語の情報
		s = p.s
		v = s[0]
		s = s[1:]

		# 命令の確認
		if v <> nodes[counter].v:
			raise Incoherent,"述語が違います:"+v+","+nodes[counter].v

		# 命令の格納
		verbonly = 1

		# 助詞をもとに引数を探す
		retryflag = 0
		counter = counter + 1
		params = []

		# 候補のすべての助詞に対して
		for n in s:

			# 助詞が無ければ、中断
			if len(nodes) <= counter:
				retryflag = 1
				break

			# 助詞が適合しなければ、中断
			if nodes[counter].v <> n:
				retryflag = 1
				break

			# 助詞の分、カウンタを進める
			counter = counter + 1

			# 目的語の分がなければ中断
			if len(nodes) <= counter:
				retryflag = 1
				break

			# 目的語の命令を作る。作れないときは例外発生。
			res = makeCommand( nodes, counter )
			counter = res[0]

			# 目的語をリストにしておく
			params.insert( 0, res[1] )

		# 中断したときは、次の候補へ
		if retryflag == 1:
			continue

		# 述語確定。カウンタと命令を返す。
		#（最初に見つかったものを返す。あいまいな述語の使用かどうかは判断していない）
		return ( counter, CommandNode( node.v, p.f, params ) )

	# 述語は知っているが、助詞が合わない
	if verbonly:
		raise Incoherent,"述語'"+ v +"'に対応する助詞がありません"

	# 適合述語が無いときはエラー
	raise Incoherent,"述語'"+ v +"'がわかりません"


# 文字列中の全角数値を半角数値に変換する
def convertZenkakuNum(str):

	# 全角数字辞書
	znumChar = {
		u"０":u"0",
		u"１":u"1",
		u"２":u"2",
		u"３":u"3",
		u"４":u"4",
		u"５":u"5",
		u"６":u"6",
		u"７":u"7",
		u"８":u"8",
		u"９":u"9",
		u"，":u",",
		u"．":u".",
	}

	number    = re.compile( u'([0-9０-９]+[,，]?[0-9,，０-９]*)([.．][0-9０-９]*)?' )
	zenNumber = re.compile( u'[０-９，．]' )

	str = unicode( str, 'Shift_JIS' )
#	print str

	endpos = 0
	temp = ""

	# 数値文字列を検索
	it = number.finditer( str )
	for val in it:
#		print val.group(0),

		# 数値文字列
		num = val.group(0)
		v = zenNumber.match(num)
		if v == None:
			continue

		s = []
		for c in num:
			s.append( znumChar.get(c,c) )

		# 配列を連結する
		s = u''.join(s)

		# 置換文字列追加
		if val.start(0) > endpos:
			temp = temp + str[endpos:val.start(0)]
		temp = temp + s

		if len(str) > val.end(0):
			endpos = val.end(0)
		else:
			endpos = len(str)

	if len(str) > endpos-1:
		str = temp + str[endpos:]
#	print str

	return str.encode('Shift_JIS')


# 文字列中の全角数値を半角数値に変換する
def convertKanNum(str):

	# 簡単な漢数字列判断
	number = re.compile( u'[零一二三四五六七八九十百千万億兆]+' )

	str = unicode( str, 'Shift_JIS' )
#	print str

	endpos = 0
	temp = ""

	# 数値文字列を検索
	it = number.finditer( str )
	for val in it:
#		print val.group(0)

		# 数値文字列
		num = val.group(0)

		d = 0
		t = 0
		a = 0
		for c in num:
			if c == u'一':
				d = 1
			elif c == u'二':
				d = 2
			elif c == u'三':
				d = 3
			elif c == u'四':
				d = 4
			elif c == u'五':
				d = 5
			elif c == u'六':
				d = 6
			elif c == u'七':
				d = 7
			elif c == u'八':
				d = 8
			elif c == u'九':
				d = 9
			elif c == u'十':
				if d == 0 and t == 0:
					d = 1
				t = t + d * 10
				d = 0
			elif c == u'百':
				if d == 0 and t == 0:
					d = 1
				t = t + d * 100
				d = 0
			elif c == u'千':
				if d == 0 and t == 0:
					d = 1
				t = t + d * 1000
				d = 0
			elif c == u'万':
				if t == 0 and d == 0:
					t = 1
				a = a + ( t + d ) * 10000
				t = 0
				d = 0
			elif c == u'億':
				if t == 0 and d == 0:
					t = 1
				a = a + ( t + d ) * 100000000
				t = 0
				d = 0
			elif c == u'兆':
				if t == 0 and d == 0:
					t = 1
				a = a + ( t + d ) * 1000000000000
				t = 0
				d = 0
			elif c == u'零':
				a = 0

		# 配列を連結する
		a = a + t + d
#		print "数値変換：",a
		s = `a`
		if s[-1]== 'L':
			s = s[:-1]

		# 置換文字列追加
		if val.start(0) > endpos:
			temp = temp + str[endpos:val.start(0)]
		temp = temp + s

		if len(str) > val.end(0):
			endpos = val.end(0)
		else:
			endpos = len(str)

	if len(str) > endpos-1:
		str = temp + str[endpos:]
#	print str

	return str.encode('Shift_JIS')


# 日本語形態素クラス
class Token:
	def __init__( self, kind, name, orig ):
		self.kind = kind
		self.name = name
		self.orig = orig


# MeCabの出力をもとに単純化したノード情報を獲得
# （できれば、あとでMeCabにそのように出力させるように辞書を作る）
def getToken(q):

	tt = q.surface
	t  = q.feature
	t  = t.split(",")

	if t[0] == '名詞':
		if t[1] =='一般':
			r = Token( '名詞', tt, tt )
		elif t[1] == '数':
			r = Token( '数', tt, tt )
		elif t[1] == '接尾' and t[2] == '助数詞':
			r = Token( '助数詞', tt, tt )
		elif t[1] == 'サ変接続':
			r = Token( 'サ変接続名詞', tt, tt )
		else:
			r = Token( '名詞', tt, tt )

	elif t[0] == '助詞':
		if t[1] =='連体化' and tt == 'の':
			r = Token( '連体化助詞', 'の', 'の' )
		elif t[1] =='接続助詞':
			r = Token( '接続助詞', tt, tt )
		else:
			r = Token( '助詞', tt, tt )

	elif t[0] == '動詞':
		if t[1] =='非自立':
			r = Token( '非自立動詞', tt, t[6] )
		else:
			r = Token( '動詞', tt, t[6] )

	elif t[0] == '助動詞':
		if t[6] =='ます':
			r = Token( '丁寧助動詞', tt, t[6] )
		else:
			r = Token( '助動詞', tt, t[6] )

	elif t[0] == '記号':
		if t[1] == '句点':
			r = Token( '句点', '。', '。' )
		elif t[1] == '読点':
			r = Token( '読点', '、', '、' )
		else:
			r = Token( '記号', tt, tt )

	elif t[0] == "BOS/EOS":
		r = Token( '端点', '', '' )

	else:
		r = Token( 'その他', tt, t[0] )

	return r


# 述語が単純になるならば単純にする
def makeSimple( s, p ):

	# 終止形に変換
	if len(s) == 1 and p <> '':
		return p

	# 〜ます → 〜
	elif len(s) == 2 and ( s[1] == 'ます' or s[1] == 'て' ):
		return p

	# 〜する → 〜
	elif len(s) == 2 and ( s[1] == 'する' or s[1] == 'しろ' or s[1] == 'せよ' ):
		return p

	# 〜します → 〜
	elif len(s) == 3 and s[1] == 'し' and s[2] == 'ます':
		return p

	# 〜たもの → 〜
	if len(s) == 3 and s[1] == 'た' and ( s[2] == 'もの' or s[2] == '値' or s[2] == 'あたい' or s[2] == '答え' or s[2] == 'こたえ' ):
		return p

	# 〜したもの → 〜
	if len(s) == 4 and s[1] == 'し'  and s[2] == 'た' and ( s[3] == 'もの' or s[3] == '値' or s[3] == 'あたい' or s[3] == '答え' or s[3] == 'こたえ' ):
		return p

	return ''.join(s)


# 文字列の解析
def parseString( string ):

	s = m.parseToNode(string)
	s = s.next

	nodes = []
	temp  = []
	name  = []

	# 形態素のある限り
	while s:
		# トークン獲得
		t = getToken(s)
#		print "トークン種類：",t.kind

		if t.kind == '助詞' or t.kind == '助数詞'  or t.kind == '連体化助詞':
			if temp <> []:
				n = Node( makeSimple( temp, name[0] ), 'ワード' )
				nodes.append(n)
				temp = []
			n = Node( t.name, 'ワード' )
			nodes.append(n)
			name = []

		elif t.kind == '句点':
			if temp <> []:
				n = Node( makeSimple( temp, name[0] ), 'ワード' )
				nodes.append(n)
				temp = []
			n = Node( "。", '句点' )
			nodes.append(n)
			name = []

		elif t.kind == '読点':
			if temp <> []:
				n = Node( makeSimple( temp, name[0] ), 'ワード' )
				nodes.append(n)
				temp = []
#			n = Node( "、", '読点' )
#			nodes.append(n)
			name = []

		elif t.kind == '端点':
			if temp <> []:
				n = Node( makeSimple( temp, name[0] ), 'ワード' )
				nodes.append(n)
				temp = []
			name = []
			break

		elif t.kind == '動詞' or t.kind == '名詞' or t.kind == '数' or t.kind == 'サ変接続名詞' or t.kind == 'その他'  or t.kind == '記号':
			name.append( t.orig )
			temp.append( t.name )

		else:
			temp.append( t.name )

		s = s.next

	# 分析結果を表示する
	printParseResult(nodes)

	# 解析しやすいように、ノードを逆順にして返す。
	nodes = nodes[::-1]

	return nodes


# 分析結果を表示する
def printParseResult(nodes):
	print "*** 解析内容 ***"
	for n in nodes:
		print n.v,' ',
	print
	print "****************"


# ノードリストのコンパイル
def compileList(nodes):

	lc = 0
	comlist = []

	while 1:
		node = nodes[lc]

		if node.k == 'ワード':

			# 合致する述語を探し、コマンドリストを作る
			ld = lc
			res = makeCommand( nodes, lc )
			lc  = res[0]
			if lc == ld:
				raise Incoherent,"コマンドが進んでいません"
			comlist.append( res[1] )

		elif node.k == '句点':
			lc = lc + 1

		elif node.k == '読点':
			lc = lc + 1

		else:
			raise Incoherent,"述語の位置に述語以外があります"

		if len(nodes) <= lc:
			break

	# コマンドリストを逆順にする。
	comlist = comlist[::-1]

	# コンパイル結果を表示する
	printCommand(comlist)

	return comlist


# コンパイル内容表示サブ
def printCommandSub(list,level):
	if list <> []:
		for n in list:
			skip = ''
			for i in range(level):
				skip = skip + '＋'
			if n.func <> None:
				print skip + "述語:",n.name
			else:
				print skip + "定数:",n.name
			if n.params:
				printCommandSub(n.params,level+1)


# コンパイル内容表示
def printCommand(list):
	level = 0
	print "*** 翻訳内容 ***"
	printCommandSub(list,level)
	print "****************"


#
# メインルーチン
#
try:
	# 文章解析
	m = MeCab.Tagger ("")

	# コマンド登録
	for t in commands:
		x = Command( t[0], t[1] )
		primitives.append(x)

	# オープニング
	print "日本語計算インタプリタ"

	# メインループ
	while 1:

		# コマンドライン入力
		string = raw_input('> ')

		# 情報無しならば、再度入力
		if string == None or string == "":
			continue

		# 終了
		if string == "終了" or string == "さようなら"  or string == "byebye":
			print "終了します。"
			break

		# 全角数字を半角にしておく
		string = convertZenkakuNum( string )
		#print "全角数字変換後:",string

		string = convertKanNum( string )
		#print "漢数字変換後:",string

		try:
			# 文字列を解析する
			parselist = parseString( string )

			# ノードリストのコンパイル
			comlist = compileList( parselist )

			# コマンドリストを評価する
			evalCommandList( comlist )

		# 命令と助詞の組み合わせが存在しないとき
		except Incoherent,e:
			print "構文エラー:",e.value

		# 引数にエラーがあるとき
		except ParamError,e:
			print "引数エラー：",e.value

		# 数値が整数値でないとき
		except ValueError:
			print "構文エラー:整数ではありません"

# エラー全般の対処
#except ValueError:
except:
	print "エラー:終了しました"

