some refactorings
This commit is contained in:
parent
d3a373ce94
commit
ebb69b6096
|
|
@ -53,7 +53,7 @@
|
|||
|`(% a b)`|Remainder of division|result of operation|
|
||||
|`(list ..)`|Create a list of values||
|
||||
|`(insert list index element)`|Insert an element into a list. Indexed from 0|new list with value inserted|
|
||||
|`(index list index)`|Return element at index in list|Element at index|
|
||||
|`(index list index)`|Return element at index in list. First element is at index 0|Element at index|
|
||||
|`(remove list index)`|Remove a value at an index from a list|List with element removed|
|
||||
|`(len list)`|Get the length of a list|list length|
|
||||
|`(push list element)`|Add an item to the end of a list|new list with element added|
|
||||
|
|
|
|||
75
ml.cpp
75
ml.cpp
|
|
@ -16,6 +16,7 @@
|
|||
#include <cmath>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
|
|
@ -81,7 +82,7 @@ MlValue::MlValue(bool b) {
|
|||
else type = NIL;
|
||||
}
|
||||
|
||||
MlValue::MlValue(const std::vector<MlValue> &list) : type(LIST), list(list) {}
|
||||
MlValue::MlValue(std::vector<MlValue> list) : type(LIST), list(std::move(list)) {}
|
||||
|
||||
MlValue::MlValue(const std::vector<std::string> &slist) : type(LIST) {
|
||||
list.reserve(slist.size());
|
||||
|
|
@ -618,7 +619,7 @@ std::ostream &operator<<(std::ostream &os, MlValue const &v) {
|
|||
}
|
||||
|
||||
|
||||
MlError::MlError(const MlValue &v, MlEnvironment const &env, const char *msg) : env(env), msg(msg) {
|
||||
MlError::MlError(const MlValue &v, const MlEnvironment &env, const char *msg) : env(env), msg(msg) {
|
||||
cause = new MlValue;
|
||||
*cause = v;
|
||||
}
|
||||
|
|
@ -660,11 +661,11 @@ std::ostream &operator<<(std::ostream &os, MlEnvironment const &e) {
|
|||
}
|
||||
|
||||
void MlEnvironment::set(const std::string &name, MlValue value) {
|
||||
defs[name] = value;
|
||||
defs[name] = std::move(value);
|
||||
}
|
||||
|
||||
|
||||
void MlEnvironment::setX(const std::string &name, MlValue value) {
|
||||
void MlEnvironment::setX(const std::string &name, const MlValue& value) {
|
||||
MlEnvironment *e = this;
|
||||
while (e != nullptr) {
|
||||
auto itr = e->defs.find(name);
|
||||
|
|
@ -729,7 +730,7 @@ MlValue MlValue::eval(MlEnvironment &env) {
|
|||
case ATOM:
|
||||
return env.get(str);
|
||||
case LIST:
|
||||
if (list.size() < 1)
|
||||
if (list.empty())
|
||||
return MlValue::nil();
|
||||
|
||||
args = std::vector<MlValue>(list.begin() + 1, list.end());
|
||||
|
|
@ -740,8 +741,8 @@ MlValue MlValue::eval(MlEnvironment &env) {
|
|||
// Builtin functions can be special forms, so we
|
||||
// leave them to evaluate their arguments.
|
||||
if (!function.is_builtin())
|
||||
for (size_t i = 0; i < args.size(); i++)
|
||||
args[i] = args[i].eval(env);
|
||||
for (auto & arg : args)
|
||||
arg = arg.eval(env);
|
||||
|
||||
MlPerfMon::instance().add_method_call(function, args);
|
||||
res = function.apply(args, env);
|
||||
|
|
@ -781,7 +782,7 @@ MlValue parse(std::string &s, int &ptr) {
|
|||
throw std::runtime_error(INTERNAL_ERROR);
|
||||
|
||||
|
||||
if (s == "") {
|
||||
if (s.empty()) {
|
||||
return MlValue();
|
||||
|
||||
} else if (s[ptr] == '\'') {
|
||||
|
|
@ -812,7 +813,7 @@ MlValue parse(std::string &s, int &ptr) {
|
|||
skip_whitespace(s, ptr);
|
||||
|
||||
if (n.find('.') != std::string::npos)
|
||||
return MlValue((negate ? -1l : 1l) * atof(n.c_str()));
|
||||
return MlValue((negate ? -1.0 : 1.0) * atof(n.c_str()));
|
||||
else return MlValue((negate ? -1l : 1l) * atol(n.c_str()));
|
||||
|
||||
} else if (s[ptr] == '\"') {
|
||||
|
|
@ -1049,8 +1050,11 @@ MlValue scope(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
// Quote an expression (SPECIAL FORM)
|
||||
MlValue quote(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
std::vector<MlValue> v;
|
||||
|
||||
v.reserve(args.size());
|
||||
for (const auto &arg : args)
|
||||
v.push_back(arg);
|
||||
|
||||
return MlValue(v);
|
||||
}
|
||||
|
||||
|
|
@ -1085,7 +1089,7 @@ MlValue exit(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
for(auto &t : threads_register)
|
||||
t.detach();
|
||||
|
||||
std::exit(args.size() < 1 ? 0 : args[0].cast_to_int().as_int());
|
||||
std::exit(args.empty() ? 0 : (int)args[0].cast_to_int().as_int());
|
||||
return MlValue(); // will not be called :-)
|
||||
}
|
||||
|
||||
|
|
@ -1093,7 +1097,7 @@ MlValue exit(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
MlValue print(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
eval_args(args, env);
|
||||
|
||||
if (args.size() < 1)
|
||||
if (args.empty())
|
||||
throw MlError(MlValue("print", print), env, TOO_FEW_ARGS);
|
||||
|
||||
MlValue acc;
|
||||
|
|
@ -1194,8 +1198,8 @@ MlValue read_url(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
eval_args(args, env);
|
||||
|
||||
// PERF optimize it for memory usage and performance
|
||||
if (args.size() < 1 || args.size() > 2)
|
||||
throw MlError(MlValue("read_url", read_url), env, args.size() < 1 ? TOO_FEW_ARGS : TOO_MANY_ARGS);
|
||||
if (args.empty() || args.size() > 2)
|
||||
throw MlError(MlValue("read_url", read_url), env, args.empty() ? TOO_FEW_ARGS : TOO_MANY_ARGS);
|
||||
|
||||
std::unordered_map<std::string, std::string> headers = {};
|
||||
HttpClient client;
|
||||
|
|
@ -1237,7 +1241,7 @@ MlValue parse_json(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
MlValue get_universal_time(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
eval_args(args, env);
|
||||
|
||||
if (args.size() != 0)
|
||||
if (!args.empty())
|
||||
throw MlError(MlValue("get-universal-time", get_universal_time), env, TOO_MANY_ARGS);
|
||||
|
||||
return MlValue(now());
|
||||
|
|
@ -1247,7 +1251,7 @@ MlValue get_universal_time(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
MlValue get_localtime_offset(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
eval_args(args, env);
|
||||
|
||||
if (args.size() != 0)
|
||||
if (!args.empty())
|
||||
throw MlError(MlValue("get-localtime-offset", get_localtime_offset), env, TOO_MANY_ARGS);
|
||||
|
||||
return MlValue(get_gmt_localtime_offset());
|
||||
|
|
@ -1280,7 +1284,7 @@ MlValue date_add(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
if (args.size() != 3)
|
||||
throw MlError(MlValue("date-add", date_add), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
||||
|
||||
return MlValue(add_to_date(args[0].as_int(), args[1].as_int(), args[2].as_string()));
|
||||
return MlValue(add_to_date(args[0].as_int(), (int)args[1].as_int(), args[2].as_string()));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1345,7 +1349,7 @@ MlValue tcp_server(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
};
|
||||
|
||||
TcpNet server;
|
||||
int r = server.server(args[0].as_int(), proccess_req);
|
||||
int r = server.server((int)args[0].as_int(), proccess_req);
|
||||
return MlValue((long)r);
|
||||
}
|
||||
|
||||
|
|
@ -1361,10 +1365,10 @@ MlValue tcp_client(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
std::vector<std::string> requests;// PERF reserve
|
||||
|
||||
std::transform(request_list.begin(), request_list.end(), back_inserter(requests), std::mem_fn(&MlValue::as_string));
|
||||
std::vector<std::string> response = tcpclient.client(args[0].as_string(), args[1].as_int(), requests);
|
||||
std::vector<std::string> response = tcpclient.client(args[0].as_string(), (int)args[1].as_int(), requests);
|
||||
return MlValue(response);
|
||||
} else {
|
||||
std::string response = tcpclient.client(args[0].as_string(), args[1].as_int(), args[2].as_string());
|
||||
std::string response = tcpclient.client(args[0].as_string(), (int)args[1].as_int(), args[2].as_string());
|
||||
return MlValue::string(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -1623,7 +1627,7 @@ MlValue len(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
MlValue push(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
eval_args(args, env);
|
||||
|
||||
if (args.size() == 0)
|
||||
if (args.empty())
|
||||
throw MlError(MlValue("push", push), env, TOO_FEW_ARGS);
|
||||
|
||||
for (size_t i = 1; i < args.size(); i++)
|
||||
|
|
@ -1739,8 +1743,9 @@ MlValue string_regex_list(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
|
||||
auto found_matches = regexp_search2(args[0].as_string(), args[1].as_string(), match_mode, ignore_case);
|
||||
std::vector<MlValue> list;
|
||||
list.reserve(found_matches.size());
|
||||
for(auto &item : found_matches) {
|
||||
list.push_back(item);
|
||||
list.emplace_back(item);
|
||||
}
|
||||
return MlValue(list);
|
||||
}
|
||||
|
|
@ -1752,7 +1757,6 @@ MlValue string_split(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
if (args.size() != 2)
|
||||
throw MlError(MlValue("string-split", string_split), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
||||
|
||||
// TODO do it more efficient
|
||||
std::vector<std::string> elements = regexp_strsplit(args[0].as_string(), args[1].as_string());
|
||||
return MlValue(elements);
|
||||
}
|
||||
|
|
@ -1779,12 +1783,12 @@ MlValue string_len(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
MlValue string_substr(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
eval_args(args, env);
|
||||
|
||||
if (args.size() < 1 || args.size() > 3)
|
||||
if (args.empty() || args.size() > 3)
|
||||
throw MlError(MlValue("string-substr", string_substr), env, args.size() > 3 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
||||
|
||||
const std::string &str = args[0].as_string();
|
||||
long pos = args.size() > 1 ? args[1].as_int() : 0;
|
||||
long count = args.size() > 2 ? args[2].as_int() : str.size();
|
||||
auto count = args.size() > 2 ? args[2].as_int() : str.size();
|
||||
|
||||
return MlValue::string(string_substring(str, pos, count));
|
||||
}
|
||||
|
|
@ -1819,8 +1823,7 @@ MlValue string_pad(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
if (args.size() != 4)
|
||||
throw MlError(MlValue("string_pad", string_pad), env, args.size() > 4 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
||||
|
||||
// TODO validate len > 0 etc
|
||||
return MlValue::string(string_padd(args[0].as_string(), args[1].as_int(), args[2].as_string()[0],
|
||||
return MlValue::string(string_padd(args[0].as_string(), (size_t)args[1].as_int(), args[2].as_string()[0],
|
||||
(args[3].as_string() == "rpad")));
|
||||
}
|
||||
|
||||
|
|
@ -1845,7 +1848,7 @@ MlValue debug(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
MlValue sprintf(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
eval_args(args, env);
|
||||
|
||||
if (args.size() < 1 || args.size() > 2)
|
||||
if (args.empty() || args.size() > 2)
|
||||
throw MlError(MlValue("sprintf", sprintf), env, args.size() > 2 ? TOO_MANY_ARGS : TOO_FEW_ARGS);
|
||||
|
||||
return MlValue::string(mini_sprintf(args[0].as_string(), args.size() == 2 ? args[1].as_list() : std::vector<MlValue>{}));
|
||||
|
|
@ -1969,7 +1972,7 @@ MlValue thread_create(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
|
||||
static_assert(sizeof(std::thread::id) == sizeof(uint64_t),
|
||||
"size of thead::id is not equal to the size of uint_64");
|
||||
uint64_t *ptr = (uint64_t *) &th_id;
|
||||
auto *ptr = (uint64_t *) &th_id;
|
||||
long tid = (*ptr);
|
||||
|
||||
return MlValue(tid);
|
||||
|
|
@ -2001,7 +2004,7 @@ MlValue thread_sleep(std::vector<MlValue> args, MlEnvironment &env) {
|
|||
}
|
||||
|
||||
MlValue threads_join(std::vector<MlValue> args, MlEnvironment &env) {
|
||||
if (args.size() != 0)
|
||||
if (!args.empty())
|
||||
throw MlError(MlValue("threads-join", threads_join), env, TOO_MANY_ARGS);
|
||||
|
||||
// here is a question about using lockGuard, when used it holds lockGuard locked until
|
||||
|
|
@ -2076,12 +2079,13 @@ bool MlEnvironment::has(const std::string &name) const {
|
|||
|
||||
|
||||
|
||||
std::map <std::string, Builtin> builtin_funcs {
|
||||
std::map <const std::string, Builtin> builtin_funcs
|
||||
{
|
||||
// Special forms
|
||||
std::make_pair("define", builtin::define),
|
||||
std::make_pair("lambda", builtin::lambda),
|
||||
std::make_pair("if", builtin::if_then_else),
|
||||
std::make_pair("if", builtin::cond),
|
||||
std::make_pair("cond", builtin::cond),
|
||||
std::make_pair("do", builtin::do_block),
|
||||
std::make_pair("for", builtin::for_loop),
|
||||
std::make_pair("while", builtin::while_loop),
|
||||
|
|
@ -2202,7 +2206,6 @@ std::map <std::string, Builtin> builtin_funcs {
|
|||
|
||||
// Get the value associated with this name in this scope
|
||||
MlValue MlEnvironment::get(const std::string &name) const {
|
||||
|
||||
// PERF, here can be a few of for fast access
|
||||
if (name == "define") return MlValue("define", builtin::define);
|
||||
if (name == "if") return MlValue("if", builtin::if_then_else);
|
||||
|
|
@ -2262,7 +2265,7 @@ void repl(MlEnvironment &env) {
|
|||
std::getline(std::cin, input);
|
||||
|
||||
write_file_contents(input, code);
|
||||
} else if (input != "") {
|
||||
} else if (!input.empty()) {
|
||||
try {
|
||||
tmp = run(input, env);
|
||||
std::cout << " => " << tmp.debug() << std::endl;
|
||||
|
|
@ -2289,7 +2292,7 @@ std::vector<std::string> getCmdOption(char *argv[], int argc, const std::string
|
|||
for (int i = 1; i < argc; ++i) {
|
||||
if (option == argv[i] && i + 1 < argc) {
|
||||
i++;
|
||||
tokens.push_back(std::string(argv[i]));
|
||||
tokens.emplace_back(argv[i]);
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
|
|
@ -2298,6 +2301,8 @@ std::vector<std::string> getCmdOption(char *argv[], int argc, const std::string
|
|||
int main(int argc, char *argv[]) {
|
||||
MlEnvironment env;
|
||||
std::vector<MlValue> args;
|
||||
|
||||
args.reserve(argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
args.push_back(MlValue::string(argv[i]));
|
||||
env.set("cmd-args", MlValue(args));
|
||||
|
|
@ -2338,7 +2343,7 @@ int main(int argc, char *argv[]) {
|
|||
for (auto & file : getCmdOption(argv, argc, "-run")) { // TODO check only one file is specified ??
|
||||
std::string file_content = read_file_contents(file);
|
||||
if (file_content.find("#!") == 0) // shebang ?
|
||||
file_content.erase(0, file_content.find("\n") + 1); // TODO mac osx newline??
|
||||
file_content.erase(0, file_content.find('\n') + 1); // TODO mac osx newline??
|
||||
|
||||
run(file_content, env);
|
||||
}
|
||||
|
|
|
|||
8
ml.h
8
ml.h
|
|
@ -43,7 +43,7 @@ public:
|
|||
|
||||
// Set the value associated with this name in this scope and parent scopes
|
||||
// and if not exists sets in this scope
|
||||
void setX(const std::string &name, MlValue value);
|
||||
void setX(const std::string &name, const MlValue& value);
|
||||
|
||||
// Get vector of executables in this scope
|
||||
std::vector<std::string> get_lambdas_list() const;
|
||||
|
|
@ -71,7 +71,7 @@ public:
|
|||
|
||||
// Create an error with the value that caused the error,
|
||||
// the scope where the error was found, and the message.
|
||||
MlError(const MlValue &v, MlEnvironment const &env, const char *msg);
|
||||
MlError(const MlValue &v, const MlEnvironment &env, const char *msg);
|
||||
|
||||
// Copy constructor is needed to prevent double frees
|
||||
MlError(MlError const &other);
|
||||
|
|
@ -104,7 +104,7 @@ public:
|
|||
MlValue(long i);
|
||||
MlValue(double f);
|
||||
MlValue(bool b);
|
||||
MlValue(const std::vector<MlValue> &list); // Constructs a list
|
||||
MlValue(std::vector<MlValue> list); // Constructs a list
|
||||
MlValue(const std::vector<std::string> &slist); // Constructs a list from vector of strings
|
||||
|
||||
static MlValue quote(const MlValue "ed); // Construct a quoted value
|
||||
|
|
@ -136,7 +136,7 @@ public:
|
|||
std::vector<MlValue> as_list() const;
|
||||
|
||||
// Push an item to the end of this list
|
||||
void push(MlValue val);
|
||||
void push(const MlValue& val);
|
||||
|
||||
// Push an item from the end of this list
|
||||
MlValue pop();
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ std::string string_padd(const std::string &str, size_t pad_len, char fill_char,
|
|||
}
|
||||
|
||||
|
||||
std::string string_substring(const std::string & str, long pos, long count) {
|
||||
std::string string_substring(const std::string & str, long pos, size_t count) {
|
||||
size_t start_pos = pos;
|
||||
|
||||
if (pos < 0) {
|
||||
|
|
@ -155,5 +155,5 @@ size_t string_find_substr(const std::string & str, const std::string & pattern,
|
|||
|
||||
size_t p = str.find(pattern, pos);
|
||||
|
||||
return p != str.npos ? p : -1;
|
||||
return p != std::string::npos ? p : -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ std::string string_trim(std::string s, const std::string &chars_to_trim, const s
|
|||
|
||||
std::string string_padd(const std::string & str, size_t pad_len, char fill_char, bool from_right);
|
||||
|
||||
std::string string_substring(const std::string & str, long pos, long count);
|
||||
std::string string_substring(const std::string & str, long pos, size_t count);
|
||||
|
||||
size_t string_find_substr(const std::string & str, const std::string & pattern, size_t pos);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ std::string get_history_file_dir() {
|
|||
else return std::string{t} + "/" + file;
|
||||
}
|
||||
|
||||
// TODO fujtajbl
|
||||
MlEnvironment * repl_env = nullptr;
|
||||
|
||||
void setup_linenoise(const MlEnvironment &env) {
|
||||
|
|
@ -69,13 +68,13 @@ void completion(const char *buf, linenoiseCompletions *lc) {
|
|||
std::string token = str.substr(pos+1);
|
||||
std::string begining = str.substr(0, pos+1);
|
||||
|
||||
// TODO optimize not to get all lambdas, but those begining with token
|
||||
// TODO optimize not to get all lambdas, but those beginning with token
|
||||
std::vector<std::string> lambdas = repl_env->get_lambdas_list();
|
||||
lambdas.insert(end(lambdas), begin(commands), end(commands));
|
||||
|
||||
for (std::vector<std::string>::iterator t = lambdas.begin(); t != lambdas.end(); ++t) {
|
||||
if(t->find(token) == 0) {
|
||||
std::string completion_string = begining + *t;
|
||||
for (const auto & lambda : lambdas) {
|
||||
if(lambda.find(token) == 0) {
|
||||
std::string completion_string = begining + lambda;
|
||||
linenoiseAddCompletion(lc, completion_string.c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ std::vector<std::pair<std::string, std::string>> Settings::m_settings =
|
|||
long Settings::string_to_long(const std::string &intstr) {
|
||||
try {
|
||||
return std::stol(intstr);
|
||||
} catch (std::invalid_argument &e) {
|
||||
} catch (const std::invalid_argument &e) {
|
||||
throw Exception("error parsing as integer: " + intstr);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ void Table::commit_row(Row &row) {
|
|||
try {
|
||||
validate_row(row);
|
||||
index_row(row);
|
||||
} catch (Exception &e) {
|
||||
} catch (const Exception &e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ std::unique_ptr<Table> USql::execute(const std::string &command) {
|
|||
std::unique_ptr<Node> node = m_parser.parse(command);
|
||||
return execute(*node);
|
||||
|
||||
} catch (std::exception &e) {
|
||||
} catch (const std::exception &e) {
|
||||
return create_stmt_result_table(-1, e.what(), 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,8 +82,7 @@ std::unique_ptr<ValueNode> USql::pp_function(const std::vector<std::unique_ptr<V
|
|||
return std::make_unique<StringValueNode>(parsed_value->getStringValue());
|
||||
}
|
||||
|
||||
std::unique_ptr<ValueNode>
|
||||
USql::max_function(const std::vector<std::unique_ptr<ValueNode>> &evaluatedPars, const ColDefNode *col_def_node, ColValue *agg_func_value) {
|
||||
std::unique_ptr<ValueNode> USql::max_function(const std::vector<std::unique_ptr<ValueNode>> &evaluatedPars, const ColDefNode *col_def_node, ColValue *agg_func_value) {
|
||||
if (col_def_node->type == ColumnType::integer_type || col_def_node->type == ColumnType::date_type) {
|
||||
if (!evaluatedPars[0]->isNull()) {
|
||||
auto val = evaluatedPars[0]->getIntegerValue();
|
||||
|
|
|
|||
Loading…
Reference in New Issue