From b41fde8f82582750ce90836f5947969be8ace261 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Mar 2025 22:16:58 -0400 Subject: [PATCH] More lessons --- koans.tm | 8 ++++ lesson-templates/lesson-02-tests.tm | 6 +-- lesson-templates/lesson-05-basic-types.tm | 10 ++--- lesson-templates/lesson-06-arrays.tm | 37 +++++++++++++++ lesson-templates/lesson-07-optionals.tm | 55 +++++++++++++++++++++++ lesson-templates/lesson-08-tables.tm | 42 +++++++++++++++++ lesson-templates/lesson-09-text.tm | 34 ++++++++++++++ lesson-templates/lesson-10-structs.tm | 42 +++++++++++++++++ lesson-templates/lesson-11-enums.tm | 37 +++++++++++++++ lesson-templates/lesson-12-allocating.tm | 42 +++++++++++++++++ lesson-templates/lesson-13-paths.tm | 47 +++++++++++++++++++ 11 files changed, 350 insertions(+), 10 deletions(-) create mode 100644 lesson-templates/lesson-06-arrays.tm create mode 100644 lesson-templates/lesson-07-optionals.tm create mode 100644 lesson-templates/lesson-08-tables.tm create mode 100644 lesson-templates/lesson-09-text.tm create mode 100644 lesson-templates/lesson-10-structs.tm create mode 100644 lesson-templates/lesson-11-enums.tm create mode 100644 lesson-templates/lesson-12-allocating.tm create mode 100644 lesson-templates/lesson-13-paths.tm diff --git a/koans.tm b/koans.tm index cb9e52e..8b5516f 100644 --- a/koans.tm +++ b/koans.tm @@ -14,6 +14,14 @@ LESSONS := [ Lesson((./lessons/lesson-03-variables.tm), "Variables"), Lesson((./lessons/lesson-04-functions.tm), "Functions"), Lesson((./lessons/lesson-05-basic-types.tm), "Basic Types"), + Lesson((./lessons/lesson-06-arrays.tm), "Arrays"), + Lesson((./lessons/lesson-07-optionals.tm), "Optionals"), + Lesson((./lessons/lesson-08-tables.tm), "Tables"), + Lesson((./lessons/lesson-09-text), "Text"), + Lesson((./lessons/lesson-10-structs.tm), "Structs"), + Lesson((./lessons/lesson-11-enums.tm), "Enums"), + Lesson((./lessons/lesson-12-allocating.tm), "Allocating Memory"), + Lesson((./lessons/lesson-13-paths.tm), "File Paths"), ] enum TestResult(Success(output:Text), Error(err:Text), WrongOutput(actual:Text, expected:Text)): diff --git a/lesson-templates/lesson-02-tests.tm b/lesson-templates/lesson-02-tests.tm index 44de43e..b00e94a 100644 --- a/lesson-templates/lesson-02-tests.tm +++ b/lesson-templates/lesson-02-tests.tm @@ -19,8 +19,4 @@ func main(): # Edit this test so it passes: >> 2 + 2 - = 9999 - - # For the rest of this tutorial, you won't - # be editing any of the tests, you'll be - # fixing code so it passes the tests. + = ??? diff --git a/lesson-templates/lesson-05-basic-types.tm b/lesson-templates/lesson-05-basic-types.tm index 27609e7..1783453 100644 --- a/lesson-templates/lesson-05-basic-types.tm +++ b/lesson-templates/lesson-05-basic-types.tm @@ -17,17 +17,17 @@ func main(): d := yes >> a - = 99 + = ??? >> b - = 2.718 + = ??? >> c - = "Hello, world!" + = ??? >> d - = no + = ??? # Text values support interpolation using `$`: name := "Alice" greeting := "Hello, $name!" >> greeting - = "Hello, Bob!" + = ??? diff --git a/lesson-templates/lesson-06-arrays.tm b/lesson-templates/lesson-06-arrays.tm new file mode 100644 index 0000000..dac19f1 --- /dev/null +++ b/lesson-templates/lesson-06-arrays.tm @@ -0,0 +1,37 @@ +# Arrays + +func main(): + + # Arrays are ordered collections of values. + # You can define an array using `[...]`: + + nums := [10, 20, 30] + + # Arrays are 1-indexed. + >> nums[2] + = ??? + + # Arrays can be empty but must have a type: + empty := [:Int] + + >> empty + = [] + + # You can loop over an array with `for value in array`: + sum := 0 + for num in nums: + sum += num + + >> sum + = ??? + + # Array comprehensions let you transform arrays concisely: + squares := [n + 1 for n in nums] + + >> squares + = [???] + + # You can also get the index with `for index, value in array`: + for i, num in nums: + >> squares[i] == num * num + = yes diff --git a/lesson-templates/lesson-07-optionals.tm b/lesson-templates/lesson-07-optionals.tm new file mode 100644 index 0000000..371764a --- /dev/null +++ b/lesson-templates/lesson-07-optionals.tm @@ -0,0 +1,55 @@ +# Optional Types + +func main(): + + # Any type `T` can be made optional with the syntax `T?`, + # meaning it can hold a value or be `none`. + + x := 42? + >> x + = x + + # You can assign a `none` value to `x` because it has type `Int?` + x = none + + # To declare a `none` variable, specify its type: + y := none:Int + + >> y + = ??? + + # Some functions return optional values: + >> Int.parse("123") + = ??? + >> Int.parse("blah") + = ??? + + # You can check if a value exists with `if`: + n := Int.parse("123") + if n: + # Inside this condition, `n` is known to be non-none + n = add(n, 1) + >> n + = ??? + + # Optionals are useful for handling missing data: + name := none:Text + greeting := if name: + "Hello, $name!" + else: + "Hello, stranger!" + + >> greeting + = ??? + + # Optional values can be converted to non-optional using `or` + >> Int.parse("blah") or 0 + = ??? + + # They can also be converted using the `!` operator, which + # will give an error if a non-none value is encountered: + >> add(Int.parse("123")!, 1) + = ??? + +func add(x:Int, y:Int -> Int): + return x + y diff --git a/lesson-templates/lesson-08-tables.tm b/lesson-templates/lesson-08-tables.tm new file mode 100644 index 0000000..730c96c --- /dev/null +++ b/lesson-templates/lesson-08-tables.tm @@ -0,0 +1,42 @@ +# Tables + +func main(): + + # Tables store key-value pairs. + # You can define a table using `{key = value, ...}`. + + scores := {"Alice"=100, "Bob"=200} + >> scores + = {"Alice"=100, "Bob"=200} + + >> scores["Alice"] + = ??? + + # Accessing a missing key gives `none` + >> scores["Zoltan"] + = ??? + + # Tables can be empty but must have key and value types: + empty := {:Text,Int} + >> empty + = {} + + # You can loop over tables: + total := 0 + for name, score in scores: + total += score + + >> total + = 9999 + + # Table keys and values can be accessed as an array: + >> scores.keys + = [???] + + >> scores.values + = [???] + + # Table comprehensions let you create tables concisely: + doubled := {k = v * 2 for k, v in scores} + >> doubled + = {???} diff --git a/lesson-templates/lesson-09-text.tm b/lesson-templates/lesson-09-text.tm new file mode 100644 index 0000000..dd76b6b --- /dev/null +++ b/lesson-templates/lesson-09-text.tm @@ -0,0 +1,34 @@ +# Text + +func main(): + + # Text values are sequences of letters. + greeting := "Hello" + + >> greeting.length + = ??? + + # Text supports interpolation with `$`: + name := "Alice" + message := "Hello, $name, your number is $(1 + 2)!" + + >> message + = ??? + + # Multi-line text uses indented quotes: + multiline := " + line one + line two + line three + " + + # Methods calls use `:` + >> multiline:lines() + = [???] + + # Common text methods: + >> "hello":upper() + = ??? + + >> "hello":split() + = [???] diff --git a/lesson-templates/lesson-10-structs.tm b/lesson-templates/lesson-10-structs.tm new file mode 100644 index 0000000..a40376e --- /dev/null +++ b/lesson-templates/lesson-10-structs.tm @@ -0,0 +1,42 @@ +# Structs and Methods + +# The keyword `struct` is used to define structures +# that hold multiple members: +struct Point(x:Int, y:Int): + + # Methods are any function defined inside of the + # indented area below a struct definition. + + # There is no implicit `self` argument, only the + # arguments you explicitly define. + func absolute(p:Point -> Point): + return Point(p.x:abs(), p.y:abs()) + + # Constants can be declared inside of a struct's namespace: + ZERO := Point(0, 0) + + # Arbitrary functions can also be defined here: + func squared_int(x:Int -> Int): + return x * x + +func main(): + + # You can create a struct instance like this: + p := Point(x=3, y=4) + + >> p + = Point(x=???, y=???) + + >> Point.ZERO + = Point(x=???, y=???) + + >> p.x + = ??? + >> p.y + = ??? + + >> p.sum() + = ??? + + >> Point.squared_int(5) + = ??? diff --git a/lesson-templates/lesson-11-enums.tm b/lesson-templates/lesson-11-enums.tm new file mode 100644 index 0000000..3622933 --- /dev/null +++ b/lesson-templates/lesson-11-enums.tm @@ -0,0 +1,37 @@ +# Enums + +# Enums define a type with multiple possible variants: +enum Shape(Circle(radius: Num), Rectangle(width: Num, height: Num), Point): + + # Use `when` to pattern match an enum: + func area(shape: Shape -> Num): + when shape is Circle(radius): + return Num.PI * radius * radius + is Rectangle(width, height): + return width * height + is Point: + return 0 + +func main(): + + # You can create instances of an enum: + s1 := Shape.Point + + # Single member enums display without the field names: + s2 := Circle(radius=10) + >> s1 + = Circle(10) + + # Multi-member enums explicitly list their field names: + s3 := Shape.Rectangle(width=4, height=5) + >> s3 + = Rectangle(width=4, height=5) + + >> s1:area() + = ??? + + >> s2:area() + = ??? + + >> "My shape is $s3" + = ??? diff --git a/lesson-templates/lesson-12-allocating.tm b/lesson-templates/lesson-12-allocating.tm new file mode 100644 index 0000000..2f02bca --- /dev/null +++ b/lesson-templates/lesson-12-allocating.tm @@ -0,0 +1,42 @@ +# Heap Allocation with `@` + +func main(): + + # By default, values in Tomo are immutable. + # To allow mutation, you need to allocate memory using `@` + + nums := @[1, 2, 3] + nums[1] = 99 + + >> nums + = @[???, 2, 3] + + nums:insert(40) + >> nums + = @[???, 2, 3, ???] + + # Allocated memory is not equal to other allocated memory: + a := @[10, 20, 30] + b := @[10, 20, 30] + >> a == b + = ??? + + # The `[]` operator can be used to access the value stored + # at a memory location: + >> a[] == b[] + = ??? + + # Tables also require `@` to allow modifications: + scores := @{"Alice"=100, "Bob"=200} + + scores["Charlie"] = 300 + + >> scores["Charlie"] + = ??? + + # Without `@`, attempting to mutate will cause an error: + frozen := {"key"="value"} + frozen["key"] = "new value" # This should fail + + >> frozen["key"] + = "new value" diff --git a/lesson-templates/lesson-13-paths.tm b/lesson-templates/lesson-13-paths.tm new file mode 100644 index 0000000..6ac0144 --- /dev/null +++ b/lesson-templates/lesson-13-paths.tm @@ -0,0 +1,47 @@ +# Paths + +func main(): + + # Tomo includes a built-in literal type for file paths + # A path is inside parentheses and begins with `/`, `~`, `.` or `..` + + file := (/tmp/test-file.txt) + >> file + = /tmp/test-file.txt + + file:write("first line") + >> file:read() + = "???" + + file:append(" + + second line + ") + + >> file:exists() + = yes + + >> file:lines() + = [???] + + # You can iterate over a file by lines: + >> upper_lines := [line:upper() for line in file:by_line()] + = [???] + + >> file:parent() + = /??? + + >> file:extension() + = "???" + + >> file:parent():child("other-file.txt") + = /??? + + >> dir := (/tmp/test-*.txt):glob() + = [???] + + file:remove() + + >> file:exists() + = ??? +