Skip to content

Commit

Permalink
Merge pull request #426 from savi-lang/change/into-string-iso
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `IntoString` and `Inspect.into` no longer use `String'iso`.
  • Loading branch information
jemc authored Jan 31, 2023
2 parents 0d898f3 + 9416b2a commit 108e365
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 106 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ spec.core: PHONY SAVI
# Update deps for the specs for the core package.
spec.core.deps: PHONY SAVI
echo && $(SAVI) deps update --cd spec/core $(extra_args)
# TODO: Remove these temporary library patches:
sh -c 'cd spec/core/deps/github:savi-lang/Time/v0.20220513.0 && git apply ../../../../../../tmp-lib-patch-Time.patch'

# Run the specs for the core package in lldb for debugging.
spec.core.lldb: PHONY SAVI
Expand Down Expand Up @@ -94,6 +96,8 @@ example.compile: PHONY SAVI
# Update deps for the specs for the given example directory.
example.deps: PHONY SAVI
echo && $(SAVI) deps update --cd "$(dir)" $(extra_args)
# TODO: Remove these temporary library patches:
sh -c "cd $(dir)/deps/github:savi-lang/Time/v0.20220513.0 && git apply ../../../../../../../tmp-lib-patch-Time.patch"

# Compile the vscode extension.
vscode: PHONY SAVI
Expand Down
31 changes: 13 additions & 18 deletions core/Bytes.Format.savi
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@
:let _value Bytes'box
:new box _new(@_value)

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
out.push_byte('b')
out.push_byte('"')
@_value.each -> (byte |
case (
| byte >= 0x7f | out = byte.format.hex.with_prefix("\\x").into_string(--out)
| byte >= 0x7f | byte.format.hex.with_prefix("\\x").into_string(out)
| byte == '"' | out.push_byte('\\').push_byte('"')
| byte >= 0x20 | out.push_byte(byte)
| byte == '\n' | out.push_byte('\\').push_byte('n')
| byte == '\r' | out.push_byte('\\').push_byte('r')
| byte == '\t' | out.push_byte('\\').push_byte('t')
| out = byte.format.hex.with_prefix("\\x").into_string(--out)
| byte.format.hex.with_prefix("\\x").into_string(out)
)
)
out.push_byte('"')
--out

:fun into_string_space USize
// Use a conservative estimate, assuming all bytes will be escaped hex.
Expand All @@ -60,45 +59,42 @@
// It takes 68 bytes to show each row of the hex dump.
@_row_count * 68

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
@_row_count.times -> (row_index |
row_address = row_index * 16

// Emit the address shown on the left.
out = row_address.u32.format.hex.bare.into_string(--out)
row_address.u32.format.hex.bare.into_string(out)
out.push_byte(':')

// Emit the hex view shown in the center.
USize[8].times -> (pair_index |
out.push_byte(' ')
out = @_emit_hex_pair(--out, row_address + pair_index * 2)
@_emit_hex_pair(out, row_address + pair_index * 2)
)
out.push_byte(' ')

// Emit the ASCII view shown on the right.
USize[16].times -> (byte_index |
out = @_emit_ascii(--out, row_address + byte_index)
@_emit_ascii(out, row_address + byte_index)
)

// Emit a final newline for this row.
out.push_byte('\n')
)
--out

:fun _emit_hex_pair(out String'iso, pair_address USize)
out = @_emit_hex(--out, pair_address)
out = @_emit_hex(--out, pair_address + 1)
--out
:fun _emit_hex_pair(out String'ref, pair_address USize) None
@_emit_hex(out, pair_address)
@_emit_hex(out, pair_address + 1)

:fun _emit_hex(out String'iso, byte_address USize)
:fun _emit_hex(out String'ref, byte_address USize) None
try (
out = @_value[byte_address]!.format.hex.bare.into_string(--out)
@_value[byte_address]!.format.hex.bare.into_string(out)
|
out.push_byte(' ').push_byte(' ')
)
--out

:fun _emit_ascii(out String'iso, byte_address USize)
:fun _emit_ascii(out String'ref, byte_address USize) None
try (
byte = @_value[byte_address]!
if (byte > 0x20 && byte < 0x7f) (
Expand All @@ -109,4 +105,3 @@
|
out.push_byte(' ')
)
--out
41 changes: 8 additions & 33 deletions core/FloatingPoint.Format.savi
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,6 @@
@is_zero = False
@is_nan = False

:: Emit the represented value into the string, in the event that it is one
:: of the "special cases" (i.e. that it is not a finite non-zero value).
::
:: If the value `is_finite_non_zero`, it will be yielded back to the caller,
:: who has a responsibility to emit as desired into the string and return it.
:fun _into_string_unless_finite_non_zero(out String'iso) String'iso
:yields String'iso for String'iso
case (
| @is_finite_non_zero |
out = yield --out
| @is_zero |
if @is_negative out.push_byte('-')
out << "0.0"
| @is_nan |
out << "NaN"
|
if @is_negative out.push_byte('-')
out << "Infinity"
)
--out

:: Return the maxmium number of bytes that may be needed to emit the
:: stored value into a string buffer, in the event that it is one
:: of the "special cases" (i.e. that it is not a finite non-zero value).
Expand Down Expand Up @@ -185,11 +164,11 @@
FloatingPoint.Format.WithoutExponent(T)._new(@_value).into_string_space
)

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
if (@_value.power_of_10 > 2 || @_value.scientific_exponent < -3) (
FloatingPoint.Format.Scientific(T)._new(@_value).into_string(--out)
FloatingPoint.Format.Scientific(T)._new(@_value).into_string(out)
|
FloatingPoint.Format.WithoutExponent(T)._new(@_value).into_string(--out)
FloatingPoint.Format.WithoutExponent(T)._new(@_value).into_string(out)
)

:: Format the given floating-point with an exponent (i.e. scientific notation).
Expand Down Expand Up @@ -222,10 +201,10 @@
+ 1
+ @_value.scientific_exponent.format.decimal.into_string_space

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
// First, deal with any special cases (zero or non-finite numbers).
// We print these in a special (hard-coded) way and return early.
try (out << @_value._special_case_as_string!, return --out)
try (out << @_value._special_case_as_string!, return)

significand = @_value.significand
digit_count = @_value.digit_count
Expand All @@ -246,11 +225,9 @@
// Print the base-10 exponent suffix.
if exponent.is_nonzero (
out.push_byte('e')
out = exponent.into_string(--out)
exponent.into_string(out)
)

--out

:: Format the given floating-point with no exponent (no scientific notation).
:struct val FloatingPoint.Format.WithoutExponent(T FloatingPoint(T)'val)
:is IntoString
Expand Down Expand Up @@ -282,10 +259,10 @@

byte_count

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
// First, deal with any special cases (zero or non-finite numbers).
// We print these in a special (hard-coded) way and return early.
try (out << @_value._special_case_as_string!, return --out)
try (out << @_value._special_case_as_string!, return)

significand = @_value.significand
digit_count = @_value.digit_count
Expand Down Expand Up @@ -320,5 +297,3 @@
out.push_byte('.')
out.push_byte('0')
)

--out
2 changes: 1 addition & 1 deletion core/FloatingPoint.savi
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
FloatingPoint.Format(F64)._new(_FormattableF64.from_f64(@as_val.f64))

:is IntoString
:fun into_string(out String'iso): @format.shortest.into_string(--out)
:fun into_string(out String'ref): @format.shortest.into_string(out)
:fun into_string_space: @format.shortest.into_string_space

:: This trait isn't meant to be used externally. It's just a base implementation
Expand Down
19 changes: 9 additions & 10 deletions core/Inspect.savi
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,44 @@
:fun "[]!"(index USize) Any'box // TODO: use `box` instead of `Any'box`

:trait box _InspectCustom
:fun inspect_into(output String'iso) String'iso
:fun inspect_into(output String'ref) None

// TODO: Move this out of savi maybe? Does that make sense?
// TODO: Make this into a trait with "implement for"/typeclass style polymorphism
:module Inspect
:fun "[]"(input Any'box) String'val // TODO: use `box` instead of `Any'box`
@into(String.new_iso, input)
output = String.new
@into(output, input)
output.take_buffer

:fun out(input Any'box) // TODO: use `box` instead of `Any'box`
_FFI.puts(@[input].cstring)

:fun into(output String'iso, input Any'box) String'iso // TODO: use `box` instead of `Any'box` // TODO: use something like Crystal IO instead of String?
:fun into(output String'ref, input Any'box) None // TODO: use `box` instead of `Any'box`
case input <: (
| _InspectCustom | input.inspect_into(--output)
| Bytes'box | input.format.literal.into_string(--output)
| _InspectCustom | input.inspect_into(output)
| Bytes'box | input.format.literal.into_string(output)
| String'box |
output.push_byte('"')
output << input.clone // TODO: show some characters as escaped.
output.push_byte('"')
--output
| _InspectEach |
output.push_byte('[')
index USize = 0
while (index < input.size) (
if (index > 0) (output.push_byte(','), output.push_byte(' '))
try (
element = input[index]!
output = @into(--output, element)
@into(output, element)
)
index += 1
)
output.push_byte(']')
--output
| IntoString |
// If there's nothing more specific, then our last option is to print
// the same representation that `into_string` gives for that value.
input.into_string(--output)
input.into_string(output)
|
// Otherwise, fall back to just printing the name of the type.
output << reflection_of_runtime_type_name input
--output
)
25 changes: 10 additions & 15 deletions core/Integer.Format.savi
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
)
@_bcd = U64.BCD.new(value.u64)

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
if @_is_negative (
out.push_byte('-')
)
@_bcd.into_string(--out)
@_bcd.into_string(out)

:fun into_string_space USize
@_bcd.into_string_space + if @_is_negative (1 | 0)
Expand Down Expand Up @@ -80,7 +80,7 @@
// TODO: different strategy when `_has_leading_zeros` is False.
@_prefix.size + if (T.bit_width == 1) (1 | T.bit_width.usize / 4)

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
zeros = @_has_leading_zeros
out << @_prefix
case T.bit_width == (
Expand Down Expand Up @@ -121,7 +121,6 @@
digit = @_digit(4), if (zeros || digit != '0') (zeros = True, out.push_byte(digit))
digit = @_digit(0), out.push_byte(digit)
)
--out

:fun _digit(shr)
u4 = @_value.bit_shr(shr).u8.bit_and(0xf)
Expand Down Expand Up @@ -158,7 +157,7 @@
// TODO: different strategy when `_has_leading_zeros` is False.
@_prefix.size + T.bit_width.usize

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
show_zeros = @_has_leading_zeros
out << @_prefix

Expand All @@ -179,8 +178,6 @@
// If we haven't seen any zeros or ones yet, show at least one zero
if !show_zeros out.push_byte('0')

--out

:: Format the given integer as a Unicode codepoint.
:struct val Integer.Format.Unicode(T Integer(T)'val)
:is IntoString
Expand All @@ -189,9 +186,8 @@

:new val _new(@_value)

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
out.push_utf8(@_value.u32)
--out

:fun into_string_space USize
// This is only a rough guess
Expand All @@ -207,7 +203,7 @@
:let _value U32
:new val _new(value T): @_value = value.u32

:fun into_string(out String'iso) String'iso
:fun into_string(out String'ref) None
case (
| @_value < 127 && @_value >= 32 |
case @_value == (
Expand All @@ -225,19 +221,18 @@
| '\n' | out.push_byte('\\'), out.push_byte('n')
|
out.push_byte('\\'), out.push_byte('x')
out = @_value.u8.format.hex.bare.into_string(--out)
@_value.u8.format.hex.bare.into_string(out)
)
| @_value <= 0xff |
out.push_byte('\\'), out.push_byte('x')
out = @_value.u8.format.hex.bare.into_string(--out)
@_value.u8.format.hex.bare.into_string(out)
| @_value <= 0xffff |
out.push_byte('\\'), out.push_byte('u')
out = @_value.u16.format.hex.bare.into_string(--out)
@_value.u16.format.hex.bare.into_string(out)
|
out.push_byte('\\'), out.push_byte('U')
out = @_value.format.hex.bare.into_string(--out)
@_value.format.hex.bare.into_string(out)
)
--out

:fun into_string_space USize
case (
Expand Down
4 changes: 2 additions & 2 deletions core/Integer.savi
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
:fun format: Integer.Format(T)._new(@as_val)

:is IntoString
:fun into_string(out String'iso): @format.decimal.into_string(--out)
:fun into_string(out String'ref): @format.decimal.into_string(out)
:fun into_string_space: @format.decimal.into_string_space

:: This trait isn't meant to be used externally. It's just a base implementation
Expand Down Expand Up @@ -423,5 +423,5 @@
:fun member_name String

:is IntoString
:fun into_string(out String'iso): @member_name.into_string(--out)
:fun into_string(out String'ref): @member_name.into_string(out)
:fun into_string_space: @member_name.into_string_space
8 changes: 5 additions & 3 deletions core/IntoString.savi
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
:: These methods are used by string interpolation syntax, so ensuring that
:: a type implements this trait will make it directly usable in interpolation.
:trait box IntoString
:: Emit a representation of this value into the given String, preserving
:: its isolation and returning the String with the new content appended.
:fun box into_string(out String'iso) String'iso
:: Emit a representation of this value into the given `String`.
::
:: This method is expected by convention to append some bytes into the
:: `String` but not to modify any earlier portion of the `String`.
:fun box into_string(out String'ref) None

:: Return a conservative estimate for how much many bytes are required to hold
:: the string representation of this value when emitted with `into_string`.
Expand Down
4 changes: 2 additions & 2 deletions core/None.savi
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
:is IntoString
// TODO: These shouldn't need to be `:fun box` - `:fun non` should be okay.
:: When emitting into a string, emit nothing (i.e. an empty string).
:fun box into_string(out String'iso): --out
:fun box into_string(out String'ref): None
:fun box into_string_space USize: 0

:: When inspecting, print explicitly using the name `None`.
:fun box inspect_into(out String'iso): out << "None", --out
:fun box inspect_into(out String'ref) None: out << "None"
Loading

0 comments on commit 108e365

Please sign in to comment.