Runtime assertions for Ruby literal.fun
ruby
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve literal properties and data structures (#83)

authored by

Joel Drapper and committed by
GitHub
1d0e064e 95181219

+558 -993
+2 -2
.github/workflows/ruby.yml
··· 23 23 with: 24 24 ruby-version: ${{ matrix.ruby-version }} 25 25 bundler-cache: true # runs 'bundle install' and caches installed gems automatically 26 - - name: Run tests 27 - run: bundle exec qt 26 + - name: Run Rubocop 27 + run: bundle exec rubocop
+4
Gemfile
··· 11 11 gem "solargraph" 12 12 13 13 gem "ruby-lsp" 14 + 15 + gem "dry-initializer" 16 + gem "dry-types" 17 + gem "dry-struct"
+29 -1
Gemfile.lock
··· 1 1 GIT 2 2 remote: https://github.com/joeldrapper/quickdraw.git 3 - revision: d41066d6282dac01992262df9281d01c5d77cfa2 3 + revision: 061b0fa9c6f10bc95190de2e1f2812fa52ff01a3 4 4 specs: 5 5 quickdraw (0.1.0) 6 6 ··· 16 16 backport (1.2.0) 17 17 benchmark (0.3.0) 18 18 benchmark-ips (2.13.0) 19 + bigdecimal (3.1.8) 20 + concurrent-ruby (1.3.3) 19 21 diff-lcs (1.5.1) 22 + dry-core (1.0.1) 23 + concurrent-ruby (~> 1.0) 24 + zeitwerk (~> 2.6) 25 + dry-inflector (1.0.0) 26 + dry-initializer (3.1.1) 27 + dry-logic (1.5.0) 28 + concurrent-ruby (~> 1.0) 29 + dry-core (~> 1.0, < 2) 30 + zeitwerk (~> 2.6) 31 + dry-struct (1.6.0) 32 + dry-core (~> 1.0, < 2) 33 + dry-types (>= 1.7, < 2) 34 + ice_nine (~> 0.11) 35 + zeitwerk (~> 2.6) 36 + dry-types (1.7.2) 37 + bigdecimal (~> 3.0) 38 + concurrent-ruby (~> 1.0) 39 + dry-core (~> 1.0) 40 + dry-inflector (~> 1.0) 41 + dry-logic (~> 1.4) 42 + zeitwerk (~> 2.6) 20 43 e2mmap (0.1.0) 44 + ice_nine (0.11.2) 21 45 jaro_winkler (1.6.0) 22 46 json (2.7.2) 23 47 kramdown (2.4.0) ··· 90 114 tilt (2.3.0) 91 115 unicode-display_width (2.5.0) 92 116 yard (0.9.36) 117 + zeitwerk (2.6.16) 93 118 94 119 PLATFORMS 95 120 aarch64-linux ··· 101 126 102 127 DEPENDENCIES 103 128 benchmark-ips 129 + dry-initializer 130 + dry-struct 131 + dry-types 104 132 literal! 105 133 quickdraw! 106 134 rubocop
+20 -12
bench.rb
··· 30 30 end 31 31 32 32 class LiteralClass 33 - extend Literal::Attributes 33 + extend Literal::Properties 34 34 35 - attribute :first_name, String 36 - attribute :last_name, String 37 - attribute :age, Integer 35 + prop :first_name, String 36 + prop :last_name, String 37 + prop :age, Integer 38 38 end 39 39 40 40 NormalStruct = Struct.new(:first_name, :last_name, :age, keyword_init: true) ··· 46 46 end 47 47 48 48 class LiteralStruct < Literal::Struct 49 - attribute :first_name, String 50 - attribute :last_name, String 51 - attribute :age, Integer 49 + prop :first_name, String 50 + prop :last_name, String 51 + prop :age, Integer 52 52 end 53 53 54 54 NormalData = Data.define(:first_name, :last_name, :age) ··· 60 60 end 61 61 62 62 class LiteralData < Literal::Data 63 - attribute :first_name, String 64 - attribute :last_name, String 65 - attribute :age, Integer 63 + prop :first_name, String 64 + prop :last_name, String 65 + prop :age, Integer 66 66 end 67 67 68 68 Benchmark.ips do |x| ··· 74 74 DryClass.new(first_name: "Joel", last_name: "Drapper", age: 29) 75 75 end 76 76 77 - x.report "Literal::Attributes" do 77 + x.report "Literal::Properties" do 78 78 LiteralClass.new(first_name: "Joel", last_name: "Drapper", age: 29) 79 79 end 80 80 81 + x.compare! 82 + end 83 + 84 + Benchmark.ips do |x| 81 85 x.report "Ruby Struct" do 82 86 NormalStruct.new(first_name: "Joel", last_name: "Drapper", age: 29) 83 87 end ··· 90 94 LiteralStruct.new(first_name: "Joel", last_name: "Drapper", age: 29) 91 95 end 92 96 97 + x.compare! 98 + end 99 + 100 + Benchmark.ips do |x| 93 101 x.report "Ruby Data" do 94 102 NormalData.new(first_name: "Joel", last_name: "Drapper", age: 29) 95 103 end ··· 102 110 LiteralData.new(first_name: "Joel", last_name: "Drapper", age: 29) 103 111 end 104 112 105 - # x.compare! 113 + x.compare! 106 114 end
+8 -11
lib/literal.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 module Literal 4 - TYPE_CHECKS = ENV["LITERAL_TYPE_CHECKS"] != "false" 4 + TYPE_CHECKS_ENABLED = ENV["LITERAL_TYPE_CHECKS"] != "false" 5 5 6 - autoload :Attributable, "literal/attributable" 7 - autoload :Attribute, "literal/attribute" 8 - autoload :Attributes, "literal/attributes" 9 - autoload :ConcurrentArray, "literal/concurrent_array" 10 - autoload :ConcurrentHash, "literal/concurrent_hash" 11 6 autoload :Data, "literal/data" 7 + autoload :DataProperty, "literal/data_property" 8 + autoload :DataStructure, "literal/data_structure" 12 9 autoload :Enum, "literal/enum" 13 - autoload :Formatter, "literal/formatter" 14 - autoload :Singleton, "literal/singleton" 10 + autoload :Null, "literal/null" 11 + autoload :Object, "literal/object" 12 + autoload :Properties, "literal/properties" 13 + autoload :Property, "literal/property" 15 14 autoload :Struct, "literal/struct" 16 - autoload :Structish, "literal/structish" 17 15 autoload :Types, "literal/types" 18 - autoload :Visitor, "literal/visitor" 19 - autoload :Null, "literal/null" 20 16 21 17 # Errors 22 18 autoload :Error, "literal/errors/error" 23 19 autoload :TypeError, "literal/errors/type_error" 20 + autoload :ArgumentError, "literal/errors/argument_error" 24 21 end
lib/literal/argument_error.rb lib/literal/errors/argument_error.rb
-86
lib/literal/attributable.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable 4 - autoload :Generators, "literal/attributable/generators" 5 - autoload :Nodes, "literal/attributable/nodes" 6 - autoload :Formatter, "literal/attributable/formatter" 7 - 8 - include Literal::Types 9 - 10 - VisibilityOptions = Set[false, :private, :protected, :public].freeze 11 - KindOptions = Set[nil, :positional, :*, :**, :&].freeze 12 - 13 - def attribute(name, type, kind = nil, reader: false, writer: false, default: nil, &coercion) 14 - if default && !(Proc === default || default.frozen?) 15 - raise Literal::ArgumentError.new("The default must be a frozen object or a Proc.") 16 - end 17 - 18 - unless VisibilityOptions.include?(reader) 19 - raise Literal::ArgumentError.new("The reader must be one of #{VisibilityOptions.map(&:inspect).join(', ')}.") 20 - end 21 - 22 - unless VisibilityOptions.include?(writer) 23 - raise Literal::ArgumentError.new("The writer must be one of #{VisibilityOptions.map(&:inspect).join(', ')}.") 24 - end 25 - 26 - if reader && :class == name 27 - raise Literal::ArgumentError.new( 28 - "The `:class` attribute should not be defined as a reader because it breaks Ruby's `Object#class` method, which Literal itself depends on.", 29 - ) 30 - end 31 - 32 - unless KindOptions.include?(kind) 33 - raise Literal::ArgumentError.new("The kind must be one of #{KindOptions.map(&:inspect).join(', ')}.") 34 - end 35 - 36 - attribute = Literal::Attribute.new( 37 - name:, 38 - type:, 39 - kind:, 40 - reader:, 41 - writer:, 42 - default:, 43 - coercion:, 44 - ) 45 - 46 - literal_attributes[name] = attribute 47 - define_literal_methods(attribute) 48 - include literal_extension 49 - end 50 - 51 - def inherited(subclass) 52 - literal_attributes = self.literal_attributes 53 - 54 - subclass.instance_exec do 55 - @literal_attributes = literal_attributes.dup 56 - end 57 - 58 - super 59 - end 60 - 61 - def literal_attributes 62 - return @literal_attributes if defined?(@literal_attributes) 63 - 64 - if superclass.is_a?(Literal::Attributable) 65 - @literal_attributes = superclass.literal_attributes.dup 66 - else 67 - @literal_attributes = Literal::ConcurrentHash.new 68 - end 69 - end 70 - 71 - def define_literal_methods(attribute) 72 - literal_extension.module_eval <<~RUBY, __FILE__, __LINE__ + 1 73 - # frozen_string_literal: true 74 - 75 - #{generate_literal_initializer} 76 - 77 - #{generate_literal_reader(attribute) if attribute.reader} 78 - 79 - #{generate_literal_writer(attribute) if attribute.writer} 80 - RUBY 81 - end 82 - 83 - def literal_extension 84 - defined?(@literal_extension) ? @literal_extension : @literal_extension = Module.new 85 - end 86 - end
-134
lib/literal/attributable/formatter.rb
··· 1 - # frozen_string_literal: true 2 - 3 - class Literal::Attributable::Formatter < Literal::Formatter 4 - def Access(node) 5 - visit node.collection 6 - text "[" 7 - visit node.key 8 - text "]" 9 - end 10 - 11 - def Assignment(node) 12 - visit node.left 13 - text " = " 14 - visit node.right 15 - end 16 - 17 - def AssignSchema(node) 18 - text "@literal_attributes = self.class.literal_attributes" 19 - end 20 - 21 - def AttributeCoercion(node) 22 - text "#{node.attribute.name} = @literal_attributes[:#{node.attribute.name}].coerce(#{node.attribute.name}, context: self)" 23 - end 24 - 25 - def BlockParam(node) 26 - text "&#{node.attribute.name}" 27 - end 28 - 29 - def Def(node) 30 - text "#{node.visibility} " if node.visibility 31 - text "def #{node.name}" 32 - if node.params&.any? 33 - text "(" 34 - visit_each(node.params) { text ", " } 35 - text ")" 36 - end 37 - 38 - indent do 39 - visit_each(node.body) { newline; newline } 40 - end 41 - 42 - newline 43 - text "end" 44 - newline 45 - end 46 - 47 - def DefaultAssignment(node) 48 - text "if Literal::Null == #{node.attribute.escaped_name}" 49 - 50 - indent do 51 - text "#{node.attribute.escaped_name} = @literal_attributes[:#{node.attribute.name}].default_value" 52 - end 53 - 54 - newline 55 - text "end" 56 - end 57 - 58 - def HashLiteral(node) 59 - text "{" 60 - 61 - indent do 62 - visit_each(node.mappings) { text ","; newline } 63 - end 64 - 65 - newline 66 - 67 - text "}" 68 - end 69 - 70 - def InitializerCallback(node) 71 - comment "Callback for your own setup logic" 72 - text "after_initialization if respond_to?(:after_initialization)" 73 - end 74 - 75 - def KeywordEscape(node) 76 - text "#{node.attribute.escaped_name} = binding.local_variable_get(:#{node.attribute.name})" 77 - end 78 - 79 - def KeywordParam(node) 80 - if node.attribute.default 81 - text "#{node.attribute.name}: Literal::Null" 82 - elsif node.attribute.type === nil 83 - text "#{node.attribute.name}: nil" 84 - else 85 - text "#{node.attribute.name}:" 86 - end 87 - end 88 - 89 - def KeywordSplat(node) 90 - text "**#{node.attribute.name}" 91 - end 92 - 93 - def Mapping(node) 94 - visit node.left 95 - text " => " 96 - visit node.right 97 - end 98 - 99 - def PositionalParam(node) 100 - if node.default 101 - text "#{node.name} = #{node.default}" 102 - else 103 - text node.name 104 - end 105 - end 106 - 107 - def PositionalSplat(node) 108 - text "*#{node.attribute.name}" 109 - end 110 - 111 - def Ref(node) 112 - text node.name 113 - end 114 - 115 - def Section(node) 116 - comment node.name if node.name 117 - visit_each(node.body) { newline; newline } if node.body 118 - end 119 - 120 - def Symbol(node) 121 - text ":#{node.name}" 122 - end 123 - 124 - def TypeCheck(node) 125 - text "unless @literal_attributes[:#{node.attribute_name}].type === #{node.variable_name}" 126 - 127 - indent do 128 - text "raise Literal::TypeError.expected(#{node.variable_name}, to_be_a: @literal_attributes[:#{node.attribute_name}].type)" 129 - end 130 - 131 - newline 132 - text "end" 133 - end 134 - end
-14
lib/literal/attributable/generators.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - autoload :Base, "literal/attributable/generators/base" 5 - autoload :Initializer, "literal/attributable/generators/initializer" 6 - autoload :IVarInitializer, "literal/attributable/generators/i_var_initializer" 7 - autoload :IVarReader, "literal/attributable/generators/i_var_reader" 8 - autoload :StructInitializer, "literal/attributable/generators/struct_initializer" 9 - autoload :StructReader, "literal/attributable/generators/struct_reader" 10 - autoload :Reader, "literal/attributable/generators/reader" 11 - autoload :StructWriter, "literal/attributable/generators/struct_writer" 12 - autoload :Writer, "literal/attributable/generators/writer" 13 - autoload :DataInitializer, "literal/attributable/generators/data_initializer" 14 - end
-11
lib/literal/attributable/generators/base.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class Base 5 - include Literal::Attributable::Nodes 6 - 7 - def call 8 - Literal::Attributable::Formatter.new.visit(template) 9 - end 10 - end 11 - end
-31
lib/literal/attributable/generators/data_initializer.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class DataInitializer < StructInitializer 5 - private 6 - 7 - def body 8 - [ 9 - assign_schema, 10 - init_attributes_ivar, 11 - handle_attributes, 12 - initializer_callback, 13 - freeze_object, 14 - ] 15 - end 16 - 17 - def freeze_object 18 - Ref.new("freeze") 19 - end 20 - 21 - def assign_value(attribute) 22 - Assignment.new( 23 - left: Access.new( 24 - collection: Ref.new("@attributes"), 25 - key: Symbol.new(attribute.name), 26 - ), 27 - right: Ref.new("#{attribute.escaped_name}.frozen? ? #{attribute.escaped_name} : #{attribute.escaped_name}.freeze"), 28 - ) 29 - end 30 - end 31 - end
-14
lib/literal/attributable/generators/i_var_initializer.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class IVarInitializer < Initializer 5 - private 6 - 7 - def assign_value(attribute) 8 - Assignment.new( 9 - left: Ref.new("@#{attribute.name}"), 10 - right: Ref.new(attribute.escaped_name), 11 - ) 12 - end 13 - end 14 - end
-13
lib/literal/attributable/generators/i_var_reader.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class IVarReader < Reader 5 - private 6 - 7 - def body 8 - [ 9 - Ref.new("@#{@attribute.name}"), 10 - ] 11 - end 12 - end 13 - end
-14
lib/literal/attributable/generators/i_var_writer.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class IVarWriter < Writer 5 - private 6 - 7 - def assignment 8 - Assignment.new( 9 - left: Ref.new("@#{@attribute.name}"), 10 - right: Ref.new("value"), 11 - ) 12 - end 13 - end 14 - end
-105
lib/literal/attributable/generators/initializer.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class Initializer < Base 5 - def initialize(attributes) 6 - @attributes = attributes 7 - end 8 - 9 - def template 10 - Def.new( 11 - visibility: :public, 12 - name: "initialize", 13 - params:, 14 - body:, 15 - ) 16 - end 17 - 18 - private 19 - 20 - def params 21 - @attributes.each_value.map do |attribute| 22 - case attribute.kind 23 - when :* 24 - PositionalSplat.new(attribute:) 25 - when :** 26 - KeywordSplat.new(attribute:) 27 - when :& 28 - BlockParam.new(attribute:) 29 - when :positional 30 - if attribute.default 31 - PositionalParam.new( 32 - name: attribute.name, 33 - default: "Literal::Null", 34 - ) 35 - elsif attribute.type === nil 36 - PositionalParam.new( 37 - name: attribute.name, 38 - default: "nil", 39 - ) 40 - else 41 - PositionalParam.new( 42 - name: attribute.name, 43 - default: nil, 44 - ) 45 - end 46 - else 47 - KeywordParam.new(attribute:) 48 - end 49 - end 50 - end 51 - 52 - def body 53 - [ 54 - assign_schema, 55 - handle_attributes, 56 - initializer_callback, 57 - ] 58 - end 59 - 60 - def assign_schema 61 - AssignSchema.new 62 - end 63 - 64 - def handle_attributes 65 - Section.new( 66 - name: "Handle attributes", 67 - body: @attributes.each_value.map do |attribute| 68 - Section.new( 69 - name: attribute.name, 70 - body: [ 71 - escape_keyword(attribute), 72 - coerce_attribute(attribute), 73 - assign_default(attribute), 74 - check_type(attribute), 75 - assign_value(attribute), 76 - ].compact, 77 - ) 78 - end, 79 - ) 80 - end 81 - 82 - def escape_keyword(attribute) 83 - KeywordEscape.new(attribute) if attribute.ruby_keyword? 84 - end 85 - 86 - def coerce_attribute(attribute) 87 - AttributeCoercion.new(attribute) if attribute.coercion 88 - end 89 - 90 - def assign_default(attribute) 91 - DefaultAssignment.new(attribute) if attribute.default 92 - end 93 - 94 - def check_type(attribute) 95 - TypeCheck.new( 96 - attribute_name: attribute.name, 97 - variable_name: attribute.escaped_name, 98 - ) 99 - end 100 - 101 - def initializer_callback 102 - InitializerCallback.new 103 - end 104 - end 105 - end
-18
lib/literal/attributable/generators/reader.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class Reader < Base 5 - def initialize(attribute) 6 - @attribute = attribute 7 - end 8 - 9 - def template 10 - Def.new( 11 - visibility: @attribute.reader, 12 - name: @attribute.name, 13 - params: nil, 14 - body:, 15 - ) 16 - end 17 - end 18 - end
-33
lib/literal/attributable/generators/struct_initializer.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class StructInitializer < Initializer 5 - private 6 - 7 - def body 8 - [ 9 - assign_schema, 10 - init_attributes_ivar, 11 - handle_attributes, 12 - initializer_callback, 13 - ] 14 - end 15 - 16 - def init_attributes_ivar 17 - Assignment.new( 18 - left: Ref.new("@attributes"), 19 - right: Ref.new("{}"), 20 - ) 21 - end 22 - 23 - def assign_value(attribute) 24 - Assignment.new( 25 - left: Access.new( 26 - collection: Ref.new("@attributes"), 27 - key: Symbol.new(attribute.name), 28 - ), 29 - right: Ref.new(attribute.escaped_name), 30 - ) 31 - end 32 - end 33 - end
-16
lib/literal/attributable/generators/struct_reader.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class StructReader < Reader 5 - private 6 - 7 - def body 8 - [ 9 - Access.new( 10 - collection: Ref.new("@attributes"), 11 - key: Symbol.new(@attribute.name), 12 - ), 13 - ] 14 - end 15 - end 16 - end
-17
lib/literal/attributable/generators/struct_writer.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class StructWriter < Writer 5 - private 6 - 7 - def assignment 8 - Assignment.new( 9 - left: Access.new( 10 - collection: Ref.new("@attributes"), 11 - key: Symbol.new(@attribute.name), 12 - ), 13 - right: Ref.new("value"), 14 - ) 15 - end 16 - end 17 - end
-40
lib/literal/attributable/generators/writer.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Generators 4 - class Writer < Base 5 - def initialize(attribute) 6 - @attribute = attribute 7 - end 8 - 9 - def template 10 - Def.new( 11 - visibility: @attribute.writer, 12 - name: "#{@attribute.name}=", 13 - params:, 14 - body:, 15 - ) 16 - end 17 - 18 - private 19 - 20 - def params 21 - [ 22 - PositionalParam.new( 23 - name: "value", 24 - default: nil, 25 - ), 26 - ] 27 - end 28 - 29 - def body 30 - [type_check, assignment] 31 - end 32 - 33 - def type_check 34 - TypeCheck.new( 35 - attribute_name: @attribute.name, 36 - variable_name: "value", 37 - ) 38 - end 39 - end 40 - end
-37
lib/literal/attributable/nodes.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributable::Nodes 4 - def self.node(name, *attributes) 5 - node_class = Data.define(*attributes) do 6 - class_eval <<~RUBY, __FILE__, __LINE__ + 1 7 - # frozen_string_literal: true 8 - 9 - def accept(visitor) 10 - visitor.#{name}(self) 11 - end 12 - RUBY 13 - end 14 - 15 - const_set(name, node_class) 16 - end 17 - 18 - node :Access, :collection, :key 19 - node :Assignment, :left, :right 20 - node :AssignSchema 21 - node :AttributeCoercion, :attribute 22 - node :BlockParam, :attribute 23 - node :Def, :visibility, :name, :params, :body 24 - node :DefaultAssignment, :attribute 25 - node :HashLiteral, :mappings 26 - node :InitializerCallback 27 - node :KeywordEscape, :attribute 28 - node :KeywordParam, :attribute 29 - node :KeywordSplat, :attribute 30 - node :Mapping, :left, :right 31 - node :PositionalParam, :name, :default 32 - node :PositionalSplat, :attribute 33 - node :Ref, :name 34 - node :Section, :name, :body 35 - node :Symbol, :name 36 - node :TypeCheck, :attribute_name, :variable_name 37 - end
-36
lib/literal/attribute.rb
··· 1 - # frozen_string_literal: true 2 - 3 - class Literal::Attribute 4 - RUBY_KEYWORDS = %i[alias and begin break case class def do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield].to_h { |k| [k, "__#{k}__"] }.freeze 5 - 6 - def initialize(name:, type:, kind:, reader:, writer:, default:, coercion:) 7 - @name = name 8 - @type = type 9 - @kind = kind 10 - @reader = reader 11 - @writer = writer 12 - @default = default 13 - @coercion = coercion 14 - end 15 - 16 - attr_reader :name, :type, :kind, :reader, :writer, :default, :coercion 17 - 18 - def coerce(value, context:) 19 - context.instance_exec(value, &@coercion) 20 - end 21 - 22 - def ruby_keyword? 23 - !!RUBY_KEYWORDS[@name] 24 - end 25 - 26 - def escaped_name 27 - RUBY_KEYWORDS[@name] || @name 28 - end 29 - 30 - def default_value 31 - case @default 32 - when Proc then @default.call 33 - else @default 34 - end 35 - end 36 - end
-19
lib/literal/attributes.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Attributes 4 - include Literal::Attributable 5 - 6 - private 7 - 8 - def generate_literal_initializer 9 - Generators::IVarInitializer.new(literal_attributes).call 10 - end 11 - 12 - def generate_literal_writer(attribute) 13 - Generators::IVarWriter.new(attribute).call 14 - end 15 - 16 - def generate_literal_reader(attribute) 17 - Generators::IVarReader.new(attribute).call 18 - end 19 - end
-15
lib/literal/attributes.test.rb
··· 1 - # frozen_string_literal: true 2 - 3 - class Person 4 - extend Literal::Attributes 5 - 6 - attribute :name, String, :positional, reader: :public 7 - attribute :age, Integer, reader: :public 8 - end 9 - 10 - test do 11 - person = Person.new("John", age: 30) 12 - 13 - expect(person.name) == "John" 14 - expect(person.age) == 30 15 - end
-23
lib/literal/concurrent_array.rb
··· 1 - # frozen_string_literal: true 2 - 3 - # @api private 4 - class Literal::ConcurrentArray 5 - def initialize 6 - @value = [] 7 - @mutex = Mutex.new 8 - end 9 - 10 - def <<(value) 11 - @mutex.synchronize { @value << value } 12 - end 13 - 14 - def each(&) 15 - @value.each(&) 16 - end 17 - 18 - def concat(other) 19 - @mutex.synchronize { @value.concat(other.value) } 20 - end 21 - 22 - protected def value = @value 23 - end
-21
lib/literal/concurrent_hash.rb
··· 1 - # frozen_string_literal: true 2 - 3 - # @api private 4 - class Literal::ConcurrentHash 5 - def initialize 6 - @hash = {} 7 - @mutex = Mutex.new 8 - end 9 - 10 - def [](key) 11 - @hash[key] 12 - end 13 - 14 - def []=(key, value) 15 - @mutex.synchronize { @hash[key] = value } 16 - end 17 - 18 - def each_value(&) 19 - @hash.each_value(&) 20 - end 21 - end
+11 -64
lib/literal/data.rb
··· 1 1 # frozen_string_literal: true 2 2 3 - class Literal::Data < Literal::Structish 3 + class Literal::Data < Literal::DataStructure 4 4 class << self 5 - def attribute(name, type, kind = nil, reader: :public, positional: false, default: nil) 6 - super(name, type, kind, reader:, writer: false, positional:, default:) 5 + def prop(name, type, kind = :keyword, reader: :public, default: nil) 6 + super(name, type, kind, reader:, writer: false, default:) 7 7 end 8 8 9 - def _load(data) 10 - data = Marshal.load(data) 9 + def literal_properties 10 + return @literal_properties if defined?(@literal_properties) 11 11 12 - allocate.tap do |instance| 13 - instance.instance_exec do 14 - @attributes = data[1] 15 - @literal_attributes = self.class.literal_attributes 16 - freeze 17 - end 12 + if superclass.is_a?(Literal::Data) 13 + @literal_properties = superclass.literal_properties.dup 14 + else 15 + @literal_properties = Literal::Properties::DataSchema.new 18 16 end 19 17 end 20 18 21 19 private 22 20 23 - def define_literal_methods(attribute) 24 - literal_extension.module_eval <<~RUBY, __FILE__, __LINE__ + 1 25 - # frozen_string_literal: true 26 - 27 - #{generate_literal_initializer} 28 - 29 - #{generate_literal_reader(attribute) if attribute.reader?} 30 - RUBY 31 - end 32 - 33 - def generate_literal_initializer 34 - Generators::DataInitializer.new(literal_attributes).call 35 - end 36 - 37 - def generate_literal_reader(attribute) 38 - Generators::StructReader.new(attribute).call 21 + def __literal_property_class__ 22 + Literal::DataProperty 39 23 end 40 - end 41 - 42 - def with(**new_attributes) 43 - new_attributes.each do |name, value| 44 - if Literal::TYPE_CHECKS 45 - unless (type = @literal_attributes[name].type) 46 - raise Literal::ArgumentError.new("Unknown attribute `#{name.inspect}`.") 47 - end 48 - 49 - unless type === value 50 - raise Literal::TypeError.expected(value, to_be_a: attribute.type) 51 - end 52 - 53 - unless value.frozen? 54 - new_attributes[name] = value.dup.tap(&:freeze) 55 - end 56 - end 57 - 58 - new_attributes[name] = value.frozen? ? value : value.dup 59 - end 60 - 61 - copy = dup 62 - copy.instance_variable_set(:@attributes, @attributes.merge(new_attributes)) 63 - copy.freeze 64 - copy 65 - end 66 - 67 - def _dump(level) 68 - Marshal.dump( 69 - [2, @attributes], 70 - ) 71 - end 72 - 73 - def marshal_load(data) 74 - @attributes = data[1] 75 - @literal_attributes = self.class.literal_attributes 76 - freeze 77 24 end 78 25 end
+13
lib/literal/data.test.rb
··· 1 + # frozen_string_literal: true 2 + 3 + class Person < Literal::Data 4 + prop :name, String 5 + end 6 + 7 + test do 8 + person = Person.new(name: +"John") 9 + expect(person.name) == "John" 10 + 11 + expect(person).to_be(:frozen?) 12 + expect(person.name).to_be(:frozen?) 13 + end
+7
lib/literal/data_property.rb
··· 1 + # frozen_string_literal: true 2 + 3 + class Literal::DataProperty < Literal::Property 4 + def generate_initializer_assign_value 5 + "#{ivar_ref} = #{local_var_ref}.frozen? ? #{local_var_ref} : #{local_var_ref}.freeze" 6 + end 7 + end
+32
lib/literal/data_structure.rb
··· 1 + # frozen_string_literal: true 2 + 3 + # @api private 4 + class Literal::DataStructure 5 + extend Literal::Properties 6 + 7 + def ==(other) 8 + if Literal::DataStructure === other 9 + to_h == other.to_h 10 + else 11 + false 12 + end 13 + end 14 + 15 + def hash 16 + [self.class, to_h].hash 17 + end 18 + 19 + def [](key) 20 + instance_variable_get("@#{key}") 21 + end 22 + 23 + def []=(key, value) 24 + @literal_properties[key].check(value) 25 + instance_variable_set("@#{key}", value) 26 + end 27 + 28 + def deconstruct_keys(keys) 29 + h = to_h 30 + keys ? h.slice(*keys) : h 31 + end 32 + end
-27
lib/literal/decorator.rb
··· 1 - # frozen_string_literal: true 2 - 3 - class Literal::Decorator < BasicObject 4 - extend ::Literal::Attributes 5 - 6 - def method_missing(method, ...) 7 - @__object__.public_send(method, ...) 8 - end 9 - 10 - def respond_to_missing?(method, include_private = false) 11 - @__object__.respond_to?(method, include_private) 12 - end 13 - 14 - def __getobj__ 15 - @__object__ 16 - end 17 - 18 - def __setobj__(object) 19 - @__object__ = object 20 - end 21 - 22 - # TODO: these are currently required by Literal::Attributes, but Literal::Attributes should be updated to be compatible with BasicObject. 23 - define_method :class, ::Object.instance_method(:class) 24 - define_method :raise, ::Object.instance_method(:raise) 25 - 26 - private :raise 27 - end
-37
lib/literal/formatter.rb
··· 1 - # frozen_string_literal: true 2 - 3 - class Literal::Formatter < Literal::Visitor 4 - INDENTATION_CHARACTER = " " 5 - 6 - def initialize 7 - @buffer = +"" 8 - @indentation_level = 0 9 - end 10 - 11 - def text(value) 12 - case value 13 - when String 14 - @buffer << value 15 - when Symbol 16 - @buffer << value.name 17 - else 18 - @buffer << value.to_s 19 - end 20 - end 21 - 22 - def comment(string) 23 - text "# #{string}" 24 - newline 25 - end 26 - 27 - def newline 28 - @buffer << "\n" << (INDENTATION_CHARACTER * @indentation_level) 29 - end 30 - 31 - def indent 32 - @indentation_level += 1 33 - newline 34 - yield 35 - @indentation_level -= 1 36 - end 37 - end
+5 -3
lib/literal/null.rb
··· 1 1 # frozen_string_literal: true 2 2 3 - Literal::Null = Literal::Singleton.new do 3 + module Literal::Null 4 + extend self 5 + 4 6 def inspect 5 7 "Literal::Null" 6 8 end 7 - end 8 9 9 - Literal::Null.freeze 10 + freeze 11 + end
+5
lib/literal/object.rb
··· 1 + # frozen_string_literal: true 2 + 3 + class Literal::Object 4 + extend Literal::Properties 5 + end
+86
lib/literal/properties.rb
··· 1 + # frozen_string_literal: true 2 + 3 + module Literal::Properties 4 + autoload :Schema, "literal/properties/schema" 5 + autoload :DataSchema, "literal/properties/data_schema" 6 + 7 + include Literal::Types 8 + 9 + def prop(name, type, kind = :keyword, reader: false, writer: false, default: nil, &coercion) 10 + if default && !(Proc === default || default.frozen?) 11 + raise Literal::ArgumentError.new("The default must be a frozen object or a Proc.") 12 + end 13 + 14 + unless Literal::Property::VISIBILITY_OPTIONS.include?(reader) 15 + raise Literal::ArgumentError.new("The reader must be one of #{Literal::Property::VISIBILITY_OPTIONS.map(&:inspect).join(', ')}.") 16 + end 17 + 18 + unless Literal::Property::VISIBILITY_OPTIONS.include?(writer) 19 + raise Literal::ArgumentError.new("The writer must be one of #{Literal::Property::VISIBILITY_OPTIONS.map(&:inspect).join(', ')}.") 20 + end 21 + 22 + if reader && :class == name 23 + raise Literal::ArgumentError.new( 24 + "The `:class` property should not be defined as a reader because it breaks Ruby's `Object#class` method, which Literal itself depends on.", 25 + ) 26 + end 27 + 28 + unless Literal::Property::KIND_OPTIONS.include?(kind) 29 + raise Literal::ArgumentError.new("The kind must be one of #{Literal::Property::KIND_OPTIONS.map(&:inspect).join(', ')}.") 30 + end 31 + 32 + property = __literal_property_class__.new( 33 + name:, 34 + type:, 35 + kind:, 36 + reader:, 37 + writer:, 38 + default:, 39 + coercion:, 40 + ) 41 + 42 + literal_properties << property 43 + __define_literal_methods__(property) 44 + include(__literal_extension__) 45 + end 46 + 47 + def literal_properties 48 + return @literal_properties if defined?(@literal_properties) 49 + 50 + if superclass.is_a?(Literal::Properties) 51 + @literal_properties = superclass.literal_properties.dup 52 + else 53 + @literal_properties = Literal::Properties::Schema.new 54 + end 55 + end 56 + 57 + private 58 + 59 + def __literal_property_class__ 60 + Literal::Property 61 + end 62 + 63 + def __define_literal_methods__(new_property) 64 + __literal_extension__.module_eval( 65 + __generate_literal_methods__(new_property), 66 + ) 67 + end 68 + 69 + def __literal_extension__ 70 + if defined?(@__literal_extension__) 71 + @__literal_extension__ 72 + else 73 + @__literal_extension__ = Module.new 74 + end 75 + end 76 + 77 + def __generate_literal_methods__(new_property) 78 + [ 79 + "# frozen_string_literal: true", 80 + literal_properties.generate_initializer, 81 + literal_properties.generate_to_h, 82 + (new_property.generate_writer_method if new_property.writer), 83 + (new_property.generate_reader_method if new_property.reader), 84 + ].join("\n") 85 + end 86 + end
+30
lib/literal/properties.test.rb
··· 1 + # frozen_string_literal: true 2 + 3 + class Person 4 + extend Literal::Properties 5 + 6 + prop :name, String, :positional, reader: :public 7 + prop :age, Integer, reader: :public 8 + end 9 + 10 + class Random 11 + extend Literal::Properties 12 + prop :begin, Integer, :positional, reader: :public 13 + end 14 + 15 + test do 16 + person = Person.new("John", age: 30) 17 + 18 + expect(person.name) == "John" 19 + expect(person.age) == 30 20 + end 21 + 22 + test "initializer type check" do 23 + expect { Person.new(1, age: "Joel") }.to_raise(Literal::TypeError) 24 + end 25 + 26 + test "initializer keyword check" do 27 + random = Random.new(1) 28 + 29 + expect(random.begin) == 1 30 + end
+10
lib/literal/properties/data_schema.rb
··· 1 + # frozen_string_literal: true 2 + 3 + class Literal::Properties::DataSchema < Literal::Properties::Schema 4 + def generate_initializer_body 5 + [ 6 + super, 7 + "freeze", 8 + ].join("\n") 9 + end 10 + end
+97
lib/literal/properties/schema.rb
··· 1 + # frozen_string_literal: true 2 + 3 + # @api private 4 + class Literal::Properties::Schema 5 + def initialize(properties_index: {}, sorted_properties: []) 6 + @properties_index = properties_index 7 + @sorted_properties = sorted_properties 8 + @mutex = Mutex.new 9 + end 10 + 11 + def [](key) 12 + @properties_index[key] 13 + end 14 + 15 + def <<(value) 16 + @mutex.synchronize do 17 + @properties_index[value.name] = value 18 + @sorted_properties << value 19 + @sorted_properties.sort! 20 + end 21 + 22 + self 23 + end 24 + 25 + def dup 26 + self.class.new( 27 + properties_index: @properties_index.dup, 28 + sorted_properties: @sorted_properties.dup, 29 + ) 30 + end 31 + 32 + def each(&) 33 + @sorted_properties.each(&) 34 + end 35 + 36 + def generate_initializer 37 + [ 38 + "def initialize(#{generate_initializer_params})", 39 + generate_initializer_body, 40 + "end", 41 + ].join("\n") 42 + end 43 + 44 + def generate_to_h 45 + [ 46 + "def to_h", 47 + "{", 48 + @sorted_properties.each.map do |property| 49 + "#{property.name}: @#{property.name}," 50 + end, 51 + "}", 52 + "end", 53 + ].join("\n") 54 + end 55 + 56 + private 57 + 58 + def generate_initializer_params 59 + @sorted_properties.each.map do |property| 60 + case property.kind 61 + when :* 62 + "*#{property.escaped_name}" 63 + when :** 64 + "**#{property.escaped_name}" 65 + when :& 66 + "&#{property.escaped_name}" 67 + when :positional 68 + if property.default 69 + "#{property.escaped_name} = Literal::Null" 70 + elsif property.type === nil # optional 71 + "#{property.escaped_name} = nil" 72 + else # required 73 + property.escaped_name 74 + end 75 + else # keyword 76 + if property.default 77 + "#{property.name}: Literal::Null" 78 + elsif property.type === nil 79 + "#{property.name}: nil" # optional 80 + else # required 81 + "#{property.name}:" 82 + end 83 + end 84 + end.join(", ") 85 + end 86 + 87 + def generate_initializer_body 88 + [ 89 + "@literal_properties = self.class.literal_properties", 90 + generate_initializer_handle_properties(@sorted_properties), 91 + ].join("\n") 92 + end 93 + 94 + def generate_initializer_handle_properties(properties) 95 + properties.each.map(&:generate_initializer_handle_property).join("\n") 96 + end 97 + end
+124
lib/literal/property.rb
··· 1 + # frozen_string_literal: true 2 + 3 + class Literal::Property 4 + ORDER = { :positional => 0, :* => 1, :keyword => 2, :** => 3, :& => 4 }.freeze 5 + RUBY_KEYWORDS = %i[alias and begin break case class def do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield].to_h { |k| [k, "__#{k}__"] }.freeze 6 + 7 + VISIBILITY_OPTIONS = Set[false, :private, :protected, :public].freeze 8 + KIND_OPTIONS = Set[:positional, :*, :keyword, :**, :&].freeze 9 + 10 + include Comparable 11 + 12 + def initialize(name:, type:, kind:, reader:, writer:, default:, coercion:) 13 + @name = name 14 + @type = type 15 + @kind = kind 16 + @reader = reader 17 + @writer = writer 18 + @default = default 19 + @coercion = coercion 20 + end 21 + 22 + attr_reader :name, :type, :kind, :reader, :writer, :default, :coercion 23 + 24 + def <=>(other) 25 + ORDER[@kind] <=> ORDER[other.kind] 26 + end 27 + 28 + def coerce(value, context:) 29 + context.instance_exec(value, &@coercion) 30 + end 31 + 32 + def ruby_keyword? 33 + !!RUBY_KEYWORDS[@name] 34 + end 35 + 36 + def escaped_name 37 + RUBY_KEYWORDS[@name] || @name 38 + end 39 + 40 + def default_value 41 + case @default 42 + when Proc then @default.call 43 + else @default 44 + end 45 + end 46 + 47 + def check(value) 48 + unless @type === value 49 + raise Literal::TypeError.expected(value, to_be_a: @type) 50 + end 51 + end 52 + 53 + def ivar_ref 54 + "@#{@name}" 55 + end 56 + 57 + alias_method :local_var_ref, :escaped_name 58 + 59 + def symbol_ref 60 + ":#{@name}" 61 + end 62 + 63 + def generate_reader_method 64 + [ 65 + "#{@reader || :public} ", 66 + [ 67 + "def #{@name}", 68 + "value = #{ivar_ref}", 69 + "@literal_properties[#{symbol_ref}].check(value)", 70 + "value", 71 + "end", 72 + ].join("\n"), 73 + ].join 74 + end 75 + 76 + def generate_writer_method 77 + [ 78 + "#{writer || :public} ", 79 + [ 80 + "def #{name}=(value)", 81 + "@literal_properties[:#{name}].check(value)", 82 + "@#{name} = value", 83 + "end", 84 + ].join("\n"), 85 + ].join 86 + end 87 + 88 + def generate_initializer_handle_property 89 + [ 90 + "# #{name}", 91 + (generate_initializer_escape_keyword if (@kind == :keyword) && ruby_keyword?), 92 + (generate_initializer_coerce_property if @coercion), 93 + (generate_initializer_assign_default if @default), 94 + (generate_initializer_check_type), 95 + (generate_initializer_assign_value), 96 + ].join("\n") 97 + end 98 + 99 + private 100 + 101 + def generate_initializer_escape_keyword 102 + "#{escaped_name} = binding.local_variable_get(#{symbol_ref})" 103 + end 104 + 105 + def generate_initializer_coerce_property 106 + "#{escaped_name} = @literal_properties[#{symbol_ref}].coerce(#{local_var_ref}, context: self)" 107 + end 108 + 109 + def generate_initializer_assign_default 110 + [ 111 + "if Literal::Null == #{local_var_ref}", 112 + "#{local_var_ref} = @literal_properties[#{symbol_ref}].default_value", 113 + "end", 114 + ].join("\n") 115 + end 116 + 117 + def generate_initializer_check_type 118 + "@literal_properties[:#{name}].check(#{escaped_name})" 119 + end 120 + 121 + def generate_initializer_assign_value 122 + "#{ivar_ref} = #{local_var_ref}" 123 + end 124 + end
-7
lib/literal/singleton.rb
··· 1 - # frozen_string_literal: true 2 - 3 - module Literal::Singleton 4 - def self.new(...) 5 - Class.new(...).new 6 - end 7 - end
+2 -33
lib/literal/struct.rb
··· 1 1 # frozen_string_literal: true 2 2 3 - class Literal::Struct < Literal::Structish 3 + class Literal::Struct < Literal::DataStructure 4 4 class << self 5 - def attribute(name, type, kind = nil, reader: :public, writer: :public, positional: false, default: nil) 5 + def prop(name, type, kind = :keyword, reader: :public, writer: :public, default: nil) 6 6 super 7 7 end 8 - 9 - private 10 - 11 - def generate_literal_initializer 12 - Generators::StructInitializer.new(literal_attributes).call 13 - end 14 - 15 - def generate_literal_writer(attribute) 16 - Generators::StructWriter.new(attribute).call 17 - end 18 - 19 - def generate_literal_reader(attribute) 20 - Generators::StructReader.new(attribute).call 21 - end 22 - end 23 - 24 - def marshal_load(data) 25 - case data 26 - when Hash # TODO: Remove this branch. 27 - @attributes = data[:attributes] 28 - freeze if data[:frozen?] 29 - when Array 30 - @attributes = data[1] 31 - freeze if data[2] 32 - end 33 - 34 - @literal_attributes = self.class.literal_attributes 35 - end 36 - 37 - def marshal_dump 38 - [2, @attributes, frozen?] 39 8 end 40 9 end
+35
lib/literal/struct.test.rb
··· 1 + # frozen_string_literal: true 2 + 3 + class Person < Literal::Struct 4 + prop :name, String 5 + end 6 + 7 + test do 8 + person = Person.new(name: "Joel") 9 + expect(person.name) == "Joel" 10 + end 11 + 12 + test do 13 + person = Person.new(name: "Joel") 14 + person.name = "Jill" 15 + expect(person.name) == "Jill" 16 + end 17 + 18 + test do 19 + person = Person.new(name: "Joel") 20 + expect(person.to_h) == { name: "Joel" } 21 + end 22 + 23 + test do 24 + a = Person.new(name: "Joel") 25 + b = Person.new(name: "Joel") 26 + 27 + expect(a) == b 28 + end 29 + 30 + test do 31 + a = Person.new(name: "Joel") 32 + b = Person.new(name: "Jill") 33 + 34 + expect(a) != b 35 + end
-43
lib/literal/structish.rb
··· 1 - # frozen_string_literal: true 2 - 3 - class Literal::Structish 4 - extend Literal::Attributable 5 - 6 - protected attr_reader :attributes 7 - 8 - def to_h 9 - @attributes.dup 10 - end 11 - 12 - def ==(other) 13 - case other 14 - when Literal::Structish 15 - @attributes == other.attributes 16 - else 17 - false 18 - end 19 - end 20 - 21 - def [](key) 22 - @attributes[key] 23 - end 24 - 25 - def []=(key, value) 26 - type = @literal_attributes[key].type 27 - 28 - if type === value 29 - @attributes[key] = value 30 - else 31 - raise Literal::TypeError.expected(value, to_be_a: type) 32 - end 33 - end 34 - 35 - def deconstruct 36 - @attributes.values 37 - end 38 - 39 - def deconstruct_keys(keys) 40 - h = to_h 41 - keys ? h.slice(*keys) : h 42 - end 43 - end
+1 -1
lib/literal/types.test.rb
··· 49 49 refute age_constraint === 17.5 50 50 end 51 51 52 - test "attribute constraints" do 52 + test "property constraints" do 53 53 age_constraint = _Constraint(Array, size: 2..3) 54 54 55 55 assert age_constraint === [1, 2]
+4 -4
lib/literal/types/boolean_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::BooleanType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::BooleanType 5 + extend self 8 6 9 7 def inspect = "_Boolean" 10 8 11 9 def ===(value) 12 10 true == value || false == value 13 11 end 12 + 13 + freeze 14 14 end
+4 -4
lib/literal/types/callable_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::CallableType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::CallableType 5 + extend self 8 6 9 7 def inspect = "_Callable" 10 8 11 9 def ===(value) 12 10 value.respond_to?(:call) 13 11 end 12 + 13 + freeze 14 14 end
+4 -4
lib/literal/types/constraint_type.rb
··· 2 2 3 3 # @api private 4 4 class Literal::Types::ConstraintType 5 - def initialize(*object_constraints, **attribute_constraints) 5 + def initialize(*object_constraints, **property_constraints) 6 6 @object_constraints = object_constraints 7 - @attribute_constraints = attribute_constraints 7 + @property_constraints = property_constraints 8 8 end 9 9 10 - def inspect = "_Constraint(#{@object_constraints.inspect}, #{@attribute_constraints.inspect})" 10 + def inspect = "_Constraint(#{@object_constraints.inspect}, #{@property_constraints.inspect})" 11 11 12 12 def ===(value) 13 13 @object_constraints.all? { |t| t === value } && 14 - @attribute_constraints.all? { |a, t| t === value.public_send(a) } 14 + @property_constraints.all? { |a, t| t === value.public_send(a) } 15 15 end 16 16 end
+4 -4
lib/literal/types/falsy_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::FalsyType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::FalsyType 5 + extend self 8 6 9 7 def inspect = "_Falsy" 10 8 11 9 def ===(value) 12 10 !value 13 11 end 12 + 13 + freeze 14 14 end
+4 -4
lib/literal/types/json_data_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::JSONDataType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::JSONDataType 5 + extend self 8 6 9 7 def inspect = "_JSONData" 10 8 ··· 20 18 false 21 19 end 22 20 end 21 + 22 + freeze 23 23 end
+4 -4
lib/literal/types/lambda_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::LambdaType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::LambdaType 5 + extend self 8 6 9 7 def inspect = "_Lambda" 10 8 11 9 def ===(value) 12 10 Proc === value && value.lambda? 13 11 end 12 + 13 + freeze 14 14 end
+1 -1
lib/literal/types/map_type.rb
··· 11 11 end 12 12 13 13 def ===(other) 14 - Enumerable === other && @shape.all? { |k, t| t === other[k] } 14 + @shape.all? { |k, t| t === other[k] } 15 15 end 16 16 end
+4 -4
lib/literal/types/procable_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::ProcableType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::ProcableType 5 + extend self 8 6 9 7 def inspect = "_Procable" 10 8 11 9 def ===(value) 12 10 Proc === value || value.respond_to?(:to_proc) 13 11 end 12 + 13 + freeze 14 14 end
+4 -4
lib/literal/types/truthy_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::TruthyType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::TruthyType 5 + extend self 8 6 9 7 def inspect = "_Truthy" 10 8 11 9 def ===(value) 12 10 !!value 13 11 end 12 + 13 + freeze 14 14 end
+4 -4
lib/literal/types/void_type.rb
··· 1 1 # frozen_string_literal: true 2 2 3 3 # @api private 4 - Literal::Types::VoidType = Literal::Singleton.new do 5 - def initialize 6 - freeze 7 - end 4 + module Literal::Types::VoidType 5 + extend self 8 6 9 7 def inspect = "_Void" 10 8 11 9 def ===(_) 12 10 true 13 11 end 12 + 13 + freeze 14 14 end
-18
lib/literal/visitor.rb
··· 1 - # frozen_string_literal: true 2 - 3 - # @api private 4 - class Literal::Visitor 5 - def visit(node) 6 - node.accept(self) 7 - end 8 - 9 - def visit_each(nodes) 10 - total = nodes.size 11 - i = 0 12 - while i < total 13 - visit(nodes[i]) 14 - i += 1 15 - yield if i < total 16 - end 17 - end 18 - end