diff --git a/code_obj.lua b/code_obj.lua index 990f3bc..2aba03d 100644 --- a/code_obj.lua +++ b/code_obj.lua @@ -12,6 +12,7 @@ Source = immutable({ }, { name = "Source", __new = function(self, filename, start, stop) + assert(type(filename) == 'string') if not start then start, stop = 1, #FILE_CACHE[filename] end @@ -159,7 +160,7 @@ do if type(self.source) == 'string' then self.source = Source:from_string(self.source) end - return assert(self.source) + return assert(self.source and Source:is_instance(self.source)) end, __base = _base_0, __name = "Code" diff --git a/code_obj.moon b/code_obj.moon index e821b7e..17ec816 100644 --- a/code_obj.moon +++ b/code_obj.moon @@ -6,6 +6,7 @@ export LINE_STARTS Source = immutable {"filename","start","stop"}, { name:"Source" __new: (filename, start, stop)=> + assert(type(filename) == 'string') if not start start, stop = 1, #FILE_CACHE[filename] if stop and start > stop+1 then error("Invalid range: #{start}, #{stop}") @@ -71,7 +72,7 @@ class Code @append(...) if type(@source) == 'string' @source = Source\from_string(@source) - assert(@source) + assert(@source and Source\is_instance(@source)) append: (...)=> n = select("#",...) diff --git a/core/text.nom b/core/text.nom index 95451ee..8b75ca4 100644 --- a/core/text.nom +++ b/core/text.nom @@ -27,7 +27,7 @@ lua> ".." }; for name, e in pairs(escapes) do local lua = "'"..e.."'"; - nomsu:define_compile_action(name, function(tree) return Lua.Value(tree.value, lua); end); + nomsu:define_compile_action(name, function(tree) return Lua.Value(tree.source, lua); end); end local colors = { ["reset color"]="\\\\27[0m", bright="\\\\27[1m", dim="\\\\27[2m", underscore="\\\\27[4m", @@ -42,9 +42,9 @@ lua> ".." for name, c in pairs(colors) do local color = "'"..c.."'"; local reset = "'"..colors["reset color"].."'"; - nomsu:define_compile_action(name, function(tree) return Lua.Value(tree.value, color); end); + nomsu:define_compile_action(name, function(tree) return Lua.Value(tree.source, color); end); nomsu:define_compile_action(name.." %", function(\%) - return Lua.Value(tree.value, color, "..", \(% as lua), "..", reset); + return Lua.Value(tree.source, color, "..", \(% as lua), "..", reset); end); end end diff --git a/nomsu.lua b/nomsu.lua index 10c907f..ddad7dd 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -233,7 +233,7 @@ setmetatable(NOMSU_DEFS, { __index = function(self, key) local make_node make_node = function(start, value, stop, userdata) - local source = userdata.source:sub(start, stop) + local source = userdata.source:sub(start, stop - 1) local tree if Types[key].is_multi then tree = Types[key](Tuple(unpack(value)), source) @@ -313,6 +313,24 @@ do define_compile_action = function(self, signature, fn) return self:define_action(signature, fn, true) end, + lua_line_to_nomsu = function(self, source, line_no) + local pos = 0 + local line = 0 + local filename = source:match('"([^[]*)') + for line in FILE_CACHE[filename]:gmatch("[^\n]*\n") do + line = line + 1 + pos = pos + #line + if line == line_no then + break + end + end + for i, entry in ipairs(self.source_map[source]) do + if entry[1] > pos then + return self.source_map[source][i - 1][2] + end + end + return self.source_map[source][#self.source_map[source]][2] + end, parse = function(self, nomsu_code) if type(nomsu_code) == 'string' then _nomsu_chunk_counter = _nomsu_chunk_counter + 1 @@ -419,7 +437,7 @@ do if not file then error("File does not exist: " .. tostring(filename), 0) end - ret = self:run(Nomsu(Source(filename), file), compile_fn) + ret = self:run(Nomsu(Source(filename, 1, #file), file), compile_fn) else error("Invalid filetype for " .. tostring(filename), 0) end @@ -437,7 +455,7 @@ do run_lua = function(self, lua) assert(type(lua) ~= 'string', "Attempt to run lua string instead of Lua (object)") local lua_string = tostring(lua) - local run_lua_fn, err = load(lua_string, filename, "t", self.environment) + local run_lua_fn, err = load(lua_string, tostring(lua.source), "t", self.environment) if not run_lua_fn then local n = 1 local fn @@ -448,6 +466,69 @@ do local line_numbered_lua = "1 |" .. lua_string:gsub("\n", fn) error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(line_numbered_lua)))) .. "\n\n" .. tostring(err), 0) end + if not (self.source_map[tostring(lua.source)]) then + local map = { } + local offset = 1 + local source = lua.source + local nomsu_raw = FILE_CACHE[source.filename]:sub(source.start, source.stop) + local get_line_starts + get_line_starts = function(s) + local starts = { } + local pos = 1 + for line in s:gmatch("[^\n]*\n") do + insert(starts, pos) + pos = pos + #line + end + insert(starts, pos) + return starts + end + local nomsu_line_to_pos = get_line_starts(nomsu_raw) + local lua_line_to_pos = get_line_starts(tostring(lua)) + local pos_to_line + pos_to_line = function(line_starts, pos) + local lo, hi = 1, #line_starts + while lo <= hi do + local mid = math.floor((lo + hi) / 2) + if line_starts[mid] > pos then + hi = mid - 1 + else + lo = mid + 1 + end + end + return hi + end + local lua_line = 1 + local nomsu_line = pos_to_line(nomsu_line_to_pos, lua.source.start) + local fn + fn = function(s) + if type(s) == 'string' then + map[lua_line] = map[lua_line] or { + nomsu_line + } + for nl in s:gmatch("\n") do + lua_line = lua_line + 1 + map[lua_line] = map[lua_line] or { + nomsu_line + } + end + else + local old_line = nomsu_line + if s.source then + nomsu_line = pos_to_line(nomsu_line_to_pos, s.source.start) + if nomsu_line ~= old_line then + insert(map[lua_line], nomsu_line) + end + end + local _list_0 = s.bits + for _index_0 = 1, #_list_0 do + local b = _list_0[_index_0] + fn(b) + end + end + end + fn(lua) + self.source_map[tostring(lua.source)] = map + end return run_lua_fn() end, tree_to_lua = function(self, tree) @@ -1185,6 +1266,7 @@ do self.file_metadata = setmetatable({ }, { __mode = "k" }) + self.source_map = { } self.environment = { nomsu = self, repr = repr, @@ -1407,6 +1489,27 @@ OPTIONS break end end + do + local map = nomsu.source_map[info.source] + if map then + if info.currentline then + info.currentline = (map[info.currentline] or { + info.currentline + })[1] + end + if info.linedefined then + info.linedefined = (map[info.linedefined] or { + info.linedefined + })[1] + end + if info.lastlinedefined then + info.lastlinedefined = (map[info.lastlinedefined] or { + info.lastlinedefined + })[1] + end + info.short_src = info.source:match('"([^[]*)') + end + end local _ = [=[ if metadata = nomsu.action_metadata[info.func] info.name = metadata.aliases[1] filename = if type(metadata.source) == 'string' diff --git a/nomsu.moon b/nomsu.moon index 883b702..36911bd 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -202,7 +202,7 @@ NOMSU_DEFS = with {} setmetatable(NOMSU_DEFS, {__index:(key)=> make_node = (start, value, stop, userdata)-> - source = userdata.source\sub(start, stop) + source = userdata.source\sub(start, stop-1) tree = if Types[key].is_multi Types[key](Tuple(unpack(value)), source) else Types[key](value, source) @@ -242,6 +242,7 @@ class NomsuCompiler return id }) @file_metadata = setmetatable({}, {__mode:"k"}) + @source_map = {} @environment = { -- Discretionary/convenience stuff @@ -324,6 +325,20 @@ class NomsuCompiler define_compile_action: (signature, fn)=> return @define_action(signature, fn, true) + lua_line_to_nomsu: (source, line_no)=> + pos = 0 + line = 0 + filename = source\match('"([^[]*)') + for line in FILE_CACHE[filename]\gmatch("[^\n]*\n") + line += 1 + pos += #line + if line == line_no + break + for i, entry in ipairs @source_map[source] + if entry[1] > pos + return @source_map[source][i-1][2] + return @source_map[source][#@source_map[source]][2] + _nomsu_chunk_counter = 0 parse: (nomsu_code)=> if type(nomsu_code) == 'string' @@ -393,7 +408,7 @@ class NomsuCompiler file = file or FILE_CACHE[filename] if not file error("File does not exist: #{filename}", 0) - ret = @run(Nomsu(Source(filename), file), compile_fn) + ret = @run(Nomsu(Source(filename,1,#file), file), compile_fn) else error("Invalid filetype for #{filename}", 0) loaded[filename] = ret or true @@ -405,7 +420,7 @@ class NomsuCompiler run_lua: (lua)=> assert(type(lua) != 'string', "Attempt to run lua string instead of Lua (object)") lua_string = tostring(lua) - run_lua_fn, err = load(lua_string, filename, "t", @environment) + run_lua_fn, err = load(lua_string, tostring(lua.source), "t", @environment) if not run_lua_fn n = 1 fn = -> @@ -413,6 +428,53 @@ class NomsuCompiler ("\n%-3d|")\format(n) line_numbered_lua = "1 |"..lua_string\gsub("\n", fn) error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack line_numbered_lua}\n\n#{err}", 0) + unless @source_map[tostring(lua.source)] + map = {} + offset = 1 + source = lua.source + nomsu_raw = FILE_CACHE[source.filename]\sub(source.start, source.stop) + + get_line_starts = (s)-> + starts = {} + pos = 1 + for line in s\gmatch("[^\n]*\n") + insert starts, pos + pos += #line + insert starts, pos + return starts + + nomsu_line_to_pos = get_line_starts(nomsu_raw) + lua_line_to_pos = get_line_starts(tostring(lua)) + + pos_to_line = (line_starts, pos)-> + -- Binary search for line number of position + lo, hi = 1, #line_starts + while lo <= hi + mid = math.floor((lo+hi)/2) + if line_starts[mid] > pos + hi = mid-1 + else lo = mid+1 + return hi + + lua_line = 1 + nomsu_line = pos_to_line(nomsu_line_to_pos, lua.source.start) + fn = (s)-> + if type(s) == 'string' + map[lua_line] or= {nomsu_line} + for nl in s\gmatch("\n") + lua_line += 1 + map[lua_line] or= {nomsu_line} + else + old_line = nomsu_line + if s.source + nomsu_line = pos_to_line(nomsu_line_to_pos, s.source.start) + if nomsu_line != old_line + insert map[lua_line], nomsu_line + for b in *s.bits do fn(b) + fn(lua) + -- Mapping from lua line number to nomsu line numbers + @source_map[tostring(lua.source)] = map + return run_lua_fn! MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value @@ -993,6 +1055,15 @@ OPTIONS if v == info.func info.name = k break + + if map = nomsu.source_map[info.source] + if info.currentline + info.currentline = (map[info.currentline] or {info.currentline})[1] + if info.linedefined + info.linedefined = (map[info.linedefined] or {info.linedefined})[1] + if info.lastlinedefined + info.lastlinedefined = (map[info.lastlinedefined] or {info.lastlinedefined})[1] + info.short_src = info.source\match('"([^[]*)') [=[ if metadata = nomsu.action_metadata[info.func] info.name = metadata.aliases[1]