# coding: utf-8 require 'forwardable' module TlaParserS # ****************************************************************** # # Manage context to parse TLA+ snippets # # # Builds context in three phases: # # 1) Initial context includes TLA+ standard definitions e.g. TRUE, FALSE # when context is created # 2) Global context includes snippets defining macros, procedures, # operators, and variables. Using pushContext/addContext # 3) During resolve phase push to context formal parameters, quantification # bind variables, variables in set constructor generator. Uses # pushContext/addContext methods internally from 'resolveDefine', # # # ****************************************************************** class Context extend Forwardable # for easy delegation attr_accessor :symbol_table def_delegators :symbol_table, :currentContext, :dumpContext, :popContext, :entries, :resolveModule # ------------------------------------------------------------------ # Logger PROGNAME = "context" # progname for logger include TlaParserS::Utils::MyLogger # mix logger # # TLA+ standar library symbols # GLOABLS = %w( TRUE FALSE ) # ------------------------------------------------------------------ # constructore def initialize( options={} ) @logger = getLogger( PROGNAME, options ) @logger.info( "#{__method__} initialized" ) initContext end # ------------------------------------------------------------------ # manage context def initContext @symbol_table = SymbolTable.new # # create context for standar library && add entries # @symbol_table.pushContext( nil ) # GLOABLS.each do |global| # entry = { # :context_type => "Init", # :context => "Global", # :symbol_type => "StandardLib", # :symbol_name => global, # } # @symbol_table.addEntry( entry ) # end return self end # Create new context in symbol table && add etnries def initEntries( entries ) return unless entries && entries.any? # create new context pushContext # add entries entries.each do |entry| @symbol_table.addEntry( entry ) end end # Add more symbols to current context levell # def addContext( snippets, moduleName=nil ) symbol_table.addContext( snippets, moduleName ) return self end # Add one level to context & and add snipets there # # @param snippets [SyntaxNode] implementing 'symbol_definitions` method # def pushContext( snippets=nil, moduleName=nil ) warn "Method symbol_definitions not implemented on #{snippets}" if snippets && !snippets.respond_to?(:symbol_definitions) symbol_table.pushContext( snippets, moduleName ) return self end # @param symbol [String] to resolve # @return [Hash] with :symbol, :resolved properties def resolveSymbol( symbol ) resolved = symbol_table.resolveContext( symbol ) return { :symbol => symbol, :resolved => resolved, } end # ------------------------------------------------------------------ # Resolver # @param stmt [Statement] parse tree node for statement to resolve # @return see 'resolveExpression' private def resolveStatement( stmt, ret=[] ) stmt.traverse(ret) do |ret,node_type, node| @logger.debug( "#{__method__}: enter node_type=#{node_type}, #{node.text_value}ret=#{ret}." ) case node_type when "Statement" when "Skip" when "Return" when "Goto" when "Identifier" @logger.debug( "#{__method__}: Identifier node=#{node.inspect}" ) ret.concat( resolveExpression( node )) when "Conditional" ret.concat( resolveExpression( node.condition )) @logger.debug( "#{__method__}: Conditional if_true=#{node.if_true.inspect}" ) ret.concat( resolveStatement( node.if_true, ret )) ret.concat( resolveStatement( node.if_not_true, ret )) if node.if_not_true when "Either" ret = node.choices.each { |s| resolveStatement( s, ret ) } when "Assert" ret.concat( resolveExpression( node.assertion )) when "Assignment" lvalue = node.lvalue @logger.debug( "#{__method__}: Assignment lvalue=#{lvalue.inspect}" ) ret.concat( resolveExpression( node.lvalue.expression ) ) ret.concat( resolveExpression( node.rvalue )) # lvalue = node.lvalue.node_value # @logger.debug( "#{__method__}: Assign lvalue=#{lvalue}" ) # # take just first variable # lvalue = lvalue.split( /[.\[]/ ).first # ret << resolvedIdentifier( lvalue, symbol_table.resolveContext( lvalue )) # # resovel expression # ret.concat( resolveExpression( node.rvalue )) when "Print" @logger.debug( "#{__method__}: Print node=#{node.inspect}" ) sret = resolveExpression( node.print_expression ) @logger.debug( "#{__method__}: Print sret=#{sret}" ) ret = ret.concat( sret ) when "CompoundStatement" ret = node.statements.each { |s| resolveStatement( s, ret ) } # sret = [] # node.statements.each { |s| sret = resolveStatement( s, sret ) } # ret.concat( sret ) when "MacroCall" # resolve macro definition ret << resolvedIdentifier( node.called, symbol_table.resolveContext( node.called ) ) # resolve actual parameters node.actual_parameters.each do |expr_node| eret = resolveExpression(expr_node ) @logger.debug "actual param eret=#{eret}" ret.concat( eret ) end # actual paramtere when "Call" # resolve procedure definition ret << resolvedIdentifier( node.called, symbol_table.resolveContext( node.called ) ) # resolve actual parameters ret = node.actual_parameters.each do |expr_node| ret.concat( resolveExpression( expr_node )) end # actual paramtere else msg = "#{__method__}: Unknwon statement node type #{node_type} for node #{node.inspect}" @logger.warn( msg ) warn msg end @logger.debug( "#{__method__}: leave node_type=#{node_type} ret=#{ret}." ) ret end ret end # def resolveStatement( stmt ) # @return see 'resolveStatement' def resolveDefine( defineNode ) # @logger.debug "resolveDefine starting callable=#{defineNode.inspect}" ret = nil pushContext( defineNode ) # dumpContext if defineNode.respond_to?( :body_node ) then # macro, procedure, operatordef @logger.info( "#{__method__} start with resolveStatement for #{defineNode.body_node}" ) if defineNode.body_node.is_a?(Sexp::Expression) # operator ret = resolveExpression( defineNode.body_node ) else # macro, procedure ret = resolveStatement( defineNode.body_node ) end # resolve expressions initializing variable declarations if defineNode.respond_to?(:variable_declarations) sret = [] defineNode.variable_declarations.each do |variable_declaration| sret = sret.concat( resolveExpression( variable_declaration.init )) end # add resolved symbols from declaration init only if symbol # not already resolved sret.each do |s| ret << s unless ret.map { |r| r[:symbol] }.include?( s[:symbol]) end end # puts "sret found=#{sret}, ret=#{ret}" if sret.any? elsif defineNode.respond_to?( :init ) then @logger.info( "#{__method__} start with resolveExpression #{defineNode.init}" ) ret = resolveExpression( defineNode.init ) @logger.debug( "#{__method__} resolveExpression-->#{ret}" ) else msg = <<-EOS Unknown tree node object #{defineNode}. Should be 'Callabe' or 'VariableDef' EOS @logger.error( "#{__method__} #{msg}" ) raise ContextException.new msg end popContext() ret end # @param expression [Expression] parse tree node for exprresion to resolve # @return [Hash:Array] {:symbol,:resolved} hashes, where :resolved is the result # of symbol table lookup def resolveExpression( expr ) ret = [] expr && expr.traverse do |m, node_type, enode, node_val | @logger.debug( "#{__method__} node_type=#{node_type}, node_val=#{node_val}" ) case node_type when "Identifier" ret << resolvedIdentifier( node_val, symbol_table.resolveContext( node_val ) ) when "AdditiveExpression", "MultitiveExpression", "UnaryExpression", "ParenthesisExpression" when "PrimaryExpression" sret = [] enode.attribute_accessors && enode.attribute_accessors.each do |identifier| sret = sret.concat( resolveExpression( identifier )) end ret = ret.concat( sret ) when "FieldByValue" ret << resolvedIdentifier( node_val, symbol_table.resolveContext( node_val ) ) when "FieldByName" when "SequenceExpression" @logger.debug( "#{__method__} SequenceExpression: enode=#{enode.inspect}" ) sret = [] enode.tuples && enode.tuples.each do |tuple_expr| sret = sret.concat( resolveExpression( tuple_expr )) end ret = ret.concat( sret ) when "IfExpression" @logger.debug( "#{__method__} IfExpression: enode=#{enode.inspect}" ) sret = resolveExpression( enode.condition_node ) ret.concat( sret ) @logger.info( "#{__method__} IfExpression: condition-expr=#{sret}" ) sret = resolveExpression( enode.then_expression_node ) ret.concat( sret ) @logger.info( "#{__method__} IfExpression: then-expr=#{sret}" ) sret = [] sret = resolveExpression( enode.else_expression_node ) if enode.else_expression_node ret.concat( sret ) @logger.info( "#{__method__} IfExpression: els-expr=#{sret}" ) when "ChooseExpression" @logger.debug( "#{__method__} ChooseExpression: enode=#{enode.inspect}" ) @logger.debug( "#{__method__} ChooseExpression: enode.binds_node=#{enode.binds_node}" ) # bind node defines symbol table pushContext( enode ) choose_expression=enode.choose_expression @logger.debug( "#{__method__} ChooseExpression: choose_expression=#{choose_expression.inspect}" ) sret = resolveExpression( choose_expression ) @logger.info( "#{__method__} ChooseExpression: resolved sret=#{sret.map {|e| { e[:symbol] => e[:resolved] ? e[:resolved][:symbol_type]+':'+e[:resolved][:context] : nil} }}" ) ret = ret.concat( sret ) popContext() when "RecordDefinition" @logger.debug( "#{__method__} RecordDefinition: enode=#{enode.inspect}" ) sret = [] enode.record_fields.each do |record_field| sret = sret.concat( resolveExpression( record_field.element_expression )) end @logger.info( "#{__method__} RecordDefinition: resolved sret=#{sret.map {|e| { e[:symbol] => e[:resolved] ? e[:resolved][:symbol_type]+':'+e[:resolved][:context] : nil} }}" ) ret = ret.concat( sret ) when "RecordExcept" # resolve where base expression # id = enode.record_identifier # ret << resolvedIdentifier( id, symbol_table.resolveContext( id ) ) bret = resolveExpression( enode.record_base ) ret = ret.concat( bret ) @logger.debug( "#{__method__} RecordExcept: record_base -->=#{bret}" ) sret = [] # resolve EXCEPT constructor field rvalue expression enode.record_field_definitions && enode.record_field_definitions.each do |field_definition| sret = sret.concat( resolveExpression( field_definition.rvalue_expression )) end ret = ret.concat( sret ) when "SetConstructor" sret = [] enode.set_elements && enode.set_elements.each do |set_element| sret = sret.concat( resolveExpression( set_element )) end ret = ret.concat( sret ) when "SetExpressionMap" @logger.debug( "#{__method__} SetExpressionMap: enode=#{enode.inspect}" ) # resolve set mapped sret = resolveExpression( enode.binds_node.bind_set ) ret = ret.concat( sret ) # resolve expression in set map pushContext( enode ) sret = resolveExpression( enode.set_expression ) @logger.debug( "#{__method__} SetExpressionMap: resolved sret=#{sret}" ) ret = ret.concat( sret ) when "SetExpression" @logger.debug( "#{__method__} SetExpression: enode=#{enode.inspect}" ) pushContext( enode ) sret = resolveExpression( enode.set_expression ) @logger.debug( "#{__method__} SetExpression: resolved sret=#{sret}" ) ret = ret.concat( sret ) popContext( ) when "OperatorExpression" @logger.debug( "#{__method__} OperatorExpression: enode=#{enode.inspect}" ) operator_name = enode.operator_name ret << resolvedIdentifier( operator_name, symbol_table.resolveContext( operator_name ) ) sret = [] enode.arguments && enode.arguments.each do |argument_node| sret = sret.concat( resolveExpression( argument_node )) end @logger.info( "#{__method__} OperatorExpression: resolved sret=#{sret.map {|e| { e[:symbol] => e[:resolved] ? e[:resolved][:symbol_type]+':'+e[:resolved][:context] : nil} }}" ) ret = ret.concat( sret ) when "QuantifyExpression" @logger.debug( "#{__method__} SetExpression: node.symbol_definitions=#{enode.symbol_definitions}" ) pushContext( enode ) sret = resolveExpression( enode.binds_nodes.first.bind_set ) ret = ret.concat( sret ) @logger.debug( "#{__method__} resolved sret=#{sret}" ) @logger.debug( "#{__method__} SetExpression: #{enode.quantified_expression.inspect}" ) qret = resolveExpression( enode.quantified_expression ) @logger.debug( "#{__method__} resolved qret=#{qret}" ) ret = ret.concat( qret ) @logger.debug( "#{__method__} resolved ret=#{ret}" ) popContext( ) when "IntegerValue", "StringValue", "OriginalValue" else msg = "Unknwon expression node type #{node_type} with value #{node_val}" @logger.error( "#{__method__} #{msg}" ) raise ContextException.new msg end end ret end # def resolveExpression( expr ) private # @return {Hash} with {:sybol,:resolved} -properties def resolvedIdentifier( identifier, resolved ) { :symbol => identifier, :resolved => resolved } end end # class end