# :stopdoc:

##
# I'm starting to warm up to this idea!
# ENV["STRICT_SEXP"] turns on various levels of conformance checking
#
# 1 = sexp[0]         => sexp_type
# 1 = sexp.first      => sexp_type
# 1 = sexp[0] = x     => sexp_type = x
# 1 = sexp[1..-1]     => sexp_body
# 1 = sexp[1..-1] = x => sexp_body = x
# 1 = sexp[-1]        => last
# 2 = sexp[1]         => no
# 2 = sexp[1] = x     => no
# 3 = sexp[n]         => no
# 3 = sexp[n] = x     => no
# 3 = sexp.node_name  => no (ie, method_missing)
# 4 = sexp.replace x  => no
# 4 = sexp.concat x   => no
# 4 = sexp.collect!   => no
# 4 = sexp.compact!   => no
# 4 = sexp.flatten!   => no
# 4 = sexp.map!       => no
# 4 = sexp.reject!    => no
# 4 = sexp.reverse!   => no
# 4 = sexp.rotate!    => no
# 4 = sexp.select!    => no
# 4 = sexp.shuffle!   => no
# 4 = sexp.slice!     => no
# 4 = sexp.sort!      => no
# 4 = sexp.sort_by!   => no
# 4 = sexp.uniq!      => no
# 4 = sexp.unshift    => no
# 4 = sexp.push       => no
# 4 = sexp.pop        => no
# 4 = sexp <<         => no

class Sexp
  alias :safe_idx   :[]
  alias :safe_asgn  :[]=
  alias :sexp_type= :sexp_type=
  alias :sexp_body= :sexp_body=
  alias :shift :shift

  def self.nuke_method name, level
    define_method name do |*args|
      raise "no mutation allowed on sexps: %s.%s %s" % [self, name, args]
    end if __strict >= level
  end

  def self.__strict
    ENV["STRICT_SEXP"].to_i
  end

  def __strict
    self.class.__strict
  end

  undef_method :method_missing if __strict > 2

  def method_missing msg, *args
    raise "don't call method_missing on Sexps: %p.(%s)" % [msg, args.inspect[1..-2]]
  end if __strict > 2

  def [] i
    raise "no idx: #{inspect}[#{i}]" if __strict > 2
    raise "no idx>1: #{inspect}[#{i}]" if Integer === i && i > 1 if __strict > 1
    raise "use sexp_type" if i == 0
    raise "use sexp_body" if i == (1..-1)
    raise "use last" if i == -1
    self.safe_idx i
  end

  def []= i, v
    raise "use sexp_type=" if i == 0
    raise "use sexp_body=" if i == (1..-1)
    raise "no asgn>1: #{inspect}[#{i}] = #{v.inspect}" if Integer === i && i > 1 if
      __strict > 1
    raise "no asgn: #{inspect}[#{i}] = #{v.inspect}" if
      __strict > 2
    self.safe_asgn i, v
  end

  def first
    raise "use sexp_type"
  end

  nuke_method :collect!, 4
  nuke_method :compact!, 4
  # nuke_method :concat,   4 # HACK: using self.class.new.concat(...) for speed 
  nuke_method :flatten!, 4
  nuke_method :map!,     4
  nuke_method :pop,      4
  nuke_method :push,     4
  nuke_method :reject!,  4
  nuke_method :replace,  4
  nuke_method :reverse!, 4
  nuke_method :rotate!,  4
  nuke_method :select!,  4
  nuke_method :shuffle!, 4
  nuke_method :slice!,   4
  nuke_method :sort!,    4
  nuke_method :sort_by!, 4
  nuke_method :uniq!,    4
  nuke_method :unshift,  4
  nuke_method :<<,       5
  nuke_method :shift,    5

  def sexp_type
    safe_idx 0
  end

  def sexp_body from = 1
    self.new.concat(safe_idx(from..-1) || [])
  end

  def sexp_type= v
    self.safe_asgn 0, v
  end

  def sexp_body= v
    self.safe_asgn 1..-1, v
  end
end unless Sexp.new.respond_to? :safe_asgn if ENV["STRICT_SEXP"]

# :startdoc:
