try.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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. *
  79. * Description:
  80. * Call as _do->break() from the template process of do() to initiate
  81. * premature termination, marking the do operation as not succeeded.
  82. */
  83. #include <stdlib.h>
  84. #include <string.h>
  85. #include <misc/offset.h>
  86. #include <ncd/module_common.h>
  87. #include <generated/blog_channel_ncd_try.h>
  88. struct instance {
  89. NCDModuleInst *i;
  90. int is_do;
  91. NCDValRef interrupt_template_name;
  92. NCDModuleProcess process;
  93. NCDModuleProcess interrupt_process;
  94. int state;
  95. int intstate;
  96. int dying;
  97. int succeeded;
  98. };
  99. enum {STATE_INIT, STATE_DEINIT, STATE_FINISHED, STATE_WAITINT};
  100. enum {INTSTATE_NONE, INTSTATE_INIT, INTSTATE_DEINIT};
  101. static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
  102. static void start_terminating (struct instance *o);
  103. static void instance_free (struct instance *o);
  104. enum {STRING_TRY, STRING_TRY_TRY, STRING_DO, STRING_DO_DO};
  105. static const char *strings[] = {"_try", "try.try", "_do", "do.do"};
  106. static void process_handler_event (NCDModuleProcess *process, int event)
  107. {
  108. struct instance *o = UPPER_OBJECT(process, struct instance, process);
  109. switch (event) {
  110. case NCDMODULEPROCESS_EVENT_UP: {
  111. ASSERT(o->state == STATE_INIT)
  112. // start terminating
  113. start_terminating(o);
  114. } break;
  115. case NCDMODULEPROCESS_EVENT_DOWN: {
  116. // Can't happen since we start terminating with it comes up.
  117. ASSERT(0)
  118. } break;
  119. case NCDMODULEPROCESS_EVENT_TERMINATED: {
  120. ASSERT(o->state == STATE_DEINIT)
  121. // free process
  122. NCDModuleProcess_Free(&o->process);
  123. if (o->dying) {
  124. // We want to die but not without interrupt_process still running.
  125. if (o->intstate == INTSTATE_NONE) {
  126. instance_free(o);
  127. } else {
  128. o->state = STATE_WAITINT;
  129. }
  130. return;
  131. }
  132. // signal up
  133. NCDModuleInst_Backend_Up(o->i);
  134. // set state finished
  135. o->state = STATE_FINISHED;
  136. } break;
  137. }
  138. }
  139. static void interrupt_process_handler_event (NCDModuleProcess *process, int event)
  140. {
  141. struct instance *o = UPPER_OBJECT(process, struct instance, interrupt_process);
  142. ASSERT(o->dying)
  143. ASSERT(o->intstate != INTSTATE_NONE)
  144. switch (event) {
  145. case NCDMODULEPROCESS_EVENT_UP: {
  146. ASSERT(o->intstate == INTSTATE_INIT)
  147. // Start terminating the interrupt_process.
  148. NCDModuleProcess_Terminate(&o->interrupt_process);
  149. o->intstate = INTSTATE_DEINIT;
  150. } break;
  151. case NCDMODULEPROCESS_EVENT_DOWN: {
  152. // Can't happen since we start terminating with it comes up.
  153. ASSERT(0)
  154. } break;
  155. case NCDMODULEPROCESS_EVENT_TERMINATED: {
  156. ASSERT(o->intstate == INTSTATE_DEINIT)
  157. // free process
  158. NCDModuleProcess_Free(&o->interrupt_process);
  159. o->intstate = INTSTATE_NONE;
  160. // If the main process has terminated, free the instance now.
  161. if (o->state == STATE_WAITINT) {
  162. instance_free(o);
  163. return;
  164. }
  165. } break;
  166. }
  167. }
  168. static int common_getspecialobj (struct instance *o, NCD_string_id_t name, NCDObject *out_object)
  169. {
  170. if (o->is_do) {
  171. if (name == ModuleString(o->i, STRING_DO)) {
  172. *out_object = NCDObject_Build(ModuleString(o->i, STRING_DO_DO), o, NCDObject_no_getvar, NCDObject_no_getobj);
  173. return 1;
  174. }
  175. return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
  176. } else {
  177. if (name == NCD_STRING_CALLER) {
  178. *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, process_caller_object_func_getobj);
  179. return 1;
  180. }
  181. if (name == ModuleString(o->i, STRING_TRY)) {
  182. *out_object = NCDObject_Build(ModuleString(o->i, STRING_TRY_TRY), o, NCDObject_no_getvar, NCDObject_no_getobj);
  183. return 1;
  184. }
  185. return 0;
  186. }
  187. }
  188. static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
  189. {
  190. struct instance *o = UPPER_OBJECT(process, struct instance, process);
  191. return common_getspecialobj(o, name, out_object);
  192. }
  193. static int interrupt_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
  194. {
  195. struct instance *o = UPPER_OBJECT(process, struct instance, interrupt_process);
  196. return common_getspecialobj(o, name, out_object);
  197. }
  198. static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
  199. {
  200. struct instance *o = NCDObject_DataPtr(obj);
  201. return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
  202. }
  203. static void start_terminating (struct instance *o)
  204. {
  205. ASSERT(o->state == STATE_INIT)
  206. // request termination of the interrupt_process if possible
  207. if (o->intstate == INTSTATE_INIT) {
  208. NCDModuleProcess_Terminate(&o->interrupt_process);
  209. o->intstate = INTSTATE_DEINIT;
  210. }
  211. // request process termination
  212. NCDModuleProcess_Terminate(&o->process);
  213. o->state = STATE_DEINIT;
  214. }
  215. 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)
  216. {
  217. struct instance *o = vo;
  218. o->i = i;
  219. o->is_do = is_do;
  220. o->interrupt_template_name = interrupt_template_name;
  221. // start process
  222. if (!NCDModuleProcess_InitValue(&o->process, i, template_name, args, process_handler_event)) {
  223. ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
  224. goto fail0;
  225. }
  226. // set special object function
  227. NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj);
  228. // set state init, not dying, assume succeeded
  229. o->state = STATE_INIT;
  230. o->intstate = INTSTATE_NONE;
  231. o->dying = 0;
  232. o->succeeded = 1;
  233. return;
  234. fail0:
  235. NCDModuleInst_Backend_DeadError(i);
  236. }
  237. static void func_new_try (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  238. {
  239. // check arguments
  240. NCDValRef template_name_arg;
  241. NCDValRef args_arg;
  242. if (!NCDVal_ListRead(params->args, 2, &template_name_arg, &args_arg)) {
  243. ModuleLog(i, BLOG_ERROR, "wrong arity");
  244. goto fail;
  245. }
  246. if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
  247. ModuleLog(i, BLOG_ERROR, "wrong type");
  248. goto fail;
  249. }
  250. return func_new_common(vo, i, params, 0, template_name_arg, args_arg, NCDVal_NewInvalid());
  251. fail:
  252. NCDModuleInst_Backend_DeadError(i);
  253. }
  254. static void func_new_do (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  255. {
  256. // check arguments
  257. NCDValRef template_name_arg;
  258. NCDValRef interrupt_template_name_arg = NCDVal_NewInvalid();
  259. if (!NCDVal_ListRead(params->args, 1, &template_name_arg) &&
  260. !NCDVal_ListRead(params->args, 2, &template_name_arg, &interrupt_template_name_arg)
  261. ) {
  262. ModuleLog(i, BLOG_ERROR, "wrong arity");
  263. goto fail;
  264. }
  265. if (!NCDVal_IsString(template_name_arg) ||
  266. (!NCDVal_IsInvalid(interrupt_template_name_arg) && !NCDVal_IsString(interrupt_template_name_arg))
  267. ) {
  268. ModuleLog(i, BLOG_ERROR, "wrong type");
  269. goto fail;
  270. }
  271. return func_new_common(vo, i, params, 1, template_name_arg, NCDVal_NewInvalid(), interrupt_template_name_arg);
  272. fail:
  273. NCDModuleInst_Backend_DeadError(i);
  274. }
  275. static void instance_free (struct instance *o)
  276. {
  277. ASSERT(o->intstate == INTSTATE_NONE)
  278. NCDModuleInst_Backend_Dead(o->i);
  279. }
  280. static void instance_break (struct instance *o)
  281. {
  282. // mark not succeeded
  283. o->succeeded = 0;
  284. // start terminating if not already
  285. if (o->state == STATE_INIT) {
  286. start_terminating(o);
  287. }
  288. }
  289. static void func_die (void *vo)
  290. {
  291. struct instance *o = vo;
  292. ASSERT(!o->dying)
  293. ASSERT(o->intstate == INTSTATE_NONE)
  294. // if we're finished, die immediately
  295. if (o->state == STATE_FINISHED) {
  296. instance_free(o);
  297. return;
  298. }
  299. // set dying
  300. o->dying = 1;
  301. // if already terminating, nothing to do
  302. if (o->state != STATE_INIT) {
  303. return;
  304. }
  305. // if we don't have the interrupt-template, start terminating
  306. if (NCDVal_IsInvalid(o->interrupt_template_name)) {
  307. goto terminate;
  308. }
  309. // start process
  310. if (!NCDModuleProcess_InitValue(&o->interrupt_process, o->i, o->interrupt_template_name, NCDVal_NewInvalid(), interrupt_process_handler_event)) {
  311. ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
  312. goto terminate;
  313. }
  314. NCDModuleProcess_SetSpecialFuncs(&o->interrupt_process, interrupt_process_func_getspecialobj);
  315. o->intstate = INTSTATE_INIT;
  316. return;
  317. terminate:
  318. start_terminating(o);
  319. }
  320. static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
  321. {
  322. struct instance *o = vo;
  323. ASSERT(o->state == STATE_FINISHED)
  324. ASSERT(!o->dying)
  325. if (name == NCD_STRING_SUCCEEDED) {
  326. *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
  327. return 1;
  328. }
  329. return 0;
  330. }
  331. static void assert_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  332. {
  333. // check arguments
  334. NCDValRef cond_arg;
  335. if (!NCDVal_ListRead(params->args, 1, &cond_arg)) {
  336. ModuleLog(i, BLOG_ERROR, "wrong arity");
  337. goto fail1;
  338. }
  339. if (!NCDVal_IsString(cond_arg)) {
  340. ModuleLog(i, BLOG_ERROR, "wrong type");
  341. goto fail1;
  342. }
  343. // signal up
  344. NCDModuleInst_Backend_Up(i);
  345. // break if needed
  346. if (!NCDVal_StringEquals(cond_arg, "true")) {
  347. instance_break((struct instance *)params->method_user);
  348. }
  349. return;
  350. fail1:
  351. NCDModuleInst_Backend_DeadError(i);
  352. }
  353. static void break_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  354. {
  355. // check arguments
  356. if (!NCDVal_ListRead(params->args, 0)) {
  357. ModuleLog(i, BLOG_ERROR, "wrong arity");
  358. goto fail1;
  359. }
  360. // signal up
  361. NCDModuleInst_Backend_Up(i);
  362. // break
  363. instance_break((struct instance *)params->method_user);
  364. return;
  365. fail1:
  366. NCDModuleInst_Backend_DeadError(i);
  367. }
  368. static struct NCDModule modules[] = {
  369. {
  370. .type = "try",
  371. .func_new2 = func_new_try,
  372. .func_die = func_die,
  373. .func_getvar2 = func_getvar2,
  374. .alloc_size = sizeof(struct instance)
  375. }, {
  376. .type = "do",
  377. .func_new2 = func_new_do,
  378. .func_die = func_die,
  379. .func_getvar2 = func_getvar2,
  380. .alloc_size = sizeof(struct instance)
  381. }, {
  382. .type = "try.try::assert",
  383. .func_new2 = assert_func_new
  384. }, {
  385. .type = "do.do::break",
  386. .func_new2 = break_func_new
  387. }, {
  388. .type = NULL
  389. }
  390. };
  391. const struct NCDModuleGroup ncdmodule_try = {
  392. .modules = modules,
  393. .strings = strings
  394. };