From 863f07d417c98781f4ff37039628ab21683fb234 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 18 Jun 2024 01:33:34 -0400 Subject: Add learnXinY example file --- learnxiny.tm | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 learnxiny.tm 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 + -- cgit v1.2.3