diff --git a/compile_lib.sh b/compile_lib.sh index 8cdfcdb..1dc9d07 100755 --- a/compile_lib.sh +++ b/compile_lib.sh @@ -11,20 +11,22 @@ while getopts ":f" opt; do esac done if [ "$FLUSH" = true ] ; then - for file in $(find lib/ -name "*.lua") ; do - rm $file - done + rm core/*.lua + rm lib/*.lua + rm tests/*.lua fi -printf "Compiling lib/core.nom ..." -./nomsu.moon -c lib/core.nom -echo "done." -for file in $(cat lib/core.nom | lua -e "for filename in io.read('*a'):gmatch('use \"([^\"]*)\"') do print(filename) end") ; do +for file in core/*.nom; do printf "Compiling $file ..." ./nomsu.moon -c $file echo "done." done -for file in $(cat tests/all.nom | lua -e "for filename in io.read('*a'):gmatch('run file \"([^\"]*)\"') do print(filename) end") ; do +for file in lib/*.nom; do + printf "Compiling $file ..." + ./nomsu.moon -c $file + echo "done." +done +for file in tests/*.nom; do printf "Compiling $file ..." ./nomsu.moon -c $file echo "done." diff --git a/lib/collections.nom b/core/collections.nom similarity index 98% rename from lib/collections.nom rename to core/collections.nom index a8cb20f..6882c96 100644 --- a/lib/collections.nom +++ b/core/collections.nom @@ -2,9 +2,9 @@ This file contains code that supports manipulating and using collections like lists and dictionaries. -use "lib/metaprogramming.nom" -use "lib/control_flow.nom" -use "lib/operators.nom" +use "core/metaprogramming.nom" +use "core/control_flow.nom" +use "core/operators.nom" # List/dict functions: diff --git a/lib/control_flow.nom b/core/control_flow.nom similarity index 99% rename from lib/control_flow.nom rename to core/control_flow.nom index 75cf6fd..315c4ef 100644 --- a/lib/control_flow.nom +++ b/core/control_flow.nom @@ -2,9 +2,9 @@ This file contains compile-time actions that define basic control flow structures like "if" statements and loops. -use "lib/metaprogramming.nom" -use "lib/text.nom" -use "lib/operators.nom" +use "core/metaprogramming.nom" +use "core/text.nom" +use "core/operators.nom" # No-Op immediately: diff --git a/lib/math.nom b/core/math.nom similarity index 97% rename from lib/math.nom rename to core/math.nom index 70e7de4..da8d931 100644 --- a/lib/math.nom +++ b/core/math.nom @@ -1,10 +1,10 @@ #.. This file defines some common math literals and functions -use "lib/metaprogramming.nom" -use "lib/text.nom" -use "lib/operators.nom" -use "lib/control_flow.nom" +use "core/metaprogramming.nom" +use "core/text.nom" +use "core/operators.nom" +use "core/control_flow.nom" # Literals: compile [infinity, inf] to {expr:"math.huge"} diff --git a/lib/metaprogramming.nom b/core/metaprogramming.nom similarity index 100% rename from lib/metaprogramming.nom rename to core/metaprogramming.nom diff --git a/lib/operators.nom b/core/operators.nom similarity index 99% rename from lib/operators.nom rename to core/operators.nom index 5d5a7a8..d2929bf 100644 --- a/lib/operators.nom +++ b/core/operators.nom @@ -1,7 +1,7 @@ #.. This file contains definitions of operators like "+" and "and". -use "lib/metaprogramming.nom" +use "core/metaprogramming.nom" # Indexing: immediately: diff --git a/lib/text.nom b/core/text.nom similarity index 98% rename from lib/text.nom rename to core/text.nom index 2a111fe..8213988 100644 --- a/lib/text.nom +++ b/core/text.nom @@ -2,7 +2,7 @@ This file contains some definitions of text escape sequences, including ANSI console color codes. -use "lib/metaprogramming.nom" +use "core/metaprogramming.nom" # Text functions action [%texts joined with %glue]: diff --git a/lib/core.nom b/lib/core.nom deleted file mode 100644 index 8843104..0000000 --- a/lib/core.nom +++ /dev/null @@ -1,10 +0,0 @@ -#.. - This file imports all the commonly used library files, which can be convenient for - avoiding retyping the whole list. - -use "lib/metaprogramming.nom" -use "lib/text.nom" -use "lib/operators.nom" -use "lib/control_flow.nom" -use "lib/math.nom" -use "lib/collections.nom" diff --git a/lib/file_hash.nom b/lib/file_hash.nom new file mode 100644 index 0000000..4d99e39 --- /dev/null +++ b/lib/file_hash.nom @@ -0,0 +1,46 @@ +use "core" + +%hash_to_filename <- {} + +lua> ".." + local Hash = require("openssl.digest"); + local function sha1(x) + local hash = Hash.new("sha1"):final(x); + local hex = hash:gsub('.', function(c) return string.format('%02x', string.byte(c)) end) + return hex + end + local lfs = require('lfs'); + local function attrdir(path) + for filename in lfs.dir(path) do + if filename ~= "." and filename ~= ".." and filename:sub(1,1) ~= "." then + local filename = path..'/'..filename + local attr = lfs.attributes(filename); + if attr.mode == "directory" then + attrdir(filename); + elseif filename:match(".*%.nom") then + local file = io.open(filename); + local hash = sha1(file:read("*a")); + file:close(); + \%hash_to_filename[hash] = filename + end + end + end + end + attrdir("."); + +action [sha1 %]: + lua> "return sha1(\%);" + +action [file with hash %hash]: + %file <- (%hash in %hash_to_filename) + assume %file or barf "File with SHA1 hash \%hash not found!" + return %file + +action [hash of file %filename]: + lua> ".." + local f = io.open(\%filename); + local hash = sha1(f:read("*a")); + f:close(); + return hash; + +parse [use file with hash %hash] as: use (file with hash %hash) diff --git a/lib/habit_breaker.nom b/lib/habit_breaker.nom new file mode 100644 index 0000000..cc423e7 --- /dev/null +++ b/lib/habit_breaker.nom @@ -0,0 +1,54 @@ +use "core" + +immediately: + compile [correct %wrong to %right] to: + lua> ".." + local signature = {}; + for i, action in ipairs(\%wrong.value) do signature[i] = action:get_src(); end + local stubs = nomsu:get_stubs_from_signature(signature); + local stub_args = nomsu:get_args_from_signature(signature); + local lua_fn_args = table.concat(stub_args[1], ", "); + local template; + if \%right.type == "Block" then + local lines = {}; + for i, line in ipairs(\%right.value) do lines[i] = nomsu:dedent(line:get_src()); end + template = repr(table.concat(lines, "\\n")); + else + template = repr(nomsu:dedent(\%right:get_src())); + end + local replacements = {}; + for i, a in ipairs(stub_args[1]) do replacements[i] = a.."="..a; end + replacements = "{"..table.concat(replacements, ", ").."}"; + local def_tree = nomsu.compilestack[#nomsu.compilestack]; + local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop); + return {statements=[[ + nomsu:define_compile_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[) + local template = nomsu:parse(]]..template..[[, ]]..repr(def_tree.filename)..[[); + local replacement = nomsu:tree_with_replaced_vars(template, ]]..replacements..[[); + error("Did you mean '"..nomsu:tree_to_nomsu(replacement).."'?"); + end); + ]]}; + +correct [%a == %b] to: %a = %b +correct [%a = %b] to: %a <- %b +correct [%a == %b] to: %a is %b +correct [%a ~= %b, %a != %b, %a <> %b] to: %a is not %b +correct [%a === %b] to: (%a's id) is (%b's id) +correct [%a !== %b] to: (%a's id) is not (%b's id) +correct [%a mod %b] to: %a wrapped around %b +correct [function %names %body, def %names %body] to: action %names %body +correct [switch %branch_value %body] to: when %branch_value = ? %body +correct [None, Null] to: nil +correct [True, true] to: yes +correct [False, false] to: no +correct [pass] to: do nothing +correct [%a || %b] to: %a or %b +correct [%a && %b] to: %a and %b +correct [continue] to: do next +correct [break] to: stop +correct [let %thing = %value in %action] to: with [%thing <- %value] %action +correct [print %] to: say % +correct [error!, panic!, fail!, abort!] to: barf! +correct [error %, panic %, fail %, abort %] to: barf % +correct [assert %condition %message] to: assume %condition or barf %message +correct [%cond ? %if_true %if_false] to: %if_true if %cond else %if_false diff --git a/lib/object.nom b/lib/object.nom index 4d8932a..2f2f3ff 100644 --- a/lib/object.nom +++ b/lib/object.nom @@ -1,4 +1,4 @@ -use "lib/core.nom" +use "core" compile [@%var] to: lua> ".." diff --git a/lib/object2.nom b/lib/object2.nom new file mode 100644 index 0000000..5e77a05 --- /dev/null +++ b/lib/object2.nom @@ -0,0 +1,135 @@ +use "core" + +compile [@] to {expr:"self"} + +compile [@%var] to: + lua> ".." + local key_lua = repr(\%var.value); + local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'") + or key_lua:match('"([a-zA-Z][a-zA-Z0-9]*)"')); + if key_attr then + return {expr="self."..key_attr}; + elseif key_lua:sub(1,1) == "[" then + key_lua = " "..key_lua.." "; + end + return {expr="self["..key_lua.."]"}; + +compile [@%var <- %val] to: + lua> ".." + local val_lua = \(%val as lua expr); + local key_lua = repr(\%var.value); + local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'") + or key_lua:match('"([a-zA-Z][a-zA-Z0-9]*)"')); + if key_attr then + return {statements="self."..key_attr.." = "..val_lua..";"}; + elseif key_lua:sub(1,1) == "[" then + key_lua = " "..key_lua.." "; + end + return {statements="self["..key_lua.."] = "..val_lua..";"}; + +compile [as %instance %body] to: + %body_lua <- (%body as lua) + return {..} + statements: ".." + do + local self = \(%instance as lua expr); + local global_actions = ACTION; + local ACTION = setmetatable({}, {__index=function(_,key) + local method = self[key]; + if method then return (function(...) return method(self, ...); end); end + return global_actions[key]; + end}); + \((%body_lua's "statements") or "\(%body_lua's "expr");") + end + locals: %body_lua's "locals" + +compile [define object %classname %class_body] to: + %class_identifier <- (=lua "nomsu:var_to_lua_identifier(\(%classname as value)):sub(2,-1)") + if: %class_identifier is "" + %class_identifier <- "class" + %methods <- [] + for %line in (%class_body's "value"): + if: (%line's "type") is "Comment" + do next %line + assume (((%line's "type") == "FunctionCall") and ((%line's "stub") == "action % %")) + ..or barf "Only action definitions are supported inside 'define object % %', not \(%line's "src")" + %actions <- (2nd in (%line's "value")) + %body <- (3rd in (%line's "value")) + lua> ".." + local signature = {}; + for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end + local stubs = nomsu:get_stubs_from_signature(signature); + local stub_args = nomsu:get_args_from_signature(signature); + local arg_set = {}; + for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end + local body_lua = nomsu:tree_to_lua(\%body); + local body_code = body_lua.statements or ("return "..body_lua.expr..";"); + local undeclared_locals = {}; + for i, body_local in ipairs(body_lua.locals or {}) do + if not arg_set[body_local] then + table.insert(undeclared_locals, body_local); + end + end + if #undeclared_locals > 0 then + body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code; + end + local lua_fn_args = table.concat({"self", unpack(stub_args[1])}, ", "); + local def_tree = nomsu.compilestack[#nomsu.compilestack]; + local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop); + + local compiled_args = {}; + for i, arg in ipairs(stub_args[1]) do + compiled_args[i] = "nomsu:tree_to_lua("..arg..").expr"; + end + compiled_args = table.concat(compiled_args, "..', '.."); + table.insert(\%methods, ([==[ + %s[ %s] = function(%s) + %s + end + ]==]):format( + \%class_identifier, repr(stubs[1]), lua_fn_args, + body_code)); + + return {..} + statements:".." + do -- \%class_identifier + -- Create the class object: + local \%class_identifier = setmetatable({ + name=\(%classname as lua expr), instances=setmetatable({}, {__mode="k"}), + }, { + __tostring=function(c) return c.name; end, + __call=function(cls, inst) + inst = inst or {}; + inst.id = tostring(inst):match('table: (.*)'); + setmetatable(inst, cls.instance_metatable); + cls.instances[inst] = true; + if inst['set % up'] then + inst['set % up'](inst); + end + return inst; + end, + }); + \%class_identifier.class = \%class_identifier; + + -- Define the methods: + \(%methods joined with "\n") + + -- Define class methods for instantiating and accessing instances: + \%class_identifier.instance_metatable = { + __index=\%class_identifier, + __tostring=\%class_identifier['% as text'] or function(inst) + return "<"..inst.class.name..": "..inst.id..">"; + end, + }; + nomsu:define_action("instances of "..\%class_identifier.name, "lib/class.nom", function() + return utils.keys(\%class_identifier.instances); + end, ""); + nomsu:define_action("new "..\%class_identifier.name.." %instance", "lib/class.nom", function(_instance) + return \%class_identifier(_instance); + end, ""); + nomsu:define_action("new "..\%class_identifier.name, "lib/class.nom", function() + return \%class_identifier({}); + end, ""); + end -- End of definition of \%class_identifier + \("\n\n") + diff --git a/lib/training_wheels.nom b/lib/training_wheels.nom index 30c20fc..e5c02fb 100644 --- a/lib/training_wheels.nom +++ b/lib/training_wheels.nom @@ -2,7 +2,7 @@ This file contains a set of definitions that bring some familiar language features from other languages into nomsu (e.g. "==" and "continue") -use "lib/core.nom" +use "core" parse [%a = %b] as: %a <- %b parse [%a == %b] as: %a is %b diff --git a/nomsu.lua b/nomsu.lua index a6d75fe..b1f49d0 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -379,6 +379,17 @@ do return ret, lua_code end, run_file = function(self, filename) + local file_attributes = assert(lfs.attributes(filename), "File not found: " .. tostring(filename)) + if file_attributes.mode == "directory" then + for short_filename in lfs.dir(filename) do + local full_filename = filename .. '/' .. short_filename + local attr = lfs.attributes(full_filename) + if attr.mode ~= "directory" and short_filename:match(".*%.nom") then + self:run_file(full_filename) + end + end + return + end if filename:match(".*%.lua") then local file = io.open(filename) local contents = file:read("*a") @@ -407,23 +418,10 @@ do end, require_file = function(self, filename) local loaded = self.environment.LOADED - local file_attributes = lfs.attributes(filename) - if file_attributes.mode == "directory" then - for short_filename in lfs.dir(filename) do - local full_filename = filename .. '/' .. short_filename - local attr = lfs.attributes(full_filename) - if attr.mode ~= "directory" and short_filename:match(".*%.nom") then - if not loaded[full_filename] then - loaded[full_filename] = self:run_file(full_filename) or true - end - end - end - else - if not loaded[filename] then - loaded[filename] = self:run_file(filename) or true - end - return loaded[filename] + if not loaded[filename] then + loaded[filename] = self:run_file(filename) or true end + return loaded[filename] end, run_lua = function(self, lua_code) local run_lua_fn, err = load(lua_code, nil, nil, self.environment) @@ -1526,13 +1524,12 @@ if arg and debug.getinfo(2).func ~= require then if args.input:match(".*%.lua") then local retval = dofile(args.input)(nomsu, { }) else - local input + local retval, code if args.input == '-' then - input = io.read('*a') + retval, code = nomsu:run(io.read('*a'), 'stdin') else - input = io.open(args.input):read("*a") + retval, code = nomsu:run_file(args.input) end - local retval, code = nomsu:run(input, args.input) if compiled_output then compiled_output:write("local IMMEDIATE = true;\n") compiled_output:write(code) @@ -1543,7 +1540,7 @@ if arg and debug.getinfo(2).func ~= require then end end if args.flags["-i"] then - nomsu:run('use "lib/core.nom"', "stdin") + nomsu:run('use "core"', "stdin") while true do io.write(colored.bright(colored.yellow(">> "))) local buff = "" diff --git a/nomsu.moon b/nomsu.moon index 20372bb..a214857 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -293,6 +293,15 @@ class NomsuCompiler return ret, lua_code run_file: (filename)=> + file_attributes = assert(lfs.attributes(filename), "File not found: #{filename}") + if file_attributes.mode == "directory" + for short_filename in lfs.dir(filename) + full_filename = filename..'/'..short_filename + attr = lfs.attributes(full_filename) + if attr.mode ~= "directory" and short_filename\match(".*%.nom") + @run_file full_filename + return + if filename\match(".*%.lua") file = io.open(filename) contents = file\read("*a") @@ -316,18 +325,9 @@ class NomsuCompiler require_file: (filename)=> loaded = @environment.LOADED - file_attributes = lfs.attributes(filename) - if file_attributes.mode == "directory" - for short_filename in lfs.dir(filename) - full_filename = filename..'/'..short_filename - attr = lfs.attributes(full_filename) - if attr.mode ~= "directory" and short_filename\match(".*%.nom") - if not loaded[full_filename] - loaded[full_filename] = @run_file(full_filename) or true - else - if not loaded[filename] - loaded[filename] = @run_file(filename) or true - return loaded[filename] + if not loaded[filename] + loaded[filename] = @run_file(filename) or true + return loaded[filename] run_lua: (lua_code)=> run_lua_fn, err = load(lua_code, nil, nil, @environment) @@ -974,10 +974,11 @@ if arg and debug.getinfo(2).func != require if args.input\match(".*%.lua") retval = dofile(args.input)(nomsu, {}) else - input = if args.input == '-' - io.read('*a') - else io.open(args.input)\read("*a") - retval, code = nomsu\run(input, args.input) + local retval, code + if args.input == '-' + retval, code = nomsu\run(io.read('*a'), 'stdin') + else + retval, code = nomsu\run_file(args.input) if compiled_output compiled_output\write("local IMMEDIATE = true;\n") compiled_output\write(code) @@ -987,7 +988,7 @@ if arg and debug.getinfo(2).func != require if args.flags["-i"] -- REPL - nomsu\run('use "lib/core.nom"', "stdin") + nomsu\run('use "core"', "stdin") while true io.write(colored.bright colored.yellow ">> ") buff = "" diff --git a/tests/all.nom b/tests/all.nom deleted file mode 100644 index 5b95786..0000000 --- a/tests/all.nom +++ /dev/null @@ -1,28 +0,0 @@ -use "lib/core.nom" - -# Prerequisites: -%var <- 99 -assume (%var = 99) or barf "Var assignment doesn't work." - -try: barf "barf" -..and if it succeeds: barf "try % and if it succeeds failed." - -try: do nothing -..and if it barfs: barf "try % and if it barfs failed." - -immediately - assume (%ONCE is (nil)) or barf "immediately is executing twice" - export %ONCE <- "ONCE" - %foo <- "immediately local" -assume (%ONCE = "ONCE") or barf "Globals don't work." -assume (%foo is (nil)) or barf "Immediately is leaking locals." - -# Run per-file test suites -run file "tests/metaprogramming.nom" -run file "tests/text.nom" -run file "tests/operators.nom" -run file "tests/control_flow.nom" -run file "tests/math.nom" -run file "tests/collections.nom" - -say "All tests passed!" diff --git a/tests/collections.nom b/tests/collections.nom index ec79653..1c82487 100644 --- a/tests/collections.nom +++ b/tests/collections.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/control_flow.nom -use "lib/core.nom" +use "core" assume ((2nd to last in [1,2,3,4,5]) = 4) assume ((first in [1,2]) = 1) @@ -41,3 +41,5 @@ assume ((unique [1,2,1,3,2,3]) = [1,2,3]) for all ["x","y","x","x","y"] (% in %c) +<- 1 assume (%c = {x:3,y:2}) + +say "Collections test passed." diff --git a/tests/control_flow.nom b/tests/control_flow.nom index 71d5f85..4ac3d8a 100644 --- a/tests/control_flow.nom +++ b/tests/control_flow.nom @@ -1,7 +1,8 @@ #.. Tests for the stuff defined in lib/control_flow.nom -use "lib/core.nom" +use "core" + do nothing action [test conditionals] @@ -189,3 +190,5 @@ try %x <- 1 ..and if it barfs: do nothing assume (%x = 1) or barf "do/then always failed" + +say "Control flow test passed." diff --git a/tests/math.nom b/tests/math.nom index 245fe52..4232d81 100644 --- a/tests/math.nom +++ b/tests/math.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/control_flow.nom -use "lib/core.nom" +use "core" assume (all of [inf, pi, tau, golden ratio, e]) or barf "math constants failed" %nan <- (NaN) @@ -15,3 +15,5 @@ assume ..or barf "math functions failed" assume ((463 to the nearest 100) = 500) or barf "rounding failed" assume ((2.6 to the nearest 0.25) = 2.5) or barf "rounding failed" + +say "Math test passed" diff --git a/tests/metaprogramming.nom b/tests/metaprogramming.nom index 37acd3e..360771e 100644 --- a/tests/metaprogramming.nom +++ b/tests/metaprogramming.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/metaprogramming.nom -use "lib/core.nom" +use "core" immediately compile [five] to {expr:"5"} @@ -53,3 +53,5 @@ assume ((nomsu) = (=lua "nomsu")) or barf "nomsu failed" assume (("x" as lua identifier) = (\%x as lua identifier)) or barf "converting to identifier failed." assume ((run "return 99") = 99) or barf "run % failed." + +say "Metaprogramming test passed." diff --git a/tests/object.nom b/tests/object.nom index 7dbaca6..84524e5 100644 --- a/tests/object.nom +++ b/tests/object.nom @@ -1,5 +1,5 @@ -use "lib/core.nom" -use "lib/object.nom" +use "core" +use "lib/object2.nom" immediately: define object "Dog": @@ -20,3 +20,4 @@ as %d: assume ("\(%d's "class")" = "Dog") assume ((%d's "barks") = 3) +say "Object test passed." diff --git a/tests/operators.nom b/tests/operators.nom index e3bd0b9..1a138cb 100644 --- a/tests/operators.nom +++ b/tests/operators.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/operators.nom -use "lib/core.nom" +use "core" assume (({x:5}'s "x") = 5) or barf "indexing doesn't work." try: % <- ({}'s "[[[\n]]]") @@ -74,3 +74,5 @@ assume (%x = 2) or barf "+<- failed" assume (%x = 4) or barf "*<- failed" wrap %x around 3 assume (%x = 1) or barf "wrap around failed" + +say "Operator test passed." diff --git a/tests/text.nom b/tests/text.nom index ea18d42..2e08e45 100644 --- a/tests/text.nom +++ b/tests/text.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/text.nom -use "lib/core.nom" +use "core" assume ((["x","y"] joined with ",") = "x,y") or barf "joined with failed" assume ((["x","y"] joined) = "xy") or barf "joined failed" @@ -11,3 +11,5 @@ assume (("asdf" with "X" instead of "s") = "aXdf") or barf "substitution failed" assume ("\n" = (newline)) or barf "Text literals failed." %x <- "\(green)hello\(reset color)" assume (("x" + "y") = "xy") + +say "Text test passed."