Section 9.3

From text to number, and back

toInt raises a ParseError when the text is not a number, toText returns a str you can still work on.

A text that looks like a number is not a number: "3" * 2 means nothing, and the compiler says so. The crossing is asked for, in either direction — and one of the two can fail.

import { StrText } from "std/extensions/str-text.arc";
import { IntText } from "std/extensions/int-text.arc";

extend archetype str StrText;
extend archetype int IntText;

@entry(start)
object MyProgram {
    start() {
        var measure := "3";
        var doses := measure.toInt();

        Console.print(text: $"twice as much: {doses * 2}");
        Console.print(text: $"in the ledger: [{doses.toText().alignedRight(width: 4)}]");

        try {
            var wrong := "three".toInt();
            Console.print(text: $"never reached: {wrong}");
        } catch (raised: ParseError) {
            Console.print(text: $"refused: {raised.message}");
        }
    }
}

Two files, two extends

import { StrText } from "std/extensions/str-text.arc";
import { IntText } from "std/extensions/int-text.arc";

extend archetype str StrText;
extend archetype int IntText;

toInt is a method of str, toText a method of int: two different types, so two bundles, so two lines each. The headers grow longer, and that is the price of a tiny core: what the program uses, it names.

toInt can fail, so it raises

"three".toInt() does not return zero, does not return some default: it raises a ParseError.

refused: 'three' is not a valid integer

It is the reasoning of 8.2 applied once more: a text explicitly asked to be a number, and which is not one, is a genuine error — not an absence to be marked with a zero, since zero would be a perfectly plausible number and nothing would tell it apart from a successful "0".

Hence the try / catch around the call, written exactly as in 8.1, with an error archetype supplied by the language.

toText, when interpolation is no longer enough

Interpolation already converts: $"{doses}" prints 3 without being asked. So why toText?

Because interpolation returns a finished text, and toText a str you can still work on:

doses.toText().alignedRight(width: 4)

alignedRight pads on the left up to the requested width — enough to line numbers up in a column. It comes from StrText, like everything else in 9.1: once toText has returned a str, that text can do everything a str can do.

What interpolation does not convert

Four types go into a $"…" hole: int and its widths, float, bool and str. An archetype of yours is not among them, and the refusal is explicit:

error[T032]: an interpolated hole of type 'Potion' has no text conversion: only 'int' and its
widths, 'float', 'bool' and 'str' convert -- give the value a 'str' first

“Give the value a str first”: a method of yours, on your archetype, returning the text you want to see. The language does not guess what a written potion looks like.

The rest of the library, in the same place

Same mechanism, other files:

  • toFloat() on a str — StrReal, in std/extensions/str-real.arc;
  • squareRoot() and floored() on a float64 — FloatMath, in std/extensions/float-math.arc;
  • toText() for a float64 or a bool — FloatText and BoolText, each in its own.

Over to you

  • Give "3 doses" to toInt: the text is not a whole number, so ParseError. It has to be cut first — 9.2 showed how.
  • Remove the try / catch and let the error through: the program stops and names it, as in 8.2.
  • Set measure to "1234" keeping width: 4, then width: 2: a text longer than the requested width is not clipped, it comes out as it is.
  • Catch the ParseError and return 0 instead: that is your decision to treat the absence that way, taken knowingly, and not the language’s taken for you.