PatternMatching.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. #if true
  2. // This portion of code is taken from UniLua: https://github.com/xebecnan/UniLua
  3. //
  4. // Copyright (C) 2013 Sheng Lunan
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
  7. // (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
  8. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
  9. // so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  14. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  15. // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
  16. // OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  17. using System;
  18. using System.Collections.Generic;
  19. using System.Globalization;
  20. using System.Linq;
  21. using System.Text;
  22. using MoonSharp.Interpreter.Execution;
  23. namespace MoonSharp.Interpreter.CoreLib.StringLib
  24. {
  25. static class PatternMatching
  26. {
  27. private const int CAP_UNFINISHED = -1;
  28. private const int CAP_POSITION = -2;
  29. public const int LUA_MAXCAPTURES = 32;
  30. private const char L_ESC = '%';
  31. private const string FLAGS = "-+ #0";
  32. private static readonly char[] SPECIALS;
  33. static PatternMatching()
  34. {
  35. SPECIALS = "^$*+?.([%-".ToCharArray();
  36. }
  37. private static int PosRelative(int pos, int len)
  38. {
  39. if (pos == int.MinValue) return 1;
  40. else if (pos >= 0) return pos;
  41. else if (0 - pos > len) return 0;
  42. else return len - (-pos) + 1;
  43. }
  44. private static int ClassEnd(MatchState ms, int p)
  45. {
  46. switch (ms.Pattern[p++])
  47. {
  48. case L_ESC:
  49. {
  50. if (p == ms.PatternEnd)
  51. throw new ScriptRuntimeException("malformed pattern (ends with '%')");
  52. return p + 1;
  53. }
  54. case '[':
  55. {
  56. if (ms.Pattern[p] == '^') p++;
  57. do
  58. {
  59. if (p == ms.PatternEnd)
  60. throw new ScriptRuntimeException("malformed pattern (missing ']')");
  61. if (ms.Pattern[p++] == L_ESC && p < ms.PatternEnd)
  62. p++; // skip escapes (e.g. `%]')
  63. } while (ms.Pattern[p] != ']');
  64. return p + 1;
  65. }
  66. default: return p;
  67. }
  68. }
  69. private static bool IsXDigit(char c)
  70. {
  71. switch (c)
  72. {
  73. case '0':
  74. case '1':
  75. case '2':
  76. case '3':
  77. case '4':
  78. case '5':
  79. case '6':
  80. case '7':
  81. case '8':
  82. case '9':
  83. case 'a':
  84. case 'b':
  85. case 'c':
  86. case 'd':
  87. case 'e':
  88. case 'f':
  89. case 'A':
  90. case 'B':
  91. case 'C':
  92. case 'D':
  93. case 'E':
  94. case 'F':
  95. return true;
  96. default:
  97. return false;
  98. }
  99. }
  100. private static bool MatchClass(char c, char cl)
  101. {
  102. bool res;
  103. bool reverse = false;
  104. if (char.IsUpper(cl))
  105. {
  106. cl = char.ToLower(cl);
  107. reverse = true;
  108. }
  109. switch (cl)
  110. {
  111. case 'a': res = Char.IsLetter(c); break;
  112. case 'c': res = Char.IsControl(c); break;
  113. case 'd': res = Char.IsDigit(c); break;
  114. case 'g': throw new System.NotImplementedException();
  115. case 'l': res = Char.IsLower(c); break;
  116. case 'p': res = Char.IsPunctuation(c); break;
  117. case 's': res = Char.IsWhiteSpace(c); break;
  118. case 'u': res = Char.IsUpper(c); break;
  119. case 'w': res = Char.IsLetterOrDigit(c); break;
  120. case 'x': res = IsXDigit(c); break;
  121. case 'z': res = (c == '\0'); break; /* deprecated option */
  122. default: return (cl == c);
  123. }
  124. if (reverse) return !res;
  125. else return res;
  126. }
  127. private static bool MatchBreaketClass(MatchState ms, char c, int p, int ec)
  128. {
  129. bool sig = true;
  130. if (ms.Pattern[p + 1] == '^')
  131. {
  132. sig = false;
  133. p++; // skip the `^'
  134. }
  135. while (++p < ec)
  136. {
  137. if (ms.Pattern[p] == L_ESC)
  138. {
  139. p++;
  140. if (MatchClass(c, ms.Pattern[p]))
  141. return sig;
  142. }
  143. else if (ms.Pattern[p + 1] == '-' && (p + 2 < ec))
  144. {
  145. p += 2;
  146. if (ms.Pattern[p - 2] <= c && c <= ms.Pattern[p])
  147. return sig;
  148. }
  149. else if (ms.Pattern[p] == c) return sig;
  150. }
  151. return !sig;
  152. }
  153. private static bool SingleMatch(MatchState ms, char c, int p, int ep)
  154. {
  155. switch (ms.Pattern[p])
  156. {
  157. case '.': return true; // matches any char
  158. case L_ESC: return MatchClass(c, ms.Pattern[p + 1]);
  159. case '[': return MatchBreaketClass(ms, c, p, ep - 1);
  160. default: return ms.Pattern[p] == c;
  161. }
  162. }
  163. private static int MatchBalance(MatchState ms, int s, int p)
  164. {
  165. if (p >= ms.PatternEnd - 1)
  166. throw new ScriptRuntimeException("malformed pattern (missing arguments to '%b')");
  167. if (ms.Src[s] != ms.Pattern[p]) return -1;
  168. else
  169. {
  170. char b = ms.Pattern[p];
  171. char e = ms.Pattern[p + 1];
  172. int count = 1;
  173. while (++s < ms.SrcEnd)
  174. {
  175. if (ms.Src[s] == e)
  176. {
  177. if (--count == 0) return s + 1;
  178. }
  179. else if (ms.Src[s] == b) count++;
  180. }
  181. }
  182. return -1; //string ends out of balance
  183. }
  184. private static int MaxExpand(MatchState ms, int s, int p, int ep)
  185. {
  186. int i = 0; // counts maximum expand for item
  187. while ((s + i) < ms.SrcEnd && SingleMatch(ms, ms.Src[s + i], p, ep))
  188. i++;
  189. // keeps trying to match with the maximum repetitions
  190. while (i >= 0)
  191. {
  192. int res = Match(ms, (s + i), (ep + 1));
  193. if (res >= 0) return res;
  194. i--; // else didn't match; reduce 1 repetition to try again
  195. }
  196. return -1;
  197. }
  198. private static int MinExpand(MatchState ms, int s, int p, int ep)
  199. {
  200. for (; ; )
  201. {
  202. int res = Match(ms, s, ep + 1);
  203. if (res >= 0)
  204. return res;
  205. else if (s < ms.SrcEnd && SingleMatch(ms, ms.Src[s], p, ep))
  206. s++; // try with one more repetition
  207. else return -1;
  208. }
  209. }
  210. private static int CaptureToClose(MatchState ms)
  211. {
  212. int level = ms.Level;
  213. for (level--; level >= 0; level--)
  214. {
  215. if (ms.Capture[level].Len == CAP_UNFINISHED)
  216. return level;
  217. }
  218. throw new ScriptRuntimeException("invalid pattern capture");
  219. }
  220. private static int StartCapture(MatchState ms, int s, int p, int what)
  221. {
  222. int level = ms.Level;
  223. if (level >= LUA_MAXCAPTURES)
  224. throw new ScriptRuntimeException("too many captures");
  225. ms.Capture[level].Init = s;
  226. ms.Capture[level].Len = what;
  227. ms.Level = level + 1;
  228. int res = Match(ms, s, p);
  229. if (res == -1) // match failed?
  230. ms.Level--;
  231. return res;
  232. }
  233. private static int EndCapture(MatchState ms, int s, int p)
  234. {
  235. int l = CaptureToClose(ms);
  236. ms.Capture[l].Len = s - ms.Capture[l].Init; // close capture
  237. int res = Match(ms, s, p);
  238. if (res == -1) // match failed?
  239. ms.Capture[l].Len = CAP_UNFINISHED; // undo capture
  240. return res;
  241. }
  242. private static int CheckCapture(MatchState ms, char l)
  243. {
  244. int i = (int)(l - '1');
  245. if (i < 0 || i >= ms.Level || ms.Capture[i].Len == CAP_UNFINISHED)
  246. throw new ScriptRuntimeException("invalid capture index {0}", i + 1);
  247. return i;
  248. }
  249. private static int MatchCapture(MatchState ms, int s, char l)
  250. {
  251. int i = CheckCapture(ms, l);
  252. int len = ms.Capture[i].Len;
  253. if (ms.SrcEnd - s >= len &&
  254. string.Compare(ms.Src, ms.Capture[i].Init, ms.Src, s, len) == 0)
  255. return s + len;
  256. else
  257. return -1;
  258. }
  259. private static int Match(MatchState ms, int s, int p)
  260. {
  261. init: // using goto's to optimize tail recursion
  262. if (p == ms.PatternEnd)
  263. return s;
  264. switch (ms.Pattern[p])
  265. {
  266. case '(': // start capture
  267. {
  268. if (ms.Pattern[p + 1] == ')') // position capture?
  269. return StartCapture(ms, s, p + 2, CAP_POSITION);
  270. else
  271. return StartCapture(ms, s, p + 1, CAP_UNFINISHED);
  272. }
  273. case ')': // end capture
  274. {
  275. return EndCapture(ms, s, p + 1);
  276. }
  277. case '$':
  278. {
  279. if (p + 1 == ms.PatternEnd) // is the `$' the last char in pattern?
  280. return (s == ms.SrcEnd) ? s : -1; // check end of string
  281. else goto dflt;
  282. }
  283. case L_ESC: // escaped sequences not in the format class[*+?-]?
  284. {
  285. switch (ms.Pattern[p + 1])
  286. {
  287. case 'b': // balanced string?
  288. {
  289. s = MatchBalance(ms, s, p + 2);
  290. if (s == -1) return -1;
  291. p += 4; goto init; // else return match(ms, s, p+4);
  292. }
  293. case 'f': // frontier?
  294. {
  295. p += 2;
  296. if (p >= ms.Pattern.Length || ms.Pattern[p] != '[')
  297. throw new ScriptRuntimeException("missing '[' after '%f' in pattern");
  298. int ep = ClassEnd(ms, p); //points to what is next
  299. char previous = (s == ms.SrcInit) ? '\0' : ms.Src[s - 1];
  300. if (MatchBreaketClass(ms, previous, p, ep - 1) ||
  301. (s < ms.Src.Length && !MatchBreaketClass(ms, ms.Src[s], p, ep - 1))) return -1;
  302. p = ep; goto init; // else return match( ms, s, ep );
  303. }
  304. case '0':
  305. case '1':
  306. case '2':
  307. case '3':
  308. case '4':
  309. case '5':
  310. case '6':
  311. case '7':
  312. case '8':
  313. case '9': // capture results (%0-%9)?
  314. {
  315. s = MatchCapture(ms, s, ms.Pattern[p + 1]);
  316. if (s == -1) return -1;
  317. p += 2; goto init; // else return match(ms, s, p+2);
  318. }
  319. default: goto dflt;
  320. }
  321. }
  322. default:
  323. dflt: // pattern class plus optional suffix
  324. {
  325. int ep = ClassEnd(ms, p);
  326. bool m = s < ms.SrcEnd && SingleMatch(ms, ms.Src[s], p, ep);
  327. if (ep < ms.PatternEnd)
  328. {
  329. switch (ms.Pattern[ep]) //fix gmatch bug patten is [^a]
  330. {
  331. case '?': // optional
  332. {
  333. if (m)
  334. {
  335. int res = Match(ms, s + 1, ep + 1);
  336. if (res != -1)
  337. return res;
  338. }
  339. p = ep + 1; goto init; // else return match(ms, s, ep+1);
  340. }
  341. case '*': // 0 or more repetitions
  342. {
  343. return MaxExpand(ms, s, p, ep);
  344. }
  345. case '+': // 1 or more repetitions
  346. {
  347. return (m ? MaxExpand(ms, s + 1, p, ep) : -1);
  348. }
  349. case '-': // 0 or more repetitions (minimum)
  350. {
  351. return MinExpand(ms, s, p, ep);
  352. }
  353. }
  354. }
  355. if (!m) return -1;
  356. s++; p = ep; goto init; // else return match(ms, s+1, ep);
  357. }
  358. }
  359. }
  360. private static DynValue PushOneCapture(MatchState ms, int i, int start, int end)
  361. {
  362. if (i >= ms.Level)
  363. {
  364. if (i == 0) // ms.Level == 0, too
  365. return DynValue.NewString(ms.Src.Substring(start, end - start));
  366. else
  367. throw new ScriptRuntimeException("invalid capture index");
  368. }
  369. else
  370. {
  371. int l = ms.Capture[i].Len;
  372. if (l == CAP_UNFINISHED)
  373. throw new ScriptRuntimeException("unfinished capture");
  374. if (l == CAP_POSITION)
  375. return DynValue.NewNumber(ms.Capture[i].Init - ms.SrcInit + 1);
  376. else
  377. return DynValue.NewString(ms.Src.Substring(ms.Capture[i].Init, l));
  378. }
  379. }
  380. private static DynValue PushCaptures(MatchState ms, int spos, int epos)
  381. {
  382. int nLevels = (ms.Level == 0 && spos >= 0) ? 1 : ms.Level;
  383. DynValue[] captures = new DynValue[nLevels];
  384. for (int i = 0; i < nLevels; ++i)
  385. captures[i] = PushOneCapture(ms, i, spos, epos);
  386. return DynValue.NewTuple(captures);
  387. }
  388. private static bool NoSpecials(string pattern)
  389. {
  390. return pattern.IndexOfAny(SPECIALS) == -1;
  391. }
  392. private static DynValue StrFindAux(string s, string p, int init, bool plain, bool find)
  393. {
  394. init = PosRelative(init, s.Length);
  395. if (init < 1)
  396. {
  397. init = 1;
  398. }
  399. else if (init > s.Length + 1) // start after string's end?
  400. {
  401. return DynValue.Nil;
  402. }
  403. // explicit request or no special characters?
  404. if (find && (plain || NoSpecials(p)))
  405. {
  406. // do a plain search
  407. int pos = s.IndexOf(p, init - 1);
  408. if (pos >= 0)
  409. {
  410. return DynValue.NewTuple(
  411. DynValue.NewNumber(pos + 1),
  412. DynValue.NewNumber(pos + p.Length));
  413. }
  414. }
  415. else
  416. {
  417. int s1 = init - 1;
  418. int ppos = 0;
  419. bool anchor = p[ppos] == '^';
  420. if (anchor)
  421. ppos++; // skip anchor character
  422. MatchState ms = new MatchState();
  423. ms.Src = s;
  424. ms.SrcInit = s1;
  425. ms.SrcEnd = s.Length;
  426. ms.Pattern = p;
  427. ms.PatternEnd = p.Length;
  428. do
  429. {
  430. ms.Level = 0;
  431. int res = Match(ms, s1, ppos);
  432. if (res != -1)
  433. {
  434. if (find)
  435. {
  436. return DynValue.NewTupleNested(
  437. DynValue.NewNumber(s1 + 1),
  438. DynValue.NewNumber(res),
  439. PushCaptures(ms, -1, 0));
  440. }
  441. else return PushCaptures(ms, s1, res);
  442. }
  443. } while (s1++ < ms.SrcEnd && !anchor);
  444. }
  445. return DynValue.Nil;
  446. }
  447. public static DynValue Str_Find(string s, string p, int init, bool plain)
  448. {
  449. return StrFindAux(s, p, init, plain, true);
  450. }
  451. private static DynValue GmatchAux(ScriptExecutionContext executionContext, CallbackArguments args)
  452. {
  453. DynValue v_idx = GetClosure(executionContext).Get("idx");
  454. DynValue v_src = GetClosure(executionContext).Get("src");
  455. DynValue v_pattern = GetClosure(executionContext).Get("pattern");
  456. MatchState ms = new MatchState();
  457. string src = v_src.String;
  458. string pattern = v_pattern.String;
  459. ms.Src = src;
  460. ms.SrcInit = 0;
  461. ms.SrcEnd = src.Length;
  462. ms.Pattern = pattern;
  463. ms.PatternEnd = pattern.Length;
  464. for (int s = (int)v_idx.Number; s <= ms.SrcEnd; s++)
  465. {
  466. ms.Level = 0;
  467. int e = Match(ms, s, 0);
  468. if (e != -1)
  469. {
  470. int newStart = (e == 0) ? e + 1 : e;
  471. GetClosure(executionContext).Set("idx", DynValue.NewNumber(newStart));
  472. return PushCaptures(ms, s, e);
  473. }
  474. }
  475. return DynValue.Nil;
  476. }
  477. private static Table GetClosure(ScriptExecutionContext executionContext)
  478. {
  479. return executionContext.AdditionalData as Table;
  480. }
  481. public static DynValue GMatch(Script script, string src, string pattern)
  482. {
  483. DynValue aux = DynValue.NewCallback(GmatchAux);
  484. var t = new Table(script);
  485. t.Set("idx", DynValue.NewNumber(0));
  486. t.Set("src", DynValue.NewString(src));
  487. t.Set("pattern", DynValue.NewString(pattern));
  488. aux.Callback.AdditionalData = t;
  489. return aux;
  490. }
  491. public static DynValue Match(string s, string p, int init)
  492. {
  493. return StrFindAux(s, p, init, false, false);
  494. }
  495. private static void Add_S(MatchState ms, StringBuilder b, int s, int e, DynValue repl)
  496. {
  497. string news = repl.CastToString();
  498. for (int i = 0; i < news.Length; i++)
  499. {
  500. if (news[i] != L_ESC)
  501. b.Append(news[i]);
  502. else
  503. {
  504. i++; /* skip ESC */
  505. if (!Char.IsDigit((news[i])))
  506. b.Append(news[i]);
  507. else if (news[i] == '0')
  508. b.Append(ms.Src.Substring(s, (e - s)));
  509. else
  510. {
  511. var v = PushOneCapture(ms, news[i] - '1', s, e);
  512. b.Append(v.CastToString()); /* add capture to accumulated result */
  513. }
  514. }
  515. }
  516. }
  517. private static void Add_Value(ScriptExecutionContext executionContext, MatchState ms, StringBuilder b, int s, int e, DynValue repl)
  518. {
  519. var result = DynValue.Nil;
  520. switch (repl.Type)
  521. {
  522. case DataType.Number:
  523. case DataType.String:
  524. {
  525. Add_S(ms, b, s, e, repl);
  526. return;
  527. }
  528. case DataType.Function:
  529. {
  530. DynValue tuple = PushCaptures(ms, s, e);
  531. result = repl.Function.Call(tuple.Tuple);
  532. break;
  533. }
  534. case DataType.ClrFunction:
  535. {
  536. DynValue tuple = PushCaptures(ms, s, e);
  537. result = repl.Callback.Invoke(executionContext, tuple.Tuple);
  538. if (result.Type == DataType.TailCallRequest || result.Type == DataType.YieldRequest)
  539. throw new ScriptRuntimeException("the function passed cannot be called directly as it contains a tail or yield request. wrap in a script function instead.");
  540. break;
  541. }
  542. case DataType.Table:
  543. {
  544. var v = PushOneCapture(ms, 0, s, e);
  545. result = repl.Table.Get(v);
  546. break;
  547. }
  548. }
  549. if (result.CastToBool() == false)
  550. {
  551. b.Append(ms.Src.Substring(s, (e - s))); /* keep original text */
  552. }
  553. else if (result.Type != DataType.String && result.Type != DataType.Number)
  554. throw new ScriptRuntimeException("invalid replacement value (a %s)", result.Type.ToLuaTypeString());
  555. else
  556. b.Append(result.CastToString());
  557. }
  558. public static DynValue Str_Gsub(ScriptExecutionContext executionContext, string src, string p, DynValue repl, int? max_s_n)
  559. {
  560. int srcl = src.Length;
  561. DataType tr = repl.Type;
  562. int max_s = max_s_n ?? srcl + 1;
  563. int anchor = 0;
  564. if (p[0] == '^')
  565. {
  566. p = p.Substring(1);
  567. anchor = 1;
  568. }
  569. int n = 0;
  570. MatchState ms = new MatchState();
  571. StringBuilder b = new StringBuilder(srcl);
  572. if (tr != DataType.Number && tr != DataType.String && tr != DataType.Function && tr != DataType.Table && tr != DataType.ClrFunction)
  573. throw new ScriptRuntimeException("string/function/table expected");
  574. ms.Src = src;
  575. ms.SrcInit = 0;
  576. ms.SrcEnd = srcl;
  577. ms.Pattern = p;
  578. ms.PatternEnd = p.Length;
  579. int s = 0;
  580. while (n < max_s)
  581. {
  582. ms.Level = 0;
  583. int e = Match(ms, s, 0);
  584. if (e != -1)
  585. {
  586. n++;
  587. Add_Value(executionContext, ms, b, s, e, repl);
  588. }
  589. if ((e != -1) && e > s) /* non empty match? */
  590. s = e; /* skip it */
  591. else if (s < ms.SrcEnd)
  592. {
  593. char c = src[s];
  594. ++s;
  595. b.Append(c);
  596. }
  597. else break;
  598. if (anchor != 0) break;
  599. }
  600. b.Append(src.Substring(s, ms.SrcEnd - s));
  601. return DynValue.NewTuple(
  602. DynValue.NewString(b.ToString()),
  603. DynValue.NewNumber(n)
  604. );
  605. }
  606. private static int ScanFormat(string format, int s, out string form)
  607. {
  608. int p = s;
  609. // skip flags
  610. while (p < format.Length && format[p] != '\0' && FLAGS.IndexOf(format[p]) != -1)
  611. p++;
  612. if (p - s > FLAGS.Length)
  613. throw new ScriptRuntimeException("invalid format (repeat flags)");
  614. if (Char.IsDigit(format[p])) p++; // skip width
  615. if (Char.IsDigit(format[p])) p++; // (2 digits at most)
  616. if (format[p] == '.')
  617. {
  618. p++;
  619. if (Char.IsDigit(format[p])) p++; // skip precision
  620. if (Char.IsDigit(format[p])) p++; // (2 digits at most)
  621. }
  622. if (Char.IsDigit(format[p]))
  623. throw new ScriptRuntimeException("invalid format (width of precision too long)");
  624. form = "%" + format.Substring(s, (p - s + 1));
  625. return p;
  626. }
  627. internal static string Str_Format(ScriptExecutionContext executionContext, CallbackArguments args)
  628. {
  629. const string FUNCNAME = "format";
  630. string format = args.AsType(0, FUNCNAME, DataType.String, false).String;
  631. StringBuilder sb = new StringBuilder();
  632. int argidx = 0;
  633. int top = args.Count;
  634. int s = 0;
  635. int e = format.Length;
  636. while (s < e)
  637. {
  638. if (format[s] != L_ESC)
  639. {
  640. sb.Append(format[s++]);
  641. continue;
  642. }
  643. if (format[++s] == L_ESC)
  644. {
  645. sb.Append(format[s++]);
  646. continue;
  647. }
  648. ++argidx;
  649. string form;
  650. s = ScanFormat(format, s, out form);
  651. switch (format[s++]) // TODO: properly handle form
  652. {
  653. case 'c':
  654. {
  655. sb.Append((char)args.AsInt(argidx, FUNCNAME));
  656. break;
  657. }
  658. case 'd':
  659. case 'i':
  660. {
  661. int n = args.AsInt(argidx, FUNCNAME);
  662. sb.Append(n.ToString(CultureInfo.InvariantCulture));
  663. break;
  664. }
  665. case 'u':
  666. {
  667. int n = args.AsInt(argidx, FUNCNAME);
  668. if (n < 0) throw ScriptRuntimeException.BadArgumentNoNegativeNumbers(argidx, FUNCNAME);
  669. sb.Append(n.ToString(CultureInfo.InvariantCulture));
  670. break;
  671. }
  672. case 'o':
  673. {
  674. int n = args.AsInt(argidx, FUNCNAME);
  675. if (n < 0) throw ScriptRuntimeException.BadArgumentNoNegativeNumbers(argidx, FUNCNAME);
  676. sb.Append(Convert.ToString(n, 8));
  677. break;
  678. }
  679. case 'x':
  680. {
  681. int n = args.AsInt(argidx, FUNCNAME);
  682. if (n < 0) throw ScriptRuntimeException.BadArgumentNoNegativeNumbers(argidx, FUNCNAME);
  683. // sb.Append( string.Format("{0:x}", n) );
  684. sb.AppendFormat("{0:x}", n);
  685. break;
  686. }
  687. case 'X':
  688. {
  689. int n = args.AsInt(argidx, FUNCNAME);
  690. if (n < 0) throw ScriptRuntimeException.BadArgumentNoNegativeNumbers(argidx, FUNCNAME);
  691. // sb.Append( string.Format("{0:X}", n) );
  692. sb.AppendFormat("{0:X}", n);
  693. break;
  694. }
  695. case 'e':
  696. case 'E':
  697. {
  698. sb.AppendFormat("{0:E}", args.AsType(argidx, FUNCNAME, DataType.Number).Number);
  699. break;
  700. }
  701. case 'f':
  702. {
  703. sb.AppendFormat("{0:F}", args.AsType(argidx, FUNCNAME, DataType.Number).Number);
  704. break;
  705. }
  706. #if LUA_USE_AFORMAT
  707. case 'a': case 'A':
  708. #endif
  709. case 'g':
  710. case 'G':
  711. {
  712. sb.AppendFormat("{0:G}", args.AsType(argidx, FUNCNAME, DataType.Number).Number);
  713. break;
  714. }
  715. case 'q':
  716. {
  717. AddQuoted(sb, args.AsStringUsingMeta(executionContext, argidx, FUNCNAME));
  718. break;
  719. }
  720. case 's':
  721. {
  722. sb.Append(args.AsStringUsingMeta(executionContext, argidx, FUNCNAME));
  723. break;
  724. }
  725. default: // also treat cases `pnLlh'
  726. {
  727. throw new ScriptRuntimeException("invalid option '{0}' to 'format'",
  728. format[s - 1]);
  729. }
  730. }
  731. }
  732. return sb.ToString();
  733. }
  734. private static void AddQuoted(StringBuilder sb, string s)
  735. {
  736. sb.Append('"');
  737. for (var i = 0; i < s.Length; ++i)
  738. {
  739. var c = s[i];
  740. if (c == '"' || c == '\\' || c == '\n' || c == '\r')
  741. {
  742. sb.Append('\\').Append(c);
  743. }
  744. else if (c == '\0' || Char.IsControl(c))
  745. {
  746. if (i + 1 >= s.Length || !Char.IsDigit(s[i + 1]))
  747. {
  748. sb.AppendFormat("\\{0:D}", (int)c);
  749. }
  750. else
  751. {
  752. sb.AppendFormat("\\{0:D3}", (int)c);
  753. }
  754. }
  755. else
  756. {
  757. sb.Append(c);
  758. }
  759. }
  760. sb.Append('"');
  761. }
  762. }
  763. }
  764. #endif