diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6e760d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/obj +/test +/run-tests.sh +/Zmakefile +/TODO diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3d50c54 --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +## CONFIG ## + +IDIR=include +SRCDIR=src +ODIR=obj +BINDIR=. + +# binary name, default name of dir +NAME = $(shell readlink -f . | xargs basename) + +# global links +LDFLAGS = -lpthread + +# compiler +CC=g++ +# compiler flags +CXXFLAGS= -I$(IDIR) -Wall -pedantic -std=c++17 +ifeq ($(DEBUG),true) + # debugging flags + CC=clang++ + CXXFLAGS += -g +else + # release flags + CXXFLAGS += -O2 +endif +ifeq ($(STATIC),true) + # static links + LDFLAGS += -l:libztd.a +else + # dynamic links + LDFLAGS += -lztd +endif + +## END CONFIG ## + + + +$(shell mkdir -p $(ODIR)) +$(shell mkdir -p $(BINDIR)) + +# automatically find .h and .hpp +DEPS = $(shell find $(IDIR) -type f -regex '.*\.hp?p?') +# automatically find .c and .cpp and make the corresponding .o rule +OBJ = $(shell find $(SRCDIR) -type f -regex '.*\.cp?p?' | sed 's|\.cpp|.o|g;s|\.c|.o|g;s|^$(SRCDIR)/|$(ODIR)/|g') + +$(ODIR)/%.o: $(SRCDIR)/%.c $(DEPS) + $(CC) $(CXXFLAGS) -c -o $@ $< + +$(ODIR)/%.o: $(SRCDIR)/%.cpp $(DEPS) + $(CC) $(CXXFLAGS) -c -o $@ $< + +$(BINDIR)/$(NAME): $(OBJ) + $(CC) $(CXXFLAGS) $(LDFLAGS) -o $@ $^ + +test: $(BINDIR)/$(NAME) + $(BINDIR)/$(NAME) + +clean: + rm $(ODIR)/*.o + +clear: + rm $(BINDIR)/$(NAME) diff --git a/include/options.hpp b/include/options.hpp new file mode 100644 index 0000000..45f724d --- /dev/null +++ b/include/options.hpp @@ -0,0 +1,36 @@ +#ifndef OPTIONS_HPP +#define OPTIONS_HPP + +#include + +extern ztd::option_set options; + +extern bool opt_minimize; + +ztd::option_set gen_options(); +void print_help(const char* arg0); + +void print_include_help(); +void print_resolve_help(); + +/** +%include [options] +options: + -s single quote contents + -d double quote contents + -e escape chars. For double quotes + -r include raw text, don't parse. Don't count as included + -f include even if already included. Don't count as included +*/ +ztd::option_set create_include_opts(); + +/** +%resolve [options] +options: + -e escape chars. For double quotes + -p parse as shell code + -f ignore non zero return values +*/ +ztd::option_set create_resolve_opts(); + +#endif //OPTIONS_HPP diff --git a/include/parse.hpp b/include/parse.hpp new file mode 100644 index 0000000..2bd9b2f --- /dev/null +++ b/include/parse.hpp @@ -0,0 +1,17 @@ +#ifndef PARSE_HPP +#define PARSE_HPP + +#include "struc.hpp" + +#include + +#include + +extern std::string g_origin; + +std::string import_file(std::string const& path); + +block parse(const char* in, uint32_t size); +inline block parse(std::string const& in) { return parse(in.c_str(), in.size()); } + +#endif //PARSE_HPP diff --git a/include/struc.hpp b/include/struc.hpp new file mode 100644 index 0000000..f9bc0ef --- /dev/null +++ b/include/struc.hpp @@ -0,0 +1,175 @@ +#ifndef STRUC_HPP +#define STRUC_HPP + +#include +#include +#include + +#include +#include + +/* +structure: + +block: +- group + - brace: condlist[] + - subsh: condlist[] +- cmd: arglist[] +- if + - pair[] + +condlist: + pipeline[] + +pipeline: + block[] + +arglist: + arg[] + +arg: +- raw +- subarg[] + +subarg: +- raw +- variable +- block: subshell + + +*/ + +#define AND_OP false +#define OR_OP true + +class condlist; +class block; +class pipeline; +class arg; +class subarg; + +typedef std::vector list_t; + +block make_cmd(std::vector args); + +bool add_include(std::string const& file); + +class arg +{ +public: + arg() { ; } + arg(std::string const& str) {this->setstring(str);} + + void setstring(std::string const& str); + + std::string raw; + + std::vector sa; + + std::string string(); + std::string generate(int ind); +}; + +class arglist +{ +public: + inline void add(arg const& in) { args.push_back(in); } + inline void push_back(arg const& in) { args.push_back(in); } + + std::vector args; + + std::vector strargs(uint32_t start); + + inline uint64_t size() { return args.size(); } + inline arg& operator[](uint32_t i) { return args[i]; } + + std::string generate(int ind); +}; + +class redir +{ +public: + enum redirtype { none, write, append, read, raw } ; + redir(redirtype in=none) { type=in; } + redirtype type; + arg val; +}; + +class block +{ +public: + enum blocktype { none, subshell, brace, main, cmd, function, case_block, for_block, if_block, while_block}; + block() { type=none; } + block(blocktype in) { type=in; } + blocktype type; + // subshell/brace/main + list_t cls; + // cmd + arglist args; + + // case + arg carg; + std::vector< std::pair > cases; + + // main: shebang + // function: name + std::string shebang; + + // subshell: return the containing cmd, if it is a single command + block* single_cmd(); + + std::string generate(int ind=0, bool print_shebang=true); + +private: + std::string generate_cmd(int ind); + std::string generate_case(int ind); +}; + +class pipeline +{ +public: + pipeline() {;} + pipeline(block const& bl) { cmds.push_back(bl); } + inline void add(block bl) { this->cmds.push_back(bl); } + std::vector cmds; + + std::string generate(int ind); +}; + +class condlist +{ +public: + condlist() { parallel=false; } + condlist(block const& pl) { parallel=false; this->add(pl);} + condlist(pipeline const& pl) { parallel=false; this->add(pl);} + void add(pipeline const& pl, bool or_op=false); + + bool parallel; + // don't push_back here + std::vector or_ops; + std::vector pls; + + std::string generate(int ind); +}; + + +class subarg +{ +public: + enum argtype { string, subshell }; + subarg(argtype in) { this->type=in; } + subarg(std::string const& in="") { type=string; val=in; } + subarg(block const& in) { type=subshell; sbsh=in; } + + argtype type; + // raw string + std::string val; + // subshell + block sbsh; + + std::string generate(int ind); +}; + + +#endif //STRUC_HPP diff --git a/include/util.hpp b/include/util.hpp new file mode 100644 index 0000000..9c1ae99 --- /dev/null +++ b/include/util.hpp @@ -0,0 +1,32 @@ +#ifndef UTIL_HPP +#define UTIL_HPP + +#include +#include +#include +#include +#include + +#define INDENT indent(ind) + +extern std::string indenting_string; + +std::string indent(int n); + +std::vector split(std::string const& in, char c); + +std::string escape_str(std::string const& in); + +template +std::string strf( const std::string& format, Args ... args ) +{ + size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' + if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); } + std::unique_ptr buf( new char[ size ] ); + snprintf( buf.get(), size, format.c_str(), args ... ); + return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside +} + +std::string delete_brackets(std::string const& in); + +#endif //UTIL_HPP diff --git a/lxsh b/lxsh new file mode 100755 index 0000000..65272ee Binary files /dev/null and b/lxsh differ diff --git a/src/generate.cpp b/src/generate.cpp new file mode 100644 index 0000000..25848bb --- /dev/null +++ b/src/generate.cpp @@ -0,0 +1,332 @@ +#include "struc.hpp" + +#include + +#include "util.hpp" +#include "options.hpp" +#include "parse.hpp" + +std::vector included; + +bool is_sub_special_cmd(std::string in) +{ + return in == "%include_sub" || in == "%resolve_sub"; +} + +std::string arg::generate(int ind) +{ + std::string ret; + for(auto it: sa) + { + ret += it.generate(ind); + } + return ret; +} + +std::string arglist::generate(int ind) +{ + std::string ret; + + for(auto it: args) + { + ret += it.generate(ind); + ret += ' '; + } + + ret.pop_back(); + + return ret; +} + +std::string pipeline::generate(int ind) +{ + std::string ret; + + ret += cmds[0].generate(ind); + for(uint32_t i=1 ; i args) +{ + std::string ret; + for(auto it: args) + ret += it + ' '; + ret.pop_back(); + return ret; +} + +std::string generate_resolve(std::vector args, int ind) +{ + auto opts=create_resolve_opts(); + auto rargs = opts.process(args, false, true, false); + + std::string cmd=concatargs(rargs); + auto p=ztd::shp("exec "+cmd); + + if(!opts['f'] && p.second!=0) + { + throw std::runtime_error( strf("command `%s` returned %u", cmd.c_str(), p.second) ); + } + while(p.first[p.first.size()-1] == '\n') + p.first.pop_back(); + + if(opts['p']) + { + block bl = parse(p.first); + std::string ret = bl.generate(ind, false); + std::string tmpind=INDENT; + ret = ret.substr(tmpind.size()); + ret.pop_back(); // remove \n + return ret; + } + else + { + return p.first; + } +} + +std::string generate_include(std::vector args, int ind) +{ + std::string ret; + + auto opts=create_include_opts(); + auto rargs = opts.process(args, false, true, false); + + std::string quote; + if(opts['s']) + quote = '\''; + else if(opts['d']) + quote = '"'; + + // do shell resolution + std::string command="for I in "; + for(auto it: rargs) + command += it + ' '; + command += "; do echo $I ; done"; + std::string inc=ztd::sh(command); + + auto v = split(inc, '\n'); + + std::string file; + block bl; + bool indent_remove=true; + + for(auto it : v) + { + if( opts['f'] || // force include + add_include(it) ) // not already included + { + file=import_file(it); + if(opts['r']) + ret += file; + else + { + try + { + bl = parse(quote + file + quote); + } + catch(ztd::format_error& e) + { + throw ztd::format_error(e.what(), it, e.data(), e.where()); + } + file = bl.generate(ind, false); + if(indent_remove) + { + indent_remove=false; + std::string tmpind=INDENT; + file = file.substr(tmpind.size()); + } + ret += file; + } + } + } + + if(!opts['r']) + ret.pop_back(); + + return ret; +} + +std::string block::generate_cmd(int ind) +{ + std::string ret; + if(args.size()<=0) + return ""; + std::string cmd=args[0].raw; + if(cmd == "%include" || cmd == "%include_s") + { + ret += generate_include(args.strargs(1), ind); + } + else if(cmd == "%resolve" || cmd == "%resolve_s") + { + ret += generate_resolve(args.strargs(1), ind); + } + else + ret = args.generate(ind); + return ret; +} + +std::string block::generate_case(int ind) +{ + std::string ret; + ret += "case " + carg.generate(ind) + " in\n"; + ind++; + for(auto cs: this->cases) + { + if(!opt_minimize) ret += INDENT; + ret += cs.first.generate(ind) + ')'; + if(!opt_minimize) ret += '\n'; + for(auto it: cs.second) + ret += it.generate(ind+1); + if(opt_minimize) + { + if(ret[ret.size()-1] == '\n') + ret.pop_back(); + } + else + { + ind++; + ret += INDENT; + ind--; + } + ret += ";;\n"; + } + ind--; + if(!opt_minimize) ret += INDENT; + ret += "esac"; + return ret; +} + +std::string block::generate(int ind, bool print_shebang) +{ + std::string ret; + + if(type==cmd) + { + ret += generate_cmd(ind); + } + else + { + if(type==function) + { + ret += shebang + "()"; + if(!opt_minimize) ret += '\n' + INDENT; + ret += "{\n"; + for(auto it: cls) + ret += it.generate(ind+1); + if(!opt_minimize) + ret += INDENT; + ret += '}'; + } + else if(type==subshell) + { + ret += '('; + if(!opt_minimize) ret += '\n'; + for(auto it: cls) + ret += it.generate(ind+1); + if(opt_minimize) + ret.pop_back(); + else + ret += INDENT; + ret += ')'; + } + else if(type==brace) + { + ret += "{\n" ; + for(auto it: cls) + ret += it.generate(ind+1); + if(!opt_minimize) + ret += INDENT; + ret += '}'; + } + else if(type==main) + { + if(print_shebang && shebang!="") + ret += shebang + '\n'; + for(auto it: cls) + ret += it.generate(ind); + } + else if(type==case_block) + { + ret += generate_case(ind); + } + + std::string t = generate_cmd(ind); // leftover redirections + if(t!="") + { + if(!opt_minimize) ret += ' '; + ret += t; + } + + } + + return ret; +} + +std::string subarg::generate(int ind) +{ + std::string ret; + if(type == subarg::string) + { + ret += val; + } + else if(type == subarg::subshell) + { + // includes and resolves inside command substitutions + // resolve here and not inside subshell + block* cmd = sbsh.single_cmd(); + if( cmd != nullptr && (cmd->args[0].raw == "%include" || cmd->args[0].raw == "%resolve") ) + { + ret += cmd->generate(ind); + } + // regular substitution + else + { + ret += '$'; + ret += sbsh.generate(ind); + } + } + return ret; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7d4deef --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,91 @@ +#include + +#include + +#include +#include + +#include + +#include "struc.hpp" +#include "parse.hpp" +#include "options.hpp" + +int main(int argc, char* argv[]) +{ + auto args=options.process(argc, argv); + + if(options['m']) + opt_minimize=true; + + bool piped=false; + + if(options['h']) + { + print_help(argv[0]); + return 1; + } + if(options["help-commands"]) + { + print_include_help(); + printf("\n\n"); + print_resolve_help(); + return 1; + } + + std::string file; + if(args.size() > 0) + { + if(args[0] == "-") + { + piped=true; + file = "/dev/stdin"; + } + else + file=args[0]; + } + else + { + if(isatty(fileno(stdin))) + { + print_help(argv[0]); + return 1; + } + else + { + piped=true; + file = "/dev/stdin"; + } + } + + if(!piped && !options['C']) + { + std::string dir=ztd::exec("dirname", file).first; + dir.pop_back(); + file=ztd::exec("basename", file).first; + file.pop_back(); + chdir(dir.c_str()); + } + + g_origin=file; + add_include(file); + + if(args.size()>0) + { + try + { + block sh(parse(import_file(file))); + std::cout << sh.generate(); + } + catch(ztd::format_error& e) + { + printFormatException(e); + } + catch(std::exception& e) + { + std::cerr << e.what() << std::endl; + } + } + + return 0; +} diff --git a/src/options.cpp b/src/options.cpp new file mode 100644 index 0000000..fa0c6e0 --- /dev/null +++ b/src/options.cpp @@ -0,0 +1,76 @@ +#include "options.hpp" + +ztd::option_set options = gen_options(); +bool opt_minimize; + +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('C', "no-cd", false, "Don't change directories")); + ret.add(ztd::option("help-commands", false, "Print help for linker commands")); + return ret; +} + +ztd::option_set create_include_opts() +{ + ztd::option_set opts; + opts.add( + ztd::option('s', false, "Single quote contents"), + ztd::option('d', false, "Double quote contents"), + // ztd::option('e', false, "Escape for double quotes"), + ztd::option('r', false, "Include raw contents, don't parse"), + ztd::option('f', false, "Force include even if already included. Don't count as included") + ); + return opts; +} + +ztd::option_set create_resolve_opts() +{ + ztd::option_set opts; + opts.add( + // ztd::option('e', false, "Escape for double quotes"), + ztd::option('p', false, "Parse contents as shell code"), + ztd::option('f', false, "Ignore non-zero return values") + ); + return opts; +} + +void print_help(const char* arg0) +{ + printf("%s [options] [file]\n", arg0); + printf("Link extended shell, allows file including and command resolving\n"); + printf("See --help-commands for help on linker commands\n"); + printf("\n"); + printf("Options:\n"); + options.print_help(3,20); +} + +void print_include_help() +{ + printf("%%include [options] \n"); + printf("Include the targeted files. Paths are relative to folder of current file\n"); + printf(" - Regular shell processing applies to the file arguments\n"); + printf(" - Only includes not already included files\n"); + printf(" - `%%include` in command substitutions replaces the substitution\n"); + printf(" =>`%%include_s` can be used inside a substitution to prevent this\n"); + printf("\n"); + + ztd::option_set opts=create_include_opts(); + printf("Options:\n"); + opts.print_help(3,7); +} +void print_resolve_help() +{ + printf("%%resolve [options] \n"); + printf("Execute shell command and substitute output. Paths is from folder of current file\n"); + printf(" - Fails if return value is not 0. Can be ignored with -f\n"); + printf(" - `%%resolve` in command substitutions replaces the substitution\n"); + printf(" =>`%%resolve_s` can be used inside a substitution to prevent this\n"); + printf("\n"); + + ztd::option_set opts=create_resolve_opts(); + printf("Options:\n"); + opts.print_help(3,7); +} diff --git a/src/parse.cpp b/src/parse.cpp new file mode 100644 index 0000000..39e4351 --- /dev/null +++ b/src/parse.cpp @@ -0,0 +1,458 @@ +#include "parse.hpp" + +#include +#include +#include + +#include "util.hpp" + +std::string g_origin; + +inline bool is_in(char c, const char* set) +{ + return index(set, c) != NULL; +} + +inline bool is_alphanum(char c) +{ + return (c >= 'a' && c<='z') || (c >= 'A' && c<='Z') || (c >= '0' && c<='9'); +} +inline bool is_alpha(char c) +{ + return (c >= 'a' && c<='z') || (c >= 'A' && c<='Z'); +} + +bool word_eq(const char* word, const char* in, uint32_t size, uint32_t start, const char* end_set=NULL) +{ + uint32_t wordsize=strlen(word); + if(wordsize > size-start) + return false; + if(strncmp(word, in+start, wordsize) == 0) + { + if(end_set==NULL) + return true; + // end set + if(wordsize < size-start) + return is_in(in[start+wordsize], end_set); + } + return false; +} + +uint32_t skip_chars(const char* in, uint32_t size, uint32_t start, const char* set) +{ + for(uint32_t i=start; i parse_subshell(const char* in, uint32_t size, uint32_t start); + +// parse one argument +// must start at a read char +// ends at either " \t|&;\n()" +std::pair parse_arg(const char* in, uint32_t size, uint32_t start) +{ + arg ret; + // j : start of subarg + uint32_t i=start,j=start; + while(i") && in[i+1]=='&') // special case for <& and >& + { + i+=2; + } + else if(in[i]=='\\') // backslash: don't check next char + { + i++; + if(i>=size) + break; + i++; + } + else if(in[i] == '"') // start double quote + { + i++; + while(i=size) + break; + i++; + } + else if( word_eq("$(", in, size, i) ) // substitution + { + // add string subarg + ret.sa.push_back(subarg(std::string(in+j, i-j))); + i+=2; + // add subshell subarg + auto r=parse_subshell(in, size, i); + ret.sa.push_back(subarg(r.first)); + j = i = r.second; + } + else + i++; + } + if(i>=size) + break; + i++; + } + else if(in[i] == '\'') // start single quote + { + i++; + while(i=size) + break; + i++; + } + else if( word_eq("$(", in, size, i) ) // substitution + { + ret.sa.push_back(subarg(std::string(in+j, i-j))); + i+=2; + auto r=parse_subshell(in, size, i); + ret.sa.push_back(subarg(r.first)); + j = i = r.second; + } + else + i++; + } + + // add string subarg + std::string val=std::string(in+j, i-j); + ret.sa.push_back(subarg(val)); + + // raw string for other uses + ret.raw = std::string(in+start, i-start); + + return std::make_pair(ret, i); +} + +// parse one list of arguments (a command for instance) +// must start at a read char +// first char has to be read +// ends at either &|;\n#() +std::pair parse_arglist(const char* in, uint32_t size, uint32_t start) +{ + uint32_t i=start; + arglist ret; + while(i=size || is_in(in[i], "&|;\n#()") ) + return std::make_pair(ret, i); + } + return std::make_pair(ret, i); +} + +std::pair parse_block(const char* in, uint32_t size, uint32_t start); + +// parse a pipeline +// must start at a read char +// separated by | +// ends at either &;\n#) +std::pair parse_pipeline(const char* in, uint32_t size, uint32_t start) +{ + uint32_t i = skip_unread(in, size, start); + pipeline ret; + while(i=size || is_in(in[i], "&;\n#)") ) || word_eq("||", in, size, i) ) + return std::make_pair(ret, i); + else if( in[i] != '|') + throw ztd::format_error( strf("Unexpected token: '%c'", in[i] ), g_origin, in, i); + i++; + } + return std::make_pair(ret, i); +} + +// parse condition lists +// must start at a read char +// separated by && or || +// ends at either ;\n)# +std::pair parse_condlist(const char* in, uint32_t size, uint32_t start) +{ + uint32_t i = skip_unread(in, size, start); + condlist ret; + bool optype=AND_OP; + while(i=size || is_in(in[i], ")#")) // end here exactly: used for control later + { + return std::make_pair(ret, i); + } + else if(is_in(in[i], ";\n")) // end one char after: skip them for next parse + { + i++; + return std::make_pair(ret, i); + } + else if( word_eq("&", in, size, i) && !word_eq("&&", in, size, i) ) // parallel: end one char after + { + ret.parallel=true; + i++; + return std::make_pair(ret, i); + } + else if( word_eq("&&", in, size, i) ) // and op + { + i += 2; + optype=AND_OP; + } + else if( word_eq("||", in, size, i) ) // or op + { + i += 2; + optype=OR_OP; + } + else if(i parse_subshell(const char* in, uint32_t size, uint32_t start) +{ + uint32_t i = skip_unread(in, size, start); + block ret(block::subshell); + while(i=size) + throw ztd::format_error("Expecting )", g_origin, in, start-1); + i++; + return std::make_pair(ret,i); +} + +// parse condlists until } +std::pair parse_brace(const char* in, uint32_t size, uint32_t start) +{ + uint32_t i = skip_unread(in, size, start); + block ret(block::brace); + while(i=size) + throw ztd::format_error("Expecting }", g_origin, in, start-1); + + i++; + + return std::make_pair(ret,i); +} + +std::pair parse_function(const char* in, uint32_t size, uint32_t start) +{ + block ret(block::function); + uint32_t i=start; + + i=skip_unread(in, size, i); + if(in[i] != '{') + throw ztd::format_error("Expecting { after ()", g_origin, in, i); + + i++; + auto pp = parse_brace(in, size, i); + ret.cls = pp.first.cls; + i=pp.second; + + return std::make_pair(ret, i); +} + +std::pair parse_cmd(const char* in, uint32_t size, uint32_t start) +{ + block ret(block::cmd); + uint32_t i=start; + + auto tp=parse_arg(in, size, i); + i=skip_unread(in, size, tp.second); + if(word_eq("()", in, size, i)) + { + i += 2; + auto pp = parse_function(in, size, i); + pp.first.shebang = tp.first.raw; + return pp; + } + else + { + auto pp=parse_arglist(in, size, start); + ret.args = pp.first; + i = pp.second; + } + + return std::make_pair(ret, i); +} + +std::pair parse_case(const char* in, uint32_t size, uint32_t start) +{ + block ret(block::case_block); + uint32_t i=start; + + auto pa = parse_arg(in, size, i); // case arg + ret.carg = pa.first; + i=skip_unread(in, size, pa.second); + + + if(!word_eq("in", in, size, i)) + { + auto pp=parse_arg(in, size, i); + throw ztd::format_error("Unexpected word: '"+pp.first.raw+"', expecting 'in' after case", g_origin, in, i); + } + + i=skip_unread(in, size, i+2); + + while(i cc; + pa = parse_arg(in, size, i); + cc.first = pa.first; + i=skip_unread(in, size, pa.second); + + if(in[i] != ')') + throw ztd::format_error( strf("Unexpected token '%c', expecting ')'", in[i]), g_origin, in, i ); + i++; + + while(true) // blocks + { + auto pc = parse_condlist(in, size, i); + cc.second.push_back(pc.first); + i=pc.second; + + if(i+1>=size) + throw ztd::format_error("Expecting ';;'", g_origin, in, i); + if(in[i] == ')') + throw ztd::format_error( strf("Unexpected token '%c', expecting ';;'", in[i]), g_origin, in, i ); + + if(in[i-1] == ';' && in[i] == ';') + { + i++; + break; + } + + i=skip_unread(in, size, i); + if(word_eq(";;", in, size, i)) + { + i+=2; + break; + } + if(word_eq("esac", in, size, i)) + break; + + } + i=skip_unread(in, size, i); + ret.cases.push_back(cc); + } + + if(i>=size) + throw ztd::format_error("Expecting 'esac'", g_origin, in, i); + i+=4; + + return std::make_pair(ret, i); +} + +// detect if brace, subshell, case or other +std::pair parse_block(const char* in, uint32_t size, uint32_t start) +{ + uint32_t i = skip_chars(in, size, start, " \n\t"); + std::pair ret; + if(in[i] == '{') // brace block + { + i++; + ret = parse_brace(in, size, i); + } + else if(in[i] == '(') //subshell + { + i++; + ret = parse_subshell(in, size, i); + } + else if(word_eq("case", in, size, i)) + { + i = skip_unread(in, size, i+4); + ret = parse_case(in, size, i); + } + else // command + { + ret = parse_cmd(in, size, i); + } + if(ret.first.args.args.size()<=0) + { + auto pp=parse_arglist(in, size, ret.second); // in case of redirects + ret.second=pp.second; + ret.first.args=pp.first; + } + return ret; +} + +// parse main +block parse(const char* in, uint32_t size) +{ + + block ret(block::main); + uint32_t i=0; + if(word_eq("#!", in, size, 0)) + { + i=skip_until(in, size, 0, "\n"); + ret.shebang=std::string(in, i); + } + i = skip_unread(in, size, i); + while(i args) +{ + block cmd(block::cmd); + for(auto it: args) + { + cmd.args.add(arg(it)); + } + return cmd; +} + +std::vector arglist::strargs(uint32_t start) +{ + std::vector ret; + for(uint32_t i=start; ipls.size() > 0) + this->or_ops.push_back(or_op); + this->pls.push_back(pl); +} + +block* block::single_cmd() +{ + if(this->type == block::subshell) + { + if( cls.size() == 1 && // only one condlist + cls[0].pls.size() == 1 && // only one pipeline + cls[0].pls[0].cmds.size() == 1 && // only one block + cls[0].pls[0].cmds[0].type == block::cmd) // block is a command + return &(cls[0].pls[0].cmds[0]); // return command + } + return nullptr; +} + +std::string arg::string() +{ + if(sa.size() > 1 || sa[0].type != subarg::string) + return ""; + return sa[0].val; +} diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..85e3cef --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,54 @@ +#include "util.hpp" + +std::string indenting_string="\t"; + +std::string indent(int n) +{ + std::string ret; + for(int i=0; i split(std::string const& in, char c) +{ + uint32_t i=0,j=0; + std::vector ret; + while(j