try.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. /**
  2. * @file try.c
  3. * @author Ambroz Bizjak <ambrop7@gmail.com>
  4. *
  5. * @section LICENSE
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. Neither the name of the author nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * @section DESCRIPTION
  30. *
  31. *
  32. * Synopsis:
  33. * try(string template_name, list args)
  34. * do(string template_name[, string interrupt_template_name])
  35. *
  36. * Description:
  37. * The basic functionality is:
  38. * 1. Starts a template process from the specified template and arguments.
  39. * 2. Waits for the process to initialize completely, or for a _try->assert()
  40. * assertion to fail or a _do->break() call.
  41. * 3. Initiates termination of the process and waits for it to terminate.
  42. * 4. Goes to up state. The "succeeded" variable reflects whether the process
  43. * managed to initialize, or an assertion failed.
  44. * If at any point during these steps termination of the try statement is
  45. * requested, requests the process to terminate (if not already), and dies
  46. * when it terminates.
  47. *
  48. * The differences between try() and do() are that do() directly exposes
  49. * the caller scope (try() does via _caller), and the availability of
  50. * assert/break.
  51. *
  52. * The two-argument version of do() is an extension, where in case of a
  53. * statement termination request while the template process is not yet
  54. * initialized completely, the interrupt_template_name template process is
  55. * started, instead of sending a termination request to the template_name
  56. * process. The interrupt_template_name process has access to the same
  57. * scope as template_name. This means that it can itself call _do->break().
  58. * Termination of the interrupt_template_name process is started as soon
  59. * as it initializes completely, or when termination of the template_name
  60. * process starts.
  61. *
  62. * Variables:
  63. * string succeeded - "true" if the template process finished, "false" if assert
  64. * or break was called.
  65. *
  66. *
  67. * Synopsis:
  68. * try.try::assert(string cond)
  69. *
  70. * Description:
  71. * Call as _try->assert() from the template process of try(). If cond is
  72. * "true", does nothing. Else, initiates termination of the process (if not
  73. * already), and marks the try operation as not succeeded.
  74. *
  75. *
  76. * Synopsis:
  77. * do.do::break()
  78. * do.do::rbreak()
  79. *
  80. * Description:
  81. * Call as _do->break() from the template process of do() to initiate
  82. * premature termination, marking the do operation as not succeeded.
  83. * The rbreak() does so on deinitialization.
  84. */
  85. #include <stdlib.h>
  86. #include <string.h>
  87. #include <misc/offset.h>
  88. #include <ncd/module_common.h>
  89. #include <generated/blog_channel_ncd_try.h>
  90. struct instance {
  91. NCDModuleInst *i;
  92. int is_do;
  93. NCDValRef interrupt_template_name;
  94. NCDModuleProcess process;
  95. NCDModuleProcess interrupt_process;
  96. int state;
  97. int intstate;
  98. int dying;
  99. int succeeded;
  100. };
  101. struct rbreak_instance {
  102. NCDModuleInst *i;
  103. NCDModuleRef ref;
  104. };
  105. enum {STATE_INIT, STATE_DEINIT, STATE_FINISHED, STATE_WAITINT};
  106. enum {INTSTATE_NONE, INTSTATE_INIT, INTSTATE_DEINIT};
  107. static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
  108. static void start_terminating (struct instance *o);
  109. static void instance_free (struct instance *o);
  110. enum {STRING_TRY, STRING_TRY_TRY, STRING_DO, STRING_DO_DO};
  111. static const char *strings[] = {"_try", "try.try", "_do", "do.do"};
  112. static void process_handler_event (NCDModuleProcess *process, int event)
  113. {
  114. struct instance *o = UPPER_OBJECT(process, struct instance, process);
  115. switch (event) {
  116. case NCDMODULEPROCESS_EVENT_UP: {
  117. ASSERT(o->state == STATE_INIT)
  118. // start terminating
  119. start_terminating(o);
  120. } break;
  121. case NCDMODULEPROCESS_EVENT_DOWN: {
  122. // Can't happen since we start terminating with it comes up.
  123. ASSERT(0)
  124. } break;
  125. case NCDMODULEPROCESS_EVENT_TERMINATED: {
  126. ASSERT(o->state == STATE_DEINIT)
  127. // free process
  128. NCDModuleProcess_Free(&o->process);
  129. if (o->dying) {
  130. // We want to die but not without interrupt_process still running.
  131. if (o->intstate == INTSTATE_NONE) {
  132. instance_free(o);
  133. } else {
  134. o->state = STATE_WAITINT;
  135. }
  136. return;
  137. }
  138. // signal up
  139. NCDModuleInst_Backend_Up(o->i);
  140. // set state finished
  141. o->state = STATE_FINISHED;
  142. } break;
  143. }
  144. }
  145. static void interrupt_process_handler_event (NCDModuleProcess *process, int event)
  146. {
  147. struct instance *o = UPPER_OBJECT(process, struct instance, interrupt_process);
  148. ASSERT(o->dying)
  149. ASSERT(o->intstate != INTSTATE_NONE)
  150. switch (event) {
  151. case NCDMODULEPROCESS_EVENT_UP: {
  152. ASSERT(o->intstate == INTSTATE_INIT)
  153. // Start terminating the interrupt_process.
  154. NCDModuleProcess_Terminate(&o->interrupt_process);
  155. o->intstate = INTSTATE_DEINIT;
  156. } break;
  157. case NCDMODULEPROCESS_EVENT_DOWN: {
  158. // Can't happen since we start terminating with it comes up.
  159. ASSERT(0)
  160. } break;
  161. case NCDMODULEPROCESS_EVENT_TERMINATED: {
  162. ASSERT(o->intstate == INTSTATE_DEINIT)
  163. // free process
  164. NCDModuleProcess_Free(&o->interrupt_process);
  165. o->intstate = INTSTATE_NONE;
  166. // If the main process has terminated, free the instance now.
  167. if (o->state == STATE_WAITINT) {
  168. instance_free(o);
  169. return;
  170. }
  171. } break;
  172. }
  173. }
  174. static int common_getspecialobj (struct instance *o, NCD_string_id_t name, NCDObject *out_object)
  175. {
  176. if (o->is_do) {
  177. if (name == ModuleString(o->i, STRING_DO)) {
  178. *out_object = NCDObject_Build(ModuleString(o->i, STRING_DO_DO), o, NCDObject_no_getvar, NCDObject_no_getobj);
  179. return 1;
  180. }
  181. return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
  182. } else {
  183. if (name == NCD_STRING_CALLER) {
  184. *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, process_caller_object_func_getobj);
  185. return 1;
  186. }
  187. if (name == ModuleString(o->i, STRING_TRY)) {
  188. *out_object = NCDObject_Build(ModuleString(o->i, STRING_TRY_TRY), o, NCDObject_no_getvar, NCDObject_no_getobj);
  189. return 1;
  190. }
  191. return 0;
  192. }
  193. }
  194. static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
  195. {
  196. struct instance *o = UPPER_OBJECT(process, struct instance, process);
  197. return common_getspecialobj(o, name, out_object);
  198. }
  199. static int interrupt_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
  200. {
  201. struct instance *o = UPPER_OBJECT(process, struct instance, interrupt_process);
  202. return common_getspecialobj(o, name, out_object);
  203. }
  204. static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
  205. {
  206. struct instance *o = NCDObject_DataPtr(obj);
  207. return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
  208. }
  209. static void start_terminating (struct instance *o)
  210. {
  211. ASSERT(o->state == STATE_INIT)
  212. // request termination of the interrupt_process if possible
  213. if (o->intstate == INTSTATE_INIT) {
  214. NCDModuleProcess_Terminate(&o->interrupt_process);
  215. o->intstate = INTSTATE_DEINIT;
  216. }
  217. // request process termination
  218. NCDModuleProcess_Terminate(&o->process);
  219. o->state = STATE_DEINIT;
  220. }
  221. static void func_new_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_do, NCDValRef template_name, NCDValRef args, NCDValRef interrupt_template_name)
  222. {
  223. struct instance *o = vo;
  224. o->i = i;
  225. o->is_do = is_do;
  226. o->interrupt_template_name = interrupt_template_name;
  227. // start process
  228. if (!NCDModuleProcess_InitValue(&o->process, i, template_name, args, process_handler_event)) {
  229. ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
  230. goto fail0;
  231. }
  232. // set special object function
  233. NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj);
  234. // set state init, not dying, assume succeeded
  235. o->state = STATE_INIT;
  236. o->intstate = INTSTATE_NONE;
  237. o->dying = 0;
  238. o->succeeded = 1;
  239. return;
  240. fail0:
  241. NCDModuleInst_Backend_DeadError(i);
  242. }
  243. static void func_new_try (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  244. {
  245. // check arguments
  246. NCDValRef template_name_arg;
  247. NCDValRef args_arg;
  248. if (!NCDVal_ListRead(params->args, 2, &template_name_arg, &args_arg)) {
  249. ModuleLog(i, BLOG_ERROR, "wrong arity");
  250. goto fail;
  251. }
  252. if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
  253. ModuleLog(i, BLOG_ERROR, "wrong type");
  254. goto fail;
  255. }
  256. return func_new_common(vo, i, params, 0, template_name_arg, args_arg, NCDVal_NewInvalid());
  257. fail:
  258. NCDModuleInst_Backend_DeadError(i);
  259. }
  260. static void func_new_do (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  261. {
  262. // check arguments
  263. NCDValRef template_name_arg;
  264. NCDValRef interrupt_template_name_arg = NCDVal_NewInvalid();
  265. if (!NCDVal_ListRead(params->args, 1, &template_name_arg) &&
  266. !NCDVal_ListRead(params->args, 2, &template_name_arg, &interrupt_template_name_arg)
  267. ) {
  268. ModuleLog(i, BLOG_ERROR, "wrong arity");
  269. goto fail;
  270. }
  271. if (!NCDVal_IsString(template_name_arg) ||
  272. (!NCDVal_IsInvalid(interrupt_template_name_arg) && !NCDVal_IsString(interrupt_template_name_arg))
  273. ) {
  274. ModuleLog(i, BLOG_ERROR, "wrong type");
  275. goto fail;
  276. }
  277. return func_new_common(vo, i, params, 1, template_name_arg, NCDVal_NewInvalid(), interrupt_template_name_arg);
  278. fail:
  279. NCDModuleInst_Backend_DeadError(i);
  280. }
  281. static void instance_free (struct instance *o)
  282. {
  283. ASSERT(o->intstate == INTSTATE_NONE)
  284. NCDModuleInst_Backend_Dead(o->i);
  285. }
  286. static void instance_break (struct instance *o)
  287. {
  288. // mark not succeeded
  289. o->succeeded = 0;
  290. // start terminating if not already
  291. if (o->state == STATE_INIT) {
  292. start_terminating(o);
  293. }
  294. }
  295. static void func_die (void *vo)
  296. {
  297. struct instance *o = vo;
  298. ASSERT(!o->dying)
  299. ASSERT(o->intstate == INTSTATE_NONE)
  300. // if we're finished, die immediately
  301. if (o->state == STATE_FINISHED) {
  302. instance_free(o);
  303. return;
  304. }
  305. // set dying
  306. o->dying = 1;
  307. // if already terminating, nothing to do
  308. if (o->state != STATE_INIT) {
  309. return;
  310. }
  311. // if we don't have the interrupt-template, start terminating
  312. if (NCDVal_IsInvalid(o->interrupt_template_name)) {
  313. goto terminate;
  314. }
  315. // start process
  316. if (!NCDModuleProcess_InitValue(&o->interrupt_process, o->i, o->interrupt_template_name, NCDVal_NewInvalid(), interrupt_process_handler_event)) {
  317. ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
  318. goto terminate;
  319. }
  320. NCDModuleProcess_SetSpecialFuncs(&o->interrupt_process, interrupt_process_func_getspecialobj);
  321. o->intstate = INTSTATE_INIT;
  322. return;
  323. terminate:
  324. start_terminating(o);
  325. }
  326. static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
  327. {
  328. struct instance *o = vo;
  329. ASSERT(o->state == STATE_FINISHED)
  330. ASSERT(!o->dying)
  331. if (name == NCD_STRING_SUCCEEDED) {
  332. *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
  333. return 1;
  334. }
  335. return 0;
  336. }
  337. static void assert_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  338. {
  339. // check arguments
  340. NCDValRef cond_arg;
  341. if (!NCDVal_ListRead(params->args, 1, &cond_arg)) {
  342. ModuleLog(i, BLOG_ERROR, "wrong arity");
  343. goto fail1;
  344. }
  345. if (!NCDVal_IsString(cond_arg)) {
  346. ModuleLog(i, BLOG_ERROR, "wrong type");
  347. goto fail1;
  348. }
  349. // signal up
  350. NCDModuleInst_Backend_Up(i);
  351. // break if needed
  352. if (!NCDVal_StringEquals(cond_arg, "true")) {
  353. instance_break((struct instance *)params->method_user);
  354. }
  355. return;
  356. fail1:
  357. NCDModuleInst_Backend_DeadError(i);
  358. }
  359. static void break_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  360. {
  361. // check arguments
  362. if (!NCDVal_ListRead(params->args, 0)) {
  363. ModuleLog(i, BLOG_ERROR, "wrong arity");
  364. goto fail1;
  365. }
  366. // signal up
  367. NCDModuleInst_Backend_Up(i);
  368. // break
  369. instance_break((struct instance *)params->method_user);
  370. return;
  371. fail1:
  372. NCDModuleInst_Backend_DeadError(i);
  373. }
  374. static void rbreak_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  375. {
  376. struct rbreak_instance *o = vo;
  377. o->i = i;
  378. // check arguments
  379. if (!NCDVal_ListRead(params->args, 0)) {
  380. ModuleLog(i, BLOG_ERROR, "wrong arity");
  381. goto fail0;
  382. }
  383. // init object reference
  384. NCDModuleRef_Init(&o->ref, ((struct instance *)params->method_user)->i);
  385. // go up
  386. NCDModuleInst_Backend_Up(i);
  387. return;
  388. fail0:
  389. NCDModuleInst_Backend_DeadError(i);
  390. }
  391. static void rbreak_func_die (void *vo)
  392. {
  393. struct rbreak_instance *o = vo;
  394. // deref backtrack_point
  395. NCDModuleInst *inst = NCDModuleRef_Deref(&o->ref);
  396. // free object reference
  397. NCDModuleRef_Free(&o->ref);
  398. // die
  399. NCDModuleInst_Backend_Dead(o->i);
  400. // break
  401. if (inst) {
  402. instance_break((struct instance *)NCDModuleInst_Backend_GetUser(inst));
  403. }
  404. }
  405. static struct NCDModule modules[] = {
  406. {
  407. .type = "try",
  408. .func_new2 = func_new_try,
  409. .func_die = func_die,
  410. .func_getvar2 = func_getvar2,
  411. .alloc_size = sizeof(struct instance)
  412. }, {
  413. .type = "do",
  414. .func_new2 = func_new_do,
  415. .func_die = func_die,
  416. .func_getvar2 = func_getvar2,
  417. .alloc_size = sizeof(struct instance)
  418. }, {
  419. .type = "try.try::assert",
  420. .func_new2 = assert_func_new
  421. }, {
  422. .type = "do.do::break",
  423. .func_new2 = break_func_new
  424. }, {
  425. .type = "do.do::rbreak",
  426. .func_new2 = rbreak_func_new,
  427. .func_die = rbreak_func_die,
  428. .alloc_size = sizeof(struct rbreak_instance)
  429. }, {
  430. .type = NULL
  431. }
  432. };
  433. const struct NCDModuleGroup ncdmodule_try = {
  434. .modules = modules,
  435. .strings = strings
  436. };