code / tomo-koans

Lines447 Tomo432 INI9 Markdown6
(293 lines)
1 # This is the Tomo koans program
3 _HELP := "tomo koans - A tutorial program for learning the Tomo programming language"
5 use colorful
6 use shell
7 use commands
9 editor := "vim"
10 user : Text?
12 LESSONS := [
13 Lesson((./lessons/lesson-01-hello-world.tm), "Hello World", "Hello world\n"),
14 Lesson((./lessons/lesson-02-tests.tm), "Testing Code"),
15 Lesson((./lessons/lesson-03-variables.tm), "Variables"),
16 Lesson((./lessons/lesson-04-functions.tm), "Functions"),
17 Lesson((./lessons/lesson-05-basic-types.tm), "Basic Types"),
18 Lesson((./lessons/lesson-06-arrays.tm), "Arrays"),
19 Lesson((./lessons/lesson-07-optionals.tm), "Optionals"),
20 Lesson((./lessons/lesson-08-tables.tm), "Tables"),
21 Lesson((./lessons/lesson-09-text.tm), "Text"),
22 Lesson((./lessons/lesson-10-structs.tm), "Structs"),
23 Lesson((./lessons/lesson-11-enums.tm), "Enums"),
24 Lesson((./lessons/lesson-12-allocating.tm), "Allocating Memory"),
25 Lesson((./lessons/lesson-13-paths.tm), "File Paths"),
26 Lesson((./lessons/lesson-14-langs.tm), "Embedded Languages"),
27 Lesson((./lessons/lesson-15-min-max.tm), "Min and Max"),
28 Lesson((./lessons/lesson-16-reducers.tm), "Reducers"),
31 enum TestResult(NotRun, Success(output:Text), Error(err:Text), WrongOutput(actual:Text, expected:Text))
32 func print(result:TestResult)
33 when result is NotRun
34 pass
35 is Success(s)
36 $Colorful"
37 @(b,u:Program Output:)
38 @(green:$s)
39 ".print()
40 is Error(e)
41 $Colorful"
42 @(b,u:Program Errors:)
44 $e
45 ".print()
46 is WrongOutput(actual, expected)
47 $Colorful"
48 @(b,u:Program Output:)
49 @(red:$actual)
50 @(b,u:Expected:)
51 @(green:$expected)
52 ".print()
54 func is_success(result:TestResult -> Bool)
55 when result is Success return yes
56 else return no
58 struct Lesson(file:Path, description:Text, expected_output:Text?=none)
59 func get_result(l:Lesson -> TestResult)
60 result := $Shell"COLOR=1 tomo -O 0 $(l.file)".result()
61 if not result.succeeded()
62 return Error(Text.from_utf8(result.errors)!)
64 output := Text.from_utf8(result.output)!
65 if expected := l.expected_output
66 if output != expected
67 return WrongOutput(output, expected)
69 return Success(output)
71 func ask_continue()
72 _ := ask("\[2]Press Enter to continue...\[]", bold=no)
74 func clear_screen()
75 say("\x1b[2J\x1b[H", newline=no)
77 func summarize_tests(results:[TestResult], highlight:Path?=none)
78 $Colorful"
80 @(yellow,b,u:Lessons)
81 ".print()
83 passing := 0
84 failing := 0
85 for i,lesson in LESSONS
86 when results[i]! is Success
87 passing += 1
88 $Colorful"
89 @(green,bold:$(Text(i).left_pad(2)): "$(lesson.description)" (passes))
90 ".print()
91 is NotRun
92 failing += 1
93 $Colorful"
94 @(dim:$(Text(i).left_pad(2)): "$(lesson.description)" (not yet attempted))
95 ".print()
96 else
97 failing += 1
98 $Colorful"
99 @(red:$(Text(i).left_pad(2)): "$(lesson.description)" (failing))
100 ".print()
102 completed := Num(passing)/Num(passing+failing)
103 $Colorful"
105 @(cyan,b:Progress: $(completed.percent()))
107 ".print()
109 func short_summarize_tests(results:[TestResult])
110 say("Progress: ", newline=no)
111 for result in results
112 when result is Success
113 $Colorful"@(green,bold:#)".print(newline=no)
114 is NotRun
115 $Colorful"@(dim:#)".print(newline=no)
116 else
117 $Colorful"@(red:#)".print(newline=no)
119 say("\n")
121 func choose_option(options:{Text:Text} -> Text)
122 repeat
123 for k,v in options
124 $Colorful"
125 @(b:($k)) $v
126 ".print()
127 say("")
128 choice := (ask("Choose an option: ") or goodbye()).lower().to(1)
129 if options.has(choice)
130 return choice
131 else if choice == "q"
132 goodbye()
133 else
134 $Colorful"
135 @(red:I'm sorry, I don't recognize that choice, please try again!")
136 ".print()
137 fail("Unreachable")
139 func show_lesson(lesson:Lesson, result:TestResult)
140 clear_screen()
141 $Colorful"
143 @(yellow,b,u:$(lesson.description))
144 Here's what we have right now:
146 ".print()
148 result.print()
149 when result is Success
150 $Colorful"
151 @(green,b:✨ Great job, this test is passing! ✨)
153 ".print()
154 is NotRun
155 $Colorful"
156 @(dim,italic:...nothing, edit the file to make your first attempt...)
158 ".print()
159 else
160 $Colorful"
161 @(red,b:Looks like this test isn't passing yet! 😢)
163 ".print()
165 func goodbye(-> Abort)
166 clear_screen()
167 $Colorful"
169 @(b:Goodbye! Come back again soon!)
171 ".print()
172 exit(code=0)
174 func give_feedback(section:Text)
175 clear_screen()
176 user = user or ask("What name do go by? (optional) ") or "anon"
177 if user == "" then user = "anon"
178 feedback := ask("What's your feedback? ") or return
179 (./feedback.txt).append("
180 [$(user!)/$section] $feedback\n
181 ")!
183 func main(clean=no -> Abort)
184 clear_screen()
185 $Colorful"
187 @(bold,green:Hello and welcome to the Tomo Koans program!)
189 We're going to run through some programs that don't work
190 and you'll be fixing them up!
192 I hope you have fun!
194 ".print()
196 if clean
197 (./editor.txt).remove(ignore_missing=yes)!
198 (./lessons).remove(ignore_missing=yes)!
200 if (./editor.txt).exists()
201 editor = (./editor.txt).read()!
202 $Colorful"
203 @(dim,i:You're using @(green:$(editor)) as your text editor. If you want to change it, just edit @(magenta:./editor.txt))
205 ".print()
206 else
207 editor = ask("
208 What command line text editor do you want to use?
210 Ones with syntax highlighting: vim, nvim, emacs
211 Simpler editors: nano, pico, edit
213 editor:$(" ")
214 ") or goodbye()
215 while editor == "" or not $Shell"command -v $editor >/dev/null".run().succeeded()
216 editor = ask("I don't recognize that editor. Try again? ") or goodbye()
217 (./editor.txt).write(editor)!
218 $Colorful"
220 Great! From now on, I'll use @(b:$(editor)) to edit files.
221 If you want to change it, just edit @(magenta:./editor.txt)
223 ".print()
225 $Shell"
226 cp -r lesson-templates lessons
227 ".run().or_fail("Could not make lessons directory")
229 test_results := &[TestResult.NotRun for l in LESSONS]
231 ask_continue()
232 message_pending := no
233 repeat
234 unless message_pending
235 clear_screen()
236 message_pending = no
238 summarize_tests(test_results)
239 choice := ask("Choose a test, (q)uit, or give (f)eedback: ") or stop repeat
240 stop repeat if choice == "q" or choice == "Q"
242 if choice == "f" or choice == "F"
243 give_feedback("menu")
244 skip
246 if choice == ""
247 for i,result in test_results
248 if not result.is_success()
249 choice = Text(i)
250 stop
252 n := Int.parse(choice) or (do
253 $Colorful"@(red:I don't know what that means! Type a test number or 'q'.)".print()
254 message_pending = yes
255 skip repeat
258 if n < 1 or n > LESSONS.length
259 $Colorful"@(red:That's not a valid test number!)".print()
260 message_pending = yes
261 skip repeat
263 repeat
264 lesson := LESSONS[n]!
265 show_lesson(lesson, test_results[n]!)
266 short_summarize_tests(test_results)
268 options := &{
269 "e": "Edit file and try again",
270 "l": "Show the lesson list",
271 "q": "Quit",
272 "f": "Give Feedback",
274 if n < LESSONS.length
275 options["n"] = "Go to the next lesson"
277 when choose_option(options) is "e"
278 $Shell"
279 $(editor) $(lesson.file)
280 ".run().or_fail("Could not open editor $(editor)")
282 test_results[n] = lesson.get_result()
283 test_results[n]!.print()
284 is "l"
285 stop
286 is "n"
287 n += 1
288 is "q"
289 goodbye()
290 is "f"
291 give_feedback("lesson$n")
293 goodbye()