chalk.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. /*++
  2. Copyright (c) 2016 Minoca Corp.
  3. This file is licensed under the terms of the GNU General Public License
  4. version 3. Alternative licensing terms are available. Contact
  5. info@minocacorp.com for details. See the LICENSE file at the root of this
  6. project for complete licensing information.
  7. Module Name:
  8. chalk.c
  9. Abstract:
  10. This module implements the Chalk interactive interpreter.
  11. Author:
  12. Evan Green 26-May-2016
  13. Environment:
  14. POSIX
  15. --*/
  16. //
  17. // ------------------------------------------------------------------- Includes
  18. //
  19. #include <minoca/lib/types.h>
  20. #include <minoca/lib/chalk.h>
  21. #include <minoca/lib/chalk/app.h>
  22. #include <minoca/lib/chalk/bundle.h>
  23. #include <assert.h>
  24. #include <errno.h>
  25. #include <getopt.h>
  26. #include <libgen.h>
  27. #include <limits.h>
  28. #include <stdio.h>
  29. #include <stdlib.h>
  30. #include <string.h>
  31. #include <sys/stat.h>
  32. #include <unistd.h>
  33. //
  34. // ---------------------------------------------------------------- Definitions
  35. //
  36. #define CHALK_USAGE \
  37. "usage: chalk [options] [file] [arguments...]\n" \
  38. "Chalk is a nifty scripting language. It's designed to be intuitive, \n" \
  39. "small, and easily embeddable. Options are:\n" \
  40. " -c \"expr\" -- Execute the given expression and exit.\n" \
  41. " --debug-gc -- Stress the garbage collector.\n" \
  42. " --debug-compiler -- Print the compiled bytecode.\n" \
  43. " --help -- Show this help text and exit.\n" \
  44. " --version -- Print the application version information and exit.\n"
  45. #define CHALK_OPTIONS_STRING "+c:chV"
  46. #define CHALK_LINE_MAX 2048
  47. #define CHALK_OPTION_DEBUG_GC 257
  48. #define CHALK_OPTION_DEBUG_COMPILER 258
  49. //
  50. // ------------------------------------------------------ Data Type Definitions
  51. //
  52. /*++
  53. Structure Description:
  54. This structure stores the context for the Chalk interactive interpreter.
  55. Members:
  56. Configuration - Stores the VM configuration.
  57. Vm - Stores a pointer to the virtual machine.
  58. LineNumber - Stores the next line number to be read.
  59. Line - Stores the line input buffer.
  60. --*/
  61. typedef struct _CK_APP_CONTEXT {
  62. CK_CONFIGURATION Configuration;
  63. PCK_VM Vm;
  64. INT LineNumber;
  65. PSTR Line;
  66. } CK_APP_CONTEXT, *PCK_APP_CONTEXT;
  67. //
  68. // ----------------------------------------------- Internal Function Prototypes
  69. //
  70. VOID
  71. ChalkSetupModulePath (
  72. PVOID Vm,
  73. PSTR Script
  74. );
  75. INT
  76. ChalkInitializeContext (
  77. PCK_APP_CONTEXT Context
  78. );
  79. VOID
  80. ChalkDestroyContext (
  81. PCK_APP_CONTEXT Context
  82. );
  83. PSTR
  84. ChalkLoadFile (
  85. PSTR FileName,
  86. PUINTN FileSize
  87. );
  88. INT
  89. ChalkRunInteractiveInterpreter (
  90. PCK_APP_CONTEXT Context
  91. );
  92. INT
  93. ChalkReadLine (
  94. PCK_APP_CONTEXT Context
  95. );
  96. //
  97. // -------------------------------------------------------------------- Globals
  98. //
  99. struct option ChalkLongOptions[] = {
  100. {"debug-gc", no_argument, 0, CHALK_OPTION_DEBUG_GC},
  101. {"debug-compiler", no_argument, 0, CHALK_OPTION_DEBUG_COMPILER},
  102. {"help", no_argument, 0, 'h'},
  103. {"verbose", no_argument, 0, 'v'},
  104. {NULL, 0, 0, 0},
  105. };
  106. //
  107. // ------------------------------------------------------------------ Functions
  108. //
  109. INT
  110. main (
  111. INT ArgumentCount,
  112. CHAR **Arguments
  113. )
  114. /*++
  115. Routine Description:
  116. This routine is the main entry point for the chalk interactive interpreter.
  117. Arguments:
  118. ArgumentCount - Supplies the number of command line arguments the program
  119. was invoked with.
  120. Arguments - Supplies a tokenized array of command line arguments.
  121. Return Value:
  122. Returns an integer exit code. 0 for success, nonzero otherwise.
  123. --*/
  124. {
  125. BOOL AppIsBundle;
  126. PSTR ArgumentCopy;
  127. ULONG ArgumentIndex;
  128. PSTR BaseName;
  129. CK_APP_CONTEXT Context;
  130. PCSTR Expression;
  131. PSTR FileBuffer;
  132. UINTN FileSize;
  133. INT Option;
  134. PSTR ScriptPath;
  135. int Status;
  136. ArgumentIndex = 1;
  137. AppIsBundle = FALSE;
  138. Expression = NULL;
  139. FileBuffer = NULL;
  140. ScriptPath = NULL;
  141. Status = ChalkInitializeContext(&Context);
  142. if (Status != 0) {
  143. fprintf(stderr, "Failed to initialize: %s.\n", strerror(Status));
  144. Status = 2;
  145. goto MainEnd;
  146. }
  147. //
  148. // Figure out early if this executable is a bundle. If it is, don't do
  149. // regular argument parsing.
  150. //
  151. if (ArgumentCount == 0) {
  152. fprintf(stderr, "Arg0 required.\n");
  153. Status = 2;
  154. goto MainEnd;
  155. }
  156. ArgumentCopy = strdup(Arguments[0]);
  157. if (ArgumentCopy != NULL) {
  158. BaseName = basename(Arguments[0]);
  159. if (BaseName != NULL) {
  160. if (strncasecmp(BaseName, "chalk", 5) != 0) {
  161. AppIsBundle = TRUE;
  162. }
  163. }
  164. free(ArgumentCopy);
  165. }
  166. CkAppArgv = Arguments;
  167. CkAppArgc = ArgumentCount;
  168. //
  169. // Process the control arguments if this is the Chalk app acting as the
  170. // Chalk app.
  171. //
  172. if (AppIsBundle == FALSE) {
  173. while (TRUE) {
  174. optarg = NULL;
  175. Option = getopt_long(ArgumentCount,
  176. Arguments,
  177. CHALK_OPTIONS_STRING,
  178. ChalkLongOptions,
  179. NULL);
  180. if (Option == -1) {
  181. break;
  182. }
  183. if ((Option == '?') || (Option == ':')) {
  184. Status = 2;
  185. goto MainEnd;
  186. }
  187. switch (Option) {
  188. case 'c':
  189. Expression = optarg;
  190. break;
  191. case CHALK_OPTION_DEBUG_GC:
  192. Context.Configuration.Flags |= CK_CONFIGURATION_GC_STRESS;
  193. break;
  194. case CHALK_OPTION_DEBUG_COMPILER:
  195. Context.Configuration.Flags |= CK_CONFIGURATION_DEBUG_COMPILER;
  196. break;
  197. case 'V':
  198. printf("Chalk version %d.%d.%d. Copyright 2017 Minoca Corp. "
  199. "All Rights Reserved.\n",
  200. CHALK_VERSION_MAJOR,
  201. CHALK_VERSION_MINOR,
  202. CHALK_VERSION_REVISION);
  203. return 1;
  204. case 'h':
  205. printf(CHALK_USAGE);
  206. return 2;
  207. default:
  208. assert(FALSE);
  209. Status = 2;
  210. goto MainEnd;
  211. }
  212. }
  213. ArgumentIndex = optind;
  214. if (ArgumentIndex < ArgumentCount) {
  215. ScriptPath = Arguments[ArgumentIndex];
  216. CkAppArgc = ArgumentCount - ArgumentIndex;
  217. CkAppArgv = Arguments + ArgumentIndex;
  218. }
  219. }
  220. Context.Vm = CkCreateVm(&(Context.Configuration));
  221. if (Context.Vm == NULL) {
  222. fprintf(stderr, "Error: Failed to create VM.\n");
  223. Status = 2;
  224. goto MainEnd;
  225. }
  226. if ((!CkPreloadAppModule(Context.Vm, Arguments[0])) ||
  227. (!CkPreloadBundleModule(Context.Vm))) {
  228. fprintf(stderr, "Error: Failed to preload builtin modules.\n");
  229. Status = 2;
  230. goto MainEnd;
  231. }
  232. //
  233. // Set up the module search path. Two stack slots are needed: one for the
  234. // module search list, and one for a new string being appended.
  235. //
  236. if (!CkEnsureStack(Context.Vm, 2)) {
  237. fprintf(stderr, "Warning: Failed to initialize module search path.\n");
  238. Status = 2;
  239. goto MainEnd;
  240. }
  241. CkPushModulePath(Context.Vm);
  242. ChalkSetupModulePath(Context.Vm, ScriptPath);
  243. CkStackPop(Context.Vm);
  244. //
  245. // If the app doesn't start with the name chalk, try to load a bundle from
  246. // the app.
  247. //
  248. if (AppIsBundle != FALSE) {
  249. Status = CkBundleThaw(Context.Vm);
  250. goto MainEnd;
  251. }
  252. //
  253. // Run the expression if there was one.
  254. //
  255. if (Expression != NULL) {
  256. Status = CkInterpret(Context.Vm,
  257. NULL,
  258. Expression,
  259. strlen(Expression),
  260. 1,
  261. FALSE);
  262. if (Status != CkSuccess) {
  263. Status = 1;
  264. }
  265. //
  266. // Run the script if there was one.
  267. //
  268. } else if (ScriptPath != NULL) {
  269. FileBuffer = ChalkLoadFile(ScriptPath, &FileSize);
  270. if (FileBuffer == NULL) {
  271. fprintf(stderr,
  272. "Error: Failed to load file %s: %s\n",
  273. Arguments[ArgumentIndex],
  274. strerror(errno));
  275. Status = 2;
  276. goto MainEnd;
  277. }
  278. CkAppArgc = ArgumentCount - ArgumentIndex;
  279. CkAppArgv = Arguments + ArgumentIndex;
  280. Status = CkInterpret(Context.Vm,
  281. ScriptPath,
  282. FileBuffer,
  283. FileSize,
  284. 1,
  285. FALSE);
  286. //
  287. // With no arguments, run the interactive interpreter.
  288. //
  289. } else {
  290. Status = ChalkRunInteractiveInterpreter(&Context);
  291. }
  292. MainEnd:
  293. ChalkDestroyContext(&Context);
  294. if (FileBuffer != NULL) {
  295. free(FileBuffer);
  296. }
  297. return Status;
  298. }
  299. VOID
  300. ChalkAddSearchPath (
  301. PVOID Vm,
  302. PSTR Directory,
  303. PSTR ChalkDirectory
  304. )
  305. /*++
  306. Routine Description:
  307. This routine adds a library search path. It assumes the module list is
  308. already pushed at the top of the stack.
  309. Arguments:
  310. Vm - Supplies a pointer to the virtual machine.
  311. Directory - Supplies the base directory path to add.
  312. ChalkDirectory - Supplies the directory to tack on to the base. If this
  313. is supplied, the major version number will be appended to it.
  314. Return Value:
  315. None.
  316. --*/
  317. {
  318. CHAR NewPath[PATH_MAX];
  319. INT NewPathSize;
  320. if (ChalkDirectory != NULL) {
  321. NewPathSize = snprintf(NewPath,
  322. PATH_MAX,
  323. "%s/%s%d",
  324. Directory,
  325. ChalkDirectory,
  326. CHALK_VERSION_MAJOR);
  327. } else {
  328. NewPathSize = snprintf(NewPath, PATH_MAX, "%s", Directory);
  329. }
  330. if (NewPathSize <= 0) {
  331. return;
  332. }
  333. CkPushString(Vm, NewPath, NewPathSize);
  334. CkListSet(Vm, -2, CkListSize(Vm, -2));
  335. return;
  336. }
  337. //
  338. // --------------------------------------------------------- Internal Functions
  339. //
  340. INT
  341. ChalkInitializeContext (
  342. PCK_APP_CONTEXT Context
  343. )
  344. /*++
  345. Routine Description:
  346. This routine initializes the Chalk application context.
  347. Arguments:
  348. Context - Supplies a pointer to the context to load.
  349. Return Value:
  350. 0 on success.
  351. Returns an error number on failure.
  352. --*/
  353. {
  354. INT Status;
  355. memset(Context, 0, sizeof(CK_APP_CONTEXT));
  356. Context->LineNumber = 1;
  357. Context->Line = malloc(CHALK_LINE_MAX);
  358. if (Context->Line == NULL) {
  359. Status = ENOMEM;
  360. goto InitializeEnd;
  361. }
  362. CkInitializeConfiguration(&(Context->Configuration));
  363. Status = 0;
  364. InitializeEnd:
  365. return Status;
  366. }
  367. VOID
  368. ChalkDestroyContext (
  369. PCK_APP_CONTEXT Context
  370. )
  371. /*++
  372. Routine Description:
  373. This routine destroys the Chalk application context.
  374. Arguments:
  375. Context - Supplies a pointer to the context to tear down.
  376. Return Value:
  377. None.
  378. --*/
  379. {
  380. if (Context->Line != NULL) {
  381. free(Context->Line);
  382. }
  383. if (Context->Vm != NULL) {
  384. CkDestroyVm(Context->Vm);
  385. }
  386. return;
  387. }
  388. PSTR
  389. ChalkLoadFile (
  390. PSTR FileName,
  391. PUINTN FileSize
  392. )
  393. /*++
  394. Routine Description:
  395. This routine loads a file, returning its contents.
  396. Arguments:
  397. FileName - Supplies a pointer to the file path to load.
  398. FileSize - Supplies a pointer where the size of the file, not including
  399. the null terminator, will be returned on success.
  400. Return Value:
  401. Returns a pointer to the contents of the file, null terminated.
  402. NULL on failure.
  403. --*/
  404. {
  405. PSTR Buffer;
  406. ssize_t BytesRead;
  407. FILE *File;
  408. struct stat Stat;
  409. INT Status;
  410. size_t TotalRead;
  411. Buffer = NULL;
  412. Status = stat(FileName, &Stat);
  413. if (Status != 0) {
  414. fprintf(stderr,
  415. "chalk: Unable to load file %s: %s\n",
  416. FileName,
  417. strerror(errno));
  418. return NULL;
  419. }
  420. File = fopen(FileName, "r");
  421. if (File == NULL) {
  422. fprintf(stderr,
  423. "chalk: Unable to load file %s: %s\n",
  424. FileName,
  425. strerror(errno));
  426. return NULL;
  427. }
  428. if (Stat.st_size > MAX_UINTN) {
  429. fprintf(stderr, "chalk: File too big: %s\n", FileName);
  430. goto LoadFileEnd;
  431. }
  432. Buffer = malloc(Stat.st_size + 1);
  433. if (Buffer == NULL) {
  434. goto LoadFileEnd;
  435. }
  436. TotalRead = 0;
  437. while (TotalRead < Stat.st_size) {
  438. BytesRead = fread(Buffer + TotalRead,
  439. 1,
  440. Stat.st_size - TotalRead,
  441. File);
  442. if (BytesRead == 0) {
  443. break;
  444. }
  445. if (BytesRead <= 0) {
  446. fprintf(stderr,
  447. "chalk: Unable to read %s: %s\n",
  448. FileName,
  449. strerror(errno));
  450. free(Buffer);
  451. Buffer = NULL;
  452. goto LoadFileEnd;
  453. }
  454. TotalRead += BytesRead;
  455. }
  456. Buffer[TotalRead] = '\0';
  457. *FileSize = TotalRead;
  458. LoadFileEnd:
  459. if (File != NULL) {
  460. fclose(File);
  461. }
  462. return Buffer;
  463. }
  464. INT
  465. ChalkRunInteractiveInterpreter (
  466. PCK_APP_CONTEXT Context
  467. )
  468. /*++
  469. Routine Description:
  470. This routine implements the main loop for the interactive interpreter.
  471. Arguments:
  472. Context - Supplies a pointer to the application context.
  473. Return Value:
  474. 0 on success.
  475. Returns an error number on failure.
  476. --*/
  477. {
  478. INT Line;
  479. INT Status;
  480. printf(" _ _\n|_ |-| /-\\ |_ |< Chalk %d.%d.%d\n",
  481. CHALK_VERSION_MAJOR,
  482. CHALK_VERSION_MINOR,
  483. CHALK_VERSION_REVISION);
  484. Status = 0;
  485. while (TRUE) {
  486. Line = Context->LineNumber;
  487. printf("%d> ", Line);
  488. fflush(NULL);
  489. Status = ChalkReadLine(Context);
  490. if (Status == EOF) {
  491. Status = 0;
  492. break;
  493. } else if (Status != 0) {
  494. break;
  495. }
  496. CkInterpret(Context->Vm,
  497. NULL,
  498. Context->Line,
  499. strlen(Context->Line),
  500. Line,
  501. TRUE);
  502. }
  503. return Status;
  504. }
  505. INT
  506. ChalkReadLine (
  507. PCK_APP_CONTEXT Context
  508. )
  509. /*++
  510. Routine Description:
  511. This routine reads a line in the interpreter.
  512. Arguments:
  513. Context - Supplies a pointer to the application context.
  514. Return Value:
  515. 0 on success.
  516. EOF on end of file.
  517. Returns an error number on failure.
  518. --*/
  519. {
  520. PSTR Buffer;
  521. UINTN Length;
  522. do {
  523. errno = 0;
  524. Buffer = fgets(Context->Line, CHALK_LINE_MAX, stdin);
  525. } while ((Buffer == NULL) && (errno == EINTR));
  526. if (Buffer != NULL) {
  527. Length = strlen(Buffer);
  528. if (Buffer[Length - 1] == '\n') {
  529. Context->LineNumber += 1;
  530. }
  531. return 0;
  532. }
  533. if ((feof(stdin)) || (errno == 0)) {
  534. return EOF;
  535. }
  536. fprintf(stderr, "Failed to read line: %s", strerror(errno));
  537. return errno;
  538. }