├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── example └── ping.bf ├── main.c └── src ├── debug.c ├── debug.h ├── eval.c ├── eval.h ├── parser.c └── parser.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET=cspfuck 2 | BUILDDIR=bin/ 3 | PREFIX=/usr/local/bin/ 4 | SOURCES=$(wildcard src/*.c) 5 | MAIN=main.c 6 | override CFLAGS+=-std=c11 -O2 -Wno-gnu -lpthread 7 | DEVFLAGS=-Werror -Wall -g -fPIC -DNDEBUG -Wfloat-equal -Wundef -Wwrite-strings -Wuninitialized -pedantic -fsanitize=address -O0 8 | 9 | all: main.c 10 | mkdir -p $(BUILDDIR) 11 | $(CC) $(MAIN) $(SOURCES) -o $(BUILDDIR)$(TARGET) $(CFLAGS) 12 | 13 | no_wrap: main.c 14 | CFLAGS=-DNO_WRAP make 15 | 16 | dev: main.c 17 | mkdir -p $(BUILDDIR) 18 | $(CC) $(MAIN) $(SOURCES) -o $(BUILDDIR)$(TARGET) $(CFLAGS) $(DEVFLAGS) 19 | 20 | install: all 21 | install $(BUILDDIR)$(TARGET) $(PREFIX)$(TARGET) 22 | 23 | uninstall: 24 | rm -rf $(PREFIX)$(TARGET) 25 | 26 | clean: 27 | rm -rf $(BUILDDIR) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cspfuck 2 | 3 | `cspfuck` is Brainfuck with channels. Every paragraph of the source code is an 4 | actor that is effectively a Brainfuck program with added primitives for 5 | receiving from and sending to other actors. 6 | 7 | If you need more speed, check out the [JITed version](https://github.com/hellerve/cspfuck/tree/jit)! 8 | 9 | ## Usage 10 | 11 | You can build the program through its Makefile by typing `make`. This will leave 12 | you with an executable called `cspfuck` in the `bin/` directory that you can 13 | feed cspfuck files through the command line. 14 | 15 | ## The language 16 | 17 | Standard Brainfuck has eight operations. You can find out about them on the web, 18 | but for ease of use I will repeat them here. Brainfuck operates on a global 19 | tape of—typically—30,000 elements, initially all set to 0. 20 | 21 | - `+`: Increment the value currently under the tape head. 22 | - `-`: Decrement the value currently under the tape head. 23 | - `>`: Advance the tape head or wrap around. 24 | - `<`: Retreat the tape head or wrap around. 25 | - `.`: Print out the value currently under the tape head, interpreted as 26 | ASCII code. 27 | - `,`: Read a character into the cell currently under the tape head, 28 | interpreted as a number by its ASCII code. 29 | - `[`: Start of a loop. Loops will be executed unless the value currently under 30 | the tape head is zero. 31 | - `]`: End of a loop. Will jump back to the matching `[`, with respect of 32 | nesting. 33 | 34 | That’s all that Brainfuck is. 35 | 36 | `cspfuck` adds three primitives to this set, namely: 37 | 38 | - `^`: Send the value currently under the tape head to the actor above. 39 | - `v`: Send the value currently under the tape head to the actor below. 40 | - `u`: Receive a value, write it into the cell currently under the tape head. 41 | 42 | And that’s all that `cspfuck` is. 43 | 44 | ## Example 45 | 46 | I’m a terrible Brainfuck programmer, so I only provide one proof-of-concept 47 | example for now. It is contained in a directory named 48 | `example/`—appropriately singular—, in which I provide a ping pong program. It 49 | contains two actors, one printing ping, then waiting for the other, the other 50 | waiting, and then printing pong, ten times. 51 | 52 | ## Implementation 53 | 54 | Actors are implemented as pthreads. The virtual machine is a simple direct 55 | threaded bytecode VM that offers 30,000 elements to each Brainfuck program. It 56 | will wrap around if you go past the low or high threshold—this can be turned 57 | off by passing `-DNO_WRAP` to the compiler or calling `make no_wrap`, as 58 | disabling it might buy you a little bit of performance, depending on your 59 | workfload. 60 | 61 | It should be reasonably performant, but who cares? I hope noone’s going to run 62 | their MapReduce jobs on it. There are some low-hanging fruits for optimization. 63 | Feel free to hack on it you want to! I’m happy to help you get started. If you 64 | do need more speed, I recently wrote a [JITed 65 | version](https://github.com/hellerve/cspfuck/tree/jit)! 66 | 67 | The VM does seem to execute [ridiculous programs](http://www.clifford.at/bfcpu/hanoi.html) 68 | in standard Brainfuck pretty efficiently, which makes me unreasonably happy. 69 | 70 | It’s only about 450 lines of C, so it should be reasonably consumable. The 71 | code isn’t necessarily pretty, but it seems to work well. It is not incredibly 72 | battle-tested, though. 73 | 74 | If you want to know more, read [my blog post](http://blog.veitheller.de/Brainfuck_and_Actors.html)! 75 | 76 | **Disclaimer**: I know approximately as much about concurrent programming in C 77 | as I know about writing production-grade Brainfuck. The system should be 78 | expected to be brittle. 79 | 80 |
81 | 82 | Have fun! 83 | -------------------------------------------------------------------------------- /example/ping.bf: -------------------------------------------------------------------------------- 1 | +++++++++++++++++++++++++++++++++++[>++>++>++>++<<<<-]>++++++++++>+++>++++++++>+>++++++++++<<<<< 2 | ++++++++++[>.>.>.>.>.>vu<<<<<<-] 3 | 4 | +++++++++++++++++++++++++++++++++++[>++>++>++>++<<<<-]>++++++++++>+++++++++>++++++++>+>++++++++++<<<<< 5 | ++++++++++[>>>>>>u<<<<<<>.>.>.>.>.^<<<<<-] 6 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "src/debug.h" 6 | #include "src/eval.h" 7 | #include "src/parser.h" 8 | 9 | int err(const char* str, ...) { 10 | va_list ap; 11 | va_start(ap, str); 12 | vfprintf(stderr, str, ap); 13 | va_end(ap); 14 | return 1; 15 | } 16 | 17 | int main(int argc, char** argv) { 18 | if (argc != 2) return err("usage: %s \n", argv[0]); 19 | 20 | FILE* f = fopen(argv[1], "rb"); 21 | 22 | if (!f) return err("couldn’t open file %s.\n", argv[1]); 23 | 24 | fseek(f, 0, SEEK_END); 25 | long fsize = ftell(f); 26 | fseek(f, 0, SEEK_SET); 27 | 28 | char *string = malloc(fsize + 1); 29 | fread(string, fsize, 1, f); 30 | fclose(f); 31 | 32 | string[fsize] = 0; 33 | 34 | actors* ac = parse(string); 35 | free(string); 36 | 37 | if (!ac) return err("parentheses don't match.\n"); 38 | 39 | eval_actors(ac); 40 | 41 | free_actors(ac); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /src/debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "debug.h" 5 | 6 | void print_bytecode(bytecode* code) { 7 | #define print(s) (printf("%4d: %*s%s\n", i, indent, "", s)) 8 | #define print_arg(s) (printf("%4d: %*s%s %d\n", i, indent, "", s, c.arg)) 9 | int i = 0; 10 | int indent = 0; 11 | while(1) { 12 | bytecode c = code[i++]; 13 | switch (c.code) { 14 | case INC: print_arg("inc"); break; 15 | case DEC: print_arg("dec"); break; 16 | case FWD: print_arg("fwd"); break; 17 | case BCK: print_arg("bck"); break; 18 | case PRN: print("prn"); break; 19 | case ZERO: print("zero"); break; 20 | case READ: print("read"); break; 21 | case STARTL: print_arg("start loop"); indent += 2; break; 22 | case ENDL: indent -= 2; print_arg("end loop"); break; 23 | 24 | case SEND: print_arg("send"); break; 25 | case RECV: print("receive"); break; 26 | 27 | case MOVE_PTR: print_arg("move ptr"); break; 28 | case MOVE_DATA: print_arg("move data"); break; 29 | 30 | case HALT: return; 31 | } 32 | } 33 | #undef print 34 | #undef print_arg 35 | } 36 | 37 | void print_actors(actors* ac) { 38 | for (int i = 0; i < ac->num; i++) { 39 | printf("Bytecode for Actor %d:\n=====================\n", i); 40 | print_bytecode(ac->code[i]); 41 | printf("=====================\n"); 42 | } 43 | } 44 | 45 | void print_bytecode_src(bytecode* code) { 46 | #define print_times(s) { for (j = 0; j < c.arg; j++) printf(s); } 47 | int i = 0; 48 | int j; 49 | while(1) { 50 | bytecode c = code[i++]; 51 | switch (c.code) { 52 | case INC: print_times("+"); break; 53 | case DEC: print_times("-"); break; 54 | case FWD: print_times(">"); break; 55 | case BCK: print_times("<"); break; 56 | case PRN: printf("."); break; 57 | case ZERO: printf("0"); break; 58 | case MOVE_PTR: 59 | printf("["); 60 | for (j = 0; j < abs(c.arg); j++) printf(c.arg > 0 ? ">" : "<"); 61 | printf("]"); 62 | break; 63 | case MOVE_DATA: 64 | printf("[-"); 65 | for (int j = 0; j < abs(c.arg); j++) printf(c.arg > 0 ? ">" : "<"); 66 | printf("+"); 67 | for (int j = 0; j < abs(c.arg); j++) printf(c.arg > 0 ? "<" : ">"); 68 | printf("]"); 69 | break; 70 | case READ: printf(","); break; 71 | case STARTL: printf("["); break; 72 | case ENDL: printf("]"); break; 73 | 74 | case SEND: if(c.arg) printf("^"); else printf("v"); break; 75 | case RECV: printf("u"); break; 76 | 77 | case HALT: return; 78 | } 79 | } 80 | #undef print_times 81 | } 82 | 83 | void print_src(actors* ac) { 84 | for (int i = 0; i < ac->num; i++) { 85 | print_bytecode_src(ac->code[i]); 86 | printf("\n\n"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef debug_h 2 | #define debug_h 3 | 4 | #include "parser.h" 5 | 6 | void print_actors(actors*); 7 | void print_src(actors*); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/eval.c: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "eval.h" 8 | 9 | #define TAPE_LEN 30000 10 | 11 | typedef struct { 12 | int num; 13 | int* up_in; 14 | int* up_written_in; 15 | int* up_out; 16 | int* up_written_out; 17 | int* down_in; 18 | int* down_written_in; 19 | int* down_out; 20 | int* down_written_out; 21 | bytecode* code; 22 | } actor_ctx; 23 | 24 | /* 25 | We’re using direct threading here. Basically, we avoid looping and case 26 | dispatch by using a computed GOTO instead. This comes at the cost of using a 27 | GNU extension, but I love those anyway. 28 | */ 29 | void* eval(void* arg) { 30 | #define DISPATCH() { c = ctx->code[i++]; goto *dispatch_table[c.code]; } 31 | int i = 0; 32 | unsigned int h = 0; 33 | unsigned int t[TAPE_LEN]; 34 | actor_ctx* ctx = (actor_ctx*) arg; 35 | bytecode c; 36 | static void* dispatch_table[] = { 37 | &&do_zero, &&do_inc, &&do_dec, &&do_fwd, &&do_bck, &&do_prn, &&do_read, 38 | &&do_startl, &&do_endl, &&do_send, &&do_recv, &&do_move_ptr, &&do_move_data, 39 | &&do_halt 40 | }; 41 | 42 | for (int idx = 0; idx < TAPE_LEN; idx++) t[idx] = 0; 43 | 44 | DISPATCH(); 45 | do_zero: t[h] = 0; DISPATCH(); 46 | do_inc: t[h] += c.arg; DISPATCH(); 47 | do_dec: t[h] -= c.arg; DISPATCH(); 48 | #ifdef NO_WRAP 49 | do_fwd: h += c.arg; DISPATCH(); 50 | do_bck: h -= c.arg; DISPATCH(); 51 | #else 52 | // modulo would be prettier here, but slows the code down by A LOT; somehow 53 | // the C compilers can’t optimize uncoditional modulos here 54 | do_fwd: h += c.arg; if (h >= TAPE_LEN-1) h %=TAPE_LEN; DISPATCH(); 55 | do_bck: h -= c.arg; if (h < 0) h %= TAPE_LEN; DISPATCH(); 56 | #endif 57 | do_prn: printf("%c", t[h]); DISPATCH(); 58 | do_read: scanf("%c", (char*)&t[h]); DISPATCH(); 59 | do_startl: if(!t[h]) i = c.arg; DISPATCH(); 60 | do_endl: if(t[h]) i = c.arg; DISPATCH(); 61 | 62 | do_send: 63 | if (c.arg) { 64 | if (!ctx->up_out) { 65 | fprintf(stderr, 66 | "Actor %d tried to write to non-existant up channel, ignoring.", 67 | ctx->num 68 | ); 69 | DISPATCH(); 70 | } 71 | *ctx->up_out = t[h]; 72 | *ctx->up_written_out = 1; 73 | DISPATCH(); 74 | } else { 75 | if (!ctx->down_out) { 76 | fprintf(stderr, 77 | "Actor %d tried to write to non-existant down channel, ignoring.", 78 | ctx->num 79 | ); 80 | DISPATCH(); 81 | } 82 | *ctx->down_out = t[h]; 83 | *ctx->down_written_out = 1; 84 | DISPATCH(); 85 | } 86 | 87 | do_recv: 88 | while ((!ctx->up_written_in || !(*ctx->up_written_in)) && 89 | (!ctx->down_written_in || !(*ctx->down_written_in))) usleep(100); 90 | if (ctx->up_written_in && *ctx->up_written_in) { 91 | t[h] = *ctx->up_in; 92 | *ctx->up_written_in = 0; 93 | } else if (ctx->down_written_in && *ctx->down_written_in) { 94 | t[h] = *ctx->down_in; 95 | *ctx->down_written_in = 0; 96 | } 97 | DISPATCH(); 98 | 99 | do_move_ptr: while (t[h]) h+=c.arg; DISPATCH(); 100 | 101 | do_move_data: 102 | if (t[h]) { 103 | t[h+c.arg] += t[h]; 104 | t[h] = 0; 105 | } 106 | DISPATCH(); 107 | 108 | do_halt: 109 | return NULL; 110 | #undef DISPATCH 111 | } 112 | 113 | void eval_actors(actors* ac) { 114 | int i; 115 | pthread_t ts[ac->num]; 116 | actor_ctx ctxs[ac->num]; 117 | int* down_in = NULL; 118 | int* down_written_in = NULL; 119 | int* down_out = NULL; 120 | int* down_written_out = NULL; 121 | for (i = 0; i < ac->num; i++) { 122 | actor_ctx ctx; 123 | ctx.num = i; 124 | ctx.down_in = down_in; 125 | ctx.down_written_in = down_written_in; 126 | ctx.down_out = down_out; 127 | ctx.down_written_out = down_written_out; 128 | if (i < ac->num-1) { 129 | ctx.up_in = malloc(sizeof(int)); 130 | ctx.up_written_in = malloc(sizeof(int)); 131 | *ctx.up_written_in = 0; 132 | ctx.up_out = malloc(sizeof(int)); 133 | ctx.up_written_out = malloc(sizeof(int)); 134 | *ctx.up_written_out = 0; 135 | } else { 136 | ctx.up_in = NULL; 137 | ctx.up_written_in = NULL; 138 | ctx.up_out = NULL; 139 | ctx.up_written_out = NULL; 140 | } 141 | ctx.code = ac->code[i]; 142 | ctxs[i] = ctx; 143 | if(ac->num == 1) { 144 | // if we just have one thread, we eval it directly; buys us about 2% perf 145 | eval(&ctx); 146 | free(ctx.up_in); 147 | free(ctx.up_written_in); 148 | free(ctx.up_out); 149 | free(ctx.up_written_out); 150 | return; 151 | } else { 152 | pthread_create(&ts[i], NULL, eval, &ctxs[i]); 153 | } 154 | down_in = ctx.up_out; 155 | down_written_in = ctx.up_written_out; 156 | down_out = ctx.up_in; 157 | down_written_out = ctx.up_written_in; 158 | } 159 | 160 | for (i = 0; i < ac->num; i++) { 161 | pthread_join(ts[i], NULL); 162 | } 163 | for (i = 0; i < ac->num; i++) { 164 | actor_ctx ctx = ctxs[i]; 165 | if (ctx.up_in) { free(ctx.up_in); free(ctx.up_written_in); free(ctx.up_out); free(ctx.up_written_out); } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/eval.h: -------------------------------------------------------------------------------- 1 | #ifndef eval_h 2 | #define eval_h 3 | 4 | #include "parser.h" 5 | 6 | void* eval(void*); 7 | void eval_actors(actors*); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "parser.h" 7 | 8 | char* remove_comments(char* inpt) { 9 | char* str = inpt; 10 | int ln = strlen(inpt); 11 | 12 | while (*str != '\0') { 13 | switch (*str) { 14 | case '+': 15 | case '-': 16 | case '>': 17 | case '<': 18 | case '.': 19 | case ',': 20 | case '[': 21 | case ']': 22 | case '^': 23 | case 'v': 24 | case 'u': 25 | break; 26 | case '\n': 27 | if (*(str+1) == '\n') { str++; break; } 28 | default: 29 | memmove(str, str+1, ln-(str-inpt)); 30 | } 31 | str++; 32 | } 33 | 34 | return inpt; 35 | } 36 | 37 | char* optimize_zero(char* inpt) { 38 | char* str = inpt; 39 | int ln = strlen(inpt); 40 | 41 | while (*str != '\0') { 42 | if ((!strncmp(str, "[+]", 3))||(!strncmp(str, "[-]", 3))) { 43 | memmove(str, str+2, ln-(str-inpt)); 44 | *str = '0'; 45 | } 46 | str++; 47 | } 48 | 49 | return inpt; 50 | } 51 | 52 | int optimize_loop(bytecode* s, int begin, int end) { 53 | bytecode rep = s[begin]; 54 | if (end-begin == 2 && (rep.code == FWD || rep.code == BCK)) { 55 | // [>] || [<] == MOVE_PTR 56 | int narg = rep.code == FWD ? rep.arg : -rep.arg; 57 | s[begin-1] = (bytecode){.code=MOVE_PTR, .arg=narg}; 58 | return 2; 59 | } else if (end-begin == 5 && rep.code == DEC && s[begin+2].code == INC && 60 | rep.arg == s[begin+2].arg == 1 && s[begin+1].arg == s[begin+3].arg) { 61 | // [->+<] || [-<+>] == MOVE_DATA 62 | if (s[begin+1].code == FWD && s[begin+3].code == BCK) { 63 | s[begin-1] = (bytecode){.code=MOVE_DATA, .arg=s[begin+1].arg}; 64 | return 5; 65 | } else if (s[begin+1].code == BCK && s[begin+3].code == FWD) { 66 | s[begin-1] = (bytecode){.code=MOVE_DATA, .arg=-s[begin+1].arg}; 67 | return 5; 68 | } 69 | } 70 | return 0; 71 | } 72 | 73 | actors* parse(char* inpt) { 74 | #define build_op(c, a) {\ 75 | res = realloc(res, sizeof(bytecode)*(++idx));\ 76 | res[idx-1] = (bytecode){.code=c, .arg=a};\ 77 | } 78 | #define build_cumulative_op(c) {\ 79 | if (idx > 0 && res[idx-1].code == c) { res[idx-1].arg++; dup++; }\ 80 | else build_op(c, 1);\ 81 | } 82 | int idx = 0; 83 | int dup = 0; 84 | char* str = inpt; 85 | int loop_stack[256]; 86 | int loop_depth = 0; 87 | actors* ac = malloc(sizeof(actors)); 88 | ac->num = 0; 89 | ac->code = NULL; 90 | bytecode* res = NULL; 91 | inpt = remove_comments(inpt); 92 | inpt = optimize_zero(inpt); 93 | 94 | while (*inpt != '\0') { 95 | switch (*inpt) { 96 | case '+': build_cumulative_op(INC); break; 97 | case '-': build_cumulative_op(DEC); break; 98 | case '>': build_cumulative_op(FWD); break; 99 | case '<': build_cumulative_op(BCK); break; 100 | case '.': build_op(PRN, 0); break; 101 | case ',': build_op(READ, 0); break; 102 | case '0': build_cumulative_op(ZERO); break; 103 | case '[': { 104 | loop_stack[loop_depth++] = idx; 105 | build_op(STARTL, 0); 106 | break; 107 | } 108 | case ']': { 109 | int matching_begin = loop_depth > 0 ? loop_stack[--loop_depth] : -1; 110 | if (matching_begin < 0) { 111 | free_actors(ac); 112 | free(res); 113 | return NULL; 114 | } 115 | 116 | build_op(ENDL, matching_begin+1); 117 | int removed = optimize_loop(res, matching_begin+1, idx); 118 | idx -= removed; 119 | dup += removed; 120 | res = realloc(res, sizeof(bytecode)*idx); 121 | if (!removed) { 122 | res[matching_begin].arg = idx; 123 | if (res[matching_begin].code != STARTL && idx < 30) { 124 | printf("wat: %d %d\n", matching_begin, idx); 125 | } 126 | } 127 | break; 128 | } 129 | 130 | case '^': build_op(SEND, 0); break; 131 | case 'v': build_op(SEND, 1); break; 132 | case 'u': build_op(RECV, 0); break; 133 | 134 | case '\n': 135 | if (*(++inpt) == '\n') { 136 | if (res) { 137 | build_op(HALT, 0); 138 | ac->code = realloc(ac->code, sizeof(bytecode*)*(++ac->num)); 139 | ac->code[ac->num-1] = res; 140 | res = NULL; 141 | } 142 | idx = 0; 143 | str = inpt+1; 144 | } else if (*inpt == '\0') continue; 145 | break; 146 | } 147 | inpt++; 148 | } 149 | 150 | if (res) { 151 | build_op(HALT, 0); 152 | ac->code = realloc(ac->code, sizeof(bytecode*)*(++ac->num)); 153 | ac->code[ac->num-1] = res; 154 | } 155 | 156 | return ac; 157 | #undef build_op 158 | #undef build_cumulative_op 159 | } 160 | 161 | void free_actors(actors* ac) { 162 | for (int i = 0; i < ac->num; i++) free(ac->code[i]); 163 | free(ac); 164 | } 165 | -------------------------------------------------------------------------------- /src/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef parser_h 2 | #define parser_h 3 | 4 | enum BYTECODES { 5 | ZERO = 0, 6 | INC, 7 | DEC, 8 | FWD, 9 | BCK, 10 | PRN, 11 | READ, 12 | STARTL, 13 | ENDL, 14 | 15 | SEND, 16 | RECV, 17 | 18 | MOVE_PTR, 19 | MOVE_DATA, 20 | 21 | HALT, 22 | }; 23 | 24 | typedef struct { 25 | int code; 26 | int arg; 27 | } bytecode; 28 | 29 | typedef struct { 30 | int num; 31 | bytecode** code; 32 | } actors; 33 | 34 | actors* parse(char*); 35 | void free_actors(actors*); 36 | 37 | #endif 38 | --------------------------------------------------------------------------------