34#include <rtl/character.hxx>
42static bool IS_WS(std::istream::int_type ch) {
43 return ch != std::istream::traits_type::eof()
44 && rtl::isAsciiWhiteSpace(
45 static_cast<unsigned char>(
46 std::istream::traits_type::to_char_type(
ch)));
50 return ch != std::istream::traits_type::eof()
51 && strchr(
"+-<=>", std::istream::traits_type::to_char_type(
ch));
58static bool eq_sentence(OString& outs, std::istream *strm,
const char *end =
nullptr);
72 {
"!=",
"\\equiv ", 0, 0 },
73 {
"#",
"\\\\", 0, 0 },
74 {
"+-",
"\\pm ", 0, 0 },
75 {
"-+",
"\\mp ", 0, 0 },
76 {
"<=",
"\\leq ", 0, 0 },
77 {
"==",
"\\equiv ", 0, 0 },
78 {
">=",
"\\geq ", 0, 0 },
79 {
"Pr",
nullptr, 0, 0 },
83 {
"acute",
nullptr, 1, 0 },
84 {
"aleph",
nullptr, 0, 0 },
85 {
"alpha",
nullptr, 0,
EQ_CASE },
86 {
"amalg",
nullptr, 0, 0 },
87 {
"and",
nullptr, 0, 0 },
88 {
"angle",
nullptr, 0, 0 },
89 {
"angstrom",
nullptr, 0, 0 },
90 {
"approx",
nullptr, 0, 0 },
91 {
"arc",
nullptr, 0, 0 },
92 {
"arccos",
nullptr, 0, 0 },
93 {
"arch",
nullptr, 0, 0 },
94 {
"arcsin",
nullptr, 0, 0 },
95 {
"arctan",
nullptr, 0, 0 },
96 {
"arg",
nullptr, 0, 0 },
97 {
"assert",
"\\vdash", 0, 0 },
98 {
"ast",
nullptr, 0, 0 },
99 {
"asymp",
nullptr, 0, 0 },
100 {
"atop",
nullptr, 1,
EQ_ATOP },
101 {
"backslash",
nullptr, 0, 0 },
102 {
"bar",
nullptr, 1, 0 },
103 {
"because",
nullptr, 0, 0 },
104 {
"beta",
nullptr, 0,
EQ_CASE },
105 {
"big",
nullptr, 0,
EQ_CASE },
106 {
"bigcap",
nullptr, 0, 0 },
107 {
"bigcirc",
nullptr, 0, 0 },
108 {
"bigcup",
nullptr, 0, 0 },
109 {
"bigg",
nullptr, 0,
EQ_CASE },
110 {
"bigodiv",
nullptr, 0, 0 },
111 {
"bigodot",
nullptr, 0, 0 },
112 {
"bigominus",
nullptr, 0, 0 },
113 {
"bigoplus",
nullptr, 0, 0 },
114 {
"bigotimes",
nullptr, 0, 0 },
115 {
"bigsqcap",
nullptr, 0, 0 },
116 {
"bigsqcup",
nullptr, 0, 0 },
117 {
"biguplus",
nullptr, 0, 0 },
118 {
"bigvee",
nullptr, 0, 0 },
119 {
"bigwedge",
nullptr, 0, 0 },
120 {
"binom",
nullptr, 2, 0 },
121 {
"bmatrix",
nullptr, 0,
EQ_ENV },
122 {
"bold",
nullptr, 0, 0 },
123 {
"bot",
nullptr, 0, 0 },
124 {
"breve",
nullptr, 1, 0 },
125 {
"buildrel",
nullptr, 0, 0 },
126 {
"bullet",
nullptr, 0, 0 },
127 {
"cap",
nullptr, 0, 0 },
128 {
"cases",
nullptr, 0,
EQ_ENV },
129 {
"ccol",
nullptr, 0, 0 },
130 {
"cdot",
nullptr, 0, 0 },
131 {
"cdots",
nullptr, 0, 0 },
132 {
"check",
nullptr, 1, 0 },
133 {
"chi",
nullptr, 0,
EQ_CASE },
134 {
"choose",
nullptr, 0,
EQ_ATOP },
135 {
"circ",
nullptr, 0, 0 },
136 {
"col",
nullptr, 0, 0 },
137 {
"cong",
nullptr, 0, 0 },
138 {
"coprod",
nullptr, 0, 0 },
139 {
"cos",
nullptr, 0, 0 },
140 {
"cosec",
nullptr, 0, 0 },
141 {
"cosh",
nullptr, 0, 0 },
142 {
"cot",
nullptr, 0, 0 },
143 {
"coth",
nullptr, 0, 0 },
144 {
"cpile",
nullptr, 0, 0 },
145 {
"csc",
nullptr, 0, 0 },
146 {
"cup",
nullptr, 0, 0 },
147 {
"dagger",
nullptr, 0, 0 },
148 {
"dashv",
nullptr, 0, 0 },
149 {
"ddagger",
nullptr, 0, 0 },
150 {
"ddot",
nullptr, 1, 0 },
151 {
"ddots",
nullptr, 0, 0 },
152 {
"def",
nullptr, 0, 0 },
153 {
"deg",
nullptr, 0, 0 },
154 {
"del",
nullptr, 0, 0 },
155 {
"delta",
nullptr, 0,
EQ_CASE },
156 {
"diamond",
nullptr, 0, 0 },
157 {
"dim",
nullptr, 0, 0 },
158 {
"div",
nullptr, 0, 0 },
159 {
"divide",
nullptr, 0, 0 },
160 {
"dline",
nullptr, 0, 0 },
161 {
"dmatrix",
nullptr, 0,
EQ_ENV },
162 {
"dot",
nullptr, 1, 0 },
163 {
"doteq",
nullptr, 0, 0 },
164 {
"dotsaxis",
nullptr, 0, 0 },
165 {
"dotsdiag",
nullptr, 0, 0 },
166 {
"dotslow",
"\\ldots", 0, 0 },
167 {
"dotsvert",
"\\vdots", 0, 0 },
168 {
"downarrow",
nullptr, 0,
EQ_CASE },
169 {
"dsum",
"+", 0, 0 },
170 {
"dyad",
nullptr, 0, 0 },
171 {
"ell",
nullptr, 0, 0 },
172 {
"emptyset",
nullptr, 0, 0 },
173 {
"epsilon",
nullptr, 0,
EQ_CASE },
174 {
"eqalign",
nullptr, 0,
EQ_ENV },
175 {
"equiv",
nullptr, 0, 0 },
176 {
"eta",
nullptr, 0,
EQ_CASE },
177 {
"exarrow",
nullptr, 0, 0 },
178 {
"exist",
"\\exists", 0, 0 },
179 {
"exists",
nullptr, 0, 0 },
180 {
"exp",
nullptr, 0,
EQ_CASE },
181 {
"for",
nullptr, 0, 0 },
182 {
"forall",
nullptr, 0, 0 },
183 {
"from",
"_", 1, 0 },
184 {
"gamma",
nullptr, 0,
EQ_CASE },
185 {
"gcd",
nullptr, 0, 0 },
186 {
"ge",
"\\geq", 0, 0 },
187 {
"geq",
nullptr, 0, 0 },
188 {
"ggg",
nullptr, 0, 0 },
189 {
"grad",
nullptr, 0, 0 },
190 {
"grave",
nullptr, 1, 0 },
191 {
"hat",
"\\widehat", 1, 0 },
192 {
"hbar",
nullptr, 0, 0 },
193 {
"hom",
nullptr, 0, 0 },
194 {
"hookleft",
nullptr, 0, 0 },
195 {
"hookright",
nullptr, 0, 0 },
196 {
"identical",
nullptr, 0, 0 },
197 {
"if",
nullptr, 0, 0 },
198 {
"imag",
nullptr, 0, 0 },
199 {
"image",
nullptr, 0, 0 },
200 {
"imath",
nullptr, 0, 0 },
201 {
"in",
nullptr, 0, 0 },
202 {
"inf",
"\\infty", 0, 0 },
203 {
"infinity",
"\\infty", 0, 0 },
204 {
"infty",
nullptr, 0, 0 },
205 {
"int",
nullptr, 0, 0 },
206 {
"integral",
"\\int", 0, 0 },
207 {
"inter",
"\\bigcap", 0, 0 },
208 {
"iota",
nullptr, 0,
EQ_CASE },
209 {
"iso",
nullptr, 0, 0 },
210 {
"it",
nullptr, 0, 0 },
211 {
"jmath",
nullptr, 0, 0 },
212 {
"kappa",
nullptr, 0,
EQ_CASE },
213 {
"ker",
nullptr, 0, 0 },
214 {
"lambda",
nullptr, 0,
EQ_CASE },
215 {
"land",
nullptr, 0, 0 },
216 {
"langle",
nullptr, 0, 0 },
217 {
"larrow",
"\\leftarrow", 0,
EQ_CASE },
218 {
"lbrace",
nullptr, 0, 0 },
219 {
"lbrack",
"[", 0, 0 },
220 {
"lceil",
nullptr, 0, 0 },
221 {
"lcol",
nullptr, 0, 0 },
222 {
"ldots",
nullptr, 0, 0 },
223 {
"le",
nullptr, 0, 0 },
224 {
"left",
nullptr, 0, 0 },
225 {
"leftarrow",
nullptr, 0,
EQ_CASE },
226 {
"leq",
nullptr, 0, 0 },
227 {
"lfloor",
nullptr, 0, 0 },
228 {
"lg",
nullptr, 0, 0 },
229 {
"lim",
nullptr, 0,
EQ_CASE },
230 {
"line",
"\\vert", 0, 0 },
231 {
"liter",
"\\ell", 0, 0 },
232 {
"lll",
nullptr, 0, 0 },
233 {
"ln",
nullptr, 0, 0 },
234 {
"log",
nullptr, 0, 0 },
235 {
"lor",
"\\vee", 0, 0 },
236 {
"lparen",
"(", 0, 0 },
237 {
"lpile",
nullptr, 0, 0 },
238 {
"lrarrow",
"\\leftrightarrow", 0,
EQ_CASE },
239 {
"lrharpoons",
"\\leftrightharpoons",0, 0 },
240 {
"mapsto",
nullptr, 0, 0 },
241 {
"massert",
"\\dashv", 0, 0 },
242 {
"matrix",
nullptr, 0,
EQ_ENV },
243 {
"max",
nullptr, 0, 0 },
244 {
"mho",
nullptr, 0, 0 },
245 {
"min",
nullptr, 0, 0 },
246 {
"minusplus",
nullptr, 0, 0 },
248 {
"mod",
"\\bmod", 0, 0 },
249 {
"models",
nullptr, 0, 0 },
250 {
"msangle",
nullptr, 0, 0 },
252 {
"nabla",
nullptr, 0, 0 },
253 {
"ne",
nullptr, 0, 0 },
254 {
"nearrow",
nullptr, 0, 0 },
255 {
"neg",
nullptr, 0, 0 },
256 {
"neq",
nullptr, 0, 0 },
257 {
"nequiv",
nullptr, 0, 0 },
258 {
"ni",
nullptr, 0, 0 },
259 {
"not",
nullptr, 0, 0 },
260 {
"notin",
nullptr, 0, 0 },
262 {
"nwarrow",
nullptr, 0, 0 },
263 {
"odiv",
nullptr, 0, 0 },
264 {
"odot",
nullptr, 0, 0 },
265 {
"oint",
nullptr, 0, 0 },
266 {
"omega",
nullptr, 0,
EQ_CASE },
267 {
"omicron",
nullptr, 0,
EQ_CASE },
268 {
"ominus",
nullptr, 0, 0 },
269 {
"oplus",
nullptr, 0, 0 },
270 {
"or ",
nullptr, 0, 0 },
271 {
"oslash",
nullptr, 0, 0 },
272 {
"otimes",
nullptr, 0, 0 },
273 {
"over",
nullptr, 1,
EQ_ATOP },
274 {
"overline",
nullptr, 1, 0 },
275 {
"owns",
"\\ni", 0, 0 },
276 {
"parallel",
nullptr, 0, 0 },
277 {
"partial",
nullptr, 0, 0 },
278 {
"phantom",
nullptr, 0, 0 },
279 {
"phi",
nullptr, 0,
EQ_CASE },
281 {
"pile",
nullptr, 0, 0 },
282 {
"plusminus",
"\\pm", 0, 0 },
283 {
"pmatrix",
nullptr, 0,
EQ_ENV },
284 {
"prec",
nullptr, 0, 0 },
285 {
"prep",
nullptr, 0, 0 },
286 {
"prime",
nullptr, 0, 0 },
287 {
"prod",
nullptr, 0, 0 },
288 {
"propto",
nullptr, 0, 0 },
289 {
"psi",
nullptr, 0,
EQ_CASE },
290 {
"rangle",
nullptr, 0, 0 },
291 {
"rarrow",
"\\rightarrow", 0,
EQ_CASE },
292 {
"rbrace",
"]", 0, 0 },
293 {
"rbrace",
nullptr, 0, 0 },
294 {
"rceil",
nullptr, 0, 0 },
295 {
"rcol",
nullptr, 0, 0 },
296 {
"real",
"\\Re", 0, 0 },
297 {
"reimage",
nullptr, 0, 0 },
298 {
"rel",
nullptr, 0, 0 },
299 {
"rfloor",
nullptr, 0, 0 },
300 {
"rho",
nullptr, 0,
EQ_CASE },
301 {
"right",
nullptr, 0, 0 },
302 {
"rightarrow",
nullptr, 0,
EQ_CASE },
303 {
"rlharpoons",
nullptr, 0, 0 },
304 {
"rm",
nullptr, 0, 0 },
305 {
"root",
"\\sqrt", 1, 0 },
306 {
"rparen",
")", 0, 0 },
307 {
"rpile",
nullptr, 0, 0 },
308 {
"rtangle",
nullptr, 0, 0 },
309 {
"sangle",
nullptr, 0, 0 },
310 {
"scale",
nullptr, 0, 0 },
311 {
"searrow",
nullptr, 0, 0 },
312 {
"sec",
nullptr, 0, 0 },
313 {
"sigma",
nullptr, 0,
EQ_CASE },
314 {
"sim",
nullptr, 0, 0 },
315 {
"simeq",
nullptr, 0, 0 },
316 {
"sin",
nullptr, 0, 0 },
317 {
"sinh",
nullptr, 0, 0 },
318 {
"slash",
nullptr, 0, 0 },
319 {
"smallint",
nullptr, 0, 0 },
320 {
"smallinter",
nullptr, 0, 0 },
321 {
"smalloint",
nullptr, 0, 0 },
322 {
"smallprod",
nullptr, 0, 0 },
323 {
"smallsum",
nullptr, 0, 0 },
324 {
"smallunion",
nullptr, 0, 0 },
325 {
"smcoprod",
nullptr, 0, 0 },
326 {
"sqcap",
nullptr, 0, 0 },
327 {
"sqcup",
nullptr, 0, 0 },
328 {
"sqrt",
nullptr, 1, 0 },
329 {
"sqsubset",
nullptr, 0, 0 },
330 {
"sqsubseteq",
nullptr, 0, 0 },
331 {
"sqsupset",
nullptr, 0, 0 },
332 {
"sqsupseteq",
nullptr, 0, 0 },
333 {
"star",
nullptr, 0, 0 },
334 {
"sub",
"_", 0, 0 },
335 {
"subset",
nullptr, 0, 0 },
336 {
"subseteq",
nullptr, 0, 0 },
337 {
"succ",
nullptr, 0, 0 },
338 {
"sum",
nullptr, 0, 0 },
339 {
"sup",
"^", 0, 0 },
340 {
"superset",
nullptr, 0, 0 },
341 {
"supset",
nullptr, 0, 0 },
342 {
"supseteq",
nullptr, 0, 0 },
343 {
"swarrow",
nullptr, 0, 0 },
344 {
"tan",
nullptr, 0, 0 },
345 {
"tanh",
nullptr, 0, 0 },
346 {
"tau",
nullptr, 0,
EQ_CASE },
347 {
"therefore",
nullptr, 0, 0 },
348 {
"theta",
nullptr, 0,
EQ_CASE },
349 {
"tilde",
"\\widetilde", 1, 0 },
350 {
"times",
nullptr, 0, 0 },
352 {
"top",
nullptr, 0, 0 },
353 {
"triangle",
nullptr, 0, 0 },
354 {
"triangled",
nullptr, 0, 0 },
355 {
"trianglel",
nullptr, 0, 0 },
356 {
"triangler",
nullptr, 0, 0 },
357 {
"triangleu",
nullptr, 0, 0 },
358 {
"udarrow",
"\\updownarrow",0,
EQ_CASE },
359 {
"under",
"\\underline", 1, 0 },
360 {
"underline",
"\\underline", 1, 0 },
361 {
"union",
"\\bigcup", 0, 0 },
362 {
"uparrow",
nullptr, 0,
EQ_CASE },
363 {
"uplus",
nullptr, 0, 0 },
364 {
"upsilon",
nullptr, 0,
EQ_CASE },
365 {
"varepsilon",
nullptr, 0, 0 },
366 {
"varphi",
nullptr, 0, 0 },
367 {
"varpi",
nullptr, 0, 0 },
368 {
"varrho",
nullptr, 0, 0 },
369 {
"varsigma",
nullptr, 0, 0 },
370 {
"vartheta",
nullptr, 0, 0 },
371 {
"varupsilon",
nullptr, 0, 0 },
372 {
"vdash",
nullptr, 0, 0 },
373 {
"vdots",
nullptr, 0, 0 },
374 {
"vec",
nullptr, 1, 0 },
375 {
"vee",
nullptr, 0, 0 },
376 {
"vert",
nullptr, 0, 0 },
377 {
"wedge",
nullptr, 0, 0 },
378 {
"wp",
nullptr, 0, 0 },
380 {
"xor",
nullptr, 0, 0 },
381 {
"zeta",
nullptr, 0,
EQ_CASE }
387 int l = 0, r = eqCount;
388 const hwpeq *
result =
nullptr;
391 const int m = (l + r) / 2;
392 const int k = strcmp(
eq_tbl[
m].key, str);
410 int len = token.length();
417 memcpy(keyword, token.data(), len);
420 if( (token[0] & 0x80) || rtl::isAsciiLowerCase(
static_cast<unsigned char>(token[0])) || token.length() < 2 )
423 bool capital = rtl::isAsciiUpperCase(
424 static_cast<unsigned char>(keyword[1]));
425 for( ptr = keyword + 2; *ptr &&
result; ptr++ )
428 (!capital && rtl::isAsciiUpperCase(
static_cast<unsigned char>(*ptr))) ||
429 (capital && rtl::isAsciiLowerCase(
static_cast<unsigned char>(*ptr))) )
440 if( rtl::isAsciiUpperCase(
static_cast<unsigned char>(*ptr)) )
441 *ptr = sal::static_int_cast<char>(
442 rtl::toAsciiLowerCase(
static_cast<unsigned char>(*ptr)));
456 eq_stack() { strm =
nullptr; };
457 bool state(std::istream
const *s) {
458 if( strm != s) { white.clear(); token.clear(); }
459 return token.getLength() != 0;
465static eq_stack *
stk =
nullptr;
467static void push_token(OString
const &white, OString
const &token, std::istream *strm)
470 assert(
stk->token.getLength() == 0);
482static int next_token(OString &white, OString &token, std::istream *strm)
484 std::istream::int_type
ch = 0;
486 if(
stk->state(strm) ) {
491 return token.getLength();
499 if(
ch == std::istream::traits_type::eof() )
506 white += OStringChar(
static_cast<char>(
ch));
511 if(
ch ==
'\\' ||
ch & 0x80
512 || (
ch != std::istream::traits_type::eof() && rtl::isAsciiAlpha(
ch)) )
515 token += OStringChar(
static_cast<char>(
ch));
519 token += OStringChar(
static_cast<char>(
ch));
521 }
while(
ch != std::istream::traits_type::eof()
522 && (
ch & 0x80 || rtl::isAsciiAlpha(
ch)) ) ;
523 strm->putback(
static_cast<char>(
ch));
527 if( token.equalsIgnoreAsciiCase(
"sub") || token.equalsIgnoreAsciiCase(
"from") ||
528 token.equalsIgnoreAsciiCase(
"sup") || token.equalsIgnoreAsciiCase(
"to") ||
529 token.equalsIgnoreAsciiCase(
"over") || token.equalsIgnoreAsciiCase(
"atop") ||
530 token.equalsIgnoreAsciiCase(
"left") || token.equalsIgnoreAsciiCase(
"right") )
536 if( token ==
"sub" || token ==
"from" )
538 if( token ==
"sup" || token ==
"to" )
544 token += OStringChar(
static_cast<char>(
ch));
548 strm->putback(
static_cast<char>(
ch));
550 else if(
ch != std::istream::traits_type::eof() && rtl::isAsciiDigit(
ch) ) {
552 token += OStringChar(
static_cast<char>(
ch));
554 }
while(
ch != std::istream::traits_type::eof() && rtl::isAsciiDigit(
ch) );
555 strm->putback(
static_cast<char>(
ch));
558 token += OStringChar(
static_cast<char>(
ch));
560 return token.getLength();
565 std::istream::int_type
result;
567 if(
stk->state(strm) ) {
570 result = std::istream::traits_type::to_int_type(
stk->token[0]);
573 std::istream::int_type
ch;
579 outs += OStringChar(
static_cast<char>(
ch));
581 strm->putback(
static_cast<char>(
ch));
601static int eq_word(OString& outs, std::istream *strm,
int status)
603 OString token, white, state;
609 if (token.getLength() <= 0)
614 state += white + token;
617 else if( token ==
"left" ) {
618 state += white + token;
620 state += white + token;
625 state += white + token;
631 state += white + token;
633 if( token[0] ==
'^' )
635 else if( token[0] ==
'_' )
641 int nargs = eq->nargs;
644 if(
ch !=
'{' ) state += OStringChar(
'{');
645 eq_word(state, strm, script_status);
646 if(
ch !=
'{' ) state += OStringChar(
'}');
653 if( (token[0] ==
'^' && status && !(status &
SCRIPT_SUP)) ||
654 (token[0] ==
'_' && status && !(status &
SCRIPT_SUB)) ||
655 "over" == token ||
"atop" == token ||
656 strchr(
"{}#&`", token[0]) ||
657 (!strchr(
"^_", token[0]) && white.getLength()) )
669static bool eq_sentence(OString& outs, std::istream *strm,
const char *end)
672 OString white, token;
673 bool multiline =
false;
676 while(
eq_word(state, strm) ) {
680 state += white + token;
684 if( token ==
"atop" || token ==
"over" )
685 outs += OStringChar(
'{') + state + OStringChar(
'}');
698static char eq2ltxconv(OString& sstr, std::istream *strm,
const char *sentinel)
700 OString white, token;
702 std::istream::int_type
ch;
706 if( sentinel && (
result == 1) && strchr(sentinel, token[0]) )
709 const hwpeq *eq =
nullptr;
711 const bool bUpperFollowingChar = ( (eq->flag &
EQ_CASE)
712 && rtl::isAsciiUpperCase(
static_cast<unsigned char>(token[0])) );
717 token = OString::Concat(
"\\") + eq->key;
720 if (bUpperFollowingChar)
721 token = token.replaceAt(1, 1, token.copy(1, 1).toAsciiUpperCase());
724 if( token[0] ==
'{' ) {
725 sstr += white + token;
727 sstr += OStringChar(
'}');
729 else if( eq && (eq->flag &
EQ_ENV) ) {
731 if( token[0] !=
'{' )
735 if( sstr[sstr.getLength() - 1] !=
'\n' )
739 else if( eq && (eq->flag &
EQ_ATOP) ) {
740 if( sstr.getLength() == 0 )
741 sstr += OStringChar(
'{');
743 int pos = sstr.lastIndexOf(
'}');
745 sstr = sstr.replaceAt(
pos, 1,
" ");
751 if (
ch == std::istream::traits_type::eof() || !
IS_WS(
ch) )
753 sstr += OStringChar(
static_cast<char>(
ch));
759 sstr += OStringChar(
'}');
763 sstr += white + token;
776 std::istringstream tstrm(s);
778 std::istringstream strm((std::string(tstr)));
#define SAL_NEWLINE_STRING
static void make_keyword(char *keyword, std::string_view token)
static int eq_word(OString &outs, std::istream *strm, int script=SCRIPT_NONE)
static bool eq_sentence(OString &outs, std::istream *strm, const char *end=nullptr)
static bool IS_WS(std::istream::int_type ch)
static bool IS_BINARY(std::istream::int_type ch)
static const hwpeq * lookup_eqn(char const *str)
static std::istream::int_type read_white_space(OString &outs, std::istream *strm)
void eq2latex(OString &outs, char const *s)
static int next_token(OString &white, OString &token, std::istream *strm)
static void push_token(OString const &white, OString const &token, std::istream *strm)
static char eq2ltxconv(OString &sstr, std::istream *strm, const char *sentinel)
#define SAL_N_ELEMENTS(arr)