(561 lines)
1 #!/usr/bin/env nomsu -V7.0.02 ###3 This is a Nomsu tutorial.5 use "filesystem"6 use "consolecolor"7 use "commandline"8 use "progressbar"9 use "shell"10 (lesson $name $lesson) compiles to ("11 {name=\($name as lua expr), lesson=\(12 quote ((SyntaxTree {.type = "FileChunks"} $lesson) as nomsu, text)13 )}14 ")16 [<your code here>, ???] all compile to17 \(18 at \("Text" tree with "\((this tree).source)") fail19 "Incomplete code: This needs to be filled in."20 )22 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~24 $lessons = [25 lesson "Variables":26 ### In Nomsu, variables have a "$" prefix, and you can just assign to them27 without declaring them first:28 $x = 129 test that ($x == 1)31 ### Variables which have not yet been set have the value (nil)32 test that ($not_yet_set == (nil))34 ### Variables can be nameless:35 $ = 9937 ### Or have spaces, if surrounded with parentheses:38 $(my favorite number) = 2340 ### Figure out what value $my_var should have:41 $my_var = 10042 $my_var = ($my_var + $x + $(my favorite number))43 test that ($my_var == (???))45 lesson "Actions":46 ### In Nomsu, actions are bits of code that run to produce a value or47 do something. They have flexible syntax, so the values passed to48 them can be interspersed with the action's name in any order.49 ### Fix this action so the tests pass:50 ($x doubled) means ((???) * $x)52 ### Tests:53 test that ((2 doubled) == 4)54 test that ((-5 doubled) == -10)56 lesson "Blocks":57 ### When you need to do multiple things inside an action, use a block.58 ### Blocks are written with a colon followed by some indented code:59 ($x doubled then squared) means:60 $x = (2 * $x)61 $x = (???)62 return $x64 ### Blocks are also used for loops and conditionals:65 for $num in [0, -1, 10, 4]:66 $correct_answer = (4 * ($num * $num))67 if (($num doubled then squared) != $correct_answer):68 fail "Wrong answer for \($num)!"70 lesson "Text":71 ### Nomsu text is enclosed in double quotation marks:72 $text = "Hello"74 ### You can insert values into text using a backslash:75 test that ("two plus three is \(2 + 3)" == (???))77 ### Variables don't require parentheses, but other expressions do:78 $x = 9979 test that ("$x is \$x" == (???))81 ### This can be used to convert values to text:82 test that ("\$x" == (???))84 ### Longer strings use '("' followed by an indented region:85 $long = ("86 line one87 line two with spaces at the front88 ")90 test that91 $long == ("92 \(<your code here>)93 \(<your code here>)94 ")96 lesson "Conditionals":97 ### Make this action return "big" if its argument is bigger98 than 99, otherwise return "small"99 (the size of $n) means:100 if (???):101 <your code here>102 ..else:103 <your code here>105 ### Tests:106 for $big_number in [9999, 100]:107 test that ((the size of $big_number) == "big")109 for $small_number in [0, 1, -5, -999, 99]:110 test that ((the size of $small_number) == "small")112 lesson "Loops":113 ### Fix this action so the tests pass:114 (the sum of $numbers) means:115 $sum = 0117 ### You can loop over the values in a list like this:118 for $number in $numbers:119 ### Hint: math expressions may need parentheses120 <your code here>121 return $sum123 ### Tests:124 test that ((the sum of [1, 2, 3, 4, 5]) == 15)125 test that ((the sum of [100, 200]) == 300)127 ### You can also loop over a number range like this:128 $total = 0129 for $i in (1 to 3):130 $total = ($total + $i)131 test that ($total == (???))133 lesson "Variable Scopes":134 ### Nomsu's variables are local by default, and actions have their own scopes:135 $x = 1136 $y = 2138 ### Define an action that sets a variable:139 (do something) means:140 ### The variable $y is never set in this action, so it has the same value141 it has outside this action.142 test that ($y == (???))144 ### $x is set inside this action, and actions have their own scopes.145 $x = $y147 ### What number should $x be here?148 test that ($x == (???))150 ### After running the action, what value should $x have?151 do something152 test that ($x == (???))154 lesson "More Variable Scopes":155 ### Loops and conditionals do *not* have their own scopes:156 $z = 1157 if (1 == 1):158 ### Set $z inside a conditional:159 $z = 2161 ### After assigning in a conditional, what should $z be?162 test that ($z == (???))163 for $ in (1 to 1):164 ### Set $z inside a loop:165 $z = 3167 ### After assigning in a loop, what should $z be?168 test that ($z == (???))170 lesson "Externals":171 ### The 'external' block lets you modify variables outside an action:172 $x = 1173 (do something) means:174 external: $x = 2175 do something177 ### After running the action that sets $x in an 'external' block, what should $x be?178 test that ($x == (???))180 lesson "Locals":181 ### The 'with' block lets you create a local scope for the variables you list:182 $y = 1183 $z = 1184 with [$y]:185 $y = 2186 $z = 2188 ### After setting $y and $z in the 'with [$y]' block, what should $y and $z be?189 test that ($y == (???))190 test that ($z == (???))192 lesson "Failure and Recovery":193 $what_happened = "nothing"195 ### In Nomsu, sometimes things fail, but you can recover from failures with 'try':196 try:197 ### The 'fail' action triggers failure198 fail "Oh no!"199 ..if it fails:200 $what_happened = "failure"201 ..if it succeeds:202 $what_happened = "success"204 ### What do you think happened?205 test that ($what_happened == (???))207 ### Note: a 'try' block will silence failures, so this has no effect:208 try: fail210 lesson "Indexing":211 ### Nomsu uses the "." operator to access things inside an object:212 $dictionary = {.dog = "A lovable doofus", .cat = "An internet superstar"}213 test that ($dictionary.dog == "A lovable doofus")214 test that ($dictionary.cat == (???))216 ### If you try to access a key that's not in an object, the result is (nil):217 test that ($dictionary.mimsy == (???))219 ### $dictionary.dog is just a shorthand for $dictionary."dog".220 You may need to use the longer form for strings with spaces:221 $dictionary."guinea pig" = "A real-life tribble"223 ### Dictionaries are created with curly braces ({}) and can have224 anything as a key or value, including numbers or other dictionaries:225 $dictionary.5 = "The number five"226 $dictionary.five = 5227 $dictionary.myself = $dictionary228 test that ($dictionary.myself == (???))230 ### Lists are similar, but use square brackets ([])231 and can only have numbers as keys, starting at 1:232 $list = ["first", "second", 999]233 test that ($list.1 == "first")234 test that ($list.2 == (???))235 test that ($list.3 == (???))237 ### Hint: 4 should be a missing key238 test that ($list.4 == (???))239 test that ($list.foobar == (???))241 ### The "#" action gets the number of items inside something:242 test that (#$list == (???))243 test that (#{.x = 10, .y = 20} == (???))245 lesson "Methods":246 ### The "," is used for method calls, which means calling an action247 that's stored on an object (with the object as the first argument).248 ### Lists have an "add" method that puts new items at the end:249 $list = [-4, -6, 5]250 $list, add 3251 test that ($list == [-4, -6, 5, 3])252 $list, add 7253 test that ($list == [???])255 ### Text also has some methods like:256 $name = "Harry Tuttle"257 test that (($name, from 7 to 12) == "Tuttle")258 test that (($name, with "Tuttle" -> "Buttle") == (???))260 ### Methods can be chained too:261 test that (($name, with "Tuttle" -> "Buttle", from 7 to 12) == (???))263 lesson "Object Oriented Programming":264 ### Object Oriented Programming deals with things that have265 associated values and behaviors.266 ### Here, we define a Buffer to be a thing that has a .bits value:267 (a Buffer) is (a thing) with [$bits]:268 ### And some methods:269 ($self, set up) means:270 ### This method runs when a new buffer is created271 $bits = []273 ### This method is used to add to a buffer274 ($self, add $bit) means:275 $bits, add $bit277 ### ($list, joined) is a list method that concatenates the list items:278 ($self, as text) means ($bits, joined)280 ### Write a method called ($self, length) that returns the sum281 of the lengths of each bit in the buffer:282 <your code here>284 ### Create an instance of a Buffer:285 $b = (a Buffer)286 test that ($b is "a Buffer")287 test that ((type of $b) is "a Buffer")288 $b, add "xx"289 $b, add "yyy"290 test that (($b, as text) == "xxyyy")291 test that (($b, length) == 5)293 ### You can define a thing that inherits the behaviors of another thing like this:294 (a Backwards Buffer) is (a Buffer) with [$bits]:295 ### ($list, reversed) is a method that returns a copy of $list with296 the order of the items reversed.297 ($self, as text) means ($bits, reversed, joined)298 $bb = (a Backwards Buffer)299 $bb, add "one"300 $bb, add "two"301 test that (($bb, length) == (???))302 test that (($bb, as text) == (???))304 lesson "Files Part 1":305 ### Define an external action here:306 external:307 ### These will be used in the next lesson308 $global_var = 23309 ($x tripled) means:310 <your code here>312 test that ((5 tripled) == 15)313 test that ((2 tripled) == 6)315 lesson "Files Part 2":316 ### 'use' is the action for importing from other files.317 ### It takes the path to the file (without the .nom extension):318 use (<prev lesson>)319 test that ((10 tripled) == (???))320 test that ($global_var == (???))321 ]322 $(ask normally) = $(ask)323 command line program with $args:324 if ($args.help or $args.h):325 say ("326 Nomsu tutorial usage: nomsu -t tutorial [-x] [<location to put tutorial files>]327 ")328 exit330 say331 bold ("333 +------------------------------------+334 | Welcome to the Nomsu tutorial! |335 +------------------------------------+337 ")339 ### For this tutorial questions are hilighted in bold cyan:340 (ask $q) means (ask normally (bold (cyan $q)))342 ### Find the tutorial file directory:343 if ($args.extras is empty):344 $tutorial_dir = "./nomsu_tutorial"345 unless ($Files.exists $tutorial_dir):346 when347 ask "The Nomsu tutorial files will be in \$tutorial_dir is that okay? [Y/n/exit] "348 ..is:349 "n" "N" "no":350 $tutorial_dir = (ask "Where do you want to put the tutorial? ")351 "exit" "quit" "q" "e": exit352 ..else:353 $tutorial_dir = $args.extras.1355 ### Set up the tutorial file directory:356 if (not ($Files.exists $tutorial_dir)):357 make directory $tutorial_dir358 (filename of $i) means ("\($tutorial_dir)/lesson\$i.nom", with "//" -> "/")359 for ($i = $lesson) in $lessons:360 $filename = (filename of $i)361 unless ($Files.exists $filename):362 $lesson_text =363 $lesson.lesson, with "%(<prev lesson>%)" ->364 "\"\(filename of ($i - 1), with "%.nom$" -> "", with "^%./" -> "")\""366 write $lesson_text to file $filename368 ### Display info about editing the tutorial files:369 if $args.x:370 say ("371 The tutorial files are located in \$tutorial_dir.372 You're in charge of editing them yourself.373 ")374 ..else:375 $EDITOR = ($os.getenv "EDITOR")376 if $EDITOR:377 when378 ask ("379 \$EDITOR is your system editor, would you like this tutorial to use it? [Y/n] \;380 ")381 ..is:382 "n" "N" "no":383 $EDITOR = (nil)385 unless $EDITOR:386 say387 $EDITOR =388 ask ("389 What program would you like to use to edit tutorial files?390 (leave blank if you want to edit on your own in a different window)391 > \;392 ")394 if ($EDITOR == ""):395 $EDITOR = (nil)397 say ("399 For this tutorial, a set of files has been created in \$tutorial_dir.400 Each file corresponds to a lesson. Your task is to fix each file401 so that it passes the tests.403 ")405 (run lesson $i) means:406 $filename = (filename of $i)407 $file = (read file $filename)408 $file = ($NomsuCode, from (Source $filename 1 #$file) $file)409 $tree = ($file parsed)410 $tree =411 $tree, with412 $ ->:413 if ($ == \(<prev lesson>)):414 return ("Text" tree with (filename of ($i - 1), with "%.nom$" -> ""))416 run $tree418 (get failures) means [:419 for ($i = $lesson) in $lessons:420 try:421 run lesson $i422 ..if it fails with $msg:423 $msg = ($msg, with "\n *stack traceback:.*" -> "")424 add {.lesson_number = $i, .failure = $msg}425 ]427 say (bold "Lessons:")428 (show first failure from $failures) means:429 say ("431 \(bold "Next thing to fix:") \(432 bold433 yellow ("434 Lesson \($failures.1.lesson_number): \($lessons.($failures.1.lesson_number).name)435 ")436 )438 \($failures.1.failure, indented)440 ")441 $failures = (get failures)442 $current_lesson = (nil)443 for ($i = $lesson) in $lessons:444 for $f in $failures:445 if ($f.lesson_number == $i):446 $color = ((red) if $current_lesson else (yellow))447 $current_lesson or= $i448 say "\$color - \(bold "\$color\$i. \($lesson.name) [incomplete]")"449 do next $lesson451 say "\(green " + ")\(bold (green "\$i. \($lesson.name) [passed]"))"453 if $(COLOR ENABLED):454 say ("456 \(bold "Your progress:") \(20 wide ((#$lessons - #$failures) / #$lessons) progress bar)457 ")458 ..else:459 say460 say ((#$lessons - #$failures) / #$lessons progress bar)462 repeat until ($failures is empty):463 show first failure from $failures465 ### Have the user fix the first failure:466 unless $EDITOR:467 ### If the user is using an external editor, wait for the file to change468 $filename = (filename of $failures.1.lesson_number)469 say ("470 \(yellow "Waiting for you to fix ")\(bold $filename) \471 ..\(yellow "(press ctrl+c to exit)...")472 ")474 try:475 $files = [: for $ in (1 to #$lessons): add (read file (filename of $))]476 repeat:477 sleep for 0.5 seconds478 $new_files = [: for $ in (1 to #$lessons): add (read file (filename of $))]479 if ($new_files != $files):480 $files = $new_files481 stop482 ..if it fails:483 say "\nGoodbye."484 exit485 ..else:486 ### If the user is using $EDITOR, launch it so they can edit the file:487 $filename = (filename of $failures.1.lesson_number)488 --- (retry file) ---489 when (ask "Edit \$filename to get it to pass? [Y/n/exit] ") is:490 "q" "quit" "exit" "n" "N" "no": exit491 "c":492 write "# cheater!\n" to file $filename494 "y" "Y" "yes" "":495 $f = (read file $filename)496 [$line, $col] = ($failures.1.failure, match ":(%d+),(%d+)")497 if ($line and $col):498 when:499 ($EDITOR, matches "vim$"):500 sh> "\$EDITOR \$filename '+call cursor(\$line,\$col)'"502 ($EDITOR, matches "nano$"):503 sh> "\$EDITOR +\$line,\$col \$filename"505 ($EDITOR, matches "emacs$"):506 sh> "\$EDITOR +\$line:\$col \$filename"508 else:509 sh> "\$EDITOR \$filename"510 ..else:511 sh> "\$EDITOR \$filename"513 else:514 say "Sorry, I don't understand that."515 go to (retry file)517 try:518 run lesson $failures.1.lesson_number519 ..if it fails with $msg:520 $failures.1.failure = $msg521 say (bold (red "\n There's a bit more to fix:"))522 $msg = ($msg, with "\n *stack traceback:.*" -> "")523 say ($msg, indented)524 say525 go to (retry file)527 $prev_progress = ((#$lessons - #$failures) / #$lessons)528 $failures = (get failures)529 $progress = ((#$lessons - #$failures) / #$lessons)531 ### Update the progressbar if progess has changed:532 if ($progress != $prev_progress):533 if ($progress > $prev_progress):534 say (bold (green "\nSuccess!\n"))535 ..else:536 say (bold (red "\nUh oh, that broke something.\n"))538 if $(COLOR ENABLED):539 $N = 100540 for $ in (0 to $N):541 $k = (($ / $N) smoothed by 2)542 $p = ($prev_progress to $progress mixed by $k)543 say "\r\(bold "Your progress:") \(20 wide $p progress bar)" inline544 $io.flush()545 sleep for (1 / $N) seconds546 ..else:547 say ((#$lessons - #$failures) / #$lessons progress bar)549 say551 ### All done, no more failures:552 say ("554 \(bold "\(slow blink "Congratulations!")")556 You've passed the tutorial!558 \\(^\("ᴗ" if $(COLOR ENABLED) else "_")^)/561 ")