mirror of
https://github.com/espressif/binutils-gdb.git
synced 2025-06-24 20:28:28 +08:00
Introduce class operation
This patch introduces class operation, the new base class for all expression operations. In the new approach, an operation is simply a class that presents a certain interface. Operations own their operands, and management is done via unique_ptr. The operation interface is largely ad hoc, based on the evolution of expression handling in GDB. Parts (for example, evaluate_with_coercion) are probably redundant; however I took this approach to try to avoid mixing different kinds of refactorings. In some specific situations, rather than add a generic method across the entire operation class hierarchy, I chose instead to use dynamic_cast and specialized methods on certain concrete subclasses. This will appear in some subsequent patches. One goal of this work is to avoid the kinds of easy-to-make errors that affected the old implementation. To this end, some helper subclasses are also added here. These helpers automate the implementation of the 'dump', 'uses_objfile', and 'constant_p' methods. Nearly every concrete operation that is subsequently added will use these facilities. (Note that the 'dump' implementation is only outlined here, the body appears in the next patch.) gdb/ChangeLog 2021-03-08 Tom Tromey <tom@tromey.com> * expression.h (expr::operation): New class. (expr::make_operation): New function. (expr::operation_up): New typedef. * expop.h: New file. * eval.c (operation::evaluate_for_cast) (operation::evaluate_for_address, operation::evaluate_for_sizeof): New methods. * ax-gdb.c (operation::generate_ax): New method.
This commit is contained in:
@ -1,3 +1,14 @@
|
|||||||
|
2021-03-08 Tom Tromey <tom@tromey.com>
|
||||||
|
|
||||||
|
* expression.h (expr::operation): New class.
|
||||||
|
(expr::make_operation): New function.
|
||||||
|
(expr::operation_up): New typedef.
|
||||||
|
* expop.h: New file.
|
||||||
|
* eval.c (operation::evaluate_for_cast)
|
||||||
|
(operation::evaluate_for_address, operation::evaluate_for_sizeof):
|
||||||
|
New methods.
|
||||||
|
* ax-gdb.c (operation::generate_ax): New method.
|
||||||
|
|
||||||
2021-03-08 Tom Tromey <tom@tromey.com>
|
2021-03-08 Tom Tromey <tom@tromey.com>
|
||||||
|
|
||||||
* ax-gdb.c (gen_expr_binop_rest): Remove "pc" parameter.
|
* ax-gdb.c (gen_expr_binop_rest): Remove "pc" parameter.
|
||||||
|
26
gdb/ax-gdb.c
26
gdb/ax-gdb.c
@ -2270,6 +2270,32 @@ gen_expr (struct expression *exp, union exp_element **pc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace expr
|
||||||
|
{
|
||||||
|
|
||||||
|
void
|
||||||
|
operation::generate_ax (struct expression *exp,
|
||||||
|
struct agent_expr *ax,
|
||||||
|
struct axs_value *value,
|
||||||
|
struct type *cast_type)
|
||||||
|
{
|
||||||
|
if (constant_p ())
|
||||||
|
{
|
||||||
|
struct value *v = evaluate (nullptr, exp, EVAL_AVOID_SIDE_EFFECTS);
|
||||||
|
ax_const_l (ax, value_as_long (v));
|
||||||
|
value->kind = axs_rvalue;
|
||||||
|
value->type = check_typedef (value_type (v));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
do_generate_ax (exp, ax, value, cast_type);
|
||||||
|
if (cast_type != nullptr)
|
||||||
|
gen_cast (ax, value, cast_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* This handles the middle-to-right-side of code generation for binary
|
/* This handles the middle-to-right-side of code generation for binary
|
||||||
expressions, which is shared between regular binary operations and
|
expressions, which is shared between regular binary operations and
|
||||||
assign-modify (+= and friends) expressions. */
|
assign-modify (+= and friends) expressions. */
|
||||||
|
36
gdb/eval.c
36
gdb/eval.c
@ -40,6 +40,7 @@
|
|||||||
#include "objfiles.h"
|
#include "objfiles.h"
|
||||||
#include "typeprint.h"
|
#include "typeprint.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include "expop.h"
|
||||||
|
|
||||||
/* Prototypes for local functions. */
|
/* Prototypes for local functions. */
|
||||||
|
|
||||||
@ -3267,6 +3268,29 @@ evaluate_subexp_for_address (struct expression *exp, int *pos,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace expr
|
||||||
|
{
|
||||||
|
|
||||||
|
value *
|
||||||
|
operation::evaluate_for_cast (struct type *expect_type,
|
||||||
|
struct expression *exp,
|
||||||
|
enum noside noside)
|
||||||
|
{
|
||||||
|
value *val = evaluate (expect_type, exp, noside);
|
||||||
|
if (noside == EVAL_SKIP)
|
||||||
|
return eval_skip_value (exp);
|
||||||
|
return value_cast (expect_type, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
value *
|
||||||
|
operation::evaluate_for_address (struct expression *exp, enum noside noside)
|
||||||
|
{
|
||||||
|
value *val = evaluate (nullptr, exp, noside);
|
||||||
|
return evaluate_subexp_for_address_base (exp, noside, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* Evaluate like `evaluate_subexp' except coercing arrays to pointers.
|
/* Evaluate like `evaluate_subexp' except coercing arrays to pointers.
|
||||||
When used in contexts where arrays will be coerced anyway, this is
|
When used in contexts where arrays will be coerced anyway, this is
|
||||||
equivalent to `evaluate_subexp' but much faster because it avoids
|
equivalent to `evaluate_subexp' but much faster because it avoids
|
||||||
@ -3455,6 +3479,18 @@ evaluate_subexp_for_sizeof (struct expression *exp, int *pos,
|
|||||||
return evaluate_subexp_for_sizeof_base (exp, type);
|
return evaluate_subexp_for_sizeof_base (exp, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace expr
|
||||||
|
{
|
||||||
|
|
||||||
|
value *
|
||||||
|
operation::evaluate_for_sizeof (struct expression *exp, enum noside noside)
|
||||||
|
{
|
||||||
|
value *val = evaluate (nullptr, exp, EVAL_AVOID_SIDE_EFFECTS);
|
||||||
|
return evaluate_subexp_for_sizeof_base (exp, value_type (val));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* Evaluate a subexpression of EXP, at index *POS, and return a value
|
/* Evaluate a subexpression of EXP, at index *POS, and return a value
|
||||||
for that subexpression cast to TO_TYPE. Advance *POS over the
|
for that subexpression cast to TO_TYPE. Advance *POS over the
|
||||||
subexpression. */
|
subexpression. */
|
||||||
|
315
gdb/expop.h
Normal file
315
gdb/expop.h
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
/* Definitions for expressions in GDB
|
||||||
|
|
||||||
|
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||||
|
|
||||||
|
This file is part of GDB.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#ifndef EXPOP_H
|
||||||
|
#define EXPOP_H
|
||||||
|
|
||||||
|
#include "block.h"
|
||||||
|
#include "c-lang.h"
|
||||||
|
#include "cp-abi.h"
|
||||||
|
#include "expression.h"
|
||||||
|
#include "objfiles.h"
|
||||||
|
#include "gdbsupport/traits.h"
|
||||||
|
#include "gdbsupport/enum-flags.h"
|
||||||
|
|
||||||
|
struct agent_expr;
|
||||||
|
struct axs_value;
|
||||||
|
|
||||||
|
namespace expr
|
||||||
|
{
|
||||||
|
|
||||||
|
/* The check_objfile overloads are used to check whether a particular
|
||||||
|
component of some operation references an objfile. The passed-in
|
||||||
|
objfile will never be a debug objfile. */
|
||||||
|
|
||||||
|
/* See if EXP_OBJFILE matches OBJFILE. */
|
||||||
|
static inline bool
|
||||||
|
check_objfile (struct objfile *exp_objfile, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
if (exp_objfile->separate_debug_objfile_backlink)
|
||||||
|
exp_objfile = exp_objfile->separate_debug_objfile_backlink;
|
||||||
|
return exp_objfile == objfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (struct type *type, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
struct objfile *ty_objfile = type->objfile_owner ();
|
||||||
|
if (ty_objfile != nullptr)
|
||||||
|
return check_objfile (ty_objfile, objfile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (struct symbol *sym, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return check_objfile (symbol_objfile (sym), objfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (const struct block *block, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return check_objfile (block_objfile (block), objfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (minimal_symbol *minsym, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
/* This may seem strange but minsyms are only used with an objfile
|
||||||
|
as well. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (internalvar *ivar, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (const std::string &str, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (const operation_up &op, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return op->uses_objfile (objfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (enum exp_opcode val, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_objfile (ULONGEST val, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static inline bool
|
||||||
|
check_objfile (enum_flags<T> val, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static inline bool
|
||||||
|
check_objfile (const std::vector<T> &collection, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
for (const auto &item : collection)
|
||||||
|
{
|
||||||
|
if (check_objfile (item, objfile))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename S, typename T>
|
||||||
|
static inline bool
|
||||||
|
check_objfile (const std::pair<S, T> &item, struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return (check_objfile (item.first, objfile)
|
||||||
|
|| check_objfile (item.second, objfile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base class for most concrete operations. This class holds data,
|
||||||
|
specified via template parameters, and supplies generic
|
||||||
|
implementations of the 'dump' and 'uses_objfile' methods. */
|
||||||
|
template<typename... Arg>
|
||||||
|
class tuple_holding_operation : public operation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit tuple_holding_operation (Arg... args)
|
||||||
|
: m_storage (std::forward<Arg> (args)...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DISABLE_COPY_AND_ASSIGN (tuple_holding_operation);
|
||||||
|
|
||||||
|
bool uses_objfile (struct objfile *objfile) const override
|
||||||
|
{
|
||||||
|
return do_check_objfile<0, Arg...> (objfile, m_storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump (struct ui_file *stream, int depth) const override
|
||||||
|
{
|
||||||
|
do_dump<0, Arg...> (stream, depth, m_storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/* Storage for the data. */
|
||||||
|
std::tuple<Arg...> m_storage;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/* do_dump does the work of dumping the data. */
|
||||||
|
template<int I, typename... T>
|
||||||
|
typename std::enable_if<I == sizeof... (T), void>::type
|
||||||
|
do_dump (struct ui_file *stream, int depth, const std::tuple<T...> &value)
|
||||||
|
const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int I, typename... T>
|
||||||
|
typename std::enable_if<I < sizeof... (T), void>::type
|
||||||
|
do_dump (struct ui_file *stream, int depth, const std::tuple<T...> &value)
|
||||||
|
const
|
||||||
|
{
|
||||||
|
do_dump<I + 1, T...> (stream, depth, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do_check_objfile does the work of checking whether this object
|
||||||
|
refers to OBJFILE. */
|
||||||
|
template<int I, typename... T>
|
||||||
|
typename std::enable_if<I == sizeof... (T), bool>::type
|
||||||
|
do_check_objfile (struct objfile *objfile, const std::tuple<T...> &value)
|
||||||
|
const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int I, typename... T>
|
||||||
|
typename std::enable_if<I < sizeof... (T), bool>::type
|
||||||
|
do_check_objfile (struct objfile *objfile, const std::tuple<T...> &value)
|
||||||
|
const
|
||||||
|
{
|
||||||
|
if (check_objfile (std::get<I> (value), objfile))
|
||||||
|
return true;
|
||||||
|
return do_check_objfile<I + 1, T...> (objfile, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* The check_constant overloads are used to decide whether a given
|
||||||
|
concrete operation is a constant. This is done by checking the
|
||||||
|
operands. */
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (const operation_up &item)
|
||||||
|
{
|
||||||
|
return item->constant_p ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (struct minimal_symbol *msym)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (struct type *type)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (const struct block *block)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (const std::string &str)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (struct objfile *objfile)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (ULONGEST cst)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
check_constant (struct symbol *sym)
|
||||||
|
{
|
||||||
|
enum address_class sc = SYMBOL_CLASS (sym);
|
||||||
|
return (sc == LOC_BLOCK
|
||||||
|
|| sc == LOC_CONST
|
||||||
|
|| sc == LOC_CONST_BYTES
|
||||||
|
|| sc == LOC_LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static inline bool
|
||||||
|
check_constant (const std::vector<T> &collection)
|
||||||
|
{
|
||||||
|
for (const auto &item : collection)
|
||||||
|
if (!check_constant (item))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename S, typename T>
|
||||||
|
static inline bool
|
||||||
|
check_constant (const std::pair<S, T> &item)
|
||||||
|
{
|
||||||
|
return check_constant (item.first) && check_constant (item.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base class for concrete operations. This class supplies an
|
||||||
|
implementation of 'constant_p' that works by checking the
|
||||||
|
operands. */
|
||||||
|
template<typename... Arg>
|
||||||
|
class maybe_constant_operation
|
||||||
|
: public tuple_holding_operation<Arg...>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using tuple_holding_operation<Arg...>::tuple_holding_operation;
|
||||||
|
|
||||||
|
bool constant_p () const override
|
||||||
|
{
|
||||||
|
return do_check_constant<0, Arg...> (this->m_storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template<int I, typename... T>
|
||||||
|
typename std::enable_if<I == sizeof... (T), bool>::type
|
||||||
|
do_check_constant (const std::tuple<T...> &value) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int I, typename... T>
|
||||||
|
typename std::enable_if<I < sizeof... (T), bool>::type
|
||||||
|
do_check_constant (const std::tuple<T...> &value) const
|
||||||
|
{
|
||||||
|
if (!check_constant (std::get<I> (value)))
|
||||||
|
return false;
|
||||||
|
return do_check_constant<I + 1, T...> (value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace expr */
|
||||||
|
|
||||||
|
#endif /* EXPOP_H */
|
@ -89,6 +89,105 @@ enum noside
|
|||||||
does in many situations. */
|
does in many situations. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct expression;
|
||||||
|
struct agent_expr;
|
||||||
|
struct axs_value;
|
||||||
|
struct type;
|
||||||
|
struct ui_file;
|
||||||
|
|
||||||
|
namespace expr
|
||||||
|
{
|
||||||
|
|
||||||
|
class operation;
|
||||||
|
typedef std::unique_ptr<operation> operation_up;
|
||||||
|
|
||||||
|
/* Base class for an operation. An operation is a single component of
|
||||||
|
an expression. */
|
||||||
|
|
||||||
|
class operation
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
|
||||||
|
operation () = default;
|
||||||
|
DISABLE_COPY_AND_ASSIGN (operation);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~operation () = default;
|
||||||
|
|
||||||
|
/* Evaluate this operation. */
|
||||||
|
virtual value *evaluate (struct type *expect_type,
|
||||||
|
struct expression *exp,
|
||||||
|
enum noside noside) = 0;
|
||||||
|
|
||||||
|
/* Evaluate this operation in a context where C-like coercion is
|
||||||
|
needed. */
|
||||||
|
virtual value *evaluate_with_coercion (struct expression *exp,
|
||||||
|
enum noside noside)
|
||||||
|
{
|
||||||
|
return evaluate (nullptr, exp, noside);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Evaluate this expression in the context of a cast to
|
||||||
|
EXPECT_TYPE. */
|
||||||
|
virtual value *evaluate_for_cast (struct type *expect_type,
|
||||||
|
struct expression *exp,
|
||||||
|
enum noside noside);
|
||||||
|
|
||||||
|
/* Evaluate this expression in the context of a sizeof
|
||||||
|
operation. */
|
||||||
|
virtual value *evaluate_for_sizeof (struct expression *exp,
|
||||||
|
enum noside noside);
|
||||||
|
|
||||||
|
/* Evaluate this expression in the context of an address-of
|
||||||
|
operation. Must return the address. */
|
||||||
|
virtual value *evaluate_for_address (struct expression *exp,
|
||||||
|
enum noside noside);
|
||||||
|
|
||||||
|
/* True if this is a constant expression. */
|
||||||
|
virtual bool constant_p () const
|
||||||
|
{ return false; }
|
||||||
|
|
||||||
|
/* Return true if this operation uses OBJFILE (and will become
|
||||||
|
dangling when OBJFILE is unloaded), otherwise return false.
|
||||||
|
OBJFILE must not be a separate debug info file. */
|
||||||
|
virtual bool uses_objfile (struct objfile *objfile) const
|
||||||
|
{ return false; }
|
||||||
|
|
||||||
|
/* Generate agent expression bytecodes for this operation. */
|
||||||
|
void generate_ax (struct expression *exp, struct agent_expr *ax,
|
||||||
|
struct axs_value *value,
|
||||||
|
struct type *cast_type = nullptr);
|
||||||
|
|
||||||
|
/* Return the opcode that is implemented by this operation. */
|
||||||
|
virtual enum exp_opcode opcode () const = 0;
|
||||||
|
|
||||||
|
/* Print this operation to STREAM. */
|
||||||
|
virtual void dump (struct ui_file *stream, int depth) const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/* Called by generate_ax to do the work for this particular
|
||||||
|
operation. */
|
||||||
|
virtual void do_generate_ax (struct expression *exp,
|
||||||
|
struct agent_expr *ax,
|
||||||
|
struct axs_value *value,
|
||||||
|
struct type *cast_type)
|
||||||
|
{
|
||||||
|
error (_("Cannot translate to agent expression"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* A helper function for creating an operation_up, given a type. */
|
||||||
|
template<typename T, typename... Arg>
|
||||||
|
operation_up
|
||||||
|
make_operation (Arg... args)
|
||||||
|
{
|
||||||
|
return operation_up (new T (std::forward<Arg> (args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
union exp_element
|
union exp_element
|
||||||
{
|
{
|
||||||
enum exp_opcode opcode;
|
enum exp_opcode opcode;
|
||||||
|
Reference in New Issue
Block a user