Newer
Older
dub_jkp / source / dub / internal / vibecompat / data / utils.d
@Sebastian Wilzbach Sebastian Wilzbach on 23 Feb 2017 18 KB Remove all trailing whitespace
  1. /**
  2. Utility functions for data serialization
  3.  
  4. Copyright: © 2012 rejectedsoftware e.K.
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Sönke Ludwig
  7. */
  8. module dub.internal.vibecompat.data.utils;
  9.  
  10. version (Have_vibe_d_data) {}
  11. else:
  12.  
  13. public import std.traits;
  14.  
  15. /**
  16. Checks if given type is a getter function type
  17.  
  18. Returns: `true` if argument is a getter
  19. */
  20. template isPropertyGetter(T...)
  21. if (T.length == 1)
  22. {
  23. import std.traits : functionAttributes, FunctionAttribute, ReturnType,
  24. isSomeFunction;
  25. static if (isSomeFunction!(T[0])) {
  26. enum isPropertyGetter =
  27. (functionAttributes!(T[0]) & FunctionAttribute.property) != 0
  28. && !is(ReturnType!T == void);
  29. }
  30. else
  31. enum isPropertyGetter = false;
  32. }
  33.  
  34. ///
  35. unittest
  36. {
  37. interface Test
  38. {
  39. @property int getter();
  40. @property void setter(int);
  41. int simple();
  42. }
  43.  
  44. static assert(isPropertyGetter!(typeof(&Test.getter)));
  45. static assert(!isPropertyGetter!(typeof(&Test.setter)));
  46. static assert(!isPropertyGetter!(typeof(&Test.simple)));
  47. static assert(!isPropertyGetter!int);
  48. }
  49.  
  50. /**
  51. Checks if given type is a setter function type
  52.  
  53. Returns: `true` if argument is a setter
  54. */
  55. template isPropertySetter(T...)
  56. if (T.length == 1)
  57. {
  58. import std.traits : functionAttributes, FunctionAttribute, ReturnType,
  59. isSomeFunction;
  60.  
  61. static if (isSomeFunction!(T[0])) {
  62. enum isPropertySetter =
  63. (functionAttributes!(T) & FunctionAttribute.property) != 0
  64. && is(ReturnType!(T[0]) == void);
  65. }
  66. else
  67. enum isPropertySetter = false;
  68. }
  69.  
  70. ///
  71. unittest
  72. {
  73. interface Test
  74. {
  75. @property int getter();
  76. @property void setter(int);
  77. int simple();
  78. }
  79.  
  80. static assert(isPropertySetter!(typeof(&Test.setter)));
  81. static assert(!isPropertySetter!(typeof(&Test.getter)));
  82. static assert(!isPropertySetter!(typeof(&Test.simple)));
  83. static assert(!isPropertySetter!int);
  84. }
  85.  
  86. /**
  87. Deduces single base interface for a type. Multiple interfaces
  88. will result in compile-time error.
  89.  
  90. Params:
  91. T = interface or class type
  92.  
  93. Returns:
  94. T if it is an interface. If T is a class, interface it implements.
  95. */
  96. template baseInterface(T)
  97. if (is(T == interface) || is(T == class))
  98. {
  99. import std.traits : InterfacesTuple;
  100.  
  101. static if (is(T == interface)) {
  102. alias baseInterface = T;
  103. }
  104. else
  105. {
  106. alias Ifaces = InterfacesTuple!T;
  107. static assert (
  108. Ifaces.length == 1,
  109. "Type must be either provided as an interface or implement only one interface"
  110. );
  111. alias baseInterface = Ifaces[0];
  112. }
  113. }
  114.  
  115. ///
  116. unittest
  117. {
  118. interface I1 { }
  119. class A : I1 { }
  120. interface I2 { }
  121. class B : I1, I2 { }
  122.  
  123. static assert (is(baseInterface!I1 == I1));
  124. static assert (is(baseInterface!A == I1));
  125. static assert (!is(typeof(baseInterface!B)));
  126. }
  127.  
  128.  
  129. /**
  130. Determins if a member is a public, non-static data field.
  131. */
  132. template isRWPlainField(T, string M)
  133. {
  134. static if (!isRWField!(T, M)) enum isRWPlainField = false;
  135. else {
  136. //pragma(msg, T.stringof~"."~M~":"~typeof(__traits(getMember, T, M)).stringof);
  137. enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M)));
  138. }
  139. }
  140.  
  141. /**
  142. Determines if a member is a public, non-static, de-facto data field.
  143.  
  144. In addition to plain data fields, R/W properties are also accepted.
  145. */
  146. template isRWField(T, string M)
  147. {
  148. import std.traits;
  149. import std.typetuple;
  150.  
  151. static void testAssign()() {
  152. T t = void;
  153. __traits(getMember, t, M) = __traits(getMember, t, M);
  154. }
  155.  
  156. // reject type aliases
  157. static if (is(TypeTuple!(__traits(getMember, T, M)))) enum isRWField = false;
  158. // reject non-public members
  159. else static if (!isPublicMember!(T, M)) enum isRWField = false;
  160. // reject static members
  161. else static if (!isNonStaticMember!(T, M)) enum isRWField = false;
  162. // reject non-typed members
  163. else static if (!is(typeof(__traits(getMember, T, M)))) enum isRWField = false;
  164. // reject void typed members (includes templates)
  165. else static if (is(typeof(__traits(getMember, T, M)) == void)) enum isRWField = false;
  166. // reject non-assignable members
  167. else static if (!__traits(compiles, testAssign!()())) enum isRWField = false;
  168. else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) {
  169. // If M is a function, reject if not @property or returns by ref
  170. private enum FA = functionAttributes!(__traits(getMember, T, M));
  171. enum isRWField = (FA & FunctionAttribute.property) != 0;
  172. } else {
  173. enum isRWField = true;
  174. }
  175. }
  176.  
  177. unittest {
  178. import std.algorithm;
  179.  
  180. struct S {
  181. alias a = int; // alias
  182. int i; // plain RW field
  183. enum j = 42; // manifest constant
  184. static int k = 42; // static field
  185. private int privateJ; // private RW field
  186.  
  187. this(Args...)(Args args) {}
  188.  
  189. // read-write property (OK)
  190. @property int p1() { return privateJ; }
  191. @property void p1(int j) { privateJ = j; }
  192. // read-only property (NO)
  193. @property int p2() { return privateJ; }
  194. // write-only property (NO)
  195. @property void p3(int value) { privateJ = value; }
  196. // ref returning property (OK)
  197. @property ref int p4() { return i; }
  198. // parameter-less template property (OK)
  199. @property ref int p5()() { return i; }
  200. // not treated as a property by DMD, so not a field
  201. @property int p6()() { return privateJ; }
  202. @property void p6(int j)() { privateJ = j; }
  203.  
  204. static @property int p7() { return k; }
  205. static @property void p7(int value) { k = value; }
  206.  
  207. ref int f1() { return i; } // ref returning function (no field)
  208.  
  209. int f2(Args...)(Args args) { return i; }
  210.  
  211. ref int f3(Args...)(Args args) { return i; }
  212.  
  213. void someMethod() {}
  214.  
  215. ref int someTempl()() { return i; }
  216. }
  217.  
  218. enum plainFields = ["i"];
  219. enum fields = ["i", "p1", "p4", "p5"];
  220.  
  221. foreach (mem; __traits(allMembers, S)) {
  222. static if (isRWField!(S, mem)) static assert(fields.canFind(mem), mem~" detected as field.");
  223. else static assert(!fields.canFind(mem), mem~" not detected as field.");
  224.  
  225. static if (isRWPlainField!(S, mem)) static assert(plainFields.canFind(mem), mem~" not detected as plain field.");
  226. else static assert(!plainFields.canFind(mem), mem~" not detected as plain field.");
  227. }
  228. }
  229.  
  230. package T Tgen(T)(){ return T.init; }
  231.  
  232.  
  233. /**
  234. Tests if the protection of a member is public.
  235. */
  236. template isPublicMember(T, string M)
  237. {
  238. import std.algorithm, std.typetuple : TypeTuple;
  239.  
  240. static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false;
  241. else {
  242. alias MEM = TypeTuple!(__traits(getMember, T, M));
  243. enum _prot = __traits(getProtection, MEM);
  244. enum isPublicMember = _prot == "public" || _prot == "export";
  245. }
  246. }
  247.  
  248. unittest {
  249. class C {
  250. int a;
  251. export int b;
  252. protected int c;
  253. private int d;
  254. package int e;
  255. void f() {}
  256. static void g() {}
  257. private void h() {}
  258. private static void i() {}
  259. }
  260.  
  261. static assert (isPublicMember!(C, "a"));
  262. static assert (isPublicMember!(C, "b"));
  263. static assert (!isPublicMember!(C, "c"));
  264. static assert (!isPublicMember!(C, "d"));
  265. static assert (!isPublicMember!(C, "e"));
  266. static assert (isPublicMember!(C, "f"));
  267. static assert (isPublicMember!(C, "g"));
  268. static assert (!isPublicMember!(C, "h"));
  269. static assert (!isPublicMember!(C, "i"));
  270.  
  271. struct S {
  272. int a;
  273. export int b;
  274. private int d;
  275. package int e;
  276. }
  277. static assert (isPublicMember!(S, "a"));
  278. static assert (isPublicMember!(S, "b"));
  279. static assert (!isPublicMember!(S, "d"));
  280. static assert (!isPublicMember!(S, "e"));
  281.  
  282. S s;
  283. s.a = 21;
  284. assert(s.a == 21);
  285. }
  286.  
  287. /**
  288. Tests if a member requires $(D this) to be used.
  289. */
  290. template isNonStaticMember(T, string M)
  291. {
  292. import std.typetuple;
  293. import std.traits;
  294.  
  295. alias MF = TypeTuple!(__traits(getMember, T, M));
  296. static if (M.length == 0) {
  297. enum isNonStaticMember = false;
  298. } else static if (anySatisfy!(isSomeFunction, MF)) {
  299. enum isNonStaticMember = !__traits(isStaticFunction, MF);
  300. } else {
  301. enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
  302. }
  303. }
  304.  
  305. unittest { // normal fields
  306. struct S {
  307. int a;
  308. static int b;
  309. enum c = 42;
  310. void f();
  311. static void g();
  312. ref int h() { return a; }
  313. static ref int i() { return b; }
  314. }
  315. static assert(isNonStaticMember!(S, "a"));
  316. static assert(!isNonStaticMember!(S, "b"));
  317. static assert(!isNonStaticMember!(S, "c"));
  318. static assert(isNonStaticMember!(S, "f"));
  319. static assert(!isNonStaticMember!(S, "g"));
  320. static assert(isNonStaticMember!(S, "h"));
  321. static assert(!isNonStaticMember!(S, "i"));
  322. }
  323.  
  324. unittest { // tuple fields
  325. struct S(T...) {
  326. T a;
  327. static T b;
  328. }
  329.  
  330. alias T = S!(int, float);
  331. auto p = T.b;
  332. static assert(isNonStaticMember!(T, "a"));
  333. static assert(!isNonStaticMember!(T, "b"));
  334.  
  335. alias U = S!();
  336. static assert(!isNonStaticMember!(U, "a"));
  337. static assert(!isNonStaticMember!(U, "b"));
  338. }
  339.  
  340.  
  341. /**
  342. Tests if a Group of types is implicitly convertible to a Group of target types.
  343. */
  344. bool areConvertibleTo(alias TYPES, alias TARGET_TYPES)()
  345. if (isGroup!TYPES && isGroup!TARGET_TYPES)
  346. {
  347. static assert(TYPES.expand.length == TARGET_TYPES.expand.length);
  348. foreach (i, V; TYPES.expand)
  349. if (!is(V : TARGET_TYPES.expand[i]))
  350. return false;
  351. return true;
  352. }
  353.  
  354. /// Test if the type $(D DG) is a correct delegate for an opApply where the
  355. /// key/index is of type $(D TKEY) and the value of type $(D TVALUE).
  356. template isOpApplyDg(DG, TKEY, TVALUE) {
  357. import std.traits;
  358. static if (is(DG == delegate) && is(ReturnType!DG : int)) {
  359. private alias PTT = ParameterTypeTuple!(DG);
  360. private alias PSCT = ParameterStorageClassTuple!(DG);
  361. private alias STC = ParameterStorageClass;
  362. // Just a value
  363. static if (PTT.length == 1) {
  364. enum isOpApplyDg = (is(PTT[0] == TVALUE) && PSCT[0] == STC.ref_);
  365. } else static if (PTT.length == 2) {
  366. enum isOpApplyDg = (is(PTT[0] == TKEY) && PSCT[0] == STC.ref_)
  367. && (is(PTT[1] == TKEY) && PSCT[1] == STC.ref_);
  368. } else
  369. enum isOpApplyDg = false;
  370. } else {
  371. enum isOpApplyDg = false;
  372. }
  373. }
  374.  
  375. /**
  376. TypeTuple which does not auto-expand.
  377.  
  378. Useful when you need
  379. to multiple several type tuples as different template argument
  380. list parameters, without merging those.
  381. */
  382. template Group(T...)
  383. {
  384. alias expand = T;
  385. }
  386.  
  387. ///
  388. unittest
  389. {
  390. alias group = Group!(int, double, string);
  391. static assert (!is(typeof(group.length)));
  392. static assert (group.expand.length == 3);
  393. static assert (is(group.expand[1] == double));
  394. }
  395.  
  396. /**
  397. */
  398. template isGroup(T...)
  399. {
  400. static if (T.length != 1) enum isGroup = false;
  401. else enum isGroup =
  402. !is(T[0]) && is(typeof(T[0]) == void) // does not evaluate to something
  403. && is(typeof(T[0].expand.length) : size_t) // expands to something with length
  404. && !is(typeof(&(T[0].expand))); // expands to not addressable
  405. }
  406.  
  407. version (unittest) // NOTE: GDC complains about template definitions in unittest blocks
  408. {
  409. import std.typetuple;
  410.  
  411. alias group = Group!(int, double, string);
  412. alias group2 = Group!();
  413.  
  414. template Fake(T...)
  415. {
  416. int[] expand;
  417. }
  418. alias fake = Fake!(int, double, string);
  419.  
  420. alias fake2 = TypeTuple!(int, double, string);
  421.  
  422. static assert (isGroup!group);
  423. static assert (isGroup!group2);
  424. static assert (!isGroup!fake);
  425. static assert (!isGroup!fake2);
  426. }
  427.  
  428. /* Copied from Phobos as it is private there.
  429. */
  430. private template isSame(ab...)
  431. if (ab.length == 2)
  432. {
  433. static if (is(ab[0]) && is(ab[1]))
  434. {
  435. enum isSame = is(ab[0] == ab[1]);
  436. }
  437. else static if (!is(ab[0]) &&
  438. !is(ab[1]) &&
  439. is(typeof(ab[0] == ab[1]) == bool) &&
  440. (ab[0] == ab[1]))
  441. {
  442. static if (!__traits(compiles, &ab[0]) ||
  443. !__traits(compiles, &ab[1]))
  444. enum isSame = (ab[0] == ab[1]);
  445. else
  446. enum isSame = __traits(isSame, ab[0], ab[1]);
  447. }
  448. else
  449. {
  450. enum isSame = __traits(isSame, ab[0], ab[1]);
  451. }
  452. }
  453.  
  454.  
  455. /**
  456. Small convenience wrapper to find and extract certain UDA from given type.
  457. Will stop on first element which is of required type.
  458.  
  459. Params:
  460. UDA = type or template to search for in UDA list
  461. Symbol = symbol to query for UDA's
  462. allow_types = if set to `false` considers attached `UDA` types an error
  463. (only accepts instances/values)
  464.  
  465. Returns: aggregated search result struct with 3 field. `value` aliases found UDA.
  466. `found` is boolean flag for having a valid find. `index` is integer index in
  467. attribute list this UDA was found at.
  468. */
  469. template findFirstUDA(alias UDA, alias Symbol, bool allow_types = false) if (!is(UDA))
  470. {
  471. enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types);
  472. }
  473.  
  474. /// Ditto
  475. template findFirstUDA(UDA, alias Symbol, bool allow_types = false)
  476. {
  477. enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types);
  478. }
  479.  
  480. private struct UdaSearchResult(alias UDA)
  481. {
  482. alias value = UDA;
  483. bool found = false;
  484. long index = -1;
  485. }
  486.  
  487. /**
  488. Small convenience wrapper to find and extract certain UDA from given type.
  489. Will start at the given index and stop on the next element which is of required type.
  490.  
  491. Params:
  492. UDA = type or template to search for in UDA list
  493. Symbol = symbol to query for UDA's
  494. idx = 0-based index to start at. Should be positive, and under the total number of attributes.
  495. allow_types = if set to `false` considers attached `UDA` types an error
  496. (only accepts instances/values)
  497.  
  498. Returns: aggregated search result struct with 3 field. `value` aliases found UDA.
  499. `found` is boolean flag for having a valid find. `index` is integer index in
  500. attribute list this UDA was found at.
  501. */
  502. template findNextUDA(alias UDA, alias Symbol, long idx, bool allow_types = false) if (!is(UDA))
  503. {
  504. import std.traits : isInstanceOf;
  505. import std.typetuple : TypeTuple;
  506.  
  507. private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol));
  508.  
  509. static assert(idx >= 0, "Index given to findNextUDA can't be negative");
  510. static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute");
  511.  
  512. public template extract(size_t index, list...)
  513. {
  514. static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1);
  515. else {
  516. static if (is(list[0])) {
  517. static if (is(UDA) && is(list[0] == UDA) || !is(UDA) && isInstanceOf!(UDA, list[0])) {
  518. static assert (allow_types, "findNextUDA is designed to look up values, not types");
  519. enum extract = UdaSearchResult!(list[0])(true, index);
  520. } else enum extract = extract!(index + 1, list[1..$]);
  521. } else {
  522. static if (is(UDA) && is(typeof(list[0]) == UDA) || !is(UDA) && isInstanceOf!(UDA, typeof(list[0]))) {
  523. import vibe.internal.meta.traits : isPropertyGetter;
  524. static if (isPropertyGetter!(list[0])) {
  525. enum value = list[0];
  526. enum extract = UdaSearchResult!(value)(true, index);
  527. } else enum extract = UdaSearchResult!(list[0])(true, index);
  528. } else enum extract = extract!(index + 1, list[1..$]);
  529. }
  530. }
  531. }
  532.  
  533. enum findNextUDA = extract!(idx, udaTuple[idx .. $]);
  534. }
  535. /// ditto
  536. template findNextUDA(UDA, alias Symbol, long idx, bool allow_types = false)
  537. {
  538. import std.traits : isInstanceOf;
  539. import std.typetuple : TypeTuple;
  540.  
  541. private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol));
  542.  
  543. static assert(idx >= 0, "Index given to findNextUDA can't be negative");
  544. static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute");
  545.  
  546. public template extract(size_t index, list...)
  547. {
  548. static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1);
  549. else {
  550. static if (is(list[0])) {
  551. static if (is(list[0] == UDA)) {
  552. static assert (allow_types, "findNextUDA is designed to look up values, not types");
  553. enum extract = UdaSearchResult!(list[0])(true, index);
  554. } else enum extract = extract!(index + 1, list[1..$]);
  555. } else {
  556. static if (is(typeof(list[0]) == UDA)) {
  557. static if (isPropertyGetter!(list[0])) {
  558. enum value = list[0];
  559. enum extract = UdaSearchResult!(value)(true, index);
  560. } else enum extract = UdaSearchResult!(list[0])(true, index);
  561. } else enum extract = extract!(index + 1, list[1..$]);
  562. }
  563. }
  564. }
  565.  
  566. enum findNextUDA = extract!(idx, udaTuple[idx .. $]);
  567. }
  568.  
  569.  
  570. ///
  571. unittest
  572. {
  573. struct Attribute { int x; }
  574.  
  575. @("something", Attribute(42), Attribute(41))
  576. void symbol();
  577.  
  578. enum result0 = findNextUDA!(string, symbol, 0);
  579. static assert (result0.found);
  580. static assert (result0.index == 0);
  581. static assert (result0.value == "something");
  582.  
  583. enum result1 = findNextUDA!(Attribute, symbol, 0);
  584. static assert (result1.found);
  585. static assert (result1.index == 1);
  586. static assert (result1.value == Attribute(42));
  587.  
  588. enum result2 = findNextUDA!(int, symbol, 0);
  589. static assert (!result2.found);
  590.  
  591. enum result3 = findNextUDA!(Attribute, symbol, result1.index + 1);
  592. static assert (result3.found);
  593. static assert (result3.index == 2);
  594. static assert (result3.value == Attribute(41));
  595. }
  596.  
  597. unittest
  598. {
  599. struct Attribute { int x; }
  600.  
  601. @(Attribute) void symbol();
  602.  
  603. static assert (!is(findNextUDA!(Attribute, symbol, 0)));
  604.  
  605. enum result0 = findNextUDA!(Attribute, symbol, 0, true);
  606. static assert (result0.found);
  607. static assert (result0.index == 0);
  608. static assert (is(result0.value == Attribute));
  609. }
  610.  
  611. unittest
  612. {
  613. struct Attribute { int x; }
  614. enum Dummy;
  615.  
  616. @property static Attribute getter()
  617. {
  618. return Attribute(42);
  619. }
  620.  
  621. @Dummy @getter void symbol();
  622.  
  623. enum result0 = findNextUDA!(Attribute, symbol, 0);
  624. static assert (result0.found);
  625. static assert (result0.index == 1);
  626. static assert (result0.value == Attribute(42));
  627. }
  628.  
  629. /// Eager version of findNextUDA that represent all instances of UDA in a Tuple.
  630. /// If one of the attribute is a type instead of an instance, compilation will fail.
  631. template UDATuple(alias UDA, alias Sym) {
  632. import std.typetuple : TypeTuple;
  633.  
  634. private template extract(size_t maxSize, Founds...)
  635. {
  636. private alias LastFound = Founds[$ - 1];
  637. // No more to find
  638. static if (!LastFound.found)
  639. enum extract = Founds[0 .. $ - 1];
  640. else {
  641. // For ease of use, this is a Tuple of UDA, not a tuple of UdaSearchResult!(...)
  642. private alias Result = TypeTuple!(Founds[0 .. $ - 1], LastFound.value);
  643. // We're at the last parameter
  644. static if (LastFound.index == maxSize)
  645. enum extract = Result;
  646. else
  647. enum extract = extract!(maxSize, Result, findNextUDA!(UDA, Sym, LastFound.index + 1));
  648. }
  649. }
  650.  
  651. private enum maxIndex = TypeTuple!(__traits(getAttributes, Sym)).length;
  652. enum UDATuple = extract!(maxIndex, findNextUDA!(UDA, Sym, 0));
  653. }
  654.  
  655. unittest
  656. {
  657. import std.typetuple : TypeTuple;
  658.  
  659. struct Attribute { int x; }
  660. enum Dummy;
  661.  
  662. @(Dummy, Attribute(21), Dummy, Attribute(42), Attribute(84)) void symbol() {}
  663. @(Dummy, Attribute(21), Dummy, Attribute(42), Attribute) void wrong() {}
  664.  
  665. alias Cmp = TypeTuple!(Attribute(21), Attribute(42), Attribute(84));
  666. static assert(Cmp == UDATuple!(Attribute, symbol));
  667. static assert(!is(UDATuple!(Attribute, wrong)));
  668. }
  669.  
  670. /// Avoid repeating the same error message again and again.
  671. /// ----
  672. /// if (!__ctfe)
  673. /// assert(0, onlyAsUda!func);
  674. /// ----
  675. template onlyAsUda(string from /*= __FUNCTION__*/)
  676. {
  677. // With default param, DMD think expression is void, even when writing 'enum string onlyAsUda = ...'
  678. enum onlyAsUda = from~" must only be used as an attribute - not called as a runtime function.";
  679. }