lib/cog/language.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module Cog

  # Describes a language support by Cog
  class Language

    # @return [Array<String>] list of file extensions
    attr_reader :extensions

    attr_reader :seed_extension
    attr_reader :seed_header
    
    # @return [String] unique lower case identifier
    attr_reader :key

    # @return [String] readable name for the language
    attr_reader :name
    
    # @return [String] the style of comments used by this language
    attr_reader :comment_style
    
    # @return [String] the style of include guards used by this language
    attr_reader :include_guard_style
      
    # @api developer
    # Initialize with default values
    # @param key [String] unique case-insensitive identifier
    def initialize(key = :text)
      @key = key.to_s.downcase
      @name = key.to_s
      @comment_pattern = '^\s*(%s)\s*$'
      @comment_prefix = nil
      @multiline_comment_prefix = nil
      @multiline_comment_postfix = nil
      @extensions = []
      identibitch = lambda {|name| ''}
      @use_named_scope_block = identibitch
      @named_scope_begin_block = identibitch
      @named_scope_end_block = identibitch
      @include_guard_begin_block = identibitch
      @include_guard_end_block = identibitch
      @reserved = []
      @prim_ident = {} # :name => 'ident'
      @prim_to_lit = {} # :name => to_literal_block
    end
      
    # @param nested_pattern [String] regular expression pattern (as a string) to embed in the regular expression which matches one line comments in this language
    # @return [Regexp] pattern for matching one line comments in this language
    def comment_pattern(nested_pattern)
      Regexp.new(@comment_pattern % nested_pattern)
    end
      
    # @param text [String] some text which should be rendered as a comment
    # @return [String] a comment appropriate for this language
    def comment(text)
      if text =~ /\n/
        multi_line_comment text
      else
        one_line_comment text
      end
    end

    # @api developer
    def one_line_comment(text)
      if @comment_prefix
        "#{@comment_prefix} #{text}"
      elsif @multiline_comment_prefix
        "#{@multiline_comment_prefix} #{text} #{@multiline_comment_postfix}"
      else
        text
      end
    end
      
    # @api developer
    def multi_line_comment(text)
      if @multiline_comment_prefix
        "#{@multiline_comment_prefix}\n#{text}\n#{@multiline_comment_postfix}"
      elsif @comment_prefix
        text.split("\n").collect do |line|
          "#{@comment_prefix} #{line}"
        end.join("\n")
      else
        text
      end
    end
    
    # @api developer
    # Called after all Cogfiles have been processed
    # @param other [Language] language to borrow notation from
    def apply_comment_style(other)
      @comment_prefix = other.instance_eval {@comment_prefix}
      @multiline_comment_prefix = other.instance_eval {@multiline_comment_prefix}
      @multiline_comment_postfix = other.instance_eval {@multiline_comment_postfix}
      @comment_pattern = other.instance_eval {@comment_pattern}
    end
    
    # @api developer
    # Called after all Cogfiles have been processed
    # @param other [Language] language to borrow notation from
    def apply_include_guard_style(other)
      @include_guard_begin_block = other.instance_eval {@include_guard_begin_block}
      @include_guard_end_block = other.instance_eval {@include_guard_end_block}
    end
    
    # @param w [FixNum] width of the first column
    # @return [String] one line summary in two columns
    def to_s(w=nil)
      w ||= @name.length
      "#{@name.ljust w} -> #{@extensions.collect {|x| x.to_s}.sort.join ', '}"
    end
    
    # Sort by name
    def <=>(other)
      @name <=> other.name
    end
    
    # @param name [String] name of the scope to use
    # @return [String] a using statement for the named scope
    def use_named_scope(name)
      @use_named_scope_block.call name
    end
      
    # @param name [String] name of the scope
    # @return [String] begin a named scope
    def named_scope_begin(name)
      @named_scope_begin_block.call name
    end

    # @param name [String] name of the scope
    # @return [String] end the given named scope
    def named_scope_end(name)
      @named_scope_end_block.call name
    end
      
    # @param name [String] name of the module to protect
    # @return [String] an include guard statement
    def include_guard_begin(name)
      @include_guard_begin_block.call name
    end
      
    # @param name [String] name of the module to protect
    # @return [String] an include guard end statement
    def include_guard_end(name)
      @include_guard_end_block.call name
    end
    
    # @param name [String] a potential identifier name
    # @return [String] an escaped version of the identifier, if it conflicted with a reserved word in the language
    def to_ident(name)
      if @reserved.member? name.to_s
        "#{name}_"
      else
        name.to_s
      end
    end
    
    # @param name [Symbol] name of a primitive cog type
    # @return [String] the representation of a primitive cog type in the native language
    # @example
    #   # For Objective-C
    #   lang.to_prim :boolean # => 'BOOL'
    def to_prim(name)
      ident = @prim_ident[name.to_sym]
      raise Errors::PrimitiveNotSupported.new :type => name, :language => @name unless ident
      ident
    end

    # @param obj [Object] a ruby object
    # @return [String] boolean literal representation of the object in this language
    def to_boolean(obj) ; try_to_lit(:boolean, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] integer literal representation of the object in this language
    def to_integer(obj) ; try_to_lit(:integer, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] long literal representation of the object in this language
    def to_long(obj) ; try_to_lit(:long, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] float literal representation of the object in this language
    def to_float(obj) ; try_to_lit(:float, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] double literal representation of the object in this language
    def to_double(obj) ; try_to_lit(:double, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] char literal representation of the object in this language
    def to_char(obj) ; try_to_lit(:char, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] string literal representation of the object in this language
    def to_string(obj) ; try_to_lit(:string, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] null literal representation of the object in this language
    def to_null(obj) ; try_to_lit(:null, obj) ; end

    # @param obj [Object] a ruby object
    # @return [String] literal representation of the object in this language
    def to_lit(obj)
      return obj.to_lit if obj.respond_to?(:to_lit)
      raise Errors::PrimitiveNotSupported.new :object => obj
    end

    # @param name [Symbol] name of a primitive cog type
    # @return [String] default value literal for the given primitive cog type
    # @example
    #   # For C++
    #   lang.to_default_value :string # => '""'
    def default_lit_for(name)
      case name
      when :boolean
        to_boolean false
      when :integer
        to_integer 0
      when :long
        to_long 0
      when :float
        to_lit 0.0
      when :double
        to_double 0.0
      when :char
        to_char ''
      when :string
        to_string ''
      when :null
        to_null nil
      end
    end
    
    private    
    
    # @api developer
    def try_to_lit(name, obj)
      to_lit_block = @prim_to_lit[name]
      raise Errors::PrimitiveNotSupported.new :type => name, :language => @name unless to_lit_block
      to_lit_block.call obj if to_lit_block
    end
    
  end
end