Skip to content
Snippets Groups Projects
Unverified Commit 60d86364 authored by Yorick Peterse's avatar Yorick Peterse
Browse files

Clean up extending of types in the runtime

Instead of various modules extending a built-in type, all this logic is
moved to separate extension modules. This makes it easier to figure out
what module defined something, keeps the code more consistend, and gives
greater control over the order in which types are refined.
parent 3634ceee
No related branches found
No related tags found
No related merge requests found
Showing
with 407 additions and 415 deletions
Loading
Loading
@@ -28,15 +28,24 @@ import std::block
import std::string
import std::array
import std::iterator
import std::byte_array
# Various types can not have their methods defined until other types have been
# set up. We extend such types using these extensions modules. By importing
# `self` as `_` we ensure no globals are created in this module, as the names of
# these globals would conflict.
import std::boolean::extensions::(self as _)
import std::integer::extensions::(self as _)
import std::float::extensions::(self as _)
import std::nil::extensions::(self as _)
import std::object::extensions::(self as _)
import std::string::extensions::(self as _)
import std::array::extensions::(self as _)
import std::range
import std::map::(Map as _Map)
import std::inspect
import std::byte_array
import std::range::(Range as _Range)
import std::process
import std::vm
import std::range::(Range as _Range)
import std::integer::extensions::(self as _)
import std::module
 
# These constants are re-exported so they're available to all modules by
Loading
Loading
Loading
Loading
@@ -4,6 +4,7 @@
# These types and methods are defined separately as otherwise `std::iterator`
# would depend on `std::array` while `std::array` would depend on
# `std::iterator`.
import std::format::(self, Formatter, Inspect)
import std::iterator::(self, Iterator)
 
impl Array!(T) {
Loading
Loading
@@ -26,3 +27,49 @@ impl Array!(T) {
}
}
}
impl Inspect for Array!(T) {
# Returns a human-readable representation of this `Array`.
#
# # Examples
#
# Converting an empty `Array`:
#
# Array.new.inspect # => 'Array'
#
# Converting an `Array` with one value:
#
# Array.new(10).inspect # => 'Array { 10 }'
#
# Converting an `Array` containing multiple values:
#
# Array.new(10, 20, 30).inspect # => 'Array { 10, 20, 30 }'
def inspect -> String where T: Inspect {
::format.inspect(self)
}
# Formats this `Array` into a human-readable representation.
def format_for_inspect(formatter: Formatter) where T: Inspect {
let last = length - 1
formatter.push('Array')
empty?.if_true {
return
}
formatter.push(' { ')
each_with_index do (value, index) {
formatter.descend {
value.format_for_inspect(formatter)
}
(index < last).if_true {
formatter.push(', ')
}
}
formatter.push(' }')
}
}
# Extensions for the `Boolean` types that can only be defined later on in the
# bootstrapping process.
import std::format::(self, Formatter, Inspect)
impl Inspect for Boolean {
def format_for_inspect(formatter: Formatter) {
formatter.push('Boolean')
}
}
impl Inspect for True {
def format_for_inspect(formatter: Formatter) {
formatter.push('True')
}
}
impl Inspect for False {
def format_for_inspect(formatter: Formatter) {
formatter.push('False')
}
}
Loading
Loading
@@ -402,13 +402,6 @@ impl ToByteArray for ByteArray {
}
}
 
impl ToByteArray for String {
# Returns a `ByteArray` containing the bytes of this `String`.
def to_byte_array -> ByteArray {
_INKOC.string_to_byte_array(self) as ByteArray
}
}
impl ByteArray {
# Returns an `Iterator` that iterates over all values in `self`.
def iter -> Iterator!(Integer) {
Loading
Loading
# Extensions for the `Float` type that can only be defined later on in the
# bootstrapping process.
import std::format::(Formatter, Inspect)
impl Inspect for Float {
def format_for_inspect(formatter: Formatter) {
formatter.push(to_string)
}
}
# Implementations of the Inspect trait for various core types.
#
# Implementing Inspect requires various other types to be in place, meaning we
# can't implement this earlier in modules such as `std::boolean`.
import std::conversion::ToString
import std::format::(self, Formatter, Inspect)
import std::map::Map
import std::mirror
impl Inspect for Object {
# Returns a human-readable representation of this object.
#
# # Examples
#
# Inspecting a simple object:
#
# Object.new.inspect # => 'Object'
#
# Inspecting an object with attributes:
#
# object Person {
# @name: String
# @age: Integer
#
# def init(name: String, age: Integer) {
# @name = name
# @age = age
# }
# }
#
# let person = Person.new(name: 'Alice', age: 28)
#
# person.inspect # => 'Person { @name = "Alice", @age = 28 }'
def inspect -> String {
::format.inspect(self)
}
# Formats a human-readable representation of this object.
def format_for_inspect(formatter: Formatter) {
let self_mirror = mirror.reflect_object(self)
let attributes = self_mirror.instance_attributes
let last_index = attributes.length - 1
formatter.push(self_mirror.name)
attributes.empty?.if_true {
return
}
formatter.push(' {')
attributes.each_with_index do (attr_name, index) {
formatter.push(' ')
formatter.push(attr_name)
formatter.push(' = ')
formatter.descend {
let value = _INKOC.get_attribute(self, attr_name) as ?Inspect
value.format_for_inspect(formatter)
}
(index < last_index).if_true {
formatter.push(',')
}
}
formatter.push(' }')
}
}
impl Inspect for Float {
def format_for_inspect(formatter: Formatter) {
formatter.push(to_string)
}
}
impl Inspect for Integer {
def format_for_inspect(formatter: Formatter) {
formatter.push(to_string)
}
}
impl Inspect for Nil {
def format_for_inspect(formatter: Formatter) {
formatter.push('Nil')
}
}
impl Inspect for Boolean {
def format_for_inspect(formatter: Formatter) {
formatter.push('Boolean')
}
}
impl Inspect for True {
def format_for_inspect(formatter: Formatter) {
formatter.push('True')
}
}
impl Inspect for False {
def format_for_inspect(formatter: Formatter) {
formatter.push('False')
}
}
impl Inspect for String {
# Formats a human-readable representation of this `String`, surrounded by
# quotes.
#
# # Examples
#
# Formatting a `String`:
#
# import std::format::DefaultFormatter
#
# let fmt = DefaultFormatter.new
#
# 'hello'.format_for_inspect(fmt)
#
# fmt.to_string # => '"hello"'
#
# Inspecting and printing a `String`:
#
# import std::stdio::stdout
# import std::format::DefaultFormatter
#
# let fmt = DefaultFormatter.new
#
# 'hello'.format_for_inspect(fmt)
#
# # This would print "hello" (including quotes) to STDOUT.
# stdout.print(fmt.to_string)
def format_for_inspect(formatter: Formatter) {
formatter.push(_INKOC.string_format_debug(self))
}
}
impl Inspect for Array!(T) {
# Returns a human-readable representation of this `Array`.
#
# # Examples
#
# Converting an empty `Array`:
#
# Array.new.inspect # => 'Array'
#
# Converting an `Array` with one value:
#
# Array.new(10).inspect # => 'Array { 10 }'
#
# Converting an `Array` containing multiple values:
#
# Array.new(10, 20, 30).inspect # => 'Array { 10, 20, 30 }'
def inspect -> String where T: Inspect {
::format.inspect(self)
}
# Formats this `Array` into a human-readable representation.
def format_for_inspect(formatter: Formatter) where T: Inspect {
let last = length - 1
formatter.push('Array')
empty?.if_true {
return
}
formatter.push(' { ')
each_with_index do (value, index) {
formatter.descend {
value.format_for_inspect(formatter)
}
(index < last).if_true {
formatter.push(', ')
}
}
formatter.push(' }')
}
}
impl Inspect for Map!(K, V) {
# Returns a human-readable representation of this `Map`.
#
# # Examples
#
# Inspecting a `Map`:
#
# let map = Map.new
#
# map['name'] = 'Alice'
# map['address'] = 'Foo Street'
#
# map.inspect # => 'Map { "name": "Alice", "address": "Foo Street" }'
def inspect -> String where K: Inspect, V: Inspect {
::format.inspect(self)
}
# Formats this `Map` into a human-readable representation.
def format_for_inspect(formatter: Formatter) where K: Inspect, V: Inspect {
let last = length - 1
let mut index = 0
formatter.push('Map')
empty?.if_true {
return
}
formatter.push(' { ')
each do (key, value) {
formatter.descend {
key.format_for_inspect(formatter)
}
formatter.push(': ')
formatter.descend {
value.format_for_inspect(formatter)
}
(index < last).if_true {
formatter.push(', ')
}
index += 1
}
formatter.push(' }')
}
}
# Extensions for the `Integer` type that can only be defined later on in the
# bootstrapping process.
import std::format::(Formatter, Inspect)
import std::iterator::(Enumerator, Iterator)
import std::process
import std::range::(Range, ToRange)
import std::string_buffer::StringBuffer
 
# The digits to use when converting an `Integer` to a `String` using a specific
Loading
Loading
@@ -84,3 +86,25 @@ impl Integer {
Enumerator.new(while: { index < max }, yield: { index += 1 })
}
}
impl ToRange!(Integer) for Integer {
# Returns a `Range` starting at `self` up to (and including) `other`.
#
# # Examples
#
# Creating a `Range`:
#
# let range = 1..10
#
# range.start # => 1
# range.end # => 10
def ..(other: Self) -> Range!(Self) {
Range.new(start: self, end: other)
}
}
impl Inspect for Integer {
def format_for_inspect(formatter: Formatter) {
formatter.push(to_string)
}
}
# A hash map using linear probing and Robin Hood bucket stealing.
import std::format::(self, Formatter, Inspect)
import std::hash::(Hash, Hasher)
import std::index::(Index, SetIndex)
import std::iterator::(Enumerator, Iterator)
Loading
Loading
@@ -616,3 +617,55 @@ impl Length for Map!(K, V) {
@length
}
}
impl Inspect for Map!(K, V) {
# Returns a human-readable representation of this `Map`.
#
# # Examples
#
# Inspecting a `Map`:
#
# let map = Map.new
#
# map['name'] = 'Alice'
# map['address'] = 'Foo Street'
#
# map.inspect # => 'Map { "name": "Alice", "address": "Foo Street" }'
def inspect -> String where K: Inspect, V: Inspect {
::format.inspect(self)
}
# Formats this `Map` into a human-readable representation.
def format_for_inspect(formatter: Formatter) where K: Inspect, V: Inspect {
let last = length - 1
let mut index = 0
formatter.push('Map')
empty?.if_true {
return
}
formatter.push(' { ')
each do (key, value) {
formatter.descend {
key.format_for_inspect(formatter)
}
formatter.push(': ')
formatter.descend {
value.format_for_inspect(formatter)
}
(index < last).if_true {
formatter.push(', ')
}
index += 1
}
formatter.push(' }')
}
}
# Extensions for the `Nil` type that can only be defined later on in the
# bootstrapping process.
import std::format::(Formatter, Inspect)
impl Inspect for Nil {
def format_for_inspect(formatter: Formatter) {
formatter.push('Nil')
}
}
# Extensions for the `Object` type that can only be defined later on in the
# bootstrapping process.
import std::format::(self, Formatter, Inspect)
import std::mirror
impl Inspect for Object {
# Returns a human-readable representation of this object.
#
# # Examples
#
# Inspecting a simple object:
#
# Object.new.inspect # => 'Object'
#
# Inspecting an object with attributes:
#
# object Person {
# @name: String
# @age: Integer
#
# def init(name: String, age: Integer) {
# @name = name
# @age = age
# }
# }
#
# let person = Person.new(name: 'Alice', age: 28)
#
# person.inspect # => 'Person { @name = "Alice", @age = 28 }'
def inspect -> String {
::format.inspect(self)
}
# Formats a human-readable representation of this object.
def format_for_inspect(formatter: Formatter) {
let self_mirror = mirror.reflect_object(self)
let attributes = self_mirror.instance_attributes
let last_index = attributes.length - 1
formatter.push(self_mirror.name)
attributes.empty?.if_true {
return
}
formatter.push(' {')
attributes.each_with_index do (attr_name, index) {
formatter.push(' ')
formatter.push(attr_name)
formatter.push(' = ')
formatter.descend {
let value = self_mirror.get_attribute(attr_name) as ?Inspect
value.format_for_inspect(formatter)
}
(index < last_index).if_true {
formatter.push(',')
}
}
formatter.push(' }')
}
}
Loading
Loading
@@ -94,19 +94,3 @@ trait ToRange!(T: Successor + SmallerOrEqual) {
# Returns a `Range` starting at `self` up to (and including) `other`.
def ..(other: T) -> Range!(T)
}
impl ToRange!(Integer) for Integer {
# Returns a `Range` starting at `self` up to (and including) `other`.
#
# # Examples
#
# Creating a `Range`:
#
# let range = 1..10
#
# range.start # => 1
# range.end # => 10
def ..(other: Self) -> Range!(Self) {
Range.new(start: self, end: other)
}
}
# Extensions for the `String` type that can only be defined later on in the
# bootstrapping process.
import std::byte_array::(ByteArray, ToByteArray)
import std::format::(Formatter, Inspect)
impl Inspect for String {
# Formats a human-readable representation of this `String`, surrounded by
# quotes.
#
# # Examples
#
# Formatting a `String`:
#
# import std::format::DefaultFormatter
#
# let fmt = DefaultFormatter.new
#
# 'hello'.format_for_inspect(fmt)
#
# fmt.to_string # => '"hello"'
#
# Inspecting and printing a `String`:
#
# import std::stdio::stdout
# import std::format::DefaultFormatter
#
# let fmt = DefaultFormatter.new
#
# 'hello'.format_for_inspect(fmt)
#
# # This would print "hello" (including quotes) to STDOUT.
# stdout.print(fmt.to_string)
def format_for_inspect(formatter: Formatter) {
formatter.push(_INKOC.string_format_debug(self))
}
}
impl ToByteArray for String {
# Returns a `ByteArray` containing the bytes of this `String`.
def to_byte_array -> ByteArray {
_INKOC.string_to_byte_array(self) as ByteArray
}
}
Loading
Loading
@@ -36,7 +36,6 @@ import test::std::test_float
import test::std::test_format
import test::std::test_fs
import test::std::test_map
import test::std::test_inspect
import test::std::test_integer
import test::std::test_io
import test::std::test_iterator
Loading
Loading
Loading
Loading
@@ -217,3 +217,14 @@ test.group('std::array::Array.contains?') do (g) {
assert.true(Array.new(10, 20).contains?(10))
}
}
test.group('std::array::Array.inspect') do (g) {
g.test('Inspecting an empty Array') {
assert.equal(Array.new.inspect, 'Array')
}
g.test('Inspecting a non-empty Array') {
assert.equal(Array.new(10).inspect, 'Array { 10 }')
assert.equal(Array.new(10, 20).inspect, 'Array { 10, 20 }')
}
}
import std::float::(INFINITY, NAN, NEGATIVE_INFINITY)
import std::test
import std::test::assert
object OneAttribute {
@number: Integer
def init {
@number = 10
}
}
object TwoAttributes {
@first: String
@second: Integer
def init {
@first = 'first'
@second = 10
}
}
object NestedObject {
@child: OneAttribute
def init {
@child = OneAttribute.new
}
}
test.group('std::object::Object.inspect') do (g) {
g.test('Inspecting an empty Object') {
assert.equal(Object.new.inspect, 'Object')
}
g.test('Inspecting an Object with one attribute') {
let obj = OneAttribute.new
assert.equal(obj.inspect, 'OneAttribute { @number = 10 }')
}
g.test('Inspecting an Object with multiple attributes') {
let obj = TwoAttributes.new
let possible = Array.new(
'TwoAttributes { @first = "first", @second = 10 }',
'TwoAttributes { @second = 10, @first = "first" }'
)
# The order of attributes is not guaranteed, so we can't perform an equality
# comparison as such a test would randomly fail.
assert.true(possible.contains?(obj.inspect))
}
g.test('Inspecting an Object containing another Object') {
let obj = NestedObject.new
assert.equal(
obj.inspect,
'NestedObject { @child = OneAttribute { @number = 10 } }'
)
}
}
test.group('std::float::Float.inspect') do (g) {
g.test('Inspecting a Float') {
assert.equal(1.0.inspect, '1.0')
assert.equal(1.123.inspect, '1.123')
}
g.test('Inspecting a NaN') {
assert.equal(NAN.inspect, 'NaN')
}
g.test('Inspecting Infinity') {
assert.equal(INFINITY.inspect, 'Infinity')
}
g.test('Inspecting negative Infinity') {
assert.equal(NEGATIVE_INFINITY.inspect, '-Infinity')
}
}
test.group('std::integer::Integer.inspect') do (g) {
g.test('Inspecting an Integer') {
assert.equal(10.inspect, '10')
assert.equal(-10.inspect, '-10')
}
}
test.group('std::nil::Nil.inspect') do (g) {
g.test('Inspecting Nil') {
assert.equal(Nil.inspect, 'Nil')
}
}
test.group('std::boolean::Boolean.inspect') do (g) {
g.test('Inspecting Boolean') {
assert.equal(Boolean.inspect, 'Boolean')
}
g.test('Inspecting boolean True') {
assert.equal(True.inspect, 'True')
}
g.test('Inspecting boolean False') {
assert.equal(False.inspect, 'False')
}
}
test.group('std::string::String.inspect') do (g) {
g.test('Inspecting an empty String') {
assert.equal(''.inspect, '""')
}
g.test('Inspecting a non-empty String') {
assert.equal('foo'.inspect, '"foo"')
}
}
test.group('std::array::Array.inspect') do (g) {
g.test('Inspecting an empty Array') {
assert.equal(Array.new.inspect, 'Array')
}
g.test('Inspecting a non-empty Array') {
assert.equal(Array.new(10).inspect, 'Array { 10 }')
assert.equal(Array.new(10, 20).inspect, 'Array { 10, 20 }')
}
}
test.group('std::map::Map.inspect') do (g) {
g.test('Inspecting an empty Map') {
assert.equal(Map.new.inspect, 'Map')
}
g.test('Inspecting a non-empty Map') {
let map = Map.new
map['foo'] = 10
map['bar'] = 20
let ins = map.inspect
let valid =
(ins == 'Map { "foo": 10, "bar": 20 }')
.or { ins == 'Map { "bar": 20, "foo": 10 }' }
assert.true(valid)
}
}
Loading
Loading
@@ -353,3 +353,10 @@ test.group('std::ineger::Integer.times') do (g) {
assert.equal(values, Array.new(0, 1, 2, 3))
}
}
test.group('std::integer::Integer.inspect') do (g) {
g.test('Inspecting an Integer') {
assert.equal(10.inspect, '10')
assert.equal(-10.inspect, '-10')
}
}
Loading
Loading
@@ -567,3 +567,23 @@ test.group('std::map::Map.length') do (g) {
assert.equal(map2.length, 2)
}
}
test.group('std::map::Map.inspect') do (g) {
g.test('Inspecting an empty Map') {
assert.equal(Map.new.inspect, 'Map')
}
g.test('Inspecting a non-empty Map') {
let map = Map.new
map['foo'] = 10
map['bar'] = 20
let ins = map.inspect
let valid =
(ins == 'Map { "foo": 10, "bar": 20 }')
.or { ins == 'Map { "bar": 20, "foo": 10 }' }
assert.true(valid)
}
}
Loading
Loading
@@ -56,3 +56,9 @@ test.group('std::nil::Nil.an_unknown_message') do (g) {
assert.equal(Nil.an_unknown_message, Nil)
}
}
test.group('std::nil::Nil.inspect') do (g) {
g.test('Inspecting Nil') {
assert.equal(Nil.inspect, 'Nil')
}
}
import std::test
import std::test::assert
 
object OneAttribute {
@number: Integer
def init {
@number = 10
}
}
object TwoAttributes {
@first: String
@second: Integer
def init {
@first = 'first'
@second = 10
}
}
object NestedObject {
@child: OneAttribute
def init {
@child = OneAttribute.new
}
}
test.group('std::object::Object.equal?') do (g) {
g.test('Comparing two objects that do not reside at the same address') {
let obj1 = Object.new
Loading
Loading
@@ -102,3 +128,37 @@ test.group('std::object::Object.not_nil?') do (g) {
assert.true(Object.new.not_nil?)
}
}
test.group('std::object::Object.inspect') do (g) {
g.test('Inspecting an empty Object') {
assert.equal(Object.new.inspect, 'Object')
}
g.test('Inspecting an Object with one attribute') {
let obj = OneAttribute.new
assert.equal(obj.inspect, 'OneAttribute { @number = 10 }')
}
g.test('Inspecting an Object with multiple attributes') {
let obj = TwoAttributes.new
let possible = Array.new(
'TwoAttributes { @first = "first", @second = 10 }',
'TwoAttributes { @second = 10, @first = "first" }'
)
# The order of attributes is not guaranteed, so we can't perform an equality
# comparison as such a test would randomly fail.
assert.true(possible.contains?(obj.inspect))
}
g.test('Inspecting an Object containing another Object') {
let obj = NestedObject.new
assert.equal(
obj.inspect,
'NestedObject { @child = OneAttribute { @number = 10 } }'
)
}
}
import std::byte_array::ByteArray
import std::fs::path::Path
import std::map::DefaultHasher
import std::test
Loading
Loading
@@ -142,3 +143,19 @@ test.group('std::string::String.byte') do (g) {
assert.equal('inko'.byte(-1), 111)
}
}
test.group('std::string::String.inspect') do (g) {
g.test('Inspecting an empty String') {
assert.equal(''.inspect, '""')
}
g.test('Inspecting a non-empty String') {
assert.equal('foo'.inspect, '"foo"')
}
}
test.group('std::string::String.to_byte_array') do (g) {
g.test('Converting a String to a ByteArray') {
assert.equal('inko'.to_byte_array, ByteArray.new(105, 110, 107, 111))
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment