YARP
Yet Another Robot Platform
BottleImpl.cpp
Go to the documentation of this file.
1 /*
2  * SPDX-FileCopyrightText: 2006-2021 Istituto Italiano di Tecnologia (IIT)
3  * SPDX-FileCopyrightText: 2006-2010 RobotCub Consortium
4  * SPDX-FileCopyrightText: 2006, 2008 Arjan Gijsberts
5  * SPDX-License-Identifier: BSD-3-Clause
6  */
7 
9 
10 #include <yarp/conf/numeric.h>
11 
17 
18 #include <limits>
19 
20 using yarp::os::Bottle;
21 using yarp::os::Bytes;
25 using yarp::os::Value;
28 
29 namespace {
30 YARP_OS_LOG_COMPONENT(BOTTLEIMPL, "yarp.os.impl.BottleImpl")
31 } // namespace
32 
33 BottleImpl::BottleImpl() :
34  parent(nullptr),
35  invalid(false),
36  ro(false),
37  speciality(0),
38  nested(false),
39  dirty(true)
40 {
41 }
42 
44  parent(parent),
45  invalid(false),
46  ro(false),
47  speciality(0),
48  nested(false),
49  dirty(true)
50 {
51 }
52 
53 
55 {
56  clear();
57 }
58 
59 
60 void BottleImpl::add(Storable* s)
61 {
62  content.push_back(s);
63  dirty = true;
64 }
65 
66 
68 {
69  for (auto& i : content) {
70  delete i;
71  }
72  content.clear();
73  dirty = true;
74 }
75 
76 void BottleImpl::smartAdd(const std::string& str)
77 {
78  if (str.length() > 0) {
79  char ch = str[0];
80  Storable* s = nullptr;
81  StoreString* ss = nullptr;
82  bool numberLike = true;
83  bool preamble = true;
84  bool hexActive = false;
85  size_t hexStart = 0;
86  int periodCount = 0;
87  int signCount = 0;
88  bool hasPeriodOrE = false;
89  for (size_t i = 0; i < str.length(); i++) {
90  if (str == "inf" || str == "-inf" || str == "nan") {
91  hasPeriodOrE = true;
92  break;
93  }
94  char ch2 = str[i];
95  if (ch2 == '.') {
96  hasPeriodOrE = true;
97  periodCount++;
98  if (periodCount > 1) {
99  numberLike = false;
100  }
101  }
102  if (!hexActive && (ch2 == 'e' || ch2 == 'E')) {
103  hasPeriodOrE = true;
104  }
105  if (preamble) {
106  if (ch2 == 'x' || ch2 == 'X') {
107  hexActive = true;
108  hexStart = i;
109  continue;
110  }
111  }
112  if (preamble) {
113  if (ch2 == '0' || ch2 == '+' || ch2 == '-') {
114  if (ch2 == '+' || ch2 == '-') {
115  signCount++;
116  if (signCount > 1) {
117  numberLike = false;
118  }
119  }
120  continue;
121  }
122  }
123  preamble = false;
124  if (!((ch2 >= '0' && ch2 <= '9') || ch2 == '.' || ch2 == 'e' ||
125  ch2 == 'E' || ch2 == '+' || ch2 == '-' ||
126  (hexActive && ((ch2 >= 'a' && ch2 <= 'f') ||
127  (ch2 >= 'A' && ch2 <= 'F'))))) {
128  numberLike = false;
129  break;
130  }
131  }
132  if (hexActive) {
133  if (static_cast<int>(str.length()) - (hexStart + 1) > 8) {
134  // we can only deal with 32bit hexadecimal
135  numberLike = false;
136  }
137  }
138 
139  if (numberLike &&
140  ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.' || ch == 'i' /* inf */ || ch == 'n' /* nan */) &&
141  (ch != '.' || str.length() > 1)) {
142  if (!hasPeriodOrE) {
143  s = new StoreInt64(0);
144  } else {
145  s = new StoreFloat64(0);
146  }
147  } else if (ch == '(') {
148  s = new StoreList();
149  } else if (ch == '[') {
150  s = new StoreVocab32();
151  } else if (ch == '{') {
152  s = new StoreBlob();
153  } else {
154  s = ss = new StoreString("");
155  }
156  if (s != nullptr) {
157  s->fromStringNested(str);
158 
159  // Traditionally all int are read as 32 bit integers, but 64 bit
160  // integers will not fit.
161  // Therefore the value is read as 64 bit, but if the value would fit
162  // a 32 bit integer, it is cast to a 32 bit integer.
163  if (s->isInt64()
164  && s->asInt64() >= std::numeric_limits<int32_t>::min()
165  && s->asInt64() <= std::numeric_limits<int32_t>::max()) {
166  Storable* s_i32 = new StoreInt32(s->asInt32());
167  delete s;
168  s = s_i32;
169  s_i32 = nullptr;
170  }
171 
172  if (ss != nullptr) {
173  if (str.length() == 0 || str[0] != '\"') {
174  std::string val = ss->asString();
175  if (val == "true") {
176  delete s;
177  s = new StoreVocab32(static_cast<int>('1'));
178  } else if (val == "false") {
179  delete s;
180  s = new StoreVocab32(0);
181  }
182  }
183  }
184  add(s);
185  }
186  ss = nullptr;
187  }
188 }
189 
190 void BottleImpl::fromString(const std::string& line)
191 {
192  clear();
193  dirty = true;
194  std::string arg;
195  bool quoted = false;
196  bool back = false;
197  bool begun = false;
198  int nested = 0;
199  int nestedAlt = 0;
200  std::string nline = line + " ";
201 
202  for (char ch : nline) {
203  if (back) {
204  arg += ch;
205  back = false;
206  } else {
207  if (!begun) {
208  if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r') {
209  begun = true;
210  }
211  }
212  if (begun) {
213  if (ch == '\"') {
214  quoted = !quoted;
215  }
216  if (!quoted) {
217  if (ch == '(') {
218  nested++;
219  }
220  if (ch == ')') {
221  nested--;
222  }
223  if (ch == '{') {
224  nestedAlt++;
225  }
226  if (ch == '}') {
227  nestedAlt--;
228  }
229  }
230  if (ch == '\\') {
231  back = true;
232  arg += ch;
233  } else {
234  if ((!quoted) && (ch == ',' || ch == ' ' || ch == '\t' ||
235  ch == '\n' || ch == '\r') &&
236  (nestedAlt == 0) && (nested == 0)) {
237  if (!arg.empty()) {
238  if (arg == "null") {
239  add(new StoreVocab32(yarp::os::createVocab32('n', 'u', 'l', 'l')));
240  } else {
241  smartAdd(arg);
242  }
243  }
244  arg = "";
245  begun = false;
246  } else {
247  arg += ch;
248  }
249  }
250  }
251  }
252  }
253 }
254 
255 bool BottleImpl::isComplete(const char* txt)
256 {
257  bool quoted = false;
258  bool back = false;
259  bool begun = false;
260  int nested = 0;
261  int nestedAlt = 0;
262  std::string nline = txt;
263  nline += " ";
264 
265  for (char ch : nline) {
266  if (back) {
267  back = false;
268  } else {
269  if (!begun) {
270  if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r') {
271  begun = true;
272  }
273  }
274  if (begun) {
275  if (ch == '\"') {
276  quoted = !quoted;
277  }
278  if (!quoted) {
279  if (ch == '(') {
280  nested++;
281  }
282  if (ch == ')') {
283  nested--;
284  }
285  if (ch == '{') {
286  nestedAlt++;
287  }
288  if (ch == '}') {
289  nestedAlt--;
290  }
291  }
292  if (ch == '\\') {
293  back = true;
294  // arg += ch;
295  } else {
296  if ((!quoted) &&
297  (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') &&
298  (nestedAlt == 0) && (nested == 0)) {
299  // smartAdd(arg);
300  begun = false;
301  } else {
302  // arg += ch;
303  }
304  }
305  }
306  }
307  }
308  return nested == 0 && nestedAlt == 0 && !quoted;
309 }
310 
311 
312 std::string BottleImpl::toString() const
313 {
314  std::string result;
315  for (unsigned int i = 0; i < content.size(); i++) {
316  if (i > 0) {
317  result += " ";
318  }
319  Storable& s = *content[i];
320  result += s.toStringNested();
321  }
322  return result;
323 }
324 
326 {
327  return content.size();
328 }
329 
330 
332 {
333  if (reader.isError()) {
334  return false;
335  }
336  std::int32_t id = speciality;
337  yCTrace(BOTTLEIMPL, "READING, nest flag is %d", nested);
338  if (id == 0) {
339  id = reader.expectInt32();
340  yCTrace(BOTTLEIMPL, "READ subcode %" PRId32, id);
341  } else {
342  yCTrace(BOTTLEIMPL, "READ skipped subcode %" PRId32, speciality);
343  }
344  Storable* storable = Storable::createByCode(id);
345  if (storable == nullptr) {
346  yCError(BOTTLEIMPL, "Reader failed, unrecognized object code %" PRId32, id);
347  return false;
348  }
349  storable->readRaw(reader);
350  add(storable);
351  return true;
352 }
353 
354 
355 void BottleImpl::fromBinary(const char* text, size_t len)
356 {
357  std::string wrapper(text, len);
358  StringInputStream sis;
359  sis.add(wrapper);
360  StreamConnectionReader reader;
361  Route route;
362  reader.reset(sis, nullptr, route, len, false);
363  read(reader);
364 }
365 
366 
367 bool BottleImpl::fromBytes(const Bytes& data)
368 {
369  std::string wrapper(data.get(), data.length());
370  StringInputStream sis;
371  sis.add(wrapper);
372  StreamConnectionReader reader;
373  Route route;
374  reader.reset(sis, nullptr, route, data.length(), false);
375 
376  clear();
377  dirty = true; // for clarity
378 
379  if (!nested) {
380  clear();
381  specialize(0);
382 
383  std::int32_t code = reader.expectInt32();
384  if (reader.isError()) {
385  return false;
386  }
387  yCTrace(BOTTLEIMPL, "READ got top level code %" PRId32, code);
388  code = code & UNIT_MASK;
389  if (code != 0) {
390  specialize(code);
391  }
392  }
393  std::int32_t len = reader.expectInt32();
394  if (reader.isError()) {
395  return false;
396  }
397  yCTrace(BOTTLEIMPL, "READ bottle length %d", len);
398  for (int i = 0; i < len; i++) {
399  bool ok = fromBytes(reader);
400  if (!ok) {
401  return false;
402  }
403  }
404 
405  return true;
406 }
407 
409 {
410  synch();
411  yCAssert(BOTTLEIMPL, data.length() == byteCount());
412  memcpy(data.get(), getBytes(), byteCount());
413 }
414 
415 
416 const char* BottleImpl::getBytes() const
417 {
418  yCTrace(BOTTLEIMPL, "am I nested? %d", nested);
419  synch();
420  return &data[0];
421 }
422 
423 size_t BottleImpl::byteCount() const
424 {
425  synch();
426  return data.size();
427 }
428 
430 {
431  synch();
432 }
433 
435 {
436  // could simplify this if knew lengths of blocks up front
437  if (writer.isTextMode()) {
438  writer.appendText(toString());
439  } else {
440  synch();
441  writer.appendBlock(getBytes(), byteCount());
442  }
443  return !writer.isError();
444 }
445 
446 
448 {
449  bool result = false;
450 
451  if (reader.isTextMode()) {
452  std::string str = reader.expectText();
453  if (reader.isError()) {
454  return false;
455  }
456  bool done = (str.length() <= 0);
457  while (!done) {
458  if (str[str.length() - 1] == '\\') {
459  str = str.substr(0, str.length() - 1);
460  str += reader.expectText();
461  if (reader.isError()) {
462  return false;
463  }
464  } else {
465  if (isComplete(str.c_str())) {
466  done = true;
467  } else {
468  str += "\n";
469  str += reader.expectText();
470  if (reader.isError()) {
471  return false;
472  }
473  }
474  }
475  }
476  fromString(str);
477  result = true;
478  } else {
479  if (!nested) {
480  // no byte length any more to facilitate nesting
481  // reader.expectInt32(); // the bottle byte ct; ignored
482 
483  clear();
484  specialize(0);
485 
486  std::int32_t code = reader.expectInt32();
487  if (reader.isError()) {
488  return false;
489  }
490  yCTrace(BOTTLEIMPL, "READ got top level code %" PRId32, code);
491  code = code & UNIT_MASK;
492  if (code != 0) {
493  specialize(code);
494  }
495  }
496 
497  result = true;
498  clear();
499  dirty = true; // for clarity
500 
501  std::int32_t len = 0;
502  len = reader.expectInt32();
503  if (reader.isError()) {
504  return false;
505  }
506  yCTrace(BOTTLEIMPL, "READ got length %d", len);
507  for (int i = 0; i < len; i++) {
508  bool ok = fromBytes(reader);
509  if (!ok) {
510  return false;
511  }
512  }
513  }
514  return result;
515 }
516 
517 void BottleImpl::synch() const
518 {
519  const_cast<BottleImpl*>(this)->synch();
520 }
521 
522 void BottleImpl::synch()
523 {
524  if (dirty) {
525  if (!nested) {
526  subCode();
527  yCTrace(BOTTLEIMPL, "bottle code %" PRId32, StoreList::code + subCode());
528  }
529  data.clear();
530  BufferedConnectionWriter writer;
531  if (!nested) {
532  writer.appendInt32(StoreList::code + speciality);
533  yCTrace(BOTTLEIMPL, "wrote bottle code %" PRId32, StoreList::code + speciality);
534  }
535  yCTrace(BOTTLEIMPL, "bottle length %zd", size());
536  writer.appendInt32(static_cast<std::int32_t>(size()));
537  for (auto s : content) {
538  if (speciality == 0) {
539  yCTrace(BOTTLEIMPL, "subcode %" PRId32, s->getCode());
540  writer.appendInt32(s->getCode());
541  } else {
542  yCTrace(BOTTLEIMPL, "skipped subcode %" PRId32, s->getCode());
543  yCAssert(BOTTLEIMPL, speciality == s->getCode());
544  }
545  if (s->isList()) {
546  s->asList()->implementation->setNested(true);
547  }
548  s->writeRaw(writer);
549  }
550  data.resize(writer.dataSize(), ' ');
551  MemoryOutputStream m(&data[0]);
552  writer.write(m);
553  dirty = false;
554  }
555 }
556 
557 
558 void BottleImpl::specialize(std::int32_t subCode)
559 {
560  speciality = subCode;
561 }
562 
563 
565 {
566  return speciality;
567 }
568 
569 void BottleImpl::setNested(bool nested)
570 {
571  this->nested = nested;
572 }
573 
574 
575 std::int32_t BottleImpl::subCode()
576 {
577  return subCoder(*this);
578 }
579 
581 {
582  return index < size();
583 }
584 
585 bool BottleImpl::isInt8(int index)
586 {
587  return (checkIndex(index) ? content[index]->isInt8() : false);
588 }
589 
590 bool BottleImpl::isInt16(int index)
591 {
592  return (checkIndex(index) ? content[index]->isInt16() : false);
593 }
594 
595 bool BottleImpl::isInt32(int index)
596 {
597  return (checkIndex(index) ? content[index]->isInt32() : false);
598 }
599 
600 bool BottleImpl::isInt64(int index)
601 {
602  return (checkIndex(index) ? content[index]->isInt64() : false);
603 }
604 
605 bool BottleImpl::isFloat32(int index)
606 {
607  return (checkIndex(index) ? content[index]->isFloat32() : false);
608 }
609 
610 bool BottleImpl::isFloat64(int index)
611 {
612  return (checkIndex(index) ? content[index]->isFloat64() : false);
613 }
614 
615 bool BottleImpl::isString(int index)
616 {
617  return (checkIndex(index) ? content[index]->isString() : false);
618 }
619 
620 bool BottleImpl::isList(int index)
621 {
622  return (checkIndex(index) ? content[index]->isList() : false);
623 }
624 
626 {
627  Storable* stb = nullptr;
628  if (size() == 0) {
629  stb = new StoreNull();
630  } else {
631  stb = content[size() - 1];
632  content.pop_back();
633  dirty = true;
634  }
635  yCAssert(BOTTLEIMPL, stb != nullptr);
636  return stb;
637 }
638 
640 {
641  return (checkIndex(index) ? *(content[index]) : getNull());
642 }
643 
645 {
646  auto* lst = new StoreList();
647  add(lst);
648  return lst->internal();
649 }
650 
652 {
653  auto* lst = new StoreDict();
654  add(lst);
655  return lst->internal();
656 }
657 
659 {
660 
661  if (len == 0 || alt->size() == 0) {
662  clear();
663  return;
664  }
665 
666  // Handle copying to the same object just a subset of the bottle
667  const BottleImpl* src = alt;
668  BottleImpl tmp(nullptr);
669  if (alt == this) {
670  tmp.fromString(toString());
671  src = &tmp;
672  }
673 
674  clear();
675 
676  const size_t last = src->size() - 1;
677  for (size_t i = 0; (i < len) && (first + i <= last); ++i) {
678  add(src->get(first + i).cloneStorable());
679  }
680 }
681 
683 {
684  if (ro) {
685  yCFatal(BOTTLEIMPL, "Attempted to modify the null bottle");
686  }
687  if (invalid) {
688  invalid = false;
689  }
690 }
691 
692 Value& BottleImpl::findGroupBit(const std::string& key) const
693 {
694  for (size_t i = 0; i < size(); i++) {
695  Value* org = &(get(static_cast<int>(i)));
696  Value* cursor = org;
697  if (cursor->isList()) {
698  cursor = &(cursor->asList()->get(0));
699  }
700  if (key == cursor->toString()) {
701  return *org;
702  }
703  }
704  // return invalid object
705  return get(-1);
706 }
707 
708 Value& BottleImpl::findBit(const std::string& key) const
709 {
710  for (size_t i = 0; i < size(); i++) {
711  Value* org = &(get(static_cast<int>(i)));
712  Value* cursor = org;
713  bool nested = false;
714  if (cursor->isList()) {
715  Bottle* bot = cursor->asList();
716  cursor = &(bot->get(0));
717  nested = true;
718  }
719  if (key == cursor->toString()) {
720  if (nested) {
721  return org->asList()->get(1);
722  }
723  if ((parent != nullptr) && (parent->getMonitor() != nullptr)) {
724  SearchReport report;
725  report.key = key;
726  report.isFound = true;
727  if (size() == 2) {
728  report.value = get(static_cast<int>(i + 1)).toString();
729  }
730  if (parent != nullptr) {
731  parent->reportToMonitor(report);
732  }
733  }
734  return get(static_cast<int>(i + 1));
735  }
736  }
737  // return invalid object
738  if ((parent != nullptr) && (parent->getMonitor() != nullptr)) {
739  SearchReport report;
740  report.key = key;
741  parent->reportToMonitor(report);
742  }
743  return get(-1);
744 }
#define UNIT_MASK
Definition: Storable.h:18
A simple collection of objects that can be described and transmitted in a portable way.
Definition: Bottle.h:74
Value & get(size_type index) const
Reads a Value v from a certain part of the list.
Definition: Bottle.cpp:246
A simple abstraction for a block of bytes.
Definition: Bytes.h:25
size_t length() const
Definition: Bytes.cpp:22
const char * get() const
Definition: Bytes.cpp:27
An interface for reading from a network connection.
virtual bool isTextMode() const =0
Check if the connection is text mode.
virtual std::int32_t expectInt32()=0
Read a 32-bit integer from the network connection.
virtual std::string expectText(const char terminatingChar='\n')=0
Read some text from the network connection.
virtual bool isError() const =0
An interface for writing to a network connection.
virtual bool isError() const =0
virtual bool isTextMode() const =0
Check if the connection is text mode.
virtual void appendText(const std::string &str, const char terminate='\n')=0
Send a terminated string to the network connection.
virtual void appendBlock(const char *data, size_t len)=0
Send a block of data to the network connection.
A class for storing options and configuration information.
Definition: Property.h:34
Information about a connection between two ports.
Definition: Route.h:29
A base class for nested structures that can be searched.
Definition: Searchable.h:66
An InputStream that reads from a string.
void add(const std::string &txt)
A single value (typically within a Bottle).
Definition: Value.h:45
virtual bool isList() const
Checks if value is a list.
Definition: Value.cpp:162
virtual Bottle * asList() const
Get list value.
Definition: Value.cpp:240
std::string toString() const override
Return a standard text representation of the content of the object.
Definition: Value.cpp:356
virtual std::int32_t getCode() const
Get standard type code of value.
Definition: Value.cpp:374
A flexible data format for holding a bunch of numbers and strings.
Definition: BottleImpl.h:33
size_type size() const
Definition: BottleImpl.cpp:325
bool fromBytes(const yarp::os::Bytes &data)
Definition: BottleImpl.cpp:367
Storable & get(size_type index) const
Definition: BottleImpl.cpp:639
yarp::os::Bottle & addList()
Definition: BottleImpl.cpp:644
const char * getBytes() const
Definition: BottleImpl.cpp:416
bool write(ConnectionWriter &writer) const
Definition: BottleImpl.cpp:434
Value & findGroupBit(const std::string &key) const
Definition: BottleImpl.cpp:692
static StoreNull & getNull()
Definition: BottleImpl.h:155
bool checkIndex(size_type index) const
Definition: BottleImpl.cpp:580
void toBytes(yarp::os::Bytes &data)
Definition: BottleImpl.cpp:408
bool isFloat64(int index)
Definition: BottleImpl.cpp:610
Value & findBit(const std::string &key) const
Definition: BottleImpl.cpp:708
bool isFloat32(int index)
Definition: BottleImpl.cpp:605
void copyRange(const BottleImpl *alt, size_type first=0, size_type len=npos)
Definition: BottleImpl.cpp:658
yarp::os::Property & addDict()
Definition: BottleImpl.cpp:651
static bool isComplete(const char *txt)
Definition: BottleImpl.cpp:255
bool read(ConnectionReader &reader)
Definition: BottleImpl.cpp:447
std::string toString() const
Definition: BottleImpl.cpp:312
void fromBinary(const char *text, size_t len)
Definition: BottleImpl.cpp:355
void setNested(bool nested)
Definition: BottleImpl.cpp:569
bool isString(int index)
Definition: BottleImpl.cpp:615
void specialize(std::int32_t subCode)
Definition: BottleImpl.cpp:558
void fromString(const std::string &line)
Definition: BottleImpl.cpp:190
A single item in a Bottle.
Definition: Storable.h:44
virtual std::string toStringNested() const
Create string representation, including any syntax that should wrap it such as braces or parentheses.
Definition: Storable.h:261
std::int64_t asInt64() const override
Get 64-bit integer value.
Definition: Storable.h:138
virtual Storable * cloneStorable() const
Typed synonym for clone()
Definition: Storable.h:59
bool isList() const override
Checks if value is a list.
Definition: Storable.h:173
virtual bool writeRaw(ConnectionWriter &connection) const =0
static Storable * createByCode(std::int32_t id)
Definition: Storable.cpp:64
virtual void fromStringNested(const std::string &src)
Initialize from a string representation.
Definition: Storable.h:250
std::string toString() const override=0
Return a standard text representation of the content of the object.
yarp::os::Bottle * asList() const override
Get list value.
Definition: Storable.h:178
bool isInt64() const override
Checks if value is a 64-bit integer.
Definition: Storable.h:133
virtual bool readRaw(ConnectionReader &connection)=0
std::int32_t asInt32() const override
Get 32-bit integer value.
Definition: Storable.h:128
Key/value pairs.
Definition: Storable.h:1105
A nested list of items.
Definition: Storable.h:1040
static const std::int32_t code
Definition: Storable.h:1067
An empty item.
Definition: Storable.h:286
std::string asString() const override
Get string value.
Definition: Storable.h:958
A vocabulary item.
Definition: Storable.h:831
Lets Readable objects read from the underlying InputStream associated with the connection between two...
void reset(yarp::os::InputStream &in, TwoWayStream *str, const Route &route, size_t len, bool textMode, bool bareMode=false)
std::int32_t expectInt32() override
Read a 32-bit integer from the network connection.
#define yCError(component,...)
Definition: LogComponent.h:154
#define yCAssert(component, x)
Definition: LogComponent.h:169
#define yCTrace(component,...)
Definition: LogComponent.h:85
#define yCFatal(component,...)
Definition: LogComponent.h:165
#define YARP_OS_LOG_COMPONENT(name, name_string)
Definition: LogComponent.h:35
std::int32_t subCoder(T &content)
Definition: Storable.h:1164
constexpr yarp::conf::vocab32_t createVocab32(char a, char b=0, char c=0, char d=0)
Create a vocab from chars.
Definition: Vocab.h:28