MLIR 24.0.0git
TypeParser.cpp
Go to the documentation of this file.
1//===- TypeParser.cpp - MLIR Type Parser Implementation -------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the parser for the MLIR Types.
10//
11//===----------------------------------------------------------------------===//
12
13#include "Parser.h"
14#include "mlir/IR/AffineMap.h"
20#include "mlir/IR/Types.h"
21#include "mlir/Support/LLVM.h"
22#include <cassert>
23#include <cstdint>
24#include <limits>
25#include <optional>
26
27using namespace mlir;
28using namespace mlir::detail;
29
30/// Optionally parse a type.
32 // There are many different starting tokens for a type, check them here.
33 switch (getToken().getKind()) {
34 case Token::l_paren:
35 case Token::kw_memref:
36 case Token::kw_tensor:
37 case Token::kw_complex:
38 case Token::kw_tuple:
39 case Token::kw_vector:
40 case Token::inttype:
41 case Token::kw_f4E2M1FN:
42 case Token::kw_f6E2M3FN:
43 case Token::kw_f6E3M2FN:
44 case Token::kw_f8E5M2:
45 case Token::kw_f8E4M3:
46 case Token::kw_f8E4M3FN:
47 case Token::kw_f8E5M2FNUZ:
48 case Token::kw_f8E4M3FNUZ:
49 case Token::kw_f8E4M3B11FNUZ:
50 case Token::kw_f8E3M4:
51 case Token::kw_f8E8M0FNU:
52 case Token::kw_f8E5M3FNU:
53 case Token::kw_bf16:
54 case Token::kw_f16:
55 case Token::kw_tf32:
56 case Token::kw_f32:
57 case Token::kw_f64:
58 case Token::kw_f80:
59 case Token::kw_f128:
60 case Token::kw_index:
61 case Token::kw_none:
62 case Token::kw_token:
63 case Token::exclamation_identifier:
64 return failure(!(type = parseType()));
65
66 default:
67 return std::nullopt;
68 }
69}
70
71/// Parse an arbitrary type.
72///
73/// type ::= function-type
74/// | non-function-type
75///
77 if (getToken().is(Token::l_paren))
78 return parseFunctionType();
79 return parseNonFunctionType();
80}
81
82/// Parse a function result type.
83///
84/// function-result-type ::= type-list-parens
85/// | non-function-type
86///
88 if (getToken().is(Token::l_paren))
89 return parseTypeListParens(elements);
90
92 if (!t)
93 return failure();
94 elements.push_back(t);
95 return success();
96}
97
98/// Parse a list of types without an enclosing parenthesis. The list must have
99/// at least one member.
100///
101/// type-list-no-parens ::= type (`,` type)*
102///
104 auto parseElt = [&]() -> ParseResult {
105 auto elt = parseType();
106 elements.push_back(elt);
107 return elt ? success() : failure();
108 };
109
110 return parseCommaSeparatedList(parseElt);
111}
112
113/// Parse a parenthesized list of types.
114///
115/// type-list-parens ::= `(` `)`
116/// | `(` type-list-no-parens `)`
117///
119 if (parseToken(Token::l_paren, "expected '('"))
120 return failure();
121
122 // Handle empty lists.
123 if (getToken().is(Token::r_paren))
124 return consumeToken(), success();
125
126 if (parseTypeListNoParens(elements) ||
127 parseToken(Token::r_paren, "expected ')'"))
128 return failure();
129 return success();
130}
131
132/// Parse a complex type.
133///
134/// complex-type ::= `complex` `<` type `>`
135///
137 consumeToken(Token::kw_complex);
138
139 // Parse the '<'.
140 if (parseToken(Token::less, "expected '<' in complex type"))
141 return nullptr;
142
143 SMLoc elementTypeLoc = getToken().getLoc();
144 auto elementType = parseType();
145 if (!elementType ||
146 parseToken(Token::greater, "expected '>' in complex type"))
147 return nullptr;
148 if (!isa<FloatType>(elementType) && !isa<IntegerType>(elementType))
149 return emitError(elementTypeLoc, "invalid element type for complex"),
150 nullptr;
151
152 return ComplexType::get(elementType);
153}
154
155/// Parse a function type.
156///
157/// function-type ::= type-list-parens `->` function-result-type
158///
160 assert(getToken().is(Token::l_paren));
161
162 SmallVector<Type, 4> arguments, results;
163 if (parseTypeListParens(arguments) ||
164 parseToken(Token::arrow, "expected '->' in function type") ||
166 return nullptr;
167
168 return builder.getFunctionType(arguments, results);
169}
170
171/// Parse a memref type.
172///
173/// memref-type ::= ranked-memref-type | unranked-memref-type
174///
175/// ranked-memref-type ::= `memref` `<` dimension-list-ranked type
176/// (`,` layout-specification)? (`,` memory-space)? `>`
177///
178/// unranked-memref-type ::= `memref` `<*x` type (`,` memory-space)? `>`
179///
180/// stride-list ::= `[` (dimension (`,` dimension)*)? `]`
181/// strided-layout ::= `offset:` dimension `,` `strides: ` stride-list
182/// layout-specification ::= semi-affine-map | strided-layout | attribute
183/// memory-space ::= integer-literal | attribute
184///
186 SMLoc loc = getToken().getLoc();
187 consumeToken(Token::kw_memref);
188
189 if (parseToken(Token::less, "expected '<' in memref type"))
190 return nullptr;
191
192 bool isUnranked;
193 SmallVector<int64_t, 4> dimensions;
194
195 if (consumeIf(Token::star)) {
196 // This is an unranked memref type.
197 isUnranked = true;
199 return nullptr;
200
201 } else {
202 isUnranked = false;
203 if (parseDimensionListRanked(dimensions))
204 return nullptr;
205 }
206
207 // Parse the element type.
208 auto typeLoc = getToken().getLoc();
209 auto elementType = parseType();
210 if (!elementType)
211 return nullptr;
212
213 // Check that memref is formed from allowed types.
214 if (!BaseMemRefType::isValidElementType(elementType))
215 return emitError(typeLoc, "invalid memref element type"), nullptr;
216
217 MemRefLayoutAttrInterface layout;
218 Attribute memorySpace;
219
220 auto parseElt = [&]() -> ParseResult {
221 // Either it is MemRefLayoutAttrInterface or memory space attribute.
222 Attribute attr = parseAttribute();
223 if (!attr)
224 return failure();
225
226 if (isa<MemRefLayoutAttrInterface>(attr)) {
227 layout = cast<MemRefLayoutAttrInterface>(attr);
228 } else if (memorySpace) {
229 return emitError("multiple memory spaces specified in memref type");
230 } else {
231 memorySpace = attr;
232 return success();
233 }
234
235 if (isUnranked)
236 return emitError("cannot have affine map for unranked memref type");
237 if (memorySpace)
238 return emitError("expected memory space to be last in memref type");
239
240 return success();
241 };
242
243 // Parse a list of mappings and address space if present.
244 if (!consumeIf(Token::greater)) {
245 // Parse comma separated list of affine maps, followed by memory space.
246 if (parseToken(Token::comma, "expected ',' or '>' in memref type") ||
247 parseCommaSeparatedListUntil(Token::greater, parseElt,
248 /*allowEmptyList=*/false)) {
249 return nullptr;
250 }
251 }
252
253 if (isUnranked)
254 return getChecked<UnrankedMemRefType>(loc, elementType, memorySpace);
255
256 return getChecked<MemRefType>(loc, dimensions, elementType, layout,
257 memorySpace);
258}
259
260/// Parse any type except the function type.
261///
262/// non-function-type ::= integer-type
263/// | index-type
264/// | float-type
265/// | extended-type
266/// | vector-type
267/// | tensor-type
268/// | memref-type
269/// | complex-type
270/// | tuple-type
271/// | none-type
272///
273/// index-type ::= `index`
274/// float-type ::= `f16` | `bf16` | `f32` | `f64` | `f80` | `f128`
275/// none-type ::= `none`
276///
278 switch (getToken().getKind()) {
279 default:
280 return (emitWrongTokenError("expected non-function type"), nullptr);
281 case Token::kw_memref:
282 return parseMemRefType();
283 case Token::kw_tensor:
284 return parseTensorType();
285 case Token::kw_complex:
286 return parseComplexType();
287 case Token::kw_tuple:
288 return parseTupleType();
289 case Token::kw_vector:
290 return parseVectorType();
291 // integer-type
292 case Token::inttype: {
293 auto width = getToken().getIntTypeBitwidth();
294 if (!width.has_value())
295 return (emitError("invalid integer width"), nullptr);
296 if (*width > IntegerType::kMaxWidth) {
297 emitError(getToken().getLoc(), "integer bitwidth is limited to ")
298 << IntegerType::kMaxWidth << " bits";
299 return nullptr;
300 }
301
302 IntegerType::SignednessSemantics signSemantics = IntegerType::Signless;
303 if (std::optional<bool> signedness = getToken().getIntTypeSignedness())
304 signSemantics = *signedness ? IntegerType::Signed : IntegerType::Unsigned;
305
306 consumeToken(Token::inttype);
307 return IntegerType::get(getContext(), *width, signSemantics);
308 }
309
310 // float-type
311 case Token::kw_f4E2M1FN:
312 consumeToken(Token::kw_f4E2M1FN);
313 return builder.getType<Float4E2M1FNType>();
314 case Token::kw_f6E2M3FN:
315 consumeToken(Token::kw_f6E2M3FN);
316 return builder.getType<Float6E2M3FNType>();
317 case Token::kw_f6E3M2FN:
318 consumeToken(Token::kw_f6E3M2FN);
319 return builder.getType<Float6E3M2FNType>();
320 case Token::kw_f8E5M2:
321 consumeToken(Token::kw_f8E5M2);
322 return builder.getType<Float8E5M2Type>();
323 case Token::kw_f8E4M3:
324 consumeToken(Token::kw_f8E4M3);
325 return builder.getType<Float8E4M3Type>();
326 case Token::kw_f8E4M3FN:
327 consumeToken(Token::kw_f8E4M3FN);
328 return builder.getType<Float8E4M3FNType>();
329 case Token::kw_f8E5M2FNUZ:
330 consumeToken(Token::kw_f8E5M2FNUZ);
331 return builder.getType<Float8E5M2FNUZType>();
332 case Token::kw_f8E4M3FNUZ:
333 consumeToken(Token::kw_f8E4M3FNUZ);
334 return builder.getType<Float8E4M3FNUZType>();
335 case Token::kw_f8E4M3B11FNUZ:
336 consumeToken(Token::kw_f8E4M3B11FNUZ);
337 return builder.getType<Float8E4M3B11FNUZType>();
338 case Token::kw_f8E3M4:
339 consumeToken(Token::kw_f8E3M4);
340 return builder.getType<Float8E3M4Type>();
341 case Token::kw_f8E8M0FNU:
342 consumeToken(Token::kw_f8E8M0FNU);
343 return builder.getType<Float8E8M0FNUType>();
344 case Token::kw_f8E5M3FNU:
345 consumeToken(Token::kw_f8E5M3FNU);
346 return builder.getType<Float8E5M3FNUType>();
347 case Token::kw_bf16:
348 consumeToken(Token::kw_bf16);
349 return builder.getType<BFloat16Type>();
350 case Token::kw_f16:
351 consumeToken(Token::kw_f16);
352 return builder.getType<Float16Type>();
353 case Token::kw_tf32:
354 consumeToken(Token::kw_tf32);
355 return builder.getType<FloatTF32Type>();
356 case Token::kw_f32:
357 consumeToken(Token::kw_f32);
358 return builder.getType<Float32Type>();
359 case Token::kw_f64:
360 consumeToken(Token::kw_f64);
361 return builder.getType<Float64Type>();
362 case Token::kw_f80:
363 consumeToken(Token::kw_f80);
364 return builder.getType<Float80Type>();
365 case Token::kw_f128:
366 consumeToken(Token::kw_f128);
367 return builder.getType<Float128Type>();
368
369 // index-type
370 case Token::kw_index:
371 consumeToken(Token::kw_index);
372 return builder.getIndexType();
373
374 // none-type
375 case Token::kw_none:
376 consumeToken(Token::kw_none);
377 return builder.getNoneType();
378
379 // token-type
380 case Token::kw_token:
381 consumeToken(Token::kw_token);
382 return builder.getType<TokenType>();
383
384 // extended type
385 case Token::exclamation_identifier:
386 return parseExtendedType();
387
388 // Handle completion of a dialect type.
389 case Token::code_complete:
390 if (getToken().isCodeCompletionFor(Token::exclamation_identifier))
391 return parseExtendedType();
392 return codeCompleteType();
393 }
394}
395
396/// Parse a tensor type.
397///
398/// tensor-type ::= `tensor` `<` dimension-list type `>`
399/// dimension-list ::= dimension-list-ranked | `*x`
400///
402 consumeToken(Token::kw_tensor);
403
404 if (parseToken(Token::less, "expected '<' in tensor type"))
405 return nullptr;
406
407 bool isUnranked;
408 SmallVector<int64_t, 4> dimensions;
409
410 if (consumeIf(Token::star)) {
411 // This is an unranked tensor type.
412 isUnranked = true;
413
415 return nullptr;
416
417 } else {
418 isUnranked = false;
419 if (parseDimensionListRanked(dimensions))
420 return nullptr;
421 }
422
423 // Parse the element type.
424 auto elementTypeLoc = getToken().getLoc();
425 auto elementType = parseType();
426
427 // Parse an optional encoding attribute.
428 Attribute encoding;
429 if (consumeIf(Token::comma)) {
430 auto parseResult = parseOptionalAttribute(encoding);
431 if (parseResult.has_value()) {
432 if (failed(parseResult.value()))
433 return nullptr;
434 if (auto v = dyn_cast_or_null<VerifiableTensorEncoding>(encoding)) {
435 if (failed(v.verifyEncoding(dimensions, elementType,
436 [&] { return emitError(); })))
437 return nullptr;
438 }
439 }
440 }
441
442 if (!elementType || parseToken(Token::greater, "expected '>' in tensor type"))
443 return nullptr;
444 if (!TensorType::isValidElementType(elementType))
445 return emitError(elementTypeLoc, "invalid tensor element type"), nullptr;
446
447 if (isUnranked) {
448 if (encoding)
449 return emitError("cannot apply encoding to unranked tensor"), nullptr;
450 return UnrankedTensorType::get(elementType);
451 }
452 return RankedTensorType::get(dimensions, elementType, encoding);
453}
454
455/// Parse a tuple type.
456///
457/// tuple-type ::= `tuple` `<` (type (`,` type)*)? `>`
458///
460 consumeToken(Token::kw_tuple);
461
462 // Parse the '<'.
463 if (parseToken(Token::less, "expected '<' in tuple type"))
464 return nullptr;
465
466 // Check for an empty tuple by directly parsing '>'.
467 if (consumeIf(Token::greater))
468 return TupleType::get(getContext());
469
470 // Parse the element types and the '>'.
472 if (parseTypeListNoParens(types) ||
473 parseToken(Token::greater, "expected '>' in tuple type"))
474 return nullptr;
475
476 return TupleType::get(getContext(), types);
477}
478
479/// Parse a vector type.
480///
481/// vector-type ::= `vector` `<` vector-dim-list vector-element-type `>`
482/// vector-dim-list := (static-dim-list `x`)? (`[` static-dim-list `]` `x`)?
483/// static-dim-list ::= decimal-literal (`x` decimal-literal)*
484///
486 SMLoc loc = getToken().getLoc();
487 consumeToken(Token::kw_vector);
488
489 if (parseToken(Token::less, "expected '<' in vector type"))
490 return nullptr;
491
492 // Parse the dimensions.
493 SmallVector<int64_t, 4> dimensions;
494 SmallVector<bool, 4> scalableDims;
495 if (parseVectorDimensionList(dimensions, scalableDims))
496 return nullptr;
497
498 // Parse the element type.
499 auto elementType = parseType();
500 if (!elementType || parseToken(Token::greater, "expected '>' in vector type"))
501 return nullptr;
502
503 return getChecked<VectorType>(loc, dimensions, elementType, scalableDims);
504}
505
506/// Parse a dimension list in a vector type. This populates the dimension list.
507/// For i-th dimension, `scalableDims[i]` contains either:
508/// * `false` for a non-scalable dimension (e.g. `4`),
509/// * `true` for a scalable dimension (e.g. `[4]`).
510///
511/// vector-dim-list := (static-dim-list `x`)?
512/// static-dim-list ::= static-dim (`x` static-dim)*
513/// static-dim ::= (decimal-literal | `[` decimal-literal `]`)
514///
515ParseResult
517 SmallVectorImpl<bool> &scalableDims) {
518 // If there is a set of fixed-length dimensions, consume it
519 while (getToken().is(Token::integer) || getToken().is(Token::l_square)) {
520 int64_t value;
521 bool scalable = consumeIf(Token::l_square);
523 return failure();
524 dimensions.push_back(value);
525 if (scalable) {
526 if (!consumeIf(Token::r_square))
527 return emitWrongTokenError("missing ']' closing scalable dimension");
528 }
529 scalableDims.push_back(scalable);
530 // Make sure we have an 'x' or something like 'xbf32'.
532 return failure();
533 }
534
535 return success();
536}
537
538/// Parse a dimension list of a tensor or memref type. This populates the
539/// dimension list, using ShapedType::kDynamic for the `?` dimensions if
540/// `allowDynamic` is set and errors out on `?` otherwise. Parsing the trailing
541/// `x` is configurable.
542///
543/// dimension-list ::= eps | dimension (`x` dimension)*
544/// dimension-list-with-trailing-x ::= (dimension `x`)*
545/// dimension ::= `?` | decimal-literal
546///
547/// When `allowDynamic` is not set, this is used to parse:
548///
549/// static-dimension-list ::= eps | decimal-literal (`x` decimal-literal)*
550/// static-dimension-list-with-trailing-x ::= (dimension `x`)*
551ParseResult
553 bool allowDynamic, bool withTrailingX) {
554 auto parseDim = [&]() -> LogicalResult {
555 auto loc = getToken().getLoc();
556 if (consumeIf(Token::question)) {
557 if (!allowDynamic)
558 return emitError(loc, "expected static shape");
559 dimensions.push_back(ShapedType::kDynamic);
560 } else {
561 int64_t value;
562 if (failed(parseIntegerInDimensionList(value)))
563 return failure();
564 dimensions.push_back(value);
565 }
566 return success();
567 };
568
569 if (withTrailingX) {
570 while (getToken().isAny(Token::integer, Token::question)) {
571 if (failed(parseDim()) || failed(parseXInDimensionList()))
572 return failure();
573 }
574 return success();
575 }
576
577 if (getToken().isAny(Token::integer, Token::question)) {
578 if (failed(parseDim()))
579 return failure();
580 while (getToken().is(Token::bare_identifier) &&
581 getTokenSpelling()[0] == 'x') {
582 if (failed(parseXInDimensionList()) || failed(parseDim()))
583 return failure();
584 }
585 }
586 return success();
587}
588
590 // Hexadecimal integer literals (starting with `0x`) are not allowed in
591 // aggregate type declarations. Therefore, `0xf32` should be processed as
592 // a sequence of separate elements `0`, `x`, `f32`.
593 if (getTokenSpelling().size() > 1 && getTokenSpelling()[1] == 'x') {
594 // We can get here only if the token is an integer literal. Hexadecimal
595 // integer literals can only start with `0x` (`1x` wouldn't lex as a
596 // literal, just `1` would, at which point we don't get into this
597 // branch).
598 assert(getTokenSpelling()[0] == '0' && "invalid integer literal");
599 value = 0;
600 state.lex.resetPointer(getTokenSpelling().data() + 1);
601 consumeToken();
602 } else {
603 // Make sure this integer value is in bound and valid.
604 std::optional<uint64_t> dimension = getToken().getUInt64IntegerValue();
605 if (!dimension ||
606 *dimension > (uint64_t)std::numeric_limits<int64_t>::max())
607 return emitError("invalid dimension");
608 value = (int64_t)*dimension;
609 consumeToken(Token::integer);
610 }
611 return success();
612}
613
614/// Parse an 'x' token in a dimension list, handling the case where the x is
615/// juxtaposed with an element type, as in "xf32", leaving the "f32" as the next
616/// token.
618 if (getToken().isNot(Token::bare_identifier) || getTokenSpelling()[0] != 'x')
619 return emitWrongTokenError("expected 'x' in dimension list");
620
621 // If we had a prefix of 'x', lex the next token immediately after the 'x'.
622 if (getTokenSpelling().size() != 1)
623 state.lex.resetPointer(getTokenSpelling().data() + 1);
624
625 // Consume the 'x'.
626 consumeToken(Token::bare_identifier);
627
628 return success();
629}
return success()
Attributes are known-constant values of operations.
Definition Attributes.h:25
static bool isValidElementType(Type type)
Return true if the specified element type is ok in a memref.
This class implements Optional functionality for ParseResult.
static bool isValidElementType(Type type)
Return true if the specified element type is ok in a tensor.
SMLoc getLoc() const
Definition Token.cpp:24
static std::optional< uint64_t > getUInt64IntegerValue(StringRef spelling)
For an integer token, return its value as an uint64_t.
Definition Token.cpp:45
std::optional< unsigned > getIntTypeBitwidth() const
For an inttype token, return its bitwidth.
Definition Token.cpp:64
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
ParseResult parseXInDimensionList()
Parse an 'x' token in a dimension list, handling the case where the x is juxtaposed with an element t...
T getChecked(SMLoc loc, ParamsT &&...params)
Invoke the getChecked method of the given Attribute or Type class, using the provided location to emi...
Definition Parser.h:198
OptionalParseResult parseOptionalType(Type &type)
Optionally parse a type.
ParseResult parseToken(Token::Kind expectedToken, const Twine &message)
Consume the specified token if present and return success.
Definition Parser.cpp:305
ParseResult parseCommaSeparatedListUntil(Token::Kind rightToken, function_ref< ParseResult()> parseElement, bool allowEmptyList=true)
Parse a comma-separated list of elements up until the specified end token.
Definition Parser.cpp:173
Type parseType()
Parse an arbitrary type.
ParseResult parseTypeListParens(SmallVectorImpl< Type > &elements)
Parse a parenthesized list of types.
ParseResult parseVectorDimensionList(SmallVectorImpl< int64_t > &dimensions, SmallVectorImpl< bool > &scalableDims)
Parse a dimension list in a vector type.
Type parseMemRefType()
Parse a memref type.
Type parseNonFunctionType()
Parse a non function type.
Type parseExtendedType()
Parse an extended type.
Type parseTupleType()
Parse a tuple type.
InFlightDiagnostic emitError(const Twine &message={})
Emit an error and return failure.
Definition Parser.cpp:192
ParserState & state
The Parser is subclassed and reinstantiated.
Definition Parser.h:370
Attribute parseAttribute(Type type={})
Parse an arbitrary attribute with an optional type.
StringRef getTokenSpelling() const
Definition Parser.h:104
void consumeToken()
Advance the current lexer onto the next token.
Definition Parser.h:119
ParseResult parseIntegerInDimensionList(int64_t &value)
Type parseComplexType()
Parse a complex type.
ParseResult parseDimensionListRanked(SmallVectorImpl< int64_t > &dimensions, bool allowDynamic=true, bool withTrailingX=true)
Parse a dimension list of a tensor or memref type.
ParseResult parseFunctionResultTypes(SmallVectorImpl< Type > &elements)
Parse a function result type.
InFlightDiagnostic emitWrongTokenError(const Twine &message={})
Emit an error about a "wrong token".
Definition Parser.cpp:254
ParseResult parseCommaSeparatedList(Delimiter delimiter, function_ref< ParseResult()> parseElementFn, StringRef contextMessage=StringRef())
Parse a list of comma-separated items with an optional delimiter.
Definition Parser.cpp:84
VectorType parseVectorType()
Parse a vector type.
Type parseFunctionType()
Parse a function type.
OptionalParseResult parseOptionalAttribute(Attribute &attribute, Type type={})
Parse an optional attribute with the provided type.
ParseResult parseTypeListNoParens(SmallVectorImpl< Type > &elements)
Parse a list of types without an enclosing parenthesis.
const Token & getToken() const
Return the current token the parser is inspecting.
Definition Parser.h:103
MLIRContext * getContext() const
Definition Parser.h:38
Type parseTensorType()
Parse a tensor type.
bool consumeIf(Token::Kind kind)
If the current token has the specified kind, consume it and return true.
Definition Parser.h:111
AttrTypeReplacer.
Include the generated interface declarations.