関数呼び出し実装できたよー
変数と関数の区別は後ろに括弧がつくかでやってる。
可変長の引数が可能になるように作ってみた。
PostfixNotationTokens#to_infix_notationでカンマを出力できていないのでそれをなんとかしたい。
putchar( 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 10 ) Hello, world! 14.0
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 } FUNCTIONS = { 'putchar' => proc {|args| args.each {|c| putc c}; args.size.to_f }, 'rand' => proc {|args| args.empty? ? rand : rand( args[0] ).to_f } } class TokenType def initialize( regexp = nil ) @regexp = regexp @prev_white_list = [] end attr_reader :regexp attr_accessor :prev_white_list SPACE = new( /\s+/ ) BINARY_OPERATOR = new UNARY_OPERATOR = new NUM = new( /-?[0-9]+(?:\.[0-9]+)?/ ) SYMBOL = new( /(?![0-9])[0-9A-Za-z]+/ ) LEFT_BRACKETS = new( /\(/ ) RIGHT_BRACKETS = new( /\)/ ) COMMA = new( /,/ ) VAR = new FUNCTION = new [NUM, SYMBOL, UNARY_OPERATOR, LEFT_BRACKETS].each do |i| i.prev_white_list = [nil, LEFT_BRACKETS, BINARY_OPERATOR, UNARY_OPERATOR, COMMA] end [BINARY_OPERATOR, RIGHT_BRACKETS, COMMA].each do |i| i.prev_white_list = [NUM, SYMBOL, RIGHT_BRACKETS] end LEFT_BRACKETS.prev_white_list << SYMBOL RIGHT_BRACKETS.prev_white_list << LEFT_BRACKETS 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', COMMA => 'COMMA', VAR => 'VAR', FUNCTION => 'FUNCTION' }[self] end end TOKEN_TYPES = class TokenType [SPACE, BINARY_OPERATOR, UNARY_OPERATOR, NUM, SYMBOL, LEFT_BRACKETS, RIGHT_BRACKETS, COMMA] end Token = Struct.new( :type, :val ) FunctionTokenVal = Struct.new( :name, :argc ) 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 += ')' when TokenType::COMMA ret += ',' when TokenType::FUNCTION ret += t.val.name when TokenType::VAR ret += t.val 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 = [] func_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 if dest.list[dest_ptr] && dest.list[dest_ptr].type == TokenType::FUNCTION dest.list[dest_ptr].val.argc = func_stack.pop || 0 end dest_ptr += 1 end end @list.each_with_index do |token, i| case token.type when TokenType::NUM, TokenType::SYMBOL if func_stack.size > 0 && func_stack.last == nil func_stack[func_stack.size-1] = 1 end if token.type == TokenType::SYMBOL && @list[i+1] && @list[i+1].type == TokenType::LEFT_BRACKETS token = Token.new( TokenType::FUNCTION, FunctionTokenVal.new( token.val ) ) dest.list.insert( dest_ptr, token ) rs_stack.push( brackets_level ) func_stack.push( nil ) else type = token.type type = TokenType::VAR if type == TokenType::SYMBOL dest.list.insert( dest_ptr, Token.new( type, token.val ) ) dest_ptr += 1 if prev_type && prev_type.operator? # 右オペランドの場合 rop_exit.call( i ) end 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 ) when TokenType::COMMA raise Error, '不正なカンマ' unless func_stack.size > 0 func_stack[func_stack.size-1] += 1 else raise Error, "不明なトークン(#{token.type.inspect})" 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::VAR 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::VAR 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 when TokenType::FUNCTION raise Error, 'オペランドが足りない' unless stack.size >= token.val.argc args = stack[stack.size - token.val.argc, stack.size].map {|v| v.value } stack = stack[0,stack.size - token.val.argc] raise Error, '関数が見つからない' unless FUNCTIONS.key?( token.val.name ) stack.push( Value.new( FUNCTIONS[token.val.name].call( args ) ) ) else raise Error, "不明なトークン(#{token.type.inspect})" 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::VAR 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 ) when TokenType::FUNCTION stack = stack[0,stack.size - token.val.argc] dest.list.insert( stack.last || 0, Token.new( TokenType::LEFT_BRACKETS ) ) dest.list.insert( stack.last || 0, token ) dest.list.insert( dest.list.size, Token.new( TokenType::RIGHT_BRACKETS ) ) stack.push( dest.list.size ) else raise Error, "不明なトークン(#{token.type.inspect})" end end raise Error, 'オペランドが足りないか、余っている' unless stack.size == 1 return dest end end def initialize @vars = {} end def calculate( expr ) tokens = lex( expr ) #p tokens tokens = tokens.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, TokenType::COMMA tokens.list << Token.new( token_type ) when TokenType::SPACE else raise Error, "不明なトークン(#{token_type.inspect})" 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