Better error reporting and codegen.

This commit is contained in:
Bruce Hill 2018-05-03 16:30:55 -07:00
parent 35a5539185
commit e64a91b8ba
5 changed files with 156 additions and 71 deletions

View File

@ -173,21 +173,29 @@ do
return start + #nodent
end
end)
_with_0.error = function(src, pos, err_msg)
if src:sub(pos, pos):match("[\r\n]") then
pos = pos + #src:match("[ \t\n\r]*", pos)
_with_0.error = function(src, end_pos, start_pos, err_msg)
local seen_errors = lpeg.userdata.errors
if seen_errors[start_pos] then
return true
end
local line_no = 1
local text_loc = lpeg.userdata.source_code.source:sub(pos, pos)
line_no = text_loc:get_line_number()
local err_pos = start_pos
local text_loc = lpeg.userdata.source_code.source:sub(err_pos, err_pos)
local line_no = text_loc:get_line_number()
src = FILE_CACHE[text_loc.filename]
local prev_line = src:sub(LINE_STARTS[src][line_no - 1] or 1, LINE_STARTS[src][line_no] - 2)
local prev_line = line_no == 1 and "" or src:sub(LINE_STARTS[src][line_no - 1] or 1, LINE_STARTS[src][line_no] - 2)
local err_line = src:sub(LINE_STARTS[src][line_no], (LINE_STARTS[src][line_no + 1] or 0) - 2)
local next_line = src:sub(LINE_STARTS[src][line_no + 1] or -1, (LINE_STARTS[src][line_no + 2] or 0) - 2)
local pointer = ("-"):rep(pos - LINE_STARTS[src][line_no]) .. "^"
err_msg = (err_msg or "Parse error") .. " in " .. tostring(lpeg.userdata.source_code.source.filename) .. " on line " .. tostring(line_no) .. ":\n"
err_msg = err_msg .. "\n" .. tostring(prev_line) .. "\n" .. tostring(err_line) .. "\n" .. tostring(pointer) .. "\n" .. tostring(next_line) .. "\n"
return error(err_msg)
local pointer = ("-"):rep(err_pos - LINE_STARTS[src][line_no]) .. "^"
err_msg = (err_msg or "Parse error") .. " at " .. tostring(lpeg.userdata.source_code.source.filename) .. ":" .. tostring(line_no) .. ":\n"
if #prev_line > 0 then
err_msg = err_msg .. ("\n" .. prev_line)
end
err_msg = err_msg .. "\n" .. tostring(err_line) .. "\n" .. tostring(pointer)
if #next_line > 0 then
err_msg = err_msg .. ("\n" .. next_line)
end
seen_errors[start_pos] = err_msg
return true
end
NOMSU_DEFS = _with_0
end
@ -350,13 +358,30 @@ do
source_code = nomsu_code,
indent_stack = {
""
}
},
errors = { }
}
local old_userdata
old_userdata, lpeg.userdata = lpeg.userdata, userdata
local tree = NOMSU_PATTERN:match(tostring(nomsu_code))
lpeg.userdata = old_userdata
assert(tree, "In file " .. tostring(colored.blue(filename)) .. " failed to parse:\n" .. tostring(colored.onyellow(colored.black(nomsu_code))))
if next(userdata.errors) then
local keys = utils.keys(userdata.errors)
table.sort(keys)
local errors
do
local _accum_0 = { }
local _len_0 = 1
for _index_0 = 1, #keys do
local k = keys[_index_0]
_accum_0[_len_0] = userdata.errors[k]
_len_0 = _len_0 + 1
end
errors = _accum_0
end
error(concat(errors, "\n\n"), 0)
end
return tree
end,
run = function(self, nomsu_code, compile_fn)
@ -950,7 +975,7 @@ do
end
if arg and debug_getinfo(2).func ~= require then
colors = require('consolecolors')
local parser = re.compile([[ args <- {| (flag ";")* {:inputs: {| ({file} ";")* |} :} |} ";"? !.
local parser = re.compile([[ args <- {| (flag ";")* {:inputs: {| ({file} ";")* |} :} {:nomsu_args: {| ("--;" ({[^;]*} ";")*)? |} :} ";"? |} !.
flag <-
{:interactive: ("-i" -> true) :}
/ {:verbose: ("-v" -> true) :}
@ -971,7 +996,7 @@ if arg and debug_getinfo(2).func ~= require then
if not args or args.help then
print([=[Nomsu Compiler
Usage: (lua nomsu.lua | moon nomsu.moon) [-i] [-O] [-f] [-s] [--help] [-o output] [-p print_file] file1 file2...
Usage: (lua nomsu.lua | moon nomsu.moon) [-i] [-O] [-f] [-s] [--help] [-o output] [-p print_file] file1 file2... [-- nomsu args...]
OPTIONS
-i Run the compiler in interactive mode (REPL)
@ -987,6 +1012,7 @@ OPTIONS
os.exit()
end
local nomsu = NomsuCompiler()
nomsu.environment.arg = args.nomsu_args
local ok, to_lua = pcall(function()
return require('moonscript.base').to_lua
end)
@ -1177,17 +1203,21 @@ OPTIONS
return output_file:flush()
end
end
local parse_errs = { }
local _list_0 = args.inputs
for _index_0 = 1, #_list_0 do
local input = _list_0[_index_0]
if args.syntax then
for input_file in all_files(input) do
nomsu:parse(io.open(input_file):read("*a"))
end
if print_file then
print_file:write("All files parsed successfully!\n")
local err
ok, err = pcall(nomsu.parse, nomsu, Nomsu(input_file, io.open(input_file):read("*a")))
if not ok then
insert(parse_errs, err)
elseif print_file then
print_file:write("Parse succeeded: " .. tostring(input_file) .. "\n")
print_file:flush()
end
end
elseif args.format then
for input_file in all_files(input) do
local tree = nomsu:parse(io.open(input_file):read("*a"))
@ -1207,6 +1237,13 @@ OPTIONS
nomsu:run_file(input, compile_fn)
end
end
if #parse_errs > 0 then
io.stderr:write(concat(parse_errs, "\n\n"))
io.stderr:flush()
os.exit(false, true)
elseif args.syntax then
os.exit(true, true)
end
if args.interactive then
while true do
io.write(colored.bright(colored.yellow(">> ")))

View File

@ -155,21 +155,27 @@ NOMSU_DEFS = with {}
if @sub(start, start+#nodent-1) == nodent
return start + #nodent
.error = (src,pos,err_msg)->
--src = tostring(FILE_CACHE[lpeg.userdata.source_code.source.filename])
if src\sub(pos,pos)\match("[\r\n]")
pos += #src\match("[ \t\n\r]*", pos)
line_no = 1
text_loc = lpeg.userdata.source_code.source\sub(pos,pos)
.error = (src,end_pos,start_pos,err_msg)->
seen_errors = lpeg.userdata.errors
if seen_errors[start_pos]
return true
err_pos = start_pos
--if src\sub(err_pos,err_pos)\match("[\r\n]")
-- err_pos += #src\match("[ \t\n\r]*", err_pos)
text_loc = lpeg.userdata.source_code.source\sub(err_pos,err_pos)
line_no = text_loc\get_line_number!
src = FILE_CACHE[text_loc.filename]
prev_line = src\sub(LINE_STARTS[src][line_no-1] or 1, LINE_STARTS[src][line_no]-2)
prev_line = line_no == 1 and "" or src\sub(LINE_STARTS[src][line_no-1] or 1, LINE_STARTS[src][line_no]-2)
err_line = src\sub(LINE_STARTS[src][line_no], (LINE_STARTS[src][line_no+1] or 0)-2)
next_line = src\sub(LINE_STARTS[src][line_no+1] or -1, (LINE_STARTS[src][line_no+2] or 0)-2)
pointer = ("-")\rep(pos-LINE_STARTS[src][line_no]) .. "^"
err_msg = (err_msg or "Parse error").." in #{lpeg.userdata.source_code.source.filename} on line #{line_no}:\n"
err_msg ..="\n#{prev_line}\n#{err_line}\n#{pointer}\n#{next_line}\n"
error(err_msg)
pointer = ("-")\rep(err_pos-LINE_STARTS[src][line_no]) .. "^"
err_msg = (err_msg or "Parse error").." at #{lpeg.userdata.source_code.source.filename}:#{line_no}:\n"
if #prev_line > 0 then err_msg ..= "\n"..prev_line
err_msg ..= "\n#{err_line}\n#{pointer}"
if #next_line > 0 then err_msg ..= "\n"..next_line
--error(err_msg)
seen_errors[start_pos] = err_msg
return true
setmetatable(NOMSU_DEFS, {__index:(key)=>
make_node = (start, value, stop)->
@ -279,7 +285,6 @@ class NomsuCompiler
-- TODO: repair
error("Not currently functional.", 0)
-- TODO: figure out whether indent/dedent should affect first line
dedent: (code)=>
unless code\find("\n")
return code
@ -307,7 +312,7 @@ class NomsuCompiler
FILE_CACHE[filename] = nomsu_code
nomsu_code = Nomsu(filename, nomsu_code)
userdata = {
source_code:nomsu_code, indent_stack: {""}
source_code:nomsu_code, indent_stack: {""}, errors: {},
}
old_userdata, lpeg.userdata = lpeg.userdata, userdata
@ -315,6 +320,13 @@ class NomsuCompiler
lpeg.userdata = old_userdata
assert tree, "In file #{colored.blue filename} failed to parse:\n#{colored.onyellow colored.black nomsu_code}"
if next(userdata.errors)
keys = utils.keys(userdata.errors)
table.sort(keys)
errors = [userdata.errors[k] for k in *keys]
error(concat(errors, "\n\n"), 0)
return tree
_nomsu_chunk_counter = 0
@ -663,7 +675,7 @@ if arg and debug_getinfo(2).func != require
export colors
colors = require 'consolecolors'
parser = re.compile([[
args <- {| (flag ";")* {:inputs: {| ({file} ";")* |} :} |} ";"? !.
args <- {| (flag ";")* {:inputs: {| ({file} ";")* |} :} {:nomsu_args: {| ("--;" ({[^;]*} ";")*)? |} :} ";"? |} !.
flag <-
{:interactive: ("-i" -> true) :}
/ {:verbose: ("-v" -> true) :}
@ -681,7 +693,7 @@ if arg and debug_getinfo(2).func != require
print [=[
Nomsu Compiler
Usage: (lua nomsu.lua | moon nomsu.moon) [-i] [-O] [-f] [-s] [--help] [-o output] [-p print_file] file1 file2...
Usage: (lua nomsu.lua | moon nomsu.moon) [-i] [-O] [-f] [-s] [--help] [-o output] [-p print_file] file1 file2... [-- nomsu args...]
OPTIONS
-i Run the compiler in interactive mode (REPL)
@ -697,6 +709,7 @@ OPTIONS
os.exit!
nomsu = NomsuCompiler!
nomsu.environment.arg = args.nomsu_args
ok, to_lua = pcall -> require('moonscript.base').to_lua
if not ok then to_lua = nil
@ -820,13 +833,16 @@ OPTIONS
output_file\write("local IMMEDIATE = true;\n"..tostring(code))
output_file\flush!
parse_errs = {}
for input in *args.inputs
if args.syntax
-- Check syntax:
for input_file in all_files(input)
nomsu\parse(io.open(input_file)\read("*a"))
if print_file
print_file\write("All files parsed successfully!\n")
ok,err = pcall nomsu.parse, nomsu, Nomsu(input_file, io.open(input_file)\read("*a"))
if not ok
insert parse_errs, err
elseif print_file
print_file\write("Parse succeeded: #{input_file}\n")
print_file\flush!
elseif args.format
-- Auto-format
@ -844,6 +860,13 @@ OPTIONS
else
nomsu\run_file(input, compile_fn)
if #parse_errs > 0
io.stderr\write concat(parse_errs, "\n\n")
io.stderr\flush!
os.exit(false, true)
elseif args.syntax
os.exit(true, true)
if args.interactive
-- REPL
while true

View File

@ -1,13 +1,15 @@
file (File):
{| shebang?
(ignored_line %nl)*
statement (nodent statement)*
statement ((nodent (statement / (({} ([^%nl]* -> "Error while parsing line")) => error)))
/ (({} ((%nl %dedent) ->"Indentation error")) => error))*
(%nl ignored_line)*
(!. / (("" -> "Parse error") => error)?) |} -> Tuple
|} -> Tuple
(!. / (({} (.* -> "Parse error")) => error))
shebang: "#!" [^%nl]* (!. / %nl)
statement: action / expression
statement: (action / expression) (eol / (({} ([^%nl]* -> "Error while parsing line")) => error))
inline_statement: inline_action / inline_expression
inline_block (Block):
@ -24,16 +26,20 @@ index_chain (IndexChain):
noindex_inline_expression:
number / variable / inline_text / inline_list / inline_dict / inline_nomsu
/ ("(" %ws* (inline_block / inline_action / inline_expression) %ws* ")")
/ (
"(" %ws* (inline_block / inline_action / inline_expression) %ws*
(")"
/ (({} ((!. / &%nl) -> 'Expected to find a ) before the end of the line')) => error)
/ (({} ([^%nl]* -> 'Error while parsing subexpression')) => error))
)
inline_expression:
index_chain / noindex_inline_expression
indented_expression:
indented_text / indented_nomsu / indented_list / indented_dict
/ ("(..)"? indent
(action dedent
/ expression dedent
/ block (dedent / (("" -> "Error while parsing indented expression") => error)))
(block / action / expression)
(dedent / (({} (non_dedent_error -> "Error while parsing indented expression")) => error))
)
expression:
inline_expression / (":" %ws* (inline_block / inline_action / inline_expression) eol) / indented_expression
@ -52,7 +58,8 @@ inline_text (Text):
'"' ({|
({~ (('\"' -> '"') / ('\\' -> '\') / %escaped_char / [^%nl\"])+ ~}
/ inline_text_interpolation)*
|} -> Tuple) '"'
|} -> Tuple) ('"' / (({} ([^%nl]*->'Failed to find a closing " mark on the same line')) => error))
-- Have to use "%indent" instead of "indent" etc. to avoid messing up text lines that start with "#"
indented_text (Text):
'".."' eol %nl ({|
@ -60,11 +67,16 @@ indented_text (Text):
({~
(("\\" -> "\") / (("\" nodent "..") -> "")/ (%nl+ {~ %nodent -> "" ~}) / [^%nl\] / (!text_interpolation "\"))+
~} / text_interpolation)*
|} -> Tuple) (((!.) &%dedent) / (&(%nl %dedent)) / (("" -> "Error while parsing Text") => error))
|} -> Tuple) (((!.) &%dedent) / (&(%nl %dedent)) / (({} (non_dedent_error -> "Error while parsing Text")) => error))
inline_text_interpolation:
"\" (
variable / inline_list / inline_dict / inline_text
/ ("(" %ws* (inline_block / inline_action / inline_expression) %ws* ")")
/ ("("
%ws* (inline_block / inline_action / inline_expression) %ws*
(")"
/ (({} (&%nl -> 'Expected to find a ")" before the end of the line')) => error)
/ (({} ([^%nl]* -> 'Error while parsing text interpolation')) => error))
)
)
text_interpolation:
inline_text_interpolation /
@ -78,12 +90,13 @@ variable (Var): "%" { plain_word? }
inline_list (List):
!('[..]')
"[" %ws* ({| (inline_list_item (comma inline_list_item)* comma?)? |} -> Tuple) %ws* "]"
"[" %ws* ({| (inline_list_item (comma inline_list_item)* comma?)? |} -> Tuple) %ws*
("]" / (({} ([^%nl]*->"Failed to find a closing ] on the same line")) => error))
indented_list (List):
"[..]" indent ({|
list_line (nodent list_line)*
|} -> Tuple)
(dedent / (("" -> "Error while parsing list") => error))
(dedent / (({} (non_dedent_error -> "Error while parsing list")) => error))
list_line:
((action / expression) !comma)
/ (inline_list_item (comma list_line?)?)
@ -91,17 +104,20 @@ inline_list_item: inline_block / inline_action / inline_expression
inline_dict (Dict):
!('{..}')
"{" %ws* ({| (inline_dict_item (comma inline_dict_item)* comma?)? |} -> Tuple) %ws* "}"
"{" %ws* ({| (inline_dict_item (comma inline_dict_item)*)? |} -> Tuple) %ws*
("}"
/ (({} (%ws* comma? (!. / &%nl)->"Failed to find a closing } on the same line")) => error)
/ (({} ([^%nl]*->"Error while parsing dictionary")) => error))
indented_dict (Dict):
"{..}" indent ({|
dict_line (nodent dict_line)*
|} -> Tuple)
(dedent / (("" -> "Error while parsing dict") => error))
(dedent / (({} (non_dedent_error -> "Error while parsing dict")) => error))
dict_line:
((dict_key %ws* ":" %ws* (action / expression)) -> DictEntry !comma)
/ (inline_dict_item (comma dict_line?)?)
inline_dict_item:
(dict_key %ws* ":" %ws* (inline_block / inline_action / inline_expression)) -> DictEntry
((dict_key %ws* (":" %ws* (inline_block / inline_action / inline_expression)?)?)-> DictEntry)
dict_key:
(({} ({|{%operator / (!number plain_word)}|} -> Tuple) {}) -> Text) / inline_expression
@ -113,6 +129,7 @@ ignored_line: (%nodent (block_comment / line_comment)) / (%ws* (!. / &%nl))
indent: eol (%nl ignored_line)* %nl %indent ((block_comment/line_comment) (%nl ignored_line)* nodent)?
nodent: eol (%nl ignored_line)* %nl %nodent
dedent: eol (%nl ignored_line)* (((!.) &%dedent) / (&(%nl %dedent)))
non_dedent_error: (!dedent .)* eol (%nl ignored_line)* (!. / &%nl)
comma: %ws* "," %ws*
dotdot: nodent ".." %ws*
plain_word: ([a-zA-Z0-9_] / %utf8_char)+

View File

@ -153,7 +153,7 @@ Tree("Block", {
return nomsu
end
})
local math_expression = re.compile([[ "%" (" " [*/^+-] " %")+ !. ]])
local math_expression = re.compile([[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]])
Tree("Action", {
as_lua = function(self, nomsu)
local stub = self:get_stub()
@ -578,7 +578,7 @@ Tree("Dict", {
local line, src = key.source:get_line(), key.source:get_text()
error(tostring(line) .. ": Cannot use " .. tostring(colored.yellow(src)) .. " as a dict key, since it's not an expression.", 0)
end
local value_lua = entry.value:as_lua(nomsu)
local value_lua = entry.value and entry.value:as_lua(nomsu) or Lua.Value(entry.key.source, "true")
if not (value_lua.is_value) then
local line, src = value.source:get_line(), value.source:get_text()
error(tostring(line) .. ": Cannot use " .. tostring(colored.yellow(src)) .. " as a dict value, since it's not an expression.", 0)
@ -624,7 +624,7 @@ Tree("Dict", {
if entry.key.type == "Action" or entry.key.type == "Block" then
key_nomsu:parenthesize()
end
local value_nomsu = entry.value:as_nomsu(true)
local value_nomsu = entry.value and entry.value:as_nomsu(true) or Nomsu(entry.key.source, "")
if not (value_nomsu) then
return nil
end
@ -652,12 +652,15 @@ Tree("Dict", {
if entry.key.type == "Action" or entry.key.type == "Block" then
key_nomsu:parenthesize()
end
local value_nomsu = entry.value:as_nomsu(true)
local value_nomsu = entry.value and entry.value:as_nomsu(true) or Nomsu(entry.key.source, "")
if value_nomsu and #line + #", " + #key_nomsu + #":" + #value_nomsu <= MAX_LINE then
if #line.bits > 1 then
line:append(", ")
end
line:append(key_nomsu, ":", value_nomsu)
line:append(key_nomsu)
if entry.value then
line:append(":", value_nomsu)
end
else
if not (value_nomsu) then
value_nomsu = entry.value:as_nomsu()
@ -669,7 +672,10 @@ Tree("Dict", {
nomsu:append(line)
line = Nomsu(bit.source, "\n ")
end
line:append(key_nomsu, ":", value_nomsu)
line:append(key_nomsu)
if entry.value then
line:append(":", value_nomsu)
end
end
end
if #line.bits > 1 then
@ -694,7 +700,7 @@ Tree("IndexChain", {
local _continue_0 = false
repeat
local key = self.value[i]
if key.type == 'Text' and #key.value == 1 and type(key.value[1]) == 'string' and key.value[1]:match("^[a-zA-Z_][a-zA-Z0-9_]$") then
if key.type == 'Text' and #key.value == 1 and type(key.value[1]) == 'string' and key.value[1]:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
lua:append("." .. tostring(key.value[1]))
_continue_0 = true
break

View File

@ -103,7 +103,7 @@ Tree "Block",
nomsu\append "\n"
return nomsu
math_expression = re.compile [[ "%" (" " [*/^+-] " %")+ !. ]]
math_expression = re.compile [[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]]
Tree "Action",
as_lua: (nomsu)=>
stub = @get_stub!
@ -365,7 +365,7 @@ Tree "Dict",
unless key_lua.is_value
line, src = key.source\get_line!, key.source\get_text!
error "#{line}: Cannot use #{colored.yellow src} as a dict key, since it's not an expression.", 0
value_lua = entry.value\as_lua(nomsu)
value_lua = entry.value and entry.value\as_lua(nomsu) or Lua.Value(entry.key.source, "true")
unless value_lua.is_value
line, src = value.source\get_line!, value.source\get_text!
error "#{line}: Cannot use #{colored.yellow src} as a dict value, since it's not an expression.", 0
@ -404,7 +404,7 @@ Tree "Dict",
return nil unless key_nomsu
if entry.key.type == "Action" or entry.key.type == "Block"
key_nomsu\parenthesize!
value_nomsu = entry.value\as_nomsu(true)
value_nomsu = entry.value and entry.value\as_nomsu(true) or Nomsu(entry.key.source, "")
return nil unless value_nomsu
if i > 1
nomsu\append ", "
@ -421,11 +421,12 @@ Tree "Dict",
return nil unless key_nomsu
if entry.key.type == "Action" or entry.key.type == "Block"
key_nomsu\parenthesize!
value_nomsu = entry.value\as_nomsu(true)
value_nomsu = entry.value and entry.value\as_nomsu(true) or Nomsu(entry.key.source, "")
if value_nomsu and #line + #", " + #key_nomsu + #":" + #value_nomsu <= MAX_LINE
if #line.bits > 1
line\append ", "
line\append key_nomsu,":",value_nomsu
line\append key_nomsu
if entry.value then line\append ":",value_nomsu
else
unless value_nomsu
value_nomsu = entry.value\as_nomsu!
@ -433,7 +434,8 @@ Tree "Dict",
if #line.bits > 1
nomsu\append line
line = Nomsu(bit.source, "\n ")
line\append key_nomsu,":",value_nomsu
line\append key_nomsu
if entry.value then line\append ":",value_nomsu
if #line.bits > 1
nomsu\append line
return nomsu
@ -450,7 +452,7 @@ Tree "IndexChain",
for i=2,#@value
key = @value[i]
if key.type == 'Text' and #key.value == 1 and type(key.value[1]) == 'string' and key.value[1]\match("^[a-zA-Z_][a-zA-Z0-9_]$")
if key.type == 'Text' and #key.value == 1 and type(key.value[1]) == 'string' and key.value[1]\match("^[a-zA-Z_][a-zA-Z0-9_]*$")
lua\append ".#{key.value[1]}"
continue
key_lua = key\as_lua(nomsu)