Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype Literal::Array #133

Merged
merged 4 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/literal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module Literal
autoload :Flags16, "literal/flags"
autoload :Flags32, "literal/flags"
autoload :Flags64, "literal/flags"
autoload :Array, "literal/array"
autoload :ArrayGeneric, "literal/array"

# Errors
autoload :Error, "literal/errors/error"
Expand All @@ -30,6 +32,10 @@ def self.Enum(type)
end
end

def self.Array(type)
Literal::ArrayGeneric.new(type)
end

def self.check(actual:, expected:)
if expected === actual
true
Expand Down
100 changes: 100 additions & 0 deletions lib/literal/array.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

class Literal::ArrayGeneric
def initialize(type)
@type = type
end

def new(*value)
Literal::Array.new(@type, value)
end
Comment on lines +8 to +10
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This importantly makes a copy of the array since it picks up the splat. This is essential to maintain the integrity of the array, since otherwise it could be mutated elsewhere. This value should never be exposed. Even to_a should only expose a duplicate.


alias_method :[], :new

def ===(value)
Literal::Array === value && @type == value.__type__
end

def inspect
"Literal::Array(#{@type.inspect})"
end
end

class Literal::Array
include Enumerable

def initialize(type, value)
collection_type = Literal::Types::ArrayType.new(type)

Literal.check(actual: value, expected: collection_type) do |c|
c.fill_receiver(receiver: self, method: "#initialize")
end

@__type__ = type
@__value__ = value
@__collection_type__ = collection_type
end

attr_reader :__type__, :__value__

def freeze
@__value__.freeze
super
end

def each(...)
@__value__.each(...)
end

def map(type, &)
Literal::Array.new(type, @__value__.map(&))
end

def [](index)
@__value__[index]
end

def []=(index, value)
Literal.check(actual: value, expected: @__type__) do |c|
c.fill_receiver(receiver: self, method: "#[]=")
end

@__value__[index] = value
end

def <<(value)
Literal.check(actual: value, expected: @__type__) do |c|
c.fill_receiver(receiver: self, method: "#<<")
end

@__value__ << value
self
end

def push(value)
Literal.check(actual: value, expected: @__type__) do |c|
c.fill_receiver(receiver: self, method: "#push")
end

@__value__.push(value)
self
end

def unshift(value)
Literal.check(actual: value, expected: @__type__) do |c|
c.fill_receiver(receiver: self, method: "#unshift")
end

@__value__.unshift(value)
self
end

def to_a
@__value__.dup
end

alias_method :to_ary, :to_a

def pop(...) = @__value__.pop(...)
def shift(...) = @__value__.shift(...)
end
70 changes: 70 additions & 0 deletions test/array.test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

test "#initialize" do
expect {
Literal::Array(String).new(1, 2, 3)
}.to_raise(Literal::TypeError)
end

test "#to_a" do
array = Literal::Array(Integer).new(1, 2, 3)
expect(array.to_a) == [1, 2, 3]

internal_array = array.__value__
refute internal_array.equal?(array.to_a)
end

test "#map maps each item correctly" do
array = Literal::Array(Integer).new(1, 2, 3)

expect(
array.map(String, &:to_s).to_a
) == ["1", "2", "3"]
end

test "#map raises if the type is wrong" do
array = Literal::Array(Integer).new(1, 2, 3)

expect {
array.map(Integer, &:to_s)
}.to_raise(Literal::TypeError)
end

test "#[]" do
array = Literal::Array(Integer).new(1, 2, 3)

expect(array[0]) == 1
expect(array[1]) == 2
expect(array[2]) == 3
expect(array[3]) == nil
end

test "#[]= raises if the type is wrong" do
array = Literal::Array(Integer).new(1, 2, 3)

expect { array[0] = "1" }.to_raise(Literal::TypeError)
end

test "#[]= works as expected" do
array = Literal::Array(Integer).new(1, 2, 3)

array[0] = 5
array[3] = 6

expect(array[0]) == 5
expect(array[3]) == 6
end

test "#<< inserts a new item" do
array = Literal::Array(Integer).new(1, 2, 3)

array << 4

expect(array.to_a) == [1, 2, 3, 4]
end

test "#<< raises if the type is wrong" do
array = Literal::Array(Integer).new(1, 2, 3)

expect { array << "4" }.to_raise(Literal::TypeError)
end
Loading