Format v0.1.0
String conversion and formatting
Installation
Add Format to the [Dependencies] table of your package's Rux.toml:
[Dependencies]
Format = { Namespace = "Rux", Version = "0.1.0" }
Standard packages are declared by hand: the registry does not serve them, so rux add Rux/Format cannot add them.
Dependencies
Guide
String conversion and formatting: turning values into text, and text back into values.
What it provides
- Placeholder rendering —
WriteFormatfills each{}from the next argument into a destination the caller supplies, andRenderis the allocating entry point that answers with aStringof its own. Used byRux/Ioto implementPrintandPrintLine. - A pattern that is wrong is refused, not printed — the grammar is
{},{:spec},{{and}}, and anything else containing a brace is a mistake. A specification that does not parse reportsInvalidSpecificationwith its byte offset into the pattern; a placeholder count that does not match the arguments reportsArgumentCountMismatchwith both numbers. Both are found before anything is written, so a wrong pattern produces no output rather than a prefix of one. A value's own rendering can still fail partway, and what it managed to write stays written. - Primitive conversion, both directions — every boolean, character and integer width, and
float8throughfloat64, implementsDisplayandDebug, and the parsers read text back.float80and wider are reached through the wide values below, because their text allocates. The two halves share the digit machinery, the base handling and the exactness argument, which is why they are one package: splitting them would duplicate all three and let the halves disagree about what a value's text is. - The presentation contracts live in
Rux/Text—Display,Debug,TextWriter,FormatSpecandFormatErrorare declared there, so a package can describe its own values without depending on this conversion engine. What stays here is the numeric spelling of a value: digits, sign, base prefix and zero padding. - The digit machinery —
WriteInt,WriteUint,WriteFloatand the decimal helpers, writing into a stack buffer throughByteCursorso no primitive's rendering allocates. - Borrowed primitive receivers — primitive
WriteDisplayandWriteDebugmethods takeself: &T. Their scalar reads preserve the existing output without transferring ownership or allocating; both direct calls and interface views borrow the caller's storage. - Narrow floats read back exactly —
ParseFloat64andParseFloat32form the decimal as an exact ratio and take the significand's bits from it, rounding to nearest with ties to even, so what a decimal names is the float nearest it and never one step off. Afloat32is read at its own precision rather than at afloat64's and narrowed, which would round twice. Overflow is refused rather than rounded to an infinity; underflow is the signed zero it rounds to. With the renderer this closes the loop: everyfloat32andfloat64renders to a decimal that reads back as the same bits, across the whole exponent range. - Parsing, and where it stopped — every width has a
Parse*answeringT ! ParseError, and everyParseErrorsays where in the text it was found:InvalidCharacterat the byte,Overflowat the digit that passed the widest magnitude or at the end of a number that does not fit the width asked for, andEmptyat zero, which is an empty text's only position.ParseErrorimplementsText::ParseFailure, so an embedding grammar adds its token's offset and the positions compose. There is nobool-and-output-slot overload: aResultcannot be ignored the way a returnedboolcould, and it carries why as well as whether. - An allocation failure is not an overflow — the wide float parser builds exact big integers to round with, and a failure to get storage for one reports
OutOfMemoryrather thanOverflow. The two want different responses: one input is too large for the format whatever the machine does, the other reads fine on a less busy machine. - Two answers for a value that is not a character —
DisplaywritesReplacementCharacter, U+FFFD, for achar8above 0x7F, achar16that is half of a surrogate pair, and achar32or any wider character cast from a number no character has.Debugwrites the value itself instead —'\x{80}'for a stray byte,'\u{d800}'for a lone half — so two values that render alike for a reader are told apart for a program, and achar128,char256orchar512holding a number past 2^64 keeps every digit of it in the escape. Achar64or wider is checked at its own width before anything is narrowed, because0x100000041narrows into a perfectly goodAand the wrong character is harder to notice than a refused one. Neither half is a validation:Rux/Text's strict conversions still refuse what is not text. - Character slices at all three widths —
char8[..]is copied through, andchar16[..]andchar32[..]are transcoded to UTF-8 under the same replacement rule, decoding surrogate pairs and replacing a lone half. A width counts the characters a slice renders rather than the units it holds, and nothing is transcoded into a buffer first, so a slice has no length bound. - Narrow floats render the shortest decimal that reads back as the same bits — exactly, by the method in Docs/NarrowFloat.md: the value and the gaps to its two neighbours are held as exact integer ratios in a fixed workspace, digits come out one at a time, and generation stops the moment what has been written is nearer this value than either neighbour. So
0.1prints as0.1and a third of one prints to the sixteen digits it needs rather than to a fixed fifteen. Nothing allocates and nothing is approximated, so there is no fallback path.float16andfloat8render through the same generator at their own precision, so the shortest digits are the ones that read back at that width rather than at afloat32's —float16::Max, 65504, prints as65500.0, since no otherfloat16is nearer.{}switches to scientific notation from a decimal exponent of 5 for afloat16and 3 for afloat8, the digit counts that tell every value of the width apart, as 9 does for afloat32and 17 for afloat64. - A precision is exact, or refused —
{:.N}means N digits after the decimal point, rounded to nearest with ties to even, generated from the same exact ratio the shortest form is. A carry runs the whole length of the digits, so{:.1}of 9.99 is10.0; a value far below the last place is a tail of zeros; and{:.20}of0.1is the float's own expansion rather than a tenth's. The supported precision is 0 to 1,100 digits, which is exactly enough for everyfloat64, and a request past it reportsUnsupportedRequest— there is no fallback to a different rendering. - Scientific notation on request —
{:e}and{:E}write afloat8,float16,float32orfloat64as one digit, a point, the remaining digits, the mark and a signed exponent of at least two digits:{:e}of123456.0is1.23456e+05and{:E}of1e300is1.0E+300, the same spelling{}falls back to for a value too large or too small for fixed notation. Without a precision the digits are the shortest that read back as the same value, with.0after a lone digit; with one, the precision counts the digits after the point, exact and rounded with ties to even, so{:.3e}of1234.5678is1.235e+03and{:.0e}of9.5is1e+01. A zero is0.0e+00. Any other style on a float reportsUnsupportedRequest. - NaN and infinity are words under every placeholder —
NaN,Infand-Inf, whatever precision or style was asked for, with the placeholder's sign, width, fill and alignment applied:{:+.2}of an infinity is+Inf. A NaN shows no sign. - A based integer keeps its sign —
{:b},{:o},{:x}and{:X}of a negative signed integer write a minus sign and the magnitude's digits, not two's-complement bits, so{:b}of-5i8is-101and the text does not depend on the type's width. - Float support —
IsFinite,IsInfiniteandIsNanforfloat8,float16,float32andfloat64, plusPow10andScaleByPow10, so decimal conversion stays exact where it can be. - The wide widths —
Float80,Float128,Float256andFloat512, held as their bits. A value is made from bits, read from text, or taken from the primitive of its width withFromValue—Float128::FromValue(x)for afloat128x— andValue()hands the primitive back, so a computed value renders withFloat128::FromValue(x).FormatWith(allocator, writer, spec)and a parsed one is computed with asParseFloat128(allocator, text)?.Value(). Both directions move the bits and nothing else; aFloat80keeps only the low eighty, so the six padding bytes of afloat80's storage are neither read nor written. Each answersIsNan,IsInfinite,IsFiniteandIsNegative, and renders throughFormatWithas the shortest decimal that reads back to exactly the same bits — found with exact big-integer arithmetic, which allocates. None of the four implementsDisplayorDebug, and neither do the primitives they bridge: the protocols offer nowhere to pass an allocator, and implementing them anyway would mean a hidden allocation behind an ordinary{}and no way for a caller who has chosen an allocator to make this one follow. Where a rendering allocates, the caller says from where.ParseFloat80throughParseFloat512read decimal text to the nearest value with ties to even, and every rendering and reading is held to reference vectors the compiler's own exact float arithmetic produced independently.
Example
import Allocator::{ Allocator, SystemAllocator };
import Format::{ Render, WriteFormat };
import Text::{ BufferWriter, String, TextWriter };
func Main() -> int ! FormatError {
// Into a destination the caller owns, allocating nothing.
var storage: char8[64];
var buffer = BufferWriter((@storage[0])[..64]);
let writer: &var TextWriter = buffer;
WriteFormat(writer, "count={}", 42i64)?;
// Or into a string of its own, which is the one place rendering allocates.
var system = SystemAllocator();
let allocator: Allocator = system;
return match Render(allocator, "pi={:.2}", 3.14159f64) {
.Success(_) => 0,
.Failure(_) => 1
};
}
Both answer with a native fallible: WriteFormat with ! FormatError and Render with String ! FormatError. There is no status to forget to read — a result nobody handles is diagnosed as discarded — and no string handed back beside an error that says it is not really there. Render releases its partial text when it fails, so a caller never receives half a rendering and the allocator gets its block back either way.
Index
Types
| Name | Summary |
|---|---|
ByteCursor | A fixed buffer being filled from the front. |
FixedNat | An unsigned integer of fixed capacity, holding no storage of its own. |
FloatParts | A finite float taken apart: the value is significand * 2^exponent. |
ParseError | Why text could not be read as a value. |
DecimalReading | What a decimal spells, before any format decides what to do with it. |
FloatLayout | The shape of one binary interchange format: where its fields are and what they mean. |
WideClass | What kind of value a bit pattern holds. |
UnpackedWide | A finite value taken apart: significand times two to the exponent. |
Float80 | An x87 extended-precision value, held as its eighty bits. |
Float128 | An IEEE binary128 value, held as its bits. |
Float256 | An IEEE binary256 value, held as its bits. |
Float512 | An IEEE binary512 value, held as its bits. |
Functions
| Name | Summary |
|---|---|
IsWideScalarValue | Whether value, at whatever width it was read from, is a Unicode scalar value. |
WriteInvalidEscape | Writes the escape for a value that is not a character, preserving exactly what it was. |
IsScalarValue512 | Whether value, read from a character wider than sixty-four bits, is a Unicode scalar value. |
WriteWideInvalidEscape | Writes the escape for a value that is not a character, from a character wider than sixty-four bits. |
WriteWideCharacterDebug | Writes the Debug form of a character wider than sixty-four bits. |
WriteCharacterDebug | Writes one character's Debug form: quoted, and preserved exactly when it is not a character. |
WriteEscapedScalar | Writes one scalar as it appears inside a quoted rendering, escaping what would otherwise be ambiguous. |
MeasurePadding | Reports how much fill a spec asks for on each side of a rendering occupied characters wide. |
WriteDecimal | Provides the public WriteDecimal operation. |
WriteUint | Writes the decimal digits of an unsigned value. |
WriteInt | Writes the sign, then the magnitude. |
IsNan | Provides the public IsNan operation. |
IsInfinite | Provides the public IsInfinite operation. |
IsFinite | Provides the public IsFinite operation. |
BitsOfFloat64 | The bits of value, as they are stored. |
BitsOfFloat32 | The bits of value, as they are stored. |
BitsOfFloat16 | The bits of value, as they are stored. |
BitsOfFloat8 | The bits of value, as they are stored. |
DecomposeFloat64 | Takes a float64 apart into the significand and exponent its digits are generated from. |
DecomposeFloat32 | Takes a float32 apart into the significand and exponent its digits are generated from. |
DecomposeFloat16 | Takes a float16 apart into the significand and exponent its digits are generated from. |
DecomposeFloat8 | Takes a float8 apart into the significand and exponent its digits are generated from. |
WriteShortestFloat | Writes the shortest decimal text of a finite, non-zero float taken apart by DecomposeFloat64 or its narrow counterpart, choosing fixed or scientific notation the way the rest of this package does. |
WriteFormat | Writes format into writer, with each {} replaced by the next argument. |
Render | The text format becomes with its placeholders filled, as a string of its own. |
WriteIntegerDigits | Writes an integer whose digits are already produced, applying sign, prefix, zero-padding and alignment. |
WriteIntegerSpec | Writes an integer under the whole of spec: base, sign, alternate prefix, zero-padding and alignment. |
FormatScalar | Writes one character under spec's width, fill and alignment. |
Float64FromBits | The float64 whose bits are bits. |
Float32FromBits | The float32 whose bits are bits. |
ExactFloat64 | Reads the decimal digits * 10^exponent into the nearest float64, with ties to even. |
ExactFloat32 | Reads the decimal digits * 10^exponent into the nearest float32, with ties to even. |
ScanDecimal | Reads the decimal grammar out of str, without deciding what float it names. |
ParseFloat64 | Reads str as the float64 nearest what it spells, rounding to nearest with ties to even. |
ParseFloat32 | Reads str as the float32 nearest what it spells, rounding to nearest with ties to even. |
ParseUint8 | Parses a uint8: an optional +, an optional base prefix, and digits. |
ParseInt8 | Parses an int8: an optional sign, an optional base prefix, and digits. |
ParseUint16 | Parses a uint16: an optional +, an optional base prefix, and digits. |
ParseInt16 | Parses an int16: an optional sign, an optional base prefix, and digits. |
ParseUint32 | Parses a uint32: an optional +, an optional base prefix, and digits. |
ParseInt32 | Parses an int32: an optional sign, an optional base prefix, and digits. |
ParseUint64 | Parses a uint64: an optional +, an optional base prefix, and digits. |
ParseInt64 | Parses an int64: an optional sign, an optional base prefix, and digits. |
ParseUint128 | Parses a uint128: an optional +, an optional base prefix, and digits. |
ParseInt128 | Parses an int128: an optional sign, an optional base prefix, and digits. |
ParseUint256 | Parses a uint256: an optional +, an optional base prefix, and digits. |
ParseInt256 | Parses an int256: an optional sign, an optional base prefix, and digits. |
ParseUint512 | Parses a uint512: an optional +, an optional base prefix, and digits. |
ParseInt512 | Parses an int512: an optional sign, an optional base prefix, and digits. |
ParseBool | Parses true or false, exactly. |
ParseChar | Parses exactly one character. |
PrecisionDigits | Writes the digits of significand * 2^exponent down to the place precision names, rounded to nearest with ties to even. |
WritePrecisionFloat | Writes a finite float in fixed notation with exactly precision digits after the decimal point. |
WritePrecision | Renders parts under a specification that carries a precision, and applies the specification's sign, width, fill and alignment to the result. |
WriteScientificNotation | Renders a finite float in scientific notation under spec, and applies the specification's sign, width, fill and alignment to the result. |
ShortestDigits | Writes the shortest decimal digits of significand * 2^exponent that read back to exactly that value. |
UnpackWide | Takes a bit pattern apart under layout. |
PackWide | Puts a finite value back together under layout, which must already be within range and precision. |
InfinityBits | The bits of infinity under layout. |
NanBits | The bits of a quiet NaN under layout. |
ParseWideFloat | Reads str as a value under layout, rounding to nearest with ties to even, and reports the bits. |
ParseFloat80 | Reads str as an x87 extended value. |
ParseFloat128 | Reads str as a binary128 value. |
ParseFloat256 | Reads str as a binary256 value. |
ParseFloat512 | Reads str as a binary512 value. |
WriteWideFloat | Writes bits, read under layout, as text under spec. |
Constants
| Name | Summary |
|---|---|
ReplacementCharacter | The character written in place of anything that is not one. |
MaximumScalarValue | The largest value a Unicode scalar can have, past which nothing is a character. |
MaxDecimalDigits | The most digits a uint64 ever needs, for 18446744073709551615. |
FixedNatLimbs | How many sixty-four-bit limbs a FixedNat holds. |
FixedNatBits | How many bits a FixedNat holds in total. |
RetainedDecimalDigits | How many significant digits a narrow float reader keeps before the rest becomes a sticky bit. |
MaximumPrecision | The largest precision a narrow float renders at. |
MaximumFixedLength | The longest a fixed rendering can be: sign, integer digits, point and fraction. |
MaximumShortestDigits | The most digits a shortest rendering can need. |