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)
# global links
LDFLAGS = -lpthread
LDFLAGS = -Wl,--no-as-needed -lpthread
# compiler
CC=g++

View file

@ -7,6 +7,8 @@
#include <exception>
#include <stdexcept>
#include <ztd/filedat.hpp>
#define INDENT indent(ind)
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);
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

View file

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

View file

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

View file

@ -4,13 +4,12 @@ ztd::option_set options = gen_options();
bool opt_minimize;
bool piped=false;
ztd::option_set gen_options()
{
ztd::option_set ret;
ret.add(ztd::option('h', "help", false, "Display this help message"));
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"));
return ret;
}

View file

@ -10,7 +10,7 @@ std::string g_origin;
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)
@ -38,6 +38,15 @@ bool word_eq(const char* word, const char* in, uint32_t size, uint32_t start, co
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)
{
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;
// 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()"))
{
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
{
q=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
{
i++;
if(i>=size)
break;
i++;
i+=2;
}
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
i++;
if(i>=size)
throw ztd::format_error("Unterminated double quote", g_origin, in, q);
}
if(i>=size)
break;
i++;
}
else if(in[i] == '\'') // start single quote
{
q=i;
i++;
while(i<size && in[i]!='\'')
i++;
if(i>=size)
break;
throw ztd::format_error("Unterminated single quote", g_origin, in, q);
i++;
}
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
// first char has to be read
// 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;
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)
{
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#)
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;
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);
else // unknown
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);
}
@ -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);
block ret(block::subshell);
while(i<size && in[i] != ')')
while(in[i] != ')')
{
auto pp=parse_condlist(in, size, i);
ret.cls.push_back(pp.first);
i = skip_unread(in, size, pp.second);
if(i>=size)
throw ztd::format_error("Expecting )", g_origin, in, start-1);
}
if(i>=size)
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++;
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);
block ret(block::brace);
while(i<size && in[i] != '}')
while(in[i] != '}')
{
auto pp=parse_condlist(in, size, i);
ret.cls.push_back(pp.first);
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], ")"))
throw ztd::format_error( strf("Unexpected token: '%c'", in[i]) , g_origin, in, i );
}
if(i>=size)
throw ztd::format_error("Expecting }", g_origin, in, start-1);
if(ret.cls.size()<=0)
throw ztd::format_error("Brace block is empty", g_origin, in, start-1);
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
{
auto pp=parse_arglist(in, size, start);
auto pp=parse_arglist(in, size, start, true);
ret.args = pp.first;
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)
{
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
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);
// 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);
throw ztd::format_error("Unexpected word: '"+pp.first.raw+"', expecting 'in' after case", g_origin, in, i);
std::string pp=get_word(in, size, i, " \t\n");
throw ztd::format_error("Unexpected word: '"+pp+"', expecting 'in' after case", g_origin, in, i);
}
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)
std::pair<arg, list_t> cc;
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;
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)
{
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;
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)
{
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.first.args=pp.first;
}

View file

@ -89,3 +89,49 @@ std::string stringReplace(std::string subject, const std::string& search, const
}
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;
}
}
}