1 # Tomo is a statically typed, garbage collected imperative language with
2 # emphasis on simplicity, safety, and speed. Tomo code cross compiles to C,
3 # which is compiled to a binary using your C compiler of choice.
5 # To begin with, let's define a main function:
7 # This function's code will run if you run this file.
12 # Declare a variable with ':=' (the type is inferred to be integer)
18 # Floating point numbers are similar, but require a decimal point:
21 # Strings can use interpolation with the dollar sign $:
22 say("My variable is $my_variable, my num is $my_num, and this is a sum: $(1 + 2)")
25 Multiline strings begin with a " at the end of a line and continue in
26 an indented region below.
27 You can have leading spaces after the first line
28 and they'll be preserved.
30 The multiline string won't include a leading or trailing newline.
33 # You can log values for debugging with ">>", which will print the line's
34 # source code and the value (with syntax highlighting) to the console on
38 # For assertions, you can use `assert`:
41 # Assert takes an optional message string, but either way, assertion
42 # failures will print a lot of contextual information.
43 assert 2 + 3 == 5, "Math is broken"
45 # Booleans use "yes" and "no" instead of "true" and "false"
51 else if my_variable == 99
57 my_numbers := [10, 20, 30]
59 # Empty lists require specifying the type:
61 assert empty_list.length == 0
63 # Lists are 1-indexed, so the first element is at index 1:
64 assert my_numbers[1] == 10
66 # Negative indices can be used to get items from the back of the list:
67 assert my_numbers[-1] == 30
69 # If an invalid index outside the list's bounds is used (e.g.
70 # my_numbers[999]), an error message will be printed and the program will
77 # Optionally, you can use an iteration index as well:
78 for index, num in my_numbers
79 pass # Pass means "do nothing"
81 # Lists can be created with list comprehensions, which are loops:
82 assert [x*10 for x in my_numbers] == [100, 200, 300]
83 assert [x*10 for x in my_numbers if x != 20] == [100, 300]
85 # Loop control flow uses "skip"/"continue" and "stop"/"break"
90 continue # This is the same as `skip`
92 # For readability, you can also use postfix conditionals:
96 # Skip or stop can specify a loop variable if you want to
97 # affect an enclosing loop:
99 break x # This is the same as `stop x`
101 # Tables are efficient hash maps
102 table := {"one": 1, "two": 2}
103 assert table["two"] == 2
105 # The value returned is optional because none will be returned if the key
106 # is not in the table:
107 assert table["xxx"] == none
109 # Optional values can be converted to regular values using `!` (which will
110 # create a runtime error if the value is null):
111 assert table["two"]! == 2
113 # You can also use `or` to provide a fallback value to replace none:
114 assert table["xxx"] or 0 == 0
116 # Empty tables require specifying the key and value types:
117 empty_table : {Text:Int}
118 assert empty_table == {}
120 # Tables can be iterated over either by key or key,value:
124 for key, value in table
127 # Tables also have ".keys" and ".values" fields to explicitly access the
128 # list of keys or values in the table.
129 assert table.keys == ["one", "two"]
130 assert table.values == [1, 2]
132 # Tables can have a fallback table that's used as a fallback when the key
133 # isn't found in the table itself:
134 table2 := {"three": 3; fallback=table}
135 assert table2["two"]! == 2
136 assert table2["three"]! == 3
138 # Tables can also be created with comprehension loops:
139 assert {x: 10*x for x in 5} == {1: 10, 2: 20, 3: 30, 4: 40, 5: 50}
141 # If no default is provided and a missing key is looked up, the program
142 # will print an error message and halt.
144 # Any types can be used in tables, for example, a table mapping lists to
146 table3 := {[10, 20]: "one", [30, 40, 50]: "two"}
147 assert table3[[10, 20]]! == "one"
149 # So far, the datastructures that have been discussed are all *immutable*,
150 # meaning you can't add, remove, or change their contents. If you want to
151 # have mutable data, you need to allocate an area of memory which can hold
152 # different values using the "@" operator (think: "(a)llocate").
153 my_arr := @[10, 20, 30]
155 assert my_arr[] == [999, 20, 30]
157 # To call a method, you must use ":" and the name of the method:
159 assert my_arr[] == [20, 30, 999]
161 # To access the immutable value that resides inside the memory area, you
162 # can use the "[]" operator:
163 assert my_arr[] == [20, 30, 999]
165 # You can think of this like taking a photograph of what's at that memory
166 # location. Later, a new value might end up there, but the photograph will
170 assert my_arr[] == [20, 30, 999, 1000]
171 assert snapshot == [20, 30, 999]
172 # Internally, this is implemented using copy-on-write, so it's quite
175 # These demos are defined below:
182 # Functions must be declared at the top level of a file and must specify the
183 # types of all of their arguments and return value (if any):
184 func add(x:Int, y:Int -> Int)
187 # Default values for arguments can be provided in place of a type (the type is
188 # inferred from the default value):
189 func show_both(first:Int, second=0 -> Text)
190 return "first=$first second=$second"
192 func demo_keyword_args()
193 assert show_both(1, 2) == "first=1 second=2"
195 # If unspecified, the default argument is used:
196 assert show_both(1) == "first=1 second=0"
198 # Arguments can be specified by name, in any order:
199 assert show_both(second=20, 10) == "first=10 second=20"
201 # Here are some different type signatures:
202 func takes_many_types(
205 floating_point_number:Num,
206 text_aka_string:Text,
208 table_of_text_to_bools:{Text=Bool},
209 pointer_to_mutable_list_of_ints:@[Int],
211 function_from_int_to_text:func(x:Int -> Text),
215 # Now let's define our own datastructure, a humble struct:
216 struct Person(name:Text, age:Int)
217 # We can define constants here if we want to:
220 # Methods are defined here as well:
221 func say_age(self:Person)
222 say("My age is $self.age")
224 # If you want to mutate a value, you must have a mutable pointer:
225 func increase_age(self:@Person, amount=1)
228 # Methods don't have to take a Person as their first argument:
229 func get_cool_name(->Text)
234 alice := Person("Alice", 30)
235 assert alice == Person(name="Alice", age=30)
238 assert alice.age == 30
243 # You can call static methods by using the class name and ".":
244 assert Person.get_cool_name() == "Blade"
246 # Comparisons, conversion to text, and hashing are all handled
247 # automatically when you create a struct:
248 bob := Person("Bob", 30)
249 assert alice == bob == no
251 assert "$alice" == 'Person(name="Alice", age=30)' == yes
253 table := {alice: "first", bob: "second"}
254 assert table[alice]! == "first"
257 # Now let's look at another feature: enums. Tomo enums are tagged unions, also
258 # known as "sum types". You enumerate all the different types of values
259 # something could have, and it's stored internally as a small integer that
260 # indicates which type it is, and any data you want to associate with it.
264 Rectangle(width:Num, height:Num),
266 # Just like with structs, you define methods and constants inside a level
268 func get_area(self:Shape->Num)
269 # In order to work with an enum, it's most often handy to use a 'when'
270 # statement to get the internal values:
277 # 'when' statements are checked for exhaustiveness, so the compiler
278 # will give an error if you forgot any cases. You can also use 'else:'
279 # if you want a fallback to handle other cases.
282 # Enums are constructed like this:
283 my_shape := Shape.Circle(1.0)
285 # If an enum type doesn't have any associated data, it is not invoked as a
286 # function, but is just a static value:
287 other_shape := Shape.Point
289 # Similar to structs, enums automatically define comparisons, conversion
290 # to text, and hashing:
291 assert my_shape == other_shape == no
293 assert "$my_shape" == "Circle(1)" == yes
295 assert {my_shape: "nice"} == {Shape.Circle(1): "nice"}
298 # Lambdas, or anonymous functions, can be used like this:
299 add_one := func(x:Int) x + 1
300 assert add_one(5) == 6
302 # Lambdas can capture closure values, but only as a snapshot from when the
303 # lambda was created:
305 add_n := func(x:Int) x + n
306 assert add_n(5) == 15
308 # The lambda's closure won't change when this variable is reassigned:
310 assert add_n(5) == 15
312 # A lang lets you define a custom Text type that is unique:
314 # You can define conversion functions to ensure safe escaping
315 convert(text:Text -> MyLanguage)
316 return MyLanguage.from_text(text.quoted())
320 # Safely quoted during interpolation:
321 lingo := $MyLanguage"My name is $name, how are you?"