diff --git a/Makefile b/Makefile index a65ca4e..fc11273 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,12 @@ LUA_BIN= $(shell which $(LUA)) PREFIX=/usr/local NOMSU_BIN_DIR= $(PREFIX)/bin NOMSU_LIB_DIR= $(PREFIX)/lib/nomsu +NOMSU_SHARE_DIR= $(PREFIX)/share/nomsu # ========= You shouldn't need to mess with any of these variables below ================ -MOON_FILES= code_obj.moon error_handling.moon nomsu.moon nomsu_compiler.moon nomsu_tree.moon parser.moon -LUA_FILES= code_obj.lua consolecolors.lua error_handling.lua nomsu.lua nomsu_compiler.lua \ +MOON_FILES= code_obj.moon error_handling.moon files.moon nomsu.moon nomsu_compiler.moon nomsu_tree.moon parser.moon +LUA_FILES= code_obj.lua consolecolors.lua error_handling.lua files.lua nomsu.lua nomsu_compiler.lua \ nomsu_tree.lua parser.lua utils.lua uuid.lua CORE_NOM_FILES= $(wildcard core/*.nom) CORE_LUA_FILES= $(patsubst %.nom,%.lua,$(CORE_NOM_FILES)) @@ -37,7 +38,7 @@ test: build optimize .PHONY: check_header check_header: $(PEG_FILE) nomsu.lua $(CORE_NOM_FILES) $(LIB_NOM_FILES) @if [ -f nomsu_latest ]; then \ - NOMSU_HEADER="#!$(LUA_BIN)\\npackage.path = [[$(NOMSU_LIB_DIR)/`$(GET_VERSION)`/?.lua;]]..package.path\\npackage.nomsupath = [[$(NOMSU_LIB_DIR)/`$(GET_VERSION)`]]"; \ + NOMSU_HEADER="#!$(LUA_BIN)\\nlocal NOMSU_VERSION, NOMSU_LIB, NOMSU_SHARE = [[`$(GET_VERSION)`]], [[$(NOMSU_LIB_DIR)]], [[$(NOMSU_SHARE_DIR)]]"; \ if [ "`head -n 3 nomsu_latest 2>/dev/null`" != "`echo $$NOMSU_HEADER`" ]; then \ rm -f nomsu_latest; \ fi; \ @@ -45,7 +46,7 @@ check_header: $(PEG_FILE) nomsu.lua $(CORE_NOM_FILES) $(LIB_NOM_FILES) nomsu_latest: nomsu.lua @rm -f nomsu_latest - @NOMSU_HEADER="#!$(LUA_BIN)\\npackage.path = [[$(NOMSU_LIB_DIR)/`$(GET_VERSION)`/?.lua;]]..package.path\\npackage.nomsupath = [[$(NOMSU_LIB_DIR)/`$(GET_VERSION)`]]"; \ + @NOMSU_HEADER="#!$(LUA_BIN)\\nlocal NOMSU_VERSION, NOMSU_LIB, NOMSU_SHARE = [[`$(GET_VERSION)`]], [[$(NOMSU_LIB_DIR)]], [[$(NOMSU_SHARE_DIR)]]"; \ echo $$NOMSU_HEADER | cat - nomsu.lua > nomsu_latest @chmod +x nomsu_latest @mv -f nomsu_latest nomsu`$(GET_VERSION)` @@ -64,14 +65,46 @@ clean: .PHONY: install install: all - mkdir -pv $(NOMSU_BIN_DIR) && cp -v nomsu nomsu`$(GET_VERSION)` $(NOMSU_BIN_DIR) - mkdir -pv $(NOMSU_LIB_DIR)/`$(GET_VERSION)` && cp -rv $(LUA_FILES) $(PEG_FILE) core lib $(NOMSU_LIB_DIR)/`$(GET_VERSION)` + @echo "Nomsu will be installed to:" + @echo " $(NOMSU_BIN_DIR)" + @echo " $(NOMSU_LIB_DIR)" + @echo " $(NOMSU_SHARE_DIR)" + @read -p "is that okay? [Y/n] " ans; \ + if [[ ! $$ans =~ ^[Nn] ]]; then \ + mkdir -pv $(NOMSU_BIN_DIR) $(NOMSU_LIB_DIR)/`$(GET_VERSION)` $(NOMSU_SHARE_DIR)/`$(GET_VERSION)` \ + && cp -v nomsu nomsu`$(GET_VERSION)` $(NOMSU_BIN_DIR) \ + && cp -rv $(LUA_FILES) $(PEG_FILE) core lib tests $(NOMSU_SHARE_DIR)/`$(GET_VERSION)`; \ + fi .PHONY: uninstall uninstall: all - @echo "Deleting..." - @rm -rvf $(NOMSU_LIB_DIR)/`$(GET_VERSION)` $(NOMSU_BIN_DIR)/nomsu`$(GET_VERSION)` - @if [ "`ls $(NOMSU_BIN_DIR)/nomsu*`" == "nomsu" ]; then rm -v $(NOMSU_BIN_DIR)/nomsu; fi - @if [ "`ls $(NOMSU_LIB_DIR) 2>/dev/null`" == "" ]; then rm -rvf $(NOMSU_LIB_DIR); fi + @echo "Nomsu will be uninstalled from:" + @echo " $(NOMSU_BIN_DIR)" + @echo " $(NOMSU_LIB_DIR)" + @echo " $(NOMSU_SHARE_DIR)" + @read -p "is that okay? [Y/n] " ans; \ + if [[ ! $$ans =~ ^[Nn] ]]; then \ + echo "Deleting..."; \ + rm -rvf $(NOMSU_LIB_DIR)/`$(GET_VERSION)` $(NOMSU_SHARE_DIR)/`$(GET_VERSION)` $(NOMSU_BIN_DIR)/nomsu`$(GET_VERSION)`; \ + if [ "`ls $(NOMSU_BIN_DIR)/nomsu* 2> /dev/null`" == "nomsu" ]; then \ + rm -vf $(NOMSU_BIN_DIR)/nomsu; \ + else \ + if [ "`ls $(NOMSU_BIN_DIR)/nomsu* 2> /dev/null`" != "" ]; then \ + read -p "It looks like there are other versions of Nomsu installed. Is it okay to leave the 'nomsu' cross-version launcher in place? (recommended) [Y/n]" ans; \ + if [[ $$ans =~ ^[Nn] ]]; then \ + echo "Deleting..."; \ + rm -vf $(NOMSU_BIN_DIR)/nomsu; \ + fi; \ + fi; \ + fi; \ + if [ "`ls $(NOMSU_LIB_DIR) 2>/dev/null`" == "" ]; then rm -rvf $(NOMSU_LIB_DIR);\ + else \ + echo "Retaining $(NOMSU_LIB_DIR), since there are other files there."; \ + fi; \ + if [ "`ls $(NOMSU_SHARE_DIR) 2>/dev/null`" == "" ]; then rm -rvf $(NOMSU_SHARE_DIR);\ + else \ + echo "Retaining $(NOMSU_SHARE_DIR), since there are other files there."; \ + fi; \ + fi # eof diff --git a/error_handling.lua b/error_handling.lua index 90c5991..b0e6dde 100644 --- a/error_handling.lua +++ b/error_handling.lua @@ -1,3 +1,4 @@ +local files = require("files") local debug_getinfo = debug.getinfo local ok, to_lua = pcall(function() return require('moonscript.base').to_lua @@ -97,7 +98,7 @@ print_error = function(error_message, stack_offset) end local filename, start, stop = calling_fn.source:match('@([^[]*)%[([0-9]+):([0-9]+)]') assert(filename) - local file = FILE_CACHE[filename]:sub(tonumber(start), tonumber(stop)) + local file = read_file(filename):sub(tonumber(start), tonumber(stop)) local err_line = get_line(file, calling_fn.currentline):sub(1, -2) local offending_statement = colored.bright(colored.red(err_line:match("^[ ]*(.*)"))) if calling_fn.name then @@ -118,7 +119,7 @@ print_error = function(error_message, stack_offset) else local file ok, file = pcall(function() - return FILE_CACHE[calling_fn.short_src] + return read_file(calling_fn.short_src) end) if not ok then file = nil diff --git a/error_handling.moon b/error_handling.moon index c71e782..5341348 100644 --- a/error_handling.moon +++ b/error_handling.moon @@ -1,4 +1,5 @@ -- This file contains the logic for making nicer error messages +files = require "files" debug_getinfo = debug.getinfo export SOURCE_MAP @@ -62,7 +63,7 @@ print_error = (error_message, stack_offset=3)-> --calling_fn.short_src = calling_fn.source\match('"([^[]*)') filename,start,stop = calling_fn.source\match('@([^[]*)%[([0-9]+):([0-9]+)]') assert(filename) - file = FILE_CACHE[filename]\sub(tonumber(start),tonumber(stop)) + file = read_file(filename)\sub(tonumber(start),tonumber(stop)) err_line = get_line(file, calling_fn.currentline)\sub(1,-2) offending_statement = colored.bright(colored.red(err_line\match("^[ ]*(.*)"))) -- TODO: get name properly @@ -73,7 +74,7 @@ print_error = (error_message, stack_offset=3)-> else "main chunk" line = colored.yellow("#{filename}:#{calling_fn.currentline} in #{name}\n #{offending_statement}") else - ok, file = pcall ->FILE_CACHE[calling_fn.short_src] + ok, file = pcall ->read_file(calling_fn.short_src) if not ok then file = nil local line_num if name == nil diff --git a/files.lua b/files.lua new file mode 100644 index 0000000..77bdf6d --- /dev/null +++ b/files.lua @@ -0,0 +1,125 @@ +local files = { } +local _FILE_CACHE = { } +files.spoof = function(filename, contents) + _FILE_CACHE[filename] = contents +end +files.read = function(filename) + do + local file_contents = _FILE_CACHE[filename] + if file_contents then + return file_contents + end + end + local file = io.open(filename) + if package.nomsupath and not file then + for nomsupath in package.nomsupath:gmatch("[^;]+") do + file = io.open(nomsupath .. "/" .. filename) + if file then + break + end + end + end + if not (file) then + return nil + end + local contents = file:read("*a") + file:close() + _FILE_CACHE[filename] = contents + return contents +end +local iterate_single +iterate_single = function(item, prev) + if item == prev then + return nil + else + return item + end +end +local match, sub, rep, gsub, format, byte, find +do + local _obj_0 = string + match, sub, rep, gsub, format, byte, match, find = _obj_0.match, _obj_0.sub, _obj_0.rep, _obj_0.gsub, _obj_0.format, _obj_0.byte, _obj_0.match, _obj_0.find +end +iterate_single = function(item, prev) + if item == prev then + return nil + else + return item + end +end +local ok, lfs = pcall(require, "lfs") +if ok then + files.walk = function(path) + local browse + browse = function(filename) + local file_type = lfs.attributes(filename, 'mode') + if file_type == 'file' then + if match(filename, "%.nom$") or match(filename, "%.lua$") then + coroutine.yield(filename) + return true + end + elseif file_type == 'directory' then + for subfile in lfs.dir(filename) do + if not (subfile == "." or subfile == "..") then + browse(filename .. "/" .. subfile) + end + end + return true + elseif file_type == 'char device' then + coroutine.yield(filename) + return true + end + return false + end + return coroutine.wrap(function() + if not browse(path) and package.nomsupath then + for nomsupath in package.nomsupath:gmatch("[^;]+") do + if browse(nomsupath .. "/" .. path) then + break + end + end + end + return nil + end) + end +else + local ret = os.execute('find . -maxdepth 0') + if not (ret == true or ret == 0) then + error("Could not find 'luafilesystem' module and couldn't run system command `find` (this might happen on Windows). Please install `luafilesystem` (which can be found at: http://keplerproject.github.io/luafilesystem/ or `luarocks install luafilesystem`)", 0) + end + files.walk = function(path) + if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") then + return iterate_single, path + end + path = gsub(path, "\\", "\\\\") + path = gsub(path, "`", "") + path = gsub(path, '"', '\\"') + path = gsub(path, "$", "") + return coroutine.wrap(function() + local f = io.popen('find -L "' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"') + local found = false + for line in f:lines() do + found = true + coroutine.yield(line) + end + if not found and package.nomsupath then + f:close() + for nomsupath in package.nomsupath:gmatch("[^;]+") do + f = io.popen('find -L "' .. package.nomsupath .. '/' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"') + for line in f:lines() do + found = true + coroutine.yield(line) + end + f:close() + if found then + break + end + end + end + if not (found) then + return error("Invalid file path: " .. tostring(path)) + end + end) + end +end +return files diff --git a/files.moon b/files.moon new file mode 100644 index 0000000..3abae95 --- /dev/null +++ b/files.moon @@ -0,0 +1,86 @@ +-- Some file utilities for searching for files recursively and using package.nomsupath +files = {} + +_FILE_CACHE = {} + +-- Create a fake file and put it in the cache +files.spoof = (filename, contents)-> + _FILE_CACHE[filename] = contents + +-- Read a file's contents (searching first locally, then in the nomsupath) +files.read = (filename)-> + if file_contents = _FILE_CACHE[filename] + return file_contents + file = io.open(filename) + if package.nomsupath and not file + for nomsupath in package.nomsupath\gmatch("[^;]+") + file = io.open(nomsupath.."/"..filename) + break if file + return nil unless file + contents = file\read("*a") + file\close! + _FILE_CACHE[filename] = contents + return contents + +iterate_single = (item, prev) -> if item == prev then nil else item + +-- `walk` returns an iterator over all files matching a path. +{:match, :sub, :rep, :gsub, :format, :byte, :match, :find} = string +iterate_single = (item, prev) -> if item == prev then nil else item +ok, lfs = pcall(require, "lfs") +if ok + files.walk = (path)-> + -- Return 'true' if any files or directories are found, otherwise 'false' + browse = (filename)-> + file_type = lfs.attributes(filename, 'mode') + if file_type == 'file' + if match(filename, "%.nom$") or match(filename, "%.lua$") + coroutine.yield filename + return true + elseif file_type == 'directory' + for subfile in lfs.dir(filename) + unless subfile == "." or subfile == ".." + browse(filename.."/"..subfile) + return true + elseif file_type == 'char device' + coroutine.yield(filename) + return true + return false + return coroutine.wrap -> + if not browse(path) and package.nomsupath + for nomsupath in package.nomsupath\gmatch("[^;]+") + break if browse(nomsupath.."/"..path) + return nil +else + ret = os.execute('find . -maxdepth 0') + unless ret == true or ret == 0 + error "Could not find 'luafilesystem' module and couldn't run system command `find` (this might happen on Windows). Please install `luafilesystem` (which can be found at: http://keplerproject.github.io/luafilesystem/ or `luarocks install luafilesystem`)", 0 + + files.walk = (path)-> + -- Sanitize path + if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") + return iterate_single, path + -- TODO: improve sanitization + path = gsub(path,"\\","\\\\") + path = gsub(path,"`","") + path = gsub(path,'"','\\"') + path = gsub(path,"$","") + return coroutine.wrap -> + f = io.popen('find -L "'..path..'" -not -path "*/\\.*" -type f -name "*.nom"') + found = false + for line in f\lines! + found = true + coroutine.yield(line) + if not found and package.nomsupath + f\close! + for nomsupath in package.nomsupath\gmatch("[^;]+") + f = io.popen('find -L "'..package.nomsupath..'/'..path..'" -not -path "*/\\.*" -type f -name "*.nom"') + for line in f\lines! + found = true + coroutine.yield(line) + f\close! + break if found + unless found + error("Invalid file path: "..tostring(path)) + +return files diff --git a/lib/os.nom b/lib/os.nom index be65370..8a27168 100644 --- a/lib/os.nom +++ b/lib/os.nom @@ -1,5 +1,10 @@ use "core" +action [path of Nomsu file %filename] + lua> ".." + for f in files.walk(\%filename) do return f end + barf "Could not find file: \%filename" + action [sh> %cmd] lua> ".." local result = io.popen(\%cmd) @@ -8,11 +13,15 @@ action [sh> %cmd] return contents action [read file %filename] - lua> ".." - local file = io.open(\%filename) - local contents = file:read("*a") - file:close() - return contents + =lua "files.read(\%filename)" + +compile [for file %f in %path %body] to + Lua ".." + for \(%f as lua expr) in files.walk(\(%path as lua expr)) do + \(%body as lua statements) + \(compile as: === next %f ===) + end + \(compile as: === stop %f ===) action [write to file %filename %text, to file %filename write %text] lua> ".." diff --git a/lib/version.nom b/lib/version.nom new file mode 100644 index 0000000..3b90888 --- /dev/null +++ b/lib/version.nom @@ -0,0 +1 @@ +lua> "NOMSU_LIB_VERSION = 2" diff --git a/nomsu b/nomsu deleted file mode 120000 index da566d1..0000000 --- a/nomsu +++ /dev/null @@ -1 +0,0 @@ -nomsu_runner \ No newline at end of file diff --git a/nomsu b/nomsu new file mode 100755 index 0000000..af9993d --- /dev/null +++ b/nomsu @@ -0,0 +1,20 @@ +#!/bin/bash +while getopts ':V:' flag; do + case "${flag}" in + V) VERSION="${OPTARG/./\.}\\b" ;; + esac +done + +if [[ $VERSION ]]; then + candidates=$(ls $(dirname $BASH_SOURCE) | grep "^nomsu$VERSION[0-9.]*$") +else + candidates=$(ls $(dirname $BASH_SOURCE) | grep "^nomsu[0-9.]\+$") +fi + +if [[ $candidates ]]; then + eval $(dirname $BASH_SOURCE)/$(echo "$candidates" | sort -V | tail -n 1) $@ +else + echo "Failed to find a Nomsu version matching the regex: \"nomsu$VERSION\"" + echo "The versions available are:" + ls $(dirname $BASH_SOURCE) | grep "^nomsu[0-9.]\+$" | sed 's/^/ * /' +fi diff --git a/nomsu.lua b/nomsu.lua index 44ad759..cfa515a 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -1,3 +1,55 @@ +if NOMSU_VERSION and NOMSU_LIB and NOMSU_SHARE then + local ver_bits + do + local _accum_0 = { } + local _len_0 = 1 + for ver_bit in NOMSU_VERSION:gmatch("[0-9]+") do + _accum_0[_len_0] = ver_bit + _len_0 = _len_0 + 1 + end + ver_bits = _accum_0 + end + local partial_vers + do + local _accum_0 = { } + local _len_0 = 1 + for i = #ver_bits, 1, -1 do + _accum_0[_len_0] = table.concat(ver_bits, '.', 1, i) + _len_0 = _len_0 + 1 + end + partial_vers = _accum_0 + end + package.path = table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #partial_vers do + local v = partial_vers[_index_0] + _accum_0[_len_0] = tostring(NOMSU_SHARE) .. "/" .. tostring(v) .. "/?.lua" + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ";") .. ";" .. package.path + package.cpath = table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #partial_vers do + local v = partial_vers[_index_0] + _accum_0[_len_0] = tostring(NOMSU_LIB) .. "/" .. tostring(v) .. "/?.so" + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ";") .. ";" .. package.cpath + package.nomsupath = table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #partial_vers do + local v = partial_vers[_index_0] + _accum_0[_len_0] = tostring(NOMSU_SHARE) .. "/" .. tostring(v) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ";") +end local EXIT_SUCCESS, EXIT_FAILURE = 0, 1 local usage = [=[Nomsu Compiler @@ -61,6 +113,7 @@ if not args or args.help then print(usage) os.exit(EXIT_FAILURE) end +local files = require("files") local nomsu = NomsuCompiler nomsu.arg = args.nomsu_args if args.version then @@ -79,85 +132,6 @@ FILE_CACHE = setmetatable({ }, { return contents end }) -local match, sub, rep, gsub, format, byte, find -do - local _obj_0 = string - match, sub, rep, gsub, format, byte, match, find = _obj_0.match, _obj_0.sub, _obj_0.rep, _obj_0.gsub, _obj_0.format, _obj_0.byte, _obj_0.match, _obj_0.find -end -local iterate_single -iterate_single = function(item, prev) - if item == prev then - return nil - else - return item - end -end -local lfs -ok, lfs = pcall(require, "lfs") -if ok then - all_files = function(path) - local browse - browse = function(filename) - local file_type = lfs.attributes(filename, 'mode') - if file_type == 'file' then - if match(filename, "%.nom$") or match(filename, "%.lua$") then - coroutine.yield(filename) - return true - end - elseif file_type == 'directory' then - for subfile in lfs.dir(filename) do - if not (subfile == "." or subfile == "..") then - browse(filename .. "/" .. subfile) - end - end - return true - elseif file_type == 'char device' then - coroutine.yield(filename) - return true - end - return false - end - return coroutine.wrap(function() - if not browse(path) and package.nomsupath then - browse(package.nomsupath .. "/" .. path) - end - return nil - end) - end -else - local ret = os.execute('find . -maxdepth 0') - if not (ret == true or ret == 0) then - error("Could not find 'luafilesystem' module and couldn't run system command `find` (this might happen on Windows). Please install `luafilesystem` (which can be found at: http://keplerproject.github.io/luafilesystem/ or `luarocks install luafilesystem`)", 0) - end - all_files = function(path) - if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") then - return iterate_single, path - end - path = gsub(path, "\\", "\\\\") - path = gsub(path, "`", "") - path = gsub(path, '"', '\\"') - path = gsub(path, "$", "") - return coroutine.wrap(function() - local f = io.popen('find -L "' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"') - local found = false - for line in f:lines() do - found = true - coroutine.yield(line) - end - if not found and package.nomsupath then - f:close() - f = io.popen('find -L "' .. package.nomsupath .. '/' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"') - for line in f:lines() do - coroutine.yield(line) - end - end - local success = f:close() - if not (success) then - return error("Invalid file path: " .. tostring(path)) - end - end) - end -end local run run = function() for i, input in ipairs(args.inputs) do @@ -199,7 +173,7 @@ run = function() local _list_0 = args.inputs for _index_0 = 1, #_list_0 do local input = _list_0[_index_0] - for f in all_files(input) do + for f in files.walk(input) do input_files[#input_files + 1] = f to_run[f] = true end @@ -245,7 +219,7 @@ run = function() print_file:flush() end elseif args.format then - local file = FILE_CACHE[filename] + local file = files.read(filename) if not file then error("File does not exist: " .. tostring(filename), 0) end @@ -257,7 +231,7 @@ run = function() end elseif filename == STDIN then local file = io.input():read("*a") - FILE_CACHE.stdin = file + files.spoof('stdin', file) nomsu:run(file, Source('stdin', 1, #file)) else nomsu:run_file(filename) @@ -305,7 +279,7 @@ run = function() end buff = table.concat(buff) local pseudo_filename = "user input #" .. repl_line - FILE_CACHE[pseudo_filename] = buff + files.spoof(pseudo_filename, buff) local err_hand err_hand = function(error_message) return Errhand.print_error(error_message) diff --git a/nomsu.moon b/nomsu.moon index 14f8992..19e7b26 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -1,5 +1,12 @@ #!/usr/bin/env moon -- This file contains the command-line Nomsu runner. +if NOMSU_VERSION and NOMSU_LIB and NOMSU_SHARE + ver_bits = [ver_bit for ver_bit in NOMSU_VERSION\gmatch("[0-9]+")] + partial_vers = [table.concat(ver_bits,'.',1,i) for i=#ver_bits,1,-1] + package.path = table.concat(["#{NOMSU_SHARE}/#{v}/?.lua" for v in *partial_vers],";")..";"..package.path + package.cpath = table.concat(["#{NOMSU_LIB}/#{v}/?.so" for v in *partial_vers],";")..";"..package.cpath + package.nomsupath = table.concat(["#{NOMSU_SHARE}/#{v}" for v in *partial_vers],";") + EXIT_SUCCESS, EXIT_FAILURE = 0, 1 usage = [=[ Nomsu Compiler @@ -58,11 +65,12 @@ if not args or args.help print usage os.exit(EXIT_FAILURE) +files = require "files" nomsu = NomsuCompiler nomsu.arg = args.nomsu_args if args.version - nomsu\run 'use "core"\nsay (Nomsu version)' + nomsu\run 'use "core"\nsay (Nomsu version)' os.exit(EXIT_SUCCESS) export FILE_CACHE @@ -77,61 +85,6 @@ FILE_CACHE = setmetatable {}, { return contents } -export all_files -{:match, :sub, :rep, :gsub, :format, :byte, :match, :find} = string -iterate_single = (item, prev) -> if item == prev then nil else item -ok, lfs = pcall(require, "lfs") -if ok - all_files = (path)-> - browse = (filename)-> - file_type = lfs.attributes(filename, 'mode') - if file_type == 'file' - if match(filename, "%.nom$") or match(filename, "%.lua$") - coroutine.yield filename - return true - elseif file_type == 'directory' - for subfile in lfs.dir(filename) - unless subfile == "." or subfile == ".." - browse(filename.."/"..subfile) - return true - elseif file_type == 'char device' - coroutine.yield(filename) - return true - return false - return coroutine.wrap -> - if not browse(path) and package.nomsupath - browse(package.nomsupath.."/"..path) - return nil -else - ret = os.execute('find . -maxdepth 0') - unless ret == true or ret == 0 - error "Could not find 'luafilesystem' module and couldn't run system command `find` (this might happen on Windows). Please install `luafilesystem` (which can be found at: http://keplerproject.github.io/luafilesystem/ or `luarocks install luafilesystem`)", 0 - - all_files = (path)-> - -- Sanitize path - if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") - return iterate_single, path - -- TODO: improve sanitization - path = gsub(path,"\\","\\\\") - path = gsub(path,"`","") - path = gsub(path,'"','\\"') - path = gsub(path,"$","") - return coroutine.wrap -> - f = io.popen('find -L "'..path..'" -not -path "*/\\.*" -type f -name "*.nom"') - found = false - for line in f\lines! - found = true - coroutine.yield(line) - if not found and package.nomsupath - f\close! - f = io.popen('find -L "'..package.nomsupath..'/'..path..'" -not -path "*/\\.*" -type f -name "*.nom"') - for line in f\lines! - coroutine.yield(line) - success = f\close! - unless success - error("Invalid file path: "..tostring(path)) - - run = -> for i,input in ipairs args.inputs if input == "-" then args.inputs[i] = STDIN @@ -159,7 +112,7 @@ run = -> input_files = {} to_run = {} for input in *args.inputs - for f in all_files(input) + for f in files.walk(input) input_files[#input_files+1] = f to_run[f] = true @@ -194,7 +147,7 @@ run = -> print_file\flush! elseif args.format -- Auto-format - file = FILE_CACHE[filename] + file = files.read(filename) if not file error("File does not exist: #{filename}", 0) tree = nomsu\parse(file, Source(filename,1,#file)) @@ -204,7 +157,7 @@ run = -> print_file\flush! elseif filename == STDIN file = io.input!\read("*a") - FILE_CACHE.stdin = file + files.spoof('stdin', file) nomsu\run(file, Source('stdin',1,#file)) else nomsu\run_file(filename) @@ -248,7 +201,7 @@ run = -> buff = table.concat(buff) pseudo_filename = "user input #"..repl_line - FILE_CACHE[pseudo_filename] = buff + files.spoof(pseudo_filename, buff) err_hand = (error_message)-> Errhand.print_error error_message ok, ret = xpcall(nomsu.run, err_hand, nomsu, buff, Source(pseudo_filename, 1, #buff)) diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua index c94685a..42cdb17 100644 --- a/nomsu_compiler.lua +++ b/nomsu_compiler.lua @@ -2,6 +2,7 @@ local lpeg = require('lpeg') local re = require('re') lpeg.setmaxstack(10000) local utils = require('utils') +local files = require('files') local repr, stringify, equivalent repr, stringify, equivalent = utils.repr, utils.stringify, utils.equivalent colors = require('consolecolors') @@ -60,45 +61,6 @@ table.fork = function(t, values) __index = t }) end -FILE_CACHE = setmetatable({ }, { - __index = function(self, filename) - local file = io.open(filename) - if not (file) then - return nil - end - local contents = file:read("*a") - file:close() - self[filename] = contents - return contents - end -}) -local iterate_single -iterate_single = function(item, prev) - if item == prev then - return nil - else - return item - end -end -all_files = function(path) - if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") then - return iterate_single, path - end - path = gsub(path, "\\", "\\\\") - path = gsub(path, "`", "") - path = gsub(path, '"', '\\"') - path = gsub(path, "$", "") - return coroutine.wrap(function() - local f = io.popen('find -L "' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"') - for line in f:lines() do - coroutine.yield(line) - end - local success = f:close() - if not (success) then - return error("Invalid file path: " .. tostring(path)) - end - end) -end local line_counter = re.compile([[ lines <- {| line (%nl line)* |} line <- {} (!%nl .)* ]], { @@ -229,7 +191,7 @@ local NomsuCompiler = setmetatable({ }, { end }) do - NomsuCompiler.NOMSU_COMPILER_VERSION = 1 + NomsuCompiler.NOMSU_COMPILER_VERSION = 2 NomsuCompiler.NOMSU_SYNTAX_VERSION = Parser.version NomsuCompiler._ENV = NomsuCompiler NomsuCompiler.nomsu = NomsuCompiler @@ -245,7 +207,7 @@ do utils = utils, lpeg = lpeg, re = re, - all_files = all_files, + files = files, next = next, unpack = unpack, setmetatable = setmetatable, @@ -299,7 +261,7 @@ do NomsuCompiler.LOADED = { } NomsuCompiler.AST = AST NomsuCompiler.compile_error = function(self, tok, err_format_string, ...) - local file = FILE_CACHE[tok.source.filename] + local file = files.read(tok.source.filename) local line_no = pos_to_line(file, tok.source.start) local line_start = LINE_STARTS[file][line_no] local src = colored.dim(file:sub(line_start, tok.source.start - 1)) @@ -422,8 +384,8 @@ do source = nil end source = source or (to_run.source or Source(to_run, 1, #to_run)) - if not rawget(FILE_CACHE, source.filename) then - FILE_CACHE[source.filename] = to_run + if not files.read(source.filename) then + files.spoof(source.filename, to_run) end local tree if AST.is_syntax_tree(to_run) then @@ -465,7 +427,7 @@ do return self.LOADED[filename] end local ret = nil - for filename in all_files(filename) do + for filename in files.walk(filename) do local _continue_0 = false repeat do @@ -493,14 +455,14 @@ do end insert(_running_files, filename) if match(filename, "%.lua$") then - local file = assert(FILE_CACHE[filename], "Could not find file: " .. tostring(filename)) + local file = assert(files.read(filename), "Could not find file: " .. tostring(filename)) ret = self:run_lua(file, Source(filename, 1, #file)) elseif match(filename, "%.nom$") or match(filename, "^/dev/fd/[012]$") then local ran_lua if self.can_optimize(filename) then local lua_filename = gsub(filename, "%.nom$", ".lua") do - local file = FILE_CACHE[lua_filename] + local file = files.read(lua_filename) if file then ret = self:run_lua(file, Source(lua_filename, 1, #file)) ran_lua = true @@ -508,7 +470,7 @@ do end end if not (ran_lua) then - local file = file or FILE_CACHE[filename] + local file = file or files.read(filename) if not file then error("File does not exist: " .. tostring(filename), 0) end @@ -551,7 +513,11 @@ do local map = { } local offset = 1 source = source or lua.source - local nomsu_str = tostring(FILE_CACHE[source.filename]:sub(source.start, source.stop)) + local file = files.read(source.filename) + if not file then + error("Failed to find file: " .. tostring(source.filename)) + end + local nomsu_str = tostring(file:sub(source.start, source.stop)) local lua_line = 1 local nomsu_line = pos_to_line(nomsu_str, source.start) local fn @@ -684,7 +650,7 @@ do local bit_lua = self:compile(bit) if not (bit_lua.is_value) then local src = ' ' .. gsub(tostring(self:tree_to_nomsu(bit)), '\n', '\n ') - local line = tostring(bit.source.filename) .. ":" .. tostring(pos_to_line(FILE_CACHE[bit.source.filename], bit.source.start)) + local line = tostring(bit.source.filename) .. ":" .. tostring(pos_to_line(files.read(bit.source.filename), bit.source.start)) self:compile_error(bit, "Cannot use:\n%s\nas a string interpolation value, since it's not an expression.") end if #lua.bits > 0 then diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index 56ef86e..e16af76 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -13,6 +13,7 @@ lpeg = require 'lpeg' re = require 're' lpeg.setmaxstack 10000 utils = require 'utils' +files = require 'files' {:repr, :stringify, :equivalent} = utils export colors, colored colors = require 'consolecolors' @@ -49,37 +50,6 @@ table.fork = (t, values)-> setmetatable(values or {}, {__index:t}) -- Add a ((%x foo %y) where {x:"asdf", y:"fdsa"}) compile-time action for substitution -- Re-implement nomsu-to-lua comment translation? -export FILE_CACHE --- FILE_CACHE is a map from filename (string) -> string of file contents -FILE_CACHE = setmetatable {}, { - __index: (filename)=> - file = io.open(filename) - return nil unless file - contents = file\read("*a") - file\close! - self[filename] = contents - return contents -} - -export all_files -iterate_single = (item, prev) -> if item == prev then nil else item -all_files = (path)-> - -- Sanitize path - if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") - return iterate_single, path - -- TODO: improve sanitization - path = gsub(path,"\\","\\\\") - path = gsub(path,"`","") - path = gsub(path,'"','\\"') - path = gsub(path,"$","") - return coroutine.wrap -> - f = io.popen('find -L "'..path..'" -not -path "*/\\.*" -type f -name "*.nom"') - for line in f\lines! - coroutine.yield(line) - success = f\close! - unless success - error("Invalid file path: "..tostring(path)) - line_counter = re.compile([[ lines <- {| line (%nl line)* |} line <- {} (!%nl .)* @@ -158,7 +128,7 @@ dict = (t)-> setmetatable(t, _dict_mt) MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value NomsuCompiler = setmetatable({}, {__index: (k)=> if _self = rawget(@, "self") then _self[k] else nil}) with NomsuCompiler - .NOMSU_COMPILER_VERSION = 1 + .NOMSU_COMPILER_VERSION = 2 .NOMSU_SYNTAX_VERSION = Parser.version ._ENV = NomsuCompiler .nomsu = NomsuCompiler @@ -167,7 +137,7 @@ with NomsuCompiler -- Discretionary/convenience stuff to_add = { - repr:repr, stringify:stringify, utils:utils, lpeg:lpeg, re:re, all_files:all_files, + repr:repr, stringify:stringify, utils:utils, lpeg:lpeg, re:re, files:files, -- Lua stuff: :next, :unpack, :setmetatable, :coroutine, :rawequal, :getmetatable, :pcall, :error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall, :module, @@ -187,7 +157,7 @@ with NomsuCompiler .AST = AST .compile_error = (tok, err_format_string, ...)=> - file = FILE_CACHE[tok.source.filename] + file = files.read(tok.source.filename) line_no = pos_to_line(file, tok.source.start) line_start = LINE_STARTS[file][line_no] src = colored.dim(file\sub(line_start, tok.source.start-1)) @@ -289,7 +259,7 @@ with NomsuCompiler .run = (to_run, source=nil)=> source or= to_run.source or Source(to_run, 1, #to_run) - if not rawget(FILE_CACHE,source.filename) then FILE_CACHE[source.filename] = to_run + if not files.read(source.filename) then files.spoof(source.filename, to_run) tree = if AST.is_syntax_tree(to_run) then to_run else @parse(to_run, source) if tree == nil -- Happens if pattern matches, but there are no captures, e.g. an empty string return nil @@ -321,7 +291,7 @@ with NomsuCompiler if @LOADED[filename] return @LOADED[filename] ret = nil - for filename in all_files(filename) + for filename in files.walk(filename) if ret = @LOADED[filename] continue @@ -334,16 +304,16 @@ with NomsuCompiler insert _running_files, filename if match(filename, "%.lua$") - file = assert(FILE_CACHE[filename], "Could not find file: #{filename}") + file = assert(files.read(filename), "Could not find file: #{filename}") ret = @run_lua file, Source(filename, 1, #file) elseif match(filename, "%.nom$") or match(filename, "^/dev/fd/[012]$") ran_lua = if @.can_optimize(filename) -- Look for precompiled version lua_filename = gsub(filename, "%.nom$", ".lua") - if file = FILE_CACHE[lua_filename] + if file = files.read(lua_filename) ret = @run_lua file, Source(lua_filename, 1, #file) true unless ran_lua - file = file or FILE_CACHE[filename] + file = file or files.read(filename) if not file error("File does not exist: #{filename}", 0) ret = @run file, Source(filename,1,#file) @@ -368,7 +338,10 @@ with NomsuCompiler map = {} offset = 1 source or= lua.source - nomsu_str = tostring(FILE_CACHE[source.filename]\sub(source.start, source.stop)) + file = files.read(source.filename) + if not file + error "Failed to find file: #{source.filename}" + nomsu_str = tostring(file\sub(source.start, source.stop)) lua_line = 1 nomsu_line = pos_to_line(nomsu_str, source.start) fn = (s)-> @@ -444,7 +417,7 @@ with NomsuCompiler bit_lua = @compile(bit) unless bit_lua.is_value src = ' '..gsub(tostring(@tree_to_nomsu(bit)), '\n','\n ') - line = "#{bit.source.filename}:#{pos_to_line(FILE_CACHE[bit.source.filename], bit.source.start)}" + line = "#{bit.source.filename}:#{pos_to_line(files.read(bit.source.filename), bit.source.start)}" @compile_error bit, "Cannot use:\n%s\nas a string interpolation value, since it's not an expression." if #lua.bits > 0 then lua\append ".." diff --git a/parser.lua b/parser.lua index c04e95c..ad81252 100644 --- a/parser.lua +++ b/parser.lua @@ -132,7 +132,15 @@ do Parser.version = tonumber(v) end }) - local peg_file = io.open("nomsu.peg") or (package.nomsupath and io.open(package.nomsupath .. "/nomsu.peg")) + local peg_file = io.open("nomsu.peg") + if not peg_file and package.nomsupath then + for path in package.nomsupath:gmatch("[^;]+") do + peg_file = io.open(path .. "/nomsu.peg") + if peg_file then + break + end + end + end assert(peg_file, "could not find nomsu.peg file") local nomsu_peg = peg_tidier:match(peg_file:read('*a')) peg_file:close() diff --git a/parser.moon b/parser.moon index a61b3c1..6fe54e6 100644 --- a/parser.moon +++ b/parser.moon @@ -56,7 +56,7 @@ NOMSU_DEFS = with {} return #src+1 err_pos = start_pos line_no = pos_to_line(src, err_pos) - --src = FILE_CACHE[userdata.source.filename] + --src = files.read(userdata.source.filename) line_starts = LINE_STARTS[src] prev_line = line_no == 1 and "" or src\sub(line_starts[line_no-1] or 1, line_starts[line_no]-2) err_line = src\sub(line_starts[line_no], (line_starts[line_no+1] or 0)-2) @@ -100,7 +100,11 @@ NOMSU_PATTERN = do ident <- [a-zA-Z_][a-zA-Z0-9_]* comment <- "--" [^%nl]* ]], {set_version: (v) -> Parser.version = tonumber(v)} - peg_file = io.open("nomsu.peg") or (package.nomsupath and io.open(package.nomsupath.."/nomsu.peg")) + peg_file = io.open("nomsu.peg") + if not peg_file and package.nomsupath + for path in package.nomsupath\gmatch("[^;]+") + peg_file = io.open(path.."/nomsu.peg") + break if peg_file assert(peg_file, "could not find nomsu.peg file") nomsu_peg = peg_tidier\match(peg_file\read('*a')) peg_file\close! diff --git a/tests/metaprogramming.nom b/tests/metaprogramming.nom index e7e22c5..0150350 100644 --- a/tests/metaprogramming.nom +++ b/tests/metaprogramming.nom @@ -1,4 +1,4 @@ -#.. +# Tests for the stuff defined in core/metaprogramming.nom use "core" diff --git a/tests/os.nom b/tests/os.nom index f96009f..c67279e 100644 --- a/tests/os.nom +++ b/tests/os.nom @@ -7,4 +7,7 @@ use "lib/os.nom" %lines <-: lines in: read file "tests/os.nom" assume: %lines.2 = " Tests for the stuff defined in lib/os.nom" +for file %f in "core" + say "\%f" + say "OS test passed."