# Defines a **`Struct`** type called *name* with the given *properties*. # # The generated struct has a constructor with the given properties # in the same order as declared. The struct only provides getters, # not setters, making it immutable by default. # # ``` # record Point, x : Int32, y : Int32 # # p = Point.new 1, 2 # => # # p.x # => 1 # p.y # => 2 # ``` # # The *properties* are a sequence of type declarations (`x : Int32`, `x : Int32 = 0`) # or assigns (`x = 0`). # They declare instance variables and respective getter methods of their name with # optional type restrictions and default value. # # When passing a block to this macro its body is inserted inside # the struct definition. This allows to define additional methods or include modules # into the record type (reopening the type would work as well). # # ``` # record Person, first_name : String, last_name : String do # def full_name # "#{first_name} #{last_name}" # end # end # # person = Person.new "John", "Doe" # person.full_name # => "John Doe" # ``` # # An example with type declarations and default values: # # ``` # record Point, x : Int32 = 0, y : Int32 = 0 # # Point.new # => # # Point.new y: 2 # => # # ``` # # An example with assignments (in this case the compiler must be able to # infer the types from the default values): # # ``` # record Point, x = 0, y = 0 # # Point.new # => # # Point.new y: 2 # => # # ``` # # This macro also provides a `#copy_with` method which returns # a copy of the record with the provided properties altered. # # ``` # record Point, x = 0, y = 0 # # p = Point.new y: 2 # => # # p.copy_with x: 3 # => # # p # => # # ``` macro record(__name name, *properties, **kwargs) {% raise <<-TXT unless kwargs.empty? macro `record` does not accept named arguments Did you mean: record #{name}, #{(properties + kwargs.map { |name, value| "#{name} : #{value}" }).join(", ").id} TXT %} struct {{name.id}} {% for property in properties %} {% if property.is_a?(Assign) %} {% if compare_versions(Crystal::VERSION, "1.11.0") >= 0 && property.doc != "" %}# {{property.doc.gsub(/\n/, "\n# ").id}}{% end %} getter {{property.target.id}} {% elsif property.is_a?(TypeDeclaration) %} {% if compare_versions(Crystal::VERSION, "1.11.0") >= 0 %} {% unless property.doc == "" %}# {{property.doc.gsub(/\n/, "\n# ").id}}{% end %} getter {{property.var.id}} : {{property.type}}{% if !property.value.nil? || property.value.stringify == "nil" %} = {{property.value}}{% end %} {% else %} getter {{property}} {% end %} {% else %} {% if compare_versions(Crystal::VERSION, "1.11.0") >= 0 && property.doc != "" %}# {{property.doc.gsub(/\n/, "\n# ").id}}{% end %} getter :{{property.id}} {% end %} {% end %} def initialize({{ properties.map do |field| "@#{field.id}".id end.splat }}) end {{yield}} def copy_with({{ properties.map do |property| if property.is_a?(Assign) "#{property.target.id} _#{property.target.id} = @#{property.target.id}".id elsif property.is_a?(TypeDeclaration) "#{property.var.id} _#{property.var.id} = @#{property.var.id}".id else "#{property.id} _#{property.id} = @#{property.id}".id end end.splat }}) self.class.new({{ properties.map do |property| if property.is_a?(Assign) "_#{property.target.id}".id elsif property.is_a?(TypeDeclaration) "_#{property.var.id}".id else "_#{property.id}".id end end.splat }}) end def clone self.class.new({{ properties.map do |property| if property.is_a?(Assign) "@#{property.target.id}.clone".id elsif property.is_a?(TypeDeclaration) "@#{property.var.id}.clone".id else "@#{property.id}.clone".id end end.splat }}) end end end # Prints a series of expressions together with their pretty printed values. # Useful for print style debugging. # # ``` # a = 1 # pp! a # => "a # => 1" # # pp! [1, 2, 3].map(&.to_s) # => "[1, 2, 3].map(&.to_s) # => ["1", "2", "3"]" # ``` # # See also: `pp`, `Object#pretty_inspect`. macro pp!(*exps) {% if exps.size == 0 %} # Nothing {% elsif exps.size == 1 %} {% exp = exps.first %} %prefix = "#{{{ exp.stringify }}} # => " ::print %prefix ::pp({{exp}}) {% else %} %names = { {{exps.map(&.stringify).splat}} } %max_size = %names.max_of &.size { {% for exp, i in exps %} begin %prefix = "#{%names[{{i}}].ljust(%max_size)} # => " ::print %prefix ::pp({{exp}}) end, {% end %} } {% end %} end # Prints a series of expressions together with their inspected values. # Useful for print style debugging. # # ``` # a = 1 # p! a # => "a # => 1" # # p! [1, 2, 3].map(&.to_s) # => "[1, 2, 3].map(&.to_s) # => ["1", "2", "3"]" # ``` # # See also: `p`, `Object#inspect`. macro p!(*exps) {% if exps.size == 0 %} # Nothing {% elsif exps.size == 1 %} {% exp = exps.first %} %prefix = "#{{{ exp.stringify }}} # => " ::print %prefix ::p({{exp}}) {% else %} %names = { {{exps.map(&.stringify).splat}} } %max_size = %names.max_of &.size { {% for exp, i in exps %} begin %prefix = "#{%names[{{i}}].ljust(%max_size)} # => " ::print %prefix ::p({{exp}}) end, {% end %} } {% end %} end