Improve parsing errors

This commit is contained in:
Mateo Feron 2020-09-01 17:11:21 +02:00
parent af1de6d8fb
commit 577a1aef94
7 changed files with 129 additions and 37 deletions

View file

@ -9,7 +9,7 @@ BINDIR=.
NAME = $(shell readlink -f . | xargs basename) NAME = $(shell readlink -f . | xargs basename)
# global links # global links
LDFLAGS = -lpthread LDFLAGS = -Wl,--no-as-needed -lpthread
# compiler # compiler
CC=g++ CC=g++

View file

@ -7,6 +7,8 @@
#include <exception> #include <exception>
#include <stdexcept> #include <stdexcept>
#include <ztd/filedat.hpp>
#define INDENT indent(ind) #define INDENT indent(ind)
extern std::string indenting_string; extern std::string indenting_string;
@ -36,4 +38,7 @@ void _exec(std::string const& bin, std::vector<std::string> const& args);
std::string stringReplace(std::string subject, const std::string& search, const std::string& replace); std::string stringReplace(std::string subject, const std::string& search, const std::string& replace);
void printFormatError(ztd::format_error const& e, bool print_line=true);
void printErrorIndex(const char* in, const int index, const std::string& message, const std::string& origin, bool print_line=true);
#endif //UTIL_HPP #endif //UTIL_HPP

View file

@ -44,6 +44,9 @@ std::string pipeline::generate(int ind)
{ {
std::string ret; std::string ret;
if(cmds.size()<=0)
return "";
ret += cmds[0].generate(ind); ret += cmds[0].generate(ind);
for(uint32_t i=1 ; i<cmds.size() ; i++) for(uint32_t i=1 ; i<cmds.size() ; i++)
{ {
@ -57,7 +60,10 @@ std::string pipeline::generate(int ind)
std::string condlist::generate(int ind) std::string condlist::generate(int ind)
{ {
std::string ret; std::string ret;
if(!opt_minimize) ret += INDENT; if(pls.size() <= 0)
return "";
if(!opt_minimize)
ret += INDENT;
ret += pls[0].generate(ind); ret += pls[0].generate(ind);
for(uint32_t i=0 ; i<pls.size()-1 ; i++) for(uint32_t i=0 ; i<pls.size()-1 ; i++)
{ {
@ -114,7 +120,8 @@ std::string generate_resolve(std::vector<std::string> args, int ind)
dir=pwd(); dir=pwd();
std::string cddir=ztd::exec("dirname", g_origin).first; std::string cddir=ztd::exec("dirname", g_origin).first;
cddir.pop_back(); cddir.pop_back();
chdir(cddir.c_str()); if(chdir(cddir.c_str()) != 0)
throw std::runtime_error("Cannot cd to '"+cddir+"'");
} }
@ -150,7 +157,8 @@ std::string generate_resolve(std::vector<std::string> args, int ind)
} }
if(!opts['C'] && !piped) if(!opts['C'] && !piped)
chdir(dir.c_str()); if(chdir(dir.c_str()) != 0)
throw std::runtime_error("Cannot cd to '"+dir+"'");
return ret; return ret;
} }
@ -176,7 +184,8 @@ std::string generate_include(std::vector<std::string> args, int ind)
dir=pwd(); dir=pwd();
std::string cddir=ztd::exec("dirname", curfile).first; std::string cddir=ztd::exec("dirname", curfile).first;
cddir.pop_back(); cddir.pop_back();
chdir(cddir.c_str()); if(chdir(cddir.c_str()) != 0)
throw std::runtime_error("Cannot cd to '"+cddir+"'");
} }
// do shell resolution // do shell resolution
@ -199,7 +208,7 @@ std::string generate_include(std::vector<std::string> args, int ind)
add_include(it) ) // not already included add_include(it) ) // not already included
{ {
file=import_file(it); file=import_file(it);
if(opts['e']) if(opts['d'])
file = stringReplace(file, "\"", "\\\""); file = stringReplace(file, "\"", "\\\"");
if(opts['r']) if(opts['r'])
ret += file; ret += file;
@ -226,7 +235,8 @@ std::string generate_include(std::vector<std::string> args, int ind)
} }
} }
if(!opts['C'] && !piped) if(!opts['C'] && !piped)
chdir(dir.c_str()); if(chdir(dir.c_str()) != 0)
throw std::runtime_error("Cannot cd to '"+dir+"'");
g_origin=curfile; g_origin=curfile;
if(!opts['r']) if(!opts['r'])
@ -321,7 +331,7 @@ std::string block::generate(int ind, bool print_shebang)
// commands // commands
for(auto it: cls) for(auto it: cls)
ret += it.generate(ind+1); ret += it.generate(ind+1);
if(opt_minimize) if(opt_minimize && ret.size()>1)
ret.pop_back(); // ) can be right after command ret.pop_back(); // ) can be right after command
else else
ret += INDENT; ret += INDENT;

View file

@ -65,7 +65,7 @@ int main(int argc, char* argv[])
try try
{ {
block sh(parse(import_file(file))); block sh(parse(import_file(file)));
if(options['E']) if(options['e'])
{ {
std::string data=sh.generate(); std::string data=sh.generate();
// generate path // generate path
@ -94,11 +94,13 @@ int main(int argc, char* argv[])
} }
catch(ztd::format_error& e) catch(ztd::format_error& e)
{ {
printFormatException(e); printFormatError(e);
return 100;
} }
catch(std::exception& e) catch(std::exception& e)
{ {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
return 2;
} }

View file

@ -4,13 +4,12 @@ ztd::option_set options = gen_options();
bool opt_minimize; bool opt_minimize;
bool piped=false; bool piped=false;
ztd::option_set gen_options() ztd::option_set gen_options()
{ {
ztd::option_set ret; ztd::option_set ret;
ret.add(ztd::option('h', "help", false, "Display this help message")); ret.add(ztd::option('h', "help", false, "Display this help message"));
ret.add(ztd::option('m', "minimize", false, "Minimize code")); ret.add(ztd::option('m', "minimize", false, "Minimize code"));
ret.add(ztd::option('E', "exec", false, "Directly exec instead of outputting")); ret.add(ztd::option('e', "exec", false, "Directly exec instead of outputting"));
ret.add(ztd::option("help-commands", false, "Print help for linker commands")); ret.add(ztd::option("help-commands", false, "Print help for linker commands"));
return ret; return ret;
} }

View file

@ -10,7 +10,7 @@ std::string g_origin;
inline bool is_in(char c, const char* set) inline bool is_in(char c, const char* set)
{ {
return index(set, c) != NULL; return strchr(set, c) != NULL;
} }
inline bool is_alphanum(char c) inline bool is_alphanum(char c)
@ -38,6 +38,15 @@ bool word_eq(const char* word, const char* in, uint32_t size, uint32_t start, co
return false; return false;
} }
std::string get_word(const char* in, uint32_t size, uint32_t start, const char* end_set)
{
uint32_t i=start;
while(i<size && !is_in(in[i], end_set))
i++;
return std::string(in+start, i-start);
}
uint32_t skip_chars(const char* in, uint32_t size, uint32_t start, const char* set) uint32_t skip_chars(const char* in, uint32_t size, uint32_t start, const char* set)
{ {
for(uint32_t i=start; i<size ; i++) for(uint32_t i=start; i<size ; i++)
@ -77,7 +86,11 @@ std::pair<arg, uint32_t> parse_arg(const char* in, uint32_t size, uint32_t start
{ {
arg ret; arg ret;
// j : start of subarg // j : start of subarg
uint32_t i=start,j=start; uint32_t i=start,j=start,q=start;
if(is_in(in[i], "&|;\n#()"))
throw ztd::format_error( strf("Unexpected token '%c'", in[i]) , g_origin, in, i);
while(i<size && !is_in(in[i], " \t|&;\n()")) while(i<size && !is_in(in[i], " \t|&;\n()"))
{ {
if(i+1<size && is_in(in[i], "<>") && in[i+1]=='&') // special case for <& and >& if(i+1<size && is_in(in[i], "<>") && in[i+1]=='&') // special case for <& and >&
@ -93,15 +106,13 @@ std::pair<arg, uint32_t> parse_arg(const char* in, uint32_t size, uint32_t start
} }
else if(in[i] == '"') // start double quote else if(in[i] == '"') // start double quote
{ {
q=i;
i++; i++;
while(i<size && in[i] != '"') // while inside quoted string while(in[i] != '"') // while inside quoted string
{ {
if(in[i] == '\\') // backslash: don't check next char if(in[i] == '\\') // backslash: don't check next char
{ {
i++; i+=2;
if(i>=size)
break;
i++;
} }
else if( word_eq("$(", in, size, i) ) // substitution else if( word_eq("$(", in, size, i) ) // substitution
{ {
@ -115,18 +126,20 @@ std::pair<arg, uint32_t> parse_arg(const char* in, uint32_t size, uint32_t start
} }
else else
i++; i++;
}
if(i>=size) if(i>=size)
break; throw ztd::format_error("Unterminated double quote", g_origin, in, q);
}
i++; i++;
} }
else if(in[i] == '\'') // start single quote else if(in[i] == '\'') // start single quote
{ {
q=i;
i++; i++;
while(i<size && in[i]!='\'') while(i<size && in[i]!='\'')
i++; i++;
if(i>=size) if(i>=size)
break; throw ztd::format_error("Unterminated single quote", g_origin, in, q);
i++; i++;
} }
else if( word_eq("$(", in, size, i) ) // substitution else if( word_eq("$(", in, size, i) ) // substitution
@ -155,10 +168,17 @@ std::pair<arg, uint32_t> parse_arg(const char* in, uint32_t size, uint32_t start
// must start at a read char // must start at a read char
// first char has to be read // first char has to be read
// ends at either &|;\n#() // ends at either &|;\n#()
std::pair<arglist, uint32_t> parse_arglist(const char* in, uint32_t size, uint32_t start) std::pair<arglist, uint32_t> parse_arglist(const char* in, uint32_t size, uint32_t start, bool hard_error=false)
{ {
uint32_t i=start; uint32_t i=start;
arglist ret; arglist ret;
if(is_in(in[i], "&|;\n#(){}"))
{
if(hard_error)
throw ztd::format_error( strf("Unexpected token '%c'", in[i]) , g_origin, in, i);
else
return std::make_pair(ret, i);
}
while(i<size) while(i<size)
{ {
auto pp=parse_arg(in, size, i); auto pp=parse_arg(in, size, i);
@ -178,7 +198,7 @@ std::pair<block, uint32_t> parse_block(const char* in, uint32_t size, uint32_t s
// ends at either &;\n#) // ends at either &;\n#)
std::pair<pipeline, uint32_t> parse_pipeline(const char* in, uint32_t size, uint32_t start) std::pair<pipeline, uint32_t> parse_pipeline(const char* in, uint32_t size, uint32_t start)
{ {
uint32_t i = skip_unread(in, size, start); uint32_t i=start;
pipeline ret; pipeline ret;
while(i<size) while(i<size)
{ {
@ -237,6 +257,9 @@ std::pair<condlist, uint32_t> parse_condlist(const char* in, uint32_t size, uint
throw ztd::format_error( strf("Unexpected token: '%c%c'", in[i], in[i+1]), g_origin, in, i); throw ztd::format_error( strf("Unexpected token: '%c%c'", in[i], in[i+1]), g_origin, in, i);
else // unknown else // unknown
throw ztd::format_error("Unknown error", g_origin, in, i); throw ztd::format_error("Unknown error", g_origin, in, i);
i = skip_unread(in, size, i);
if(i>=size)
throw ztd::format_error( "Unexpected end of file", g_origin, in, i );
} }
return std::make_pair(ret, i); return std::make_pair(ret, i);
} }
@ -248,14 +271,16 @@ std::pair<block, uint32_t> parse_subshell(const char* in, uint32_t size, uint32_
{ {
uint32_t i = skip_unread(in, size, start); uint32_t i = skip_unread(in, size, start);
block ret(block::subshell); block ret(block::subshell);
while(i<size && in[i] != ')') while(in[i] != ')')
{ {
auto pp=parse_condlist(in, size, i); auto pp=parse_condlist(in, size, i);
ret.cls.push_back(pp.first); ret.cls.push_back(pp.first);
i = skip_unread(in, size, pp.second); i = skip_unread(in, size, pp.second);
}
if(i>=size) if(i>=size)
throw ztd::format_error("Expecting )", g_origin, in, start-1); throw ztd::format_error("Expecting )", g_origin, in, start-1);
}
if(ret.cls.size()<=0)
throw ztd::format_error("Subshell is empty", g_origin, in, start-1);
i++; i++;
return std::make_pair(ret,i); return std::make_pair(ret,i);
} }
@ -267,17 +292,18 @@ std::pair<block, uint32_t> parse_brace(const char* in, uint32_t size, uint32_t s
{ {
uint32_t i = skip_unread(in, size, start); uint32_t i = skip_unread(in, size, start);
block ret(block::brace); block ret(block::brace);
while(i<size && in[i] != '}') while(in[i] != '}')
{ {
auto pp=parse_condlist(in, size, i); auto pp=parse_condlist(in, size, i);
ret.cls.push_back(pp.first); ret.cls.push_back(pp.first);
i = skip_unread(in, size, pp.second); i = skip_unread(in, size, pp.second);
if(i>=size)
throw ztd::format_error("Expecting }", g_origin, in, start-1);
if(is_in(in[i], ")")) if(is_in(in[i], ")"))
throw ztd::format_error( strf("Unexpected token: '%c'", in[i]) , g_origin, in, i ); throw ztd::format_error( strf("Unexpected token: '%c'", in[i]) , g_origin, in, i );
} }
if(i>=size) if(ret.cls.size()<=0)
throw ztd::format_error("Expecting }", g_origin, in, start-1); throw ztd::format_error("Brace block is empty", g_origin, in, start-1);
i++; i++;
return std::make_pair(ret,i); return std::make_pair(ret,i);
@ -321,7 +347,7 @@ std::pair<block, uint32_t> parse_cmd(const char* in, uint32_t size, uint32_t sta
} }
else // is a command else // is a command
{ {
auto pp=parse_arglist(in, size, start); auto pp=parse_arglist(in, size, start, true);
ret.args = pp.first; ret.args = pp.first;
i = pp.second; i = pp.second;
} }
@ -335,7 +361,7 @@ std::pair<block, uint32_t> parse_cmd(const char* in, uint32_t size, uint32_t sta
std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t start) std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t start)
{ {
block ret(block::case_block); block ret(block::case_block);
uint32_t i=skip_unread(in, size, start);; uint32_t i=skip_chars(in, size, start, " \t");;
// get the treated argument // get the treated argument
auto pa = parse_arg(in, size, i); auto pa = parse_arg(in, size, i);
@ -343,10 +369,10 @@ std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t st
i=skip_unread(in, size, pa.second); i=skip_unread(in, size, pa.second);
// must be an 'in' // must be an 'in'
if(!word_eq("in", in, size, i)) if(!word_eq("in", in, size, i, " \t\n"))
{ {
auto pp=parse_arg(in, size, i); std::string pp=get_word(in, size, i, " \t\n");
throw ztd::format_error("Unexpected word: '"+pp.first.raw+"', expecting 'in' after case", g_origin, in, i); throw ztd::format_error("Unexpected word: '"+pp+"', expecting 'in' after case", g_origin, in, i);
} }
i=skip_unread(in, size, i+2); i=skip_unread(in, size, i+2);
@ -357,6 +383,8 @@ std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t st
// toto) // toto)
std::pair<arg, list_t> cc; std::pair<arg, list_t> cc;
pa = parse_arg(in, size, i); pa = parse_arg(in, size, i);
if(pa.first.raw == "")
throw ztd::format_error("Empty case value", g_origin, in, i);
cc.first = pa.first; cc.first = pa.first;
i=skip_unread(in, size, pa.second); i=skip_unread(in, size, pa.second);
@ -410,6 +438,8 @@ std::pair<block, uint32_t> parse_case(const char* in, uint32_t size, uint32_t st
std::pair<block, uint32_t> parse_block(const char* in, uint32_t size, uint32_t start) std::pair<block, uint32_t> parse_block(const char* in, uint32_t size, uint32_t start)
{ {
uint32_t i = skip_chars(in, size, start, " \n\t"); uint32_t i = skip_chars(in, size, start, " \n\t");
if(i>=size)
throw ztd::format_error("Unexpected end of file", g_origin, in, i);
std::pair<block, uint32_t> ret; std::pair<block, uint32_t> ret;
if(in[i] == '{') // brace block if(in[i] == '{') // brace block
{ {
@ -431,7 +461,7 @@ std::pair<block, uint32_t> parse_block(const char* in, uint32_t size, uint32_t s
} }
if(ret.first.args.args.size()<=0) if(ret.first.args.args.size()<=0)
{ {
auto pp=parse_arglist(in, size, ret.second); // in case of redirects auto pp=parse_arglist(in, size, ret.second, false); // in case of redirects
ret.second=pp.second; ret.second=pp.second;
ret.first.args=pp.first; ret.first.args=pp.first;
} }

View file

@ -89,3 +89,49 @@ std::string stringReplace(std::string subject, const std::string& search, const
} }
return subject; return subject;
} }
void printFormatError(ztd::format_error const& e, bool print_line)
{
printErrorIndex(e.data(), e.where(), e.what(), e.origin(), print_line);
}
std::string repeatString(std::string const& str, uint32_t n)
{
std::string ret;
for(uint32_t i=0; i<n; i++)
ret += str;
return ret;
}
void printErrorIndex(const char* in, const int index, const std::string& message, const std::string& origin, bool print_line)
{
int i=0, j=0; // j: last newline
int line=1; //n: line #
int in_size=strlen(in);
if(index >= 0)
{
while(i < in_size && i < index)
{
if(in[i] == '\n')
{
line++;
j=i+1;
}
i++;
}
while(i < in_size && in[i]!='\n')
{
i++;
}
}
if(origin != "")
{
fprintf(stderr, "%s:%u:%u: %s\n", origin.c_str(), line, index-j+1, message.c_str());
if(print_line)
{
std::cerr << std::string(in+j, i-j) << std::endl;
std::cerr << repeatString(" ", index-j) << '^' << std::endl;
}
}
}