Remove support for Array literals

Similar to the removal of Map literal support, this is aimed at
simplifying the syntax and making it more consistent. Arrays are now
created using `Array.new`, which takes every value as a separate
argument:

    Array.new(10, 20, 30, ...)

To make this more pleasant to work with, the methods `StringBuffer.new`
and `ByteArray.new` now take a rest argument. This means you don't have
to write `ByteArray.new(Array.new(...))`, and instead can write
`ByteArray.new(...)`. For the `StringBuffer` type this requires that we
manually create an instance of it. To remove the need for using VM
instructions directly, we introduce `Object.allocate` as a wrapper
around this. This allows one to manually create instances like so:

    static def some_method {
      let instance = allocate

      instance.init(...)

      instance
    }
parent d27a2430
Pipeline #76955346 failed with stages
in 14 minutes and 51 seconds
......@@ -581,7 +581,6 @@ module Inkoc
when :identifier then identifier_or_reassign(start)
when :constant then constant(start)
when :curly_open then block_without_arguments(start)
when :bracket_open then array(start)
when :define then def_method(start)
when :static then def_static_method(start)
when :do, :lambda then block(start, start.type)
......@@ -745,23 +744,6 @@ module Inkoc
AST::Body.new(nodes, start.location)
end
# Parses an array literal
#
# Example:
#
# [10, 20, 30]
def array(start)
values = []
while (token = @lexer.advance) && token.valid_but_not?(:bracket_close)
values << expression(token)
break if comma_or_break_on(:bracket_close)
end
new_array(values, start)
end
# Parses a method definition.
#
# Examples:
......@@ -1498,12 +1480,6 @@ module Inkoc
)
end
end
def new_array(values, start)
receiver = AST::Global.new(Config::ARRAY_CONST, start.location)
AST::Send.new('new', receiver, [], values, start.location)
end
end
# rubocop: enable Metrics/ClassLength
end
......@@ -1544,7 +1544,7 @@ module Inkoc
end
def on_raw_byte_array_from_array(*)
TypeSystem::Dynamic.new
typedb.byte_array_type.new_instance
end
def on_raw_byte_array_set(*)
......
......@@ -2156,7 +2156,7 @@ describe Inkoc::Pass::DefineType do
.self_type
.define_attribute('Integer', state.typedb.integer_type)
type = expression_type('let x: Array!(Integer) = [10]')
type = expression_type('let x: Array!(Integer) = Array.new(10)')
param = state
.typedb
......@@ -2894,7 +2894,7 @@ describe Inkoc::Pass::DefineType do
end
it 'returns the type of an empty Array' do
type = expression_type('[]')
type = expression_type('Array.new')
array_type = state.typedb.array_type
param =
......@@ -2905,7 +2905,7 @@ describe Inkoc::Pass::DefineType do
end
it 'returns the type of an Array of Strings' do
type = expression_type('["hello"]')
type = expression_type('Array.new("hello")')
array_type = state.typedb.array_type
......@@ -2919,7 +2919,7 @@ describe Inkoc::Pass::DefineType do
end
it 'returns the type of an Array of Integers' do
type = expression_type('[10]')
type = expression_type('Array.new(10)')
array_type = state.typedb.array_type
......
......@@ -38,9 +38,14 @@ _INKOC.set_object_name(Block, 'Block')
# building blocks of Inko, such as "Object.new" and the bits necessary to allow
# creating of modules.
impl Object {
## Creates a new instance of `self`, without sending `init` to the instance.
static def allocate -> Self {
_INKOC.set_object(False, self)
}
## Creates a new instance of `self` and sends `init` to the instance.
static def new -> Self {
let obj = _INKOC.set_object(False, self)
let obj = allocate
obj.init
......
......@@ -36,7 +36,7 @@ let YELLOW = '33'
## Wraps a `ToString` in an ANSI escape sequence.
def wrap(string: ToString, code: String) -> String {
StringBuffer.new([START, code, 'm', string.to_string, RESET]).to_string
StringBuffer.new(START, code, 'm', string.to_string, RESET).to_string
}
## Makes the `ToString` bold.
......
......@@ -4,14 +4,6 @@
#! same type in order. Arrays are mutable and allow you to add new values or
#! remove existing values.
#!
#! # Syntax
#!
#! Creating an Array is done using square brackets or by sending `new` to Array:
#!
#! [] # this is an empty Array
#! Array.new # this is also an empty Array
#! 'foo'
#!
#! # Indexing
#!
#! Like most programming languages Arrays indexes are zero based, with the first
......@@ -34,11 +26,11 @@ impl Array!(T) {
##
## Creating an empty Array:
##
## Array.new # => []
## Array.new
##
## Creating an Array with values:
##
## Array.new(10, 20, 30) # => [10, 20, 30]
## Array.new(10, 20, 30)
static def new!(V)(*values: V) -> Array!(V) {
values
}
......@@ -49,15 +41,14 @@ impl Array!(T) {
##
## Sending `clear` to an Array will remove all of its elements:
##
## let array = [10, 20, 30]
## let array = Array.new(10, 20, 30)
##
## array.clear
##
## array # => []
## array.empty? # => True
##
## Clear will return the Array itself so you can chain message sends:
##
## let array = [10, 20, 30]
## let array = Array.new(10, 20, 30)
##
## array.clear.length # => 0
def clear -> Self {
......@@ -71,10 +62,10 @@ impl Array!(T) {
##
## Pushing a value into an Array:
##
## let array = []
## let array = Array.new
##
## array.push(10) # => 10
## array # => [10]
## array[0] # => 10
def push(value: T) -> T {
self[length] = value
}
......@@ -87,17 +78,16 @@ impl Array!(T) {
##
## Popping an existing value:
##
## let array = [10]
## let array = Array.new(10)
##
## array.pop # => 10
## array # => []
## array.pop # => 10
## array.empty? # => True
##
## Popping a value when the Array is empty:
##
## let array = []
## let array = Array.new
##
## array.pop # => Nil
## array # => []
def pop -> ?T {
(length > 0).if true: {
remove_at(length - 1)
......@@ -113,18 +103,18 @@ impl Array!(T) {
## Removing an existing value will result in the value being removed from the
## Array and returned:
##
## let array = [10]
## let array = Array.new(10)
##
## array.remove_at(0) # => 10
## array # => []
## array.empty? # => True
##
## When removing a non-existing value the Array won't be modified, and the
## returned value will be Nil:
##
## let array = [10]
## let array = Array.new(10)
##
## array.remove_at(1) # => Nil
## array # => []
## array.empty? # => False
def remove_at(index: Integer) -> ?T {
_INKOC.array_remove(self, index)
}
......@@ -141,7 +131,7 @@ impl Array!(T) {
##
## import std::stdio::stdout
##
## [10, 20, 30].each do (number) {
## Array.new(10, 20, 30).each do (number) {
## stdout.print(number)
## }
def each(block: do (T)) {
......@@ -166,7 +156,7 @@ impl Array!(T) {
##
## import std::stdio::stdout
##
## [10, 20, 30].each_with_index do (number, index) {
## Array.new(10, 20, 30).each_with_index do (number, index) {
## stdout.print(index) # => 0, 1, 2
## }
def each_with_index(block: do (T, Integer)) {
......@@ -184,11 +174,11 @@ impl Array!(T) {
##
## Appending one `Array` to another:
##
## let numbers = [10, 20, 30]
## let numbers = Array.new(10, 20, 30)
##
## numbers.append([40, 50])
## numbers.append(Array.new(40, 50))
##
## numbers # => [10, 20, 30, 40, 50]
## numbers.length # => 5
def append(other: Self) -> Self {
other.each do (value: T) {
push(value)
......@@ -203,7 +193,7 @@ impl Array!(T) {
##
## Checking if an `Array` contains a value:
##
## [10, 20, 30].contains?(10) # => True
## Array.new(10, 20, 30).contains?(10) # => True
def contains?(value: T) -> Boolean where T: Equal {
each do (val) {
val == value
......@@ -223,11 +213,11 @@ impl Length for Array!(T) {
##
## Getting the length of an empty Array:
##
## [].length # => 0
## Array.new.length # => 0
##
## Getting the length of an Array with values:
##
## [10].length # => 1
## Array.new(10).length # => 1
def length -> Integer {
_INKOC.array_length(self)
}
......@@ -240,20 +230,20 @@ impl Index!(Integer, T) for Array!(T) {
##
## Retrieving a value by its index:
##
## let array = [10, 20, 30]
## let array = Array.new(10, 20, 30)
##
## array[1] # => 20
##
## We can also use a negative index to access a value from the back of the
## Array:
##
## let array = [10, 20, 30]
## let array = Array.new(10, 20, 30)
##
## array[-2] # => 20
##
## Accessing an out-of-bounds index will produce a Nil:
##
## let array = []
## let array = Array.new
##
## array[0] # => Nil
def [](index: Integer) -> ?T {
......@@ -273,24 +263,24 @@ impl SetIndex!(Integer, T) for Array!(T) {
##
## Setting an index to a value:
##
## let array = []
## let array = Array.new
##
## array[0] = 10 # => 10
## array # => [10]
## array # => Array.new(10)
##
## Setting an out-of-bounds index:
##
## let array = []
## let array = Array.new
##
## array[2] = 10 # => 10
## array # => [Nil, Nil, 10]
## array # => Array.new(Nil, Nil, 10)
##
## We can also use negative indexes:
##
## let array = [10]
## let array = Array.new(10)
##
## array[-1] = 20 # => 20
## array # => [20]
## array # => Array.new(20)
def []=(index: Integer, value: T) -> T {
_INKOC.array_set(self, index, value)
}
......@@ -303,7 +293,7 @@ impl ToArray!(T) for Array!(T) {
##
## "Converting" an array to an array:
##
## [10].to_array # => [10]
## Array.new(10).to_array # => Array.new(10)
def to_array -> Array!(T) {
self
}
......@@ -316,15 +306,15 @@ impl Equal for Array!(T) {
##
## Comparing two identical arrays:
##
## [10, 20, 30] == [10, 20, 30] # => True
## Array.new(10, 20, 30) == Array.new(10, 20, 30) # => True
##
## Comparing two arrays with a different length:
##
## [10] == [10, 20] # => False
## Array.new(10) == Array.new(10, 20) # => False
##
## Comparing two arrays with the same length but with different values:
##
## [10, 20] == [20, 10] # => False
## Array.new(10, 20) == Array.new(20, 10) # => False
def ==(other: Self) -> Boolean where T: Equal {
length == other.length
.if_false { return False }
......
......@@ -12,7 +12,7 @@ impl Array!(T) {
##
## Iterating over an `Array`:
##
## let numbers = [10, 20, 30]
## let numbers = Array.new(10, 20, 30)
## let iter = numbers.iter
##
## iter.next # => 10
......
......@@ -28,10 +28,10 @@ trait ToByteArray {
}
impl ByteArray {
## Creates a new `ByteArray` from the given `Array` of `Integer` values.
## Creates a new `ByteArray`.
##
## This method will panic if any of the integers in the `bytes` array are not
## in the range `0..256`.
## This method will panic if any of the `Integer` values passed to this method
## are not in the range `0..256`.
##
## # Examples
##
......@@ -41,13 +41,13 @@ impl ByteArray {
##
## ByteArray.new
##
## Creating a `ByteArray` from a list of `Integer` values:
## Creating a `ByteArray` with values:
##
## import std::byte_array::ByteArray
##
## ByteArray.new([10, 20, 30])
static def new(bytes: Array!(Integer) = []) -> Self {
_INKOC.byte_array_from_array(bytes) as ByteArray
## ByteArray.new(10, 20, 30)
static def new(*bytes: Integer) -> Self {
_INKOC.byte_array_from_array(bytes)
}
## Removes all values from this `ByteArray`.
......@@ -58,7 +58,7 @@ impl ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([10, 20, 30])
## let bytes = ByteArray.new(10, 20, 30)
##
## bytes.clear
## bytes.length # => 0
......@@ -94,7 +94,7 @@ impl ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([10])
## let bytes = ByteArray.new(10)
##
## bytes.pop # => 10
## bytes.length # => 0
......@@ -124,7 +124,7 @@ impl ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([10])
## let bytes = ByteArray.new(10)
##
## bytes.remove_at(0) # => 10
## bytes.length # => 0
......@@ -154,7 +154,7 @@ impl ByteArray {
## import std::stdio::stdout
## import std::byte_array::ByteArray
##
## ByteArray.new([10, 20, 30]).each do (byte) {
## ByteArray.new(10, 20, 30).each do (byte) {
## stdout.print(byte)
## }
def each(block: do (Integer)) {
......@@ -180,7 +180,7 @@ impl ByteArray {
## import std::stdio::stdout
## import std::byte_array::ByteArray
##
## ByteArray.new([10, 20, 30]).each_with_index do (byte, index) {
## ByteArray.new(10, 20, 30).each_with_index do (byte, index) {
## stdout.print(index) # => 0, 1, 2
## }
def each_with_index(block: do (Integer, Integer)) {
......@@ -205,7 +205,7 @@ impl ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([105, 110, 107, 111])
## let bytes = ByteArray.new(105, 110, 107, 111)
##
## bytes.drain_to_string # => 'inko'
## bytes.empty? # => True
......@@ -225,7 +225,7 @@ impl ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([1, 2, 3, 4])
## let bytes = ByteArray.new(1, 2, 3, 4)
## let sliced = bytes.slice(start: 1, length: 2)
##
## sliced[0] # => 2
......@@ -266,7 +266,7 @@ impl Index!(Integer, Integer) for ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([10, 20])
## let bytes = ByteArray.new(10, 20)
##
## bytes[0] # => 10
##
......@@ -274,7 +274,7 @@ impl Index!(Integer, Integer) for ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([10, 20])
## let bytes = ByteArray.new(10, 20)
##
## bytes[5] # => Nil
def [](index: Integer) -> ?Integer {
......@@ -300,7 +300,7 @@ impl SetIndex!(Integer, Integer) for ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([10, 20])
## let bytes = ByteArray.new(10, 20)
##
## bytes[0] = 30 # => 30
## bytes[0] # => 30
......@@ -321,7 +321,7 @@ impl ToString for ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([105, 110, 107, 111])
## let bytes = ByteArray.new(105, 110, 107, 111)
##
## bytes.to_string # => 'inko'
def to_string -> String {
......@@ -342,11 +342,11 @@ impl ToArray!(Integer) for ByteArray {
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new([105, 110, 107, 111])
## let bytes = ByteArray.new(105, 110, 107, 111)
##
## bytes.to_array # => [105, 110, 107, 111]
## bytes.to_array # => Array.new(105, 110, 107, 111)
def to_array -> Array!(Integer) {
let integers = []
let integers = Array.new
length.positive?.if_true {
# This fills up the Array with zeroes, removing the need for reallocating
......@@ -374,8 +374,8 @@ impl Equal for ByteArray {
##
## import std::byte_array::ByteArray
##
## ByteArray.new([10]) == ByteArray.new([10]) # => True
## ByteArray.new([10]) == ByteArray.new([20]) # => False
## ByteArray.new(10) == ByteArray.new(10) # => True
## ByteArray.new(10) == ByteArray.new(20) # => False
def ==(other: ByteArray) -> Boolean {
_INKOC.byte_array_equals(self, other)
}
......@@ -390,8 +390,8 @@ impl Length for ByteArray {
##
## import std::byte_array::ByteArray
##
## ByteArray.new.length # => 0
## ByteArray.new([10]).length # => 1
## ByteArray.new.length # => 0
## ByteArray.new(10).length # => 1
def length -> Integer {
_INKOC.byte_array_length(self)
}
......
......@@ -9,7 +9,7 @@
import std::byte_array::ByteArray
## The associated values for the first and second bytes in a keyword.
let ASSOCIATED_VALUES = [
let ASSOCIATED_VALUES = Array.new(
22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
......@@ -36,10 +36,10 @@ let ASSOCIATED_VALUES = [
22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22
]
)
## All available keywords, as ByteArray instances.
let WORD_LIST = [
let WORD_LIST = Array.new(
''.to_byte_array,
''.to_byte_array,
'as'.to_byte_array,
......@@ -62,7 +62,7 @@ let WORD_LIST = [
''.to_byte_array,
''.to_byte_array,
'object'.to_byte_array
]
)
## Input values with a length outside of this `Range` definitely can not be a
## keyword.
......
......@@ -42,13 +42,13 @@ object CallFrame {
impl ToString for CallFrame {
## Formats the CallFrame as a String
def to_string -> String {
let buffer = StringBuffer.new([
let buffer = StringBuffer.new(
@path.to_string.inspect,
', line ',
@line.to_string,
', in ',
@name.inspect,
])
)
buffer.to_string
}
......@@ -90,7 +90,7 @@ impl ToString for CallFrame {
## first.line # => 8
def stacktrace(skip = 1, limit: ?Integer = Nil) -> Array!(CallFrame) {
let mut raw_frames = _INKOC.stacktrace(limit, skip)
let mut frames = []
let mut frames = Array.new
raw_frames.each do (raw_frame) {
frames.push(
......
......@@ -162,7 +162,7 @@ def working_directory=(directory: ToPath) !! IOError -> Path {
## import std::env
##
## # Assuming this program is executed using `inko foo.inko first second`:
## env.arguments # => ['first', 'second']
## env.arguments # => Array.new('first', 'second')
def arguments -> Array!(String) {
_INKOC.env_arguments
}
......@@ -33,7 +33,7 @@
#! import std::ffi::types
#! import std::process
#!
#! let libc = Library.new(['libc.so.6'])
#! let libc = Library.new(Array.new('libc.so.6'))
#!
#! process.pinned {
#! let errno = libc.variable('errno')
......@@ -56,7 +56,7 @@
#! import std::ffi::Library
#! import std::ffi::types
#!
#! let libc = Library.new(['libc.so.6'])
#! let libc = Library.new(Array.new('libc.so.6'))
#!
#! `Library.new` takes an `Array!(String)` to use for searching libraries. The
#! values can be library file names, relative paths, or absolute paths. If
......@@ -67,7 +67,7 @@
#! import std::ffi::Library
#! import std::ffi::types
#!
#! let libc = Library.new(['libc.so', 'libc.so.6', 'libSystem.dylib'])
#! let libc = Library.new(Array.new('libc.so', 'libc.so.6', 'libSystem.dylib'))
#!
#! Once the library has been loaded, we can attach functions by sending the
#! `function` message to the library:
......@@ -75,19 +75,19 @@
#! import std::ffi::Library
#! import std::ffi::types
#!
#! let libc = Library.new(['libc.so.6'])
#! let puts = libc.function('puts', [types.string], types.i32)
#! let libc = Library.new(Array.new('libc.so.6'))
#! let puts = libc.function('puts', Array.new(types.string), types.i32)