From 5eb30318de30e9f6e7e73ebd025fa70f13d733b8 Mon Sep 17 00:00:00 2001 From: buckle2000 Date: Fri, 9 Dec 2016 21:02:59 +0800 Subject: [PATCH 1/3] Keep line number, it's optional --- moonscript/compile.moon | 679 +++++++++++++--------------------------- 1 file changed, 211 insertions(+), 468 deletions(-) diff --git a/moonscript/compile.moon b/moonscript/compile.moon index a2b17fee..b54a82f9 100644 --- a/moonscript/compile.moon +++ b/moonscript/compile.moon @@ -1,485 +1,228 @@ +-- assorted utilities for moonc command line tool -util = require "moonscript.util" -dump = require "moonscript.dump" -transform = require "moonscript.transform" - -import NameProxy, LocalName from require "moonscript.transform.names" -import Set from require "moonscript.data" -import ntype, value_can_be_statement from require "moonscript.types" - -statement_compilers = require "moonscript.compile.statement" -value_compilers = require "moonscript.compile.value" - -import concat, insert from table -import pos_to_line, get_closest_line, trim, unpack from util - -mtype = util.moon.type - -indent_char = " " - -local Line, DelayedLine, Lines, Block, RootBlock - --- a buffer for building up lines -class Lines - new: => - @posmap = {} - - mark_pos: (pos, line=#@) => - @posmap[line] = pos unless @posmap[line] - - -- append a line or lines to the buffer - add: (item) => - switch mtype item - when Line - item\render self - when Block - item\render self - else -- also captures DelayedLine - @[#@ + 1] = item - @ - - flatten_posmap: (line_no=0, out={}) => - posmap = @posmap - for i, l in ipairs @ - switch mtype l - when "string", DelayedLine - line_no += 1 - out[line_no] = posmap[i] - - line_no += 1 for _ in l\gmatch"\n" - out[line_no] = posmap[i] - when Lines - _, line_no = l\flatten_posmap line_no, out - else - error "Unknown item in Lines: #{l}" - - out, line_no - - flatten: (indent=nil, buffer={}) => - for i = 1, #@ - l = @[i] - t = mtype l - - if t == DelayedLine - l = l\render! - t = "string" - - switch t - when "string" - insert buffer, indent if indent - insert buffer, l - - -- insert breaks between ambiguous statements - if "string" == type @[i + 1] - lc = l\sub(-1) - if (lc == ")" or lc == "]") and @[i + 1]\sub(1,1) == "(" - insert buffer, ";" - - insert buffer, "\n" - when Lines - l\flatten indent and indent .. indent_char or indent_char, buffer - else - error "Unknown item in Lines: #{l}" - buffer - - __tostring: => - -- strip non-array elements - strip = (t) -> - if "table" == type t - [strip v for v in *t] - else - t - - "Lines<#{util.dump(strip @)\sub 1, -2}>" - --- Buffer for building up a line --- A plain old table holding either strings or Block objects. --- Adding a line to a line will cause that line to be merged in. -class Line - pos: nil - - append_list: (items, delim) => - for i = 1,#items - @append items[i] - if i < #items then insert self, delim - nil - - append: (first, ...) => - if Line == mtype first - -- print "appending line to line", first.pos, first - @pos = first.pos unless @pos -- bubble pos if there isn't one - @append value for value in *first - else - insert self, first - - if ... - @append ... +lfs = require "lfs" - -- todo: try to remove concats from here - render: (buffer) => - current = {} +import split from require "moonscript.util" - add_current = -> - buffer\add concat current - buffer\mark_pos @pos +local * - for chunk in *@ - switch mtype chunk - when Block - for block_chunk in *chunk\render Lines! - if "string" == type block_chunk - insert current, block_chunk - else - add_current! - buffer\add block_chunk - current = {} - else - insert current, chunk +dirsep = package.config\sub 1,1 +dirsep_chars = if dirsep == "\\" + "\\/" -- windows +else + dirsep - if current[1] - add_current! +-- similar to mkdir -p +mkdir = (path) -> + chunks = split path, dirsep - buffer + local accum + for dir in *chunks + accum = accum and "#{accum}#{dirsep}#{dir}" or dir + lfs.mkdir accum - __tostring: => - "Line<#{util.dump(@)\sub 1, -2}>" + lfs.attributes path, "mode" -class DelayedLine - new: (fn) => - @prepare = fn +-- strips excess / and ensures path ends with / +normalize_dir = (path) -> + path\match("^(.-)[#{dirsep_chars}]*$") .. dirsep - prepare: -> +-- parse the directory out of a path +parse_dir = (path) -> + (path\match "^(.-)[^#{dirsep_chars}]*$") - render: => - @prepare! - concat @ +-- parse the filename out of a path +parse_file = (path) -> + (path\match "^.-([^#{dirsep_chars}]*)$") -class Block - header: "do" - footer: "end" +-- converts .moon to a .lua path for calcuating compile target +convert_path = (path) -> + new_path = path\gsub "%.moon$", ".lua" + if new_path == path + new_path = path .. ".lua" + new_path - export_all: false - export_proper: false +format_time = (time) -> + "%.3fms"\format time*1000 - value_compilers: value_compilers - statement_compilers: statement_compilers - - __tostring: => - h = if "string" == type @header - @header - else - unpack @header\render {} +gettime = do + local socket + -> + if socket == nil + pcall -> + socket = require "socket" - "Block<#{h}> <- " .. tostring @parent + unless socket + socket = false - new: (@parent, @header, @footer) => - @_lines = Lines! - - @_names = {} - @_state = {} - @_listeners = {} - - with transform - @transform = { - value: .Value\bind self - statement: .Statement\bind self - } - - if @parent - @root = @parent.root - @indent = @parent.indent + 1 - setmetatable @_state, { __index: @parent._state } - setmetatable @_listeners, { __index: @parent._listeners } - else - @indent = 0 - - set: (name, value) => - @_state[name] = value - - get: (name) => - @_state[name] - - get_current: (name) => - rawget @_state, name - - listen: (name, fn) => - @_listeners[name] = fn - - unlisten: (name) => - @_listeners[name] = nil - - send: (name, ...) => - if fn = @_listeners[name] - fn self, ... - - extract_assign_name: (node) => - is_local = false - real_name = switch mtype node - when LocalName - is_local = true - node\get_name self - when NameProxy - node\get_name self - when "table" - node[1] == "ref" and node[2] - when "string" - -- TOOD: some legacy transfomers might use string for ref - node - - real_name, is_local - - declare: (names) => - undeclared = for name in *names - real_name, is_local = @extract_assign_name name - continue unless is_local or real_name and not @has_name real_name, true - -- this also puts exported names so they can be assigned a new value in - -- deeper scope - @put_name real_name - continue if @name_exported real_name - real_name - - undeclared - - whitelist_names: (names) => - @_name_whitelist = Set names - - name_exported: (name) => - return true if @export_all - return true if @export_proper and name\match"^%u" - - put_name: (name, ...) => - value = ... - value = true if select("#", ...) == 0 - - name = name\get_name self if NameProxy == mtype name - @_names[name] = value - - -- Check if a name is defined in the current or any enclosing scope - -- skip_exports: ignore names that have been exported using `export` - has_name: (name, skip_exports) => - return true if not skip_exports and @name_exported name - - yes = @_names[name] - if yes == nil and @parent - if not @_name_whitelist or @_name_whitelist[name] - @parent\has_name name, true + if socket + socket.gettime() else - yes - - is_local: (node) => - t = mtype node - - return @has_name(node, false) if t == "string" - return true if t == NameProxy or t == LocalName - - if t == "table" - if node[1] == "ref" or (node[1] == "chain" and #node == 2) - return @is_local node[2] - - false - - free_name: (prefix, dont_put) => - prefix = prefix or "moon" - searching = true - name, i = nil, 0 - while searching - name = concat {"", prefix, i}, "_" - i = i + 1 - searching = @has_name name, true - - @put_name name if not dont_put - name - - init_free_var: (prefix, value) => - name = @free_name prefix, true - @stm {"assign", {name}, {value}} - name - - -- add something to the line buffer - add: (item, pos) => - with @_lines - \add item - \mark_pos pos if pos - item - - -- todo: pass in buffer as argument - render: (buffer) => - buffer\add @header - buffer\mark_pos @pos - - if @next - buffer\add @_lines - @next\render buffer - else - -- join an empty block into a single line - if #@_lines == 0 and "string" == type buffer[#buffer] - buffer[#buffer] ..= " " .. (unpack Lines!\add @footer) + nil, "LuaSocket needed for benchmark" + + +import pos_to_line from require "moonscript.util" + +reverse_line_number = (code, line_table, line_num) -> + for i = line_num,0,-1 + if line_table[i] + return pos_to_line code, line_table[i] + "unknown" + +-- compiles file to lua, returns lua code +-- returns nil, error on error +-- returns true if some option handled the output instead +compile_file_text = (text, opts={}) -> + parse = require "moonscript.parse" + compile = require "moonscript.compile" + + parse_time = if opts.benchmark + assert gettime! + + tree, err = parse.string text + return nil, err unless tree + + if parse_time + parse_time = gettime! - parse_time + + if opts.show_parse_tree + dump = require "moonscript.dump" + dump.tree tree + return true + + compile_time = if opts.benchmark + gettime! + + code, posmap_or_err, err_pos = compile.tree tree + + unless code + return nil, compile.format_error posmap_or_err, err_pos, text + + if compile_time + compile_time = gettime() - compile_time + + if opts.show_posmap + import debug_posmap from require "moonscript.util" + print "Pos", "Lua", ">>", "Moon" + print debug_posmap posmap_or_err, text, code + return true + + if opts.benchmark + print table.concat { + opts.fname or "stdin", + "Parse time \t" .. format_time(parse_time), + "Compile time\t" .. format_time(compile_time), + "" + }, "\n" + return true + + if opts.keep_line_number + line_map = {} -- lua line to moon line + for lua_line_number,moon_pos in pairs(posmap_or_err) + line_map[lua_line_number] = reverse_line_number text, posmap_or_err, lua_line_number + aligned_code = "" + lua_line_number = 1 + current_moon_line_number = 1 + for line in string.gmatch(code..'\n', "(.-)\n") + if moon_line_number = line_map[lua_line_number] + to_next_line = false + while current_moon_line_number < moon_line_number + to_next_line = true + aligned_code ..= '\n' + current_moon_line_number += 1 + unless to_next_line + aligned_code ..= ' ' -- add a space else - buffer\add @_lines - buffer\add @footer - buffer\mark_pos @pos - - buffer - - block: (header, footer) => - Block self, header, footer - - line: (...) => - with Line! - \append ... - - is_stm: (node) => - @statement_compilers[ntype node] != nil - - is_value: (node) => - t = ntype node - @value_compilers[t] != nil or t == "value" - - -- compile name for assign - name: (node, ...) => - if type(node) == "string" - node - else - @value node, ... - - value: (node, ...) => - node = @transform.value node - action = if type(node) != "table" - "raw_value" - else - node[1] - - fn = @value_compilers[action] - unless fn - error { - "compile-error" - "Failed to find value compiler for: " .. dump.value node - node[-1] - } - - out = fn self, node, ... - - -- store the pos, creating a line if necessary - if type(node) == "table" and node[-1] - if type(out) == "string" - out = with Line! do \append out - out.pos = node[-1] - - out - - values: (values, delim) => - delim = delim or ', ' - with Line! - \append_list [@value v for v in *values], delim - - stm: (node, ...) => - return if not node -- skip blank statements - node = @transform.statement node - - result = if fn = @statement_compilers[ntype(node)] - fn @, node, ... - else - if value_can_be_statement node - @value node - else - -- coerce value into statement - @stm {"assign", {"_"}, {node}} - - if result - if type(node) == "table" and type(result) == "table" and node[-1] - result.pos = node[-1] - @add result - - nil - - stms: (stms, ret) => - error "deprecated stms call, use transformer" if ret - {:current_stms, :current_stm_i} = @ - - @current_stms = stms - for i=1,#stms - @current_stm_i = i - @stm stms[i] - - @current_stms = current_stms - @current_stm_i = current_stm_i - - nil - - -- takes the existing set of lines and replaces them with the result of - -- calling fn on them - splice: (fn) => - lines = {"lines", @_lines} - @_lines = Lines! - @stms fn lines - -class RootBlock extends Block - new: (@options) => - @root = self - super! - - __tostring: => "RootBlock<>" - - root_stms: (stms) => - unless @options.implicitly_return_root == false - stms = transform.Statement.transformers.root_stms self, stms - @stms stms - - render: => - -- print @_lines - buffer = @_lines\flatten! - buffer[#buffer] = nil if buffer[#buffer] == "\n" - table.concat buffer - -format_error = (msg, pos, file_str) -> - line_message = if pos - line = pos_to_line file_str, pos - line_str, line = get_closest_line file_str, line - line_str = line_str or "" - (" [%d] >> %s")\format line, trim line_str - - concat { - "Compile error: "..msg - line_message - }, "\n" - -value = (value) -> - out = nil - with RootBlock! - \add \value value - out = \render! - out - -tree = (tree, options={}) -> - assert tree, "missing tree" - - scope = (options.scope or RootBlock) options - - runner = coroutine.create -> - scope\root_stms tree - - success, err = coroutine.resume runner - - unless success - error_msg, error_pos = if type(err) == "table" - switch err[1] - when "user-error", "compile-error" - unpack err, 2 - else - -- unknown error, bubble it - error "Unknown error thrown", util.dump error_msg - else - concat {err, debug.traceback runner}, "\n" - - return nil, error_msg, error_pos or scope.last_pos - - lua_code = scope\render! - posmap = scope._lines\flatten_posmap! - lua_code, posmap - --- mmmm -with data = require "moonscript.data" - for name, cls in pairs {:Line, :Lines, :DelayedLine} - data[name] = cls - -{ :tree, :value, :format_error, :Block, :RootBlock } + aligned_code ..= ' ' + -- BUG cannot tell whether it is part of multi-line string + -- there should be \n only if it is a multi-line string + aligned_code ..= line + lua_line_number += 1 + code = aligned_code .. '\n' + + code + +write_file = (fname, code) -> + mkdir parse_dir fname + f, err = io.open fname, "w" + unless f + return nil, err + + assert f\write code + assert f\write "\n" + f\close! + "build" + +compile_and_write = (src, dest, opts={}) -> + f = io.open src + unless f + return nil, "Can't find file" + + text = assert f\read("*a") + f\close! + + code, err = compile_file_text text, opts + + if not code + return nil, err + + if code == true + return true + + if opts.print + print code + return true + + write_file dest, code + +is_abs_path = (path) -> + first = path\sub 1, 1 + if dirsep == "\\" + first == "/" or first == "\\" or path\sub(2,1) == ":" + else + first == dirsep + + +-- calcuate where a path should be compiled to +-- target_dir: the directory to place the file (optional, from -t flag) +-- base_dir: the directory where the file came from when globbing recursively +path_to_target = (path, target_dir=nil, base_dir=nil) -> + target = convert_path path + + if target_dir + target_dir = normalize_dir target_dir + + if base_dir and target_dir + -- one directory back + head = base_dir\match("^(.-)[^#{dirsep_chars}]*[#{dirsep_chars}]?$") + + if head + start, stop = target\find head, 1, true + if start == 1 + target = target\sub(stop + 1) + + if target_dir + if is_abs_path target + target = parse_file target + + target = target_dir .. target + + target + +{ + :dirsep + :mkdir + :normalize_dir + :parse_dir + :parse_file + :convert_path + :gettime + :format_time + :path_to_target + + :compile_file_text + :compile_and_write +} + +print compile_file_text [[x = [i for i in {1,2,3}] +]], {keep_line_number: true} \ No newline at end of file From 7ee0be31509075b25ac9a42b3030a8214c5834b1 Mon Sep 17 00:00:00 2001 From: buckle2000 Date: Fri, 9 Dec 2016 21:15:24 +0800 Subject: [PATCH 2/3] Fix file naming --- moonscript/cmd/moonc.moon | 33 ++ moonscript/compile.moon | 679 ++++++++++++++++++++++++++------------ 2 files changed, 501 insertions(+), 211 deletions(-) diff --git a/moonscript/cmd/moonc.moon b/moonscript/cmd/moonc.moon index b2e92e63..060b8665 100644 --- a/moonscript/cmd/moonc.moon +++ b/moonscript/cmd/moonc.moon @@ -60,6 +60,15 @@ gettime = do else nil, "LuaSocket needed for benchmark" + +import pos_to_line from require "moonscript.util" + +reverse_line_number = (code, line_table, line_num) -> + for i = line_num,0,-1 + if line_table[i] + return pos_to_line code, line_table[i] + "unknown" + -- compiles file to lua, returns lua code -- returns nil, error on error -- returns true if some option handled the output instead @@ -107,6 +116,30 @@ compile_file_text = (text, opts={}) -> }, "\n" return true + if opts.keep_line_number + line_map = {} -- lua line to moon line + for lua_line_number,moon_pos in pairs(posmap_or_err) + line_map[lua_line_number] = reverse_line_number text, posmap_or_err, lua_line_number + aligned_code = "" + lua_line_number = 1 + current_moon_line_number = 1 + for line in string.gmatch(code..'\n', "(.-)\n") + if moon_line_number = line_map[lua_line_number] + to_next_line = false + while current_moon_line_number < moon_line_number + to_next_line = true + aligned_code ..= '\n' + current_moon_line_number += 1 + unless to_next_line + aligned_code ..= ' ' -- add a space + else + aligned_code ..= ' ' + -- BUG cannot tell whether it is part of multi-line string + -- there should be \n only if it is a multi-line string + aligned_code ..= line + lua_line_number += 1 + code = aligned_code .. '\n' + code write_file = (fname, code) -> diff --git a/moonscript/compile.moon b/moonscript/compile.moon index b54a82f9..a2b17fee 100644 --- a/moonscript/compile.moon +++ b/moonscript/compile.moon @@ -1,228 +1,485 @@ --- assorted utilities for moonc command line tool -lfs = require "lfs" +util = require "moonscript.util" +dump = require "moonscript.dump" +transform = require "moonscript.transform" + +import NameProxy, LocalName from require "moonscript.transform.names" +import Set from require "moonscript.data" +import ntype, value_can_be_statement from require "moonscript.types" + +statement_compilers = require "moonscript.compile.statement" +value_compilers = require "moonscript.compile.value" + +import concat, insert from table +import pos_to_line, get_closest_line, trim, unpack from util + +mtype = util.moon.type + +indent_char = " " + +local Line, DelayedLine, Lines, Block, RootBlock + +-- a buffer for building up lines +class Lines + new: => + @posmap = {} + + mark_pos: (pos, line=#@) => + @posmap[line] = pos unless @posmap[line] + + -- append a line or lines to the buffer + add: (item) => + switch mtype item + when Line + item\render self + when Block + item\render self + else -- also captures DelayedLine + @[#@ + 1] = item + @ + + flatten_posmap: (line_no=0, out={}) => + posmap = @posmap + for i, l in ipairs @ + switch mtype l + when "string", DelayedLine + line_no += 1 + out[line_no] = posmap[i] + + line_no += 1 for _ in l\gmatch"\n" + out[line_no] = posmap[i] + when Lines + _, line_no = l\flatten_posmap line_no, out + else + error "Unknown item in Lines: #{l}" + + out, line_no + + flatten: (indent=nil, buffer={}) => + for i = 1, #@ + l = @[i] + t = mtype l + + if t == DelayedLine + l = l\render! + t = "string" + + switch t + when "string" + insert buffer, indent if indent + insert buffer, l + + -- insert breaks between ambiguous statements + if "string" == type @[i + 1] + lc = l\sub(-1) + if (lc == ")" or lc == "]") and @[i + 1]\sub(1,1) == "(" + insert buffer, ";" + + insert buffer, "\n" + when Lines + l\flatten indent and indent .. indent_char or indent_char, buffer + else + error "Unknown item in Lines: #{l}" + buffer + + __tostring: => + -- strip non-array elements + strip = (t) -> + if "table" == type t + [strip v for v in *t] + else + t + + "Lines<#{util.dump(strip @)\sub 1, -2}>" + +-- Buffer for building up a line +-- A plain old table holding either strings or Block objects. +-- Adding a line to a line will cause that line to be merged in. +class Line + pos: nil + + append_list: (items, delim) => + for i = 1,#items + @append items[i] + if i < #items then insert self, delim + nil + + append: (first, ...) => + if Line == mtype first + -- print "appending line to line", first.pos, first + @pos = first.pos unless @pos -- bubble pos if there isn't one + @append value for value in *first + else + insert self, first + + if ... + @append ... -import split from require "moonscript.util" + -- todo: try to remove concats from here + render: (buffer) => + current = {} -local * + add_current = -> + buffer\add concat current + buffer\mark_pos @pos -dirsep = package.config\sub 1,1 -dirsep_chars = if dirsep == "\\" - "\\/" -- windows -else - dirsep + for chunk in *@ + switch mtype chunk + when Block + for block_chunk in *chunk\render Lines! + if "string" == type block_chunk + insert current, block_chunk + else + add_current! + buffer\add block_chunk + current = {} + else + insert current, chunk --- similar to mkdir -p -mkdir = (path) -> - chunks = split path, dirsep + if current[1] + add_current! - local accum - for dir in *chunks - accum = accum and "#{accum}#{dirsep}#{dir}" or dir - lfs.mkdir accum + buffer - lfs.attributes path, "mode" + __tostring: => + "Line<#{util.dump(@)\sub 1, -2}>" --- strips excess / and ensures path ends with / -normalize_dir = (path) -> - path\match("^(.-)[#{dirsep_chars}]*$") .. dirsep +class DelayedLine + new: (fn) => + @prepare = fn --- parse the directory out of a path -parse_dir = (path) -> - (path\match "^(.-)[^#{dirsep_chars}]*$") + prepare: -> --- parse the filename out of a path -parse_file = (path) -> - (path\match "^.-([^#{dirsep_chars}]*)$") + render: => + @prepare! + concat @ --- converts .moon to a .lua path for calcuating compile target -convert_path = (path) -> - new_path = path\gsub "%.moon$", ".lua" - if new_path == path - new_path = path .. ".lua" - new_path +class Block + header: "do" + footer: "end" -format_time = (time) -> - "%.3fms"\format time*1000 + export_all: false + export_proper: false -gettime = do - local socket - -> - if socket == nil - pcall -> - socket = require "socket" + value_compilers: value_compilers + statement_compilers: statement_compilers + + __tostring: => + h = if "string" == type @header + @header + else + unpack @header\render {} - unless socket - socket = false + "Block<#{h}> <- " .. tostring @parent - if socket - socket.gettime() + new: (@parent, @header, @footer) => + @_lines = Lines! + + @_names = {} + @_state = {} + @_listeners = {} + + with transform + @transform = { + value: .Value\bind self + statement: .Statement\bind self + } + + if @parent + @root = @parent.root + @indent = @parent.indent + 1 + setmetatable @_state, { __index: @parent._state } + setmetatable @_listeners, { __index: @parent._listeners } + else + @indent = 0 + + set: (name, value) => + @_state[name] = value + + get: (name) => + @_state[name] + + get_current: (name) => + rawget @_state, name + + listen: (name, fn) => + @_listeners[name] = fn + + unlisten: (name) => + @_listeners[name] = nil + + send: (name, ...) => + if fn = @_listeners[name] + fn self, ... + + extract_assign_name: (node) => + is_local = false + real_name = switch mtype node + when LocalName + is_local = true + node\get_name self + when NameProxy + node\get_name self + when "table" + node[1] == "ref" and node[2] + when "string" + -- TOOD: some legacy transfomers might use string for ref + node + + real_name, is_local + + declare: (names) => + undeclared = for name in *names + real_name, is_local = @extract_assign_name name + continue unless is_local or real_name and not @has_name real_name, true + -- this also puts exported names so they can be assigned a new value in + -- deeper scope + @put_name real_name + continue if @name_exported real_name + real_name + + undeclared + + whitelist_names: (names) => + @_name_whitelist = Set names + + name_exported: (name) => + return true if @export_all + return true if @export_proper and name\match"^%u" + + put_name: (name, ...) => + value = ... + value = true if select("#", ...) == 0 + + name = name\get_name self if NameProxy == mtype name + @_names[name] = value + + -- Check if a name is defined in the current or any enclosing scope + -- skip_exports: ignore names that have been exported using `export` + has_name: (name, skip_exports) => + return true if not skip_exports and @name_exported name + + yes = @_names[name] + if yes == nil and @parent + if not @_name_whitelist or @_name_whitelist[name] + @parent\has_name name, true else - nil, "LuaSocket needed for benchmark" - - -import pos_to_line from require "moonscript.util" - -reverse_line_number = (code, line_table, line_num) -> - for i = line_num,0,-1 - if line_table[i] - return pos_to_line code, line_table[i] - "unknown" - --- compiles file to lua, returns lua code --- returns nil, error on error --- returns true if some option handled the output instead -compile_file_text = (text, opts={}) -> - parse = require "moonscript.parse" - compile = require "moonscript.compile" - - parse_time = if opts.benchmark - assert gettime! - - tree, err = parse.string text - return nil, err unless tree - - if parse_time - parse_time = gettime! - parse_time - - if opts.show_parse_tree - dump = require "moonscript.dump" - dump.tree tree - return true - - compile_time = if opts.benchmark - gettime! - - code, posmap_or_err, err_pos = compile.tree tree - - unless code - return nil, compile.format_error posmap_or_err, err_pos, text - - if compile_time - compile_time = gettime() - compile_time - - if opts.show_posmap - import debug_posmap from require "moonscript.util" - print "Pos", "Lua", ">>", "Moon" - print debug_posmap posmap_or_err, text, code - return true - - if opts.benchmark - print table.concat { - opts.fname or "stdin", - "Parse time \t" .. format_time(parse_time), - "Compile time\t" .. format_time(compile_time), - "" - }, "\n" - return true - - if opts.keep_line_number - line_map = {} -- lua line to moon line - for lua_line_number,moon_pos in pairs(posmap_or_err) - line_map[lua_line_number] = reverse_line_number text, posmap_or_err, lua_line_number - aligned_code = "" - lua_line_number = 1 - current_moon_line_number = 1 - for line in string.gmatch(code..'\n', "(.-)\n") - if moon_line_number = line_map[lua_line_number] - to_next_line = false - while current_moon_line_number < moon_line_number - to_next_line = true - aligned_code ..= '\n' - current_moon_line_number += 1 - unless to_next_line - aligned_code ..= ' ' -- add a space + yes + + is_local: (node) => + t = mtype node + + return @has_name(node, false) if t == "string" + return true if t == NameProxy or t == LocalName + + if t == "table" + if node[1] == "ref" or (node[1] == "chain" and #node == 2) + return @is_local node[2] + + false + + free_name: (prefix, dont_put) => + prefix = prefix or "moon" + searching = true + name, i = nil, 0 + while searching + name = concat {"", prefix, i}, "_" + i = i + 1 + searching = @has_name name, true + + @put_name name if not dont_put + name + + init_free_var: (prefix, value) => + name = @free_name prefix, true + @stm {"assign", {name}, {value}} + name + + -- add something to the line buffer + add: (item, pos) => + with @_lines + \add item + \mark_pos pos if pos + item + + -- todo: pass in buffer as argument + render: (buffer) => + buffer\add @header + buffer\mark_pos @pos + + if @next + buffer\add @_lines + @next\render buffer + else + -- join an empty block into a single line + if #@_lines == 0 and "string" == type buffer[#buffer] + buffer[#buffer] ..= " " .. (unpack Lines!\add @footer) else - aligned_code ..= ' ' - -- BUG cannot tell whether it is part of multi-line string - -- there should be \n only if it is a multi-line string - aligned_code ..= line - lua_line_number += 1 - code = aligned_code .. '\n' - - code - -write_file = (fname, code) -> - mkdir parse_dir fname - f, err = io.open fname, "w" - unless f - return nil, err - - assert f\write code - assert f\write "\n" - f\close! - "build" - -compile_and_write = (src, dest, opts={}) -> - f = io.open src - unless f - return nil, "Can't find file" - - text = assert f\read("*a") - f\close! - - code, err = compile_file_text text, opts - - if not code - return nil, err - - if code == true - return true - - if opts.print - print code - return true - - write_file dest, code - -is_abs_path = (path) -> - first = path\sub 1, 1 - if dirsep == "\\" - first == "/" or first == "\\" or path\sub(2,1) == ":" - else - first == dirsep - - --- calcuate where a path should be compiled to --- target_dir: the directory to place the file (optional, from -t flag) --- base_dir: the directory where the file came from when globbing recursively -path_to_target = (path, target_dir=nil, base_dir=nil) -> - target = convert_path path - - if target_dir - target_dir = normalize_dir target_dir - - if base_dir and target_dir - -- one directory back - head = base_dir\match("^(.-)[^#{dirsep_chars}]*[#{dirsep_chars}]?$") - - if head - start, stop = target\find head, 1, true - if start == 1 - target = target\sub(stop + 1) - - if target_dir - if is_abs_path target - target = parse_file target - - target = target_dir .. target - - target - -{ - :dirsep - :mkdir - :normalize_dir - :parse_dir - :parse_file - :convert_path - :gettime - :format_time - :path_to_target - - :compile_file_text - :compile_and_write -} - -print compile_file_text [[x = [i for i in {1,2,3}] -]], {keep_line_number: true} \ No newline at end of file + buffer\add @_lines + buffer\add @footer + buffer\mark_pos @pos + + buffer + + block: (header, footer) => + Block self, header, footer + + line: (...) => + with Line! + \append ... + + is_stm: (node) => + @statement_compilers[ntype node] != nil + + is_value: (node) => + t = ntype node + @value_compilers[t] != nil or t == "value" + + -- compile name for assign + name: (node, ...) => + if type(node) == "string" + node + else + @value node, ... + + value: (node, ...) => + node = @transform.value node + action = if type(node) != "table" + "raw_value" + else + node[1] + + fn = @value_compilers[action] + unless fn + error { + "compile-error" + "Failed to find value compiler for: " .. dump.value node + node[-1] + } + + out = fn self, node, ... + + -- store the pos, creating a line if necessary + if type(node) == "table" and node[-1] + if type(out) == "string" + out = with Line! do \append out + out.pos = node[-1] + + out + + values: (values, delim) => + delim = delim or ', ' + with Line! + \append_list [@value v for v in *values], delim + + stm: (node, ...) => + return if not node -- skip blank statements + node = @transform.statement node + + result = if fn = @statement_compilers[ntype(node)] + fn @, node, ... + else + if value_can_be_statement node + @value node + else + -- coerce value into statement + @stm {"assign", {"_"}, {node}} + + if result + if type(node) == "table" and type(result) == "table" and node[-1] + result.pos = node[-1] + @add result + + nil + + stms: (stms, ret) => + error "deprecated stms call, use transformer" if ret + {:current_stms, :current_stm_i} = @ + + @current_stms = stms + for i=1,#stms + @current_stm_i = i + @stm stms[i] + + @current_stms = current_stms + @current_stm_i = current_stm_i + + nil + + -- takes the existing set of lines and replaces them with the result of + -- calling fn on them + splice: (fn) => + lines = {"lines", @_lines} + @_lines = Lines! + @stms fn lines + +class RootBlock extends Block + new: (@options) => + @root = self + super! + + __tostring: => "RootBlock<>" + + root_stms: (stms) => + unless @options.implicitly_return_root == false + stms = transform.Statement.transformers.root_stms self, stms + @stms stms + + render: => + -- print @_lines + buffer = @_lines\flatten! + buffer[#buffer] = nil if buffer[#buffer] == "\n" + table.concat buffer + +format_error = (msg, pos, file_str) -> + line_message = if pos + line = pos_to_line file_str, pos + line_str, line = get_closest_line file_str, line + line_str = line_str or "" + (" [%d] >> %s")\format line, trim line_str + + concat { + "Compile error: "..msg + line_message + }, "\n" + +value = (value) -> + out = nil + with RootBlock! + \add \value value + out = \render! + out + +tree = (tree, options={}) -> + assert tree, "missing tree" + + scope = (options.scope or RootBlock) options + + runner = coroutine.create -> + scope\root_stms tree + + success, err = coroutine.resume runner + + unless success + error_msg, error_pos = if type(err) == "table" + switch err[1] + when "user-error", "compile-error" + unpack err, 2 + else + -- unknown error, bubble it + error "Unknown error thrown", util.dump error_msg + else + concat {err, debug.traceback runner}, "\n" + + return nil, error_msg, error_pos or scope.last_pos + + lua_code = scope\render! + posmap = scope._lines\flatten_posmap! + lua_code, posmap + +-- mmmm +with data = require "moonscript.data" + for name, cls in pairs {:Line, :Lines, :DelayedLine} + data[name] = cls + +{ :tree, :value, :format_error, :Block, :RootBlock } From 28eff8f24bcca7b6f3b314906c8528ea1c081128 Mon Sep 17 00:00:00 2001 From: buckle2000 Date: Thu, 10 Aug 2017 22:32:38 -0700 Subject: [PATCH 3/3] Add CLI arg for keep_line_number --- bin/moonc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/moonc b/bin/moonc index 61416327..24a176a9 100755 --- a/bin/moonc +++ b/bin/moonc @@ -21,7 +21,7 @@ local help = [[Usage: %s [options] files/directories... -l Perform lint on the file instead of compiling -b Dump parse and compile time (doesn't write output) -v Print version - + -n Keep line numbers so that every output line maps a input line -- Read from standard in, print to standard out (Must be first and only argument) ]] @@ -248,6 +248,7 @@ else benchmark = opts.b, show_posmap = opts.X, show_parse_tree = opts.T, + keep_line_number = opts.n }) if not success then