Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

790 lines
21 KiB

  1. /*++
  2. Copyright (c) 1997-2000 Microsoft Corporation
  3. Module Name:
  4. EzParse.cpp
  5. Abstract:
  6. Poor man C/C++/any file parser.
  7. Author:
  8. Gor Nishanov (gorn) 03-Apr-1999
  9. Revision History:
  10. Gor Nishanov (gorn) 03-Apr-1999 -- hacked together to prove that this can work
  11. GorN: 29-Sep-2000 - fix enumeration bug
  12. GorN: 29-Sep-2000 - add support for KdPrintEx like function
  13. GorN: 09-Oct-2000 - fixed "//" in the string bug
  14. GorN: 23-Oct-2000 - IGNORE_CPP_COMMENT, IGNORE_POUND_COMMENT options added
  15. GorN: 16-Apr-2001 - Properly handle \" within a string
  16. ToDo:
  17. Clean it up
  18. --*/
  19. #define STRICT
  20. #include <stdio.h>
  21. #include <windows.h>
  22. #pragma warning(disable: 4100)
  23. #include <algorithm>
  24. #include <xstring>
  25. #include "ezparse.h"
  26. DWORD ErrorCount = 0;
  27. PEZPARSE_CONTEXT EzParseCurrentContext = NULL;
  28. // To force build tool to recognize our errors
  29. #define BUILD_PREFIX_FNAME "cl %s\n"
  30. #define BUILD_PREFIX "cl wpp\n"
  31. void ExParsePrintErrorPrefix(FILE* f, char * func)
  32. {
  33. ++ErrorCount;
  34. if (EzParseCurrentContext) {
  35. fprintf(f,BUILD_PREFIX_FNAME "%s(%d) : error : (%s)",
  36. EzParseCurrentContext->filename,
  37. EzParseCurrentContext->filename,
  38. EzGetLineNo(EzParseCurrentContext->currentStart, EzParseCurrentContext),
  39. func);
  40. } else {
  41. fprintf(f,BUILD_PREFIX "wpp : error : (%s)", func);
  42. }
  43. }
  44. LPCSTR skip_stuff_in_quotes(LPCSTR q, LPCSTR begin)
  45. {
  46. char ch = *q;
  47. if (q > begin) {
  48. if (q[-1] == '\\') {
  49. return q - 1;
  50. }
  51. }
  52. for(;;) {
  53. if (q == begin) {
  54. return 0;
  55. }
  56. --q;
  57. if (*q == ch && ( (q == begin) || (q[-1] != '\\') ) ) {
  58. return q;
  59. }
  60. }
  61. }
  62. void
  63. adjust_pair( STR_PAIR& str )
  64. /*++
  65. Shrink the pair to remote leading and trailing whitespace
  66. */
  67. {
  68. while (str.beg < str.end && isspace(*str.beg)) { ++str.beg; }
  69. while (str.beg < str.end && isspace(str.end[-1])) { --str.end; }
  70. }
  71. void
  72. remove_cpp_comment(STR_PAIR& str)
  73. {
  74. LPCSTR p = str.beg;
  75. // printf("rcb: %s\n", std::string(str.beg, str.end).c_str());
  76. // let's cut the comment in the beginning of the string
  77. for(;;) {
  78. // skip the whitespace
  79. for(;;) {
  80. if (p == str.end) return;
  81. if (!isspace(*p)) break;
  82. ++p;
  83. }
  84. str.beg = p;
  85. if (p + 1 == str.end) return;
  86. if (p[0] == '/' && p[1] == '/') {
  87. // we have a comment. Need to get to the end of the comment
  88. p += 2;
  89. // printf("rcd: %s %s\n", std::string(str.beg, p).c_str(), std::string(p,str.end).c_str());
  90. for(;;) {
  91. if (p == str.end) return;
  92. if (*p == '\r' || *p == '\n') {
  93. str.beg = p;
  94. break;
  95. }
  96. ++p;
  97. }
  98. } else {
  99. // no leading comment
  100. break;
  101. }
  102. }
  103. // printf("rcc: %s %s\n", std::string(str.beg, p).c_str(), std::string(p,str.end).c_str());
  104. for(;;) {
  105. if (p == str.end) return;
  106. if (*p == '"') {
  107. // don't look for comments within a string
  108. for(;;) {
  109. if (++p == str.end) return;
  110. if (*p == '"' && p[-1] != '\\') break;
  111. }
  112. ++p;
  113. continue;
  114. }
  115. if (p + 1 == str.end) return;
  116. if (p[0] == '/')
  117. if (p[1] == '/') break;
  118. else p += 2;
  119. else
  120. p += 1;
  121. }
  122. str.end = p;
  123. // printf("rce: %s\n", std::string(str.beg, str.end).c_str());
  124. }
  125. DWORD
  126. ScanForFunctionCallsEx(
  127. IN LPCSTR begin,
  128. IN LPCSTR end,
  129. IN EZPARSE_CALLBACK Callback,
  130. IN PVOID Context,
  131. IN OUT PEZPARSE_CONTEXT ParseContext,
  132. IN DWORD Options
  133. )
  134. /*++
  135. Routine Description:
  136. Scan the buffer for expressions that looks like function calls,
  137. i.e name(sd,sdf,sdf,sdf,sdf); . It will treat variable declaration
  138. with constructor call as a function call as well.
  139. Inputs:
  140. begin, end -- pointers to the beginning and the end of the buffer
  141. Callback -- to be called for every function
  142. Context -- opaque context to be passed to callback
  143. ParseContext -- holds current parse state information
  144. --*/
  145. {
  146. LPCSTR p = begin;
  147. LPCSTR q, funcNameEnd;
  148. DWORD Status = ERROR_SUCCESS;
  149. bool double_par = FALSE;
  150. no_match:
  151. if (Options & NO_SEMICOLON) {
  152. q = end;
  153. Options &= ~NO_SEMICOLON;
  154. } else {
  155. do {
  156. ++p;
  157. if (p == end) {
  158. return Status;
  159. }
  160. } while ( *p != ';' );
  161. // Ok. Now p points to ';' //
  162. q = p;
  163. }
  164. do {
  165. if (--q <= begin) {
  166. goto no_match;
  167. }
  168. } while ( isspace(*q) );
  169. // Now q points on the first non white space character //
  170. // If it is not a ')' then we need to search for the next ';' //
  171. if (*q != ')') {
  172. goto no_match;
  173. }
  174. ParseContext->macroEnd = q;
  175. // Ok. This is a function call (definition).
  176. // Now, let's go and collect all the arguments of the first level and
  177. // get to the name of the function
  178. // HACKHACK
  179. // We need a special case for functions that looks like
  180. // KdPrintEx((Level, Indent, Msg, ...));
  181. // Essentially, we need to treat them as
  182. // KdPrintEx(Level, Indent, Msg, ...);
  183. const char *r = q;
  184. // check if we have ));
  185. do {
  186. if (--r <= begin) break; // no "));"
  187. } while ( isspace(*r) );
  188. double_par = r > begin && *r == ')';
  189. if (double_par) {
  190. q = r;
  191. // we assume that this is KdPrint((a,b,c,d,...)); at the moment
  192. // if our assumtion is wrong, we will retry the loop below
  193. }
  194. retry:
  195. {
  196. int level = 0;
  197. LPCSTR ends[128], *current = ends;
  198. STR_PAIR strs[128];
  199. // LPCSTR closing_parenthisis = q;
  200. *current = q;
  201. for(;;) {
  202. --q;
  203. if (q <= begin) {
  204. goto no_match;
  205. }
  206. switch (*q) {
  207. case ',': if (!level) *++current = q; break;
  208. case '(': if (level) --level; else goto maybe_match; break;
  209. case ')': ++level; break;
  210. case '\'':
  211. case '"':
  212. q = skip_stuff_in_quotes(q, begin); if(!q) goto no_match;
  213. }
  214. }
  215. maybe_match:
  216. *++current = q;
  217. funcNameEnd = q;
  218. // now q point to '(' we need to find name of the function //
  219. do {
  220. --q;
  221. if (q <= begin) {
  222. goto no_match;
  223. }
  224. } while(isspace(*q));
  225. // now q points to first not white character
  226. if (double_par) {
  227. // if we see )); and found a matching
  228. // parenthesis for the inner one, we can have
  229. // one of two cases
  230. // 1) KdPrint((a,b,c,d,...));
  231. // or
  232. // 2) DebugPrint(a,b,(c,d));
  233. // If it is the latter, we just need to
  234. // retry the scanning, now using leftmost bracket as a starting point
  235. if (*q != '(') {
  236. // restore q to the rightmost parenthesis
  237. q = ParseContext->macroEnd;
  238. double_par = FALSE;
  239. goto retry;
  240. }
  241. funcNameEnd = q;
  242. // now q point to '(' we need to find name of the function //
  243. do {
  244. --q;
  245. if (q <= begin) {
  246. goto no_match;
  247. }
  248. } while(isspace(*q));
  249. }
  250. // now q points to first non white character
  251. // BUGBUG '{' and '}' are allowed only in config files
  252. if (*q == '}') {
  253. for(;;) {
  254. if (--q < begin) goto no_match;
  255. if (*q == '{') break;
  256. }
  257. if (--q < begin) goto no_match;
  258. }
  259. if (!(isalpha(*q) || isdigit(*q) || *q == '_')) {
  260. goto no_match;
  261. }
  262. do {
  263. --q;
  264. if (q <= begin) {
  265. goto found;
  266. }
  267. } while ( isalpha(*q) || isdigit(*q) || *q == '_');
  268. ++q;
  269. if (isdigit(*q)) {
  270. goto no_match;
  271. }
  272. found:
  273. if (Options & IGNORE_COMMENT)
  274. // Verify that it is not a comment
  275. // # sign in the beginning of the line
  276. {
  277. LPCSTR line = q;
  278. //
  279. // Find the beginning of the line or file
  280. //
  281. for(;;) {
  282. if (line == begin) {
  283. // Beginning of the file. Good enough
  284. break;
  285. }
  286. if (Options & IGNORE_CPP_COMMENT && line[0] == '/' && line[1] == '/') {
  287. // C++ comment. Ignore
  288. goto no_match;
  289. }
  290. if (*line == 13 || *line == 10) {
  291. ++line;
  292. break;
  293. }
  294. --line;
  295. }
  296. //
  297. // If the first non-white character is #, ignore it
  298. //
  299. while (line <= q) {
  300. if ( *line != ' ' && *line != '\t' ) {
  301. break;
  302. }
  303. ++line;
  304. }
  305. if (Options & IGNORE_POUND_COMMENT && *line == '#') {
  306. goto no_match;
  307. }
  308. }
  309. {
  310. int i = 0;
  311. strs[0].beg = q;
  312. strs[0].end = funcNameEnd;
  313. adjust_pair(strs[0]);
  314. while (current != ends) {
  315. // putchar('<');printrange(current[0]+1, current[-1]); putchar('>');
  316. ++i;
  317. strs[i].beg = current[0]+1;
  318. --current;
  319. strs[i].end = current[0];
  320. adjust_pair(strs[i]);
  321. remove_cpp_comment(strs[i]);
  322. }
  323. ParseContext->currentStart = strs[0].beg;
  324. ParseContext->currentEnd = strs[0].end;
  325. ParseContext->doubleParent = double_par;
  326. Status = Callback(strs, i+1, Context, ParseContext);
  327. if (Status != ERROR_SUCCESS) {
  328. return Status;
  329. }
  330. }
  331. goto no_match;
  332. }
  333. // return ERROR_SUCCESS; // unreachable code
  334. }
  335. DWORD
  336. ScanForFunctionCalls(
  337. IN LPCSTR begin,
  338. IN LPCSTR end,
  339. IN EZPARSE_CALLBACK Callback,
  340. IN PVOID Context,
  341. IN OUT PEZPARSE_CONTEXT ParseContext
  342. )
  343. {
  344. return ScanForFunctionCallsEx(
  345. begin, end, Callback, Context,
  346. ParseContext, IGNORE_COMMENT);
  347. }
  348. DWORD
  349. EzGetLineNo(
  350. IN LPCSTR Ptr,
  351. IN OUT PEZPARSE_CONTEXT ParseContext
  352. )
  353. /*++
  354. Computes a line number based on
  355. an pointer within a buffer.
  356. Last known lineno/pointer is cached in ParseContext
  357. for performance
  358. */
  359. {
  360. int count = ParseContext->scannedLineCount;
  361. LPCSTR downto = ParseContext->lastScanned;
  362. LPCSTR p = Ptr;
  363. if (downto > p) {
  364. count = 1;
  365. downto = ParseContext->start;
  366. }
  367. while (p > downto) {
  368. if (*p == '\n') {
  369. ++count;
  370. }
  371. --p;
  372. }
  373. ParseContext->scannedLineCount = count;
  374. ParseContext->lastScanned = Ptr;
  375. return count;
  376. }
  377. const char begin_wpp[] = "begin_wpp";
  378. const char end_wpp[] = "end_wpp";
  379. const char define_[] = "#define";
  380. const char enum_[] = "enum ";
  381. enum {
  382. begin_wpp_size = (sizeof(begin_wpp)-1),
  383. end_wpp_size = (sizeof(end_wpp)-1),
  384. define_size = (sizeof(define_)-1),
  385. enum_size = (sizeof(enum_)-1),
  386. };
  387. typedef struct _SmartContext {
  388. EZPARSE_CALLBACK Callback;
  389. PVOID Context;
  390. OUT PEZPARSE_CONTEXT ParseContext;
  391. std::string buf;
  392. } SMART_CONTEXT, *PSMART_CONTEXT;
  393. void DoEnumItems(PSTR_PAIR name, LPCSTR begin, LPCSTR end, PSMART_CONTEXT ctx)
  394. {
  395. LPCSTR p,q;
  396. ULONG value = 0;
  397. STR_PAIR Item;
  398. BOOL First = TRUE;
  399. ctx->buf.assign("CUSTOM_TYPE(");
  400. ctx->buf.append(name->beg, name->end);
  401. ctx->buf.append(", ItemListLong");
  402. p = begin;
  403. while(begin < end && isspace(*--end)); // skip spaces
  404. if (begin < end && *end != ',') ++end;
  405. for(;p < end;) {
  406. Item.beg = p;
  407. q = p;
  408. for(;;) {
  409. if (q == end) {
  410. goto enum_end;
  411. }
  412. if (*q == ',' || *q == '}') {
  413. // valueless item. Use current
  414. Item.end = q;
  415. break;
  416. } else if (*q == '=') {
  417. // need to calc the value. Skip for now //
  418. Item.end = q;
  419. while (q < end && *q != ',') ++q;
  420. break;
  421. }
  422. ++q;
  423. }
  424. adjust_pair(Item);
  425. if (Item.beg == Item.end) {
  426. break;
  427. }
  428. if (First) {ctx->buf.append("("); First = FALSE;} else ctx->buf.append(",");
  429. ctx->buf.append(Item.beg, Item.end);
  430. if (q == end) break;
  431. p = q+1;
  432. ++value;
  433. }
  434. enum_end:;
  435. ctx->buf.append(") )");
  436. ScanForFunctionCallsEx(
  437. &ctx->buf[0], &ctx->buf[0] + ctx->buf.size(), ctx->Callback, ctx->Context,
  438. ctx->ParseContext, NO_SEMICOLON);
  439. Flood("enum %s\n", ctx->buf.c_str());
  440. }
  441. void DoEnum(LPCSTR begin, LPCSTR end, PSMART_CONTEXT Ctx)
  442. {
  443. LPCSTR p, q, current = begin;
  444. for(;;) {
  445. p = std::search(current, end, enum_, enum_ + enum_size);
  446. if (p == end) break;
  447. q = std::find(p, end, '{');
  448. if (q == end) break;
  449. // let's figure out enum name //
  450. STR_PAIR name;
  451. name.beg = p + enum_size;
  452. name.end = q;
  453. adjust_pair(name);
  454. if ( *name.beg == '_' ) ++name.beg;
  455. p = q+1; // past "{";
  456. q = std::find(p, end, '}');
  457. if (q == end) break;
  458. if (name.end > name.beg) {
  459. DoEnumItems(&name, p, q, Ctx);
  460. } else {
  461. ReportError("Cannot handle tagless enums yet");
  462. }
  463. current = q;
  464. }
  465. }
  466. DWORD
  467. SmartScan(
  468. IN LPCSTR begin,
  469. IN LPCSTR end,
  470. IN EZPARSE_CALLBACK Callback,
  471. IN PVOID Context,
  472. IN OUT PEZPARSE_CONTEXT ParseContext
  473. )
  474. {
  475. LPCSTR block_start, block_end, current = begin;
  476. SMART_CONTEXT Ctx;
  477. Ctx.Callback = Callback;
  478. Ctx.Context = Context;
  479. Ctx.ParseContext = ParseContext;
  480. for(;;) {
  481. block_start = std::search(current, end, begin_wpp, begin_wpp + begin_wpp_size);
  482. if (block_start == end) break;
  483. current = block_start;
  484. block_end = std::search(block_start, end, end_wpp, end_wpp + end_wpp_size);
  485. if (block_end == end) break;
  486. Flood("Block Found\n");
  487. // determine block type //
  488. // begin_wpp enum
  489. // begin_wpp config
  490. // begin_wpp func
  491. // begin_wpp define
  492. LPCSTR block_type = block_start + begin_wpp_size + 1;
  493. Flood("block_type = %c%c%c%c\n", block_type[0],block_type[1],block_type[2],block_type[3]);
  494. if (memcmp(block_type, "enum", 4) == 0) {
  495. // do enum block //
  496. DoEnum( block_type + 4, block_end, &Ctx );
  497. } else if (memcmp(block_type, "config", 6) == 0) {
  498. // do config block //
  499. ScanForFunctionCallsEx(block_type + 6, block_end, Callback, Context, ParseContext, IGNORE_POUND_COMMENT);
  500. } else if (memcmp(block_type, "func", 4) == 0) {
  501. LPCSTR func_start, func_end;
  502. current = block_type + 6;
  503. for(;;) {
  504. func_start = std::search(current, block_end, define_, define_ + define_size);
  505. if (func_start == block_end) break;
  506. func_start += define_size;
  507. while (isspace(*func_start)) {
  508. if(++func_start == block_end) goto no_func;
  509. }
  510. func_end = func_start;
  511. while (!isspace(*func_end)) {
  512. if(*func_end == '(') break;
  513. if(++func_end == block_end) goto no_func;
  514. }
  515. if(*func_end != '(') {
  516. Ctx.buf.assign(func_start, func_end);
  517. Ctx.buf.append("(MSGARGS)");
  518. } else {
  519. func_end = std::find(func_start, block_end, ')');
  520. if (func_end == block_end) break;
  521. ++func_end; // include ")"
  522. Ctx.buf.assign(func_start, func_end);
  523. }
  524. Flood("Func %s\n", Ctx.buf.c_str());
  525. ScanForFunctionCallsEx(
  526. Ctx.buf.begin(), Ctx.buf.end(), Callback, Context,
  527. ParseContext, NO_SEMICOLON);
  528. current = func_end;
  529. }
  530. no_func:;
  531. } else if (memcmp(block_type, "define", 6) == 0) {
  532. // do define block
  533. } else {
  534. ReportError("Unknown block");
  535. }
  536. current = block_end + end_wpp_size;
  537. }
  538. if (current == begin) {
  539. // file without marking, let's do default processing
  540. Unusual("Reverting back to plain scan\n");
  541. ScanForFunctionCalls(begin, end, Callback, Context, ParseContext);
  542. }
  543. return ERROR_SUCCESS;
  544. }
  545. DWORD
  546. EzParse(
  547. IN LPCSTR filename,
  548. IN EZPARSE_CALLBACK Callback,
  549. IN PVOID Context)
  550. {
  551. // return EzParseEx(filename, SmartScan, Callback, Context);
  552. return EzParseEx(filename, ScanForFunctionCalls, Callback, Context, IGNORE_POUND_COMMENT);
  553. }
  554. DWORD
  555. EzParseWithOptions(
  556. IN LPCSTR filename,
  557. IN EZPARSE_CALLBACK Callback,
  558. IN PVOID Context,
  559. IN DWORD Options)
  560. {
  561. return EzParseEx(filename, ScanForFunctionCalls, Callback, Context, Options);
  562. }
  563. DWORD
  564. EzParseEx(
  565. IN LPCSTR filename,
  566. IN PROCESSFILE_CALLBACK ProcessData,
  567. IN EZPARSE_CALLBACK Callback,
  568. IN PVOID Context,
  569. IN DWORD Options
  570. )
  571. {
  572. DWORD Status = ERROR_SUCCESS;
  573. HANDLE mapping;
  574. HANDLE file = CreateFileA(filename,
  575. GENERIC_READ, FILE_SHARE_READ, NULL,
  576. OPEN_EXISTING, 0, 0);
  577. if (file == INVALID_HANDLE_VALUE) {
  578. Status = GetLastError();
  579. ReportError("Cannot open file %s, error %u\n", filename, Status );
  580. return Status;
  581. }
  582. DWORD size = GetFileSize(file, 0);
  583. mapping = CreateFileMapping(file,0,PAGE_READONLY,0,0, 0);
  584. if (!mapping) {
  585. Status = GetLastError();
  586. ReportError("Cannot create mapping, error %u\n", Status );
  587. CloseHandle(file);
  588. return Status;
  589. }
  590. PCHAR buf = (PCHAR)MapViewOfFileEx(mapping, FILE_MAP_READ,0,0,0,0);
  591. if (buf) {
  592. EZPARSE_CONTEXT ParseContext;
  593. ZeroMemory(&ParseContext, sizeof(ParseContext) );
  594. ParseContext.start = buf;
  595. ParseContext.filename = filename;
  596. ParseContext.scannedLineCount = 1;
  597. ParseContext.lastScanned = buf;
  598. ParseContext.previousContext = EzParseCurrentContext;
  599. ParseContext.Options = Options;
  600. EzParseCurrentContext = &ParseContext;
  601. Status = (*ProcessData)(buf, buf + size, Callback, Context, &ParseContext);
  602. EzParseCurrentContext = ParseContext.previousContext;
  603. UnmapViewOfFile( buf );
  604. } else {
  605. Status = GetLastError();
  606. ReportError("MapViewOfFileEx failed, error %u\n", Status );
  607. }
  608. CloseHandle(mapping);
  609. CloseHandle(file);
  610. return Status;
  611. }
  612. DWORD
  613. EzParseResourceEx(
  614. IN LPCSTR ResName,
  615. IN PROCESSFILE_CALLBACK ProcessData,
  616. IN EZPARSE_CALLBACK Callback,
  617. IN PVOID Context)
  618. {
  619. DWORD Status = ERROR_SUCCESS;
  620. HRSRC hRsrc;
  621. hRsrc = FindResource(
  622. NULL, //this Module
  623. ResName,
  624. RT_RCDATA);
  625. if (hRsrc == NULL) {
  626. Status = GetLastError();
  627. ReportError("Cannot open resource %s, error %u\n", ResName, Status );
  628. return Status;
  629. }
  630. HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
  631. if (!hGlobal) {
  632. Status = GetLastError();
  633. ReportError("LockResource failed, error %u\n", Status );
  634. return Status;
  635. }
  636. DWORD size = SizeofResource(NULL, hRsrc);
  637. PCHAR buf = (PCHAR)LockResource(hGlobal);
  638. if (buf) {
  639. EZPARSE_CONTEXT ParseContext;
  640. ZeroMemory(&ParseContext, sizeof(ParseContext) );
  641. ParseContext.start = buf;
  642. ParseContext.filename = ResName;
  643. ParseContext.scannedLineCount = 1;
  644. ParseContext.lastScanned = buf;
  645. ParseContext.previousContext = EzParseCurrentContext;
  646. EzParseCurrentContext = &ParseContext;
  647. Status = (*ProcessData)(buf, buf + size, Callback, Context, &ParseContext);
  648. EzParseCurrentContext = ParseContext.previousContext;
  649. } else {
  650. Status = GetLastError();
  651. ReportError("LockResource failed, error %u\n", Status );
  652. }
  653. // According to MSDN. There is no need to call Unlock/Free Resource
  654. return Status;
  655. }