diff --git a/Makefile b/Makefile index ad6a9a5..0da53ea 100644 --- a/Makefile +++ b/Makefile @@ -14,11 +14,11 @@ UNINSTALL_VERSION= MOON_FILES= code_obj.moon error_handling.moon files.moon nomsu.moon nomsu_compiler.moon \ syntax_tree.moon containers.moon bitops.moon parser.moon pretty_errors.moon \ text.moon nomsu_decompiler.moon nomsu_environment.moon bootstrap.moon \ - builtin_metatables.moon + builtin_metatables.moon colors.moon LUA_FILES= code_obj.lua error_handling.lua files.lua nomsu.lua nomsu_compiler.lua \ syntax_tree.lua containers.lua bitops.lua parser.lua pretty_errors.lua \ text.lua nomsu_decompiler.lua nomsu_environment.lua bootstrap.lua \ - builtin_metatables.lua + builtin_metatables.lua colors.lua CORE_NOM_FILES=$(shell cat lib/core/init.nom | sed -n 's;export "\(.*\)";lib/\1.nom;p') lib/core/init.nom CORE_LUA_FILES= $(patsubst %.nom,%.lua, $(CORE_NOM_FILES)) COMPAT_NOM_FILES=$(wildcard lib/compatibility/*.nom) diff --git a/error_handling.lua b/error_handling.lua index fd6572e..b7d5d13 100644 --- a/error_handling.lua +++ b/error_handling.lua @@ -1,14 +1,7 @@ local debug_getinfo = debug.getinfo local Files = require("files") +local C = require("colors") local pretty_error = require("pretty_errors") -local RED = "\027[31m" -local BRIGHT_RED = "\027[31;1m" -local RESET = "\027[0m" -local YELLOW = "\027[33m" -local CYAN = "\027[36m" -local GREEN = "\027[32m" -local BLUE = "\027[34m" -local DIM = "\027[37;2m" local ok, to_lua = pcall(function() return require('moonscript.base').to_lua end) @@ -66,10 +59,10 @@ debug.getinfo = function(thread, f, what) end local enhance_error enhance_error = function(error_message, start_fn, stop_fn) - if not (error_message and error_message:match("\x1b")) then + if not (error_message and error_message:match("%d|")) then error_message = error_message or "" do - local fn = error_message:match("attempt to call a nil value %(global '(.*)'%)") + local fn = (error_message:match("attempt to call a nil value %(global '(.*)'%)") or error_message:match("attempt to call global '(.*)' %(a nil value%)")) if fn then error_message = "The action '" .. tostring(fn:from_lua_id()) .. "' is not defined." end @@ -130,7 +123,7 @@ enhance_error = function(error_message, start_fn, stop_fn) end end local ret = { - tostring(RED) .. "ERROR: " .. tostring(BRIGHT_RED) .. tostring(error_message or "") .. tostring(RESET), + C('bold red', error_message or "Error"), "stack traceback:" } local level = 2 @@ -181,10 +174,10 @@ enhance_error = function(error_message, start_fn, stop_fn) do local err_line = lines[calling_fn.currentline] if err_line then - local offending_statement = tostring(BRIGHT_RED) .. tostring(err_line:match("^[ ]*(.*)")) .. tostring(RESET) - line = tostring(YELLOW) .. tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name) .. "\n " .. tostring(offending_statement) .. tostring(RESET) + local offending_statement = C('bright red', err_line:match("^[ ]*(.*)")) + line = C('yellow', tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name) .. "\n " .. tostring(offending_statement)) else - line = tostring(YELLOW) .. tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name) .. tostring(RESET) + line = C('yellow', tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name)) end end else @@ -236,20 +229,20 @@ enhance_error = function(error_message, start_fn, stop_fn) if file and (calling_fn.short_src:match("%.moon$") or file:match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' then local char = MOON_SOURCE_MAP[file][calling_fn.currentline] line_num = file:line_number_at(char) - line = tostring(CYAN) .. tostring(calling_fn.short_src) .. ":" .. tostring(line_num) .. " in " .. tostring(name or '?') .. tostring(RESET) + line = C('cyan', tostring(calling_fn.short_src) .. ":" .. tostring(line_num) .. " in " .. tostring(name or '?')) else line_num = calling_fn.currentline if calling_fn.short_src == '[C]' then - line = tostring(GREEN) .. tostring(calling_fn.short_src) .. " in " .. tostring(name or '?') .. tostring(RESET) + line = C('green', tostring(calling_fn.short_src) .. " in " .. tostring(name or '?')) else - line = tostring(BLUE) .. tostring(calling_fn.short_src) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name or '?') .. tostring(RESET) + line = C('blue', tostring(calling_fn.short_src) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name or '?')) end end if file then do local err_line = lines[line_num] if err_line then - local offending_statement = tostring(BRIGHT_RED) .. tostring(err_line:match("^[ ]*(.*)$")) .. tostring(RESET) + local offending_statement = C('bright red', tostring(err_line:match("^[ ]*(.*)$"))) line = line .. ("\n " .. offending_statement) end end @@ -258,7 +251,7 @@ enhance_error = function(error_message, start_fn, stop_fn) end table.insert(ret, line) if calling_fn.istailcall then - table.insert(ret, " " .. tostring(DIM) .. "(...tail calls...)" .. tostring(RESET)) + table.insert(ret, C('dim', " (...tail calls...)")) end _continue_0 = true until true diff --git a/error_handling.moon b/error_handling.moon index 18ae4ef..ebd8309 100644 --- a/error_handling.moon +++ b/error_handling.moon @@ -1,18 +1,10 @@ -- This file contains the logic for making nicer error messages debug_getinfo = debug.getinfo Files = require "files" +C = require "colors" pretty_error = require("pretty_errors") export SOURCE_MAP -RED = "\027[31m" -BRIGHT_RED = "\027[31;1m" -RESET = "\027[0m" -YELLOW = "\027[33m" -CYAN = "\027[36m" -GREEN = "\027[32m" -BLUE = "\027[34m" -DIM = "\027[37;2m" - ok, to_lua = pcall -> require('moonscript.base').to_lua if not ok then to_lua = -> nil MOON_SOURCE_MAP = setmetatable {}, @@ -47,9 +39,11 @@ debug.getinfo = (thread,f,what)-> return info enhance_error = (error_message, start_fn, stop_fn)-> - unless error_message and error_message\match("\x1b") + -- Hacky: detect the line numbering + unless error_message and error_message\match("%d|") error_message or= "" - if fn = error_message\match("attempt to call a nil value %(global '(.*)'%)") + if fn = (error_message\match("attempt to call a nil value %(global '(.*)'%)") or + error_message\match("attempt to call global '(.*)' %(a nil value%)")) error_message = "The action '#{fn\from_lua_id!}' is not defined." level = 2 while true @@ -91,7 +85,7 @@ enhance_error = (error_message, start_fn, stop_fn)-> ret = { - "#{RED}ERROR: #{BRIGHT_RED}#{error_message or ""}#{RESET}" + C('bold red', error_message or "Error") "stack traceback:" } @@ -125,10 +119,10 @@ enhance_error = (error_message, start_fn, stop_fn)-> file = Files.read(filename) lines = file and file\lines! or {} if err_line = lines[calling_fn.currentline] - offending_statement = "#{BRIGHT_RED}#{err_line\match("^[ ]*(.*)")}#{RESET}" - line = "#{YELLOW}#{filename}:#{calling_fn.currentline} in #{name}\n #{offending_statement}#{RESET}" + offending_statement = C('bright red', err_line\match("^[ ]*(.*)")) + line = C('yellow', "#{filename}:#{calling_fn.currentline} in #{name}\n #{offending_statement}") else - line = "#{YELLOW}#{filename}:#{calling_fn.currentline} in #{name}#{RESET}" + line = C('yellow', "#{filename}:#{calling_fn.currentline} in #{name}") else local line_num if name == nil @@ -161,21 +155,21 @@ enhance_error = (error_message, start_fn, stop_fn)-> if file and (calling_fn.short_src\match("%.moon$") or file\match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' char = MOON_SOURCE_MAP[file][calling_fn.currentline] line_num = file\line_number_at(char) - line = "#{CYAN}#{calling_fn.short_src}:#{line_num} in #{name or '?'}#{RESET}" + line = C('cyan', "#{calling_fn.short_src}:#{line_num} in #{name or '?'}") else line_num = calling_fn.currentline if calling_fn.short_src == '[C]' - line = "#{GREEN}#{calling_fn.short_src} in #{name or '?'}#{RESET}" + line = C('green', "#{calling_fn.short_src} in #{name or '?'}") else - line = "#{BLUE}#{calling_fn.short_src}:#{calling_fn.currentline} in #{name or '?'}#{RESET}" + line = C('blue', "#{calling_fn.short_src}:#{calling_fn.currentline} in #{name or '?'}") if file if err_line = lines[line_num] - offending_statement = "#{BRIGHT_RED}#{err_line\match("^[ ]*(.*)$")}#{RESET}" + offending_statement = C('bright red', "#{err_line\match("^[ ]*(.*)$")}") line ..= "\n "..offending_statement table.insert ret, line if calling_fn.istailcall - table.insert ret, " #{DIM}(...tail calls...)#{RESET}" + table.insert ret, C('dim', " (...tail calls...)") return table.concat(ret, "\n") diff --git a/lib/consolecolor/init.nom b/lib/consolecolor/init.nom index 27823b8..606f4b6 100644 --- a/lib/consolecolor/init.nom +++ b/lib/consolecolor/init.nom @@ -17,9 +17,12 @@ $colors = { } for $name = $colornum in $colors: - $colornum = "\$colornum" - \($name \$text) compiles to: - if $text: - return (Lua "('\\027[\($colornum)m'..\($text as lua expr)..'\\027[0m')") - ..else: - return (Lua "'\\027[\($colornum)m'") + (nomsu environment).($name, as lua id) = + for $text: + if $(COLOR ENABLED): + if $text: + return "\x1B[\($colornum)m\$text\x1b[0m" + ..else: + return "\x1B[\($colornum)m" + ..else: + return ($text or "") diff --git a/lib/core/errors.nom b/lib/core/errors.nom index b1f1941..6be7e7f 100644 --- a/lib/core/errors.nom +++ b/lib/core/errors.nom @@ -75,6 +75,7 @@ use "core/control_flow" (assume $a == $b) parses as (assume ($a == $b)) (assume $a != $b) parses as (assume ($a != $b)) +(test that $condition) parses as (assume $condition) test: try: fail diff --git a/lib/progressbar/init.nom b/lib/progressbar/init.nom index 98efe11..b3fb3a2 100644 --- a/lib/progressbar/init.nom +++ b/lib/progressbar/init.nom @@ -1,12 +1,25 @@ #!/usr/bin/env nomsu -V6.15.13.8 # A progress bar - +use "consolecolor" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + assume (0 / 1 progress bar) + assume (10 wide 0.5 progress bar) + external: ($x / $w progress bar) means: $x = ($x clamped between 0 and $w) - $bits = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"] - $middle = ("" if ($x == $w) else $bits.(1 + (floor ((#$bits) * ($x mod 1))))) - return (" - \027[0m[\027[32;40m\($bits, last, rep (floor $x))\$middle\(" ", rep ($w - ((floor $x) + 1)))\027[0m] - ") + if $(COLOR ENABLED): + $bits = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"] + $middle = ("" if ($x == $w) else $bits.(1 + (floor ((#$bits) * ($x mod 1))))) + return (" + \(reset color)[\(green)\($bits, last, rep (floor $x))\$middle\(" ", rep ($w - ((floor $x) + 1)))\(reset color)] + ") + ..else: + # Probably not unicode support either: + return (" + [\("#", rep ($x, rounded down))\("-", rep ($w - ($x, rounded down)))] + ") ($w wide $ progress bar) means (($ * $w) / $w progress bar) diff --git a/lib/tools/repl.nom b/lib/tools/repl.nom index 3d2318c..029df4a 100755 --- a/lib/tools/repl.nom +++ b/lib/tools/repl.nom @@ -52,7 +52,8 @@ command line program with $args: if (($line == "\n") or (not $line)): if ((size of $buff) > 0): # clear the line - say "\027[1A\027[2K" inline + if $(COLOR ENABLED): + say "\027[1A\027[2K" inline go to (run buffer) $buff, add ($line, with "\t" -> " ") say (dim (yellow ".. ")) inline diff --git a/lib/tools/tutorial.nom b/lib/tools/tutorial.nom index 7c7d39c..dd26ada 100755 --- a/lib/tools/tutorial.nom +++ b/lib/tools/tutorial.nom @@ -25,11 +25,11 @@ $lessons = [ lesson "Variables": # In Nomsu, variables have a "$" prefix, and you can just assign to them without declaring them first: - $x = 10 - assume ($x == 10) + $x = 1 + test that ($x == 1) # Variables which have not yet been set have the value (nil) - assume ($foobar == (nil)) + test that ($foobar == (nil)) # Variables can be nameless: $ = 99 @@ -40,16 +40,15 @@ $lessons = [ # Figure out what value $my_var should have: $my_var = 100 $my_var = ($my_var + $x + $(my favorite number)) - assume ($my_var == (???)) + test that ($my_var == (???)) lesson "Actions": - # Fix this action so the tests pass, then save and quit. - # If the tests don't pass, you can come back to this file later. + # Fix this action so the tests pass: ($x doubled) means ((???) * $x) # Tests: - assume ((2 doubled) == 4) - assume ((-5 doubled) == -10) + test that ((2 doubled) == 4) + test that ((-5 doubled) == -10) lesson "Blocks": # When you need to do multiple things inside an action, use a block. @@ -64,6 +63,32 @@ $lessons = [ $correct_answer = (4 * ($num * $num)) if (($num doubled then squared) != $correct_answer): fail "Wrong answer for \($num)!" + + lesson "Text": + # Nomsu text is enclosed in double quotation marks: + $text = "Hello" + + # You can insert values into text using a backslash: + test that ("two plus three is \(2 + 3)" == (???)) + + # Variables don't require parentheses, but other expressions do: + $x = 99 + test that ("$x is \$x" == (???)) + + # This can be used to convert values to text: + test that ("\$x" == (???)) + + # Longer strings use '("' followed by an indented region: + $long = (" + line one + line two with spaces at the front + ") + + test that + $long == (" + \() + \() + ") lesson "Conditionals": # Make this action return "big" if its argument @@ -75,11 +100,11 @@ $lessons = [ # Tests: - for $small_number in [0, 1, -5, -999, 99]: - assume ((the size of $small_number) == "small") - for $big_number in [9999, 100]: - assume ((the size of $big_number) == "big") + test that ((the size of $big_number) == "big") + + for $small_number in [0, 1, -5, -999, 99]: + test that ((the size of $small_number) == "small") lesson "Loops": # Fix this action so the tests pass: @@ -93,14 +118,14 @@ $lessons = [ return $sum # Tests: - assume ((the sum of [1, 2, 3, 4, 5]) == 15) - assume ((the sum of [100, 200]) == 300) + test that ((the sum of [1, 2, 3, 4, 5]) == 15) + test that ((the sum of [100, 200]) == 300) # You can also loop over a number range like this: $total = 0 for $i in 1 to 3: $total = ($total + $i) - assume ($total == (???)) + test that ($total == (???)) lesson "Variable Scopes": # Nomsu's variables are local by default, and actions have their own scopes: @@ -111,17 +136,17 @@ $lessons = [ (do something) means: # The variable $y is never set in this action, so it has the same value it has outside this action. - assume ($y == (???)) + test that ($y == (???)) # $x is set inside this action, and actions have their own scopes. $x = $y # What number should $x be here? - assume ($x == (???)) + test that ($x == (???)) # After running the action, what value should $x have? do something - assume ($x == (???)) + test that ($x == (???)) lesson "More Variable Scopes": # Loops and conditionals do *not* have their own scopes: @@ -131,13 +156,13 @@ $lessons = [ $z = 2 # After assigning in a conditional, what should $z be? - assume ($z == (???)) + test that ($z == (???)) for $ in 1 to 1: # Set $z inside a loop: $z = 3 # After assigning in a loop, what should $z be? - assume ($z == (???)) + test that ($z == (???)) lesson "Externals": # The 'external' block lets you modify variables outside an action: @@ -147,7 +172,7 @@ $lessons = [ do something # After running the action that sets $x in an 'external' block, what should $x be? - assume ($x == (???)) + test that ($x == (???)) lesson "Locals": # The 'with' block lets you create a local scope for the variables you list: @@ -158,8 +183,8 @@ $lessons = [ $z = 2 # After setting $y and $z in the 'with [$y]' block, what should $y and $z be? - assume ($y == (???)) - assume ($z == (???)) + test that ($y == (???)) + test that ($z == (???)) lesson "Failure and Recovery": $what_happened = "nothing" @@ -174,7 +199,7 @@ $lessons = [ $what_happened = "success" # What do you think happened? - assume ($what_happened == (???)) + test that ($what_happened == (???)) # Note: a 'try' block will silence failures, so this has no effect: try: fail @@ -182,11 +207,11 @@ $lessons = [ lesson "Indexing": # Nomsu uses the "." operator to access things inside an object: $dictionary = {.dog = "A lovable doofus", .cat = "An internet superstar"} - assume ($dictionary.dog == "A lovable doofus") - assume ($dictionary.cat == (???)) + test that ($dictionary.dog == "A lovable doofus") + test that ($dictionary.cat == (???)) # If you try to access a key that's not in an object, the result is (nil): - assume ($dictionary.mimsy == (???)) + test that ($dictionary.mimsy == (???)) # $dictionary.dog is just a shorthand for $dictionary."dog". You may need to use the longer form for strings with spaces: @@ -197,22 +222,22 @@ $lessons = [ $dictionary.5 = "The number five" $dictionary.five = 5 $dictionary.myself = $dictionary - assume ($dictionary.myself == (???)) + test that ($dictionary.myself == (???)) # Lists are similar, but use square brackets ([]) and can only have numbers as keys, starting at 1: $list = ["first", "second", 999] - assume ($list.1 == "first") - assume ($list.2 == (???)) - assume ($list.3 == (???)) + test that ($list.1 == "first") + test that ($list.2 == (???)) + test that ($list.3 == (???)) # Hint: 4 should be a missing key - assume ($list.4 == (???)) - assume ($list.foobar == (???)) + test that ($list.4 == (???)) + test that ($list.foobar == (???)) # The "#" action gets the number of items inside something: - assume ((#$list) == (???)) - assume ((#{.x = 10, .y = 20}) == (???)) + test that ((#$list) == (???)) + test that ((#{.x = 10, .y = 20}) == (???)) lesson "Methods": # The "," is used for method calls, which means calling an action @@ -220,17 +245,17 @@ $lessons = [ # Lists have an "add" method that puts new items at the end: $list = [-4, -6, 5] $list, add 3 - assume ($list == [-4, -6, 5, 3]) + test that ($list == [-4, -6, 5, 3]) $list, add 7 - assume ($list == [???]) + test that ($list == [???]) # Text also has some methods like: $name = "Harry Tuttle" - assume (($name, character 7) == "T") - assume (($name, with "Tuttle" -> "Buttle") == (???)) + test that (($name, from 7 to 12) == "Tuttle") + test that (($name, with "Tuttle" -> "Buttle") == (???)) # Methods can be chained too: - assume (($name, with "Tuttle" -> "Buttle", character 7) == (???)) + test that (($name, with "Tuttle" -> "Buttle", from 7 to 12) == (???)) lesson "Object Oriented Programming": # Object Oriented Programming deals with things that have @@ -246,13 +271,30 @@ $lessons = [ ($self, add $bit) means: $bits, add $bit - # Write a method called ($self, length) that returns the total - length of all the bits in the buffer: + # Write a method called ($self, length) that returns the sum + of the lengths of each bit in the buffer: $b = (a Buffer) $b, add "xx" $b, add "yyy" - assume (($b, length) == 5) + test that (($b, length) == 5) + + lesson "Files Part 1": + # Define an external action here: + external: + # These will be used in the next lesson + $foobar = 23 + ($x tripled) means: + + test that ((5 tripled) == 15) + test that ((2 tripled) == 6) + + lesson "Files Part 2": + # 'use' is the action for importing from other files + # It takes the path to the file (without the .nom extension): + use () + test that ((10 tripled) == (???)) + test that ($foobar == (???)) ] $(ask normally) = $(ask) @@ -282,14 +324,12 @@ command line program with $args: unless ($Files.exists $tutorial_dir): when ask (" - The Nomsu tutorial files will be in \$tutorial_dir is that okay? [Y/n] \; + The Nomsu tutorial files will be in \$tutorial_dir is that okay? [Y/n/exit] \; ") ..is: "n" "N" "no": - say (" - Okay. If you want to specify another file location, run `nomsu -t tutorial ` - ") - exit + $tutorial_dir = (ask "Where do you want to put the tutorial? ") + "exit" "quit" "q" "e": exit ..else: $tutorial_dir = $args.extras.1 @@ -300,7 +340,10 @@ command line program with $args: for $lesson in $lessons at $i: $filename = (filename of $i) unless ($Files.exists $filename): - write $lesson.lesson to file $filename + $lesson_text = + $lesson.lesson, with "%(%)" -> + "\"\(filename of ($i - 1), with "%.nom$" -> "", with "^%./" -> "")\"" + write $lesson_text to file $filename # Display info about editing the tutorial files: if $args.x: @@ -328,14 +371,23 @@ command line program with $args: > \; ") if ($EDITOR == ""): $EDITOR = (nil) + + (run lesson $i) means: + $filename = (filename of $i) + $file = (read file $filename) + $file = ($NomsuCode, from (Source $filename 1 (#$file)) $file) + $tree = ($file parsed) + $tree = + $tree with $ ->: + if ($ == \()): + return ("Text" tree with (filename of ($i - 1), with "%.nom$" -> "")) + run $tree + (get failures) means [ : for $lesson in $lessons at $i: - $filename = (filename of $i) - $file = (read file $filename) - $file = ($NomsuCode, from (Source $filename 1 (#$file)) $file) try: - run $file + run lesson $i ..if it fails with $msg: $msg = ($msg, with "\n *stack traceback:.*" -> "") add {.lesson_number = $i, .failure = $msg} @@ -360,17 +412,21 @@ command line program with $args: for $lesson in $lessons at $i: for $f in $failures: if ($f.lesson_number == $i): - say (bold (red " \$i. \($lesson.name) [incomplete]")) + say "\(red " - ")\(bold (red "\$i. \($lesson.name) [incomplete]"))" do next $lesson - say (bold (green " \$i. \($lesson.name) [passed]")) + say "\(green " + ")\(bold (green "\$i. \($lesson.name) [passed]"))" - say (" + if $(COLOR ENABLED): + say (" + + \(bold "Your progress:") \( + 20 wide (((#$lessons) - (#$failures)) / (#$lessons)) progress bar + ) + ") + ..else: + say + say (((#$lessons) - (#$failures)) / (#$lessons) progress bar) - \(bold "Your progress:") \( - 20 wide (((#$lessons) - (#$failures)) / (#$lessons)) progress bar - ) - ") - repeat until ($failures is empty): show first failure from $failures @@ -400,6 +456,8 @@ command line program with $args: --- (retry file) --- when (ask "Edit \$filename to get it to pass? [Y/n/exit] ") is: "q" "quit" "exit" "n" "N" "no": exit + "c": + write "# cheater!\n" to file $filename "y" "Y" "yes" "": $f = (read file $filename) [$line, $col] = ($failures.1.failure, match ":(%d+),(%d+)") @@ -422,10 +480,8 @@ command line program with $args: else: say "Sorry, I don't understand that." go to (retry file) - $file = (read file $filename) - $file = ($NomsuCode, from (Source $filename 1 (#$file)) $file) try: - run $file + run lesson $failures.1.lesson_number ..if it fails with $msg: $failures.1.failure = $msg say (bold (red "\n There's a bit more to fix:")) @@ -443,13 +499,16 @@ command line program with $args: say (bold (green "\nSuccess!\n")) ..else: say (bold (red "\nUh oh, that broke something.\n")) - $N = 100 - for $ in 0 to $N: - $k = (($ / $N) smoothed by 2) - $p = ($prev_progress to $progress mixed by $k) - say "\r\(bold "Your progress:") \(20 wide $p progress bar)" inline - $io.flush() - sleep for (1 / $N) seconds + if $(COLOR ENABLED): + $N = 100 + for $ in 0 to $N: + $k = (($ / $N) smoothed by 2) + $p = ($prev_progress to $progress mixed by $k) + say "\r\(bold "Your progress:") \(20 wide $p progress bar)" inline + $io.flush() + sleep for (1 / $N) seconds + ..else: + say (((#$lessons) - (#$failures)) / (#$lessons) progress bar) say # All done, no more failures: @@ -459,7 +518,7 @@ command line program with $args: You've passed the tutorial! - \\(^ᴗ^)/ + \\(^\("ᴗ" if $(COLOR ENABLED) else "_")^)/ ") diff --git a/nomsu.lua b/nomsu.lua index dc5b97f..21e4af2 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -1,16 +1,17 @@ local clibtype = package.cpath:match("?%.(so)") or package.cpath:match("?%.(dll)") +COLOR_ENABLED = true if clibtype == "dll" then + local enable_colors = require('wincolors') + local ok, _ = pcall(enable_colors) + if not ok then + COLOR_ENABLED = false + end os.execute("chcp 65001>nul") end if NOMSU_VERSION and NOMSU_PREFIX then package.path = tostring(NOMSU_PREFIX) .. "/share/nomsu/" .. tostring(NOMSU_VERSION) .. "/?.lua;" .. package.path package.cpath = tostring(NOMSU_PREFIX) .. "/lib/nomsu/" .. tostring(NOMSU_VERSION) .. "/?." .. tostring(clibtype) .. ";" .. package.cpath end -local EXIT_SUCCESS, EXIT_FAILURE = 0, 1 -if _VERSION == "Lua 5.1" and not jit then - print("Sorry, Nomsu does not run on Lua 5.1. Please use LuaJIT 2+ or Lua 5.2+") - os.exit(EXIT_FAILURE) -end local usage = [=[Nomsu Compiler Usage: (nomsu | lua nomsu.lua | moon nomsu.moon) [-V version] [--help | -h] [--version] [-O optimization level] [-v] [-c] [-s] [-d debugger] [--no-core] [(file | -t tool | -e "nomsu code..." | files... -- ) [nomsu args...]] @@ -92,7 +93,7 @@ local arg_string = table.concat(arg, sep) .. sep local args, err = parser:match(arg_string) if not args or err or args.help then if err then - print("Didn't understand: \x1b[31;1m" .. tostring(err) .. "\x1b[0m") + print("Didn't understand: " .. tostring(err)) end print(usage) os.exit(EXIT_FAILURE) @@ -142,6 +143,7 @@ nomsu_environment.COMMAND_LINE_ARGS = nomsu_args nomsu_environment.OPTIMIZATION = optimization nomsu_environment.NOMSU_PACKAGEPATH = NOMSU_PACKAGEPATH nomsu_environment.NOMSU_PREFIX = NOMSU_PREFIX +nomsu_environment.COLOR_ENABLED = COLOR_ENABLED local run run = function() if not (args.no_core) then diff --git a/nomsu.moon b/nomsu.moon index 240d457..96053eb 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -2,7 +2,14 @@ -- This file contains the command-line Nomsu runner. clibtype = package.cpath\match("?%.(so)") or package.cpath\match("?%.(dll)") +export COLOR_ENABLED +COLOR_ENABLED = true if clibtype == "dll" + -- Enable colors: + enable_colors = require('wincolors') + ok,_ = pcall(enable_colors) + if not ok + COLOR_ENABLED = false -- Special hack to enable utf8 for windows console applications: os.execute("chcp 65001>nul") @@ -10,13 +17,6 @@ if NOMSU_VERSION and NOMSU_PREFIX package.path = "#{NOMSU_PREFIX}/share/nomsu/#{NOMSU_VERSION}/?.lua;"..package.path package.cpath = "#{NOMSU_PREFIX}/lib/nomsu/#{NOMSU_VERSION}/?.#{clibtype};"..package.cpath -EXIT_SUCCESS, EXIT_FAILURE = 0, 1 - -if _VERSION == "Lua 5.1" and not jit - -- Cannot run on Lua5.1 because it doesn't have gotos. - print("Sorry, Nomsu does not run on Lua 5.1. Please use LuaJIT 2+ or Lua 5.2+") - os.exit(EXIT_FAILURE) - usage = [=[ Nomsu Compiler @@ -91,7 +91,7 @@ arg_string = table.concat(arg, sep)..sep args, err = parser\match(arg_string) if not args or err or args.help if err - print("Didn't understand: \x1b[31;1m#{err}\x1b[0m") + print("Didn't understand: #{err}") print usage os.exit(EXIT_FAILURE) nomsu_args = Dict{} @@ -121,6 +121,7 @@ nomsu_environment.COMMAND_LINE_ARGS = nomsu_args nomsu_environment.OPTIMIZATION = optimization nomsu_environment.NOMSU_PACKAGEPATH = NOMSU_PACKAGEPATH nomsu_environment.NOMSU_PREFIX = NOMSU_PREFIX +nomsu_environment.COLOR_ENABLED = COLOR_ENABLED run = -> unless args.no_core diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua index 0cedd50..c9a15db 100644 --- a/nomsu_compiler.lua +++ b/nomsu_compiler.lua @@ -9,6 +9,7 @@ do local _obj_0 = require("code_obj") LuaCode, Source = _obj_0.LuaCode, _obj_0.Source end +require("text") local SyntaxTree = require("syntax_tree") local Files = require("files") local pretty_error = require("pretty_errors") @@ -20,6 +21,17 @@ fail_at = function(source, msg) source = source.source elseif type(source) == 'string' then source = Source:from_string(source) + else + assert(source.short_src and source.currentline) + file = Files.read(source.short_src) + assert(file, "Could not find " .. tostring(source.short_src)) + local lines = file:lines() + local start = 1 + for i = 1, source.currentline - 1 do + start = start + #lines[i] + end + local stop = start + #lines[source.currentline] + source = Source(source.short_src, start, stop) end if source and not file then file = Files.read(source.filename) diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index 3993490..73cc5a8 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -4,6 +4,7 @@ unpack or= table.unpack {:match, :sub, :gsub, :format, :byte, :find} = string {:LuaCode, :Source} = require "code_obj" +require "text" SyntaxTree = require "syntax_tree" Files = require "files" @@ -15,6 +16,18 @@ fail_at = (source, msg)-> source = source.source elseif type(source) == 'string' source = Source\from_string(source) + else + -- debug.getinfo() output: + assert(source.short_src and source.currentline) + file = Files.read(source.short_src) + assert file, "Could not find #{source.short_src}" + lines = file\lines! + start = 1 + for i=1,source.currentline-1 + start += #lines[i] + stop = start + #lines[source.currentline] + source = Source(source.short_src, start, stop) + if source and not file file = Files.read(source.filename) diff --git a/nomsu_environment.lua b/nomsu_environment.lua index e359257..5ea6a62 100644 --- a/nomsu_environment.lua +++ b/nomsu_environment.lua @@ -12,6 +12,7 @@ local Text = require('text') local SyntaxTree = require("syntax_tree") local Files = require("files") local Errhand = require("error_handling") +local C = require("colors") local make_parser = require("parser") local pretty_error = require("pretty_errors") local make_tree @@ -65,7 +66,6 @@ do local _obj_0 = require('nomsu_compiler') compile, fail_at = _obj_0.compile, _obj_0.fail_at end -local _currently_running_files = List({ }) local _module_imports = { } local _importer_mt = { __index = function(self, k) @@ -225,7 +225,7 @@ nomsu_environment = Importer({ err_strings = _accum_0 end if num_errs > #err_strings then - table.insert(err_strings, "\027[31;1m +" .. tostring(num_errs - #err_strings) .. " additional errors...\027[0m\n") + table.insert(err_strings, C("bright red", " +" .. tostring(num_errs - #err_strings) .. " additional errors...\n")) end error(table.concat(err_strings, '\n\n'), 0) end @@ -249,11 +249,27 @@ nomsu_environment = Importer({ return ret end end - if _currently_running_files:has(path) then - local i = _currently_running_files:index_of(path) - _currently_running_files:add(path) - local circle = _currently_running_files:from_1_to(i, -1) - error("Circular import detected:\n " .. circle:joined_with("\n..imports ")) + local currently_running = { } + for i = 2, 999 do + local info = debug.getinfo(i, 'f') + if not (info.func) then + break + end + if info.func == self.Module then + local n, upper_path = debug.getlocal(i, 3) + table.insert(currently_running, upper_path) + assert(n == "path") + if upper_path == path then + local circle = table.concat(currently_running, "', which imports '") + local err_i = 2 + info = debug.getinfo(err_i) + while info and (info.func == self.Module or info.func == self.use or info.func == self.export) do + err_i = err_i + 1 + info = debug.getinfo(err_i) + end + fail_at((info or debug.getinfo(2)), "Circular import: File '" .. tostring(path) .. "' imports '" .. circle .. "'") + end + end end local mod = self:new_environment() mod.MODULE_NAME = package_name @@ -263,12 +279,10 @@ nomsu_environment = Importer({ else code = NomsuCode:from(Source(path, 1, #code), code) end - _currently_running_files:add(path) local ret = mod:run(code) if ret ~= nil then mod = ret end - _currently_running_files:pop() package.nomsuloaded[package_name] = mod package.nomsuloaded[path] = mod return mod @@ -360,7 +374,7 @@ nomsu_environment = Importer({ lines = _accum_0 end local line_numbered_lua = table.concat(lines, "\n") - error("Failed to compile generated code:\n\027[1;34m" .. tostring(line_numbered_lua) .. "\027[0m\n\n" .. tostring(err), 0) + error("Failed to compile generated code:\n" .. tostring(C("bright blue", line_numbered_lua)) .. "\n\n" .. tostring(err), 0) end local source_key = tostring(source) if not (self.SOURCE_MAP[source_key] or self.OPTIMIZATION >= 2) then diff --git a/nomsu_environment.moon b/nomsu_environment.moon index 1deac6f..d572f8c 100644 --- a/nomsu_environment.moon +++ b/nomsu_environment.moon @@ -6,6 +6,7 @@ Text = require 'text' SyntaxTree = require "syntax_tree" Files = require "files" Errhand = require "error_handling" +C = require "colors" make_parser = require("parser") pretty_error = require("pretty_errors") @@ -35,7 +36,6 @@ for version=1,999 {:tree_to_nomsu, :tree_to_inline_nomsu} = require "nomsu_decompiler" {:compile, :fail_at} = require('nomsu_compiler') -_currently_running_files = List{} -- Used to check for circular imports in run_file_1_in _module_imports = {} _importer_mt = {__index: (k)=> _module_imports[@][k]} Importer = (t, imports)-> @@ -108,12 +108,13 @@ nomsu_environment = Importer{ start:e.source.start, stop:e.source.stop, filename:e.source.filename } for i, e in ipairs(errs) when i <= 3] if num_errs > #err_strings - table.insert(err_strings, "\027[31;1m +#{num_errs-#err_strings} additional errors...\027[0m\n") + table.insert(err_strings, C("bright red", " +#{num_errs-#err_strings} additional errors...\n")) error(table.concat(err_strings, '\n\n'), 0) return tree Module: (package_name)=> + -- This *must* be the first local local path if package_name\match("%.nom$") or package_name\match("%.lua") path = package_name @@ -125,11 +126,26 @@ nomsu_environment = Importer{ if ret = package.nomsuloaded[package_name] or package.nomsuloaded[path] return ret - if _currently_running_files\has(path) - i = _currently_running_files\index_of(path) - _currently_running_files\add path - circle = _currently_running_files\from_1_to(i, -1) - error("Circular import detected:\n "..circle\joined_with("\n..imports ")) + -- Traverse up the callstack to look for import loops + -- This is more reliable than keeping a list of running files, since + -- that gets messed up when errors occur. + currently_running = {} + for i=2,999 + info = debug.getinfo(i, 'f') + break unless info.func + if info.func == @Module + n, upper_path = debug.getlocal(i, 3) -- 3 means "path" + table.insert(currently_running, upper_path) + assert(n == "path") + if upper_path == path + --circle = "\n "..table.concat(currently_running, "\n..imports ") + circle = table.concat(currently_running, "', which imports '") + err_i = 2 + info = debug.getinfo(err_i) + while info and (info.func == @Module or info.func == @use or info.func == @export) + err_i += 1 + info = debug.getinfo(err_i) + fail_at (info or debug.getinfo(2)), "Circular import: File '#{path}' imports '"..circle.."'" mod = @new_environment! mod.MODULE_NAME = package_name code = Files.read(path) @@ -137,11 +153,9 @@ nomsu_environment = Importer{ code = LuaCode\from(Source(path, 1, #code), code) else code = NomsuCode\from(Source(path, 1, #code), code) - _currently_running_files\add path ret = mod\run(code) if ret != nil mod = ret - _currently_running_files\pop! package.nomsuloaded[package_name] = mod package.nomsuloaded[path] = mod return mod @@ -165,7 +179,6 @@ nomsu_environment = Importer{ imports[k] = v for k,v in pairs(mod) if rawget(@, k) == nil - --if k != "_G" and k != "_ENV" and k != "COMPILE_RULES" and k != "MODULE_NAME" @[k] = v cr_imports = assert _module_imports[@COMPILE_RULES] if mod.COMPILE_RULES @@ -210,7 +223,7 @@ nomsu_environment = Importer{ if not run_lua_fn lines =[("%3d|%s")\format(i,line) for i, line in ipairs lua_string\lines!] line_numbered_lua = table.concat(lines, "\n") - error("Failed to compile generated code:\n\027[1;34m#{line_numbered_lua}\027[0m\n\n#{err}", 0) + error("Failed to compile generated code:\n#{C("bright blue", line_numbered_lua)}\n\n#{err}", 0) source_key = tostring(source) unless @SOURCE_MAP[source_key] or @OPTIMIZATION >= 2 map = {} diff --git a/pretty_errors.lua b/pretty_errors.lua index ecc6cd2..a675f12 100644 --- a/pretty_errors.lua +++ b/pretty_errors.lua @@ -1,5 +1,6 @@ require("containers") local Text = require('text') +local C = require('colors') local box box = function(text) local max_line = 0 @@ -8,7 +9,7 @@ box = function(text) end local ret = ("\n" .. text):gsub("\n([^\n]*)", function(line) local line_len = #(line:gsub("\027%[[0-9;]*m", "")) - return "\n" .. tostring(line) .. tostring((" "):rep(max_line - line_len)) .. " \027[0m" + return "\n" .. tostring(line) .. tostring((" "):rep(max_line - line_len)) .. " " .. C('reset') end) return ret:sub(2, -1), max_line end @@ -20,13 +21,16 @@ format_error = function(err) local nl_indicator = (err_linepos > #err_line) and " " or "" local fmt_str = " %" .. tostring(#tostring(err_linenum + context)) .. "d|" local pointer = (" "):rep(err_linepos + #fmt_str:format(0) - 1) .. ("^"):rep(err_size) - local err_msg = "\027[31;1m" .. tostring(err.title or "Error") .. " at " .. tostring(err.filename or '???') .. ":" .. tostring(err_linenum) .. "," .. tostring(err_linepos) .. "\027[0m" + local err_msg = C('bold red', err.title or "Error") .. C('red', " at " .. tostring(err.filename or '???') .. ":" .. tostring(err_linenum) .. "," .. tostring(err_linepos)) + if not (COLOR_ENABLED) then + err_msg = err_msg .. "\n" + end local lines = err.source:lines() for i = err_linenum - context, err_linenum - 1 do do local line = lines[i] if line then - err_msg = err_msg .. "\n\027[2m" .. tostring(fmt_str:format(i)) .. "\027[0m" .. tostring(line) .. "\027[0m" + err_msg = err_msg .. ("\n" .. C('dim', fmt_str:format(i)) .. line) end end end @@ -34,8 +38,8 @@ format_error = function(err) local before = err_line:sub(1, err_linepos - 1) local during = err_line:sub(err_linepos, err_linepos + err_size - 1) local after = err_line:sub(err_linepos + err_size, -1) - err_line = "\027[0m" .. tostring(before) .. "\027[41;30m" .. tostring(during) .. tostring(nl_indicator) .. "\027[0m" .. tostring(after) - err_msg = err_msg .. "\n\027[2m" .. tostring(fmt_str:format(err_linenum)) .. tostring(err_line) .. "\027[0m" + err_line = before .. C('black on red', during .. nl_indicator) .. after + err_msg = err_msg .. ("\n" .. C('dim', fmt_str:format(err_linenum)) .. err_line) end local _, err_linenum_end, err_linepos_end = err.source:line_info_at(err.stop) err_linenum_end = err_linenum_end or err_linenum @@ -48,9 +52,9 @@ format_error = function(err) if line then if i == err_linenum_end then local during, after = line:sub(1, err_linepos_end - 1), line:sub(err_linepos_end, -1) - err_msg = err_msg .. "\n\027[2m" .. tostring(fmt_str:format(i)) .. "\027[0;41;30m" .. tostring(during) .. "\027[0m" .. tostring(after) + err_msg = err_msg .. ("\n" .. C('dim', fmt_str:format(i)) .. C('black on red', during) .. after) else - err_msg = err_msg .. "\n\027[2m" .. tostring(fmt_str:format(i)) .. "\027[0;41;30m" .. tostring(line) .. "\027[0m" + err_msg = err_msg .. ("\n" .. C('dim', fmt_str:format(i)) .. C('black on red', line)) end end end @@ -61,19 +65,25 @@ format_error = function(err) end end local box_width = 70 - local err_text = "\027[47;30;1m" .. tostring(" " .. err.error:wrapped_to(box_width, 16):gsub("\n", "\n\027[47;30;1m ")) + local err_text = C('black on white', " " .. err.error:wrapped_to(box_width, 16):gsub("\n", "\n" .. C('black on white') .. " ")) if err.hint then - err_text = err_text .. "\n\027[47;30;3m" .. tostring((" Suggestion: " .. tostring(err.hint)):wrapped_to(box_width, 16):gsub("\n", "\n\027[47;30;3m ")) + err_text = err_text .. ("\n" .. C('italic black on white', (" Suggestion: " .. tostring(err.hint)):wrapped_to(box_width, 16):gsub("\n", "\n" .. C('italic black on white') .. " "))) + end + err_msg = err_msg .. ("\n " .. box(err_text):gsub("\n", "\n ")) + if not (COLOR_ENABLED) then + err_msg = err_msg .. "\n" end - err_msg = err_msg .. ("\n\027[33;1m " .. box(err_text):gsub("\n", "\n ")) for i = err_linenum_end + 1, err_linenum_end + context do do local line = lines[i] if line then - err_msg = err_msg .. "\n\027[2m" .. tostring(fmt_str:format(i)) .. "\027[0m" .. tostring(line) .. "\027[0m" + err_msg = err_msg .. ("\n" .. C('dim', fmt_str:format(i)) .. line) end end end + if not (COLOR_ENABLED) then + err_msg = err_msg .. "\n" + end return err_msg end return format_error diff --git a/pretty_errors.moon b/pretty_errors.moon index bb8f63f..b769d32 100644 --- a/pretty_errors.moon +++ b/pretty_errors.moon @@ -2,6 +2,7 @@ -- line numbers, code excerpts, and so on. require "containers" Text = require 'text' +C = require 'colors' box = (text)-> max_line = 0 @@ -9,7 +10,7 @@ box = (text)-> max_line = math.max(max_line, #(line\gsub("\027%[[0-9;]*m",""))) ret = ("\n"..text)\gsub "\n([^\n]*)", (line)-> line_len = #(line\gsub("\027%[[0-9;]*m","")) - return "\n#{line}#{(" ")\rep(max_line-line_len)} \027[0m" + return "\n#{line}#{(" ")\rep(max_line-line_len)} "..C('reset') return ret\sub(2,-1), max_line format_error = (err)-> @@ -26,17 +27,18 @@ format_error = (err)-> --else -- (" ")\rep(err_linepos+#fmt_str\format(0)-1).."⬆" - err_msg = "\027[31;1m#{err.title or "Error"} at #{err.filename or '???'}:#{err_linenum},#{err_linepos}\027[0m" + err_msg = C('bold red', err.title or "Error")..C('red', " at #{err.filename or '???'}:#{err_linenum},#{err_linepos}") + err_msg ..= "\n" unless COLOR_ENABLED lines = err.source\lines! for i=err_linenum-context,err_linenum-1 if line = lines[i] - err_msg ..= "\n\027[2m#{fmt_str\format(i)}\027[0m#{line}\027[0m" + err_msg ..= "\n"..C('dim', fmt_str\format(i))..line if err_line before = err_line\sub(1, err_linepos-1) during = err_line\sub(err_linepos,err_linepos+err_size-1) after = err_line\sub(err_linepos+err_size, -1) - err_line = "\027[0m#{before}\027[41;30m#{during}#{nl_indicator}\027[0m#{after}" - err_msg ..= "\n\027[2m#{fmt_str\format(err_linenum)}#{err_line}\027[0m" + err_line = before..C('black on red', during..nl_indicator)..after + err_msg ..= "\n"..C('dim', fmt_str\format(err_linenum))..err_line _, err_linenum_end, err_linepos_end = err.source\line_info_at(err.stop) err_linenum_end or= err_linenum if err_linenum_end == err_linenum @@ -46,22 +48,24 @@ format_error = (err)-> if line = lines[i] if i == err_linenum_end during, after = line\sub(1,err_linepos_end-1), line\sub(err_linepos_end,-1) - err_msg ..= "\n\027[2m#{fmt_str\format(i)}\027[0;41;30m#{during}\027[0m#{after}" + err_msg ..= "\n"..C('dim', fmt_str\format(i))..C('black on red', during)..after else - err_msg ..= "\n\027[2m#{fmt_str\format(i)}\027[0;41;30m#{line}\027[0m" + err_msg ..= "\n"..C('dim', fmt_str\format(i))..C('black on red', line) if i > err_linenum+1 + 5 err_msg ..= "\n ...\n" break box_width = 70 - err_text = "\027[47;30;1m#{" "..err.error\wrapped_to(box_width, 16)\gsub("\n", "\n\027[47;30;1m ")}" + err_text = C('black on white', " "..err.error\wrapped_to(box_width, 16)\gsub("\n", "\n"..C('black on white').." ")) if err.hint - err_text ..= "\n\027[47;30;3m#{(" Suggestion: #{err.hint}")\wrapped_to(box_width, 16)\gsub("\n", "\n\027[47;30;3m ")}" - err_msg ..= "\n\027[33;1m "..box(err_text)\gsub("\n", "\n ") + err_text ..= "\n"..C('italic black on white', (" Suggestion: #{err.hint}")\wrapped_to(box_width, 16)\gsub("\n", "\n"..C('italic black on white').." ")) + err_msg ..= "\n "..box(err_text)\gsub("\n", "\n ") + err_msg ..= "\n" unless COLOR_ENABLED for i=err_linenum_end+1,err_linenum_end+context if line = lines[i] - err_msg ..= "\n\027[2m#{fmt_str\format(i)}\027[0m#{line}\027[0m" + err_msg ..= "\n"..C('dim', fmt_str\format(i))..line + err_msg ..= "\n" unless COLOR_ENABLED return err_msg return format_error