Pilots, values and types#
How to name a value, what kinds of value there are, and the strict rules that keep them from being confused with one another.
Chapter 2 used pilots, numbers, words and truth values without stopping to explain them. This chapter goes back over each one. It covers:
- declaring pilots, and keeping one from changing with
fixed - where a pilot can be seen, and for how long
- the words you cannot use as names
- the four basic types:
int,num,boolandword - why FREAK never converts between types unless you ask
Pilots#
A pilot is a name for a value. You create one with pilot, a name, an equals sign, and the value it starts with.
-- Pilots: named values.
task main() {
pilot callsign = "Valkyrie-1" -- the type is inferred: word
pilot sorties: int = 12 -- or written out after a colon
pilot fuel = 87.5 -- num
pilot cleared = true -- bool
say callsign
say sorties
say fuel
say cleared
-- A pilot you intend to change is declared with `mut`.
pilot mut altitude = 0
altitude = 3000
altitude += 500
say altitude
}
Valkyrie-1
12
87.5
true
3500
Every pilot has a type, which says what kind of value it holds. The first four pilots here hold a word, an int, a num and a bool.
You can let the compiler work the type out from the starting value, as callsign, fuel and cleared do. Or you can write it after the name with a colon, as sorties: int does. The two forms mean the same thing when they agree. Writing the type is useful as a note to the reader, and as a check: if the value is not of the type you wrote, the program does not build.
task main() {
pilot sorties: int = "twelve"
say sorties
}
type error: cannot initialize int binding 'sorties' with word (line 2)
--> book_wrong_type.fk:2:1
2 | pilot sorties: int = "twelve"
| ^
A pilot must be given a value when it is created. There is no way to declare one now and fill it in later:
task main() {
pilot fuel: int
fuel = 50
say fuel
}
error: expected '=', found 'fuel'
--> book_no_initial_value.fk:3:5
|
3 | fuel = 50
| ^
error: unexpected '=' — this token cannot start an expression
--> book_no_initial_value.fk:3:10
|
3 | fuel = 50
| ^
The message is indirect. After pilot fuel: int the compiler expected = and a value. It found the next line instead, and reports that.
A pilot keeps its type#
Once a pilot holds an int, it holds ints for as long as it exists. You can give it a different int. You cannot give it a word.
task main() {
pilot mut sorties = 12
sorties = "thirteen"
say sorties
}
type error: cannot assign word to int (line 3)
--> book_assign_wrong_type.fk:3:1
3 | sorties = "thirteen"
| ^
Names#
A name is made of letters, digits and underscores, and does not start with a digit. Only the unaccented letters a to z and A to Z are allowed.
FREAK code follows two conventions. Ordinary pilots and tasks are written in lowercase with underscores between words: secret_number, sorties_flown. Values that are fixed for the whole program are written in capitals: MAX_SORTIES.
Changing a pilot#
A pilot can be changed after it is created, unless you say otherwise.
| Declaration | Meaning |
|---|---|
pilot x = 1 | A pilot you may change later |
pilot mut x = 1 | The same; mut is optional |
fixed pilot x = 1 | A pilot whose value must never change |
grounded pilot x = 1 | The same as fixed: a grounded pilot isn't going anywhere |
Assignment uses =. For arithmetic on the pilot's own value there are shorthand forms: x += 5 means x = x + 5, and -=, *=, /= and %= work the same way.
fixed and grounded are enforced for every kind of value: numbers, words, lists, and the fields of a shape. Assigning to one stops the build, and the message uses the word you wrote:
task main() {
fixed pilot fuel = 50
fuel = 75
say fuel
}
type error: fixed pilot cannot be modified (line 3)
--> book_fixed_reassign.fk:3:1
3 | fuel = 75
| ^
mut is accepted for readers coming from languages where values cannot be changed by default. It changes nothing, so use it or leave it out as you prefer. Mark the values that must not change, and let everything else be a plain pilot.
Scope#
A pilot exists from the line that declares it to the end of the block it was declared in. A block is whatever sits between a pair of braces: the body of a task, of an if, of a loop.
-- A pilot lives from its declaration to the end of the block it is in.
task main() {
pilot squadron = "Valkyries"
if true {
pilot wing = "A-01" -- exists only inside these braces
say "{squadron} / {wing}"
pilot squadron = "Isumi" -- a new pilot that hides the outer one
say squadron
}
say squadron -- the outer pilot was never changed
}
Valkyries / A-01
Isumi
Valkyries
wing is created inside the if block and ends with it. Using it afterwards is an error, and the compiler treats it like any other name it has never heard of:
task main() {
if true {
pilot wing = "A-01"
}
say wing
}
type error: unknown binding 'wing' (line 5)
--> book_out_of_scope.fk:5:1
5 | say wing
| ^
Shadowing#
The example also declares a second squadron inside the block. This is allowed. The inner pilot is a new one that happens to have the same name, and while it exists it hides the outer one. When the block ends, the inner pilot is gone and the name means the outer pilot again, whose value was never touched.
That only works across blocks. Declaring the same name twice in the same block is an error:
task main() {
pilot fuel = 50
pilot fuel = 75
say fuel
}
type error: duplicate binding 'fuel' in the same scope (line 3)
--> book_duplicate_pilot.fk:3:1
3 | pilot fuel = 75
| ^
If you meant to change the existing pilot, drop the second pilot and assign.
Pilots outside tasks#
A pilot can be declared at the top level of a file, outside every task. It is then visible in all of them.
-- Pilots declared outside every task are visible to all of them.
fixed pilot MAX_SORTIES: int = 3
fixed pilot SQUADRON: word = "Valkyries"
task briefing() {
say "{SQUADRON}: at most {MAX_SORTIES} sorties today"
}
task main() {
briefing()
say MAX_SORTIES * 2
}
Valkyries: at most 3 sorties today
6
This is the right place for values the whole program agrees on: limits, names, settings. Declare them fixed pilot and name them in capitals.
A top-level pilot can also be changed by the tasks that see it:
-- A top-level pilot is shared by every task in the file.
pilot mut sorties_flown = 0
task scramble(callsign: word) {
sorties_flown += 1
say "{callsign} away (sortie {sorties_flown})"
}
task main() {
scramble("Valkyrie-1")
scramble("Valkyrie-2")
say sorties_flown
}
Valkyrie-1 away (sortie 1)
Valkyrie-2 away (sortie 2)
2
Use that sparingly. A value that any task may change is hard to reason about, because to know what it holds you have to know everything that ran before. Passing values into tasks and giving results back, the subject of Chapter 4, keeps programs easier to follow.
Reserved words#
Some words belong to the language and cannot be used as names. In most languages that list is short and obvious. In FREAK it is longer than you would guess, because it includes words the language has reserved for features that are not built yet.
These 55 words are reserved in V3:
pilot fixed task say shape impl doctrine
launch use as in lend mut move
copy break continue if else when repeat
times until done for each check result
got nobody some ok err sessions max
foreshadow payoff route sadly deus_ex_machina isekai
eventually and or not nakama tsundere extern
while
true false yes no hai iieSeveral of them are words you will reach for naturally: max, result, ok, check, done, times, in, use, copy, move, route, launch. This book's own examples ran into it. A task that starts a sortie wanted to be called launch:
task launch(callsign: word) {
say "{callsign} away"
}
task main() {
launch("Valkyrie-1")
}
error: expected an identifier for task name, found 'launch'
--> book_reserved_task_name.fk:1:6
|
1 | task launch(callsign: word) {
| ^ use an identifier here
error: unexpected 'launch' — this token cannot start an expression
--> book_reserved_task_name.fk:6:5
|
6 | launch("Valkyrie-1")
| ^
When you see expected an identifier ... found followed by a word you thought was a perfectly good name, this is why. Pick another: limit for max, outcome for result, ready for ok, scramble for launch.
Words that are not reserved#
In one respect the list is shorter than you might guess. Several words the language uses constantly are missing from it: give, back, training, arc, ask, grounded, step, with, and the type names int, num, bool and word. V3 recognises those by where they appear, so it accepts them as names too:
-- These words are used by the language and are not reserved.
-- The program builds. It is here as a warning, not as a model.
task twice(int: int) -> int {
pilot give = int * 2
give back give
}
task main() {
pilot mut training = 0
pilot arc = 3
training arc until training >= arc max 10 sessions {
training += 1
}
pilot ask = twice(training)
say "training {training}, ask {ask}"
}
training 3, ask 6
That listing builds, and give back give is reason enough never to write it. give back is a keyword only as a pair on one line, so a pilot called give at the end of one line and a pilot called back at the start of the next stay two pilots. Even so, treat every word the language uses as reserved, whether or not the compiler insists.
Capital letters do not help#
V3 reserves these words in every combination of capital and small letters. Max, MAX and max are all refused as names:
task main() {
pilot Max = 9000
say Max
}
error: expected an identifier for binding name, found 'Max'
--> book_keyword_name.fk:2:11
|
2 | pilot Max = 9000
| ^ use an identifier here
error: unexpected 'Max' — this token cannot start an expression
--> book_keyword_name.fk:3:9
|
3 | say Max
| ^
The reservation covers every spelling, but a single-word keyword works only in lowercase. Pilot x = 1 is not a declaration and If does not start a condition; both are syntax errors. The truth literals are the exception: a capitalised one is accepted and means the same value as the lowercase one.
-- Single-word keywords work only in lowercase. The truth literals are the
-- exception: a capitalised spelling is accepted and means the same value.
task main() {
pilot lowercase = true
pilot uppercase = TRUE
say lowercase
say uppercase
}
true
true
The two-word keywords are treated differently. V3 accepts give back, for each and training arc in any letter case:
-- V3 accepts its two-word keywords in any letter case.
-- Write them in lowercase anyway.
task double(n: int) -> int {
Give Back n * 2
}
task main() {
pilot mut total = 0
For Each reading in [1, 2, 3] {
total += reading
}
Training Arc until total >= 10 max 5 sessions {
total += 2
}
say double(total)
}
20
Do not rely on that. Write every keyword in lowercase and none of this touches you.
The basic types#
V3 has four types of simple value.
| Type | Holds | Example values |
|---|---|---|
int | A whole number | 0, 42, -7 |
num | A number with a fractional part | 2.5, 0.75, -3.0 |
bool | A truth value | true, false |
word | Text | "Valkyrie-1", "" |
There is a fifth name, void, for a task that gives nothing back. It is not a type of value: you cannot have a void pilot. Chapter 4 covers it.
Beyond these, V3 has lists, an older kind of array, a byte buffer, and the shapes you define yourself. Those are Part II.
int#
An int is a whole number stored in 64 bits, with a sign. It ranges from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. A number written without a decimal point is an int.
-- int: a 64-bit signed whole number.
task main() {
say 7 + 2
say 7 - 2
say 7 * 2
say 7 / 2 -- division between ints drops the fraction
say 7 % 2 -- and % gives the remainder
say -7 / 2 -- rounding is toward zero, not downward
say -7 % 2 -- so the remainder takes the sign of the left side
say 1 + 2 * 3 -- * / % bind tighter than + -
say (1 + 2) * 3
pilot largest: int = 9223372036854775807
say largest
say largest + 1 -- past the largest value, the program stops here
say "never printed"
}
9
5
14
3
1
-3
-1
7
9
9223372036854775807
Four things in that listing are worth stating as rules.
Dividing two ints gives an int. The fractional part is dropped, so 7 / 2 is 3. The result is rounded toward zero, which matters for negative numbers: -7 / 2 is -3, where rounding downward would give -4.
**% gives the remainder** of that division, and takes the sign of the left side: -7 % 2 is -1.
Multiplication, division and remainder are done before addition and subtraction. Parentheses change the order.
Arithmetic that goes past the largest or smallest int stops the program. The last line of the listing never runs. The program prints FREAK: integer overflow in addition to the error stream and ends with exit code 1. A number literal too large to fit is refused when you build.
Dividing an int by zero stops the program too, with FREAK: integer division by zero, and so does taking a remainder by zero. If a divisor could be zero, test it before you divide.
Ints are written in decimal only. There is no hexadecimal form, no separator for grouping digits, and no exponent. The compiler does not say so directly. It reads 0xFF as the number 0 followed by a name, and reports unknown binding 'xFF'. If you see an unknown binding that looks like the tail of a number, this is the cause.
num#
A num is a floating-point number, stored in 64 bits. A number written with a decimal point is a num. At least one digit must come before the point: 0.5, never .5.
-- num: a 64-bit floating-point number.
task main() {
pilot thrust = 2.5
say thrust * 2.0 -- 5, printed without a trailing .0
say 10.0 / 4.0
say 1.0 / 3.0 -- at most ten significant digits are printed
say 0.1 + 0.2
say 0.1 + 0.2 == 0.3 -- false: the stored values differ beyond the digits shown
-- An int and a num can be mixed; the result is a num.
say 7 / 2
say 7 / 2.0
say 3 * 1.5
say 123456789012.5 -- large and small values switch to scientific notation
say 0.000001234
}
5
2.5
0.3333333333
0.3
false
3
3.5
4.5
1.23456789e+11
1.234e-06
say prints a num with up to ten significant digits, and leaves off a fractional part that is zero. So 5.0 prints as 5, and one third prints as 0.3333333333. Very large and very small values are printed in scientific notation: 1.23456789e+11 is 1.23456789 times ten to the eleventh.
Ten digits is what is printed. The value itself is kept more precisely than that, and this is why 0.1 + 0.2 prints as 0.3 and still is not equal to 0.3. Neither 0.1 nor 0.2 can be stored exactly in binary, and their sum differs from the stored 0.3 somewhere past the sixteenth digit. This is true of floating-point numbers in every language. The rule that follows from it: do not test nums for exact equality. Test whether they are close enough.
When an operation has an int on one side and a num on the other, the int is treated as a num and the result is a num. That is why 7 / 2.0 is 3.5.
The remainder operator is for ints only:
task main() {
say 7.5 % 2.0
}
type error: operator '%' does not accept num and num (line 2)
--> book_remainder_num.fk:2:1
2 | say 7.5 % 2.0
| ^
bool#
A bool is one of two values, true or false. Comparisons produce bools, and if and the loops consume them.
-- bool: true or false.
task announce(tag: word, value: bool) -> bool {
say "checking {tag}"
give back value
}
task main() {
pilot fueled = true
pilot armed = false
say fueled and armed
say fueled or armed
say not armed
say 3 < 5
say "A-01" == "A-01"
-- `not` binds tightest, then `and`, then `or`.
say true or false and false -- true or (false and false)
say not false and false -- (not false) and false
-- Three pairs of literals mean the same two values.
say yes
say hai
say iie
-- `and` and `or` stop as soon as the answer is known.
if announce("fuel", false) and announce("weapons", true) {
say "launch"
}
if announce("radar", true) or announce("visual", true) {
say "contact"
}
}
false
true
true
true
true
true
false
true
true
false
checking fuel
checking radar
contact
The comparison operators are == (equal), != (not equal), <, >, <= and >=. Bools combine with three operators written as words:
| Operator | True when |
|---|---|
a and b | both are true |
a or b | at least one is true |
not a | a is false |
not is applied first, then and, then or. When in doubt, use parentheses; the reader will thank you even where the compiler did not need them.
and and or evaluate their right side only when they have to. In the listing, announce("fuel", false) and ... already knows the answer is false after its left side, so the right side never runs and checking weapons is never printed. This is called short-circuit evaluation, and you can rely on it.
FREAK has three spellings for each truth value: true, yes and hai all mean true, and false, no and iie all mean false. They are the same two values. say prints true or false whichever you wrote.
word#
A word is a piece of text of any length, written in double quotes. It is what other languages call a string.
-- word: a piece of text.
task main() {
pilot title = "Muv" + "-" + "Luv" -- + joins two words
say title
say title.length() -- 7: one for each of these letters
pilot mut system = "XM"
system += "3" -- += appends
say system
say title == "Muv-Luv" -- words compare with == and !=
say "apple" < "banana" -- and are ordered byte by byte
-- A name in braces is replaced by its value. It works for
-- word, int, num and bool pilots.
pilot sorties = 12
pilot ratio = 0.75
pilot cleared = true
say "{title}: {sorties} sorties, ratio {ratio}, cleared {cleared}"
-- A backslash before a brace prints the brace itself.
say "\{sorties\} is printed as written"
-- A backslash introduces a character that is awkward to type.
say "tab:[\t] quote:[\"] backslash:[\\]"
}
Muv-Luv
7
XM3
true
true
Muv-Luv: 12 sorties, ratio 0.75, cleared true
{sorties} is printed as written
tab:[ ] quote:["] backslash:[\]
Only a name, or a name followed by .field, can go between the braces. A calculation cannot, and the program does not build:
task main() {
pilot sorties = 12
say "{sorties + 1} sorties"
}
error: unsupported interpolation expression
--> book_interpolation_expression.fk:3:9
|
3 | say "{sorties + 1} sorties"
| ^ use a binding or field path, or escape literal braces
Work the value out into a pilot first, and put the pilot's name in the braces.
That is enough about words to read the next two chapters. Words have a chapter of their own in Part II, which covers their methods, the exact rules of interpolation, and how to take them apart.
Types do not mix by themselves#
FREAK will not guess what you mean when two types meet. Joining a word and a number is an error:
task main() {
pilot sorties = 12
say "sorties: " + sorties
}
type error: operator '+' does not accept word and int (line 3)
--> book_word_plus_int.fk:3:1
3 | say "sorties: " + sorties
| ^
So is comparing them:
task main() {
pilot guess = "42"
pilot secret = 42
if guess == secret {
say "You got it!"
}
}
type error: operator '==' does not accept word and int (line 4)
--> book_compare_int_word.fk:4:1
4 | if guess == secret {
| ^
Read the messages closely, because you will see this form often. operator '+' does not accept word and int names the operator and the types it found on its left and right. The fix is always the same: convert one side so that both are the same type.
A num cannot be stored in an int pilot either, because that would lose the fractional part without your asking:
task main() {
pilot sorties: int = 2.5
say sorties
}
type error: cannot initialize int binding 'sorties' with num (line 2)
--> book_num_into_int.fk:2:1
2 | pilot sorties: int = 2.5
| ^
There is exactly one conversion FREAK performs for you. Where a num is expected, an int is accepted and converted, since no information is lost. You saw it in 7 / 2.0, and it also applies when you give an int to a pilot or a task parameter declared as num.
Converting between types#
Every other conversion is written out as a method call on the value.
-- Moving a value from one type to another is always written out.
task main() {
pilot sorties: int = 12
pilot average: num = 3.75
say sorties.to_num() / 5.0 -- int -> num
say average.to_int() -- num -> int, dropping the fraction
say "sorties: " + sorties.to_word()
say "average: " + average.to_word()
say "ready: " + true.to_word()
-- The one conversion that happens by itself: an int where a num is wanted.
pilot thrust: num = 3
say thrust / 2
say "42".to_int() + 1 -- word -> int
say "2.5".to_num() * 2.0 -- word -> num
say "forty-two".to_int() -- not a number: quietly 0
}
2.4
3
sorties: 12
average: 3.75
ready: true
1.5
43
5
0
| From | To | Write | Notes |
|---|---|---|---|
int | num | n.to_num() | Also happens by itself where a num is expected |
int | word | n.to_word() | |
num | int | x.to_int() | Drops the fraction: 3.75 becomes 3, -3.75 becomes -3 |
num | word | x.to_word() | Formatted the way say prints it |
bool | word | b.to_word() | "true" or "false" |
word | int | w.parse_int() | Checked; see Chapter 2 |
word | num | w.parse_num() | Checked the same way |
word | int | w.to_int() | Unchecked: reads the digits at the start of the word, and gives 0 if there are none |
word | num | w.to_num() | Unchecked in the same way |
In practice you need the word conversions less often than this table suggests. say prints ints, nums and bools directly, and interpolation puts any of them inside a word. Reach for to_word() when you are building a word with +.
In V4#
V4 agrees with V3 on the basics, and the following all hold at the pinned commit: ints and nums are 64 bits, int division drops the fraction, and mixed arithmetic produces a num.
task main() -> int {
pilot a = 7
pilot b = 2
pilot thrust: num = 2.5
-- 7 / 2 is 3 and 7 % 2 is 1, so this part is 31.
pilot whole = a / b * 10 + a % b
if thrust * 2.0 > 4.9 { give back whole + 5 }
give back whole
}
Int arithmetic wraps at the limits at this commit. V3 stops the program instead, as the specification says V4 will:
-- Past the largest int the value wraps around at this commit. V3 stops the program.
task main() -> int {
pilot largest: int = 9223372036854775807
pilot next = largest + 1
if next < 0 { give back 1 }
give back 0
}
V4 also refuses a bool in an int pilot, and a duplicate name in one block:
task main() -> int {
pilot sorties: int = true
give back sorties
}
2|0@25:50|local declaration type mismatch|sorties expects int but got bool
task main() -> int {
pilot fuel = 50
pilot fuel = 75
give back fuel
}
2|0@45:60|duplicate local declaration|fuel already exists in this scope
The rest of this section is what differs.
Keywords are reserved in lowercase only. V4 matches a keyword by its exact lowercase spelling. The capitalised names that V3 refuses are ordinary names in V4:
-- In V4 only the exact lowercase spelling is a keyword,
-- so these are ordinary names.
task main() -> int {
pilot Max = 4
pilot Pilot = 3
give back Max + Pilot
}
The truth literals are the exception, and here V4 and V3 agree. A capitalised truth literal is still a truth literal, so it cannot be a name, and it means what it says:
-- A truth literal in any capitalisation is still a truth literal in V4,
-- and it means what it says. Each test that passes adds to the exit code.
task main() -> int {
pilot code = 0
if TRUE { code += 1 }
if Yes { code += 2 }
if not FALSE { code += 4 }
give back code
}
**Every pilot can be reassigned, and mut is not accepted.** Chapter 2 showed pilot mut being rejected. Assignment to a plain pilot works:
-- At this snapshot every pilot can be reassigned; `mut` is not accepted yet.
task main() -> int {
pilot fuel = 50
fuel = 75
fuel += 5
give back fuel
}
That much agrees with the specification's section on pilots and with V3, both of which let a pilot be reassigned by default. The same section says a fixed pilot cannot be, which V3 enforces and this commit does not yet:
-- `fixed` is accepted, and not enforced at this commit.
task main() -> int {
fixed pilot fuel = 50
fuel = 75
give back fuel
}
A num is accepted where an int is required. V3 refuses to store a num in an int pilot, to assign one, or to pass one to an int parameter. This commit accepts all three and drops the fraction without a message:
-- V3 refuses all three of these. This V4 commit drops the fraction
-- and says nothing.
task whole(n: int) -> int {
give back n
}
task main() -> int {
pilot sorties: int = 2.5 -- becomes 2
pilot fuel = 10
fuel = 7.9 -- becomes 7
give back sorties * 10 + fuel + whole(3.99) -- 20 + 7 + 3
}
The conversion methods are missing. to_num(), to_int() and the others in this chapter's table are not there yet:
task main() -> int {
pilot sorties = 12
pilot average: num = sorties.to_num() / 5.0
if average > 2.0 { give back 1 }
give back 0
}
2|0@69:86|receiver has no matching method|int offers no method to_num
Pilots cannot be declared outside a task. A plain top-level pilot is a syntax error. The specification allows only declarations and constants at the top level:
pilot launches = 0
task main() -> int {
launches += 1
give back launches
}
2|0@0:19|unexpected token at top level|pilot
A top-level fixed pilot is such a constant. This commit reads it and cannot build it yet:
fixed pilot MAX_SORTIES: int = 3
task main() -> int {
give back MAX_SORTIES
}
v4-native-contract-error=native symbol values currently support only task pointers
A hexadecimal literal is misread silently. V3 reports 0xFF as an unknown binding. This commit takes the leading 0 as the value and carries on:
-- V3 rejects this literal. This V4 commit reads it as 0 and carries on.
task main() -> int {
pilot mask = 0xFF
give back mask + 7
}
A misspelled name is not reported well yet. V4 notices that something is wrong only when it tries to generate code, and says so in terms of types instead of naming the pilot:
task main() -> int {
give back altitude
}
v4-native-contract-error=native type not yet supported: unknown
An inner block cannot shadow. A second pilot with the same name inside a nested block is reported as a duplicate, where V3 allows it:
task main() -> int {
pilot fuel = 50
if fuel > 25 {
pilot fuel = 75
}
give back fuel
}
2|0@68:83|duplicate local declaration|fuel already exists in this scope
V4 has more basic types. The specification defines several that V3 lacks, and this commit already builds programs that use five of them. Three have a literal suffix, and a char is written in single quotes:
-- Five of the specification's extra types. Three have a literal suffix;
-- a char is written in single quotes.
task main() -> int {
pilot count: uint = 42u -- unsigned 64-bit
pilot more: uint = count + 8u
pilot small: tiny = 7t -- one byte
pilot next: tiny = small + 1t
pilot ratio: float = 3.14f -- the explicit 64-bit float
pilot narrow: float32 = 1.5 -- a 32-bit float; it has no suffix
pilot initial: char = 'a' -- one character, in single quotes
pilot passed = 0
if more == 50u { passed += 1 }
if next == 8t { passed += 1 }
if ratio > 3.0f { passed += 1 }
give back passed
}
uint is an unsigned 64-bit whole number, tiny is a single byte, float is an explicitly named 64-bit float, float32 is a 32-bit float, and char is a single character.
Planned#
The specification defines big, a whole number of any size, which cannot overflow, with literals written like 999b. V3 does not have it, and the pinned V4 commit recognises the type and cannot build it yet.
task main() -> int {
pilot huge: big = 999b
give back 0
}
v4-native-contract-error=native type not yet supported: big
The specification describes num as the default numeric type, with whole literals narrowing to int where an int is required. V3 does it the other way round: a literal without a decimal point is an int.
Two ways of grouping values without declaring a shape are specified and not in V3: tuples, written (42, "hello"), and fixed-size arrays, written [int; 4].
Summary#
A pilot is a named value with a type that never changes. The type is inferred from the starting value or written after a colon. A pilot can be reassigned unless it is declared fixed or grounded, and V3 enforces that.
A pilot lives until the end of its block. An inner block may shadow an outer name; the same block may not declare a name twice. Fifty-five words are reserved in any letter case, and single-word keywords work only in lowercase.
There are four basic types. int is a 64-bit whole number whose division truncates, and whose overflow or division by zero stops the program. num is a 64-bit floating-point number, printed to ten significant digits. bool is true or false, combined with and, or and not. word is text.
Types never mix on their own, with one exception: an int becomes a num where a num is expected. Everything else is converted by calling a method.
Chapter 4 is about tasks: how to define them, pass values in, and give values back.