module Melbourne
module AST
# An +alias+ statement as in:
#
# alias x y
#
class Alias < Node
# The element that is aliased
#
attr_accessor :to
# The alias that is defined for the element
#
attr_accessor :from
def initialize(line, to, from) #:nodoc:
@line = line
@to = to
@from = from
end
end
# TODO: document!
class VAlias < Alias #:nodoc:
end
# An +undef+ statement as in:
#
# undef :x
#
class Undef < Node
# The name of the +undef+'d element
#
attr_accessor :name
def initialize(line, sym) #:nodoc:
@line = line
@name = sym
end
end
# A code block as in:
#
# begin
# end
#
# but also as in (implicit block):
#
# a = 1; b = 2
#
class Block < Node
# The statements inside of the block
#
attr_accessor :array
def initialize(line, array) #:nodoc:
@line = line
@array = array
end
# TODO: document!
def strip_arguments #:nodoc:
if @array.first.kind_of? FormalArguments
node = @array.shift
if @array.first.kind_of? BlockArgument
node.block_arg = @array.shift
end
return node
end
end
end
# TODO: document!
class ClosedScope < Node #:nodoc:
attr_accessor :body
end
# A method definition as in:
#
# def method
# end
#
class Define < ClosedScope
# The name of the defined method
#
attr_accessor :name
# The arguments of the defined method
#
attr_accessor :arguments
def initialize(line, name, block) #:nodoc:
@line = line
@name = name
@arguments = block.strip_arguments
block.array << Nil.new(line) if block.array.empty?
@body = block
end
end
# A singleton method definition as in:
#
# def object.method
# end
#
class DefineSingleton < Node
# The receiver of the definition
#
attr_accessor :name
# The body of the defined method
#
attr_accessor :body
def initialize(line, receiver, name, block) #:nodoc:
@line = line
@receiver = receiver
@body = DefineSingletonScope.new line, name, block
end
end
# The scope a singletion method definition opens. This is the actual body of +Melbourne::AST::DefineSingleton+s
#
class DefineSingletonScope < Define
def initialize(line, name, block) #:nodoc:
super line, name, block
end
end
# The formal arguments of a method definition as in:
#
# def method(x) # x is a formal argument of the method
# end
#
class FormalArguments < Node
# The names of the arguments
#
attr_accessor :names
# The names of the required arguments
#
attr_accessor :required
# The names of the optional arguments
#
attr_accessor :optional
# The arguments of the method that have default values
#
attr_accessor :defaults
# The splat (*some) arguments of the method
#
attr_accessor :splat
# The block argument if there is one
#
attr_accessor :block_arg
def initialize(line, args, defaults, splat) #:nodoc:
@line = line
@defaults = nil
@block_arg = nil
if defaults
defaults = DefaultArguments.new line, defaults
@defaults = defaults
@optional = defaults.names
stop = defaults.names.first
last = args.each_with_index { |a, i| break i if a == stop }
@required = args[0, last]
else
@required = args.dup
@optional = []
end
if splat.kind_of? Symbol
args << splat
elsif splat
splat = :@unnamed_splat
args << splat
end
@names = args
@splat = splat
end
def block_arg=(node) #:nodoc:
@names << node.name
@block_arg = node
end
# Gets the arity of the method. The arity is the number of required arguments the method defines.
#
# === Example
#
# def method(a, b = 1) # arity is 1 as the second argument is optional
# end
#
def arity
@required.size
end
# Gets the number of required arguments the method defines.
#
def required_args
@required.size
end
# Gets the total number of all arguments the method defines.
#
def total_args
@required.size + @optional.size
end
# TODO: document!
def splat_index #:nodoc:
if @splat
index = @names.size
index -= 1 if @block_arg
index -= 1 if @splat.kind_of? Symbol
index
end
end
end
# Defaults arguments of a method. This of node also contains the actual statements that define the defaults.
#
class DefaultArguments < Node
# The statements that define the defaults (these are usually Melbourne::AST::LocalVariableAssignment nodes)
#
attr_accessor :arguments
# The names of the arguments
#
attr_accessor :names
def initialize(line, block) #:nodoc:
@line = line
array = block.array
@names = array.map { |a| a.name }
@arguments = array
end
end
module LocalVariable #:nodoc:
attr_accessor :variable
end
# A block argument as in:
#
# def method(&block)
# end
#
class BlockArgument < Node
include LocalVariable
# The name of the block argument
#
attr_accessor :name
def initialize(line, name) #:nodoc:
@line = line
@name = name
end
end
# A class as in:
#
# class X; end
#
class Class < Node
# The name of the +class+
#
attr_accessor :name
# The superclass of the +class+
#
attr_accessor :superclass
# The body of the +class+
#
attr_accessor :body
def initialize(line, name, superclass, body) #:nodoc:
@line = line
@superclass = superclass ? superclass : Nil.new(line)
if name.kind_of? Symbol
@name = ClassName.new line, name, @superclass
else
@name = ScopedClassName.new line, name, @superclass
end
if body
@body = ClassScope.new line, @name, body
else
@body = EmptyBody.new line
end
end
end
# The scope a class definition opens. This is the actual body of +Melbourne::AST::Class+es
#
class ClassScope < ClosedScope
def initialize(line, name, body) #:nodoc:
@line = line
@name = name.name
@body = body
end
end
# A class name as in:
#
# class X; end
#
class ClassName < Node
# The actual name of the class
#
attr_accessor :name
# TODO: document!
attr_accessor :superclass #:nodoc:
def initialize(line, name, superclass) #:nodoc:
@line = line
@name = name
@superclass = superclass
end
end
# A scoped class name as in:
#
# class X::Y; end
#
class ScopedClassName < ClassName
# The parent of the scoped class name; for a class class X::Y; end the scoped class name is +Y+ and the parent is +X+
#
attr_accessor :parent
def initialize(line, parent, superclass) #:nodoc:
@line = line
@name = parent.name
@parent = parent.parent
@superclass = superclass
end
end
# A module as in:
#
# module M; end
#
class Module < Node
# The name of the module
#
attr_accessor :name
# The body of the module
#
attr_accessor :body
def initialize(line, name, body) #:nodoc:
@line = line
if name.kind_of? Symbol
@name = ModuleName.new line, name
else
@name = ScopedModuleName.new line, name
end
if body
@body = ModuleScope.new line, @name, body
else
@body = EmptyBody.new line
end
end
end
# The body of a class that has no body as in:
#
# class X; end
#
class EmptyBody < Node
end
# The name of a module
#
class ModuleName < Node
# The actual name of the module
#
attr_accessor :name
def initialize(line, name) #:nodoc:
@line = line
@name = name
end
end
# A scoped module name as in:
#
# module X::M; end
#
class ScopedModuleName < ModuleName
# The parent of the scoped module name; for a module module X::M; end the scoped module name is +Y+ and the parent is +X+
#
attr_accessor :parent
def initialize(line, parent) #:nodoc:
@line = line
@name = parent.name
@parent = parent.parent
end
end
# The scope a module definition opens. This is the actual body of +Melbourne::AST::Module+s
#
class ModuleScope < ClosedScope
def initialize(line, name, body) #:nodoc:
@line = line
@name = name.name
@body = body
end
end
# A singleton class as in:
#
# class X
# class << self
# end
# end
#
class SClass < Node
# The receiver (for class << self, +self+ is the receiver)
#
attr_accessor :receiver
def initialize(line, receiver, body) #:nodoc:
@line = line
@receiver = receiver
@body = SClassScope.new line, body
end
end
# The scope a singleton class definition opens. This is the actual body of +Melbourne::AST::SClass+s
#
class SClassScope < ClosedScope
def initialize(line, body) #:nodoc:
@line = line
@body = body
@name = nil
end
end
# TODO: document!
class Container < ClosedScope #:nodoc:
attr_accessor :file, :name, :variable_scope
def initialize(body)
@body = body || Nil.new(1)
end
end
# TODO: document!
class EvalExpression < Container #:nodoc:
def initialize(body)
super body
@name = :__eval_script__
end
end
# TODO: document!
class Snippit < Container #:nodoc:
def initialize(body)
super body
@name = :__snippit__
end
end
# TODO: document!
class Script < Container #:nodoc:
def initialize(body)
super body
@name = :__script__
end
end
# A defined? statement as in:
#
# defined? a
#
class Defined < Node
# The expression passed to defined?
#
attr_accessor :expression
def initialize(line, expr) #:nodoc:
@line = line
@expression = expr
end
end
end
end