require 'yaml' require 'lib/semantic_types' module Semantic # Parsing and storing of the syntactic functions. # The Functions class subclasses the Hash class. It maps nonterminal symbols to its expansion hashes of the semantic functions: # # { # "symbol1" => { "expansion1_1"=>[ AttrFn, AttrFn ... ], "expansion1_2"=>[ AttrFn, AttrFn ... ], ... }, # "symbol2" => { "expansion2_1"=>[ AttrFn, AttrFn ... ], "expansion2_2"=>[ AttrFn, AttrFn ... ], ... }, # ... # } # class Functions < Hash # Load the semantic file. The file has to follow this form: # # symbol1: # expansion1: # result1_1 : function1_1 # result1_2 : function1_2 # ... # result1_N1 : function1_N1 # expansion2: # result2_1 : function2_1 # ... # result2_N2 : function2_N2 # ... # expansionM: # ... # symbol2: # ... # symbolP: # ... # # where: # - symbol ... a nonterminal symbol defined by the ABNF grammar (see Mapper::Grammar) # - expansion ... the expansion of the symbol defined by the grammar. # All terminal symbols are matched by the '$' character. # For instance, the ABNF rule: # # if-statement = "if(" condition ") {" block "} else {" block "}" # # is represented by: # # if-statement: # $ condition $ block $ block $: # # There is another special character '*' which matches all expansions. # - result ... the semantic attribute of some symbol defined by the semantic function. # It has the form: # node.attribute # where attribute is the identifier of the attribute and node is either # p (parent node) or ci (child node, i is the index of the child beginning from 0). # # There are reserved attribute identifiers: # _text .. represents the text of the terminal symbol or the symbol identifier # of the nonterminal symbol # _valid .. the boolean attribute which restricts the usage of the expansion # (see AttrGrDepthFirst class for details) # - function ... the semantic function using node.attribute arguments, using the Ruby syntax. # # # Notes: The '_' identifier is reserved and cannot be used as the attribute name or another variable name. # If the attribute's 'nil' value means 'not defined'. Do not use 'nil' as a result of a semantic function. # def initialize semantic=nil super() @attributes = AttrIndices.clone # default ctor return if semantic.nil? # parsing ctor yaml = YAML::load( semantic ) yaml.keys.sort.each do |symbol| rules = yaml[symbol] newrules = {} rules.each_pair do |expansion,funcs| batch = [] funcs.each_pair do |dest,body| target = new_attr_ref dest args = Functions.extract_args( body ) inputs = args.map { |arg| new_attr_ref arg } fn = Functions.make_proc( Functions.replace_args(body,args) ) batch << AttrFn.new( fn, target, inputs, body ) end newrules[expansion] = batch end store( symbol, newrules ) end end # Attribute identifier array. It contains all attribute identifiers appearing on both # the left and right sides of semantic fuctions. For example: # # [ '_text', '_valid', 'cont', 'def', 'span' ] # # Note that reserved identifiers beginning with '_' are always present. # attr_reader :attributes # Convert the Mapper::RuleAlt into the representing string (the expansion key). def Functions.match_key rulealt result = rulealt.map do |token| case token.type when :literal '$' when :symbol token.data else raise 'Semantic::Functions wrong token type' end end result.join ' ' end # Extract all arguments from the body of the semantic function. def Functions.extract_args text text.scan( /[\w]+\.[\w]+/ ).uniq end # Replace argument identifiers in the source of the semantic function by the _[i] notation. def Functions.replace_args( text_orig, args ) text = text_orig.clone args.each_with_index { |a,i| text.gsub!( a, "_[#{i}]" ) } text end # Compile the semantic function previously processed by the extract_args and replace_args. def Functions.make_proc text eval( "proc { |_| #{text} }" ) end # Search for the appropriate semantic functions by the particular nonterminal symbol and the RuleAlt expansion. def node_expansion( symbol, expansion ) node = fetch(symbol.data, nil) return [] if node.nil? batch = deep_copy( node.fetch( Functions.match_key(expansion), [] ) ) return batch.concat( deep_copy( node.fetch( '*', [] ) ) ) end # Convert the textual representation of the attribute to the AttrRef (the inversion is done by the render_attr method). # For instance: 'c1._text' -> AttrRef.new( 2, 0 ). # Note it modifies @attributes if given a new attribute identifier. def new_attr_ref text all,node,attr = /^([^.]*)\.([^.].*)$/.match( text ).to_a raise "Semantic::Functions wrong node/attribute '#{text}'" if all.nil? idx = @attributes.index attr if idx.nil? idx = @attributes.size @attributes.push attr end return AttrRef.new( 0, idx ) if node == 'p' raise "Semantic::Functions wrong node '#{text}'" unless node.size > 1 and /^c/ =~ node return AttrRef.new( node[1,node.size-1].to_i+1, idx ) end # Convert the AttrRef to the textual representation of the attribute (the inversion is done by the new_attr_ref). # For example: AttrRef.new( 0, 1 ) -> 'p._valid'. def render_attr ref "#{ ref.node_idx==0 ? 'p' : 'c'+(ref.node_idx-1).to_s }.#{ @attributes[ ref.attr_idx ] }" end protected def deep_copy funcs funcs.map { |f| AttrFn.new(f.func, f.target, f.args.clone, f.orig) } end end end