Refactor std::mirror

This is a pretty big refactor of std::mirror to make it entirely
optional. As part of this std::format has been refactored as well, and
all implementations of the Inspect trait (now called Format) have been
moved into std::mirror.

Formatting objects is now part of std::mirror as that's where it
belongs, since the output is often based on the internal representation
of an object. The new API for pretty printing objects is a bit more
verbose, but not that much:

    import std::mirror

    10.mirror.inspect # => '10'

All of this means that the only module currently using std::module is
std::test::assert, which needs std::mirror to produce useful test
failures. Other code that previously used "inspect" has been refactored
to no longer depend on it.
parent b59f2f72
Pipeline #103876058 passed with stages
in 22 minutes and 19 seconds
......@@ -34,11 +34,7 @@ import std::byte_array
# 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 _)
......
......@@ -5,7 +5,6 @@
# would depend on `std::array` while `std::array` would depend on
# `std::iterator`.
import std::conversion::ToString
import std::format::(self, Formatter, Inspect)
import std::iterator::(self, Iterator)
import std::string_buffer::StringBuffer
......@@ -54,51 +53,3 @@ impl Array!(T) {
buffer.to_string
}
}
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) -> Nil 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(' }')
Nil
}
}
# 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) -> Nil {
formatter.push('Boolean')
Nil
}
}
impl Inspect for True {
def format_for_inspect(formatter: Formatter) -> Nil {
formatter.push('True')
Nil
}
}
impl Inspect for False {
def format_for_inspect(formatter: Formatter) -> Nil {
formatter.push('False')
Nil
}
}
......@@ -10,7 +10,6 @@
# numbers, you're better off using the `Array` object.
import std::conversion::ToArray
import std::conversion::ToString
import std::format::(self, Formatter, Inspect)
import std::index::(Index, SetIndex)
import std::iterator::(self, Iterator)
import std::length::Length
......@@ -347,17 +346,6 @@ impl Length for ByteArray {
}
}
impl Inspect for ByteArray {
# Formats this `ByteArray` into a human-readable representation.
def format_for_inspect(formatter: Formatter) -> Nil {
formatter.push('ByteArray { ')
formatter.push(length.to_string)
formatter.push(' bytes }')
Nil
}
}
impl ToByteArray for ByteArray {
# Converts `self` to a `ByteArray`.
def to_byte_array -> ByteArray {
......
......@@ -3,7 +3,6 @@ import std::compiler::source_location::SourceLocation
import std::compiler::token::Token
import std::conversion::ToString
import std::error::Error
import std::string_buffer::StringBuffer
# An error that may occur when parsing source code.
trait ParseError: Error {
......@@ -23,9 +22,7 @@ object TokenError {
@message = token.null?.if(
true: { 'More input was expected, but we ran out of input' },
false: {
StringBuffer
.new(token.value.inspect, " can't be used here")
.to_string
'The following unexpected input was encountered: ' + token.value
}
)
......
# Types for tokens produced by the lexer.
import std::compiler::source_location::SourceLocation
import std::format::Inspect
# A single token produced by a `Lexer`.
trait Token: Inspect {
trait Token {
# Returns the type of the token.
def type -> String
......
......@@ -500,8 +500,8 @@ impl SetIndex!(String, Type) for LayoutBuilder {
# Trying to redefine a member will result in a panic.
def []=(name: String, type: Type) -> Type {
@existing[name].if_true {
let error = StringBuffer
.new('The member ', name.inspect, ' has already been defined')
let error =
StringBuffer.new('The member ', name, ' has already been defined')
process.panic(error)
}
......
# 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) -> Nil {
formatter.push(to_string)
Nil
}
}
# Types for formatting objects in a human-readable manner.
import std::conversion::ToString
import std::string_buffer::StringBuffer
......@@ -72,28 +73,13 @@ impl Formatter for DefaultFormatter {
}
}
# A Trait that can be implemented to format an object for debugging purposes.
trait Inspect {
# Returns a human-readable representation of the object.
def inspect -> String {
ThisModule.inspect(self)
}
# Formats an object using the given formatter.
# A type that can be formatted into a human-readable format for debugging
# purposes.
trait Format {
# Formats `self` in a human-readable format for debugging purposes.
#
# Objects implementing this method should push their values into the given
# `Formatter`, instead of immediately returning a `String`.
def format_for_inspect(formatter: Formatter) -> Nil
}
# Returns a human-readable representation of the object.
#
# This method provides a default implementation of "inspect", allowing objects
# to redefine this method, without having to redefine all logic from scratch.
def inspect(value: Inspect) -> String {
let formatter = DefaultFormatter.new
value.format_for_inspect(formatter)
formatter.to_string
# The returned value is the formatter used to format the object. To obtain the
# formatting output as a `String`, simply send `to_string` to the returned
# `Formatter`.
def format!(F: Formatter)(formatter: F) -> F
}
# 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)
......@@ -102,10 +101,3 @@ impl ToRange!(Integer) for Integer {
Range.new(start: self, end: other)
}
}
impl Inspect for Integer {
def format_for_inspect(formatter: Formatter) -> Nil {
formatter.push(to_string)
Nil
}
}
# 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)
......@@ -623,59 +622,3 @@ 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
) -> Nil 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(' }')
Nil
}
}
This diff is collapsed.
......@@ -56,7 +56,10 @@ impl ToString for AddressParseError {
impl Error for AddressParseError {
def message -> String {
@address.inspect + ' is not a valid IP address'
@address.empty?.if(
true: { 'The String to parse into an IP address is empty' },
false: { @address + ' is not a valid IP address' }
)
}
}
......
# 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) -> Nil {
formatter.push('Nil')
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) -> Nil {
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(' }')
Nil
}
}
......@@ -3,7 +3,6 @@
# This module only offers types for binary and ternary tuples. If you need a
# combination of more values (e.g. a 4-arity tuple), it's best to define your
# own type instead.
import std::format::(Formatter, Inspect)
import std::hash::*
import std::operators::Equal
......@@ -51,28 +50,6 @@ object Pair!(A, B) {
}
}
impl Inspect for Pair!(A, B) {
def format_for_inspect(
formatter: Formatter
) -> Nil where A: Inspect, B: Inspect {
formatter.push('Pair { ')
formatter.descend {
@first.format_for_inspect(formatter)
}
formatter.push(', ')
formatter.descend {
@second.format_for_inspect(formatter)
}
formatter.push(' }')
Nil
}
}
impl Equal for Pair!(A, B) {
def ==(other: Self) -> Boolean where A: Equal, B: Equal {
(@first == other.first).and { @second == other.second }
......@@ -151,35 +128,6 @@ object Triple!(A, B, C) {
}
}
impl Inspect for Triple!(A, B, C) {
def format_for_inspect(
formatter: Formatter
) -> Nil where A: Inspect, B: Inspect, C: Inspect {
formatter.push('Triple { ')
formatter.descend {
@first.format_for_inspect(formatter)
}
formatter.push(', ')
formatter.descend {
@second.format_for_inspect(formatter)
}
formatter.push(', ')
formatter.descend {
@third.format_for_inspect(formatter)
}
formatter.push(' }')
Nil
}
}
impl Equal for Triple!(A, B, C) {
def ==(other: Self) -> Boolean where A: Equal, B: Equal, C: Equal {
(@first == other.first)
......@@ -197,4 +145,3 @@ impl Hash for Triple!(A, B, C) {
Nil
}
}
# A hash set implemented using a Map.
import std::format::(self, Formatter, Inspect)
import std::hash::Hash
import std::iterator::Iterator
import std::length::Length
......@@ -178,51 +177,3 @@ impl Length for Set!(V) {
@map.length
}
}
impl Inspect for Set!(V) {
# Returns a human-readable representation of this `Set`.
#
# # Examples
#
# Inspecting a `Set`:
#
# let set = Set.new
#
# set.insert('Alice')
# set.insert('Foo Street')
#
# set.inspect # => 'Set { "Alice", "Foo Street" }'
def inspect -> String where V: Inspect {
::format.inspect(self)
}
# Formats this `Set` into a human-readable representation.
def format_for_inspect(formatter: Formatter) -> Nil where V: Inspect {
let last = length - 1
let mut index = 0
formatter.push('Set')
empty?.if_true {
return
}
formatter.push(' { ')
each do (value) {
formatter.descend {
value.format_for_inspect(formatter)
}
(index < last).if_true {
formatter.push(', ')
}
index += 1
}
formatter.push(' }')
Nil
}
}
# Extensions for the `String` type that can only be defined later on in the
# bootstrapping process.
import std::byte_array::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) -> Nil {
formatter.push(_INKOC.string_format_debug(self))
Nil
}
}
impl ToByteArray for String {
# Returns a `ByteArray` containing the bytes of this `String`.
......
......@@ -8,7 +8,7 @@
# `try` keyword. The use of `try!` is discouraged as it will result in the
# entire test suite terminating in the event of an assertion failing.
import std::conditional::Conditional
import std::format::Inspect
import std::mirror::Mirrored
import std::operators::(Equal, Greater)
import std::process::(self, Process)
import std::string_buffer::StringBuffer
......@@ -66,48 +66,48 @@ object PanicTest {
}
# Asserts that the given arguments are equal to each other.
def equal!(T: Inspect + Equal)(given: T, expected: T) -> Nil {
def equal!(T: Mirrored + Equal)(given: T, expected: T) -> Nil {
(given == expected).if_true {
return
}
let error = StringBuffer.new(
'Expected ',
given.inspect,
given.mirror.inspect,
' to equal ',
expected.inspect,
expected.mirror.inspect,
)
process.panic(error)
}
# Asserts that the given arguments are not equal to each other.
def not_equal!(T: Inspect + Equal)(given: T, expected: T) -> Nil {
def not_equal!(T: Mirrored + Equal)(given: T, expected: T) -> Nil {
(given == expected).if_false {
return
}
let error = StringBuffer.new(
'Expected ',
given.inspect,
given.mirror.inspect,
' not to equal ',
expected.inspect,
expected.mirror.inspect,
)