scdoc (mirror)
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.

756 lines
17KB

  1. #define _XOPEN_SOURCE 600
  2. #include <assert.h>
  3. #include <ctype.h>
  4. #include <errno.h>
  5. #include <limits.h>
  6. #include <stdbool.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #include <time.h>
  11. #include <unistd.h>
  12. #include "str.h"
  13. #include "unicode.h"
  14. #include "util.h"
  15. char *strstr(const char *haystack, const char *needle);
  16. char *strerror(int errnum);
  17. static struct str *parse_section(struct parser *p) {
  18. struct str *section = str_create();
  19. uint32_t ch;
  20. char *subsection;
  21. while ((ch = parser_getch(p)) != UTF8_INVALID) {
  22. if (ch < 0x80 && isalnum(ch)) {
  23. int ret = str_append_ch(section, ch);
  24. assert(ret != -1);
  25. } else if (ch == ')') {
  26. if (section->len == 0) {
  27. break;
  28. }
  29. int sec = strtol(section->str, &subsection, 10);
  30. if (section->str == subsection) {
  31. parser_fatal(p, "Expected section digit");
  32. break;
  33. }
  34. if (sec < 0 || sec > 9) {
  35. parser_fatal(p, "Expected section between 0 and 9");
  36. break;
  37. }
  38. return section;
  39. } else {
  40. parser_fatal(p, "Expected alphanumerical character or )");
  41. break;
  42. }
  43. };
  44. parser_fatal(p, "Expected manual section");
  45. return NULL;
  46. }
  47. static struct str *parse_extra(struct parser *p) {
  48. struct str *extra = str_create();
  49. int ret = str_append_ch(extra, '"');
  50. assert(ret != -1);
  51. uint32_t ch;
  52. while ((ch = parser_getch(p)) != UTF8_INVALID) {
  53. if (ch == '"') {
  54. ret = str_append_ch(extra, ch);
  55. assert(ret != -1);
  56. return extra;
  57. } else if (ch == '\n') {
  58. parser_fatal(p, "Unclosed extra preamble field");
  59. break;
  60. } else {
  61. ret = str_append_ch(extra, ch);
  62. assert(ret != -1);
  63. }
  64. }
  65. str_free(extra);
  66. return NULL;
  67. }
  68. static void parse_preamble(struct parser *p) {
  69. struct str *name = str_create();
  70. int ex = 0;
  71. struct str *extras[2] = { NULL };
  72. struct str *section = NULL;
  73. uint32_t ch;
  74. time_t date_time;
  75. char date[256];
  76. char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
  77. if (source_date_epoch != NULL) {
  78. unsigned long long epoch;
  79. char *endptr;
  80. errno = 0;
  81. epoch = strtoull(source_date_epoch, &endptr, 10);
  82. if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
  83. || (errno != 0 && epoch == 0)) {
  84. fprintf(stderr, "$SOURCE_DATE_EPOCH: strtoull: %s\n",
  85. strerror(errno));
  86. exit(EXIT_FAILURE);
  87. }
  88. if (endptr == source_date_epoch) {
  89. fprintf(stderr, "$SOURCE_DATE_EPOCH: No digits were found: %s\n",
  90. endptr);
  91. exit(EXIT_FAILURE);
  92. }
  93. if (*endptr != '\0') {
  94. fprintf(stderr, "$SOURCE_DATE_EPOCH: Trailing garbage: %s\n",
  95. endptr);
  96. exit(EXIT_FAILURE);
  97. }
  98. if (epoch > ULONG_MAX) {
  99. fprintf(stderr, "$SOURCE_DATE_EPOCH: value must be smaller than or "
  100. "equal to %lu but was found to be: %llu \n",
  101. ULONG_MAX, epoch);
  102. exit(EXIT_FAILURE);
  103. }
  104. date_time = epoch;
  105. } else {
  106. date_time = time(NULL);
  107. }
  108. struct tm *date_tm = gmtime(&date_time);
  109. strftime(date, sizeof(date), "%F", date_tm);
  110. while ((ch = parser_getch(p)) != UTF8_INVALID) {
  111. if ((ch < 0x80 && isalnum(ch)) || ch == '_' || ch == '-' || ch == '.') {
  112. int ret = str_append_ch(name, ch);
  113. assert(ret != -1);
  114. } else if (ch == '(') {
  115. section = parse_section(p);
  116. } else if (ch == '"') {
  117. if (ex == 2) {
  118. parser_fatal(p, "Too many extra preamble fields");
  119. }
  120. extras[ex++] = parse_extra(p);
  121. } else if (ch == '\n') {
  122. if (name->len == 0) {
  123. parser_fatal(p, "Expected preamble");
  124. }
  125. if (section == NULL) {
  126. parser_fatal(p, "Expected manual section");
  127. }
  128. char *ex2 = extras[0] != NULL ? extras[0]->str : NULL;
  129. char *ex3 = extras[1] != NULL ? extras[1]->str : NULL;
  130. fprintf(p->output, ".TH \"%s\" \"%s\" \"%s\"", name->str, section->str, date);
  131. /* ex2 and ex3 are already double-quoted */
  132. if (ex2) {
  133. fprintf(p->output, " %s", ex2);
  134. }
  135. if (ex3) {
  136. fprintf(p->output, " %s", ex3);
  137. }
  138. fprintf(p->output, "\n");
  139. break;
  140. } else if (section == NULL) {
  141. parser_fatal(p, "Name characters must be A-Z, a-z, 0-9, `-`, `_`, or `.`");
  142. }
  143. }
  144. str_free(name);
  145. for (int i = 0; i < 2; ++i) {
  146. if (extras[i] != NULL) {
  147. str_free(extras[i]);
  148. }
  149. }
  150. }
  151. static void parse_format(struct parser *p, enum formatting fmt) {
  152. char formats[FORMAT_LAST] = {
  153. [FORMAT_BOLD] = 'B',
  154. [FORMAT_UNDERLINE] = 'I',
  155. };
  156. char error[512];
  157. if (p->flags) {
  158. if ((p->flags & ~fmt)) {
  159. snprintf(error, sizeof(error), "Cannot nest inline formatting "
  160. "(began with %c at %d:%d)",
  161. p->flags == FORMAT_BOLD ? '*' : '_',
  162. p->fmt_line, p->fmt_col);
  163. parser_fatal(p, error);
  164. }
  165. fprintf(p->output, "\\fR");
  166. } else {
  167. fprintf(p->output, "\\f%c", formats[fmt]);
  168. p->fmt_line = p->line;
  169. p->fmt_col = p->col;
  170. }
  171. p->flags ^= fmt;
  172. }
  173. static void parse_linebreak(struct parser *p) {
  174. uint32_t plus = parser_getch(p);
  175. if (plus != '+') {
  176. fprintf(p->output, "+");
  177. parser_pushch(p, plus);
  178. return;
  179. }
  180. uint32_t lf = parser_getch(p);
  181. if (lf != '\n') {
  182. fprintf(p->output, "+");
  183. parser_pushch(p, plus);
  184. parser_pushch(p, '\n');
  185. return;
  186. }
  187. uint32_t ch = parser_getch(p);
  188. if (ch == '\n') {
  189. parser_fatal(
  190. p, "Explicit line breaks cannot be followed by a blank line");
  191. }
  192. parser_pushch(p, ch);
  193. fprintf(p->output, "\n.br\n");
  194. }
  195. static void parse_text(struct parser *p) {
  196. uint32_t ch, next, last = ' ';
  197. int i = 0;
  198. while ((ch = parser_getch(p)) != UTF8_INVALID) {
  199. switch (ch) {
  200. case '\\':
  201. ch = parser_getch(p);
  202. if (ch == UTF8_INVALID) {
  203. parser_fatal(p, "Unexpected EOF");
  204. } else if (ch == '\\') {
  205. fprintf(p->output, "\\\\");
  206. } else {
  207. utf8_fputch(p->output, ch);
  208. }
  209. break;
  210. case '*':
  211. parse_format(p, FORMAT_BOLD);
  212. break;
  213. case '_':
  214. next = parser_getch(p);
  215. if (!isalnum(last) || ((p->flags & FORMAT_UNDERLINE) && !isalnum(next))) {
  216. parse_format(p, FORMAT_UNDERLINE);
  217. } else {
  218. utf8_fputch(p->output, ch);
  219. }
  220. if (next == UTF8_INVALID) {
  221. return;
  222. }
  223. parser_pushch(p, next);
  224. break;
  225. case '+':
  226. parse_linebreak(p);
  227. break;
  228. case '\n':
  229. utf8_fputch(p->output, ch);
  230. return;
  231. case '.':
  232. if (!i) {
  233. // Escape . if it's the first character
  234. fprintf(p->output, "\\&.");
  235. break;
  236. }
  237. /* fallthrough */
  238. default:
  239. last = ch;
  240. utf8_fputch(p->output, ch);
  241. break;
  242. }
  243. ++i;
  244. }
  245. }
  246. static void parse_heading(struct parser *p) {
  247. uint32_t ch;
  248. int level = 1;
  249. while ((ch = parser_getch(p)) != UTF8_INVALID) {
  250. if (ch == '#') {
  251. ++level;
  252. } else if (ch == ' ') {
  253. break;
  254. } else {
  255. parser_fatal(p, "Invalid start of heading (probably needs a space)");
  256. }
  257. }
  258. switch (level) {
  259. case 1:
  260. fprintf(p->output, ".SH ");
  261. break;
  262. case 2:
  263. fprintf(p->output, ".SS ");
  264. break;
  265. default:
  266. parser_fatal(p, "Only headings up to two levels deep are permitted");
  267. break;
  268. }
  269. while ((ch = parser_getch(p)) != UTF8_INVALID) {
  270. utf8_fputch(p->output, ch);
  271. if (ch == '\n') {
  272. break;
  273. }
  274. }
  275. }
  276. static int parse_indent(struct parser *p, int *indent, bool write) {
  277. int i = 0;
  278. uint32_t ch;
  279. while ((ch = parser_getch(p)) == '\t') {
  280. ++i;
  281. }
  282. parser_pushch(p, ch);
  283. if ((ch == '\n' || ch == UTF8_INVALID) && *indent != 0) {
  284. // Don't change indent when we encounter empty lines or EOF
  285. return *indent;
  286. }
  287. if (write) {
  288. if ((i - *indent) > 1) {
  289. parser_fatal(p, "Indented by an amount greater than 1");
  290. } else if (i < *indent) {
  291. for (int j = *indent; i < j; --j) {
  292. roff_macro(p, "RE", NULL);
  293. }
  294. } else if (i == *indent + 1) {
  295. fprintf(p->output, ".RS 4\n");
  296. }
  297. }
  298. *indent = i;
  299. return i;
  300. }
  301. static void list_header(struct parser *p, int *num) {
  302. fprintf(p->output, ".RS 4\n");
  303. fprintf(p->output, ".ie n \\{\\\n");
  304. if (*num == -1) {
  305. fprintf(p->output, "\\h'-0%d'%s\\h'+03'\\c\n",
  306. *num >= 10 ? 5 : 4, "\\(bu");
  307. } else {
  308. fprintf(p->output, "\\h'-0%d'%d.\\h'+03'\\c\n",
  309. *num >= 10 ? 5 : 4, *num);
  310. }
  311. fprintf(p->output, ".\\}\n");
  312. fprintf(p->output, ".el \\{\\\n");
  313. if (*num == -1) {
  314. fprintf(p->output, ".IP %s 4\n", "\\(bu");
  315. } else {
  316. fprintf(p->output, ".IP %d. 4\n", *num);
  317. *num = *num + 1;
  318. }
  319. fprintf(p->output, ".\\}\n");
  320. }
  321. static void parse_list(struct parser *p, int *indent, int num) {
  322. uint32_t ch;
  323. if ((ch = parser_getch(p)) != ' ') {
  324. parser_fatal(p, "Expected space before start of list entry");
  325. }
  326. list_header(p, &num);
  327. parse_text(p);
  328. do {
  329. parse_indent(p, indent, true);
  330. if ((ch = parser_getch(p)) == UTF8_INVALID) {
  331. break;
  332. }
  333. switch (ch) {
  334. case ' ':
  335. if ((ch = parser_getch(p)) != ' ') {
  336. parser_fatal(p, "Expected two spaces for list entry continuation");
  337. }
  338. parse_text(p);
  339. break;
  340. case '-':
  341. case '.':
  342. if ((ch = parser_getch(p)) != ' ') {
  343. parser_fatal(p, "Expected space before start of list entry");
  344. }
  345. roff_macro(p, "RE", NULL);
  346. list_header(p, &num);
  347. parse_text(p);
  348. break;
  349. default:
  350. fprintf(p->output, "\n");
  351. parser_pushch(p, ch);
  352. goto ret;
  353. }
  354. } while (ch != UTF8_INVALID);
  355. ret:
  356. roff_macro(p, "RE", NULL);
  357. }
  358. static void parse_literal(struct parser *p, int *indent) {
  359. uint32_t ch;
  360. if ((ch = parser_getch(p)) != '`' ||
  361. (ch = parser_getch(p)) != '`' ||
  362. (ch = parser_getch(p)) != '\n') {
  363. parser_fatal(p, "Expected ``` and a newline to begin literal block");
  364. }
  365. int stops = 0;
  366. roff_macro(p, "nf", NULL);
  367. fprintf(p->output, ".RS 4\n");
  368. bool check_indent = true;
  369. do {
  370. if (check_indent) {
  371. int _indent = *indent;
  372. parse_indent(p, &_indent, false);
  373. if (_indent < *indent) {
  374. parser_fatal(p, "Cannot deindent in literal block");
  375. }
  376. while (_indent > *indent) {
  377. --_indent;
  378. fprintf(p->output, "\t");
  379. }
  380. check_indent = false;
  381. }
  382. if ((ch = parser_getch(p)) == UTF8_INVALID) {
  383. break;
  384. }
  385. if (ch == '`') {
  386. if (++stops == 3) {
  387. if ((ch = parser_getch(p)) != '\n') {
  388. parser_fatal(p, "Expected literal block to end with newline");
  389. }
  390. roff_macro(p, "fi", NULL);
  391. roff_macro(p, "RE", NULL);
  392. return;
  393. }
  394. } else {
  395. while (stops != 0) {
  396. fputc('`', p->output);
  397. --stops;
  398. }
  399. switch (ch) {
  400. case '.':
  401. fprintf(p->output, "\\&.");
  402. break;
  403. case '\\':
  404. ch = parser_getch(p);
  405. if (ch == UTF8_INVALID) {
  406. parser_fatal(p, "Unexpected EOF");
  407. } else if (ch == '\\') {
  408. fprintf(p->output, "\\\\");
  409. } else {
  410. utf8_fputch(p->output, ch);
  411. }
  412. break;
  413. case '\n':
  414. check_indent = true;
  415. /* fallthrough */
  416. default:
  417. utf8_fputch(p->output, ch);
  418. break;
  419. }
  420. }
  421. } while (ch != UTF8_INVALID);
  422. }
  423. enum table_align {
  424. ALIGN_LEFT,
  425. ALIGN_CENTER,
  426. ALIGN_RIGHT,
  427. ALIGN_LEFT_EXPAND,
  428. ALIGN_CENTER_EXPAND,
  429. ALIGN_RIGHT_EXPAND,
  430. };
  431. struct table_row {
  432. struct table_cell *cell;
  433. struct table_row *next;
  434. };
  435. struct table_cell {
  436. enum table_align align;
  437. struct str *contents;
  438. struct table_cell *next;
  439. };
  440. static void parse_table(struct parser *p, uint32_t style) {
  441. struct table_row *table = NULL;
  442. struct table_row *currow = NULL, *prevrow = NULL;
  443. struct table_cell *curcell = NULL;
  444. int column = 0;
  445. uint32_t ch;
  446. parser_pushch(p, '|');
  447. do {
  448. if ((ch = parser_getch(p)) == UTF8_INVALID) {
  449. break;
  450. }
  451. switch (ch) {
  452. case '\n':
  453. goto commit_table;
  454. case '|':
  455. prevrow = currow;
  456. currow = calloc(1, sizeof(struct table_row));
  457. if (prevrow) {
  458. // TODO: Verify the number of columns match
  459. prevrow->next = currow;
  460. }
  461. curcell = calloc(1, sizeof(struct table_cell));
  462. currow->cell = curcell;
  463. column = 0;
  464. if (!table) {
  465. table = currow;
  466. }
  467. break;
  468. case ':':
  469. if (!currow) {
  470. parser_fatal(p, "Cannot start a column without "
  471. "starting a row first");
  472. } else {
  473. struct table_cell *prev = curcell;
  474. curcell = calloc(1, sizeof(struct table_cell));
  475. if (prev) {
  476. prev->next = curcell;
  477. }
  478. ++column;
  479. }
  480. break;
  481. case ' ':
  482. goto continue_cell;
  483. default:
  484. parser_fatal(p, "Expected either '|' or ':'");
  485. break;
  486. }
  487. if ((ch = parser_getch(p)) == UTF8_INVALID) {
  488. break;
  489. }
  490. switch (ch) {
  491. case '[':
  492. curcell->align = ALIGN_LEFT;
  493. break;
  494. case '-':
  495. curcell->align = ALIGN_CENTER;
  496. break;
  497. case ']':
  498. curcell->align = ALIGN_RIGHT;
  499. break;
  500. case '<':
  501. curcell->align = ALIGN_LEFT_EXPAND;
  502. break;
  503. case '=':
  504. curcell->align = ALIGN_CENTER_EXPAND;
  505. break;
  506. case '>':
  507. curcell->align = ALIGN_RIGHT_EXPAND;
  508. break;
  509. case ' ':
  510. if (prevrow) {
  511. struct table_cell *pcell = prevrow->cell;
  512. for (int i = 0; i <= column && pcell; ++i, pcell = pcell->next) {
  513. if (i == column) {
  514. curcell->align = pcell->align;
  515. break;
  516. }
  517. }
  518. } else {
  519. parser_fatal(p, "No previous row to infer alignment from");
  520. }
  521. break;
  522. default:
  523. parser_fatal(p, "Expected one of '[', '-', ']', or ' '");
  524. break;
  525. }
  526. curcell->contents = str_create();
  527. continue_cell:
  528. switch (ch = parser_getch(p)) {
  529. case ' ':
  530. // Read out remainder of the text
  531. while ((ch = parser_getch(p)) != UTF8_INVALID) {
  532. switch (ch) {
  533. case '\n':
  534. goto commit_cell;
  535. default:;
  536. int ret = str_append_ch(curcell->contents, ch);
  537. assert(ret != -1);
  538. break;
  539. }
  540. }
  541. break;
  542. case '\n':
  543. goto commit_cell;
  544. default:
  545. parser_fatal(p, "Expected ' ' or a newline");
  546. break;
  547. }
  548. commit_cell:
  549. if (strstr(curcell->contents->str, "T{")
  550. || strstr(curcell->contents->str, "T}")) {
  551. parser_fatal(p, "Cells cannot contain T{ or T} "
  552. "due to roff limitations");
  553. }
  554. } while (ch != UTF8_INVALID);
  555. commit_table:
  556. if (ch == UTF8_INVALID) {
  557. return;
  558. }
  559. roff_macro(p, "TS", NULL);
  560. switch (style) {
  561. case '[':
  562. fprintf(p->output, "allbox;");
  563. break;
  564. case ']':
  565. fprintf(p->output, "box;");
  566. break;
  567. }
  568. // Print alignments first
  569. currow = table;
  570. while (currow) {
  571. curcell = currow->cell;
  572. while (curcell) {
  573. char *align = "";
  574. switch (curcell->align) {
  575. case ALIGN_LEFT:
  576. align = "l";
  577. break;
  578. case ALIGN_CENTER:
  579. align = "c";
  580. break;
  581. case ALIGN_RIGHT:
  582. align = "r";
  583. break;
  584. case ALIGN_LEFT_EXPAND:
  585. align = "lx";
  586. break;
  587. case ALIGN_CENTER_EXPAND:
  588. align = "cx";
  589. break;
  590. case ALIGN_RIGHT_EXPAND:
  591. align = "rx";
  592. break;
  593. }
  594. fprintf(p->output, "%s%s", align, curcell->next ? " " : "");
  595. curcell = curcell->next;
  596. }
  597. fprintf(p->output, "%s\n", currow->next ? "" : ".");
  598. currow = currow->next;
  599. }
  600. // Then contents
  601. currow = table;
  602. while (currow) {
  603. curcell = currow->cell;
  604. fprintf(p->output, "T{\n");
  605. while (curcell) {
  606. parser_pushstr(p, curcell->contents->str);
  607. parse_text(p);
  608. if (curcell->next) {
  609. fprintf(p->output, "\nT}\tT{\n");
  610. } else {
  611. fprintf(p->output, "\nT}");
  612. }
  613. struct table_cell *prev = curcell;
  614. curcell = curcell->next;
  615. str_free(prev->contents);
  616. free(prev);
  617. }
  618. fprintf(p->output, "\n");
  619. struct table_row *prev = currow;
  620. currow = currow->next;
  621. free(prev);
  622. }
  623. roff_macro(p, "TE", NULL);
  624. fprintf(p->output, ".sp 1\n");
  625. }
  626. static void parse_document(struct parser *p) {
  627. uint32_t ch;
  628. int indent = 0;
  629. do {
  630. parse_indent(p, &indent, true);
  631. if ((ch = parser_getch(p)) == UTF8_INVALID) {
  632. break;
  633. }
  634. switch (ch) {
  635. case ';':
  636. if ((ch = parser_getch(p)) != ' ') {
  637. parser_fatal(p, "Expected space after ; to begin comment");
  638. }
  639. do {
  640. ch = parser_getch(p);
  641. } while (ch != UTF8_INVALID && ch != '\n');
  642. break;
  643. case '#':
  644. if (indent != 0) {
  645. parser_pushch(p, ch);
  646. parse_text(p);
  647. break;
  648. }
  649. parse_heading(p);
  650. break;
  651. case '-':
  652. parse_list(p, &indent, -1);
  653. break;
  654. case '.':
  655. if ((ch = parser_getch(p)) == ' ') {
  656. parser_pushch(p, ch);
  657. parse_list(p, &indent, 1);
  658. } else {
  659. parser_pushch(p, ch);
  660. parse_text(p);
  661. }
  662. break;
  663. case '`':
  664. parse_literal(p, &indent);
  665. break;
  666. case '[':
  667. case '|':
  668. case ']':
  669. if (indent != 0) {
  670. parser_fatal(p, "Tables cannot be indented");
  671. }
  672. parse_table(p, ch);
  673. break;
  674. case ' ':
  675. parser_fatal(p, "Tabs are required for indentation");
  676. break;
  677. case '\n':
  678. if (p->flags) {
  679. char error[512];
  680. snprintf(error, sizeof(error), "Expected %c before starting "
  681. "new paragraph (began with %c at %d:%d)",
  682. p->flags == FORMAT_BOLD ? '*' : '_',
  683. p->flags == FORMAT_BOLD ? '*' : '_',
  684. p->fmt_line, p->fmt_col);
  685. parser_fatal(p, error);
  686. }
  687. roff_macro(p, "P", NULL);
  688. break;
  689. default:
  690. parser_pushch(p, ch);
  691. parse_text(p);
  692. break;
  693. }
  694. } while (ch != UTF8_INVALID);
  695. }
  696. static void output_scdoc_preamble(struct parser *p) {
  697. fprintf(p->output, ".\\\" Generated by scdoc " VERSION "\n");
  698. fprintf(p->output, ".\\\" Complete documentation for this program is not "
  699. "available as a GNU info page\n");
  700. // Fix weird quotation marks
  701. // http://bugs.debian.org/507673
  702. // http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
  703. fprintf(p->output, ".ie \\n(.g .ds Aq \\(aq\n");
  704. fprintf(p->output, ".el .ds Aq '\n");
  705. // Disable hyphenation:
  706. roff_macro(p, "nh", NULL);
  707. // Disable justification:
  708. roff_macro(p, "ad l", NULL);
  709. fprintf(p->output, ".\\\" Begin generated content:\n");
  710. }
  711. int main(int argc, char **argv) {
  712. if (argc == 2 && strcmp(argv[1], "-v") == 0) {
  713. printf("scdoc " VERSION "\n");
  714. return 0;
  715. } else if (argc > 1) {
  716. fprintf(stderr, "Usage: scdoc < input.scd > output.roff\n");
  717. return 1;
  718. }
  719. struct parser p = {
  720. .input = stdin,
  721. .output = stdout,
  722. .line = 1,
  723. .col = 1
  724. };
  725. output_scdoc_preamble(&p);
  726. parse_preamble(&p);
  727. parse_document(&p);
  728. return 0;
  729. }