strlib.inc 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547
  1. #include <a_samp>
  2. #if !defined STRLIB_BUFFER_SIZE
  3. #define STRLIB_BUFFER_SIZE 2048
  4. #endif
  5. #if !defined STRLIB_RETURN_SIZE
  6. #define STRLIB_RETURN_SIZE 128
  7. #endif
  8. #if !defined STRLIB_USE_FORMATEX
  9. #if defined __fmt_funcinc
  10. #if !defined FormatSpecifier
  11. #error Please include formatex before strlib.
  12. #endif
  13. #define STRLIB_USE_FORMATEX true
  14. #else
  15. #define STRLIB_USE_FORMATEX false
  16. #endif
  17. #endif
  18. // Used in strtrim (deprecated)
  19. enum trim_edges {
  20. trim_left = 1,
  21. trim_right = 2,
  22. trim_both = trim_left | trim_right
  23. };
  24. // Used in strtrim and strpad
  25. enum string_edges {
  26. edge_left = 1,
  27. edge_right = 2,
  28. edge_both = edge_left | edge_right
  29. };
  30. /*
  31. * Returns a formatted string.
  32. *
  33. * Parameters:
  34. * fmat[] - The format string.
  35. * ... - The format variables.
  36. *
  37. * Returns:
  38. * The formatted string.
  39. */
  40. forward sprintf(const fmat[], {Float, _}:...);
  41. /*
  42. * Get the first character of a string
  43. *
  44. * Parameters:
  45. * string[] - The string.
  46. *
  47. * Returns:
  48. * The first character of the string.
  49. */
  50. forward strgetfirstc(const string[]);
  51. /*
  52. * Get a character from a specific index in a string.
  53. *
  54. * Parameters:
  55. * string[] - The string.
  56. * index - The position in the string.
  57. *
  58. * Returns:
  59. * The character at that index, or '\0' if out of range.
  60. */
  61. forward strgetc(const string[], index);
  62. /*
  63. * Get the size of a string.
  64. *
  65. * Parameters:
  66. * string[] - The string.
  67. *
  68. * Returns:
  69. * The size of the string, in bytes.
  70. */
  71. forward strsize(const string[]);
  72. /*
  73. * Find out if a string is empty.
  74. *
  75. * Parameters:
  76. * string[] - The string.
  77. *
  78. * Returns:
  79. * True if empty, otherwise false.
  80. */
  81. forward bool:isempty(const string[]);
  82. /*
  83. * Compare two strings.
  84. *
  85. * Parameters:
  86. * str1[] - The first string.
  87. * str2[] - The second string.
  88. * ignorecase - Whether to compare them in a case-insensitive manner.
  89. *
  90. * Returns:
  91. * True if equal, otherwise false.
  92. */
  93. forward bool:isequal(const str1[], const str2[], bool:ignorecase = false);
  94. /*
  95. * Split a string by a given delimiter.
  96. *
  97. * Parameters:
  98. * output[][] - A multi-dimensional array that will be filled with substrings.
  99. * input[] - The input string to split.
  100. * delimiter[] - The delimiter to split by. Defaults to ",".
  101. * limit - The max. no. substrings.
  102. * trim - Whether to trim the substrings from whitespace. Defaults to true.
  103. * ignorecase - Whether the search for "delimiter" should be case-insensitive.
  104. * size1 - The size of the 1st dimension of output (otput[this][]). Defaults to sizeof(output).
  105. * size2 - The size of the 2nd dimension of output (otput[][this]). Defaults to sizeof(output[]).
  106. *
  107. * Returns:
  108. * The number of substrings that were copied into the array.
  109. */
  110. forward strexplode(output[][], const input[], const delimiter[] = !",", limit = cellmax, bool:trim = true, bool:ignorecase = false, size1 = sizeof(output), size2 = sizeof(output[]));
  111. /*
  112. * Glue together strings into one.
  113. *
  114. * Parameters:
  115. * glue[] - The string that will be between all other strings.
  116. * output[] - The output string.
  117. * maxlength - The size of "output". Defaults to sizeof(output).
  118. * ...[] - Strings to glue together.
  119. *
  120. * Returns:
  121. * Nothing
  122. */
  123. forward strimplode(const glue[], output[], maxlength = sizeof(output), ...);
  124. /*
  125. * Replace occurrences of the search string with the replacement string.
  126. *
  127. * Parameters:
  128. * string[] - The string to perform the replacing in.
  129. * search[] - The string to look for.
  130. * replacement[] - The string to put instead of "search".
  131. * ignorecase - Whether the search for "search" should be case-insensitive. Defaults to false.
  132. * pos - The position to start at. Defaults to 0 (the beginning).
  133. * limit - Limit the number of replacements. Defaults to -1 (no limit).
  134. * maxlength - The size of "string". Defaults to sizeof(string).
  135. *
  136. * Returns:
  137. * The number of replacements that were made.
  138. */
  139. forward strreplace(string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1, maxlength = sizeof(string));
  140. /*
  141. * Trim whitespace or a specific group of characters from a string.
  142. *
  143. * Parameters:
  144. * string[] - The string to trim.
  145. * chars[] - A string with characters to trim, or all whitespace if empty. Default is all whitespace.
  146. * edge - The edge(s) to trim (edge_left/edge_right/edge_both). Default is edge_both.
  147. *
  148. * Returns:
  149. * Nothing
  150. */
  151. forward strtrim(string[], const chars[] = !"", string_edges:edge = edge_both);
  152. /*
  153. * Pad edge(s) of a string with spaces.
  154. *
  155. * Parameters:
  156. * string[] - The string to pad.
  157. * length - The new length of the string.
  158. * substr[] - The substring to pad with. Defaults to a space (" ").
  159. * edge - The edge(s) to pad (edge_left/edge_right/edge_both). Default is edge_both.
  160. * trim_first - Whether to trim the string before padding.
  161. * trim_chars[] - The chars to trim, defaults is all whitespace.
  162. * maxlength - The size of "string". Defaults to sizeof(string).
  163. * input - Used internally.
  164. */
  165. forward strpad(string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !"", maxlength = sizeof(string), const input[] = !"");
  166. /*
  167. * Wrap a string inside two other strings.
  168. *
  169. * Parameters:
  170. * left[] - The string on the left side.
  171. * string[] - The middle string that will be modified.
  172. * right[] - The string on the right side.
  173. * maxlength - The size of "string". Defaults to sizeof(string).
  174. */
  175. forward strwrap(const left[], string[], const right[], maxlength = sizeof(string));
  176. /*
  177. * Count substrings.
  178. *
  179. * Parameters:
  180. * string[] - The string to search inside.
  181. * sub[] - The string to search for.
  182. * ignorecase - Whether the search should be case-insensitive.
  183. * count_overlapped - Whether to count overlapping strings ("abcabc" in "abcabcabc" will count 2 instead of 1).
  184. *
  185. * Returns:
  186. * The number of occurrences of "sub" in "string".
  187. */
  188. forward strcount(const string[], const sub[], bool:ignorecase = false, bool:count_overlapped = false);
  189. /*
  190. * Read a string from a PAWN string literal.
  191. *
  192. * Parameters:
  193. * output[] - The variable to save into.
  194. * input[] - The string literal.
  195. * pos - The position in "input" to start reading from. Will be modified to the end of the literal.
  196. * maxlength - The size of "output". Defaults to sizeof(output).
  197. *
  198. * Returns:
  199. * true on success, false on error.
  200. */
  201. forward bool:strfromliteral(output[], const input[], &pos = 0, maxlength = sizeof(output));
  202. /*
  203. * Build a PAWN string literal from a given string.
  204. *
  205. * Parameters:
  206. * output[] - The variable to save into.
  207. * substrings[] - The string to build from.
  208. * maxlength - The size of "output". Defaults to sizeof(output).
  209. *
  210. * Returns:
  211. * Nothing
  212. */
  213. forward strtoliteral(output[], const input[], maxlength = sizeof(output));
  214. /*
  215. * Convert an array to a string.
  216. *
  217. * Example: {0x1122, 0x5566} becomes "0000112200005566".
  218. *
  219. * Parameters:
  220. * output[] - The variable to save into.
  221. * input[] - The array to build from.
  222. * inputlength - The size of "input". Defaults to sizeof(input).
  223. * maxlength - The size of "output". Defaults to sizeof(output).
  224. *
  225. * Returns:
  226. * Nothing
  227. */
  228. forward strfrombin(output[], const input[], inputlength = sizeof(input), maxlength = sizeof(output));
  229. /*
  230. * Convert a string to an array.
  231. *
  232. * Example: "0000112200005566" becomes {0x1122, 0x5566}.
  233. *
  234. * Parameters:
  235. * output[] - The variable to save into.
  236. * input[] - The array to build from.
  237. * maxlength - The size of "output". Defaults to sizeof(output).
  238. *
  239. * Returns:
  240. * The length of the output, in cells.
  241. */
  242. forward strtobin(output[], const input[], maxlength = sizeof(output));
  243. /*
  244. * Concatenate one string with a part of another.
  245. *
  246. * Parameters:
  247. * dest[] - The variable to concatenate the other part with.
  248. * source[] - The string to extract from.
  249. * start - The start offset, defaults to 0.
  250. * end - The start offset, defaults to end of string.
  251. * maxlength - The size of "dest". Defaults to sizeof(dest).
  252. */
  253. forward strcatmid(dest[], const source[], start = 0, end = -1, maxlength = sizeof(dest));
  254. /*
  255. * Decode an encoded URL.
  256. *
  257. * Parameters:
  258. * output[] - The output variable.
  259. * input[] - The string to decode.
  260. * maxlength - The size of "output". Defaults to sizeof(output).
  261. */
  262. forward strurldecode(output[], const input[], maxlength = sizeof(output));
  263. /*
  264. * URL encode a string.
  265. *
  266. * Parameters:
  267. * output[] - The output variable.
  268. * input[] - The string to encode.
  269. * maxlength - The size of "output". Defaults to sizeof(output).
  270. * pack - Whether to pack the output. Defaults to false.
  271. */
  272. forward strurlencode(output[], const input[], maxlength = sizeof(output), bool:pack = false);
  273. // Same as above, but output is returned
  274. forward ret_strcatmid(const string[], const source[], start = 0, end = -1);
  275. forward ret_strfrombin(const input[], inputlength = sizeof(input));
  276. forward ret_strimplode(const glue[], ...);
  277. forward ret_strreplace(const string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1);
  278. forward ret_strfromliteral(const input[], &pos = 0);
  279. forward ret_strtoliteral(const input[]);
  280. forward ret_strtrim(const string[], const chars[] = !"", string_edges:edge = edge_both);
  281. forward ret_strpad(const string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !"");
  282. forward ret_strwrap(const left[], const string[], const right[]);
  283. forward ret_strurldecode(const input[]);
  284. forward ret_strurlencode(const input[], bool:pack = false);
  285. // Return from native functions
  286. forward ret_strpack(const source[]);
  287. forward ret_strunpack(const source[]);
  288. forward ret_strcat(const string1[], const string2[]);
  289. forward ret_strmid(const source[], start, end);
  290. forward ret_strins(const string[], const substr[], pos, maxlength = sizeof(string));
  291. forward ret_strdel(const string[], start, end);
  292. forward ret_valstr(value, bool:pack = false);
  293. forward ret_GetPlayerName(playerid, bool:pack = false);
  294. stock
  295. // Used throughout the library
  296. g_StrlibBuffer[2048]
  297. ;
  298. // Workaround for compiler bug
  299. forward _strlib_funcinc();
  300. public _strlib_funcinc() {
  301. new temp[1];
  302. format(!"", 0, !"");
  303. strcat(temp, temp);
  304. strpack(temp, temp);
  305. strunpack(temp, temp);
  306. }
  307. // Internal functions
  308. static stock RedirectArgument(arg, ...) {
  309. #emit LOAD.S.pri 0
  310. #emit ADD.C 12
  311. #emit LOAD.S.alt arg
  312. #emit SHL.C.alt 2
  313. #emit ADD
  314. #emit MOVE.alt
  315. #emit LOAD.S.pri 16
  316. #emit STOR.I
  317. }
  318. static stock CopyArgumentToHeap(arg, bool:pack = false, const argptr[] = "") {
  319. new arg_address, address;
  320. #emit LOAD.S.pri 0
  321. #emit ADD.C 12
  322. #emit LOAD.S.alt arg
  323. #emit SHL.C.alt 2
  324. #emit ADD
  325. #emit LOAD.I
  326. #emit STOR.S.pri arg_address
  327. #emit STOR.S.pri argptr
  328. if (pack) {
  329. new bytes = ((strlen(argptr) + 1 + 3) / 4) * 4;
  330. #emit LCTRL 2
  331. #emit STOR.S.pri address
  332. #emit LOAD.S.alt bytes
  333. #emit ADD
  334. #emit SCTRL 2
  335. //strpack(dest[], const source[], maxlength = sizeof dest)
  336. #emit LOAD.S.pri bytes
  337. #emit SHR.C.pri 2
  338. #emit PUSH.pri
  339. #emit PUSH.S arg_address
  340. #emit PUSH.S address
  341. #emit PUSH.C 12
  342. #emit SYSREQ.C strpack
  343. #emit STACK 16
  344. } else {
  345. new bytes = (strlen(argptr) + 1) * 4;
  346. #emit LCTRL 2
  347. #emit STOR.S.pri address
  348. #emit LOAD.S.alt bytes
  349. #emit ADD
  350. #emit SCTRL 2
  351. //strunpack(dest[], const source[], maxlength = sizeof dest)
  352. #emit LOAD.S.pri bytes
  353. #emit SHR.C.pri 2
  354. #emit PUSH.pri
  355. #emit PUSH.S arg_address
  356. #emit PUSH.S address
  357. #emit PUSH.C 12
  358. #emit SYSREQ.C strunpack
  359. #emit STACK 16
  360. }
  361. #emit LOAD.S.pri 0
  362. #emit ADD.C 12
  363. #emit LOAD.S.alt arg
  364. #emit SHL.C.alt 2
  365. #emit ADD
  366. #emit MOVE.alt
  367. #emit LOAD.S.pri address
  368. #emit STOR.I
  369. return address;
  370. }
  371. static stock RestoreHeapToAddress(address) {
  372. #emit LOAD.S.pri address
  373. #emit SCTRL 2
  374. }
  375. static stock IsOverlapping(const str1[], size1 = sizeof(str1), const str2[], size2 = sizeof(str2)) {
  376. new addr1, addr2;
  377. if (size1 == -1) {
  378. size1 = strsize(str1);
  379. } else {
  380. size1 *= 4;
  381. }
  382. if (size2 == -1) {
  383. size2 = strsize(str2);
  384. } else {
  385. size2 *= 4;
  386. }
  387. #emit LOAD.S.pri str1
  388. #emit STOR.S.pri addr1
  389. #emit LOAD.S.pri str2
  390. #emit STOR.S.pri addr2
  391. if (addr2 <= addr1 < addr2 + size2)
  392. return true;
  393. else if (addr1 <= addr2 < addr1 + size1)
  394. return true;
  395. else
  396. return false;
  397. }
  398. // strlib functions
  399. #define ispacked(%1) \
  400. ((%1)[0] > 255)
  401. stock strgetfirstc(const string[]) {
  402. return ispacked(string) ? string{0} : string[0];
  403. }
  404. stock strgetc(const string[], index) {
  405. if (index < 0)
  406. return '\0';
  407. new len = strlen(string);
  408. if (index >= len)
  409. return '\0';
  410. return ispacked(string) ? string{index} : string[index];
  411. }
  412. stock strsize(const string[]) {
  413. new len = strlen(string);
  414. if (ispacked(string))
  415. return len + 1;
  416. return (len + 1) * 4;
  417. }
  418. stock bool:isempty(const string[]) {
  419. if (ispacked(string))
  420. return string{0} == '\0';
  421. else
  422. return string[0] == '\0';
  423. }
  424. stock bool:isequal(const str1[], const str2[], bool:ignorecase = false) {
  425. new
  426. c1 = (str1[0] > 255) ? str1{0} : str1[0],
  427. c2 = (str2[0] > 255) ? str2{0} : str2[0]
  428. ;
  429. if (!c1 != !c2)
  430. return false;
  431. return !strcmp(str1, str2, ignorecase);
  432. }
  433. stock strimplode(const glue[], output[], maxlength = sizeof(output), ...) {
  434. new args = numargs();
  435. // Null-out "output"
  436. output[0] = '\0';
  437. // Loop the variable arguments (the ones after "maxlength").
  438. for (new arg = 3; arg < args; arg++) {
  439. // If this isn't the first string, append the glue.
  440. if (arg != 3)
  441. strcat(output, glue, maxlength);
  442. // Wrap these in braces or they will be a part of the above if statement (compiler bug)
  443. {
  444. // Get the address of argument no. <arg>
  445. #emit LCTRL 5
  446. #emit ADD.C 12
  447. #emit LOAD.S.alt arg
  448. #emit SHL.C.alt 2
  449. #emit ADD
  450. #emit LOAD.I
  451. // Push the maxlength, arg address, and output address
  452. #emit PUSH.S maxlength
  453. #emit PUSH.pri
  454. #emit PUSH.S output
  455. // Push the argument count
  456. #emit PUSH.C 12
  457. // call strcat
  458. #emit SYSREQ.C strcat
  459. // Restore the stack
  460. #emit STACK 16
  461. }
  462. }
  463. }
  464. stock strexplode(output[][], const input[], const delimiter[] = !",", limit = cellmax, bool:trim = true, bool:ignorecase = false, size1 = sizeof(output), size2 = sizeof(output[])) {
  465. if (!size1 || !size2) {
  466. printf("(strexplode) ERROR: size1 = %d, size2 = %d. Can't be 0.", size1, size2);
  467. return 0;
  468. }
  469. if (isempty(delimiter)) {
  470. print(!"(strexplode) ERROR: delimiter is empty.");
  471. return 0;
  472. }
  473. if (trim) {
  474. new i = -1;
  475. if (ispacked(input)) {
  476. while (input{++i}) {
  477. if (input{i} > ' ') {
  478. i = -1;
  479. break;
  480. }
  481. }
  482. } else {
  483. while (input[++i]) {
  484. if (input[i] > ' ') {
  485. i = -1;
  486. break;
  487. }
  488. }
  489. }
  490. if (i != -1)
  491. return 0;
  492. } else if (isempty(input)) {
  493. return 0;
  494. }
  495. if (limit == 0) {
  496. return 0;
  497. } else if (limit == cellmax) {
  498. limit = 0;
  499. }
  500. new
  501. pos = 0,
  502. next,
  503. bool:packed = ispacked(input),
  504. dlen = strlen(delimiter),
  505. count = 0,
  506. end
  507. ;
  508. while (pos != -1) {
  509. ++count;
  510. if (limit > 0 && count >= limit) {
  511. next = -1;
  512. } else {
  513. next = strfind(input, delimiter, ignorecase, pos);
  514. }
  515. end = (next == -1) ? cellmax : next;
  516. if (trim) {
  517. if (end == cellmax)
  518. end = strlen(input);
  519. if (packed) {
  520. while (0 < input{pos} <= ' ') pos++;
  521. while (end > 0 && input{end - 1} <= ' ') end--;
  522. } else {
  523. while (0 < input[pos] <= ' ') pos++;
  524. while (end > 0 && input[end - 1] <= ' ') end--;
  525. }
  526. }
  527. strmid(output[count - 1], input, pos, end, size2);
  528. if (count >= size1 || next == -1 || (limit < 0 && count >= -limit))
  529. break;
  530. pos = next + dlen;
  531. }
  532. return count;
  533. }
  534. stock strreplace(string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1, maxlength = sizeof(string)) {
  535. // No need to do anything if the limit is 0.
  536. if (limit == 0)
  537. return 0;
  538. new
  539. sublen = strlen(search),
  540. replen = strlen(replacement),
  541. bool:packed = ispacked(string),
  542. maxlen = maxlength,
  543. len = strlen(string),
  544. count = 0
  545. ;
  546. // "maxlen" holds the max string length (not to be confused with "maxlength", which holds the max. array size).
  547. // Since packed strings hold 4 characters per array slot, we multiply "maxlen" by 4.
  548. if (packed)
  549. maxlen *= 4;
  550. // If the length of the substring is 0, we have nothing to look for..
  551. if (!sublen)
  552. return 0;
  553. // In this line we both assign the return value from "strfind" to "pos" then check if it's -1.
  554. while (-1 != (pos = strfind(string, search, ignorecase, pos))) {
  555. // Delete the string we found
  556. strdel(string, pos, pos + sublen);
  557. len -= sublen;
  558. // If there's anything to put as replacement, insert it. Make sure there's enough room first.
  559. if (replen && len + replen < maxlen) {
  560. strins(string, replacement, pos, maxlength);
  561. pos += replen;
  562. len += replen;
  563. }
  564. // Is there a limit of number of replacements, if so, did we break it?
  565. if (limit != -1 && ++count >= limit)
  566. break;
  567. }
  568. return count;
  569. }
  570. stock strtrim(string[], const chars[] = !"", string_edges:edge = edge_both) {
  571. new bool:packed = ispacked(string);
  572. // If "chars" is empty, trim whitespace
  573. if (!strgetfirstc(chars)) {
  574. // Should the left side be trimmed?
  575. if (edge & edge_left) {
  576. new i = 0;
  577. if (packed)
  578. while (0 < string{i} <= ' ') i++;
  579. else
  580. while (0 < string[i] <= ' ') i++;
  581. if (i) {
  582. strdel(string, 0, i);
  583. }
  584. }
  585. // Should the right side be trimmed?
  586. if (edge & edge_right) {
  587. new i = strlen(string);
  588. if (i) {
  589. if (packed) {
  590. while (--i && 0 < string{i} <= ' ') {}
  591. string{i + 1} = '\0';
  592. } else {
  593. while (--i && 0 < string[i] <= ' ') {}
  594. string[i + 1] = '\0';
  595. }
  596. }
  597. }
  598. } else {
  599. // Should the left side be trimmed?
  600. if (edge & edge_left) {
  601. new i = 0, sub[2];
  602. if (packed) {
  603. while ((sub[0] = string{i})) {
  604. if (strfind(chars, sub) == -1)
  605. break;
  606. i++;
  607. }
  608. if (i) {
  609. strdel(string, 0, i);
  610. }
  611. } else {
  612. while ((sub[0] = string[i])) {
  613. if (strfind(chars, sub) == -1)
  614. break;
  615. i++;
  616. }
  617. if (i) strdel(string, 0, i);
  618. }
  619. }
  620. // Should the right side be trimmed?
  621. if (edge & edge_right) {
  622. new i = strlen(string), sub[2];
  623. if (i >= 0) {
  624. if (packed) {
  625. while (i--) {
  626. sub[0] = string{i};
  627. if (strfind(chars, sub) == -1)
  628. break;
  629. }
  630. string{i + 1} = '\0';
  631. } else {
  632. while (i--) {
  633. sub[0] = string[i];
  634. if (strfind(chars, sub) == -1)
  635. break;
  636. }
  637. string[i + 1] = '\0';
  638. }
  639. }
  640. }
  641. }
  642. }
  643. stock strpad(string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !"", maxlength = sizeof(string), const input[] = !"") {
  644. if (trim_first) {
  645. strtrim(string, trim_chars, edge);
  646. }
  647. new
  648. heap,
  649. length_left = 0,
  650. length_right = 0,
  651. len = strlen(string),
  652. sublen = strlen(substr),
  653. bool:packed,
  654. bool:subpacked = ispacked(substr)
  655. ;
  656. if (len > length)
  657. return;
  658. else
  659. length -= len;
  660. // Make "input" a pointer to "string"
  661. #emit LOAD.S.pri string
  662. #emit STOR.S.pri input
  663. // Copy "input" to the heap so it won't be linked to "string" anymore.
  664. heap = CopyArgumentToHeap(7);
  665. string[0] = '\0';
  666. len = 0;
  667. switch (edge) {
  668. case edge_left:
  669. length_left = length;
  670. case edge_right:
  671. length_right = length;
  672. default:
  673. length_left = length / 2, length_right = length - length_left;
  674. }
  675. if (length_left) {
  676. while (len < length_left) {
  677. if (subpacked)
  678. strcat(string, substr, length_left * 4);
  679. else
  680. strcat(string, substr, length_left + 1);
  681. len += sublen;
  682. }
  683. if (subpacked)
  684. string{length_left} = 0;
  685. }
  686. strcat(string, input, maxlength);
  687. if (length_right) {
  688. len = strlen(string);
  689. length_right += len;
  690. packed = ispacked(string);
  691. while (len < length_right) {
  692. if (packed)
  693. strcat(string, substr, length_right / 4 + 1);
  694. else
  695. strcat(string, substr, length_right + 1);
  696. len += sublen;
  697. }
  698. if (packed)
  699. string{length_right + 1} = 0;
  700. }
  701. RestoreHeapToAddress(heap);
  702. }
  703. stock strwrap(const left[], string[], const right[], maxlength = sizeof(string)) {
  704. strins(string, left, 0, maxlength);
  705. strcat(string, right, maxlength);
  706. }
  707. stock strcount(const string[], const sub[], bool:ignorecase = false, bool:count_overlapped = false) {
  708. new
  709. increment = count_overlapped ? 1 : strlen(sub),
  710. pos = -increment,
  711. count = 0
  712. ;
  713. while (-1 != (pos = strfind(string, sub, ignorecase, pos + increment)))
  714. count++;
  715. return count;
  716. }
  717. stock bool:strfromliteral(output[], const input[], &pos = 0, maxlength = sizeof(output)) {
  718. new
  719. length = strlen(input),
  720. c,
  721. outlen = 0,
  722. heap = 0
  723. ;
  724. // No need to do anything else.
  725. if (!length)
  726. return true;
  727. if (IsOverlapping(output, maxlength, input, -1))
  728. heap = CopyArgumentToHeap(1);
  729. output[0] = '\0';
  730. if (input[0] == '"')
  731. pos++;
  732. for (;; pos++) {
  733. if (outlen >= maxlength - 1 || pos >= length)
  734. break;
  735. c = input[pos];
  736. switch (c) {
  737. // String ended
  738. case '"': break;
  739. case '\\': {}
  740. default: {
  741. output[outlen++] = c;
  742. continue;
  743. }
  744. }
  745. // String ends with a backslash - invalid.
  746. if (pos == length - 1)
  747. goto return_false;
  748. // We're after a backslash now, let's see what's there.
  749. c = input[++pos];
  750. switch (c) {
  751. case '"',
  752. '\'',
  753. '\\',
  754. '%': output[outlen++] = c;
  755. case 'a': output[outlen++] = '\a';
  756. case 'b': output[outlen++] = '\b';
  757. case 'e': output[outlen++] = '\e';
  758. case 'f': output[outlen++] = '\f';
  759. case 'r': output[outlen++] = '\r';
  760. case 'n': output[outlen++] = '\n';
  761. case 't': output[outlen++] = '\t';
  762. case 'v': output[outlen++] = '\v';
  763. case 'x': {
  764. new val = 0;
  765. // String ends with "\x" - invalid.
  766. if (c == length - 1)
  767. goto return_false;
  768. while ((c = input[pos + 1])) {
  769. if ('a' <= c <= 'f' || 'A' <= c <= 'F') {
  770. val = (val << 4) + (tolower(c) - 'a' + 10);
  771. } else if ('0' <= c <= '9') {
  772. val = (val << 4) + (c - '0');
  773. } else {
  774. break;
  775. }
  776. pos++;
  777. }
  778. if (c == ';')
  779. pos++;
  780. output[outlen++] = val;
  781. }
  782. case '0' .. '9': {
  783. new val = 0;
  784. while ((c = input[pos])) {
  785. if ('0' <= c <= '9') {
  786. val = val * 10 + (c - '0');
  787. } else {
  788. break;
  789. }
  790. pos++;
  791. }
  792. if (c != ';') pos--;
  793. output[outlen++] = val;
  794. }
  795. default: {
  796. goto return_false;
  797. }
  798. }
  799. }
  800. output[outlen] = '\0';
  801. pos++;
  802. new bool:ret = true;
  803. goto return_true;
  804. return_false:
  805. ret = false;
  806. return_true:
  807. if (heap)
  808. RestoreHeapToAddress(heap);
  809. return ret;
  810. }
  811. stock strtoliteral(output[], const input[], maxlength = sizeof(output)) {
  812. new i, c, outlen, heap = 0;
  813. if (IsOverlapping(output, maxlength, input, -1))
  814. heap = CopyArgumentToHeap(1);
  815. output[outlen++] = '"';
  816. for (i = 0; (c = input[i]); i++) {
  817. if (maxlength - outlen <= 3) {
  818. outlen = min(outlen, maxlength - 2);
  819. break;
  820. }
  821. switch (c) {
  822. case ' ', '!', '#' .. '[', ']', '^' .. '~':
  823. output[outlen++] = c;
  824. case '"': strunpack(output[outlen], !"\\\"", 3), outlen += 2;
  825. case '\a': strunpack(output[outlen], !"\\a" , 3), outlen += 2;
  826. case '\b': strunpack(output[outlen], !"\\b" , 3), outlen += 2;
  827. case '\e': strunpack(output[outlen], !"\\e" , 3), outlen += 2;
  828. case '\f': strunpack(output[outlen], !"\\f" , 3), outlen += 2;
  829. case '\r': strunpack(output[outlen], !"\\r" , 3), outlen += 2;
  830. case '\n': strunpack(output[outlen], !"\\n" , 3), outlen += 2;
  831. case '\t': strunpack(output[outlen], !"\\t" , 3), outlen += 2;
  832. case '\v': strunpack(output[outlen], !"\\v" , 3), outlen += 2;
  833. default: {
  834. if (maxlength - outlen <= 8)
  835. break;
  836. format(output[outlen], 7, "\\x%03x;", c);
  837. outlen += 6;
  838. }
  839. }
  840. }
  841. output[outlen++] = '"';
  842. output[outlen] = '\0';
  843. if (heap)
  844. RestoreHeapToAddress(heap);
  845. }
  846. stock strfrombin(output[], const input[], inputlength = sizeof(input), maxlength = sizeof(output)) {
  847. static const hex_chars[] = "0123456789ABCDEF";
  848. new outlen = 0, heap = 0;
  849. if (IsOverlapping(output, maxlength, input, -1))
  850. heap = CopyArgumentToHeap(1);
  851. for (new i = 0; i < inputlength; i++) {
  852. if (maxlength - outlen <= 7) {
  853. outlen = min(outlen, maxlength - 1);
  854. break;
  855. }
  856. new input_cell = input[i];
  857. output[outlen++] = hex_chars[(input_cell ) >>> 28];
  858. output[outlen++] = hex_chars[(input_cell & 0x0F000000) >>> 24];
  859. output[outlen++] = hex_chars[(input_cell & 0x00F00000) >>> 20];
  860. output[outlen++] = hex_chars[(input_cell & 0x000F0000) >>> 16];
  861. output[outlen++] = hex_chars[(input_cell & 0x0000F000) >>> 12];
  862. output[outlen++] = hex_chars[(input_cell & 0x00000F00) >>> 8];
  863. output[outlen++] = hex_chars[(input_cell & 0x000000F0) >>> 4];
  864. output[outlen++] = hex_chars[(input_cell & 0x0000000F) ];
  865. }
  866. output[outlen] = '\0';
  867. if (heap)
  868. RestoreHeapToAddress(heap);
  869. }
  870. stock strtobin(output[], const input[], maxlength = sizeof(output)) {
  871. new len = strlen(input), outlen = 0, heap = 0;
  872. if (IsOverlapping(output, maxlength, input, -1))
  873. heap = CopyArgumentToHeap(1);
  874. for (new i = 0; i < len;) {
  875. if (outlen >= maxlength || i > len - 8) {
  876. break;
  877. }
  878. new c, out = 0;
  879. #define ADD_OUT(%1) \
  880. c = input[i++]; out |= (('a' <= c <= 'f' || 'A' <= c <= 'F') ? (tolower(c) - 'a' + 10) : (c - '0')) << %1
  881. ADD_OUT(28);
  882. ADD_OUT(24);
  883. ADD_OUT(20);
  884. ADD_OUT(16);
  885. ADD_OUT(12);
  886. ADD_OUT(8);
  887. ADD_OUT(4);
  888. ADD_OUT(0);
  889. #undef ADD_OUT
  890. output[outlen++] = out;
  891. }
  892. if (heap)
  893. RestoreHeapToAddress(heap);
  894. return outlen;
  895. }
  896. stock strurlencode(output[], const input[], maxlength = sizeof(output), bool:pack = false) {
  897. static const hex_chars[] = "0123456789ABCDEF";
  898. new
  899. len = strlen(input),
  900. bool:packed = ispacked(input),
  901. outlen = 0,
  902. heap = 0
  903. ;
  904. if (IsOverlapping(output, maxlength, input, -1))
  905. heap = CopyArgumentToHeap(1, packed);
  906. if (pack)
  907. maxlength *= 4;
  908. for (new i = 0; i < len; i++) {
  909. if (maxlength - outlen <= 1)
  910. break;
  911. new c = packed ? input{i} : input[i];
  912. switch (c) {
  913. case 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_': {
  914. if (pack)
  915. output{outlen++} = c;
  916. else
  917. output[outlen++] = c;
  918. }
  919. case ' ': {
  920. if (pack)
  921. output{outlen++} = '+';
  922. else
  923. output[outlen++] = '+';
  924. }
  925. default: {
  926. if (maxlength - outlen <= 3)
  927. break;
  928. if (pack) {
  929. output{outlen++} = '%';
  930. output{outlen++} = hex_chars[(c & 0xF0) >>> 4];
  931. output{outlen++} = hex_chars[c & 0x0F];
  932. } else {
  933. output[outlen++] = '%';
  934. output[outlen++] = hex_chars[(c & 0xF0) >>> 4];
  935. output[outlen++] = hex_chars[c & 0x0F];
  936. }
  937. }
  938. }
  939. }
  940. if (pack)
  941. output{outlen} = '\0';
  942. else
  943. output[outlen] = '\0';
  944. if (heap)
  945. RestoreHeapToAddress(heap);
  946. }
  947. stock strurldecode(output[], const input[], maxlength = sizeof(output)) {
  948. new prev_pos = 0, pos = 0, inputlen = strlen(input), len, heap = 0;
  949. if (IsOverlapping(output, maxlength, input, -1))
  950. heap = CopyArgumentToHeap(1);
  951. output[0] = '\0';
  952. while (-1 != (pos = strfind(input, "%", _, pos))) {
  953. static str[2];
  954. new c;
  955. if (prev_pos != pos) {
  956. len = strlen(output);
  957. strcatmid(output, input, prev_pos, pos, maxlength);
  958. strreplace(output, "+", " ", _, len, _, maxlength);
  959. }
  960. if (inputlen < pos + 3)
  961. goto func_end;
  962. str[0] = 0;
  963. c = input[pos + 1]; str[0] |= (('a' <= c <= 'f' || 'A' <= c <= 'F') ? (tolower(c) - 'a' + 10) : (c - '0')) << 4;
  964. c = input[pos + 2]; str[0] |= (('a' <= c <= 'f' || 'A' <= c <= 'F') ? (tolower(c) - 'a' + 10) : (c - '0'));
  965. strcat(output, str, maxlength);
  966. prev_pos = (pos += 3);
  967. }
  968. len = strlen(output);
  969. strcatmid(output, input, prev_pos, _, maxlength);
  970. strreplace(output, "+", " ", _, len, _, maxlength);
  971. func_end:
  972. if (heap)
  973. RestoreHeapToAddress(heap);
  974. }
  975. stock strcatmid(dest[], const source[], start = 0, end = -1, maxlength = sizeof(dest)) {
  976. new heap = 0;
  977. if (IsOverlapping(dest, maxlength, source, -1))
  978. heap = CopyArgumentToHeap(1);
  979. if (start == 0 && end == -1) {
  980. strcat(dest, source, maxlength);
  981. } else {
  982. if (end == -1)
  983. end = strlen(source);
  984. if (ispacked(dest)) {
  985. new len = strlen(dest);
  986. if (ispacked(source)) {
  987. strunpack(g_StrlibBuffer, source);
  988. strcat(dest, g_StrlibBuffer[start], min(maxlength, (len + end - start) / 4 + 1));
  989. } else {
  990. strcat(dest, source[start], min(maxlength, (len + end - start) / 4 + 1));
  991. }
  992. dest{len + end - start} = '\0';
  993. } else {
  994. if (ispacked(source)) {
  995. strunpack(g_StrlibBuffer, source);
  996. strcat(dest, g_StrlibBuffer[start], min(maxlength, strlen(dest) + end - start + 1));
  997. } else {
  998. strcat(dest, source[start], min(maxlength, strlen(dest) + end - start + 1));
  999. }
  1000. }
  1001. }
  1002. if (heap)
  1003. RestoreHeapToAddress(heap);
  1004. }
  1005. stock ret_strcatmid(const string[], const source[], start = 0, end = -1) {
  1006. new output[STRLIB_RETURN_SIZE];
  1007. strcat(output, string);
  1008. strcatmid(output, source, start, end);
  1009. return output;
  1010. }
  1011. stock ret_strfrombin(const input[], inputlength = sizeof(input)) {
  1012. new output[STRLIB_RETURN_SIZE];
  1013. strfrombin(output, input, inputlength);
  1014. return output;
  1015. }
  1016. stock ret_strimplode(const glue[], ...) {
  1017. new output[STRLIB_RETURN_SIZE];
  1018. const maxlength = sizeof(output);
  1019. new args = numargs();
  1020. // Loop the variable arguments (the ones after "maxlength").
  1021. for (new arg = 1; arg < args; arg++) {
  1022. // If this isn't the first string, append the glue.
  1023. if (arg != 1)
  1024. strcat(output, glue, maxlength);
  1025. // Wrap these in braces or they will be a part of the above if statement (compiler bug)
  1026. {
  1027. // Get the address of argument no. <arg>
  1028. #emit LCTRL 5
  1029. #emit ADD.C 12
  1030. #emit LOAD.S.alt arg
  1031. #emit SHL.C.alt 2
  1032. #emit ADD
  1033. #emit LOAD.I
  1034. // Push the maxlength, arg address, and output address
  1035. #emit PUSH.C maxlength
  1036. #emit PUSH.pri
  1037. #emit PUSH.ADR output
  1038. // Push the argument count
  1039. #emit PUSH.C 12
  1040. // call strcat
  1041. #emit SYSREQ.C strcat
  1042. // Restore the stack
  1043. #emit STACK 16
  1044. }
  1045. }
  1046. // Fix compiler bug (returning strings in variadic functions)
  1047. #emit LOAD.S.pri 8
  1048. #emit ADD.C 12
  1049. #emit MOVE.alt
  1050. #emit LCTRL 5
  1051. #emit ADD
  1052. #emit LOAD.I
  1053. #emit STOR.S.pri 20
  1054. return output;
  1055. }
  1056. stock ret_strreplace(const string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1) {
  1057. new output[STRLIB_RETURN_SIZE];
  1058. strcat(output, string);
  1059. strreplace(output, search, replacement, ignorecase, pos, limit);
  1060. return output;
  1061. }
  1062. stock ret_strfromliteral(const input[], &pos = 0) {
  1063. new output[STRLIB_RETURN_SIZE];
  1064. strcat(output, input);
  1065. strfromliteral(output, input, pos);
  1066. return output;
  1067. }
  1068. stock ret_strtoliteral(const input[]) {
  1069. new output[STRLIB_RETURN_SIZE];
  1070. strcat(output, input);
  1071. strtoliteral(output, input);
  1072. return output;
  1073. }
  1074. stock ret_strtrim(const string[], const chars[] = !"", string_edges:edge = edge_both) {
  1075. new output[STRLIB_RETURN_SIZE];
  1076. strcat(output, string);
  1077. strtrim(output, chars, edge);
  1078. return output;
  1079. }
  1080. stock ret_strpad(const string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !"") {
  1081. new output[STRLIB_RETURN_SIZE];
  1082. strcat(output, string);
  1083. strpad(output, length, substr, edge, trim_first, trim_chars);
  1084. return output;
  1085. }
  1086. stock ret_strwrap(const left[], const string[], const right[]) {
  1087. new output[STRLIB_RETURN_SIZE];
  1088. strcat(output, left);
  1089. strcat(output, string);
  1090. strcat(output, right);
  1091. return output;
  1092. }
  1093. stock ret_strurldecode(const input[]) {
  1094. new output[STRLIB_RETURN_SIZE];
  1095. strcat(output, input);
  1096. strurldecode(output, input);
  1097. return output;
  1098. }
  1099. stock ret_strurlencode(const input[], bool:pack = false) {
  1100. new output[STRLIB_RETURN_SIZE];
  1101. strcat(output, input);
  1102. strurlencode(output, input, _, pack);
  1103. return output;
  1104. }
  1105. stock ret_strpack(const source[]) {
  1106. new output[STRLIB_RETURN_SIZE];
  1107. strpack(output, source);
  1108. return output;
  1109. }
  1110. stock ret_strunpack(const source[]) {
  1111. new output[STRLIB_RETURN_SIZE];
  1112. strunpack(output, source);
  1113. return output;
  1114. }
  1115. stock ret_strcat(const string1[], const string2[]) {
  1116. new output[STRLIB_RETURN_SIZE];
  1117. strcat(output, string1);
  1118. strcat(output, string2);
  1119. return output;
  1120. }
  1121. stock ret_strmid(const source[], start, end) {
  1122. new output[STRLIB_RETURN_SIZE];
  1123. strmid(output, source, start, end);
  1124. return output;
  1125. }
  1126. stock ret_strins(const string[], const substr[], pos, maxlength = sizeof(string)) {
  1127. new output[STRLIB_RETURN_SIZE];
  1128. strcat(output, string);
  1129. strins(output, substr, pos);
  1130. return output;
  1131. }
  1132. stock ret_strdel(const string[], start, end) {
  1133. new output[STRLIB_RETURN_SIZE];
  1134. strcat(output, string);
  1135. strdel(output, start, end);
  1136. return output;
  1137. }
  1138. stock ret_valstr(value, bool:pack = false) {
  1139. new output[STRLIB_RETURN_SIZE];
  1140. format(output, sizeof(output), "%d", value);
  1141. if (pack)
  1142. strpack(output, output);
  1143. return output;
  1144. }
  1145. stock ret_GetPlayerName(playerid, bool:pack = false) {
  1146. new output[MAX_PLAYER_NAME];
  1147. GetPlayerName(playerid, output, sizeof(output));
  1148. if (pack)
  1149. strpack(output, output);
  1150. return output;
  1151. }
  1152. stock sprintf(const fmat[], {Float, _}:...) {
  1153. static output[STRLIB_RETURN_SIZE], frm_header[3], heap;
  1154. const output_size = sizeof(output);
  1155. if (ispacked(fmat)) {
  1156. heap = CopyArgumentToHeap(0);
  1157. } else {
  1158. heap = 0;
  1159. }{}
  1160. // Store current frame header
  1161. #emit LCTRL 5
  1162. #emit CONST.alt frm_header
  1163. #emit MOVS 12
  1164. // Change the stack pointer to FRM + 12
  1165. #emit ADD.C 12 // pri is FRM (see above)
  1166. #emit SCTRL 4
  1167. // Push sizeof(output)
  1168. #emit PUSH.C output_size
  1169. // Push output
  1170. #emit PUSH.C output
  1171. // Push the argument count
  1172. #emit LOAD.S.pri 8
  1173. #emit ADD.C 8
  1174. #emit PUSH.pri
  1175. #if !STRLIB_USE_FORMATEX
  1176. const formatex = 0; // Dummy used to avoid "unknown symbol" error
  1177. goto do_sysreq;
  1178. #endif
  1179. // Call formatex (unless this was skipped above)
  1180. #emit LCTRL 6
  1181. #emit ADD.C 36
  1182. #emit PUSH.pri
  1183. #emit CONST.pri formatex
  1184. #emit SCTRL 6
  1185. #if !STRLIB_USE_FORMATEX
  1186. do_sysreq:
  1187. #endif
  1188. // Call format (unless formatex was called, in which case this is skipped)
  1189. #emit SYSREQ.C format
  1190. // Restore the stack pointer to FRM
  1191. #emit LCTRL 5
  1192. #emit SCTRL 4
  1193. // Copy back the frame header
  1194. #emit MOVE.alt
  1195. #emit CONST.pri frm_header
  1196. #emit MOVS 12
  1197. // Restore heap if needed
  1198. if (heap) {
  1199. RestoreHeapToAddress(heap);
  1200. }{}
  1201. // IMPORTANT: Fix compiler bug (returning strings in variadic functions)
  1202. #emit LOAD.S.pri 8
  1203. #emit ADD.C 12
  1204. #emit MOVE.alt
  1205. #emit LCTRL 5
  1206. #emit ADD
  1207. #emit LOAD.I
  1208. #emit STOR.S.pri 20 // 16 + (static_args * 4)
  1209. return output;
  1210. // It is actually used, just not by its symbol name
  1211. #pragma unused fmat
  1212. }