lab 4 complete
This commit is contained in:
parent
2769a3a0ad
commit
69af73e315
12 changed files with 476 additions and 79 deletions
59
lab4/.vscode/settings.json
vendored
Normal file
59
lab4/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"regex": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"new": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ostream": "cpp",
|
||||
"span": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"text_encoding": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp"
|
||||
}
|
||||
}
|
74
lab4/Sieve.cc
Normal file
74
lab4/Sieve.cc
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Sieve {
|
||||
private:
|
||||
std::string sieve;
|
||||
|
||||
public:
|
||||
// Constructor to initialize sieve with 'P' (prime assumption)
|
||||
Sieve(size_t limit) : sieve(limit + 1, 'P') {
|
||||
// 0 and 1 are not primes
|
||||
sieve[0] = 'C';
|
||||
if (limit > 0) sieve[1] = 'C';
|
||||
|
||||
// Sieve of Eratosthenes
|
||||
for (size_t i = 2; i * i <= limit; ++i) {
|
||||
if (sieve[i] == 'P') {
|
||||
for (size_t j = i * i; j <= limit; j += i) {
|
||||
sieve[j] = 'C';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get primes as a vector of integers
|
||||
std::vector<int> getPrimes() const {
|
||||
std::vector<int> primes;
|
||||
for (size_t i = 2; i < sieve.size(); ++i) {
|
||||
if (sieve[i] == 'P') {
|
||||
primes.push_back(i);
|
||||
}
|
||||
}
|
||||
return primes;
|
||||
}
|
||||
|
||||
// Print all primes in the range
|
||||
void printPrimesInRange(int start, int end) const {
|
||||
for (int i = start; i <= end; ++i) {
|
||||
if (sieve[i] == 'P') {
|
||||
std::cout << i << " ";
|
||||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Get the largest prime below a limit
|
||||
int largestPrimeBelow(int limit) const {
|
||||
for (int i = limit; i >= 2; --i) {
|
||||
if (sieve[i] == 'P') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // If no prime is found
|
||||
}
|
||||
};
|
||||
|
||||
// Main function for testing
|
||||
int main() {
|
||||
const int limit = 100000;
|
||||
|
||||
// Create a Sieve instance for the range 0 to limit
|
||||
Sieve sieve(limit);
|
||||
|
||||
// Print primes between 1 and 200
|
||||
std::cout << "Primes between 1 and 200:" << std::endl;
|
||||
sieve.printPrimesInRange(1, 200);
|
||||
|
||||
// Find and print the largest prime below 100,000
|
||||
int largestPrime = sieve.largestPrimeBelow(limit);
|
||||
std::cout << "Largest prime below " << limit << ": " << largestPrime << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
32
lab4/TagRemover.cc
Normal file
32
lab4/TagRemover.cc
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
class TagRemover {
|
||||
private:
|
||||
std::string content;
|
||||
|
||||
public:
|
||||
TagRemover(std::istream& input) {
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
content += line + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void print(std::ostream& output) const {
|
||||
std::string result = std::regex_replace(content, std::regex("<[^>]*>"), "");
|
||||
result = std::regex_replace(result, std::regex("<"), "<");
|
||||
result = std::regex_replace(result, std::regex(">"), ">");
|
||||
result = std::regex_replace(result, std::regex(" "), " ");
|
||||
result = std::regex_replace(result, std::regex("&"), "&");
|
||||
|
||||
output << result;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
TagRemover tr(std::cin);
|
||||
tr.print(std::cout);
|
||||
return 0;
|
||||
}
|
70
lab4/date.cc
70
lab4/date.cc
|
@ -1,30 +1,86 @@
|
|||
#include <ctime> // time and localtime
|
||||
#include <ctime> // för tid och localtime
|
||||
#include <iomanip> // för setw och setfill
|
||||
#include <sstream> // för inputhantering
|
||||
#include "date.h"
|
||||
|
||||
int Date::daysPerMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
// Konstruktor: dagens datum
|
||||
Date::Date() {
|
||||
time_t timer = time(0); // time in seconds since 1970-01-01
|
||||
tm* locTime = localtime(&timer); // broken-down time
|
||||
time_t timer = time(0); // tid i sekunder sedan 1970-01-01
|
||||
tm* locTime = localtime(&timer); // lokal tid
|
||||
year = 1900 + locTime->tm_year;
|
||||
month = 1 + locTime->tm_mon;
|
||||
day = locTime->tm_mday;
|
||||
}
|
||||
|
||||
Date::Date(int y, int m, int d) {}
|
||||
// Konstruktor: specifikt datum
|
||||
Date::Date(int y, int m, int d) : year(y), month(m), day(d) {}
|
||||
|
||||
// Get-funktioner
|
||||
int Date::getYear() const {
|
||||
return 0;
|
||||
return year;
|
||||
}
|
||||
|
||||
int Date::getMonth() const {
|
||||
return 0;
|
||||
return month;
|
||||
}
|
||||
|
||||
int Date::getDay() const {
|
||||
return 0;
|
||||
return day;
|
||||
}
|
||||
|
||||
// Gå till nästa dag
|
||||
void Date::next() {
|
||||
day++;
|
||||
if (day > daysPerMonth[month - 1] + (month == 2 && isLeapYear(year))) {
|
||||
day = 1;
|
||||
month++;
|
||||
if (month > 12) {
|
||||
month = 1;
|
||||
year++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kontrollera om ett år är ett skottår
|
||||
bool Date::isLeapYear(int year) {
|
||||
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
||||
}
|
||||
|
||||
// Overloaded operator<< (output)
|
||||
std::ostream& operator<<(std::ostream& os, const Date& date) {
|
||||
os << std::setw(4) << std::setfill('0') << date.getYear() << '-'
|
||||
<< std::setw(2) << std::setfill('0') << date.getMonth() << '-'
|
||||
<< std::setw(2) << std::setfill('0') << date.getDay();
|
||||
return os;
|
||||
}
|
||||
|
||||
// Overloaded operator>> (input)
|
||||
std::istream& operator>>(std::istream& is, Date& date) {
|
||||
std::string input;
|
||||
is >> input;
|
||||
|
||||
std::istringstream iss(input);
|
||||
char dash1, dash2;
|
||||
int y, m, d;
|
||||
|
||||
if (iss >> y >> dash1 >> m >> dash2 >> d && dash1 == '-' && dash2 == '-') {
|
||||
// Validera månad och dag
|
||||
if (m >= 1 && m <= 12) {
|
||||
int maxDay = Date::daysPerMonth[m - 1];
|
||||
if (m == 2 && Date::isLeapYear(y)) {
|
||||
maxDay = 29; // Februari har 29 dagar under skottår
|
||||
}
|
||||
|
||||
if (d >= 1 && d <= maxDay) {
|
||||
date = Date(y, m, d); // Sätt datumet om det är giltigt
|
||||
return is;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ogiltig inmatning
|
||||
is.setstate(std::ios_base::failbit);
|
||||
return is;
|
||||
}
|
||||
|
|
26
lab4/date.h
26
lab4/date.h
|
@ -1,19 +1,27 @@
|
|||
#ifndef DATE_H
|
||||
#define DATE_H
|
||||
|
||||
#include <iostream>
|
||||
|
||||
class Date {
|
||||
public:
|
||||
Date(); // today's date
|
||||
Date(); // dagens datum
|
||||
Date(int y, int m, int d); // yyyy-mm-dd
|
||||
int getYear() const; // get the year
|
||||
int getMonth() const; // get the month
|
||||
int getDay() const; // get the day
|
||||
void next(); // advance to next day
|
||||
int getYear() const; // returnerar året
|
||||
int getMonth() const; // returnerar månaden
|
||||
int getDay() const; // returnerar dagen
|
||||
void next(); // går till nästa dag
|
||||
|
||||
// Overloaded operators
|
||||
friend std::ostream& operator<<(std::ostream& os, const Date& date);
|
||||
friend std::istream& operator>>(std::istream& is, Date& date);
|
||||
|
||||
private:
|
||||
int year; // the year (four digits)
|
||||
int month; // the month (1-12)
|
||||
int day; // the day (1-..)
|
||||
static int daysPerMonth[12]; // number of days in each month
|
||||
int year; // året (fyra siffror)
|
||||
int month; // månaden (1-12)
|
||||
int day; // dagen (1-...)
|
||||
static int daysPerMonth[12]; // antal dagar i varje månad
|
||||
static bool isLeapYear(int year); // kontrollera skottår
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,64 +1,40 @@
|
|||
#include <iostream>
|
||||
#include <iomanip> // for setw and setfill
|
||||
#include "date.h"
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::setw;
|
||||
using std::setfill;
|
||||
|
||||
/*
|
||||
* Prints the date d in the format yyyy-mm-dd. You shall replace this
|
||||
* function with an overloaded operator<<, and add an overloaded operator>>.
|
||||
*
|
||||
*/
|
||||
void print(const Date& d) {
|
||||
cout << setw(4) << setfill('0') << d.getYear() << '-';
|
||||
cout << setw(2) << setfill('0') << d.getMonth() << '-';
|
||||
cout << setw(2) << setfill('0') << d.getDay();
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Check input and output of dates. Uncomment the following when you
|
||||
// have added operator>> and operator<<.
|
||||
/*
|
||||
// Test input och output
|
||||
bool cont = true;
|
||||
while (cont) {
|
||||
cout << "Type a date: ";
|
||||
std::cout << "Type a date: ";
|
||||
Date aDate;
|
||||
cin >> aDate;
|
||||
if (cin.eof()) {
|
||||
cont = false;
|
||||
} else if (!cin.good()) {
|
||||
cout << "Wrong input format" << endl;
|
||||
// restore stream state and ignore the rest of the line
|
||||
cin.clear();
|
||||
cin.ignore(10000, '\n');
|
||||
}
|
||||
else {
|
||||
cout << "Output: " << aDate << endl;
|
||||
}
|
||||
}
|
||||
*/
|
||||
std::cin >> aDate;
|
||||
|
||||
// Check 'next' by creating an object describing today's date, then
|
||||
// printing dates more than a month ahead
|
||||
cout << "--- Today and more than a month ahead:" << endl;
|
||||
if (std::cin.eof()) {
|
||||
cont = false;
|
||||
} else if (!std::cin.good()) {
|
||||
std::cout << "Wrong input format" << std::endl;
|
||||
std::cin.clear();
|
||||
std::cin.ignore(10000, '\n');
|
||||
} else {
|
||||
std::cout << "Output: " << aDate << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Testa 'next' med dagens datum
|
||||
std::cout << "--- Today and more than a month ahead:" << std::endl;
|
||||
Date d1;
|
||||
print(d1);
|
||||
cout << endl;
|
||||
std::cout << d1 << std::endl;
|
||||
for (int i = 1; i <= 35; ++i) {
|
||||
d1.next();
|
||||
print(d1);
|
||||
cout << endl;
|
||||
std::cout << d1 << std::endl;
|
||||
}
|
||||
|
||||
// Check so 'next' functions correctly from one year to the next
|
||||
cout << "--- New Year's Eve and the next day:" << endl;
|
||||
// Testa 'next' från nyårsafton
|
||||
std::cout << "--- New Year's Eve and the next day:" << std::endl;
|
||||
Date d2(2013, 12, 31);
|
||||
print(d2);
|
||||
cout << endl;
|
||||
std::cout << d2 << std::endl;
|
||||
d2.next();
|
||||
print(d2);
|
||||
cout << endl;
|
||||
std::cout << d2 << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
56
lab4/makefile
Normal file
56
lab4/makefile
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Compiler
|
||||
CXX = g++
|
||||
CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic
|
||||
|
||||
# Targets
|
||||
TARGETS = TagRemover Sieve date_test toString_test string_cast_test
|
||||
|
||||
# Source files
|
||||
SRCS_TAGREMOVER = TagRemover.cc
|
||||
SRCS_SIEVE = Sieve.cc
|
||||
SRCS_DATE = date.cc
|
||||
SRCS_DATE_TEST = date_test.cc
|
||||
SRCS_TOSTRING_TEST = toString_test.cc date.cc
|
||||
SRCS_STRING_CAST_TEST = string_cast_test.cc date.cc
|
||||
|
||||
# Object files
|
||||
OBJS_TAGREMOVER = $(SRCS_TAGREMOVER:.cc=.o)
|
||||
OBJS_SIEVE = $(SRCS_SIEVE:.cc=.o)
|
||||
OBJS_DATE = $(SRCS_DATE:.cc=.o)
|
||||
OBJS_DATE_TEST = $(SRCS_DATE_TEST:.cc=.o)
|
||||
OBJS_TOSTRING_TEST = $(SRCS_TOSTRING_TEST:.cc=.o)
|
||||
OBJS_STRING_CAST_TEST = $(SRCS_STRING_CAST_TEST:.cc=.o)
|
||||
|
||||
# Default target
|
||||
all: $(TARGETS)
|
||||
|
||||
# Rule to build TagRemover
|
||||
TagRemover: $(OBJS_TAGREMOVER)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $^
|
||||
|
||||
# Rule to build Sieve
|
||||
Sieve: $(OBJS_SIEVE)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $^
|
||||
|
||||
# Rule to build date_test
|
||||
date_test: $(OBJS_DATE_TEST) $(OBJS_DATE)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $^
|
||||
|
||||
# Rule to build toString_test
|
||||
toString_test: $(OBJS_TOSTRING_TEST)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $^
|
||||
|
||||
# Rule to build string_cast_test
|
||||
string_cast_test: $(OBJS_STRING_CAST_TEST)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $^
|
||||
|
||||
# Rule to compile source files into object files
|
||||
%.o: %.cc
|
||||
$(CXX) $(CXXFLAGS) -c $<
|
||||
|
||||
# Clean up build artifacts
|
||||
clean:
|
||||
rm -f $(OBJS_TAGREMOVER) $(OBJS_SIEVE) $(OBJS_DATE) $(OBJS_DATE_TEST) $(OBJS_TOSTRING_TEST) $(OBJS_STRING_CAST_TEST) $(TARGETS)
|
||||
|
||||
# Phony targets
|
||||
.PHONY: all clean
|
61
lab4/reflektion.txt
Normal file
61
lab4/reflektion.txt
Normal file
|
@ -0,0 +1,61 @@
|
|||
1. In your tests, how did you test the error handling (e.g., that a wrong string_cast actually
|
||||
throws?)
|
||||
|
||||
Genom att använda en ogiltig inmatning, som "abc" eller "123abc", och verifiera att std::invalid_argument kastas korrekt.
|
||||
|
||||
|
||||
2. In TagRemover, why do you think the constructor takes an istream instead of just the
|
||||
filename?
|
||||
|
||||
Det gör klassen mer flexibel eftersom den kan arbeta med vilken ström som helst, inte bara filer. Till exempel kan den användas med std::cin eller std::stringstream.
|
||||
|
||||
|
||||
3. In TagRemover, did you process the file line by line, or did you first read the entire file?
|
||||
What are the pros and cons of these two approaches?
|
||||
|
||||
Jag läste hela filen först eftersom det är enklare att använda regex för att bearbeta hela texten på en gång.
|
||||
Fördelen är att det är mer effektivt för regex-matchning, men nackdelen är att det kräver mer minne för stora filer.
|
||||
|
||||
|
||||
4. How do you read the entire contents of an std::istream into a std::string without using
|
||||
a for or while loop?
|
||||
|
||||
Genom att använda:
|
||||
std::string content((std::istreambuf_iterator<char>(istream)), std::istreambuf_iterator<char>());
|
||||
|
||||
|
||||
5. In TagRemover, do you have duplicate code for translating the special characters? If so, how
|
||||
would you refactor your code to avoid duplicate code?
|
||||
|
||||
Ja, det finns duplicerad kod för varje specialtecken. Jag skulle använda en std::map för att lagra mönster och ersättningar och iterera genom den:
|
||||
std::map<std::string, std::string> replacements = {
|
||||
{"<", "<"}, {">", ">"}, {" ", " "}, {"&", "&"}
|
||||
};
|
||||
for (const auto& [pattern, replacement] : replacements) {
|
||||
result = std::regex_replace(result, std::regex(pattern), replacement);
|
||||
}
|
||||
|
||||
|
||||
6. How do you check if an input or output operation on a stream (e.g., operator>> or
|
||||
operator<<) has failed?
|
||||
|
||||
Genom att använda std::istream::fail() eller std::ostream::fail().
|
||||
|
||||
|
||||
7. How do you know if you have reached the end of an istream?
|
||||
|
||||
Genom att använda std::istream::eof().
|
||||
|
||||
|
||||
8. Does string_cast<int>("123kalle") return the value 123 or does it throw an exception?
|
||||
How do you implement each of those behaviours?
|
||||
|
||||
Det kastar ett undantag eftersom std::istringstream misslyckas med att konsumera hela strängen. För att tillåta delvis inläsning skulle vi behöva anpassa funktionen.
|
||||
|
||||
|
||||
9. When calling the function template toString, the template type argument is not ex-
|
||||
plicitly given in the call. For string_cast, on the other hand, you have to specify
|
||||
string_cast<int> or string_cast<Date>. What is the difference? When should explicit
|
||||
template arguments be given to function templates?
|
||||
|
||||
toString använder <<-operatören, som automatiskt identifierar typen av objekt, medan string_cast kräver en explicit typ eftersom det behöver veta vad strängen ska konverteras till.
|
14
lab4/string_cast.h
Normal file
14
lab4/string_cast.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept> // För std::invalid_argument
|
||||
|
||||
// Template function for string_cast
|
||||
template <typename T>
|
||||
T string_cast(const std::string& str) {
|
||||
std::istringstream iss(str);
|
||||
T value;
|
||||
if (!(iss >> value) || !(iss.eof())) {
|
||||
throw std::invalid_argument("Invalid conversion from string: " + str);
|
||||
}
|
||||
return value;
|
||||
}
|
29
lab4/string_cast_test.cc
Normal file
29
lab4/string_cast_test.cc
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#include "date.h" // För Date-klassen
|
||||
#include "string_cast.h" // Inkludera string_cast
|
||||
|
||||
int main() {
|
||||
try {
|
||||
// Testa string_cast med int
|
||||
int i = string_cast<int>("123");
|
||||
std::cout << "Integer: " << i << std::endl;
|
||||
|
||||
// Testa string_cast med double
|
||||
double d = string_cast<double>("12.34");
|
||||
std::cout << "Double: " << d << std::endl;
|
||||
|
||||
// Testa string_cast med Date
|
||||
Date date = string_cast<Date>("2015-01-10");
|
||||
std::cout << "Date: " << date << std::endl;
|
||||
|
||||
// Testa ogiltig konvertering
|
||||
int invalid = string_cast<int>("abc");
|
||||
std::cout << "Invalid conversion: " << invalid << std::endl;
|
||||
|
||||
} catch (const std::invalid_argument& e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
10
lab4/toString.h
Normal file
10
lab4/toString.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
// Template function to convert an object to a string
|
||||
template <typename T>
|
||||
std::string toString(const T& obj) {
|
||||
std::ostringstream oss;
|
||||
oss << obj;
|
||||
return oss.str();
|
||||
}
|
22
lab4/toString_test.cc
Normal file
22
lab4/toString_test.cc
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include <iostream>
|
||||
#include "date.h" // Inkludera Date-klassen från tidigare lösning
|
||||
#include "toString.h" // Inkludera toString-mallen
|
||||
|
||||
int main() {
|
||||
// Testa med ett primitivt datatyper
|
||||
double d = 1.234;
|
||||
int i = 42;
|
||||
std::string strDouble = toString(d);
|
||||
std::string strInt = toString(i);
|
||||
|
||||
std::cout << "Double as string: " << strDouble << std::endl;
|
||||
std::cout << "Integer as string: " << strInt << std::endl;
|
||||
|
||||
// Testa med Date-klassen
|
||||
Date today(2023, 12, 11); // Skapa ett datumobjekt
|
||||
std::string strDate = toString(today);
|
||||
|
||||
std::cout << "Date as string: " << strDate << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue