変数実装できたよー

Operatorまわりの構造も変更。

class Calc
	class Error < RuntimeError
	end
	
	class Operator
		def initialize( mark, priority, proc, is_assignment = false )
			@mark = mark
			@priority = priority
			@proc = proc
			@is_assignment = is_assignment
		end
		
		attr_reader :mark, :priority, :proc
		
		def assignment?
			@is_assignment
		end
		
		POW = new( '**', 4, proc {|a,b| a**b } )
		MUL = new( '*',  2, proc {|a,b| a*b } )
		DIV = new( '/',  2, proc {|a,b| a/b } )
		MOD = new( '%',  2, proc {|a,b| a%b } )
		ADD = new( '+',  1, proc {|a,b| a+b } )
		SUB = new( '-',  1, proc {|a,b| a-b } )
		
		SET = new( '=', 0, nil, true )
		
		ASSIGNMENTS = [POW, MUL, DIV, MOD, ADD, SUB].map {|v|
			new( v.mark + '=', 0, v.proc, true ) }
		
		INC = new( '++', 6, proc{|a| a+1 }, true )
		DEC = new( '--', 6, proc{|a| a-1 }, true )
		
		PLUS  = new( '+', 5, proc{|a| +a } )
		MINUS = new( '-', 3, proc{|a| -a } )
		
		
		IS_RIGHT_ASSOCIATIVE = { 0 => true, 4 => true }
		IS_RIGHT_ASSOCIATIVE.default = false
	end
	
	BINARY_OPERATORS = class Operator
		[POW, MUL, DIV, MOD, ADD, SUB, SET] + ASSIGNMENTS
	end.sort_by {|v| -v.mark.size }
	
	UNARY_OPERATORS = class Operator
		[INC, DEC, PLUS, MINUS]
	end.sort_by {|v| -v.mark.size }
	
	class TokenType
		def initialize( regexp )
			@regexp = regexp
			@prev_white_list = []
		end
		
		attr_reader :regexp
		attr_accessor :prev_white_list
		
		SPACE           = new( /\s+/ )
		BINARY_OPERATOR = new( nil )
		UNARY_OPERATOR  = new( nil )
		NUM             = new( /-?[0-9]+(?:\.[0-9]+)?/ )
		SYMBOL          = new( /(?![0-9])[0-9A-Za-z]+/ )
		LEFT_BRACKETS   = new( /\(/ )
		RIGHT_BRACKETS  = new( /\)/ )
		
		[NUM, SYMBOL, UNARY_OPERATOR, LEFT_BRACKETS].each do |i|
			i.prev_white_list = 
				[nil, LEFT_BRACKETS, BINARY_OPERATOR, UNARY_OPERATOR]
		end
		
		[BINARY_OPERATOR, RIGHT_BRACKETS].each do |i|
			i.prev_white_list = [NUM, SYMBOL, RIGHT_BRACKETS]
		end
		
		def operators
			case self
			when BINARY_OPERATOR
				BINARY_OPERATORS
			when UNARY_OPERATOR
				UNARY_OPERATORS
			end
		end
		
		def operator?
			!!operators
		end
		
		def inspect
			{ SPACE => 'SPACE', BINARY_OPERATOR => 'BINARY_OPERATOR', 
			UNARY_OPERATOR => 'UNARY_OPERATOR', NUM => 'NUM', SYMBOL => 'SYMBOL', 
			LEFT_BRACKETS => 'LEFT_BRACKETS', RIGHT_BRACKETS => 'RIGHT_BRACKETS' }[self]
		end
	end
	
	TOKEN_TYPES = class TokenType
		[SPACE, BINARY_OPERATOR, UNARY_OPERATOR,
			NUM, SYMBOL, LEFT_BRACKETS, RIGHT_BRACKETS]
	end
	
	class Token
		def initialize( type, val = nil )
			@type = type
			@val = val
		end
		attr_reader :type, :val
	end
	
	class Tokens
		def initialize
			@list = []
		end
		
		attr_accessor :list
	
		def inspect( ptr = nil )
			ret = ''
			@list.each_with_index do |t,i|
				ret += (ptr && i == ptr) ? '|' : ' '
				case t.type
				when TokenType::BINARY_OPERATOR, TokenType::UNARY_OPERATOR
					ret += t.val.mark
					if t.type == TokenType::UNARY_OPERATOR && [Operator::PLUS, Operator::MINUS].include?( t.val )
						ret += '@'
					end
				when TokenType::LEFT_BRACKETS
					ret += '('
				when TokenType::RIGHT_BRACKETS
					ret += ')'
				else
					ret += t.val.to_s
				end
			end
			ret += '|' if ptr && @list.size == ptr
			return ret
		end
	end
	
	class InfixNotationTokens < Tokens
		def to_postfix_notation
			dest = PostfixNotationTokens.new
			dest_ptr = 0
			prev_type = nil
			rs_stack = []
			brackets_level = 0
			rop_exit = proc do |i|
				while rop_end?( rs_stack, brackets_level, dest.list[dest_ptr], @list[i+1] )
					puts dest.inspect( dest_ptr )
					rs_stack.pop
					dest_ptr += 1
				end
			end
			
			@list.each_with_index do |token, i|
				case token.type
				when TokenType::NUM, TokenType::SYMBOL
					dest.list.insert( dest_ptr, token )
					dest_ptr += 1
					if prev_type && prev_type.operator? # 右オペランドの場合
						rop_exit.call( i )
					end
				when TokenType::BINARY_OPERATOR, TokenType::UNARY_OPERATOR
					dest.list.insert( dest_ptr, token )
					rs_stack.push( brackets_level )
				when TokenType::LEFT_BRACKETS
					brackets_level += 1
				when TokenType::RIGHT_BRACKETS
					raise Error, '不正な閉じカッコ' unless brackets_level > 0
					brackets_level -= 1
					rop_exit.call( i )
				else
					raise Error, '不明なトークン'
				end
				prev_type = token.type
				puts dest.inspect( dest_ptr )
			end
			raise Error, '式が途中で終了している' unless dest.list.size == dest_ptr
			raise Error, 'カッコが閉じられていない' unless brackets_level == 0
			
			return dest
		end
		
		private
		# 右オペランドは終了している?
		def rop_end?( rs_stack, brackets_level, token, next_token )
			return false unless rs_stack.last == brackets_level
			
			return true unless next_token && token
			return true unless [next_token, token].all? {|v| v.type.operator? }
			next_token_priority = next_token.val.priority
			token_priority = token.val.priority
			
			if next_token_priority == token_priority && Operator::IS_RIGHT_ASSOCIATIVE[token_priority]
				return false
			end
			
			return next_token_priority <= token_priority
		end
	end
	
	class Value
		class Type
			NUM = new
			VAR = new
		end
	
		def initialize( val, val2 = nil, type = Type::NUM )
			@type = type
			@val = val
			@val2 = val2
		end
		
		def self.from_token( token, vars )
			case token.type
			when TokenType::NUM
				new( token.val )
			when TokenType::SYMBOL
				new( token.val, vars[token.val], Type::VAR )
			end
		end
		
		attr_reader :val
		
		def value
			if @type == Type::VAR
				raise Error, '未初期化の変数を参照した' unless @val2
				@val2
			else
				@val
			end
		end
		
		def var?
			@type == Type::VAR
		end
	end
	
	class PostfixNotationTokens < Tokens
		def calculate( vars )
			stack = []
			@list.each do |token|
				case token.type
				when TokenType::NUM, TokenType::SYMBOL
					stack.push( Value.from_token( token, vars ) )
				when TokenType::BINARY_OPERATOR
					raise Error, 'オペランドが足りない' unless stack.size >= 2
					b = stack.pop
					a = stack.pop
					if token.val.assignment?
						raise Error, '代入先が変数ではない' unless a.var?
						if token.val == Operator::SET
							stack.push( Value.new( vars[a.val] = b.value ) )
						else
							stack.push( Value.new( vars[a.val] = 
								token.val.proc.call( a.value, b.value ) ) )
						end
					else
						stack.push( Value.new( token.val.proc.call( a.value, b.value ) ) )
					end
				when TokenType::UNARY_OPERATOR
					raise Error, 'オペランドが足りない' unless stack.size >= 1
					a = stack.pop
					if token.val.assignment?
						raise Error, '代入先が変数ではない' unless a.var?
						stack.push( Value.new( vars[a.val] = token.val.proc.call( a.value ) ) )
					else
						stack.push( Value.new( token.val.proc.call( a.value ) ) )
					end
				else 
					raise Error, '不明なトークン'
				end
			end

			raise Error, 'オペランドが足りないか、余っている' unless stack.size == 1
			return stack[0].value
		end
		
		def to_infix_notation
			dest = InfixNotationTokens.new
			stack = [] # 演算子が挿入されるべき位置
			@list.each do |token|
				case token.type
				when TokenType::NUM, TokenType::SYMBOL
					dest.list << token
					stack.push( dest.list.size )
				when TokenType::BINARY_OPERATOR
					raise Error, 'オペランドが足りない' unless stack.size >= 2
					stack.pop
					pos = stack.pop
					dest.list.insert( pos, token )
					dest.list.insert( stack.last || 0, Token.new( TokenType::LEFT_BRACKETS ) )
					dest.list.insert( dest.list.size, Token.new( TokenType::RIGHT_BRACKETS ) )
					stack.push( dest.list.size )
				when TokenType::UNARY_OPERATOR
					raise Error, 'オペランドが足りない' unless stack.size >= 1
					stack.pop
					dest.list.insert( stack.last || 0, token )
					dest.list.insert( stack.last || 0, Token.new( TokenType::LEFT_BRACKETS ) )
					dest.list.insert( dest.list.size, Token.new( TokenType::RIGHT_BRACKETS ) )
					stack.push( dest.list.size )
				else 
					raise Error, '不明なトークン'
				end
			end
			raise Error, 'オペランドが足りないか、余っている' unless stack.size == 1
			return dest
		end
	end
	
	def initialize
		@vars = {}
	end
	
	def calculate( expr )
		tokens = lex( expr ).to_postfix_notation
		p tokens.to_infix_notation
		return tokens.calculate( @vars )
	end
	
	private
	def lex( expr )
		tokens = InfixNotationTokens.new
		i = 0
		prev_type = nil
		while expr[i]
			find = nil
			TOKEN_TYPES.each do |token_type|
				unless token_type == TokenType::SPACE
					next unless token_type.prev_white_list.include?( prev_type )
				end
				size = nil
				if operators = token_type.operators
					operator = nil
					operators.each do |o|
						next unless expr[i,o.mark.size] == o.mark
						operator = o
						find = true
						size = o.mark.size
						break
					end
				else
					find = /\A#{token_type.regexp}/.match( expr[i..-1] )
					size = find.to_s.size
				end
				next unless find
				case token_type
				when TokenType::BINARY_OPERATOR, TokenType::UNARY_OPERATOR
					tokens.list << Token.new( token_type, operator )
				when TokenType::NUM
					tokens.list << Token.new( token_type, find.to_s.to_f )
				when TokenType::SYMBOL
					tokens.list << Token.new( token_type, find.to_s )
				when TokenType::LEFT_BRACKETS, TokenType::RIGHT_BRACKETS
					tokens.list << Token.new( token_type )
				end
				i += size
				prev_type = token_type unless token_type == TokenType::SPACE
				break
			end
			raise Error, '構文エラー' unless find
		end
		return tokens
	end
end

calc = Calc.new
while s = gets
	begin
		puts calc.calculate( s.chomp )
	rescue Calc::Error => e
		puts "Error: #{e}"
	end
end