FREAKV3 docs freak 0.14.2 (Maverick)

Words & interpolation#

> word is the string type. Its methods are compiler builtins, and {path} interpolation has precise rules.

Literals and escapes#

A word literal is double-quoted.

EscapeMeaning
\nnewline
\rcarriage return
\ttab
\"double quote
\\backslash
\xNNbyte with hex value NN

\x1b is the ESC byte, which is how you produce coloured console output — see Coloured output.

Careful

\x00 is rejected at lex time: "embedded NUL escape is not supported". V3 words use a NUL-terminated runtime representation, so a word can never contain a zero byte. A malformed hex escape (fewer than two hex digits) is also a lex error.

An unterminated literal reports "unterminated string literal" pointing at the opening quote.

Methods#

All of these are compiler builtins — no import, and they work under freak check as well as freak build.

examples/strings.fk compilesruns
-- `word` is FREAK's string type. These methods are compiler builtins,
-- available with no import.
task main() -> void {
    pilot s: word = "  Muv-Luv Alternative  "

    say s.trim()
    say word_from_int(s.length())
    say s.trim().to_upper()
    say s.trim().to_lower()
    say word_from_bool(s.contains("Luv"))
    say word_from_bool(s.trim().starts_with("Muv"))
    say word_from_bool(s.trim().ends_with("ive"))
    say s.trim().replace("Luv", "Love")
    say s.trim().substring(0, 3)
    say s.trim().char_at(0)

    -- Concatenation with `+`, or with the builtin.
    say "Muv" + "-" + "Luv"
    say word_concat("XM", "3")

    -- Numeric conversion in both directions.
    say word_from_int("108".to_int())
    say format_num("2.5".to_num())
    say char_to_word(70)
}
Program output
Muv-Luv Alternative
23
MUV-LUV ALTERNATIVE
muv-luv alternative
true
true
true
Muv-Love Alternative
Muv
M
Muv-Luv
XM3
108
2.5
F
MethodSignatureNotes
.length()-> intCharacter count
.trim()-> wordStrips leading and trailing whitespace
.to_upper()-> word
.to_lower()-> word
.contains(needle)word -> bool
.starts_with(prefix)word -> bool
.ends_with(suffix)word -> bool
.replace(old, new)word, word -> wordReplaces every occurrence
.substring(start, len)int, int -> wordStart index and length, not an end index
.char_at(i)int -> wordA one-character word, not a char type
.to_int()-> int
.to_num()-> num
.checksum()-> intRuntime hash
.parse_int()-> intChecked; sets the parse status on failure
.parse_num()-> numChecked; sets the parse status on failure
.repeated(n)int -> wordThe word repeated n times
Note

.substring(start, len) takes a length as its second argument. The bible describes slice(from, to); V3's builtin is not that function. "abcdef".substring(1, 3) yields "bcd".

Snapshot helpers#

Seven further word methods exist for the compiler's own line-and-field serialisation format. They are part of the builtin table and will type-check in your code, but they are internal plumbing rather than a general-purpose API:

.snapshot_escape(), .snapshot_unescape(), .snapshot_line_count(), .snapshot_line(i), .snapshot_lines(), .snapshot_field_count(), .snapshot_field_raw(i).

Checked parsing#

.to_int() and .to_num() convert silently — a word that is not a number yields 0, indistinguishable from parsing "0". As of v0.14.2 there is a checked pair that reports failure out of band:

FREAK
say word_from_int("42".parse_int())      -- 42
say word_from_int(parse_status())        -- 0, clean

say word_from_int("nope".parse_int())    -- 0
say word_from_int(parse_status())        -- 1, failed
parse_clear_status()
CallSignatureMeaning
w.parse_int()-> intParse, 0 on failure
w.parse_num()-> numParse, 0.0 on failure
parse_status()-> int0 clean, non-zero if a parse failed
parse_clear_status()-> voidReset it

The status is global and sticky — it stays set until you clear it — so check it immediately after the parse you care about, exactly as with ByteBuffer.status(). This is V3's substitute for maybe<int>, which does not exist.

std/convert.fk also offers word_to_int_safe(s), which returns 0 on failure without touching any status.

Concatenation and conversion#

FREAK
say "Muv" + "-" + "Luv"      -- operator
say word_concat("XM", "3")   -- builtin, identical

+ on two words concatenates, and += appends in place as of v0.14.2. + on a word and a number is a type error — convert first:

FREAK
pilot n: int = 42
say "answer: " + word_from_int(n)
say "answer: {n}"              -- or interpolate
BuiltinSignature
word_from_int(i)int -> word
word_from_bool(b)bool -> word
word_to_int(w)word -> int
word_concat(a, b)word, word -> word
word_join(handle)int -> word
char_to_word(code)int -> word
format_num(f)num -> word
parse_num(w)word -> num

format_num prints a num without a trailing .0 when it is integral — format_num(12.0) gives 12.

Indexing#

word[i] yields a one-character word. It is the only indexable type in V3.

FREAK
pilot s: word = "abc"
say s[1]           -- b
say s.char_at(1)   -- b, identical

Interpolation#

{path} inside a double-quoted word substitutes a binding's value.

examples/interpolation.fk compilesruns
-- `{path}` inside a double-quoted word interpolates a binding.
-- A path is an identifier plus zero or more `.field` hops, and must
-- resolve to word, int, num or bool. Anything that is not a valid path
-- stays literal text.
-- (`Aviator`, not `Pilot`: V3 keywords are case-insensitive, so `Pilot`
-- is the `pilot` keyword and cannot name a shape.)
shape Aviator {
    name: word
    score: int
}

impl Aviator {
    task summary(self) -> word {
        give back "{self.name} scored {self.score}"
    }
}

task main() -> void {
    pilot callsign: word = "Valkyrie"
    pilot sorties: int = 12
    pilot rate: num = 0.75
    pilot active: bool = true

    say "callsign={callsign} sorties={sorties}"
    say "rate={rate} active={active}"

    pilot p: Aviator = Aviator { name: "Meiya", score: 98 }
    say "shape: {p.name} / {p.score}"
    say p.summary()

    -- Not a path, so it is printed literally:
    say "literal {1 + 2} braces"
}
Program output
callsign=Valkyrie sorties=12
rate=0.75 active=true
shape: Meiya / 98
Meiya scored 98
literal {1 + 2} braces

The rules are exact:

  • A path is an identifier followed by zero or more .field hops: name, p.score, self.engine.thrust.
  • The path must resolve to word, int, num or bool. Any other type is a type error.
  • Every segment must be a valid identifier — so it cannot be a keyword, in any casing.
  • A matched {...} whose body is not a valid path is left as literal text, braces included. "literal {1 + 2} braces" prints exactly that.
  • An unmatched { is literal text.
  • Unknown bindings are a compile error: "unknown interpolation binding 'x'".
Careful

Interpolation substitutes paths only — never calls, never expressions. "{p.sum()}" is not a call; it is literal text, because sum() is not a valid path segment. Compute into a binding first: ``fk pilot total: int = p.sum() say "sum={total}"

Dotted shape interpolation, including {self.field} inside a method, is verified working on the LLVM backend. It is not a claimed executable path on the C backend.

Printing without a newline#

say always appends a newline. To put several pieces on one line, build the word first and say it once — or use carriage-return and cursor-movement escapes to redraw. All three techniques are in Printing on the same line.

Building words incrementally#

Repeated + concatenation copies the whole accumulated word each time. word_builder::* avoids that. The builder is an int handle, not a shape.

examples/word_builder.fk compilesruns
-- `word_builder::*` accumulates a word without the quadratic cost of
-- repeated `+` concatenation. The builder is an `int` handle.
task main() -> void {
    pilot b: int = word_builder::new()

    word_builder::append(b, "Mission: ")
    word_builder::append(b, "Valkyries")
    word_builder::append_char(b, 32)      -- a space, by codepoint
    word_builder::append_int(b, 4)

    say word_from_int(word_builder::length(b))

    -- `finish` consumes the builder and returns the accumulated word.
    say word_builder::finish(b)

    -- with_capacity pre-allocates; clear reuses; discard frees unused.
    pilot c: int = word_builder::with_capacity(64)
    word_builder::append(c, "scratch")
    word_builder::clear(c)
    word_builder::append(c, "reused")
    say word_builder::finish(c)
}
Program output
20
Mission: Valkyries 4
reused
CallSignatureNotes
word_builder::new()-> int
word_builder::with_capacity(n)int -> intPre-allocate
word_builder::reserve(b, n)int, int -> voidGrow the capacity
word_builder::append(b, w)int, word -> void
word_builder::append_char(b, code)int, int -> voidBy codepoint
word_builder::append_int(b, n)int, int -> void
word_builder::length(b)int -> int
word_builder::capacity(b)int -> int
word_builder::clear(b)int -> voidKeep the buffer, drop the content
word_builder::finish(b)int -> wordConsumes the builder
word_builder::discard(b)int -> voidFree without producing a word
Note

finish consumes the builder — do not reuse the handle afterwards. Use clear to keep building with the same allocation, and discard when you are abandoning it without producing a word.

This is V3's answer to the bible's WordBuilder from §7.2. The API is namespaced calls over a handle rather than methods on an object.

Standard-library word tasks#

std/string.fk is ordinary FREAK source linked in by freak build and freak run. Its tasks are global and take the word as a normal argument.

examples/stdlib_string.fk compilesruns
-- std/string.fk ships as plain FREAK source and is always linked in by
-- `freak build` / `freak run`. Its tasks are global; there is no import.
task main() -> void {
    say string_repeat("ha", 3)
    say string_reverse("FREAK")
    say word_from_int(string_count("banana", "an"))
    say string_pad_left("7", 4, "0")
    say string_pad_right("7", 4, ".")
    say word_from_int(string_index_of("alternative", "native"))
    say word_from_bool(is_digit("5"))
    say word_from_bool(is_alpha("x"))
    say word_from_bool(is_whitespace(" "))
}
Program output
hahaha
KAERF
2
0007
7...
5
true
true
true
TaskSignature
string_repeat(s, count)word, int -> word
string_reverse(s)word -> word
string_count(haystack, needle)word, word -> int
string_index_of(s, needle)word, word -> int, -1 when absent
string_pad_left(s, width, pad)word, int, word -> word
string_pad_right(s, width, pad)word, int, word -> word
string_split(s, delim)word, word -> word (an encoded word, not an array)
string_join(parts, sep)word, word -> word
string_trim(s)word -> word
string_substring(s, start, end)word, int, int -> word — an end index, unlike the builtin
string_replace(s, old, new)word, word, word -> word
string_contains / string_starts_with / string_ends_with-> bool
is_digit(c) / is_alpha(c) / is_whitespace(c)word -> bool
Note

These duplicate several builtin methods. The builtins are cheaper and work under freak check; the std/ tasks exist because the standard library is itself written in FREAK. Where the two disagree — substring — prefer the builtin and mind the length-vs-end-index difference.

Splitting into an array#

string_split returns an encoded word, not an array handle. To get a real array, walk the characters yourself:

examples/wordcount.fk compilesruns
-- A complete small program: split a sentence into words, count them,
-- and report the longest one. Uses shapes, arrays, loops and std tasks.
shape Report {
    total: int
    longest: word
}

impl Report {
    task show(self) -> word {
        give back "total={self.total} longest={self.longest}"
    }
}

task split_words(text: word, out: int) -> void {
    pilot mut current: word = ""
    pilot mut i: int = 0

    repeat text.length() times {
        pilot c: word = text.char_at(i)
        if c == " " {
            if current.length() > 0 {
                array_push(out, current)
                current = ""
            }
        } else {
            current = current + c
        }
        i += 1
    }

    if current.length() > 0 {
        array_push(out, current)
    }
}

task analyse(text: word) -> Report {
    pilot words: int = array_new()
    split_words(text, words)

    pilot mut longest: word = ""
    pilot mut i: int = 0

    repeat array_len(words) times {
        pilot w: word = array_get(words, i)
        if w.length() > longest.length() {
            longest = w
        }
        i += 1
    }

    -- `result` is a reserved word in V3, so the binding is `summary`.
    pilot summary: Report = Report { total: array_len(words), longest: longest }
    array_release(words)
    give back summary
}

task main() -> void {
    pilot r: Report = analyse("the beta will not negotiate with humanity")
    say r.show()
    say word_from_int(r.total)
    say r.longest.to_upper()
}
Program output
total=7 longest=negotiate
7
NEGOTIATE

Every FREAK snippet on this site was compiled by freak 0.14.2 (Maverick), built from source with a verified self-host fixed point. 45/45 examples compile; regenerate with python tools/verify.py then python tools/build_docs.py.