aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimplyTheOther <simplytheother@gmail.com>2020-10-29 22:11:41 +0800
committerSimplyTheOther <simplytheother@gmail.com>2020-12-08 21:10:18 +0800
commit69048af1878e95e26b57febb701f884f513c7b93 (patch)
tree553c3d302a1e9ebf668b7d1629e33b09a525fbbe
parent98d429466bf783ff1a7ac59bf800061d3e67061a (diff)
downloadgcc-69048af1878e95e26b57febb701f884f513c7b93.zip
gcc-69048af1878e95e26b57febb701f884f513c7b93.tar.gz
gcc-69048af1878e95e26b57febb701f884f513c7b93.tar.bz2
Added cfg stripping for ExternalItems
Fixed non-renaming of has_variadic_outer_attrs() Fixed old as_string function for ExternalItem Fixed parse_named_function_param arguments
-rw-r--r--gcc/rust/ast/rust-ast-full-test.cc80
-rw-r--r--gcc/rust/ast/rust-ast.h33
-rw-r--r--gcc/rust/ast/rust-item.h170
-rw-r--r--gcc/rust/expand/rust-macro-expand.cc32
-rw-r--r--gcc/rust/parse/rust-parse-impl.h83
-rw-r--r--gcc/rust/parse/rust-parse.h3
6 files changed, 303 insertions, 98 deletions
diff --git a/gcc/rust/ast/rust-ast-full-test.cc b/gcc/rust/ast/rust-ast-full-test.cc
index cbeb7d4..1e95e47 100644
--- a/gcc/rust/ast/rust-ast-full-test.cc
+++ b/gcc/rust/ast/rust-ast-full-test.cc
@@ -3979,6 +3979,7 @@ EnumItemDiscriminant::as_string () const
return str;
}
+#if 0
std::string
ExternalItem::as_string () const
{
@@ -3993,9 +3994,7 @@ ExternalItem::as_string () const
/* note that this does not print them with "outer attribute" syntax -
* just the body */
for (const auto &attr : outer_attrs)
- {
str += "\n " + attr.as_string ();
- }
}
// start visibility on new line and with a space
@@ -4003,21 +4002,35 @@ ExternalItem::as_string () const
return str;
}
+#endif
std::string
ExternalStaticItem::as_string () const
{
- std::string str = ExternalItem::as_string ();
+ // outer attributes
+ std::string str = "outer attributes: ";
+ if (outer_attrs.empty ())
+ {
+ str += "none";
+ }
+ else
+ {
+ /* note that this does not print them with "outer attribute" syntax -
+ * just the body */
+ for (const auto &attr : outer_attrs)
+ str += "\n " + attr.as_string ();
+ }
+
+ // start visibility on new line and with a space
+ str += "\n" + visibility.as_string () + " ";
str += "static ";
if (has_mut)
- {
str += "mut ";
- }
// add name
- str += get_item_name ();
+ str += item_name;
// add type on new line
str += "\n Type: " + item_type->as_string ();
@@ -4028,12 +4041,27 @@ ExternalStaticItem::as_string () const
std::string
ExternalFunctionItem::as_string () const
{
- std::string str = ExternalItem::as_string ();
+ // outer attributes
+ std::string str = "outer attributes: ";
+ if (outer_attrs.empty ())
+ {
+ str += "none";
+ }
+ else
+ {
+ /* note that this does not print them with "outer attribute" syntax -
+ * just the body */
+ for (const auto &attr : outer_attrs)
+ str += "\n " + attr.as_string ();
+ }
+
+ // start visibility on new line and with a space
+ str += "\n" + visibility.as_string () + " ";
str += "fn ";
// add name
- str += get_item_name ();
+ str += item_name;
// generic params
str += "\n Generic params: ";
@@ -4061,19 +4089,29 @@ ExternalFunctionItem::as_string () const
// function params
str += "\n Function params: ";
- if (function_params.empty ())
+ if (function_params.empty () && !has_variadics)
{
str += "none";
}
else
{
for (const auto &param : function_params)
- {
str += "\n " + param.as_string ();
- }
+
if (has_variadics)
{
- str += "\n .. (variadic)";
+ str += "\n variadic outer attrs: ";
+ if (has_variadic_outer_attrs ())
+ {
+
+ for (const auto &attr : variadic_outer_attrs)
+ str += "\n " + attr.as_string ();
+ }
+ else
+ {
+ str += "none";
+ }
+ str += "\n ... (variadic)";
}
}
@@ -4083,13 +4121,9 @@ ExternalFunctionItem::as_string () const
// where clause
str += "\n Where clause: ";
if (has_where_clause ())
- {
str += where_clause.as_string ();
- }
else
- {
str += "none";
- }
return str;
}
@@ -4097,7 +4131,19 @@ ExternalFunctionItem::as_string () const
std::string
NamedFunctionParam::as_string () const
{
- std::string str = name;
+ std::string str = "outer attributes: ";
+
+ if (!has_outer_attrs ())
+ {
+ str += "none";
+ }
+ else
+ {
+ for (const auto& attr : outer_attrs)
+ str += "\n " + attr.as_string ();
+ }
+
+ str += "\n" + name;
str += "\n Type: " + param_type->as_string ();
diff --git a/gcc/rust/ast/rust-ast.h b/gcc/rust/ast/rust-ast.h
index 31c547a..4c4b043 100644
--- a/gcc/rust/ast/rust-ast.h
+++ b/gcc/rust/ast/rust-ast.h
@@ -1289,12 +1289,36 @@ public:
virtual void accept_vis (ASTVisitor &vis) = 0;
};
+// Abstract base class for an item used inside an extern block
+class ExternalItem
+{
+public:
+ virtual ~ExternalItem () {}
+
+ // Unique pointer custom clone function
+ std::unique_ptr<ExternalItem> clone_external_item () const
+ {
+ return std::unique_ptr<ExternalItem> (clone_external_item_impl ());
+ }
+
+ virtual std::string as_string () const = 0;
+
+ virtual void accept_vis (ASTVisitor &vis) = 0;
+
+ virtual void mark_for_strip () = 0;
+ virtual bool is_marked_for_strip () const = 0;
+
+protected:
+ // Clone function implementation as pure virtual method
+ virtual ExternalItem *clone_external_item_impl () const = 0;
+};
+
/* A macro invocation item (or statement) AST node (i.e. semi-coloned macro
* invocation) */
class MacroInvocationSemi : public MacroItem,
public TraitItem,
public InherentImplItem,
- public TraitImplItem
+ public TraitImplItem, public ExternalItem
{
std::vector<Attribute> outer_attrs;
SimplePath path;
@@ -1394,6 +1418,13 @@ protected:
{
return clone_macro_invocation_semi_impl ();
}
+
+ /* Use covariance to implement clone function as returning this object rather
+ * than base */
+ MacroInvocationSemi *clone_external_item_impl () const override
+ {
+ return clone_macro_invocation_semi_impl ();
+ }
};
// A crate AST object - holds all the data for a single compilation unit
diff --git a/gcc/rust/ast/rust-item.h b/gcc/rust/ast/rust-item.h
index c35e65f..8542638 100644
--- a/gcc/rust/ast/rust-item.h
+++ b/gcc/rust/ast/rust-item.h
@@ -2964,6 +2964,7 @@ protected:
TraitImpl *clone_item_impl () const override { return new TraitImpl (*this); }
};
+#if 0
// Abstract base class for an item used inside an extern block
class ExternalItem
{
@@ -3040,35 +3041,57 @@ protected:
// possibly make this public if required
std::string get_item_name () const { return item_name; }
};
+#endif
// A static item used in an extern block
class ExternalStaticItem : public ExternalItem
{
+ // bool has_outer_attrs;
+ std::vector<Attribute> outer_attrs;
+
+ // bool has_visibility;
+ Visibility visibility;
+
+ Identifier item_name;
+ Location locus;
+
bool has_mut;
std::unique_ptr<Type> item_type;
public:
ExternalStaticItem (Identifier item_name, std::unique_ptr<Type> item_type,
- bool is_mut, Visibility vis,
- std::vector<Attribute> outer_attrs, Location locus)
- : ExternalItem (std::move (item_name), std::move (vis),
- std::move (outer_attrs), locus),
- has_mut (is_mut), item_type (std::move (item_type))
+ bool is_mut, Visibility vis, std::vector<Attribute> outer_attrs,
+ Location locus)
+ : outer_attrs (std::move (outer_attrs)), visibility (std::move (vis)),
+ item_name (std::move (item_name)), locus (locus), has_mut (is_mut),
+ item_type (std::move (item_type))
{}
// Copy constructor
ExternalStaticItem (ExternalStaticItem const &other)
- : ExternalItem (other), has_mut (other.has_mut),
- item_type (other.item_type->clone_type ())
- {}
+ : outer_attrs (other.outer_attrs), visibility (other.visibility), item_name (other.item_name),
+ locus (other.locus), has_mut (other.has_mut)
+ {
+ // guard to prevent null dereference (only required if error state)
+ if (other.item_type != nullptr)
+ item_type = other.item_type->clone_type ();
+ }
// Overloaded assignment operator to clone
ExternalStaticItem &operator= (ExternalStaticItem const &other)
{
- ExternalItem::operator= (other);
- item_type = other.item_type->clone_type ();
+ outer_attrs = other.outer_attrs;
+ visibility = other.visibility;
+ item_name = other.item_name;
+ locus = other.locus;
has_mut = other.has_mut;
+ // guard to prevent null dereference (only required if error state)
+ if (other.item_type != nullptr)
+ item_type = other.item_type->clone_type ();
+ else
+ item_type = nullptr;
+
return *this;
}
@@ -3080,6 +3103,22 @@ public:
void accept_vis (ASTVisitor &vis) override;
+ // Returns whether item has outer attributes.
+ bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+ // Returns whether item has non-default visibility.
+ bool has_visibility () const { return !visibility.is_error (); }
+
+ Location get_locus () const { return locus; }
+
+ // Based on idea that type should never be null.
+ void mark_for_strip () override { item_type = nullptr; };
+ bool is_marked_for_strip () const override { return item_type == nullptr; };
+
+ // TODO: this mutable getter seems really dodgy. Think up better way.
+ std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+ const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
protected:
/* Use covariance to implement clone function as returning this object
* rather than base */
@@ -3094,17 +3133,22 @@ struct NamedFunctionParam
{
private:
// bool has_name; // otherwise is _
- Identifier name; // TODO: handle wildcard in identifier?
+ std::string name;
std::unique_ptr<Type> param_type;
// TODO: should this store location data?
+ // seemingly new since writing this node
+ std::vector<Attribute> outer_attrs;
+
public:
- // Returns whether the named function parameter has a name (i.e. name is not
- // '_').
+ /* Returns whether the named function parameter has a name (i.e. name is not
+ * '_'). */
bool has_name () const { return name != "_"; }
+ bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
// Returns whether the named function parameter is in an error state.
bool is_error () const
{
@@ -3115,17 +3159,21 @@ public:
// Creates an error state named function parameter.
static NamedFunctionParam create_error ()
{
- return NamedFunctionParam ("", nullptr);
+ return NamedFunctionParam ("", nullptr, {});
}
- NamedFunctionParam (Identifier name, std::unique_ptr<Type> param_type)
- : name (std::move (name)), param_type (std::move (param_type))
+ NamedFunctionParam (std::string name, std::unique_ptr<Type> param_type, std::vector<Attribute> outer_attrs)
+ : name (std::move (name)), param_type (std::move (param_type)), outer_attrs (std::move (outer_attrs))
{}
// Copy constructor
NamedFunctionParam (NamedFunctionParam const &other)
- : name (other.name), param_type (other.param_type->clone_type ())
- {}
+ : name (other.name), outer_attrs (other.outer_attrs)
+ {
+ // guard to prevent null dereference (only required if error state)
+ if (other.param_type != nullptr)
+ param_type = other.param_type->clone_type ();
+ }
~NamedFunctionParam () = default;
@@ -3133,8 +3181,14 @@ public:
NamedFunctionParam &operator= (NamedFunctionParam const &other)
{
name = other.name;
- param_type = other.param_type->clone_type ();
// has_name = other.has_name;
+ outer_attrs = other.outer_attrs;
+
+ // guard to prevent null dereference (only required if error state)
+ if (other.param_type != nullptr)
+ param_type = other.param_type->clone_type ();
+ else
+ param_type = nullptr;
return *this;
}
@@ -3144,11 +3198,28 @@ public:
NamedFunctionParam &operator= (NamedFunctionParam &&other) = default;
std::string as_string () const;
+
+ // Based on idea that nane should never be empty.
+ void mark_for_strip () { param_type = nullptr; };
+ bool is_marked_for_strip () const { return is_error (); };
+
+ // TODO: this mutable getter seems really dodgy. Think up better way.
+ std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+ const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
};
// A function item used in an extern block
class ExternalFunctionItem : public ExternalItem
{
+ // bool has_outer_attrs;
+ std::vector<Attribute> outer_attrs;
+
+ // bool has_visibility;
+ Visibility visibility;
+
+ Identifier item_name;
+ Location locus;
+
// bool has_generics;
// Generics generic_params;
std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
@@ -3162,6 +3233,7 @@ class ExternalFunctionItem : public ExternalItem
std::vector<NamedFunctionParam> function_params;
bool has_variadics;
+ std::vector<Attribute> variadic_outer_attrs;
public:
// Returns whether item has generic parameters.
@@ -3173,28 +3245,48 @@ public:
// Returns whether item has a where clause.
bool has_where_clause () const { return !where_clause.is_empty (); }
+ // Returns whether item has outer attributes.
+ bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+ // Returns whether item has non-default visibility.
+ bool has_visibility () const { return !visibility.is_error (); }
+
+ // Returns whether item has variadic parameters.
+ bool is_variadic () const { return has_variadics; }
+
+ // Returns whether item has outer attributes on its variadic parameters.
+ bool has_variadic_outer_attrs () const { return !variadic_outer_attrs.empty (); }
+
+ Location get_locus () const { return locus; }
+
ExternalFunctionItem (
Identifier item_name,
std::vector<std::unique_ptr<GenericParam>> generic_params,
std::unique_ptr<Type> return_type, WhereClause where_clause,
- std::vector<NamedFunctionParam> function_params, bool has_variadics,
+ std::vector<NamedFunctionParam> function_params, bool has_variadics, std::vector<Attribute> variadic_outer_attrs,
Visibility vis, std::vector<Attribute> outer_attrs, Location locus)
- : ExternalItem (std::move (item_name), std::move (vis),
- std::move (outer_attrs), locus),
+ : outer_attrs (std::move (outer_attrs)), visibility (std::move (vis)),
+ item_name (std::move (item_name)), locus (locus),
generic_params (std::move (generic_params)),
return_type (std::move (return_type)),
where_clause (std::move (where_clause)),
function_params (std::move (function_params)),
- has_variadics (has_variadics)
- {}
+ has_variadics (has_variadics), variadic_outer_attrs (std::move (variadic_outer_attrs))
+ {
+ // TODO: assert that if has variadic outer attrs, then has_variadics is true?
+ }
// Copy constructor with clone
ExternalFunctionItem (ExternalFunctionItem const &other)
- : ExternalItem (other), return_type (other.return_type->clone_type ()),
- where_clause (other.where_clause),
+ : outer_attrs (other.outer_attrs), visibility (other.visibility), item_name (other.item_name),
+ locus (other.locus), where_clause (other.where_clause),
function_params (other.function_params),
- has_variadics (other.has_variadics)
+ has_variadics (other.has_variadics), variadic_outer_attrs (other.variadic_outer_attrs)
{
+ // guard to prevent null pointer dereference
+ if (other.return_type != nullptr)
+ return_type = other.return_type->clone_type ();
+
generic_params.reserve (other.generic_params.size ());
for (const auto &e : other.generic_params)
generic_params.push_back (e->clone_generic_param ());
@@ -3203,11 +3295,20 @@ public:
// Overloaded assignment operator with clone
ExternalFunctionItem &operator= (ExternalFunctionItem const &other)
{
- ExternalItem::operator= (other);
- return_type = other.return_type->clone_type ();
+ outer_attrs = other.outer_attrs;
+ visibility = other.visibility;
+ item_name = other.item_name;
+ locus = other.locus;
where_clause = other.where_clause;
function_params = other.function_params;
has_variadics = other.has_variadics;
+ variadic_outer_attrs = other.variadic_outer_attrs;
+
+ // guard to prevent null pointer dereference
+ if (other.return_type != nullptr)
+ return_type = other.return_type->clone_type ();
+ else
+ return_type = nullptr;
generic_params.reserve (other.generic_params.size ());
for (const auto &e : other.generic_params)
@@ -3224,6 +3325,17 @@ public:
void accept_vis (ASTVisitor &vis) override;
+ // Based on idea that nane should never be empty.
+ void mark_for_strip () override { item_name = ""; };
+ bool is_marked_for_strip () const override { return item_name.empty (); };
+
+ // TODO: this mutable getter seems really dodgy. Think up better way.
+ std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+ const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+ std::vector<NamedFunctionParam> &get_function_params () { return function_params; }
+ const std::vector<NamedFunctionParam> &get_function_params () const { return function_params; }
+
protected:
/* Use covariance to implement clone function as returning this object
* rather than base */
diff --git a/gcc/rust/expand/rust-macro-expand.cc b/gcc/rust/expand/rust-macro-expand.cc
index a7d2a1a..80ad5d0 100644
--- a/gcc/rust/expand/rust-macro-expand.cc
+++ b/gcc/rust/expand/rust-macro-expand.cc
@@ -125,8 +125,35 @@ namespace Rust {
void visit(AST::Trait& trait) override {}
void visit(AST::InherentImpl& impl) override {}
void visit(AST::TraitImpl& impl) override {}
- void visit(AST::ExternalStaticItem& item) override {}
- void visit(AST::ExternalFunctionItem& item) override {}
+ void visit(AST::ExternalStaticItem& item) override {
+ // strip test based on outer attrs
+ expander.expand_cfg_attrs(item.get_outer_attrs());
+ if (expander.fails_cfg(item.get_outer_attrs())) {
+ item.mark_for_strip();
+ return;
+ }
+ }
+ void visit(AST::ExternalFunctionItem& item) override {
+ // strip test based on outer attrs
+ expander.expand_cfg_attrs(item.get_outer_attrs());
+ if (expander.fails_cfg(item.get_outer_attrs())) {
+ item.mark_for_strip();
+ return;
+ }
+
+ /* strip function parameters if required - this is specifically
+ * allowed by spec */
+ auto& params = item.get_function_params();
+ for (auto i = 0; i < params.size(); ) {
+ if (expander.fails_cfg (params[i].get_outer_attrs ()))
+ params.erase (params.begin() + i);
+ else
+ i++;
+ }
+
+ /* TODO: assuming that variadic nature cannot be stripped. If this
+ * is not true, then have code here to do so. */
+ }
void visit(AST::ExternBlock& block) override {
// initial strip test based on outer attrs
expander.expand_cfg_attrs(block.get_outer_attrs());
@@ -154,7 +181,6 @@ namespace Rust {
extern_items.erase (extern_items.begin() + i);
else
i++;
-
}
}
diff --git a/gcc/rust/parse/rust-parse-impl.h b/gcc/rust/parse/rust-parse-impl.h
index 4d54242..9c293dc 100644
--- a/gcc/rust/parse/rust-parse-impl.h
+++ b/gcc/rust/parse/rust-parse-impl.h
@@ -5632,62 +5632,51 @@ Parser<ManagedTokenSource>::parse_external_item ()
// parse parameters
std::vector<AST::NamedFunctionParam> function_params;
bool is_variadic = false;
+ std::vector<AST::Attribute> variadic_attrs;
const_TokenPtr t = lexer.peek_token ();
- while (t->get_id () != RIGHT_PAREN)
- {
- AST::NamedFunctionParam param = parse_named_function_param ();
+ while (t->get_id () != RIGHT_PAREN) {
+ std::vector<AST::Attribute> maybe_variadic_attrs = parse_outer_attributes ();
+ if (lexer.peek_token ()->get_id () == ELLIPSIS) {
+ // variadic - use attrs for this
+ lexer.skip_token ();
+ is_variadic = true;
+ variadic_attrs = std::move (maybe_variadic_attrs);
+ t = lexer.peek_token ();
+
+ if (t->get_id() != RIGHT_PAREN) {
+ rust_error_at (t->get_locus (),
+ "expected right parentheses after variadic in named function "
+ "parameters, found %qs",
+ t->get_token_description ());
+ skip_after_semicolon ();
+ return nullptr;
+ }
- if (param.is_error ())
- {
- // is this an error? probably
- rust_error_at (t->get_locus (),
- "could not parse named function parameter in "
- "external function");
- skip_after_semicolon ();
- return nullptr;
- }
+ break;
+ }
+ AST::NamedFunctionParam param = parse_named_function_param (std::move (maybe_variadic_attrs));
+ if (param.is_error ()) {
+ rust_error_at (t->get_locus (),
+ "could not parse named function parameter in external function");
+ skip_after_semicolon ();
+ return nullptr;
+ }
function_params.push_back (std::move (param));
- t = lexer.peek_token ();
- if (t->get_id () != COMMA)
- {
- if (t->get_id () != RIGHT_PAREN)
- {
- rust_error_at (t->get_locus (),
- "expected comma or right parentheses in "
- "named function parameters, found %qs",
- t->get_token_description ());
- }
- else
- {
- // end of loop
+ if (lexer.peek_token ()->get_id () != COMMA)
break;
- }
- }
+
// skip comma
lexer.skip_token ();
-
t = lexer.peek_token ();
+ }
- // parse variadic ... if it exists
- if (t->get_id () == ELLIPSIS
- && lexer.peek_token (1)->get_id () == RIGHT_PAREN)
- {
- lexer.skip_token ();
-
- is_variadic = true;
-
- t = lexer.peek_token ();
- }
- }
-
- if (!skip_token (RIGHT_PAREN))
- {
+ if (!skip_token (RIGHT_PAREN)) {
skip_after_semicolon ();
return nullptr;
- }
+ }
// parse (optional) return type
std::unique_ptr<AST::Type> return_type = parse_function_return_type ();
@@ -5705,7 +5694,7 @@ Parser<ManagedTokenSource>::parse_external_item ()
new AST::ExternalFunctionItem (
std::move (ident), std::move (generic_params),
std::move (return_type), std::move (where_clause),
- std::move (function_params), is_variadic, std::move (vis),
+ std::move (function_params), is_variadic, std::move (variadic_attrs), std::move (vis),
std::move (outer_attrs), locus));
}
default:
@@ -5722,10 +5711,10 @@ Parser<ManagedTokenSource>::parse_external_item ()
* identifier). */
template <typename ManagedTokenSource>
AST::NamedFunctionParam
-Parser<ManagedTokenSource>::parse_named_function_param ()
+Parser<ManagedTokenSource>::parse_named_function_param (std::vector<AST::Attribute> outer_attrs)
{
// parse identifier/_
- Identifier name;
+ std::string name;
const_TokenPtr t = lexer.peek_token ();
switch (t->get_id ())
@@ -5760,7 +5749,7 @@ Parser<ManagedTokenSource>::parse_named_function_param ()
return AST::NamedFunctionParam::create_error ();
}
- return AST::NamedFunctionParam (std::move (name), std::move (param_type));
+ return AST::NamedFunctionParam (std::move (name), std::move (param_type), std::move (outer_attrs));
}
// Parses a statement (will further disambiguate any statement).
diff --git a/gcc/rust/parse/rust-parse.h b/gcc/rust/parse/rust-parse.h
index e2f3a78..82d8d6f 100644
--- a/gcc/rust/parse/rust-parse.h
+++ b/gcc/rust/parse/rust-parse.h
@@ -230,7 +230,8 @@ private:
parse_extern_block (AST::Visibility vis,
std::vector<AST::Attribute> outer_attrs);
std::unique_ptr<AST::ExternalItem> parse_external_item ();
- AST::NamedFunctionParam parse_named_function_param ();
+ AST::NamedFunctionParam parse_named_function_param (
+ std::vector<AST::Attribute> outer_attrs = std::vector<AST::Attribute> ());
AST::Method parse_method ();
// Expression-related (Pratt parsed)