Brainfuck compiler
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.

214 lines
5.0KB

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <stdint.h>
  5. #include <stdbool.h>
  6. #include <assert.h>
  7. #include <errno.h>
  8. #define exit_fatal(...) \
  9. do { \
  10. fprintf (stderr, "fatal: " __VA_ARGS__); \
  11. exit (EXIT_FAILURE); \
  12. } while (0)
  13. // --- Safe memory management --------------------------------------------------
  14. static void *
  15. xcalloc (size_t m, size_t n)
  16. {
  17. void *p = calloc (m, n);
  18. if (!p)
  19. exit_fatal ("calloc: %s\n", strerror (errno));
  20. return p;
  21. }
  22. static void *
  23. xrealloc (void *o, size_t n)
  24. {
  25. void *p = realloc (o, n);
  26. if (!p && n)
  27. exit_fatal ("realloc: %s\n", strerror (errno));
  28. return p;
  29. }
  30. // --- Dynamically allocated strings -------------------------------------------
  31. struct str
  32. {
  33. char *str; ///< String data, null terminated
  34. size_t alloc; ///< How many bytes are allocated
  35. size_t len; ///< How long the string actually is
  36. };
  37. static void
  38. str_init (struct str *self)
  39. {
  40. self->len = 0;
  41. self->str = xcalloc (1, (self->alloc = 16));
  42. }
  43. static void
  44. str_ensure_space (struct str *self, size_t n)
  45. {
  46. // We allocate at least one more byte for the terminating null character
  47. size_t new_alloc = self->alloc;
  48. while (new_alloc <= self->len + n)
  49. new_alloc <<= 1;
  50. if (new_alloc != self->alloc)
  51. self->str = xrealloc (self->str, (self->alloc = new_alloc));
  52. }
  53. static void
  54. str_append_data (struct str *self, const void *data, size_t n)
  55. {
  56. str_ensure_space (self, n);
  57. memcpy (self->str + self->len, data, n);
  58. self->str[self->len += n] = '\0';
  59. }
  60. static void
  61. str_append_c (struct str *self, char c)
  62. {
  63. str_append_data (self, &c, 1);
  64. }
  65. // --- Main --------------------------------------------------------------------
  66. struct str program; ///< Raw program
  67. struct str data; ///< Data tape
  68. enum command { RIGHT, LEFT, INC, DEC, SET, IN, OUT, BEGIN, END };
  69. bool grouped[] = { 1, 1, 1, 1, 1, 0, 0, 0, 0 };
  70. struct instruction { enum command cmd; size_t arg; };
  71. int
  72. main (int argc, char *argv[])
  73. {
  74. (void) argc; str_init (&program);
  75. (void) argv; str_init (&data);
  76. int c;
  77. while ((c = fgetc (stdin)) != EOF)
  78. str_append_c (&program, c);
  79. if (ferror (stdin))
  80. exit_fatal ("can't read program\n");
  81. FILE *input = fopen ("/dev/tty", "rb");
  82. if (!input)
  83. exit_fatal ("can't open terminal for reading\n");
  84. // - - Decode and group - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  85. struct instruction *parsed = xcalloc (sizeof *parsed, program.len);
  86. size_t parsed_len = 0;
  87. for (size_t i = 0; i < program.len; i++)
  88. {
  89. enum command cmd;
  90. switch (program.str[i])
  91. {
  92. case '>': cmd = RIGHT; break;
  93. case '<': cmd = LEFT; break;
  94. case '+': cmd = INC; break;
  95. case '-': cmd = DEC; break;
  96. case '.': cmd = OUT; break;
  97. case ',': cmd = IN; break;
  98. case '[': cmd = BEGIN; break;
  99. case ']': cmd = END; break;
  100. default: continue;
  101. }
  102. if (!parsed_len || !grouped[cmd] || parsed[parsed_len - 1].cmd != cmd)
  103. parsed_len++;
  104. parsed[parsed_len - 1].cmd = cmd;
  105. parsed[parsed_len - 1].arg++;
  106. }
  107. // - - Simple optimization pass - - - - - - - - - - - - - - - - - - - - - - - -
  108. size_t in = 0, out = 0;
  109. for (; in < parsed_len; in++, out++)
  110. {
  111. if (in + 2 < parsed_len
  112. && parsed[in ].cmd == BEGIN
  113. && parsed[in + 1].cmd == DEC && parsed[in + 1].arg == 1
  114. && parsed[in + 2].cmd == END)
  115. {
  116. parsed[out].cmd = SET;
  117. parsed[out].arg = 0;
  118. in += 2;
  119. }
  120. else if (out && parsed[out - 1].cmd == SET && parsed[in].cmd == INC)
  121. parsed[--out].arg += parsed[in].arg;
  122. else if (out != in)
  123. parsed[out] = parsed[in];
  124. }
  125. parsed_len = out;
  126. // - - Loop pairing - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  127. size_t nesting = 0;
  128. size_t *stack = xcalloc (sizeof *stack, parsed_len);
  129. for (size_t i = 0; i < parsed_len; i++)
  130. {
  131. switch (parsed[i].cmd)
  132. {
  133. case BEGIN:
  134. stack[nesting++] = i;
  135. break;
  136. case END:
  137. assert (nesting > 0);
  138. --nesting;
  139. parsed[stack[nesting]].arg = i;
  140. parsed[i].arg = stack[nesting];
  141. default:
  142. break;
  143. }
  144. }
  145. assert (nesting == 0);
  146. // - - Runtime - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  147. size_t dataptr = 0;
  148. str_append_c (&data, 0);
  149. for (size_t i = 0; i < parsed_len; i++)
  150. {
  151. size_t arg = parsed[i].arg;
  152. switch (parsed[i].cmd)
  153. {
  154. case RIGHT:
  155. assert (SIZE_MAX - dataptr > arg);
  156. dataptr += arg;
  157. while (dataptr >= data.len)
  158. str_append_c (&data, 0);
  159. break;
  160. case LEFT:
  161. assert (dataptr >= arg);
  162. dataptr -= arg;
  163. break;
  164. case INC: data.str[dataptr] += arg; break;
  165. case DEC: data.str[dataptr] -= arg; break;
  166. case SET: data.str[dataptr] = arg; break;
  167. case OUT:
  168. fputc (data.str[dataptr], stdout);
  169. break;
  170. case IN:
  171. data.str[dataptr] = c = fgetc (input);
  172. assert (c != EOF);
  173. break;
  174. case BEGIN: if (!data.str[dataptr]) i = arg; break;
  175. case END: if ( data.str[dataptr]) i = arg; break;
  176. }
  177. }
  178. return 0;
  179. }