From 11b8b96a861ea8c67b0c0a89c254beeda7105e94 Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Tue, 28 Jan 2025 17:30:07 +0100 Subject: [PATCH] Generate Object getter/property macros to remove duplications (#15386) --- scripts/generate_object_properties.cr | 862 +++++++++++++ src/object.cr | 948 +------------- src/object/properties.cr | 1722 +++++++++++++++++++++++++ 3 files changed, 2586 insertions(+), 946 deletions(-) create mode 100755 scripts/generate_object_properties.cr create mode 100644 src/object/properties.cr diff --git a/scripts/generate_object_properties.cr b/scripts/generate_object_properties.cr new file mode 100755 index 000000000000..38c7a4d8d1d9 --- /dev/null +++ b/scripts/generate_object_properties.cr @@ -0,0 +1,862 @@ +#! /usr/bin/env crystal + +struct Generator + def initialize(@file : File, @macro_prefix : String, @method_prefix : String, @var_prefix : String, @doc_prefix : String) + end + + def puts + @file.puts + end + + def puts(message) + @file.puts(message) + end + + def def_vars + <<-TEXT + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + #{@var_prefix}{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + #{@var_prefix}{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + #{@var_prefix}{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + TEXT + end + + def def_getter(suffix = "") + <<-TEXT + def #{@method_prefix}{{var_name}}#{suffix} {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = #{@var_prefix}{{var_name}}).nil? + #{@var_prefix}{{var_name}} = {{yield}} + else + %value + end + {% else %} + #{@var_prefix}{{var_name}} + {% end %} + end + + TEXT + end + + def def_vars! + <<-TEXT + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + #{@var_prefix}{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + TEXT + end + + def def_getter! + <<-TEXT + def #{@method_prefix}{{var_name}}? {% if type %} : {{type}}? \{% end %} + #{@var_prefix}{{var_name}} + end + + def #{@method_prefix}{{var_name}} {% if type %} : {{type}} {% end %} + if (%value = #{@var_prefix}{{var_name}}).nil? + ::raise ::NilAssertionError.new("{{@type.id}}{{#{@doc_prefix.inspect}.id}}{{var_name}} cannot be nil") + else + %value + end + end + + TEXT + end + + def def_setter + <<-TEXT + def #{@method_prefix}{{var_name}}=(#{@var_prefix}{{var_name}}{% if type %} : {{type}} {% end %}) + end + + TEXT + end + + def gen_getter + puts <<-TEXT + # Defines getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # #{@macro_prefix}getter name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}name + # #{@var_prefix}name + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # #{@macro_prefix}getter :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # #{@macro_prefix}getter name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String + # + # def #{@method_prefix}name : String + # #{@var_prefix}name + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # #{@macro_prefix}getter name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String = "John Doe" + # + # def #{@method_prefix}name : String + # #{@var_prefix}name + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # #{@macro_prefix}getter name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name = "John Doe" + # + # def #{@method_prefix}name : String + # #{@var_prefix}name + # end + # end + # ``` + # + # If a block is given to the macro, a getter is generated + # with a variable that is lazily initialized with + # the block's contents: + # + # ``` + # class Person + # #{@macro_prefix}getter(birth_date) { Time.local } + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}birth_date + # if (value = #{@var_prefix}birth_date).nil? + # #{@var_prefix}birth_date = Time.local + # else + # value + # end + # end + # end + # ``` + macro #{@macro_prefix}getter(*names, &block) + {% for name in names %} + #{def_vars} + #{def_getter} + {% end %} + end + TEXT + end + + def gen_getter? + puts <<-TEXT + # Defines query getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # #{@macro_prefix}getter? happy + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}happy? + # #{@var_prefix}happy + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # #{@macro_prefix}getter? :happy, "famous" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # #{@macro_prefix}getter? happy : Bool + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}happy : Bool + # + # def #{@method_prefix}happy? : Bool + # #{@var_prefix}happy + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # #{@macro_prefix}getter? happy : Bool = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}happy : Bool = true + # + # def #{@method_prefix}happy? : Bool + # #{@var_prefix}happy + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # #{@macro_prefix}getter? happy = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}happy = true + # + # def #{@method_prefix}happy? + # #{@var_prefix}happy + # end + # end + # ``` + # + # If a block is given to the macro, a getter is generated with a variable + # that is lazily initialized with the block's contents, for examples see + # `##{@macro_prefix}getter`. + macro #{@macro_prefix}getter?(*names, &block) + {% for name in names %} + #{def_vars} + #{def_getter "?"} + {% end %} + end + TEXT + end + + def gen_property + puts <<-TEXT + # Defines property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # #{@macro_prefix}property name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}name=(#{@var_prefix}name) + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # #{@macro_prefix}property :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # #{@macro_prefix}property name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String + # + # def #{@method_prefix}name=(#{@var_prefix}name) + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # #{@macro_prefix}property name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String = "John Doe" + # + # def #{@method_prefix}name=(#{@var_prefix}name : String) + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # #{@macro_prefix}property name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name = "John Doe" + # + # def #{@method_prefix}name=(#{@var_prefix}name : String) + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name + # end + # end + # ``` + # + # If a block is given to the macro, a property is generated + # with a variable that is lazily initialized with + # the block's contents: + # + # ``` + # class Person + # #{@macro_prefix}property(birth_date) { Time.local } + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}birth_date + # if (value = #{@var_prefix}birth_date).nil? + # #{@var_prefix}birth_date = Time.local + # else + # value + # end + # end + # + # def #{@method_prefix}birth_date=(#{@var_prefix}birth_date) + # end + # end + # ``` + macro #{@macro_prefix}property(*names, &block) + {% for name in names %} + #{def_vars} + #{def_getter} + #{def_setter} + {% end %} + end + TEXT + end + + def gen_property? + puts <<-TEXT + # Defines query property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # #{@macro_prefix}property? happy + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}happy=(#{@var_prefix}happy) + # end + # + # def #{@method_prefix}happy? + # #{@var_prefix}happy + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # #{@macro_prefix}property? :happy, "famous" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # #{@macro_prefix}property? happy : Bool + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}happy : Bool + # + # def #{@method_prefix}happy=(#{@var_prefix}happy : Bool) + # end + # + # def #{@method_prefix}happy? : Bool + # #{@var_prefix}happy + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # #{@macro_prefix}property? happy : Bool = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}happy : Bool = true + # + # def #{@method_prefix}happy=(#{@var_prefix}happy : Bool) + # end + # + # def #{@method_prefix}happy? : Bool + # #{@var_prefix}happy + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # #{@macro_prefix}property? happy = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}happy = true + # + # def #{@method_prefix}happy=(#{@var_prefix}happy) + # end + # + # def #{@method_prefix}happy? + # #{@var_prefix}happy + # end + # end + # ``` + # + # If a block is given to the macro, a property is generated + # with a variable that is lazily initialized with + # the block's contents, for examples see `##{@macro_prefix}property`. + macro #{@macro_prefix}property?(*names, &block) + {% for name in names %} + #{def_vars} + #{def_getter "?"} + #{def_setter} + {% end %} + end + TEXT + end + + def gen_getter! + puts <<-TEXT + # Defines raise-on-nil and nilable getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # #{@macro_prefix}getter! name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}name? + # #{@var_prefix}name + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name.not_nil! + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # #{@macro_prefix}getter! :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type, as nilable. + # + # ``` + # class Person + # #{@macro_prefix}getter! name : String + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String? + # + # def #{@method_prefix}name? + # #{@var_prefix}name + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name.not_nil! + # end + # end + # ``` + macro #{@macro_prefix}getter!(*names) + {% for name in names %} + #{def_vars!} + #{def_getter!} + {% end %} + end + TEXT + end + + def gen_property! + puts <<-TEXT + # Defines raise-on-nil property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # #{@macro_prefix}property! name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}name=(#{@var_prefix}name) + # end + # + # def #{@method_prefix}name? + # #{@var_prefix}name + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name.not_nil! + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # #{@macro_prefix}property! :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type, as nilable. + # + # ``` + # class Person + # #{@macro_prefix}property! name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String? + # + # def #{@method_prefix}name=(#{@var_prefix}name) + # end + # + # def #{@method_prefix}name? + # #{@var_prefix}name + # end + # + # def #{@method_prefix}name + # #{@var_prefix}name.not_nil! + # end + # end + # ``` + macro #{@macro_prefix}property!(*names) + {% for name in names %} + #{def_vars!} + #{def_getter!} + #{def_setter} + {% end %} + end + TEXT + end + + def gen_setter + puts <<-TEXT + # Defines setter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # #{@macro_prefix}setter name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def #{@method_prefix}name=(#{@var_prefix}name) + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # #{@macro_prefix}setter :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # #{@macro_prefix}setter name : String + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String + # + # def #{@method_prefix}name=(#{@var_prefix}name : String) + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # #{@macro_prefix}setter name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name : String = "John Doe" + # + # def #{@method_prefix}name=(#{@var_prefix}name : String) + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # #{@macro_prefix}setter name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # #{@var_prefix}name = "John Doe" + # + # def #{@method_prefix}name=(#{@var_prefix}name) + # end + # end + # ``` + macro #{@macro_prefix}setter(*names) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + #{@var_prefix}{{name}} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + #{@var_prefix}{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + #{def_setter} + {% end %} + end + TEXT + end +end + +directory = File.expand_path("../src/object", __DIR__) +Dir.mkdir(directory) unless Dir.exists?(directory) + +output = File.join(directory, "properties.cr") +File.open(output, "w") do |f| + f.puts "# WARNING: THIS FILE HAS BEEN AUTOGENERATED BY scripts/generate_object_properties.cr" + f.puts "# WARNING: DO NOT EDIT MANUALLY!" + f.puts + f.puts "class Object" + + g = Generator.new(f, "", "", "@", "#") + + g.gen_getter + g.gen_getter? + g.gen_getter! + + g.gen_setter + + g.gen_property + g.gen_property? + g.gen_property! + + g = Generator.new(f, "class_", "self.", "@@", ".") + + g.gen_getter + g.gen_getter? + g.gen_getter! + + g.gen_setter + + g.gen_property + g.gen_property? + g.gen_property! + + f.puts "end" +end diff --git a/src/object.cr b/src/object.cr index 4443eaec3916..9482dc170892 100644 --- a/src/object.cr +++ b/src/object.cr @@ -1,3 +1,5 @@ +require "./object/properties" + # `Object` is the base type of all Crystal objects. class Object # Returns `true` if this object is equal to *other*. @@ -324,952 +326,6 @@ class Object pointerof(x).as(T*).value end - {% for prefixes in { {"", "", "@", "#"}, {"class_", "self.", "@@", "."} } %} - {% - macro_prefix = prefixes[0].id - method_prefix = prefixes[1].id - var_prefix = prefixes[2].id - doc_prefix = prefixes[3].id - %} - - # Defines getter methods for each of the given arguments. - # - # Writing: - # - # ``` - # class Person - # {{macro_prefix}}getter name - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}name - # {{var_prefix}}name - # end - # end - # ``` - # - # The arguments can be string literals, symbol literals or plain names: - # - # ``` - # class Person - # {{macro_prefix}}getter :name, "age" - # end - # ``` - # - # If a type declaration is given, a variable with that name - # is declared with that type. - # - # ``` - # class Person - # {{macro_prefix}}getter name : String - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String - # - # def {{method_prefix}}name : String - # {{var_prefix}}name - # end - # end - # ``` - # - # The type declaration can also include an initial value: - # - # ``` - # class Person - # {{macro_prefix}}getter name : String = "John Doe" - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String = "John Doe" - # - # def {{method_prefix}}name : String - # {{var_prefix}}name - # end - # end - # ``` - # - # An assignment can be passed too, but in this case the type of the - # variable must be easily inferable from the initial value: - # - # ``` - # class Person - # {{macro_prefix}}getter name = "John Doe" - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name = "John Doe" - # - # def {{method_prefix}}name : String - # {{var_prefix}}name - # end - # end - # ``` - # - # If a block is given to the macro, a getter is generated - # with a variable that is lazily initialized with - # the block's contents: - # - # ``` - # class Person - # {{macro_prefix}}getter(birth_date) { Time.local } - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}birth_date - # if (value = {{var_prefix}}birth_date).nil? - # {{var_prefix}}birth_date = Time.local - # else - # value - # end - # end - # end - # ``` - macro {{macro_prefix}}getter(*names, &block) - \{% if block %} - \{% if names.size != 1 %} - \{{ raise "Only one argument can be passed to `getter` with a block" }} - \{% end %} - - \{% name = names[0] %} - - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name.var.id}} : \{{name.type}}? - - def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - if (%value = {{var_prefix}}\{{name.var.id}}).nil? - {{var_prefix}}\{{name.var.id}} = \{{yield}} - else - %value - end - end - \{% else %} - def {{method_prefix}}\{{name.id}} - if (%value = {{var_prefix}}\{{name.id}}).nil? - {{var_prefix}}\{{name.id}} = \{{yield}} - else - %value - end - end - \{% end %} - \{% else %} - \{% for name in names %} - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - {{var_prefix}}\{{name.var.id}} - end - \{% elsif name.is_a?(Assign) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.target.id}} - {{var_prefix}}\{{name.target.id}} - end - \{% else %} - def {{method_prefix}}\{{name.id}} - {{var_prefix}}\{{name.id}} - end - \{% end %} - \{% end %} - \{% end %} - end - - # Defines raise-on-nil and nilable getter methods for each of the given arguments. - # - # Writing: - # - # ``` - # class Person - # {{macro_prefix}}getter! name - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}name? - # {{var_prefix}}name - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name.not_nil! - # end - # end - # ``` - # - # The arguments can be string literals, symbol literals or plain names: - # - # ``` - # class Person - # {{macro_prefix}}getter! :name, "age" - # end - # ``` - # - # If a type declaration is given, a variable with that name - # is declared with that type, as nilable. - # - # ``` - # class Person - # {{macro_prefix}}getter! name : String - # end - # ``` - # - # is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String? - # - # def {{method_prefix}}name? - # {{var_prefix}}name - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name.not_nil! - # end - # end - # ``` - macro {{macro_prefix}}getter!(*names) - \{% for name in names %} - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name}}? - - def {{method_prefix}}\{{name.var.id}}? : \{{name.type}}? - {{var_prefix}}\{{name.var.id}} - end - - def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - if (%value = {{var_prefix}}\{{name.var.id}}).nil? - ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil") - else - %value - end - end - \{% else %} - def {{method_prefix}}\{{name.id}}? - {{var_prefix}}\{{name.id}} - end - - def {{method_prefix}}\{{name.id}} - if (%value = {{var_prefix}}\{{name.id}}).nil? - ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil") - else - %value - end - end - \{% end %} - \{% end %} - end - - # Defines query getter methods for each of the given arguments. - # - # Writing: - # - # ``` - # class Person - # {{macro_prefix}}getter? happy - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}happy? - # {{var_prefix}}happy - # end - # end - # ``` - # - # The arguments can be string literals, symbol literals or plain names: - # - # ``` - # class Person - # {{macro_prefix}}getter? :happy, "famous" - # end - # ``` - # - # If a type declaration is given, a variable with that name - # is declared with that type. - # - # ``` - # class Person - # {{macro_prefix}}getter? happy : Bool - # end - # ``` - # - # is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}happy : Bool - # - # def {{method_prefix}}happy? : Bool - # {{var_prefix}}happy - # end - # end - # ``` - # - # The type declaration can also include an initial value: - # - # ``` - # class Person - # {{macro_prefix}}getter? happy : Bool = true - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}happy : Bool = true - # - # def {{method_prefix}}happy? : Bool - # {{var_prefix}}happy - # end - # end - # ``` - # - # An assignment can be passed too, but in this case the type of the - # variable must be easily inferable from the initial value: - # - # ``` - # class Person - # {{macro_prefix}}getter? happy = true - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}happy = true - # - # def {{method_prefix}}happy? - # {{var_prefix}}happy - # end - # end - # ``` - # - # If a block is given to the macro, a getter is generated - # with a variable that is lazily initialized with - # the block's contents, for examples see `#{{macro_prefix}}getter`. - macro {{macro_prefix}}getter?(*names, &block) - \{% if block %} - \{% if names.size != 1 %} - \{{ raise "Only one argument can be passed to `getter?` with a block" }} - \{% end %} - - \{% name = names[0] %} - - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name.var.id}} : \{{name.type}}? - - def {{method_prefix}}\{{name.var.id}}? : \{{name.type}} - if (%value = {{var_prefix}}\{{name.var.id}}).nil? - {{var_prefix}}\{{name.var.id}} = \{{yield}} - else - %value - end - end - \{% else %} - def {{method_prefix}}\{{name.id}}? - if (%value = {{var_prefix}}\{{name.id}}).nil? - {{var_prefix}}\{{name.id}} = \{{yield}} - else - %value - end - end - \{% end %} - \{% else %} - \{% for name in names %} - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.var.id}}? : \{{name.type}} - {{var_prefix}}\{{name.var.id}} - end - \{% elsif name.is_a?(Assign) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.target.id}}? - {{var_prefix}}\{{name.target.id}} - end - \{% else %} - def {{method_prefix}}\{{name.id}}? - {{var_prefix}}\{{name.id}} - end - \{% end %} - \{% end %} - \{% end %} - end - - # Defines setter methods for each of the given arguments. - # - # Writing: - # - # ``` - # class Person - # {{macro_prefix}}setter name - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}name=({{var_prefix}}name) - # end - # end - # ``` - # - # The arguments can be string literals, symbol literals or plain names: - # - # ``` - # class Person - # {{macro_prefix}}setter :name, "age" - # end - # ``` - # - # If a type declaration is given, a variable with that name - # is declared with that type. - # - # ``` - # class Person - # {{macro_prefix}}setter name : String - # end - # ``` - # - # is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String - # - # def {{method_prefix}}name=({{var_prefix}}name : String) - # end - # end - # ``` - # - # The type declaration can also include an initial value: - # - # ``` - # class Person - # {{macro_prefix}}setter name : String = "John Doe" - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String = "John Doe" - # - # def {{method_prefix}}name=({{var_prefix}}name : String) - # end - # end - # ``` - # - # An assignment can be passed too, but in this case the type of the - # variable must be easily inferable from the initial value: - # - # ``` - # class Person - # {{macro_prefix}}setter name = "John Doe" - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name = "John Doe" - # - # def {{method_prefix}}name=({{var_prefix}}name) - # end - # end - # ``` - macro {{macro_prefix}}setter(*names) - \{% for name in names %} - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.var.id}}=({{var_prefix}}\{{name.var.id}} : \{{name.type}}) - end - \{% elsif name.is_a?(Assign) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.target.id}}=({{var_prefix}}\{{name.target.id}}) - end - \{% else %} - def {{method_prefix}}\{{name.id}}=({{var_prefix}}\{{name.id}}) - end - \{% end %} - \{% end %} - end - - # Defines property methods for each of the given arguments. - # - # Writing: - # - # ``` - # class Person - # {{macro_prefix}}property name - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}name=({{var_prefix}}name) - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name - # end - # end - # ``` - # - # The arguments can be string literals, symbol literals or plain names: - # - # ``` - # class Person - # {{macro_prefix}}property :name, "age" - # end - # ``` - # - # If a type declaration is given, a variable with that name - # is declared with that type. - # - # ``` - # class Person - # {{macro_prefix}}property name : String - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String - # - # def {{method_prefix}}name=({{var_prefix}}name) - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name - # end - # end - # ``` - # - # The type declaration can also include an initial value: - # - # ``` - # class Person - # {{macro_prefix}}property name : String = "John Doe" - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String = "John Doe" - # - # def {{method_prefix}}name=({{var_prefix}}name : String) - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name - # end - # end - # ``` - # - # An assignment can be passed too, but in this case the type of the - # variable must be easily inferable from the initial value: - # - # ``` - # class Person - # {{macro_prefix}}property name = "John Doe" - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name = "John Doe" - # - # def {{method_prefix}}name=({{var_prefix}}name : String) - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name - # end - # end - # ``` - # - # If a block is given to the macro, a property is generated - # with a variable that is lazily initialized with - # the block's contents: - # - # ``` - # class Person - # {{macro_prefix}}property(birth_date) { Time.local } - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}birth_date - # if (value = {{var_prefix}}birth_date).nil? - # {{var_prefix}}birth_date = Time.local - # else - # value - # end - # end - # - # def {{method_prefix}}birth_date=({{var_prefix}}birth_date) - # end - # end - # ``` - macro {{macro_prefix}}property(*names, &block) - \{% if block %} - \{% if names.size != 1 %} - \{{ raise "Only one argument can be passed to `property` with a block" }} - \{% end %} - - \{% name = names[0] %} - - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name.var.id}} : \{{name.type}}? - - def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - if (%value = {{var_prefix}}\{{name.var.id}}).nil? - {{var_prefix}}\{{name.var.id}} = \{{yield}} - else - %value - end - end - - def {{method_prefix}}\{{name.var.id}}=({{var_prefix}}\{{name.var.id}} : \{{name.type}}) - end - \{% else %} - def {{method_prefix}}\{{name.id}} - if (%value = {{var_prefix}}\{{name.id}}).nil? - {{var_prefix}}\{{name.id}} = \{{yield}} - else - %value - end - end - - def {{method_prefix}}\{{name.id}}=({{var_prefix}}\{{name.id}}) - end - \{% end %} - \{% else %} - \{% for name in names %} - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.var.id}} : \{{name.type}} - {{var_prefix}}\{{name.var.id}} - end - - def {{method_prefix}}\{{name.var.id}}=({{var_prefix}}\{{name.var.id}} : \{{name.type}}) - end - \{% elsif name.is_a?(Assign) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.target.id}} - {{var_prefix}}\{{name.target.id}} - end - - def {{method_prefix}}\{{name.target.id}}=({{var_prefix}}\{{name.target.id}}) - end - \{% else %} - def {{method_prefix}}\{{name.id}} - {{var_prefix}}\{{name.id}} - end - - def {{method_prefix}}\{{name.id}}=({{var_prefix}}\{{name.id}}) - end - \{% end %} - \{% end %} - \{% end %} - end - - # Defines raise-on-nil property methods for each of the given arguments. - # - # Writing: - # - # ``` - # class Person - # {{macro_prefix}}property! name - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}name=({{var_prefix}}name) - # end - # - # def {{method_prefix}}name? - # {{var_prefix}}name - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name.not_nil! - # end - # end - # ``` - # - # The arguments can be string literals, symbol literals or plain names: - # - # ``` - # class Person - # {{macro_prefix}}property! :name, "age" - # end - # ``` - # - # If a type declaration is given, a variable with that name - # is declared with that type, as nilable. - # - # ``` - # class Person - # {{macro_prefix}}property! name : String - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}name : String? - # - # def {{method_prefix}}name=({{var_prefix}}name) - # end - # - # def {{method_prefix}}name? - # {{var_prefix}}name - # end - # - # def {{method_prefix}}name - # {{var_prefix}}name.not_nil! - # end - # end - # ``` - macro {{macro_prefix}}property!(*names) - {{macro_prefix}}getter! \{{names.splat}} - - \{% for name in names %} - \{% if name.is_a?(TypeDeclaration) %} - def {{method_prefix}}\{{name.var.id}}=({{var_prefix}}\{{name.var.id}} : \{{name.type}}) - end - \{% else %} - def {{method_prefix}}\{{name.id}}=({{var_prefix}}\{{name.id}}) - end - \{% end %} - \{% end %} - end - - # Defines query property methods for each of the given arguments. - # - # Writing: - # - # ``` - # class Person - # {{macro_prefix}}property? happy - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # def {{method_prefix}}happy=({{var_prefix}}happy) - # end - # - # def {{method_prefix}}happy? - # {{var_prefix}}happy - # end - # end - # ``` - # - # The arguments can be string literals, symbol literals or plain names: - # - # ``` - # class Person - # {{macro_prefix}}property? :happy, "famous" - # end - # ``` - # - # If a type declaration is given, a variable with that name - # is declared with that type. - # - # ``` - # class Person - # {{macro_prefix}}property? happy : Bool - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}happy : Bool - # - # def {{method_prefix}}happy=({{var_prefix}}happy : Bool) - # end - # - # def {{method_prefix}}happy? : Bool - # {{var_prefix}}happy - # end - # end - # ``` - # - # The type declaration can also include an initial value: - # - # ``` - # class Person - # {{macro_prefix}}property? happy : Bool = true - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}happy : Bool = true - # - # def {{method_prefix}}happy=({{var_prefix}}happy : Bool) - # end - # - # def {{method_prefix}}happy? : Bool - # {{var_prefix}}happy - # end - # end - # ``` - # - # An assignment can be passed too, but in this case the type of the - # variable must be easily inferable from the initial value: - # - # ``` - # class Person - # {{macro_prefix}}property? happy = true - # end - # ``` - # - # Is the same as writing: - # - # ``` - # class Person - # {{var_prefix}}happy = true - # - # def {{method_prefix}}happy=({{var_prefix}}happy) - # end - # - # def {{method_prefix}}happy? - # {{var_prefix}}happy - # end - # end - # ``` - # - # If a block is given to the macro, a property is generated - # with a variable that is lazily initialized with - # the block's contents, for examples see `#{{macro_prefix}}property`. - macro {{macro_prefix}}property?(*names, &block) - \{% if block %} - \{% if names.size != 1 %} - \{{ raise "Only one argument can be passed to `property?` with a block" }} - \{% end %} - - \{% name = names[0] %} - - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name.var.id}} : \{{name.type}}? - - def {{method_prefix}}\{{name.var.id}}? : \{{name.type}} - if (%value = {{var_prefix}}\{{name.var.id}}).nil? - {{var_prefix}}\{{name.var.id}} = \{{yield}} - else - %value - end - end - - def {{method_prefix}}\{{name.var.id}}=({{var_prefix}}\{{name.var.id}} : \{{name.type}}) - end - \{% else %} - def {{method_prefix}}\{{name.id}}? - if (%value = {{var_prefix}}\{{name.id}}).nil? - {{var_prefix}}\{{name.id}} = \{{yield}} - else - %value - end - end - - def {{method_prefix}}\{{name.id}}=({{var_prefix}}\{{name.id}}) - end - \{% end %} - \{% else %} - \{% for name in names %} - \{% if name.is_a?(TypeDeclaration) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.var.id}}? : \{{name.type}} - {{var_prefix}}\{{name.var.id}} - end - - def {{method_prefix}}\{{name.var.id}}=({{var_prefix}}\{{name.var.id}} : \{{name.type}}) - end - \{% elsif name.is_a?(Assign) %} - {{var_prefix}}\{{name}} - - def {{method_prefix}}\{{name.target.id}}? - {{var_prefix}}\{{name.target.id}} - end - - def {{method_prefix}}\{{name.target.id}}=({{var_prefix}}\{{name.target.id}}) - end - \{% else %} - def {{method_prefix}}\{{name.id}}? - {{var_prefix}}\{{name.id}} - end - - def {{method_prefix}}\{{name.id}}=({{var_prefix}}\{{name.id}}) - end - \{% end %} - \{% end %} - \{% end %} - end - {% end %} - # Delegate *methods* to *to*. # # Note that due to current language limitations this is only useful diff --git a/src/object/properties.cr b/src/object/properties.cr new file mode 100644 index 000000000000..c9bc1c74c4c1 --- /dev/null +++ b/src/object/properties.cr @@ -0,0 +1,1722 @@ +# WARNING: THIS FILE HAS BEEN AUTOGENERATED BY scripts/generate_object_properties.cr +# WARNING: DO NOT EDIT MANUALLY! + +class Object + # Defines getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # getter name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def name + # @name + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # getter :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # getter name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name : String + # + # def name : String + # @name + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # getter name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name : String = "John Doe" + # + # def name : String + # @name + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # getter name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name = "John Doe" + # + # def name : String + # @name + # end + # end + # ``` + # + # If a block is given to the macro, a getter is generated + # with a variable that is lazily initialized with + # the block's contents: + # + # ``` + # class Person + # getter(birth_date) { Time.local } + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def birth_date + # if (value = @birth_date).nil? + # @birth_date = Time.local + # else + # value + # end + # end + # end + # ``` + macro getter(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def {{var_name}} {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @{{var_name}}).nil? + @{{var_name}} = {{yield}} + else + %value + end + {% else %} + @{{var_name}} + {% end %} + end + + {% end %} + end + + # Defines query getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # getter? happy + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def happy? + # @happy + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # getter? :happy, "famous" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # getter? happy : Bool + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # @happy : Bool + # + # def happy? : Bool + # @happy + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # getter? happy : Bool = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @happy : Bool = true + # + # def happy? : Bool + # @happy + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # getter? happy = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @happy = true + # + # def happy? + # @happy + # end + # end + # ``` + # + # If a block is given to the macro, a getter is generated with a variable + # that is lazily initialized with the block's contents, for examples see + # `#getter`. + macro getter?(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def {{var_name}}? {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @{{var_name}}).nil? + @{{var_name}} = {{yield}} + else + %value + end + {% else %} + @{{var_name}} + {% end %} + end + + {% end %} + end + + # Defines raise-on-nil and nilable getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # getter! name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def name? + # @name + # end + # + # def name + # @name.not_nil! + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # getter! :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type, as nilable. + # + # ``` + # class Person + # getter! name : String + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # @name : String? + # + # def name? + # @name + # end + # + # def name + # @name.not_nil! + # end + # end + # ``` + macro getter!(*names) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + @{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def {{var_name}}? {% if type %} : {{type}}? {% end %} + @{{var_name}} + end + + def {{var_name}} {% if type %} : {{type}} {% end %} + if (%value = @{{var_name}}).nil? + ::raise ::NilAssertionError.new("{{@type.id}}{{"#".id}}{{var_name}} cannot be nil") + else + %value + end + end + + {% end %} + end + + # Defines setter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # setter name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def name=(@name) + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # setter :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # setter name : String + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # @name : String + # + # def name=(@name : String) + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # setter name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name : String = "John Doe" + # + # def name=(@name : String) + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # setter name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name = "John Doe" + # + # def name=(@name) + # end + # end + # ``` + macro setter(*names) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + @{{name}} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + def {{var_name}}=(@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end + + # Defines property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # property name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def name=(@name) + # end + # + # def name + # @name + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # property :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # property name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name : String + # + # def name=(@name) + # end + # + # def name + # @name + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # property name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name : String = "John Doe" + # + # def name=(@name : String) + # end + # + # def name + # @name + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # property name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name = "John Doe" + # + # def name=(@name : String) + # end + # + # def name + # @name + # end + # end + # ``` + # + # If a block is given to the macro, a property is generated + # with a variable that is lazily initialized with + # the block's contents: + # + # ``` + # class Person + # property(birth_date) { Time.local } + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def birth_date + # if (value = @birth_date).nil? + # @birth_date = Time.local + # else + # value + # end + # end + # + # def birth_date=(@birth_date) + # end + # end + # ``` + macro property(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def {{var_name}} {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @{{var_name}}).nil? + @{{var_name}} = {{yield}} + else + %value + end + {% else %} + @{{var_name}} + {% end %} + end + + def {{var_name}}=(@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end + + # Defines query property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # property? happy + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def happy=(@happy) + # end + # + # def happy? + # @happy + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # property? :happy, "famous" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # property? happy : Bool + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @happy : Bool + # + # def happy=(@happy : Bool) + # end + # + # def happy? : Bool + # @happy + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # property? happy : Bool = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @happy : Bool = true + # + # def happy=(@happy : Bool) + # end + # + # def happy? : Bool + # @happy + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # property? happy = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @happy = true + # + # def happy=(@happy) + # end + # + # def happy? + # @happy + # end + # end + # ``` + # + # If a block is given to the macro, a property is generated + # with a variable that is lazily initialized with + # the block's contents, for examples see `#property`. + macro property?(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def {{var_name}}? {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @{{var_name}}).nil? + @{{var_name}} = {{yield}} + else + %value + end + {% else %} + @{{var_name}} + {% end %} + end + + def {{var_name}}=(@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end + + # Defines raise-on-nil property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # property! name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def name=(@name) + # end + # + # def name? + # @name + # end + # + # def name + # @name.not_nil! + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # property! :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type, as nilable. + # + # ``` + # class Person + # property! name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @name : String? + # + # def name=(@name) + # end + # + # def name? + # @name + # end + # + # def name + # @name.not_nil! + # end + # end + # ``` + macro property!(*names) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + @{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def {{var_name}}? {% if type %} : {{type}}? {% end %} + @{{var_name}} + end + + def {{var_name}} {% if type %} : {{type}} {% end %} + if (%value = @{{var_name}}).nil? + ::raise ::NilAssertionError.new("{{@type.id}}{{"#".id}}{{var_name}} cannot be nil") + else + %value + end + end + + def {{var_name}}=(@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end + + # Defines getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # class_getter name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.name + # @@name + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # class_getter :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # class_getter name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name : String + # + # def self.name : String + # @@name + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # class_getter name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name : String = "John Doe" + # + # def self.name : String + # @@name + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # class_getter name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name = "John Doe" + # + # def self.name : String + # @@name + # end + # end + # ``` + # + # If a block is given to the macro, a getter is generated + # with a variable that is lazily initialized with + # the block's contents: + # + # ``` + # class Person + # class_getter(birth_date) { Time.local } + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.birth_date + # if (value = @@birth_date).nil? + # @@birth_date = Time.local + # else + # value + # end + # end + # end + # ``` + macro class_getter(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @@{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @@{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @@{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def self.{{var_name}} {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @@{{var_name}}).nil? + @@{{var_name}} = {{yield}} + else + %value + end + {% else %} + @@{{var_name}} + {% end %} + end + + {% end %} + end + + # Defines query getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # class_getter? happy + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.happy? + # @@happy + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # class_getter? :happy, "famous" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # class_getter? happy : Bool + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # @@happy : Bool + # + # def self.happy? : Bool + # @@happy + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # class_getter? happy : Bool = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@happy : Bool = true + # + # def self.happy? : Bool + # @@happy + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # class_getter? happy = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@happy = true + # + # def self.happy? + # @@happy + # end + # end + # ``` + # + # If a block is given to the macro, a getter is generated with a variable + # that is lazily initialized with the block's contents, for examples see + # `#class_getter`. + macro class_getter?(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @@{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @@{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @@{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def self.{{var_name}}? {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @@{{var_name}}).nil? + @@{{var_name}} = {{yield}} + else + %value + end + {% else %} + @@{{var_name}} + {% end %} + end + + {% end %} + end + + # Defines raise-on-nil and nilable getter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # class_getter! name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.name? + # @@name + # end + # + # def self.name + # @@name.not_nil! + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # class_getter! :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type, as nilable. + # + # ``` + # class Person + # class_getter! name : String + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # @@name : String? + # + # def self.name? + # @@name + # end + # + # def self.name + # @@name.not_nil! + # end + # end + # ``` + macro class_getter!(*names) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + @@{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def self.{{var_name}}? {% if type %} : {{type}}? {% end %} + @@{{var_name}} + end + + def self.{{var_name}} {% if type %} : {{type}} {% end %} + if (%value = @@{{var_name}}).nil? + ::raise ::NilAssertionError.new("{{@type.id}}{{".".id}}{{var_name}} cannot be nil") + else + %value + end + end + + {% end %} + end + + # Defines setter methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # class_setter name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.name=(@@name) + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # class_setter :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # class_setter name : String + # end + # ``` + # + # is the same as writing: + # + # ``` + # class Person + # @@name : String + # + # def self.name=(@@name : String) + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # class_setter name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name : String = "John Doe" + # + # def self.name=(@@name : String) + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # class_setter name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name = "John Doe" + # + # def self.name=(@@name) + # end + # end + # ``` + macro class_setter(*names) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + @@{{name}} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @@{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + def self.{{var_name}}=(@@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end + + # Defines property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # class_property name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.name=(@@name) + # end + # + # def self.name + # @@name + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # class_property :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # class_property name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name : String + # + # def self.name=(@@name) + # end + # + # def self.name + # @@name + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # class_property name : String = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name : String = "John Doe" + # + # def self.name=(@@name : String) + # end + # + # def self.name + # @@name + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # class_property name = "John Doe" + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name = "John Doe" + # + # def self.name=(@@name : String) + # end + # + # def self.name + # @@name + # end + # end + # ``` + # + # If a block is given to the macro, a property is generated + # with a variable that is lazily initialized with + # the block's contents: + # + # ``` + # class Person + # class_property(birth_date) { Time.local } + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.birth_date + # if (value = @@birth_date).nil? + # @@birth_date = Time.local + # else + # value + # end + # end + # + # def self.birth_date=(@@birth_date) + # end + # end + # ``` + macro class_property(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @@{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @@{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @@{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def self.{{var_name}} {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @@{{var_name}}).nil? + @@{{var_name}} = {{yield}} + else + %value + end + {% else %} + @@{{var_name}} + {% end %} + end + + def self.{{var_name}}=(@@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end + + # Defines query property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # class_property? happy + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.happy=(@@happy) + # end + # + # def self.happy? + # @@happy + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # class_property? :happy, "famous" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type. + # + # ``` + # class Person + # class_property? happy : Bool + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@happy : Bool + # + # def self.happy=(@@happy : Bool) + # end + # + # def self.happy? : Bool + # @@happy + # end + # end + # ``` + # + # The type declaration can also include an initial value: + # + # ``` + # class Person + # class_property? happy : Bool = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@happy : Bool = true + # + # def self.happy=(@@happy : Bool) + # end + # + # def self.happy? : Bool + # @@happy + # end + # end + # ``` + # + # An assignment can be passed too, but in this case the type of the + # variable must be easily inferable from the initial value: + # + # ``` + # class Person + # class_property? happy = true + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@happy = true + # + # def self.happy=(@@happy) + # end + # + # def self.happy? + # @@happy + # end + # end + # ``` + # + # If a block is given to the macro, a property is generated + # with a variable that is lazily initialized with + # the block's contents, for examples see `#class_property`. + macro class_property?(*names, &block) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + {% if block %} + @@{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + @@{{name}} + {% end %} + {% elsif name.is_a?(Assign) %} + {% var_name = name.target %} + {% type = nil %} + @@{{name}} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def self.{{var_name}}? {% if type %} : {{type}} {% end %} + {% if block %} + if (%value = @@{{var_name}}).nil? + @@{{var_name}} = {{yield}} + else + %value + end + {% else %} + @@{{var_name}} + {% end %} + end + + def self.{{var_name}}=(@@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end + + # Defines raise-on-nil property methods for each of the given arguments. + # + # Writing: + # + # ``` + # class Person + # class_property! name + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # def self.name=(@@name) + # end + # + # def self.name? + # @@name + # end + # + # def self.name + # @@name.not_nil! + # end + # end + # ``` + # + # The arguments can be string literals, symbol literals or plain names: + # + # ``` + # class Person + # class_property! :name, "age" + # end + # ``` + # + # If a type declaration is given, a variable with that name + # is declared with that type, as nilable. + # + # ``` + # class Person + # class_property! name : String + # end + # ``` + # + # Is the same as writing: + # + # ``` + # class Person + # @@name : String? + # + # def self.name=(@@name) + # end + # + # def self.name? + # @@name + # end + # + # def self.name + # @@name.not_nil! + # end + # end + # ``` + macro class_property!(*names) + {% for name in names %} + {% if name.is_a?(TypeDeclaration) %} + {% var_name = name.var.id %} + {% type = name.type %} + @@{{var_name}} : {{type}}? {% if name.value %} = {{name.value}} {% end %} + {% else %} + {% var_name = name.id %} + {% type = nil %} + {% end %} + + def self.{{var_name}}? {% if type %} : {{type}}? {% end %} + @@{{var_name}} + end + + def self.{{var_name}} {% if type %} : {{type}} {% end %} + if (%value = @@{{var_name}}).nil? + ::raise ::NilAssertionError.new("{{@type.id}}{{".".id}}{{var_name}} cannot be nil") + else + %value + end + end + + def self.{{var_name}}=(@@{{var_name}}{% if type %} : {{type}} {% end %}) + end + + {% end %} + end +end