restructure struc object extensions
This commit is contained in:
parent
44d71155cc
commit
2b1e7c008b
7 changed files with 216 additions and 170 deletions
|
|
@ -4,6 +4,8 @@
|
|||
#include "struc.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <ztd/filedat.hpp>
|
||||
|
||||
|
|
@ -27,4 +29,38 @@ shmain* parse_text(const char* in, uint32_t size, std::string const& filename=""
|
|||
inline shmain* parse_text(std::string const& in, std::string const& filename="") { return parse_text(in.c_str(), in.size(), filename); }
|
||||
inline shmain* parse(std::string const& file) { return parse_text(import_file(file), file); }
|
||||
|
||||
// ** unit parsers ** //
|
||||
|
||||
/* util parsers */
|
||||
// list
|
||||
std::pair<list*, uint32_t> parse_list_until(const char* in, uint32_t size, uint32_t start, char end_c, const char* expecting=NULL);
|
||||
std::pair<list*, uint32_t> parse_list_until(const char* in, uint32_t size, uint32_t start, std::string const& end_word);
|
||||
std::tuple<list*, uint32_t, std::string> parse_list_until(const char* in, uint32_t size, uint32_t start, std::vector<std::string> const& end_words, const char* expecting=NULL);
|
||||
// name
|
||||
std::pair<std::string, uint32_t> parse_varname(const char* in, uint32_t size, uint32_t start);
|
||||
|
||||
// subarg parsers
|
||||
std::pair<arithmetic_subarg*, uint32_t> parse_arithmetic(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<manipulation_subarg*, uint32_t> parse_manipulation(const char* in, uint32_t size, uint32_t start);
|
||||
// arg parser
|
||||
std::pair<arg*, uint32_t> parse_arg(const char* in, uint32_t size, uint32_t start, const char* end=ARG_END, const char* unexpected=SPECIAL_TOKENS, bool doquote=true);
|
||||
// redirect parser
|
||||
std::pair<redirect*, uint32_t> parse_redirect(const char* in, uint32_t size, uint32_t start);
|
||||
// arglist parser
|
||||
std::pair<arglist*, uint32_t> parse_arglist(const char* in, uint32_t size, uint32_t start, bool hard_error=false, std::vector<redirect*>* redirs=nullptr);
|
||||
// block parsers
|
||||
std::pair<block*, uint32_t> parse_block(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<cmd*, uint32_t> parse_cmd(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<function*, uint32_t> parse_function(const char* in, uint32_t size, uint32_t start, const char* after="()");
|
||||
std::pair<subshell*, uint32_t> parse_subshell(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<brace*, uint32_t> parse_brace(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<case_block*, uint32_t> parse_case(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<if_block*, uint32_t> parse_if(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<for_block*, uint32_t> parse_for(const char* in, uint32_t size, uint32_t start);
|
||||
std::pair<while_block*, uint32_t> parse_while(const char* in, uint32_t size, uint32_t start);
|
||||
// pipeline parser
|
||||
std::pair<pipeline*, uint32_t> parse_pipeline(const char* in, uint32_t size, uint32_t start);
|
||||
// condlist parser
|
||||
std::pair<condlist*, uint32_t> parse_condlist(const char* in, uint32_t size, uint32_t start);
|
||||
|
||||
#endif //PARSE_HPP
|
||||
|
|
|
|||
|
|
@ -78,8 +78,6 @@ typedef std::vector<arg*> arglist_t;
|
|||
|
||||
extern std::string g_origin;
|
||||
|
||||
cmd* make_cmd(std::vector<std::string> args);
|
||||
|
||||
// meta object type
|
||||
class _obj
|
||||
{
|
||||
|
|
@ -111,11 +109,11 @@ class arg : public _obj
|
|||
{
|
||||
public:
|
||||
arg() { type=_obj::_arg; }
|
||||
arg(std::string const& str) { type=_obj::_arg; this->setstring(str);}
|
||||
arg(std::string const& str) { type=_obj::_arg; this->set(str);}
|
||||
arg(subarg* in) { type=_obj::_arg; sa.push_back(in); }
|
||||
~arg() { for(auto it: sa) delete it; }
|
||||
|
||||
void setstring(std::string const& str);
|
||||
void set(std::string const& str);
|
||||
|
||||
std::vector<subarg*> sa;
|
||||
|
||||
|
|
@ -127,8 +125,6 @@ public:
|
|||
std::string generate(int ind);
|
||||
};
|
||||
|
||||
inline bool operator==(arg a, std::string const& b) { return a.equals(b); }
|
||||
|
||||
// arglist
|
||||
|
||||
class arglist : public _obj
|
||||
|
|
@ -202,7 +198,7 @@ class condlist : public _obj
|
|||
{
|
||||
public:
|
||||
condlist() { type=_obj::_condlist; parallel=false; }
|
||||
condlist(pipeline* pl);
|
||||
condlist(pipeline* pl) { type=_obj::_condlist; parallel=false; this->add(pl); }
|
||||
condlist(block* bl);
|
||||
~condlist() { for(auto it: pls) delete it; }
|
||||
|
||||
|
|
@ -231,6 +227,7 @@ public:
|
|||
~list() { for(auto it: cls) delete it; }
|
||||
|
||||
std::vector<condlist*> cls;
|
||||
void add(condlist* in) { cls.push_back(in); }
|
||||
|
||||
condlist* last_cond() { return cls[cls.size()-1]; }
|
||||
|
||||
|
|
@ -267,7 +264,7 @@ public:
|
|||
|
||||
size_t arglist_size();
|
||||
|
||||
void add_arg(arg* in);
|
||||
void add(arg* in);
|
||||
|
||||
|
||||
// preceding var assigns
|
||||
|
|
|
|||
12
include/struc_helper.hpp
Normal file
12
include/struc_helper.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef STRUC_HELPER_HPP
|
||||
#define STRUC_HELPER_HPP
|
||||
|
||||
#include "struc.hpp"
|
||||
|
||||
cmd* make_cmd(std::vector<std::string> const& args);
|
||||
cmd* make_cmd(std::string const& in);
|
||||
condlist* make_condlist(std::string const& in);
|
||||
|
||||
inline bool operator==(arg a, std::string const& b) { return a.equals(b); }
|
||||
|
||||
#endif //STRUC_HELPER_HPP
|
||||
|
|
@ -125,8 +125,6 @@ uint32_t skip_unread(const char* in, uint32_t size, uint32_t start)
|
|||
|
||||
// parse fcts
|
||||
|
||||
std::pair<subshell*, uint32_t> parse_subshell(const char* in, uint32_t size, uint32_t start);
|
||||
|
||||
std::pair<std::string, uint32_t> parse_varname(const char* in, uint32_t size, uint32_t start)
|
||||
{
|
||||
uint32_t i=start;
|
||||
|
|
@ -178,8 +176,6 @@ std::pair<arithmetic_subarg*, uint32_t> parse_arithmetic(const char* in, uint32_
|
|||
return std::make_pair(ret, i);
|
||||
}
|
||||
|
||||
std::pair<arg*, uint32_t> parse_arg(const char* in, uint32_t size, uint32_t start, const char* end=ARG_END, const char* unexpected=SPECIAL_TOKENS, bool doquote=true);
|
||||
|
||||
std::pair<manipulation_subarg*, uint32_t> parse_manipulation(const char* in, uint32_t size, uint32_t start)
|
||||
{
|
||||
manipulation_subarg* ret = new manipulation_subarg;
|
||||
|
|
@ -526,7 +522,7 @@ std::pair<redirect*, uint32_t> parse_redirect(const char* in, uint32_t size, uin
|
|||
// 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, bool hard_error=false, std::vector<redirect*>* redirs=nullptr)
|
||||
std::pair<arglist*, uint32_t> parse_arglist(const char* in, uint32_t size, uint32_t start, bool hard_error, std::vector<redirect*>* redirs)
|
||||
{
|
||||
uint32_t i=start;
|
||||
arglist* ret = nullptr;
|
||||
|
|
@ -592,8 +588,6 @@ std::pair<arglist*, uint32_t> parse_arglist(const char* in, uint32_t size, uint3
|
|||
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 |
|
||||
|
|
@ -680,7 +674,7 @@ std::pair<condlist*, uint32_t> parse_condlist(const char* in, uint32_t size, uin
|
|||
return std::make_pair(ret, i);
|
||||
}
|
||||
|
||||
std::pair<list*, uint32_t> parse_list_until(const char* in, uint32_t size, uint32_t start, char end_c, const char* expecting=NULL)
|
||||
std::pair<list*, uint32_t> parse_list_until(const char* in, uint32_t size, uint32_t start, char end_c, const char* expecting)
|
||||
{
|
||||
list* ret = new list;
|
||||
uint32_t i=skip_unread(in, size, start);
|
||||
|
|
@ -778,7 +772,7 @@ std::pair<list*, uint32_t> parse_list_until(const char* in, uint32_t size, uint3
|
|||
}
|
||||
|
||||
|
||||
std::tuple<list*, uint32_t, std::string> parse_list_until(const char* in, uint32_t size, uint32_t start, std::vector<std::string> const& end_words, const char* expecting=NULL)
|
||||
std::tuple<list*, uint32_t, std::string> parse_list_until(const char* in, uint32_t size, uint32_t start, std::vector<std::string> const& end_words, const char* expecting)
|
||||
{
|
||||
list* ret = new list;
|
||||
uint32_t i=skip_unread(in, size, start);;
|
||||
|
|
@ -896,7 +890,7 @@ std::pair<brace*, uint32_t> parse_brace(const char* in, uint32_t size, uint32_t
|
|||
// parse a function
|
||||
// must start right after the ()
|
||||
// then parses a brace block
|
||||
std::pair<function*, uint32_t> parse_function(const char* in, uint32_t size, uint32_t start, const char* after="()")
|
||||
std::pair<function*, uint32_t> parse_function(const char* in, uint32_t size, uint32_t start, const char* after)
|
||||
{
|
||||
uint32_t i=start;
|
||||
function* ret = new function;
|
||||
|
|
|
|||
|
|
@ -239,9 +239,9 @@ void add_unset_variables(shmain* sh, std::regex const& exclude)
|
|||
if(m_vars.size()>0)
|
||||
{
|
||||
cmd* unset_cmd = new cmd;
|
||||
unset_cmd->add_arg(new arg("unset"));
|
||||
unset_cmd->add(new arg("unset"));
|
||||
for(auto it: m_vars)
|
||||
unset_cmd->add_arg(new arg(it.first));
|
||||
unset_cmd->add(new arg(it.first));
|
||||
condlist* cl = new condlist(unset_cmd);
|
||||
sh->lst->cls.insert(sh->lst->cls.begin(), cl);
|
||||
}
|
||||
|
|
|
|||
143
src/struc.cpp
143
src/struc.cpp
|
|
@ -9,144 +9,9 @@ std::string g_origin="";
|
|||
|
||||
const std::string cmd::empty_string="";
|
||||
|
||||
std::vector<std::string> arglist::strargs(uint32_t start)
|
||||
condlist::condlist(block* bl)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
bool t=opt_minimize;
|
||||
opt_minimize=true;
|
||||
for(uint32_t i=start; i<args.size(); i++)
|
||||
{
|
||||
ret.push_back(args[i]->generate(0));
|
||||
}
|
||||
opt_minimize=t;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void arg::setstring(std::string const& str)
|
||||
{
|
||||
for(auto it: sa)
|
||||
delete it;
|
||||
sa.resize(0);
|
||||
sa.push_back(new string_subarg(str));
|
||||
}
|
||||
|
||||
std::string arg::string()
|
||||
{
|
||||
if(sa.size() != 1 || sa[0]->type != subarg::subarg_string)
|
||||
return "";
|
||||
return dynamic_cast<string_subarg*>(sa[0])->val;
|
||||
}
|
||||
|
||||
void condlist::prune_first_cmd()
|
||||
{
|
||||
if(pls.size()>0 && pls[0]->cmds.size()>0)
|
||||
{
|
||||
delete pls[0]->cmds[0];
|
||||
pls[0]->cmds.erase(pls[0]->cmds.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void condlist::add(pipeline* pl, bool or_op)
|
||||
{
|
||||
if(this->pls.size() > 0)
|
||||
this->or_ops.push_back(or_op);
|
||||
this->pls.push_back(pl);
|
||||
}
|
||||
|
||||
block* condlist::first_block()
|
||||
{
|
||||
if(pls.size() > 0 && pls[0]->cmds.size() > 0)
|
||||
return (pls[0]->cmds[0]);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cmd* condlist::first_cmd()
|
||||
{
|
||||
if(pls.size() > 0 && pls[0]->cmds.size() > 0 && pls[0]->cmds[0]->type == _obj::block_cmd)
|
||||
return dynamic_cast<cmd*>(pls[0]->cmds[0]);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cmd* block::single_cmd()
|
||||
{
|
||||
if(this->type == _obj::block_subshell)
|
||||
{
|
||||
return dynamic_cast<subshell*>(this)->single_cmd();
|
||||
}
|
||||
if(this->type == _obj::block_brace)
|
||||
{
|
||||
return dynamic_cast<brace*>(this)->single_cmd();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cmd* subshell::single_cmd()
|
||||
{
|
||||
if( lst->size() == 1 && // only one condlist
|
||||
(*lst)[0]->pls.size() == 1 && // only one pipeline
|
||||
(*lst)[0]->pls[0]->cmds.size() == 1 && // only one block
|
||||
(*lst)[0]->pls[0]->cmds[0]->type == _obj::block_cmd) // block is a command
|
||||
return dynamic_cast<cmd*>((*lst)[0]->pls[0]->cmds[0]); // return command
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t cmd::arglist_size()
|
||||
{
|
||||
if(args==nullptr)
|
||||
return 0;
|
||||
else
|
||||
return args->size();
|
||||
}
|
||||
|
||||
cmd* brace::single_cmd()
|
||||
{
|
||||
if( lst->size() == 1 && // only one condlist
|
||||
(*lst)[0]->pls.size() == 1 && // only one pipeline
|
||||
(*lst)[0]->pls[0]->cmds.size() == 1 && // only one block
|
||||
(*lst)[0]->pls[0]->cmds[0]->type == _obj::block_cmd) // block is a command
|
||||
return dynamic_cast<cmd*>((*lst)[0]->pls[0]->cmds[0]); // return command
|
||||
return nullptr;
|
||||
}
|
||||
cmd* condlist::get_cmd(std::string const& cmdname)
|
||||
{
|
||||
for(auto pl: pls)
|
||||
{
|
||||
for(auto bl: pl->cmds)
|
||||
{
|
||||
if(bl->type == _obj::block_cmd)
|
||||
{
|
||||
cmd* c=dynamic_cast<cmd*>(bl);
|
||||
if(c->args->size()>0 && (*c->args)[0]->equals(cmdname) )
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void shmain::concat(shmain* in)
|
||||
{
|
||||
this->lst->cls.insert(this->lst->cls.end(), in->lst->cls.begin(), in->lst->cls.end());
|
||||
in->lst->cls.resize(0);
|
||||
if(this->shebang == "")
|
||||
this->shebang = in->shebang;
|
||||
}
|
||||
|
||||
void condlist::negate()
|
||||
{
|
||||
// invert commands
|
||||
for(uint32_t i=0; i<pls.size(); i++)
|
||||
pls[i]->negated = !pls[i]->negated;
|
||||
// invert bool operators
|
||||
for(uint32_t i=0; i<or_ops.size(); i++)
|
||||
or_ops[i] = !or_ops[i];
|
||||
}
|
||||
|
||||
std::string const& cmd::firstarg_string()
|
||||
{
|
||||
if(args!=nullptr && args->args.size()>0 && args->args[0]->sa.size() == 1 && args->args[0]->sa[0]->type == _obj::subarg_string)
|
||||
return dynamic_cast<string_subarg*>(args->args[0]->sa[0])->val;
|
||||
return cmd::empty_string;
|
||||
type=_obj::_condlist;
|
||||
parallel=false;
|
||||
this->add(new pipeline(bl));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
#include "struc.hpp"
|
||||
#include "struc_helper.hpp"
|
||||
|
||||
cmd* make_cmd(std::vector<std::string> args)
|
||||
#include "parse.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
// ** FUNCTIONS ** //
|
||||
|
||||
cmd* make_cmd(std::vector<std::string> const& args)
|
||||
{
|
||||
cmd* ret = new cmd();
|
||||
ret->args = new arglist();
|
||||
|
|
@ -11,24 +16,161 @@ cmd* make_cmd(std::vector<std::string> args)
|
|||
return ret;
|
||||
}
|
||||
|
||||
condlist::condlist(pipeline* pl)
|
||||
cmd* make_cmd(std::string const& in)
|
||||
{
|
||||
type=_obj::_condlist;
|
||||
parallel=false;
|
||||
if(pl!=nullptr) this->add(pl);
|
||||
return parse_cmd(in.c_str(), in.size(), 0).first;
|
||||
}
|
||||
|
||||
condlist::condlist(block* bl)
|
||||
condlist* make_condlist(std::string const& in)
|
||||
{
|
||||
type=_obj::_condlist;
|
||||
parallel=false;
|
||||
this->add(new pipeline(bl));
|
||||
return parse_condlist(in.c_str(), in.size(), 0).first;
|
||||
}
|
||||
|
||||
void cmd::add_arg(arg* in)
|
||||
// ** CLASS EXTENSIONS ** //
|
||||
|
||||
/// GETTERS ///
|
||||
|
||||
// property getters
|
||||
|
||||
size_t cmd::arglist_size()
|
||||
{
|
||||
if(args==nullptr)
|
||||
return 0;
|
||||
else
|
||||
return args->size();
|
||||
}
|
||||
|
||||
// string getters
|
||||
|
||||
std::string arg::string()
|
||||
{
|
||||
if(sa.size() != 1 || sa[0]->type != subarg::subarg_string)
|
||||
return "";
|
||||
return dynamic_cast<string_subarg*>(sa[0])->val;
|
||||
}
|
||||
|
||||
std::vector<std::string> arglist::strargs(uint32_t start)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
bool t=opt_minimize;
|
||||
opt_minimize=true;
|
||||
for(uint32_t i=start; i<args.size(); i++)
|
||||
{
|
||||
ret.push_back(args[i]->generate(0));
|
||||
}
|
||||
opt_minimize=t;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string const& cmd::firstarg_string()
|
||||
{
|
||||
if(args!=nullptr && args->args.size()>0 && args->args[0]->sa.size() == 1 && args->args[0]->sa[0]->type == _obj::subarg_string)
|
||||
return dynamic_cast<string_subarg*>(args->args[0]->sa[0])->val;
|
||||
return cmd::empty_string;
|
||||
}
|
||||
|
||||
// subobject getters
|
||||
|
||||
block* condlist::first_block()
|
||||
{
|
||||
if(pls.size() > 0 && pls[0]->cmds.size() > 0)
|
||||
return (pls[0]->cmds[0]);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cmd* condlist::first_cmd()
|
||||
{
|
||||
if(pls.size() > 0 && pls[0]->cmds.size() > 0 && pls[0]->cmds[0]->type == _obj::block_cmd)
|
||||
return dynamic_cast<cmd*>(pls[0]->cmds[0]);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cmd* brace::single_cmd()
|
||||
{
|
||||
if( lst->size() == 1 && // only one condlist
|
||||
(*lst)[0]->pls.size() == 1 && // only one pipeline
|
||||
(*lst)[0]->pls[0]->cmds.size() == 1 && // only one block
|
||||
(*lst)[0]->pls[0]->cmds[0]->type == _obj::block_cmd) // block is a command
|
||||
return dynamic_cast<cmd*>((*lst)[0]->pls[0]->cmds[0]); // return command
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cmd* subshell::single_cmd()
|
||||
{
|
||||
if( lst->size() == 1 && // only one condlist
|
||||
(*lst)[0]->pls.size() == 1 && // only one pipeline
|
||||
(*lst)[0]->pls[0]->cmds.size() == 1 && // only one block
|
||||
(*lst)[0]->pls[0]->cmds[0]->type == _obj::block_cmd) // block is a command
|
||||
return dynamic_cast<cmd*>((*lst)[0]->pls[0]->cmds[0]); // return command
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cmd* block::single_cmd()
|
||||
{
|
||||
if(this->type == _obj::block_subshell)
|
||||
return dynamic_cast<subshell*>(this)->single_cmd();
|
||||
if(this->type == _obj::block_brace)
|
||||
return dynamic_cast<brace*>(this)->single_cmd();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// MODIFIERS ///
|
||||
|
||||
// simple setters
|
||||
|
||||
void arg::set(std::string const& str)
|
||||
{
|
||||
for(auto it: sa)
|
||||
delete it;
|
||||
sa.resize(0);
|
||||
sa.push_back(new string_subarg(str));
|
||||
}
|
||||
|
||||
void condlist::prune_first_cmd()
|
||||
{
|
||||
if(pls.size()>0 && pls[0]->cmds.size()>0)
|
||||
{
|
||||
delete pls[0]->cmds[0];
|
||||
pls[0]->cmds.erase(pls[0]->cmds.begin());
|
||||
}
|
||||
}
|
||||
|
||||
// add/extend
|
||||
|
||||
void cmd::add(arg* in)
|
||||
{
|
||||
if(args==nullptr)
|
||||
args = new arglist;
|
||||
|
||||
args->push_back(in);
|
||||
}
|
||||
|
||||
void condlist::add(pipeline* pl, bool or_op)
|
||||
{
|
||||
if(pls.size() > 0)
|
||||
or_ops.push_back(or_op);
|
||||
pls.push_back(pl);
|
||||
}
|
||||
|
||||
|
||||
void shmain::concat(shmain* in)
|
||||
{
|
||||
this->lst->cls.insert(this->lst->cls.end(), in->lst->cls.begin(), in->lst->cls.end());
|
||||
in->lst->cls.resize(0);
|
||||
if(this->shebang == "")
|
||||
this->shebang = in->shebang;
|
||||
}
|
||||
|
||||
// special modifiers
|
||||
|
||||
void condlist::negate()
|
||||
{
|
||||
// invert commands
|
||||
for(uint32_t i=0; i<pls.size(); i++)
|
||||
pls[i]->negated = !pls[i]->negated;
|
||||
// invert bool operators
|
||||
for(uint32_t i=0; i<or_ops.size(); i++)
|
||||
or_ops[i] = !or_ops[i];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue