diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2024-09-13 00:27:46 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2024-09-13 00:27:46 -0400 |
| commit | 94a3714686391321ed0842a808c7895d092cc000 (patch) | |
| tree | db818f4cc60dde0e39fc1c692b11e583f39d97ca /examples | |
| parent | 0f673458da5a1897c76cc7be7aa3189cca1cf5aa (diff) | |
Move learnxiny to examples
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/README.md | 2 | ||||
| -rw-r--r-- | examples/learnxiny.tm | 357 |
2 files changed, 359 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md index aee5b95c..df5af833 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,6 +5,8 @@ - [game/](game/): An example game using raylib. - [http.tm](http.tm): An HTTP library to make basic synchronous HTTP requests. - [ini.tm](ini.tm): An INI configuration file reader tool. +- [learnxiny.tm](learnxiny.tm): A quick overview of language features in the + style of [learnxinyminutes.com](https://learnxinyminutes.com/). - [log.tm](log.tm): A logging utility. - [vectors.tm](vectors.tm): A math vector library. - [wrap.tm](wrap.tm): A command-line program to wrap text. diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm new file mode 100644 index 00000000..7feca6c1 --- /dev/null +++ b/examples/learnxiny.tm @@ -0,0 +1,357 @@ +# Tomo is a statically typed, garbage collected imperative language with +# emphasis on simplicity, safety, and speed. Tomo code cross compiles to C, +# which is compiled to a binary using your C compiler of choice. + +# To begin with, let's define a main function: +func main(): + # This function's code will run if you run this file. + + # Print to the console + say("Hello world!") + + # You can also use !! as a shorthand: + !! This is the same as using say(), but a bit easier to type + + # Declare a variable with ':=' (the type is inferred to be integer) + my_variable := 123 + + # Assign a new value + my_variable = 99 + + # Floating point numbers are similar, but require a decimal point: + my_num := 2.0 + + # Strings can use interpolation with the dollar sign $: + say("My variable is $my_variable and this is a sum: $(1 + 2)") + + say(" + Multiline strings begin with a " at the end of a line and continue in + an indented region below. + You can have leading spaces after the first line + and they'll be preserved. + + The multiline string won't include a leading or trailing newline. + ") + + # Docstring tests use ">>" and when the program runs, they will print + # their source code to the console on stderr. + >> 1 + 2 + + # If there is a following line with "=", you can perform a check that + # the output matches what was expected. + >> 2 + 3 + = 5 + # If there is a mismatch, the program will halt and print a useful + # error message. + + # Booleans use "yes" and "no" instead of "true" and "false" + my_bool := yes + + # Conditionals: + if my_bool: + say("It worked!") + else if my_variable == 99: + say("else if") + else: + say("else") + + # Arrays: + my_numbers := [10, 20, 30] + + # Empty arrays require specifying the type: + empty_array := [:Int] + >> empty_array.length + = 0 + + # Arrays are 1-indexed, so the first element is at index 1: + >> my_numbers[1] + = 10 + + # Negative indices can be used to get items from the back of the array: + >> my_numbers[-1] + = 30 + + # If an invalid index outside the array's bounds is used (e.g. + # my_numbers[999]), an error message will be printed and the program will + # exit. + + # Iteration: + for num in my_numbers: + >> num + + # Optionally, you can use an iteration index as well: + for index, num in my_numbers: + pass # Pass means "do nothing" + + # Arrays can be created with array comprehensions, which are loops: + >> [x*10 for x in my_numbers] + = [100, 200, 300] + >> [x*10 for x in my_numbers if x != 20] + = [100, 300] + + # Loop control flow uses "skip" and "stop" + for x in my_numbers: + for y in my_numbers: + if x == y: + skip + + # For readability, you can also use postfix conditionals: + skip if x == y + + if x + y == 60: + # Skip or stop can specify a loop variable if you want to + # affect an enclosing loop: + stop x + + # Tables are efficient hash maps + table := {"one": 1, "two": 2} + >> table:get("two") + = 2? + + # The value returned is optional (because the key might not be in the table). + # Optional values can be converted to regular values using `!` (which will + # create a runtime error if the value is null) or :or_else() which uses a + # fallback value if it's null. + >> table:get("two")! + = 2 + + >> table:get("xxx"):or_else(0) + = 0 + + # Empty tables require specifying the key and value types: + empty_table := {:Text:Int} + + # Tables can be iterated over either by key or key,value: + for key in table: + pass + + for key, value in table: + pass + + # Tables also have ".keys" and ".values" fields to explicitly access the + # array of keys or values in the table. + >> table.keys + = ["one", "two"] + >> table.values + = [1, 2] + + # Tables can have a fallback table that's used as a fallback when the key + # isn't found in the table itself: + table2 := {"three": 3; fallback=table} + >> table2:get("two")! + = 2 + >> table2:get("three")! + = 3 + + # Tables can also be created with comprehension loops: + >> {x:10*x for x in 5} + = {1:10, 2:20, 3:30, 4:40, 5:50} + + # If no default is provided and a missing key is looked up, the program + # will print an error message and halt. + + # Any types can be used in tables, for example, a table mapping arrays to + # strings: + table3 := {[10, 20]: "one", [30, 40, 50]: "two"} + >> table3:get([10, 20])! + = "one" + + # Sets are similar to tables, but they represent an unordered collection of + # unique values: + set := {10, 20, 30} + >> set:has(20) + = yes + >> set:has(999) + = no + + # You can do some operations on sets: + other_set := {30, 40, 50} + >> set:with(other_set) + = {10, 20, 30, 40, 50} + >> set:without(other_set) + = {10, 20} + >> set:overlap(other_set) + = {30} + + # So far, the datastructures that have been discussed are all *immutable*, + # meaning you can't add, remove, or change their contents. If you want to + # have mutable data, you need to allocate an area of memory which can hold + # different values using the "@" operator (think: "(a)llocate"). + my_arr := @[10, 20, 30] + my_arr[1] = 999 + >> my_arr + = @[999, 20, 30] + + # To call a method, you must use ":" and the name of the method: + my_arr:sort() + >> my_arr + = @[20, 30, 999] + + # To access the immutable value that resides inside the memory area, you + # can use the "[]" operator: + >> my_arr[] + = [20, 30, 999] + + # You can think of this like taking a photograph of what's at that memory + # location. Later, a new value might end up there, but the photograph will + # remain unchanged. + snapshot := my_arr[] + my_arr:insert(1000) + >> my_arr + = @[20, 30, 999, 1000] + >> snapshot + = [20, 30, 999] + # Internally, this is implemented using copy-on-write, so it's quite + # efficient. + + # These demos are defined below: + demo_keyword_args() + demo_structs() + demo_enums() + demo_lambdas() + +# Functions must be declared at the top level of a file and must specify the +# types of all of their arguments and return value (if any): +func add(x:Int, y:Int)->Int: + return x + y + +# Default values for arguments can be provided in place of a type (the type is +# inferred from the default value): +func show_both(first:Int, second=0)->Text: + return "first=$first second=$second" + +func demo_keyword_args(): + >> show_both(1, 2) + = "first=1 second=2" + + # If unspecified, the default argument is used: + >> show_both(1) + = "first=1 second=0" + + # Arguments can be specified by name, in any order: + >> show_both(second=20, 10) + = "first=10 second=20" + +# Here are some different type signatures: +func takes_many_types( + boolean:Bool, + integer:Int, + floating_point_number:Num, + text_aka_string:Text, + array_of_ints:[Int], + table_of_text_to_bools:{Text:Bool}, + pointer_to_mutable_array_of_ints:@[Int], + optional_int:Int?, + function_from_int_to_text:func(x:Int)->Text, +): + pass + +# Now let's define our own datastructure, a humble struct: +struct Person(name:Text, age:Int): + # We can define constants here if we want to: + max_age := 122 + + # Methods are defined here as well: + func say_age(self:Person): + say("My age is $self.age") + + # If you want to mutate a value, you must have a mutable pointer: + func increase_age(self:@Person, amount=1): + self.age += amount + + # Methods don't have to take a Person as their first argument: + func get_cool_name()->Text: + return "Blade" + +func demo_structs(): + # Creating a struct: + alice := Person("Alice", 30) + >> alice + = Person(name="Alice", age=30) + + # Accessing fields: + >> alice.age + = 30 + + # Calling methods: + alice:say_age() + + # You can call static methods by using the class name and ".": + >> Person.get_cool_name() + = "Blade" + + # Comparisons, conversion to text, and hashing are all handled + # automatically when you create a struct: + bob := Person("Bob", 30) + >> alice == bob + = no + + >> "$alice" == 'Person(name="Alice", age=30)' + = yes + + table := {alice: "first", bob: "second"} + >> table:get(alice)! + = "first" + + +# Now let's look at another feature: enums. Tomo enums are tagged unions, also +# known as "sum types". You enumerate all the different types of values +# something could have, and it's stored internally as a small integer that +# indicates which type it is, and any data you want to associate with it. +enum Shape( + Point, + Circle(radius:Num), + Rectangle(width:Num, height:Num), +): + # Just like with structs, you define methods and constants inside a level + # of indentation: + func get_area(self:Shape)->Num: + # In order to work with an enum, it's most often handy to use a 'when' + # statement to get the internal values: + when self is Point: + return 0 + is Circle(r): + return Num.PI * r^2 + is Rectangle(w, h): + return w * h + # 'when' statements are checked for exhaustiveness, so the compiler + # will give an error if you forgot any cases. You can also use 'else:' + # if you want a fallback to handle other cases. + +func demo_enums(): + # Enums are constructed like this: + my_shape := Shape.Circle(1.0) + + # If an enum type doesn't have any associated data, it is not invoked as a + # function, but is just a static value: + other_shape := Shape.Point + + # Similar to structs, enums automatically define comparisons, conversion + # to text, and hashing: + >> my_shape == other_shape + = no + + >> "$my_shape" == "Shape.Circle(1)" + = yes + + >> {my_shape:"nice"} + = {Shape.Circle(1):"nice"} + +func demo_lambdas(): + # Lambdas, or anonymous functions, can be used like this: + add_one := func(x:Int): x + 1 + >> add_one(5) + = 6 + + # Lambdas can capture closure values, but only as a snapshot from when the + # lambda was created: + n := 10 + add_n := func(x:Int): x + n + >> add_n(5) + = 15 + + # The lambda's closure won't change when this variable is reassigned: + n = -999 + >> add_n(5) + = 15 + |
