Init sources
This commit is contained in:
parent
597ccca191
commit
ca41c27246
13 changed files with 1392 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/obj
|
||||
/test
|
||||
/run-tests.sh
|
||||
/Zmakefile
|
||||
/TODO
|
||||
62
Makefile
Normal file
62
Makefile
Normal file
|
|
@ -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)
|
||||
36
include/options.hpp
Normal file
36
include/options.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef OPTIONS_HPP
|
||||
#define OPTIONS_HPP
|
||||
|
||||
#include <ztd/options.hpp>
|
||||
|
||||
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
|
||||
17
include/parse.hpp
Normal file
17
include/parse.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef PARSE_HPP
|
||||
#define PARSE_HPP
|
||||
|
||||
#include "struc.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <ztd/filedat.hpp>
|
||||
|
||||
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
|
||||
175
include/struc.hpp
Normal file
175
include/struc.hpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#ifndef STRUC_HPP
|
||||
#define STRUC_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
/*
|
||||
structure:
|
||||
|
||||
block:
|
||||
- group
|
||||
- brace: condlist[]
|
||||
- subsh: condlist[]
|
||||
- cmd: arglist[]
|
||||
- if
|
||||
- pair<condlist[]>[]
|
||||
|
||||
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<condlist> list_t;
|
||||
|
||||
block make_cmd(std::vector<std::string> 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<subarg> 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<arg> args;
|
||||
|
||||
std::vector<std::string> 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<arg, list_t> > 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<block> 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<bool> or_ops;
|
||||
std::vector<pipeline> 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
|
||||
32
include/util.hpp
Normal file
32
include/util.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef UTIL_HPP
|
||||
#define UTIL_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
#define INDENT indent(ind)
|
||||
|
||||
extern std::string indenting_string;
|
||||
|
||||
std::string indent(int n);
|
||||
|
||||
std::vector<std::string> split(std::string const& in, char c);
|
||||
|
||||
std::string escape_str(std::string const& in);
|
||||
|
||||
template<typename ... Args>
|
||||
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<char[]> 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
|
||||
BIN
lxsh
Executable file
BIN
lxsh
Executable file
Binary file not shown.
332
src/generate.cpp
Normal file
332
src/generate.cpp
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
#include "struc.hpp"
|
||||
|
||||
#include <ztd/shell.hpp>
|
||||
|
||||
#include "util.hpp"
|
||||
#include "options.hpp"
|
||||
#include "parse.hpp"
|
||||
|
||||
std::vector<std::string> 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<cmds.size() ; i++)
|
||||
{
|
||||
ret += opt_minimize ? "|" : " | " ;
|
||||
ret += cmds[i].generate(ind);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string condlist::generate(int ind)
|
||||
{
|
||||
std::string ret;
|
||||
if(!opt_minimize) ret += INDENT;
|
||||
ret += pls[0].generate(ind);
|
||||
for(uint32_t i=0 ; i<pls.size()-1 ; i++)
|
||||
{
|
||||
if(or_ops[i])
|
||||
ret += opt_minimize ? "||" : " || ";
|
||||
else
|
||||
ret += opt_minimize ? "&&" : " && ";
|
||||
ret += pls[i+1].generate(ind);
|
||||
}
|
||||
if(ret=="")
|
||||
return "";
|
||||
if(parallel)
|
||||
{
|
||||
ret += opt_minimize ? "&" : "&\n";
|
||||
}
|
||||
else
|
||||
ret += '\n';
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool add_include(std::string const& file)
|
||||
{
|
||||
std::string truepath=ztd::exec("readlink", "-f", file).first;
|
||||
for(auto it: included)
|
||||
{
|
||||
if(it == truepath)
|
||||
return false;
|
||||
}
|
||||
included.push_back(truepath);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string concatargs(std::vector<std::string> args)
|
||||
{
|
||||
std::string ret;
|
||||
for(auto it: args)
|
||||
ret += it + ' ';
|
||||
ret.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string generate_resolve(std::vector<std::string> 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<std::string> 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;
|
||||
}
|
||||
91
src/main.cpp
Normal file
91
src/main.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <ztd/options.hpp>
|
||||
#include <ztd/shell.hpp>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
76
src/options.cpp
Normal file
76
src/options.cpp
Normal file
|
|
@ -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] <file...>\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] <command...>\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);
|
||||
}
|
||||
458
src/parse.cpp
Normal file
458
src/parse.cpp
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
#include "parse.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
|
||||
#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<size ; i++)
|
||||
{
|
||||
if(!is_in(in[i],set))
|
||||
return i;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
uint32_t skip_until(const char* in, uint32_t size, uint32_t start, const char* set)
|
||||
{
|
||||
for(uint32_t i=start; i<size ; i++)
|
||||
{
|
||||
if(is_in(in[i],set))
|
||||
return i;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
uint32_t skip_unread(const char* in, uint32_t size, uint32_t start)
|
||||
{
|
||||
uint32_t i=start;
|
||||
while(true)
|
||||
{
|
||||
i = skip_chars(in, size, i, " \t\n");
|
||||
if(in[i] != '#') // not a comment
|
||||
return i;
|
||||
i = skip_until(in, size, i, "\n"); //skip to endline
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<block, uint32_t> 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<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;
|
||||
while(i<size && !is_in(in[i], " \t|&;\n()"))
|
||||
{
|
||||
if(i+1<size && is_in(in[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 && in[i] != '"') // while inside quoted string
|
||||
{
|
||||
if(in[i] == '\\') // backslash: don't check next char
|
||||
{
|
||||
i++;
|
||||
if(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 && in[i]!='\'')
|
||||
i++;
|
||||
if(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<arglist, uint32_t> parse_arglist(const char* in, uint32_t size, uint32_t start)
|
||||
{
|
||||
uint32_t i=start;
|
||||
arglist ret;
|
||||
while(i<size)
|
||||
{
|
||||
auto pp=parse_arg(in, size, i);
|
||||
ret.args.push_back(pp.first);
|
||||
i = skip_chars(in, size, pp.second, " \t");
|
||||
if(i>=size || is_in(in[i], "&|;\n#()") )
|
||||
return std::make_pair(ret, i);
|
||||
}
|
||||
return std::make_pair(ret, i);
|
||||
}
|
||||
|
||||
std::pair<block, uint32_t> 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<pipeline, uint32_t> parse_pipeline(const char* in, uint32_t size, uint32_t start)
|
||||
{
|
||||
uint32_t i = skip_unread(in, size, start);
|
||||
pipeline ret;
|
||||
while(i<size)
|
||||
{
|
||||
auto pp=parse_block(in, size, i);
|
||||
ret.add(pp.first);
|
||||
i = skip_chars(in, size, pp.second, " \t");
|
||||
if( (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<condlist, uint32_t> 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)
|
||||
{
|
||||
auto pp=parse_pipeline(in, size, i);
|
||||
ret.add(pp.first, optype);
|
||||
i = pp.second;
|
||||
if(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<size-1) // bad combination
|
||||
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);
|
||||
}
|
||||
return std::make_pair(ret, i);
|
||||
}
|
||||
|
||||
// parse condlists until )
|
||||
std::pair<block, uint32_t> 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 && 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);
|
||||
i++;
|
||||
return std::make_pair(ret,i);
|
||||
}
|
||||
|
||||
// parse condlists until }
|
||||
std::pair<block, uint32_t> 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 && in[i] != '}')
|
||||
{
|
||||
auto pp=parse_condlist(in, size, i);
|
||||
ret.cls.push_back(pp.first);
|
||||
i = skip_unread(in, size, pp.second);
|
||||
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);
|
||||
|
||||
i++;
|
||||
|
||||
return std::make_pair(ret,i);
|
||||
}
|
||||
|
||||
std::pair<block, uint32_t> 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<block, uint32_t> 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<block, uint32_t> 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<size && !word_eq("esac", in, size, i, " \t\n;()&") )
|
||||
{
|
||||
// toto)
|
||||
std::pair<arg, list_t> 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<block, uint32_t> parse_block(const char* in, uint32_t size, uint32_t start)
|
||||
{
|
||||
uint32_t i = skip_chars(in, size, start, " \n\t");
|
||||
std::pair<block, uint32_t> 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<size)
|
||||
{
|
||||
auto pp=parse_condlist(in, size, i);
|
||||
ret.cls.push_back(pp.first);
|
||||
i = skip_unread(in, size, pp.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string import_file(std::string const& path)
|
||||
{
|
||||
std::ifstream st(path);
|
||||
if(!st)
|
||||
throw std::runtime_error("Cannot open stream to '"+path+'\'');
|
||||
|
||||
std::string ret, ln;
|
||||
while(getline(st, ln))
|
||||
{
|
||||
ret += ln + '\n';
|
||||
}
|
||||
st.close();
|
||||
return ret;
|
||||
}
|
||||
54
src/struc.cpp
Normal file
54
src/struc.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#include "struc.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
block make_cmd(std::vector<std::string> args)
|
||||
{
|
||||
block cmd(block::cmd);
|
||||
for(auto it: args)
|
||||
{
|
||||
cmd.args.add(arg(it));
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
std::vector<std::string> arglist::strargs(uint32_t start)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
for(uint32_t i=start; i<args.size(); i++)
|
||||
ret.push_back(args[i].raw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void arg::setstring(std::string const& str)
|
||||
{
|
||||
sa.resize(0);
|
||||
sa.push_back(subarg(str));
|
||||
}
|
||||
|
||||
void condlist::add(pipeline const& pl, bool or_op)
|
||||
{
|
||||
if(this->pls.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;
|
||||
}
|
||||
54
src/util.cpp
Normal file
54
src/util.cpp
Normal file
|
|
@ -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<n; i++)
|
||||
ret += indenting_string;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(std::string const& in, char c)
|
||||
{
|
||||
uint32_t i=0,j=0;
|
||||
std::vector<std::string> ret;
|
||||
while(j<in.size())
|
||||
{
|
||||
i=in.find(c, j);
|
||||
ret.push_back(in.substr(j,i-j));
|
||||
j=i+1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string escape_str(std::string const& in)
|
||||
{
|
||||
std::string ret;
|
||||
for(uint64_t i=0; i<in.size(); i++)
|
||||
{
|
||||
if(in[i] == '\n')
|
||||
ret += "\\n";
|
||||
else if(in[i] == '\"')
|
||||
ret += "\\\"";
|
||||
else if(in[i] == '\t')
|
||||
ret += "\\t";
|
||||
else if(in[i] == '\\')
|
||||
ret += "\\\\";
|
||||
else
|
||||
ret += in[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string delete_brackets(std::string const& in)
|
||||
{
|
||||
std::string ret;
|
||||
for(auto it: in)
|
||||
{
|
||||
if(it!='[' && it!=']')
|
||||
ret += it;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Loading…
Reference in a new issue