String
In the prelude: every iyi program has it, with no import.
The compiler declares String's fields itself — @bytesize, @length and @c, in that order, in Program#initialize — because codegen lays out string literals before any prelude is read. So this file reopens a type that already has its shape and only says what can be done with it.
Stringclass
class String
# `"ab" * -3` answered `""` where the other library raises (III.1.7a).
# The size is a division, so the refusal names the string: `"abcd" *
# 600_000_000` panicked "arithmetic overflow", the machine's adder.
def *(count : Int32) : String
def +(other : String) : String
# Byte order, which for ASCII is dictionary order with the capitals first;
# a prefix sorts before what it prefixes. What `sorted` and `sorted_by`
# ask of a string.
def <(other : String) : Bool
# Thirty-two bytes a step while they last, then one at a time, and the
# same string is itself: a byte a step was eleven times Python's `==`,
# 230 ms for 1,000 compares of equal megabytes. No load needs alignment.
def ==(other : String) : Bool
# A negative *start* counts from the end, the same wrap as the one-index
# form. *count* stays non-negative: a negative count is not a wrap, it is a
# mistake, and it raises for that reason rather than being silently clamped.
def [](start : Int32, count : Int32) : String
# The character at *index*, asked for by `samples/iyi/calc`, which scans
# its input a character at a time. Byte-indexed: a byte is the character
# for ASCII, and `each_char` is the decoded walk. A negative index counts
# from the end; out of range after that wrap raises, `[]?` answers nil.
def [](index : Int32) : Char
def []?(index : Int32) : Char | ::Nil
# Where *search* first starts at or after byte *offset* - counted from
# the end when negative - in bytes, as Crystal's `byte_index` answers.
# Naive while that is cheap, and Rabin-Karp - one rolling hash per
# position, bytes compared only where it matches - once more than four
# bytes a position have been compared: a needle that keeps almost
# matching, `"a" * 1000 + "b"` in a run of `a`, made the naive loop
# n * m, 222 ms for a megabyte.
def byte_index(search : String, offset : Int32 = 0) : Int32 | ::Nil
def bytesize : Int32
# `'\n'` is a line's end, so it takes the `\r` before it, or a lone `\r`
# in its place, as the other library's does: `"a\r\n".chomp('\n')` is "a".
def chomp(char : Char) : String
# The other half of `upcase`, ASCII for the same reason.
def downcase : String
# Every character in order, decoded, as many as `size` counts: it yielded
# one per byte, so `"héllo".size` was 5 and `each_char` ran six times.
# Indexing stays byte-indexed (`[]` above), as `samples/iyi/calc` scans.
# A byte that begins no character is one U+FFFD, as in the other library:
# a lead byte took the bytes after it whatever they were, C3 41 was the
# one character U+00C1, and `"a" C3 '"'` swallowed the quote.
def each_char(& : (Char -> Nil)) : Nil
def empty? : Bool
def ends_with?(suffix : String) : Bool
# FNV-1a over the bytes: three lines, no table, and good enough for a
# linear-probing table of a few dozen entries.
def hash : Int32
def includes?(other : String) : Bool
# Where a character first is, or nil: the question a parser asks before
# it cuts.
def index(char : Char) : Int32 | ::Nil
# The literal that spells it, escaped as the other library's, a byte that
# is no UTF-8 as `\x`: unescaped, `["a\", \"b"]` printed as two strings.
def inspect : String
def lchop(char : Char) : String
# The lines of a text, blank ones included, a final newline ending the last
# line; `\r\n` ends one too, and a last `\r` with no `\n` after it is kept:
# `"1\r\n".lines[0].to_i` panicked where `File.read_lines` read 1.
def lines : Array(String)
def ljust(width : Int32, pad : Char = ' ') : String
# Pad on the left (rjust) or the right (ljust) to *width*, in characters
# as `size` counts them, the pad as its own bytes: in bytes,
# `"héllo".rjust(7, '.')` answered a string of `size` 6, and a `'·'`
# written as one byte left a lone continuation byte in the result.
def rjust(width : Int32, pad : Char = ' ') : String
# A zero `@length` on a non-empty string is "not counted yet", which is
# how the other library's own code leaves a string it builds: `to_json`'s
# result answered `size` 0 here while the field was returned as it stood.
# The layout is shared on purpose (SPEC.md Part V item 12), so the
# invariant is too. The count is of what `each_char` yields, a byte that
# begins no character one U+FFFD.
def size : Int32
# The pieces between separators, empty ones included: `"a,,b".split(',')`
# is three, `"a,".split(',')` is two, and `"".split(',')` is one empty
# piece - the count the other library gives, so a program reads the same
# under both.
def split(separator : Char) : Array(String)
# The three the Kemal port's path joining needs, byte-indexed: the same
# as by character while the strings are ASCII.
def starts_with?(prefix : String) : Bool
# A `Char` above ASCII is more than one byte, and these narrowed it to one
# through a *checked* convert, so `"a☃b".index('☃')` died of "arithmetic
# overflow". Each compares the character's own bytes now.
def starts_with?(char : Char) : Bool
# Without `Char#whitespace?`'s characters at either end, decoded: it read
# bytes, and `"b\u00A0".strip` kept the space the other library takes.
def strip : String
# The double nearest the decimal written here, correctly rounded: the
# digits and the exponent are read into the printer's bignum and the
# quotient taken to 54 bits with a sticky remainder, so what `to_s`
# wrote reads back as the same double, every time, and "0.1" is the
# double the literal `0.1` is. Accepts `-1.5`, `2.5e-3`, `1E10`, `.5`,
# `7.`, `Infinity`, `NaN`, with `Char#whitespace?` around as `to_i?`
# takes it; raises "not a number" for anything else.
def to_f : Float64
# The number this string spells - an optional sign and decimal digits,
# the whole string - as `samples/iyi/calc` reads a token. Raises on
# anything else, as the other library's does (SPEC.md III.1.7a): a
# silent 0 for `"oops"` is wrong twice; `to_i?` answers nil for it.
def to_i : Int32
# Around the digits, `Char#whitespace?` is skipped as the other
# library's `whitespace: true` skips it (`"42\n".to_i?` was nil). Read
# as a *negative* magnitude, which holds every value the type has:
# `2147483648` accumulated and negated, `Int32::MIN` overflowed on its
# last digit, a panic rather than the `nil` a `?` promises.
def to_i? : Int32 | ::Nil
def to_s : String
def to_unsafe : Pointer(UInt8)
# ASCII only, and the samples are ASCII. A prelude that folded case for
# every alphabet would be a Unicode table, which is more lines than this
# whole file and not what any sample is about.
def upcase : String
def self.build(capacity : Int32 = 64, & : (String::Builder -> _)) : String
# Interpolation is a call the compiler writes: `"a#{b}"` reaches here as
# `String.interpolation("a", b)` (`LiteralExpander#expand`). The overloads
# are what let the common shapes avoid work — a single string is itself.
def self.interpolation(value : String) : String
def self.interpolation(value) : String
def self.interpolation(*values : *T) : String forall T
# Allocates *bytesize* bytes, hands the buffer to the block to fill, and
# returns the string that results. The character count is left at zero,
# "not counted yet", for `size` to scan the first time it is asked: most
# strings built here - an encoding, a join, a line - are never asked, and
# counting them all was a second pass over every byte built.
#
# The byte after the last is a NUL, as a literal's is, so `to_unsafe` is
# a C string: `std/process` and `std/debug` hand one to the platform as a
# path. The byte was allocated for that and never written, and a string's
# chunk is atomic - not cleared - so a chunk that had held a longer
# string kept that string's byte there. Measured on Windows after a
# collection that freed longer strings: 961 of 1,000 strings built after
# it had no terminator, and `Process.run` of a path built that way
# answered "no such program" for a file that was there.
def self.new(bytesize : Int32, & : (Pointer(UInt8) -> Nil)) : String
end
Text — SPEC.md, "0.1.0", item 3.