aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-06-18 01:33:34 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-06-18 01:33:34 -0400
commit863f07d417c98781f4ff37039628ab21683fb234 (patch)
tree1d7f7470a1c7fdfd4665519b69ea07c30cafdfff
parented935eb882593540e827f8a18c384bf4ab54a653 (diff)
Add learnXinY example file
-rw-r--r--learnxiny.tm297
1 files changed, 297 insertions, 0 deletions
diff --git a/learnxiny.tm b/learnxiny.tm
new file mode 100644
index 00000000..0c75b66b
--- /dev/null
+++ b/learnxiny.tm
@@ -0,0 +1,297 @@
+// 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!")
+
+ // Declare an integer variable (types are inferred)
+ my_variable := 123
+
+ // Assign a new value
+ my_variable = 99
+
+ // Floating point numbers are similar, but have a decimal point:
+ my_num := 2.0
+
+ // Strings can use interpolation with curly braces:
+ say("My variable is {my_variable}")
+
+ // 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]
+
+ // 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"
+
+ // Loop control flow uses "skip" and "stop"
+ for num in my_numbers:
+ if num == 20:
+ // You can specify which loop variable you're skipping/stopping if
+ // there is any ambiguity.
+ skip num
+
+ if num == 30:
+ stop
+
+ >> num
+
+ // Tables are efficient hash maps
+ table := {"one": 1, "two": 2}
+ >> table["two"]
+ = 2
+
+ // 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 default values and fallbacks:
+ table2 := {"three": 3; fallback=table; default=0}
+ >> table2["two"]
+ = 2
+ >> table2["three"]
+ = 3
+ >> table2["???"]
+ = 0
+
+ // 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:
+ >> {[10, 20]: "one", [30, 40, 50]: "two"}
+
+ // 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: "@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],
+ maybe_null_pointer_to_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[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(radius=1)"
+ = yes
+
+ >> {my_shape:"nice"}
+ = {Shape.Circle(radius=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
+