shell.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. /*
  2. ** Copyright (c) 2011 D. Richard Hipp
  3. **
  4. ** This program is free software; you can redistribute it and/or
  5. ** modify it under the terms of the Simplified BSD License (also
  6. ** known as the "2-Clause License" or "FreeBSD License".)
  7. **
  8. ** This program is distributed in the hope that it will be useful,
  9. ** but without any warranty; without even the implied warranty of
  10. ** merchantability or fitness for a particular purpose.
  11. **
  12. ** Author contact information:
  13. ** [email protected]
  14. ** http://www.hwaci.com/drh/
  15. **
  16. *************************************************************************
  17. ** This file contains C code that implements a simple command-line
  18. ** wrapper shell around xjd1.
  19. */
  20. #include "xjd1Int.h"
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include <assert.h>
  25. #ifndef _WIN32
  26. #include <unistd.h>
  27. #endif
  28. /*
  29. ** Remove n elements from argv beginning with the i-th element.
  30. */
  31. void remove_from_argv(char **argv, int *pArgc, int i, int n){
  32. int j;
  33. int argc = *pArgc;
  34. for(j=i+n; j<argc; i++, j++){
  35. argv[i] = argv[j];
  36. }
  37. *pArgc = i;
  38. }
  39. /*
  40. ** Look for a command-line option. If present, return a pointer.
  41. ** Return NULL if missing.
  42. **
  43. ** hasArg==0 means the option is a flag. It is either present or not.
  44. ** hasArg==1 means the option has an argument. Return a pointer to the
  45. ** argument.
  46. */
  47. const char *find_option(
  48. char **argv,
  49. int *pArgc,
  50. const char *zLong,
  51. const char *zShort,
  52. int hasArg
  53. ){
  54. int i;
  55. int nLong;
  56. const char *zReturn = 0;
  57. int argc = *pArgc;
  58. assert( hasArg==0 || hasArg==1 );
  59. nLong = strlen(zLong);
  60. for(i=1; i<argc; i++){
  61. char *z;
  62. if (i+hasArg >= argc) break;
  63. z = argv[i];
  64. if( z[0]!='-' ) continue;
  65. z++;
  66. if( z[0]=='-' ){
  67. if( z[1]==0 ){
  68. remove_from_argv(argv, &argc, i, 1);
  69. break;
  70. }
  71. z++;
  72. }
  73. if( strncmp(z,zLong,nLong)==0 ){
  74. if( hasArg && z[nLong]=='=' ){
  75. zReturn = &z[nLong+1];
  76. remove_from_argv(argv, &argc, i, 1);
  77. break;
  78. }else if( z[nLong]==0 ){
  79. zReturn = argv[i+hasArg];
  80. remove_from_argv(argv, &argc, i, 1+hasArg);
  81. break;
  82. }
  83. }else if( zShort && strcmp(z,zShort)==0 ){
  84. zReturn = argv[i+hasArg];
  85. remove_from_argv(argv, &argc, i, 1+hasArg);
  86. break;
  87. }
  88. }
  89. *pArgc = argc;
  90. return zReturn;
  91. }
  92. /*
  93. ** State information
  94. */
  95. typedef struct Shell Shell;
  96. struct Shell {
  97. xjd1 *pDb; /* Open database connection */
  98. const char *zFile; /* Current filename */
  99. int nLine; /* Current line number */
  100. FILE *pIn; /* Input file */
  101. int isTTY; /* True if pIn is a TTY */
  102. int shellFlags; /* Flag settings */
  103. int testErrcode; /* Test case error code */
  104. String testOut; /* Output from a test case */
  105. char zModule[50]; /* Mame of current test module */
  106. char zTestCase[50]; /* Name of current testcase */
  107. String inBuf; /* Buffered input text */
  108. int nCase; /* Number of --testcase commands seen */
  109. int nTest; /* Number of tests performed */
  110. int nErr; /* Number of test errors */
  111. };
  112. /*
  113. ** Flag names
  114. */
  115. #define SHELL_PARSER_TRACE 0x00001
  116. #define SHELL_CMD_TRACE 0x00002
  117. #define SHELL_ECHO 0x00004
  118. #define SHELL_TEST_MODE 0x00008
  119. static const struct {
  120. const char *zName;
  121. int iValue;
  122. } FlagNames[] = {
  123. { "parser-trace", SHELL_PARSER_TRACE },
  124. { "cmd-trace", SHELL_CMD_TRACE },
  125. { "echo", SHELL_ECHO },
  126. { "test-mode", SHELL_TEST_MODE },
  127. };
  128. /*
  129. ** Convert a flag name into a mask.
  130. */
  131. static int flagMask(const char *zName){
  132. int i;
  133. for(i=0; i<sizeof(FlagNames)/sizeof(FlagNames[0]); i++){
  134. if( strcmp(zName, FlagNames[i].zName)==0 ){
  135. return FlagNames[i].iValue;
  136. }
  137. }
  138. return 0;
  139. }
  140. /*
  141. ** True for space characters.
  142. */
  143. static int shellIsSpace(char c){
  144. return c==' ' || c=='\t' || c=='\r' || c=='\n';
  145. }
  146. /*
  147. ** Set flags
  148. */
  149. static int shellSet(Shell *p, int argc, char **argv){
  150. int i;
  151. if( argc>=2 ){
  152. p->shellFlags |= flagMask(argv[1]);
  153. }else{
  154. for(i=0; i<sizeof(FlagNames)/sizeof(FlagNames[0]); i++){
  155. printf("%-20s %s\n",
  156. FlagNames[i].zName,
  157. (p->shellFlags & FlagNames[i].iValue)!=0 ? "on" : "off");
  158. }
  159. }
  160. return 0;
  161. }
  162. static int shellClear(Shell *p, int argc, char **argv){
  163. if( argc>=2 ){
  164. p->shellFlags &= ~flagMask(argv[1]);
  165. }
  166. return 0;
  167. }
  168. /*
  169. ** Command: .quit
  170. */
  171. static int shellQuit(Shell *p, int argc, char **argv){
  172. return 1;
  173. }
  174. /*
  175. ** Command: .testcase NAME
  176. */
  177. static int shellTestcase(Shell *p, int argc, char **argv){
  178. if( argc>=2 ){
  179. int n = strlen(argv[1]);
  180. if( n>=sizeof(p->zTestCase) ) n = sizeof(p->zTestCase)-1;
  181. memcpy(p->zTestCase, argv[1], n);
  182. p->zTestCase[n] = 0;
  183. xjd1StringTruncate(&p->testOut);
  184. p->testErrcode = XJD1_OK;
  185. p->shellFlags |= SHELL_TEST_MODE;
  186. }
  187. return 0;
  188. }
  189. /*
  190. ** Return non-zero if string z matches glob pattern zGlob and zero if the
  191. ** pattern does not match.
  192. **
  193. ** Globbing rules:
  194. **
  195. ** '*' Matches any sequence of zero or more characters.
  196. **
  197. ** '?' Matches exactly one character.
  198. **
  199. ** [...] Matches one character from the enclosed list of
  200. ** characters.
  201. **
  202. ** [^...] Matches one character not in the enclosed list.
  203. **
  204. ** '#' Matches any sequence of one or more digits with an
  205. ** optional + or - sign in front
  206. */
  207. static int strnotglob(const char *zGlob, const char *z){
  208. int c, c2;
  209. int invert;
  210. int seen;
  211. while( (c = (*(zGlob++)))!=0 ){
  212. if( c=='*' ){
  213. while( (c=(*(zGlob++))) == '*' || c=='?' ){
  214. if( c=='?' && (*(z++))==0 ) return 0;
  215. }
  216. if( c==0 ){
  217. return 1;
  218. }else if( c=='[' ){
  219. while( *z && strnotglob(zGlob-1,z)==0 ){
  220. z++;
  221. }
  222. return (*z)!=0;
  223. }
  224. while( (c2 = (*(z++)))!=0 ){
  225. while( c2!=c ){
  226. c2 = *(z++);
  227. if( c2==0 ) return 0;
  228. }
  229. if( strnotglob(zGlob,z) ) return 1;
  230. }
  231. return 0;
  232. }else if( c=='?' ){
  233. if( (*(z++))==0 ) return 0;
  234. }else if( c=='[' ){
  235. int prior_c = 0;
  236. seen = 0;
  237. invert = 0;
  238. c = *(z++);
  239. if( c==0 ) return 0;
  240. c2 = *(zGlob++);
  241. if( c2=='^' ){
  242. invert = 1;
  243. c2 = *(zGlob++);
  244. }
  245. if( c2==']' ){
  246. if( c==']' ) seen = 1;
  247. c2 = *(zGlob++);
  248. }
  249. while( c2 && c2!=']' ){
  250. if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
  251. c2 = *(zGlob++);
  252. if( c>=prior_c && c<=c2 ) seen = 1;
  253. prior_c = 0;
  254. }else{
  255. if( c==c2 ){
  256. seen = 1;
  257. }
  258. prior_c = c2;
  259. }
  260. c2 = *(zGlob++);
  261. }
  262. if( c2==0 || (seen ^ invert)==0 ) return 0;
  263. }else if( c=='#' ){
  264. if( (z[0]=='-' || z[0]=='+') && (z[1]>='0' && z[1]<='9') ) z++;
  265. if( z[0]<'0' || z[0]>'9' ) return 0;
  266. z++;
  267. while( z[0]>='0' && z[0]<='9' ){ z++; }
  268. }else{
  269. if( c!=(*(z++)) ) return 0;
  270. }
  271. }
  272. return *z==0;
  273. }
  274. static int strglob(const char *zGlob, const char *z){
  275. return !strnotglob(zGlob,z);
  276. }
  277. /*
  278. ** Verify the output from a test.
  279. */
  280. static int shellResultCheck(
  281. Shell *p,
  282. int argc,
  283. char **argv,
  284. int(*xCmp)(const char*,const char*)
  285. ){
  286. const char *zAns = argc>=2 ? argv[1] : "";
  287. const char *zRes = xjd1StringText(&p->testOut);
  288. if( zRes==0 ) zRes = "";
  289. p->nTest++;
  290. if( xCmp(zAns, zRes) ){
  291. p->nErr++;
  292. fprintf(stderr, " Test failed: %s.%s\n Expected: [%s]\n Got: [%s]\n",
  293. p->zFile, p->zTestCase, argv[1], zRes);
  294. }
  295. return 0;
  296. }
  297. /* Command: .result PATTERN */
  298. static int shellResult(Shell *p, int argc, char **argv){
  299. shellResultCheck(p,argc,argv,strcmp);
  300. return 0;
  301. }
  302. /* Command: .glob PATTERN */
  303. static int shellGlob(Shell *p, int argc, char **argv){
  304. shellResultCheck(p,argc,argv,strglob);
  305. return 0;
  306. }
  307. /* Command: .notglob PATTERN */
  308. static int shellNotGlob(Shell *p, int argc, char **argv){
  309. shellResultCheck(p,argc,argv,strnotglob);
  310. return 0;
  311. }
  312. /* Command: .json PATTERN */
  313. static int shellJson(Shell *p, int argc, char **argv){
  314. char *aArg[2];
  315. String tidy;
  316. assert( argc==1 || argc==2 );
  317. xjd1StringInit(&tidy, 0, 0);
  318. if( argc==2 ){
  319. int rc = xjd1JsonTidy(&tidy, argv[1]);
  320. assert( rc==XJD1_OK );
  321. }
  322. aArg[0] = argv[0];
  323. aArg[1] = tidy.zBuf;
  324. shellResultCheck(p, argc, aArg, strcmp);
  325. xjd1StringClear(&tidy);
  326. return 0;
  327. }
  328. /*
  329. ** Command: .open FILENAME
  330. */
  331. static int shellOpenDB(Shell *p, int argc, char **argv){
  332. if( argc>=2 ){
  333. int rc;
  334. if( p->pDb ) xjd1_close(p->pDb);
  335. rc = xjd1_open(0, argv[1], &p->pDb);
  336. if( rc!=XJD1_OK ){
  337. fprintf(stderr, "%s:%d: cannot open \"%s\"\n",
  338. p->zFile, p->nLine, argv[1]);
  339. p->nErr++;
  340. p->pDb = 0;
  341. }
  342. }
  343. return 0;
  344. }
  345. /*
  346. ** Command: .new FILENAME
  347. */
  348. static int shellNewDB(Shell *p, int argc, char **argv){
  349. if( argc>=2 ){
  350. if( p->pDb ) xjd1_close(p->pDb);
  351. p->pDb = 0;
  352. unlink(argv[1]);
  353. shellOpenDB(p, argc, argv);
  354. }
  355. return 0;
  356. }
  357. /* Forward declaration */
  358. static void processOneFile(Shell*, const char*);
  359. /*
  360. ** Command: .read FILENAME
  361. ** Read and process text from a file.
  362. */
  363. static int shellRead(Shell *p, int argc, char **argv){
  364. if( argc>=2 ){
  365. int nErr = p->nErr;
  366. String newFile;
  367. char *zNew;
  368. xjd1StringInit(&newFile, 0, 0);
  369. if( argv[1][0]=='/' || strcmp(p->zFile,"-")==0 ){
  370. xjd1StringAppend(&newFile, argv[1], -1);
  371. }else{
  372. int i, j;
  373. for(i=j=0; p->zFile[i]; i++){ if( p->zFile[i]=='/' ) j = i; }
  374. xjd1StringAppendF(&newFile, "%.*s/%s", j, p->zFile, argv[1]);
  375. }
  376. zNew = xjd1StringText(&newFile);
  377. printf("BEGIN %s\n", zNew);
  378. processOneFile(p, zNew);
  379. printf("END %s - %d new errors\n", zNew, p->nErr - nErr);
  380. xjd1StringClear(&newFile);
  381. }
  382. return 0;
  383. }
  384. /*
  385. ** Command: .breakpoint
  386. ** A place to seet a breakpoint
  387. */
  388. static int shellBreakpoint(Shell *p, int argc, char **argv){
  389. /* no-op */
  390. return 0;
  391. }
  392. /*
  393. ** Command: .puts TEXT
  394. */
  395. static int shellPuts(Shell *p, int argc, char **argv){
  396. if( argc>=2 ) printf("%s\n", argv[1]);
  397. return 0;
  398. }
  399. static void checkForTestError(Shell *p){
  400. if( (p->shellFlags & SHELL_TEST_MODE) && p->testErrcode ){
  401. fprintf(stderr, "%s:%d: ERROR: %s\n", p->zFile, p->nLine, p->testOut.zBuf);
  402. xjd1StringTruncate(&p->testOut);
  403. p->testErrcode = XJD1_OK;
  404. p->nErr++;
  405. }
  406. }
  407. /*
  408. ** Process a command intended for this shell - not for the database.
  409. **
  410. ** Return 1 to abort or 0 to continue.
  411. */
  412. static int processMetaCommand(Shell *p){ char *z;
  413. int n;
  414. char *azArg[2];
  415. int nArg;
  416. int i;
  417. int rc;
  418. static const struct shcmd {
  419. const char *zCmd; /* Name of the command */
  420. int (*xCmd)(Shell*,int,char**); /* Procedure to run the command */
  421. const char *zHelp; /* Help string */
  422. } cmds[] = {
  423. { "quit", shellQuit, ".quit" },
  424. { "testcase", shellTestcase, ".testcase NAME" },
  425. { "json", shellJson, ".json TEXT" },
  426. { "result", shellResult, ".result TEXT" },
  427. { "glob", shellGlob, ".glob PATTERN" },
  428. { "notglob", shellNotGlob, ".notglob PATTERN" },
  429. { "puts", shellPuts, ".puts TEXT" },
  430. { "read", shellRead, ".read FILENAME" },
  431. { "open", shellOpenDB, ".open DATABASE" },
  432. { "new", shellNewDB, ".new DATABASE" },
  433. { "set", shellSet, ".set FLAG" },
  434. { "clear", shellClear, ".clear FLAG" },
  435. { "breakpoint", shellBreakpoint, ".breakpoint" },
  436. };
  437. /* Remove trailing whitespace from the command */
  438. z = xjd1StringText(&p->inBuf);
  439. if( p->shellFlags & SHELL_ECHO ){
  440. fprintf(stdout, "%s", z);
  441. }
  442. n = xjd1StringLen(&p->inBuf);
  443. while( n>0 && shellIsSpace(z[n-1]) ){ n--; }
  444. z[n] = 0;
  445. /* If the last character of the command is a backslash, the command
  446. ** arguments continue onto the next line. Return early without truncating
  447. ** the input buffer in this case. */
  448. if( z[n-1]=='\\' ){
  449. p->inBuf.nUsed = n;
  450. z[n-1] = ' ';
  451. return 0;
  452. }
  453. /* Find the command name text and its argument (if any) */
  454. z++;
  455. azArg[0] = z;
  456. for(i=0; z[i] && !shellIsSpace(z[i]); i++){}
  457. if( z[i] ){
  458. z[i] = 0;
  459. z += i+1;
  460. while( shellIsSpace(z[0]) ) z++;
  461. azArg[1] = z;
  462. nArg = 2;
  463. }else{
  464. azArg[1] = 0;
  465. nArg = 1;
  466. }
  467. if( strcmp(azArg[0], "error") ){
  468. checkForTestError(p);
  469. }else{
  470. p->testErrcode = XJD1_OK;
  471. azArg[0] = "result";
  472. }
  473. /* Find the command */
  474. for(i=0; i<sizeof(cmds)/sizeof(cmds[0]); i++){
  475. if( strcmp(azArg[0], cmds[i].zCmd)==0 ){
  476. rc = cmds[i].xCmd(p, nArg, azArg);
  477. break;
  478. }
  479. }
  480. if( i>=sizeof(cmds)/sizeof(cmds[0]) ){
  481. p->nErr++;
  482. fprintf(stderr, "%s:%d: unknown command \".%s\"\n",
  483. p->zFile, p->nLine, azArg[0]);
  484. return 1;
  485. }
  486. xjd1StringTruncate(&p->inBuf);
  487. return rc;
  488. }
  489. /*
  490. ** Append text to the end of the testOut string. If there is already
  491. ** text in the testOut buffer, prepent a space first.
  492. */
  493. static void appendTestOut(Shell *p, const char *z, int n){
  494. if( xjd1StringLen(&p->testOut)>0 ){
  495. xjd1StringAppend(&p->testOut, " ", 1);
  496. }
  497. xjd1StringAppend(&p->testOut, z, n);
  498. }
  499. /*
  500. ** Run a single statment.
  501. */
  502. static void processOneStatement(Shell *p, const char *zCmd){
  503. xjd1_stmt *pStmt;
  504. int N, rc;
  505. int once = 0;
  506. if( p->shellFlags & SHELL_ECHO ){
  507. fprintf(stdout, "%s\n", zCmd);
  508. }
  509. xjd1_config(p->pDb, XJD1_CONFIG_PARSERTRACE,
  510. (p->shellFlags & SHELL_PARSER_TRACE)!=0);
  511. rc = xjd1_stmt_new(p->pDb, zCmd, &pStmt, &N);
  512. if( rc==XJD1_OK ){
  513. if( p->shellFlags & SHELL_CMD_TRACE ){
  514. char *zTrace = xjd1_stmt_debug_listing(pStmt);
  515. if( zTrace ) printf("%s", zTrace);
  516. free(zTrace);
  517. }
  518. do{
  519. rc = xjd1_stmt_step(pStmt);
  520. if( rc==XJD1_ROW ){
  521. const char *zValue;
  522. xjd1_stmt_value(pStmt, &zValue);
  523. if( (p->shellFlags & SHELL_TEST_MODE)==0 ){
  524. if( once==0 && (p->shellFlags & SHELL_ECHO)!=0 ){
  525. printf("--------- query results ---------\n");
  526. once = 1;
  527. }
  528. printf("%s\n", zValue);
  529. }else{
  530. appendTestOut(p, zValue, -1);
  531. }
  532. }
  533. }while( rc==XJD1_ROW );
  534. xjd1_stmt_delete(pStmt);
  535. }else{
  536. if( p->shellFlags & SHELL_TEST_MODE ){
  537. appendTestOut(p, xjd1_errcode_name(p->pDb), -1);
  538. appendTestOut(p, xjd1_errmsg(p->pDb), -1);
  539. p->testErrcode = rc;
  540. }else{
  541. fprintf(stderr, "%s:%d: ERROR: %s\n",
  542. p->zFile, p->nLine, xjd1_errmsg(p->pDb));
  543. p->nErr++;
  544. }
  545. }
  546. if( once ) printf("---------------------------------\n");
  547. }
  548. /*
  549. ** Process one or more database commands
  550. */
  551. static void processScript(Shell *p){
  552. char *z, c;
  553. int i;
  554. checkForTestError(p);
  555. if( p->pDb==0 ){
  556. if( p->shellFlags & SHELL_ECHO ) printf("%s", xjd1StringText(&p->inBuf));
  557. xjd1StringTruncate(&p->inBuf);
  558. return;
  559. }
  560. while( xjd1StringLen(&p->inBuf) ){
  561. z = xjd1StringText(&p->inBuf);
  562. for(i=0; shellIsSpace(z[i]); i++){}
  563. if( z[i]=='-' && z[i+1]=='-' ){
  564. for(i+=2; z[i] && z[i]!='\n'; i++){}
  565. if( p->shellFlags & SHELL_ECHO ){
  566. printf("%.*s\n", i, z);
  567. }
  568. while( shellIsSpace(z[i]) ) i++;
  569. xjd1StringRemovePrefix(&p->inBuf, i);
  570. break;
  571. }
  572. if( z[i]==0 ){
  573. xjd1StringTruncate(&p->inBuf);
  574. break;
  575. }
  576. for(; z[i]; i++){
  577. if( z[i]!=';' ) continue;
  578. c = z[i+1];
  579. z[i+1] = 0;
  580. if( !xjd1_complete(z) ){
  581. z[i+1] = c;
  582. continue;
  583. }
  584. processOneStatement(p, z);
  585. z[i+1] = c;
  586. while( shellIsSpace(z[i+1]) ){ i++; }
  587. xjd1StringRemovePrefix(&p->inBuf, i+1);
  588. i = 0;
  589. break;
  590. }
  591. if( i>0 ) break;
  592. }
  593. }
  594. /*
  595. ** Process a single file of input by reading the text of the file and
  596. ** sending statements into the XJD1 database connection.
  597. */
  598. static void processOneFile(
  599. Shell *p, /* Current state of the shell */
  600. const char *zFile /* Name of file to process. "-" for stdin. */
  601. ){
  602. Shell savedState;
  603. char *zIn;
  604. char zLine[1000];
  605. savedState = *p;
  606. if( strcmp(zFile, "-")==0 ){
  607. p->pIn = stdin;
  608. p->isTTY = 1;
  609. }else{
  610. p->pIn = fopen(zFile, "r");
  611. if( p->pIn==0 ){
  612. fprintf(stderr, "%s:%d: cannot open \"%s\"\n", p->zFile, p->nLine, zFile);
  613. p->nErr++;
  614. return;
  615. }
  616. p->isTTY = 0;
  617. }
  618. p->zFile = zFile;
  619. p->nLine = 0;
  620. while( !feof(p->pIn) ){
  621. if( p->isTTY ){
  622. printf("%s", xjd1StringLen(&p->inBuf)>0 ? " ...> " : "xjd1> ");
  623. }
  624. if( fgets(zLine, sizeof(zLine), p->pIn)==0 ) break;
  625. p->nLine++;
  626. if( zLine[0]=='.' && xjd1StringLen(&p->inBuf) ){
  627. fprintf(stderr, "%s:%d: missing ';'\n", p->zFile, p->nLine);
  628. xjd1StringTruncate(&p->inBuf);
  629. }
  630. xjd1StringAppend(&p->inBuf, zLine, -1);
  631. zIn = xjd1StringText(&p->inBuf);
  632. if( zIn[0]=='.' ){
  633. if( processMetaCommand(p) ) break;
  634. }else{
  635. processScript(p);
  636. }
  637. }
  638. if( p->isTTY==0 ){
  639. fclose(p->pIn);
  640. }
  641. p->zFile = savedState.zFile;
  642. p->nLine = savedState.nLine;
  643. p->pIn = savedState.pIn;
  644. p->isTTY = savedState.isTTY;
  645. }
  646. int main(int argc, char **argv){
  647. int i;
  648. Shell s;
  649. memset(&s, 0, sizeof(s));
  650. if( argc>1 ){
  651. for(i=1; i<argc; i++){
  652. processOneFile(&s, argv[i]);
  653. }
  654. }else{
  655. processOneFile(&s, "-");
  656. }
  657. if( s.nTest ){
  658. printf("%d errors from %d tests\n", s.nErr, s.nTest);
  659. }
  660. if( s.pDb ) xjd1_close(s.pDb);
  661. xjd1StringClear(&s.inBuf);
  662. xjd1StringClear(&s.testOut);
  663. return 0;
  664. }