# 
# Copyright (C) 2006-2019 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

class Treetop::Runtime::SyntaxNode

  # An alias which can be used inside a derived cpp implementation
  # The default implementation collects all objects emitted by the subnodes.
  def get_cpp
    a = elements && elements.collect { |e| e.cpp }.select { |e| e }.flatten
    (a && !a.empty?) ? a : nil
  end

  # Returns the CPP objects emitted by this node. 
  # This method delivers CPP objects or arrays of CPP objects.
  def cpp
    get_cpp
  end

  # Returns the single CPP object or nil
  def cpp_reduced
    a = self.cpp
    if a.is_a?(Array) 
      a.size == 1 || raise("Internal error: more than one syntax tree node")
      a = a[0]
    end
    a
  end

  # Note: this method is required because curly braces cannot be put
  # into parser conditions (treetop syntax flaw?) 
  def text_value_ends_with_curly_brace
    text_value =~ /\}$/
  end

end

# These are a couple of emitter extensions to SyntaxNode which produce the definition syntax tree
# through the "cpp" method:

module PTypeOf 
  def cpp
    CPPTypeOf::new(qid.cpp)
  end
end

def extract_signed(t)
  if t =~ /unsigned/
    :unsigned
  elsif t =~ /signed/
    :signed
  else
    nil
  end
end

def extract_length(t)
  if t =~ /long long/
    :longlong
  elsif t =~ /long/
    :long
  elsif t =~ /short/
    :short
  else
    nil
  end
end

module PIntType 
  def cpp
    CPPPOD::new(extract_signed(text_value), extract_length(text_value), :int)
  end
end

module PCharType 
  def cpp
    CPPPOD::new(extract_signed(text_value), nil, :char)
  end
end

module PBoolType
  def cpp
    CPPPOD::new(nil, nil, :bool)
  end
end

module PSpecialType
  def cpp
    # the special type produces an ID
    CPPQualifiedId::new(false, [ CPPId::new(id.text_value, nil) ])
  end
end

module PFloatType
  def cpp
    kind = text_value =~ /double/ ? :double : :float
    CPPPOD::new(nil, extract_length(text_value), kind)
  end
end

module PVoidType
  def cpp
    CPPPOD::new(nil, nil, :void)
  end
end

module PEnumSpec
  def cpp
    CPPEnumSpec::new(id.text_value, initspec.nonterminal? ? initspec.init.text_value : nil)
  end
end

module PEnumType
  def cpp
    name = id ? id.text_value : nil
    specs = bodyspec.nonterminal? ? bodyspec.body.cpp : nil
    CPPEnum::new(name, specs)
  end
end

module PConst
  def cpp
    CPPConst::new(text_value)
  end
end

module PTemplateArgs
  def cpp
    decl = self.get_cpp
    decl ? CPPTemplateArgs::new(decl) : nil
  end
end

module PId
  def cpp
    CPPId::new(id.text_value, taspec.nonterminal? ? taspec.ta.cpp_reduced : nil)
  end
end

module PQualifiedId
  def cpp
    CPPQualifiedId::new(globalspec.nonterminal?, self.get_cpp)
  end
end

module PBaseClass
  def cpp
    visibility = nil
    virtual = false
    at = attr.text_value
    if at =~ /private/
      visibility = :private
    elsif at =~ /public/
      visibility = :public
    elsif at =~ /protected/
      visibility = :protected
    end
    if at =~ /virtual/
      virtual = true
    end
    CPPBaseClass::new(visibility, virtual, cid.cpp_reduced)
  end
end

module PStructOrClassType

  def cpp

    kind = :class 
    if stype.text_value == "struct"
      kind = :struct
    elsif stype.text_value == "union"
      kind = :union
    end

    id = idspec.nonterminal? ? idspec.id.cpp_reduced : nil
    base_classes = bcspec.nonterminal? ? bcspec.bc.cpp : nil
    body_decl = bodyspec.nonterminal? ? bodyspec.body.cpp : nil

    CPPStruct::new(kind, id, base_classes, body_decl)

  end

end

module PCV
  def to_symbol
    return self.text_value == "const" ? :const : :volatile
  end
end

module PFriendDecl
  def cpp
    CPPFriendDecl::new(t.cpp)
  end
end

module PPointer
  def cpp
    CPPCV::wrap(cvspec.nonterminal? && cvspec.cv.to_symbol, CPPPointer::new(itspec.nonterminal? ? itspec.it.cpp_reduced : CPPAnonymousId::new))
  end
end

module PReference
  def cpp
    CPPCV::wrap(cvspec.nonterminal? && cvspec.cv.to_symbol, CPPReference::new(itspec.nonterminal? ? itspec.it.cpp_reduced : CPPAnonymousId::new))
  end
end

module PMemberPointer
  def cpp
    CPPMemberPointer::new(cspec.qid.cpp, itspec.nonterminal? ? itspec.it.cpp_reduced : CPPAnonymousId::new, cvspec.nonterminal? && cvspec.cv.to_symbol)
  end
end

module PArraySpec
  def cpp
    CPPArray::new(nil)
  end
end

module PFuncArgPart
  def cpp
    t.cpp_reduced
  end
end

module PFuncSpec
  def cpp
    CPPFunc::new(nil, (fa.nonterminal? && fa.text_value != "void") ? (fa.a.cpp || []) : [], cvspec.nonterminal? && cvspec.cv.to_symbol)
  end
end

module PInnerTypeWithCV
  def cpp
    CPPCV::wrap(cvspec.to_symbol, it.cpp_reduced)
  end
end

module PInnerType
  def cpp
    if pfx.nonterminal?
      pfx.elements.inject(it.cpp_reduced) do |r,e|
        ee = e.spec.cpp_reduced
        ee.inner = r
        ee
      end
    else
      it.cpp
    end
  end
end

module PStorageClass
  def cpp
    if text_value =~ /^static/
      return CPPAttr::new(:static)
    elsif text_value =~ /^extern/
      return CPPAttr::new(:extern)
    else
      return nil
    end
  end
end

module PVirtual
  def cpp 
    return CPPAttr::new(:virtual)
  end
end

module PInline
  def cpp 
    return CPPAttr::new(:inline)
  end
end

module PType

  def cpp
    # This is the class/struct/union/enum declaration if there is one
    d = ct.cpp
    if d.is_a?(Array)
      r = d.select { |i| i.is_a?(CPPStruct) || i.is_a?(CPPEnum) }
    elsif d.is_a?(CPPStruct) || d.is_a?(CPPEnum)
      r = [d]
    else
      r = []
    end
    # Create each declaration
    ot = CPPCV::wrap(cvspec.nonterminal? && cvspec.cv.to_symbol, ct.cpp_reduced)
    if il.nonterminal? 
      r << CPPType::new(ot, il.t1.cpp_reduced, il.i1.nonterminal? ? il.i1.is1.text_value : nil)
      il.tt.elements.each do |t|
        r << CPPType::new(ot, t.t2.cpp_reduced, t.i2.nonterminal? ? t.i2.is2.text_value : nil) 
      end
    else
      r << CPPType::new(ot, CPPAnonymousId::new, pi.nonterminal? ? pi.is.text_value : nil)
    end
    r
  end

end

module PEllipsis
  def cpp
    CPPEllipsis::new
  end
end

module PTypeWoComma
  def cpp
    ot = CPPCV::wrap(cvspec.nonterminal? && cvspec.cv.to_symbol, ct.cpp_reduced)
    if il.nonterminal?
      CPPType::new(ot, il.t.cpp_reduced, il.i.nonterminal? ? il.i.is.text_value : nil)
    else
      CPPType::new(ot, CPPAnonymousId::new, pi.nonterminal? ? pi.is.text_value : nil)
    end
  end
end

module PTypeForTemplate
  def cpp
    ot = CPPCV::wrap(cvspec.nonterminal? && cvspec.cv.to_symbol, ct.cpp_reduced)
    CPPType::new(ot, il.nonterminal? ? il.t.cpp_reduced : CPPAnonymousId::new, nil)
  end
end

module PUsing
  def cpp
    CPPUsingSpec::new(id.cpp_reduced, :default)
  end
end

module PTypedef
  def cpp
    t.cpp.collect do |d|
      if d.is_a?(CPPStruct)
        CPPStructDeclaration::new(d, nil, :default)
      elsif d.is_a?(CPPEnum)
        CPPEnumDeclaration::new(d, :default)
      else
        CPPTypedef::new(d, :default)
      end
    end
  end
end

module PPrivateClassStructBodyDeclarations
  def cpp
    decl = d.cpp
    decl && decl.collect do |d| 
      if d.respond_to?(:visibility) 
        d.visibility = :private
      end
      d
    end
  end
end

module PProtectedClassStructBodyDeclarations
  def cpp
    decl = d.cpp
    decl && decl.collect do |d| 
      if d.respond_to?(:visibility) 
        d.visibility = :protected
      end
      d
    end
  end
end

module PPublicClassStructBodyDeclarations
  def cpp
    decl = d.cpp
    decl && decl.collect do |d| 
      if d.respond_to?(:visibility) 
        d.visibility = :public
      end
      d
    end
  end
end

module PTemplateDecl
  def cpp
    CPPTemplateDecl::new(self.get_cpp)
  end
end

module PClassTemplateArg
  def cpp
    CPPClassTemplateArg::new(id.text_value, dtspec.nonterminal? ? dtspec.cpp : nil)
  end
end

module PDirectTemplateArg
  def cpp
    CPPDirectTemplateArg::new(t.cpp, initspec.nonterminal? ? initspec.text_value : nil)
  end
end

module PDeclaration
  def cpp
    td = nil
    if template.nonterminal? 
      td = template.d.cpp
    end
    storage_class = nil
    virtual = nil
    inline = nil
    if attr.nonterminal? && attr.cpp
      attr.cpp.each do |d|
        if d.attr == :virtual
          virtual = true
        elsif d.attr == :inline
          inline = true
        elsif d.attr == :static
          storage_class = :static
        elsif d.attr == :extern
          storage_class = :extern
        end
      end
    end
    # TODO: abstract declaration determination should be based on initializers on the
    # inner types
    t.cpp.collect do |d|
      if d.is_a?(CPPStruct)
        CPPStructDeclaration::new(d, td, :default)
      elsif d.is_a?(CPPEnum)
        CPPEnumDeclaration::new(d, :default)
      else
        CPPDeclaration::new(d, td, :default, storage_class, virtual, inline)
      end
    end
  end
end

module PExternBlock
  def cpp
    self.get_cpp.collect do |d| 
      if d.is_a?(CPPDeclaration)
        d.storage_class = :extern
      end
      d
    end
  end
end

module PNamespace
  def cpp
    CPPNamespace::new(n.text_value, decls.cpp || [])
  end
end

module PModule
  def cpp
    CPPModule::new(self.get_cpp)
  end
end
