vhdlpp: Unified Expression::evaluate() method.
This commit is contained in:
parent
23633c498f
commit
2c010d34bb
|
|
@ -88,14 +88,14 @@ ExpBinary::~ExpBinary()
|
||||||
delete operand2_;
|
delete operand2_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpBinary::eval_operand1(ScopeBase*scope, int64_t&val) const
|
bool ExpBinary::eval_operand1(Entity*ent, ScopeBase*scope, int64_t&val) const
|
||||||
{
|
{
|
||||||
return operand1_->evaluate(scope, val);
|
return operand1_->evaluate(ent, scope, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpBinary::eval_operand2(ScopeBase*scope, int64_t&val) const
|
bool ExpBinary::eval_operand2(Entity*ent, ScopeBase*scope, int64_t&val) const
|
||||||
{
|
{
|
||||||
return operand2_->evaluate(scope, val);
|
return operand2_->evaluate(ent, scope, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExpBinary::visit(ExprVisitor& func)
|
void ExpBinary::visit(ExprVisitor& func)
|
||||||
|
|
@ -493,7 +493,7 @@ ExpInteger::~ExpInteger()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpInteger::evaluate(ScopeBase*, int64_t&val) const
|
bool ExpInteger::evaluate(Entity*, ScopeBase*, int64_t&val) const
|
||||||
{
|
{
|
||||||
val = value_;
|
val = value_;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,8 @@ class Expression : public LineInfo {
|
||||||
// to constant literal values. Return true and set the val
|
// to constant literal values. Return true and set the val
|
||||||
// argument if the evaluation works, or return false if it
|
// argument if the evaluation works, or return false if it
|
||||||
// cannot be done.
|
// cannot be done.
|
||||||
virtual bool evaluate(ScopeBase*scope, int64_t&val) const;
|
virtual bool evaluate(Entity*, ScopeBase*, int64_t&) const { return false; }
|
||||||
virtual bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
bool evaluate(ScopeBase*scope, int64_t&val) const { return evaluate(NULL, scope, val); }
|
||||||
|
|
||||||
// The symbolic compare returns true if the two expressions
|
// The symbolic compare returns true if the two expressions
|
||||||
// are equal without actually calculating the value.
|
// are equal without actually calculating the value.
|
||||||
|
|
@ -205,8 +205,8 @@ class ExpBinary : public Expression {
|
||||||
int emit_operand1(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit_operand1(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
int emit_operand2(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit_operand2(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
|
|
||||||
bool eval_operand1(ScopeBase*scope, int64_t&val) const;
|
bool eval_operand1(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
bool eval_operand2(ScopeBase*scope, int64_t&val) const;
|
bool eval_operand2(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
|
|
||||||
inline void write_to_stream_operand1(std::ostream&out) const
|
inline void write_to_stream_operand1(std::ostream&out) const
|
||||||
{ operand1_->write_to_stream(out); }
|
{ operand1_->write_to_stream(out); }
|
||||||
|
|
@ -343,7 +343,7 @@ class ExpArithmetic : public ExpBinary {
|
||||||
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
||||||
void write_to_stream(std::ostream&fd) const;
|
void write_to_stream(std::ostream&fd) const;
|
||||||
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
virtual bool evaluate(ScopeBase*scope, int64_t&val) const;
|
virtual bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
void dump(ostream&out, int indent = 0) const;
|
void dump(ostream&out, int indent = 0) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -369,7 +369,6 @@ class ExpAttribute : public Expression {
|
||||||
void write_to_stream(std::ostream&fd) const;
|
void write_to_stream(std::ostream&fd) const;
|
||||||
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
// Some attributes can be evaluated at compile time
|
// Some attributes can be evaluated at compile time
|
||||||
bool evaluate(ScopeBase*scope, int64_t&val) const;
|
|
||||||
bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
void dump(ostream&out, int indent = 0) const;
|
void dump(ostream&out, int indent = 0) const;
|
||||||
void visit(ExprVisitor& func);
|
void visit(ExprVisitor& func);
|
||||||
|
|
@ -440,7 +439,6 @@ class ExpConcat : public Expression {
|
||||||
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
||||||
void write_to_stream(std::ostream&fd) const;
|
void write_to_stream(std::ostream&fd) const;
|
||||||
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
virtual bool evaluate(ScopeBase*scope, int64_t&val) const;
|
|
||||||
bool is_primary(void) const;
|
bool is_primary(void) const;
|
||||||
void dump(ostream&out, int indent = 0) const;
|
void dump(ostream&out, int indent = 0) const;
|
||||||
void visit(ExprVisitor& func);
|
void visit(ExprVisitor& func);
|
||||||
|
|
@ -583,7 +581,7 @@ class ExpInteger : public Expression {
|
||||||
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
int emit_package(std::ostream&out);
|
int emit_package(std::ostream&out);
|
||||||
bool is_primary(void) const { return true; }
|
bool is_primary(void) const { return true; }
|
||||||
bool evaluate(ScopeBase*scope, int64_t&val) const;
|
bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
void dump(ostream&out, int indent = 0) const;
|
void dump(ostream&out, int indent = 0) const;
|
||||||
virtual ostream& dump_inline(ostream&out) const;
|
virtual ostream& dump_inline(ostream&out) const;
|
||||||
|
|
||||||
|
|
@ -665,7 +663,6 @@ class ExpName : public Expression {
|
||||||
void write_to_stream(std::ostream&fd) const;
|
void write_to_stream(std::ostream&fd) const;
|
||||||
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
bool is_primary(void) const;
|
bool is_primary(void) const;
|
||||||
bool evaluate(ScopeBase*scope, int64_t&val) const;
|
|
||||||
bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
bool symbolic_compare(const Expression*that) const;
|
bool symbolic_compare(const Expression*that) const;
|
||||||
void dump(ostream&out, int indent = 0) const;
|
void dump(ostream&out, int indent = 0) const;
|
||||||
|
|
@ -773,7 +770,7 @@ class ExpShift : public ExpBinary {
|
||||||
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
||||||
void write_to_stream(std::ostream&fd) const;
|
void write_to_stream(std::ostream&fd) const;
|
||||||
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
virtual bool evaluate(ScopeBase*scope, int64_t&val) const;
|
bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
void dump(ostream&out, int indent = 0) const;
|
void dump(ostream&out, int indent = 0) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -891,7 +888,6 @@ class ExpTime : public Expression {
|
||||||
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
int elaborate_expr(Entity*ent, ScopeBase*scope, const VType*ltype);
|
||||||
void write_to_stream(std::ostream&) const;
|
void write_to_stream(std::ostream&) const;
|
||||||
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
int emit(ostream&out, Entity*ent, ScopeBase*scope);
|
||||||
//bool evaluate(ScopeBase*scope, int64_t&val) const;
|
|
||||||
//bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
//bool evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const;
|
||||||
void dump(ostream&out, int indent = 0) const;
|
void dump(ostream&out, int indent = 0) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ const VType* Expression::fit_type(Entity*ent, ScopeBase*scope, const VTypeArray*
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VType*ExpName::elaborate_adjust_type_with_range_(Entity*, ScopeBase*scope, const VType*type)
|
const VType*ExpName::elaborate_adjust_type_with_range_(Entity*ent, ScopeBase*scope,
|
||||||
|
const VType*type)
|
||||||
{
|
{
|
||||||
// Unfold typedefs
|
// Unfold typedefs
|
||||||
while (const VTypeDef*tdef = dynamic_cast<const VTypeDef*>(type)) {
|
while (const VTypeDef*tdef = dynamic_cast<const VTypeDef*>(type)) {
|
||||||
|
|
@ -76,9 +77,9 @@ const VType*ExpName::elaborate_adjust_type_with_range_(Entity*, ScopeBase*scope,
|
||||||
int64_t use_msb, use_lsb;
|
int64_t use_msb, use_lsb;
|
||||||
bool flag;
|
bool flag;
|
||||||
|
|
||||||
flag = index_->evaluate(scope, use_msb);
|
flag = index_->evaluate(ent, scope, use_msb);
|
||||||
ivl_assert(*this, flag);
|
ivl_assert(*this, flag);
|
||||||
flag = lsb_->evaluate(scope, use_lsb);
|
flag = lsb_->evaluate(ent, scope, use_lsb);
|
||||||
ivl_assert(*this, flag);
|
ivl_assert(*this, flag);
|
||||||
|
|
||||||
type = new VTypeArray(array->element_type(), use_msb, use_lsb);
|
type = new VTypeArray(array->element_type(), use_msb, use_lsb);
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ int ExpAttribute::emit(ostream&out, Entity*ent, ScopeBase*scope)
|
||||||
|
|
||||||
// Try to evaluate first
|
// Try to evaluate first
|
||||||
int64_t val;
|
int64_t val;
|
||||||
if(evaluate(scope, val)) {
|
if(evaluate(ent, scope, val)) {
|
||||||
out << val;
|
out << val;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,26 +25,16 @@
|
||||||
# include <limits>
|
# include <limits>
|
||||||
# include <cmath>
|
# include <cmath>
|
||||||
|
|
||||||
bool Expression::evaluate(ScopeBase*, int64_t&) const
|
bool ExpArithmetic::evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Expression::evaluate(Entity*, ScopeBase*scope, int64_t&val) const
|
|
||||||
{
|
|
||||||
return evaluate(scope, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpArithmetic::evaluate(ScopeBase*scope, int64_t&val) const
|
|
||||||
{
|
{
|
||||||
int64_t val1, val2;
|
int64_t val1, val2;
|
||||||
bool rc;
|
bool rc;
|
||||||
|
|
||||||
rc = eval_operand1(scope, val1);
|
rc = eval_operand1(ent, scope, val1);
|
||||||
if (rc == false)
|
if (rc == false)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
rc = eval_operand2(scope, val2);
|
rc = eval_operand2(ent, scope, val2);
|
||||||
if (rc == false)
|
if (rc == false)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -83,151 +73,97 @@ bool ExpArithmetic::evaluate(ScopeBase*scope, int64_t&val) const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpAttribute::evaluate(ScopeBase*scope, int64_t&val) const
|
|
||||||
{
|
|
||||||
/* Special Case: The array attributes can sometimes be calculated all
|
|
||||||
the down to a literal integer at compile time, and all it
|
|
||||||
needs is the type of the base expression. (The base
|
|
||||||
expression doesn't even need to be evaluated.) */
|
|
||||||
if (name_ == "length" || name_ == "right" || name_ == "left") {
|
|
||||||
const VType*base_type = base_->peek_type();
|
|
||||||
|
|
||||||
if(!base_type) {
|
|
||||||
const ExpName*name = NULL;
|
|
||||||
|
|
||||||
if(scope && (name = dynamic_cast<const ExpName*>(base_))) {
|
|
||||||
const perm_string& n = name->peek_name();
|
|
||||||
if(const Variable*var = scope->find_variable(n))
|
|
||||||
base_type = var->peek_type();
|
|
||||||
else if(const Signal*sig = scope->find_signal(n))
|
|
||||||
base_type = sig->peek_type();
|
|
||||||
else if(const InterfacePort*port = scope->find_param(n))
|
|
||||||
base_type = port->type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!base_type)
|
|
||||||
return false; // I tried really hard, sorry
|
|
||||||
|
|
||||||
const VTypeArray*arr = dynamic_cast<const VTypeArray*>(base_type);
|
|
||||||
if (arr == 0) {
|
|
||||||
cerr << endl << get_fileline() << ": error: "
|
|
||||||
<< "Cannot apply the '" << name_ << " attribute to non-array objects"
|
|
||||||
<< endl;
|
|
||||||
ivl_assert(*this, false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(name_ == "length") {
|
|
||||||
int64_t size = arr->get_width(scope);
|
|
||||||
|
|
||||||
if(size > 0)
|
|
||||||
val = size;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
} else if(name_ == "left") {
|
|
||||||
arr->dimension(0).msb()->evaluate(scope, val);
|
|
||||||
} else if(name_ == "right") {
|
|
||||||
arr->dimension(0).lsb()->evaluate(scope, val);
|
|
||||||
} else ivl_assert(*this, false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpAttribute::evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const
|
bool ExpAttribute::evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const
|
||||||
{
|
{
|
||||||
if (!ent || !scope) { // it's impossible to evaluate, probably it is inside a subprogram
|
const VType*base_type = base_->peek_type();
|
||||||
return false;
|
|
||||||
}
|
if (base_type == NULL)
|
||||||
|
base_type = base_->probe_type(ent, scope);
|
||||||
|
|
||||||
|
if (base_type == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (name_ == "left" || name_ == "right") {
|
if (name_ == "left" || name_ == "right") {
|
||||||
const VType*base_type = base_->peek_type();
|
|
||||||
if (base_type == 0)
|
|
||||||
base_type = base_->probe_type(ent, scope);
|
|
||||||
|
|
||||||
ivl_assert(*this, base_type);
|
|
||||||
|
|
||||||
const VTypeArray*arr = dynamic_cast<const VTypeArray*>(base_type);
|
const VTypeArray*arr = dynamic_cast<const VTypeArray*>(base_type);
|
||||||
if (arr == 0) {
|
if (arr == NULL) {
|
||||||
cerr << endl << get_fileline() << ": error: "
|
cerr << endl << get_fileline() << ": error: "
|
||||||
<< "Cannot apply the '" << name_
|
<< "Cannot apply the '" << name_
|
||||||
<< " attribute to non-array objects" << endl;
|
<< " attribute to non-array objects" << endl;
|
||||||
ivl_assert(*this, false);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ivl_assert(*this, arr->dimensions() == 1);
|
if (arr->dimensions() != 1) {
|
||||||
if(name_ == "left")
|
cerr << endl << get_fileline() << ": error: "
|
||||||
arr->dimension(0).msb()->evaluate(ent, scope, val);
|
<< "Cannot apply the '" << name_
|
||||||
else // "right"
|
<< " attribute to multidimensional arrays" << endl;
|
||||||
arr->dimension(0).lsb()->evaluate(ent, scope, val);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
if (arr->dimension(0).is_box())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (name_ == "left") {
|
||||||
|
return arr->dimension(0).msb()->evaluate(ent, scope, val);
|
||||||
|
} else if (name_ == "right") {
|
||||||
|
return arr->dimension(0).lsb()->evaluate(ent, scope, val);
|
||||||
|
} else {
|
||||||
|
ivl_assert(*this, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return evaluate(scope, val);
|
else if (name_ == "length") {
|
||||||
}
|
int64_t size = base_type->get_width(scope);
|
||||||
|
|
||||||
|
if (size > 0) {
|
||||||
|
val = size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* I don't yet know how to evaluate concatenations. It is not likely
|
|
||||||
* to come up anyhow.
|
|
||||||
*/
|
|
||||||
bool ExpConcat::evaluate(ScopeBase*, int64_t&) const
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpName::evaluate(ScopeBase*scope, int64_t&val) const
|
|
||||||
{
|
|
||||||
const VType*type;
|
|
||||||
Expression*exp;
|
|
||||||
|
|
||||||
if (prefix_.get()) {
|
|
||||||
cerr << get_fileline() << ": sorry: I don't know how to evaluate ExpName prefix parts." << endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scope)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!scope->find_constant(name_, type, exp))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return exp->evaluate(scope, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpName::evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const
|
bool ExpName::evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const
|
||||||
{
|
{
|
||||||
if (prefix_.get()) {
|
if (prefix_.get()) {
|
||||||
cerr << get_fileline() << ": sorry: I don't know how to evaluate ExpName prefix parts." << endl;
|
cerr << get_fileline() << ": sorry: I don't know how to evaluate "
|
||||||
return false;
|
<< "ExpName prefix parts." << endl;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InterfacePort*gen = ent->find_generic(name_);
|
if (ent) {
|
||||||
if (gen) {
|
const InterfacePort*gen = ent->find_generic(name_);
|
||||||
cerr << get_fileline() << ": sorry: I don't necessarily handle generic overrides." << endl;
|
if (gen) {
|
||||||
|
cerr << get_fileline() << ": sorry: I don't necessarily "
|
||||||
|
<< "handle generic overrides." << endl;
|
||||||
|
|
||||||
// Evaluate the default expression and use that.
|
// Evaluate the default expression and use that.
|
||||||
if (gen->expr)
|
if (gen->expr)
|
||||||
return gen->expr->evaluate(ent, scope, val);
|
return gen->expr->evaluate(ent, scope, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return evaluate(scope, val);
|
if (scope) {
|
||||||
|
const VType*type;
|
||||||
|
Expression*exp;
|
||||||
|
|
||||||
|
if (scope->find_constant(name_, type, exp))
|
||||||
|
return exp->evaluate(ent, scope, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpShift::evaluate(ScopeBase*scope, int64_t&val) const
|
bool ExpShift::evaluate(Entity*ent, ScopeBase*scope, int64_t&val) const
|
||||||
{
|
{
|
||||||
int64_t val1, val2;
|
int64_t val1, val2;
|
||||||
bool rc;
|
bool rc;
|
||||||
|
|
||||||
rc = eval_operand1(scope, val1);
|
rc = eval_operand1(ent, scope, val1);
|
||||||
if (rc == false)
|
if (rc == false)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
rc = eval_operand2(scope, val2);
|
rc = eval_operand2(ent, scope, val2);
|
||||||
if (rc == false)
|
if (rc == false)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -252,7 +188,7 @@ bool ExpShift::evaluate(ScopeBase*scope, int64_t&val) const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*bool ExpTime::evaluate(ScopeBase*, int64_t&val) const
|
/*bool ExpTime::evaluate(Entity*, ScopeBase*, int64_t&val) const
|
||||||
{
|
{
|
||||||
double v = to_fs();
|
double v = to_fs();
|
||||||
|
|
||||||
|
|
@ -265,8 +201,4 @@ bool ExpShift::evaluate(ScopeBase*scope, int64_t&val) const
|
||||||
val = v;
|
val = v;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpTime::evaluate(Entity*, ScopeBase*, int64_t&val) const
|
|
||||||
{
|
|
||||||
return evaluate(NULL, val);
|
|
||||||
}*/
|
}*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue