Values
Values are just a fancy term for data - in fact, most of what programs do is to manipulate data, to compute numbers from other numbers, to process strings, etc.
The type of nothing is Void
, based on C’s void
type.
Numbers
There are two classes of numbers built into the language. Integers, like 3
,
-45
or 124_500
, and reals, like 3.14
or -0.124_325_963
. Underscores
in number literals are ignored, thus they are purely cosmetic, allowing numbers
to be expressed in code in a more human-friendly way.
Integer types
There are fixed-length integer types, like:
ooc types | Width |
Int8, UInt8 | 8 |
Int16, UInt16 | 16 |
Int32, UInt32 | 32 |
Int64, UInt64 | 64 |
And others, mapped on C types:
ooc types | C types | Width |
Char, UChar | signed char, unsigned char | 8 |
Short, UShort | signed short, unsigned short | at least 16 |
Int, UInt | signed int, unsigned int | at least 16 |
Long, ULong | signed long, unsigned long | at least 32 |
LLong, ULLong | signed long long, unsigned long long | at least 32 |
There are several type of integer literals. Decimal literals are the most common, but octal, hexadecimal, and binary literals exist as well, for example:
75 // decimal
0c113 // octal
0x4b // hexadecimal
0b1001011 // binary
Floating point types
Similarly, real number types are based on C types:
ooc types | C types | Width |
Float | float | 32 |
Double | double | 64 |
Double | double | 64 |
LDouble | long double | 64, 80, 96 |
Integer Ranges
Any two values of integer type separated by two dots is a range. For example, this prints “Hello” ten times:
for (i in 0..10) {
// i takes values from 0 to 9
"Hello" println()
}
Text
Characters
A character in ooc is akin to a byte, it’s not a Unicode character. A character literal is enclosed in single quotes, and supports the following escape codes:
'a' // regular character
'\\' // literal backslash
'\'' // single quote
'\n' // new line
'\r' // carriage return
'\b' // backspace
'\t' // horizontal tab
'\f' // form feed
'\a' // alert (bell)
'\v' // vertical tab
'\nnn' // character with octal value nnn
'\xhh' // character with hexadecimal value hh
Multi-character literals are syntax errors, e.g. 'abcd'
is invalid.
The ooc type Char
is based on the C type char
.
Strings
There are two types of strings in ooc. CString
is a cover from Char*
and
is a vanilla C string, null-terminated. String
is a class that contains a
length and may be implemented however the implementor chooses.
A string literal is enclosed in double quotes, gives an ooc String
, and supports
the following escape codes:
"a" // regular character
"\\" // literal backslash
"\"" // double quote
"\n" // new line
"\r" // carriage return
"\b" // backspace
"\t" // horizontal tab
"\f" // form feed
"\a" // alert (bell)
"\v" // vertical tab
"\nnn" // character with octal value nnn
"\xhh" // character with hexadecimal value hh
String interpolation
ooc’s string interpolation syntax is inspired by Ruby’s:
"Hello, my name is #{name} and I am #{age} years old." println()
Numeric and stringy types are handled correctly by interpolated strings. For
all other types, a toString()
call is added. Object types that do not have
a toString()
method, when used in an interpolated string, will trigger a
compile error.
Raw strings
A raw string literal is enclosed in double quotes and preceded by c
without spaces,
for example this will be of type CString
, not String
:
puts(c"Some like em raw.")
Raw strings can be used to avoid extra allocations. One would hope that one day compilers would be smart enough to avoid that on their own, but in the meantime, one could roll their own implementations using only raw strings.
Variables
A value can also be simply a variable declaration or a variable access, for example:
a := "Hello" // this evaluates to "Hello"
a // so does this
The :=
operator is the declare-assign operator. It creates a new variable slot, infers
its type from the right-hand-side value, and assigns the value to the variable. The same
code can be rewritten like so:
a: String = "Hello" // this evaluates to "Hello"
a // this too
Or, in even longer form:
a: String
a = "Hello" // this evaluates to "Hello"
a // this too
The same applies inside a class declaration:
Dog: class {
age := 23
init: func {
age // this evalutes to 23
}
}
dog := Dog new()
dog age // this evalutes to 23 as well
Functions
Functions are values as well. For example:
Dog: class {
bark: func { "Woof!" println() }
}
Dog bark // this is a value
a := func { "Waf" println() } // this is a value as well
a // and so is this
Functions are of type Func
. Its syntax resembles a function definition.
For example, Func (Int, Int) -> Int
is the type of a function that takes
two integers and returns an integer. Both the argument list and the return
type are optional.
Pointers
Pointers are references to a region of memory. For example,
Dog: class {
age := 23
}
dog := Dog new()
age := dog age
age = 23 // `dog age` is still 23
agePtr := dog age&
agePtr@ = 42 // `dog age` is now 42
Post-fixing with &
takes the address of something. Post-fixing
with @
returns the value a pointer points to.
The type of a pointer is Type*
where Type is the underlying type, for
example, Int*
is a pointer to an Int. To accept or return any kind of
pointer, the catch-all Pointer
type can be used.
References
References are a variant of pointers especially useful in functions.
Here’s a mul2
function with pointers:
mul2: func (var: Int*) {
var@ *= 2
}
a := 12
mul2(a&)
a // now evalutes to 24
And here’s the same with references:
mul2: func (var: Int@) {
var *= 2
}
a := 12
mul2(a&)
a // now evalutes to 24
Notice that the function still neds to be called with a pointer, in this
case a&
. This is so that the caller is aware that the variable being
passed might be modified by the function.
However, inside the body of a function using a by-reference parameter, there
is no need to dereference it (postfix it @
) every time it is being accessed
or assigned.
Covers vs Classes
Objects are references, like in Java. For example, the following:
Container: class {
value := 19
}
modify: func (c: Container) {
c value = 23
}
c := Container new()
modify(c)
c value // now evalutes to 23
However, covers are passed by value, see:
Container: cover {
value: Int
}
modify: func (c: Container) {
// woops, we're modifying a copy of the original
c value = 23
}
c: Container
c value = 19
modify(c)
c value // still evalutes to 19
The same applies inside methods of covers - by default,
they apply to a copy of the cover. To be able to modify
the content of a cover, use func@
instead.
Container: cover {
value: Int
setValue: func@ (.value) {
// since we're using `func@`, `this` is a reference
this value = value
}
}
Hence, any cover constructor should be defined with func@
,
like so:
Container: cover {
value: Int
init: func@ (=value)
}
Enums
By default, enums are backed by Int
s. However, that’s transparent.
A value from an enum will be of the type of the enum. See:
// defining a new type named 'State'
State: enum {
// .. with two possible values
AWAKE
ASLEEP
}
// currentState is of type 'State'
currentState := State AWAKE
// only accepts values of type 'State'
isAsleep?: func (s: State) -> Bool {
(s == State ASLEEP)
}