├── .gitignore ├── src ├── Makefile ├── compiler.h ├── lexer.l ├── compiler.c └── parser.y ├── examples ├── Makefile ├── http_parser.h ├── varray.h ├── fiber.h ├── echo.co ├── http_parser.c └── http_server.co ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.o 3 | corc 4 | parser.tab.* 5 | lex.yy.c 6 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | corc: parser.tab.o lex.yy.o compiler.o 2 | $(CC) -W -Wall $^ -ll -o $@ 3 | 4 | parser.tab.c: parser.y 5 | bison -d $< 6 | 7 | lex.yy.c: lexer.l 8 | flex $< 9 | 10 | .PHONY: clean 11 | 12 | clean: 13 | @rm *.o 14 | @rm lex.yy.c parser.tab.* corc 15 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | all: echo-server http-server 2 | 3 | http-server: http_server.c fiber.h http_parser.c http_parser.h varray.h 4 | $(CC) -O3 http_server.c http_parser.c -l ev -o http-server 5 | 6 | http_server.c: http_server.co ../src/corc 7 | ../src/corc < http_server.co > http_server.c 8 | 9 | echo-server: echo.c fiber.h 10 | $(CC) echo.c -l ev -o echo-server 11 | 12 | echo.c: echo.co ../src/corc 13 | ../src/corc < echo.co > echo.c 14 | 15 | ../src/corc: 16 | cd ../src; make 17 | 18 | -------------------------------------------------------------------------------- /examples/http_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_PARSER_H 2 | #define HTTP_PARSER_H 3 | 4 | #define HTTP_METHOD 0 5 | #define HTTP_TARGET 1 6 | #define HTTP_VERSION 2 7 | #define HTTP_HEADER_KEY 3 8 | #define HTTP_HEADER_VALUE 4 9 | #define HTTP_NONE 6 10 | #define HTTP_BODY 7 11 | 12 | typedef struct { 13 | int index; 14 | int len; 15 | int type; 16 | } http_token_t; 17 | 18 | typedef struct { 19 | int content_length; 20 | int len; 21 | int token_start_index; 22 | int start; 23 | int content_length_i; 24 | char in_content_length; 25 | char state; 26 | char sub_state; 27 | } http_parser_t; 28 | 29 | http_token_t http_parse(http_parser_t* parser, char* input, int n); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jeremy Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/varray.h: -------------------------------------------------------------------------------- 1 | #ifndef VARRAY_H 2 | #define VARRAY_H 3 | 4 | #include 5 | 6 | #define varray_decl(type) \ 7 | typedef struct { \ 8 | type* buf; \ 9 | int capacity; \ 10 | int size; \ 11 | } varray_##type##_t; \ 12 | void varray_##type##_push(varray_##type##_t* varray, type a); \ 13 | void varray_##type##_init(varray_##type##_t* varray, int capacity); 14 | 15 | #define varray_defn(type) \ 16 | void varray_##type##_push(varray_##type##_t* varray, type a) { \ 17 | if (varray->size == varray->capacity) { \ 18 | varray->capacity *= 2; \ 19 | varray->buf = realloc(varray->buf, varray->capacity * sizeof(type)); \ 20 | } \ 21 | varray->buf[varray->size] = a; \ 22 | varray->size++; \ 23 | } \ 24 | void varray_##type##_init(varray_##type##_t* varray, int capacity) { \ 25 | varray->buf = malloc(sizeof(type) * capacity); \ 26 | varray->size = 0; \ 27 | varray->capacity = capacity; \ 28 | } 29 | 30 | #define varray_t(type) \ 31 | varray_##type##_t 32 | 33 | #define varray_push(type, varray, a) \ 34 | varray_##type##_push(varray, a); 35 | 36 | #define varray_init(type, varray, capacity) \ 37 | varray_##type##_init(varray, capacity); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/compiler.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPILER_H 2 | #define COMPILER_H 3 | 4 | typedef struct { 5 | char* condition; 6 | struct statement_s* statements; 7 | struct statement_s* else_statements; 8 | int raw; 9 | } if_t; 10 | 11 | typedef struct { 12 | char* sub; 13 | int id; 14 | } call_t; 15 | 16 | typedef struct { 17 | char* exp; 18 | int raw; 19 | int id; 20 | } yield_t; 21 | 22 | typedef struct { 23 | char* condition; 24 | struct statement_s* statements; 25 | int raw; 26 | } while_t; 27 | 28 | typedef struct statement_s { 29 | union { 30 | if_t if_; 31 | while_t while_; 32 | call_t call_; 33 | yield_t yield_; 34 | int id; 35 | char* string; 36 | } s; 37 | struct statement_s* next; 38 | int type; 39 | } statement_t; 40 | 41 | typedef struct { 42 | char* name; 43 | } subroutine_t; 44 | 45 | typedef struct { 46 | char* name; 47 | char* type; 48 | char* rettype; 49 | } coroutine_t; 50 | 51 | typedef struct node_s { 52 | union { 53 | coroutine_t coroutine; 54 | subroutine_t subroutine; 55 | char* string; 56 | } val; 57 | struct node_s* next; 58 | statement_t* statements; 59 | int type; 60 | } node_t; 61 | 62 | void compile(node_t* root); 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /src/lexer.l: -------------------------------------------------------------------------------- 1 | %{ 2 | #include "parser.tab.h" 3 | 4 | int call_id = 2; 5 | char* raw_buf = NULL; 6 | int raw_count = 0; 7 | int raw_capacity = 128; 8 | 9 | %} 10 | 11 | %x raw 12 | 13 | %% 14 | 15 | "if" { return IF; } 16 | "else" { return ELSE; } 17 | "while" { return WHILE; } 18 | "yield" { return YIELD; } 19 | "call" { yylval.num = call_id++; return CALL; } 20 | "coroutine" { return ASYNC; } 21 | "sub" { return SUBROUTINE; } 22 | \([^\)]*\) { yylval.str = strdup(yytext); return TYPE; } 23 | "{%" { BEGIN(raw); } 24 | \/\/.*\n { } 25 | "%}" { 26 | BEGIN(INITIAL); 27 | raw_buf[raw_count] = '\0'; 28 | yylval.str = raw_buf; 29 | raw_buf = NULL; 30 | raw_count = 0; 31 | raw_capacity = 128; 32 | return RAWC; 33 | } 34 | (.|\n) { 35 | if (!raw_buf || raw_count == raw_capacity) { 36 | raw_capacity *= 2; 37 | raw_buf = realloc(raw_buf, raw_capacity); 38 | } 39 | char c = yytext[0]; 40 | raw_buf[raw_count] = c; 41 | raw_count++; 42 | } 43 | ";" { return SEMICOLON; } 44 | "{" { return OPEN_BRACE; } 45 | "}" { return CLOSE_BRACE; } 46 | [a-zA-Z0-9_]+ { yylval.str = strdup(yytext); return IDENT; } 47 | [ \t\n] { } 48 | 49 | %% 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # corc 2 | 3 | Stackless coroutine compiler for C 4 | 5 | ## Purpose 6 | 7 | Unstructured state machines are not a good solution for asynchronous logic. They 8 | have all the same problems as `goto` making the logic of your program very difficult 9 | to follow. This compiler allows you to add stackless coroutines to your C program. 10 | These coroutines enable you to structure your asynchronous logic in a more readable 11 | and maintainable way. 12 | 13 | ## Usage 14 | 15 | ### Build the compiler 16 | 17 | `cd src; make` 18 | 19 | ### Example Program 20 | 21 | ```c 22 | {% 23 | 24 | struct example_s { 25 | int i; 26 | int count_twice; 27 | }; 28 | 29 | void increment(struct example_s* ctx) { 30 | ctx->i++; 31 | } 32 | 33 | int twice(struct example_s* ctx) { 34 | return ctx->count_twice; 35 | } 36 | 37 | %} //inject raw c into your program 38 | 39 | //define the return type, name and argument type of the coroutine 40 | coroutine (int) count_to_five(struct example_s*) { 41 | if twice { //conditions can be calls to procedures like this or raw c 42 | //use {% and %} to drop down to C when convenient the argument can be accessed via arg 43 | {% arg->i = 0; %} 44 | call count; //call subroutines with the call keyword 45 | {% arg->i = 0; %} 46 | call count; 47 | } else { 48 | {% arg->i = 0; %} 49 | call count; 50 | } 51 | } 52 | 53 | sub count { //define a subroutine 54 | while {% arg->i <= 5 %} { 55 | increment; //call procedures that operate on the argument 56 | yield {% arg->i; %}; //yield a return value 57 | } 58 | } 59 | 60 | {% 61 | 62 | int main() { 63 | int state = 0; //stores the state of the coroutine 64 | struct example_s ctx = { .i = 0, .count_twice = 1 }; 65 | 66 | //corc generates a procedure that takes an int pointer and the argument type 67 | call_count_to_five(&state, &ctx); //returns 1 68 | call_count_to_five(&state, &ctx); //returns 2 69 | call_count_to_five(&state, &ctx); //returns 3 70 | call_count_to_five(&state, &ctx); //returns 4 71 | call_count_to_five(&state, &ctx); //returns 5 72 | 73 | call_count_to_five(&state, &ctx); //returns 1 74 | call_count_to_five(&state, &ctx); //returns 2 75 | call_count_to_five(&state, &ctx); //returns 3 76 | call_count_to_five(&state, &ctx); //returns 4 77 | call_count_to_five(&state, &ctx); //returns 5 78 | 79 | call_count_to_five(&state, &ctx); //returns undefined int, state will be -1; 80 | assert(state == -1); 81 | } 82 | 83 | %} 84 | ``` 85 | 86 | The compiler operates on standard input and output. To compile this program: 87 | `corc < example.co > example.c` 88 | 89 | Take a look at `examples/echo.co` for a more real world example that implements 90 | fibers and an echo server on top of libev. 91 | 92 | -------------------------------------------------------------------------------- /examples/fiber.h: -------------------------------------------------------------------------------- 1 | #ifndef FIBER_H 2 | #define FIBER_H 3 | 4 | #include 5 | 6 | #define FIBER_TIMEOUT 1 7 | 8 | typedef void* fiber_t; 9 | 10 | #define _spawn_fiber(_1, _2, _3, NAME, ...) NAME 11 | 12 | #define spawn_fiber(...) _spawn_fiber(__VA_ARGS__, \ 13 | spawn_fiber2, spawn_fiber1, _err)(__VA_ARGS__) 14 | 15 | #define spawn_fiber_main(coroutine, param) \ 16 | ctx->state = 0; \ 17 | ctx->arg = param; \ 18 | ev_init(&ctx->timer, coroutine##_timeout); \ 19 | ctx->timer.data = ctx; \ 20 | ev_io_init(&ctx->io, coroutine##_callback, -1, EV_READ); \ 21 | await_t await = call_##coroutine(&ctx->state, ctx->arg); \ 22 | schedule_or_finish(coroutine) 23 | 24 | #define spawn_fiber1(coroutine, param) \ 25 | coroutine##_ctx_t* ctx = malloc(sizeof(coroutine##_ctx_t)); \ 26 | spawn_fiber_main(coroutine, param) 27 | 28 | #define spawn_fiber2(coroutine, param, fiber_handle) \ 29 | coroutine##_ctx_t* ctx = malloc(sizeof(coroutine##_ctx_t)); \ 30 | fiber_handle = (void*)ctx; \ 31 | spawn_fiber_main(coroutine, param) 32 | 33 | #define fiber_resume(coroutine, handle) \ 34 | coroutine##_ctx_t* ctx = (coroutine##_ctx_t*)handle; \ 35 | await_t await = call_##coroutine(&ctx->state, ctx->arg); \ 36 | schedule_or_finish(coroutine) 37 | 38 | #define schedule_or_finish(coroutine) \ 39 | ev_io_stop(fiber_scheduler, &ctx->io); \ 40 | if (ctx->state == -1) { \ 41 | await.fd = -1; \ 42 | ev_timer_stop(fiber_scheduler, &ctx->timer); \ 43 | free(ctx); \ 44 | } else { \ 45 | if (await.timeout > 0.f) { \ 46 | ctx->timer.repeat = await.timeout; \ 47 | ev_timer_again(fiber_scheduler, &ctx->timer); \ 48 | } else if (await.timeout < 0.f) { \ 49 | ev_timer_stop(fiber_scheduler, &ctx->timer); \ 50 | } \ 51 | ctx->io.data = ctx; \ 52 | ev_io_init(&ctx->io, coroutine##_callback, await.fd, await.type); \ 53 | ev_io_start(fiber_scheduler, &ctx->io); \ 54 | } 55 | 56 | #define declare_fiber(name, type) \ 57 | typedef struct { \ 58 | int state; \ 59 | type arg; \ 60 | ev_timer timer; \ 61 | ev_io io; \ 62 | } name##_ctx_t; \ 63 | void name##_callback(EV_P_ ev_io *w, int revents) { \ 64 | name##_ctx_t* ctx = (name##_ctx_t*)w->data; \ 65 | await_t await = call_##name(&ctx->state, ctx->arg); \ 66 | schedule_or_finish(name) \ 67 | } \ 68 | void name##_timeout(EV_P_ ev_timer *w, int revents) { \ 69 | name##_ctx_t* ctx = (name##_ctx_t*)w->data; \ 70 | fibererror = FIBER_TIMEOUT; \ 71 | await_t await = call_##name(&ctx->state, ctx->arg); \ 72 | schedule_or_finish(name) \ 73 | } 74 | 75 | #define fiber_scheduler_init() fiber_scheduler = EV_DEFAULT 76 | 77 | #define fiber_scheduler_run() ev_run(fiber_scheduler, 0) 78 | 79 | typedef struct { 80 | int fd; 81 | int type; 82 | float timeout; 83 | } await_t; 84 | 85 | void schedule_fiber(await_t await, ev_io* io, void* ctx, void(*cb)(struct ev_loop*, ev_io*, int)); 86 | await_t fiber_await(int fd, int type, float timeout); 87 | await_t fiber_pause(); 88 | 89 | extern struct ev_loop* fiber_scheduler; 90 | extern int fibererror; 91 | 92 | #endif 93 | 94 | #ifdef FIBER_IMPL 95 | 96 | int fibererror = 0; 97 | 98 | struct ev_loop* fiber_scheduler; 99 | 100 | void schedule_fiber(await_t await, ev_io* io, void* ctx, void(*cb)(struct ev_loop*, ev_io*, int)) { 101 | } 102 | 103 | await_t fiber_pause() { 104 | return (await_t) { 105 | .fd = -1, 106 | .type = 0, 107 | .timeout = -1.f 108 | }; 109 | } 110 | 111 | await_t fiber_await(int fd, int type, float timeout) { 112 | return (await_t) { 113 | .fd = fd, 114 | .type = type, 115 | .timeout = timeout 116 | }; 117 | } 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /examples/echo.co: -------------------------------------------------------------------------------- 1 | {% 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #define FIBER_IMPL 9 | #include "fiber.h" 10 | 11 | #define BUF_SIZE 128 12 | 13 | struct echo_server_s { 14 | int socket; 15 | int port; 16 | socklen_t len; 17 | struct sockaddr_in addr; 18 | }; 19 | 20 | struct echo_session_s { 21 | int socket; 22 | char* buf; 23 | int bytes; 24 | int active; 25 | }; 26 | 27 | await_t call_echo_server_listen(int* state, struct echo_server_s* arg); 28 | await_t call_echo_session(int* state, struct echo_session_s* arg); 29 | 30 | declare_fiber(echo_server_listen, struct echo_server_s*); 31 | declare_fiber(echo_session, struct echo_session_s*); 32 | 33 | void accept_connection(struct echo_server_s* arg) { 34 | int sock = accept(arg->socket, (struct sockaddr *)&arg->addr, &arg->len); 35 | if (sock > 0) { 36 | struct echo_session_s* session = malloc(sizeof(struct echo_session_s)); 37 | session->socket = sock; 38 | int flags = fcntl(sock, F_GETFL, 0); 39 | fcntl(sock, F_SETFL, flags | O_NONBLOCK); 40 | spawn_fiber(echo_session, session); 41 | } 42 | } 43 | 44 | void bind_localhost(int s, struct sockaddr_in* addr, int port) { 45 | addr->sin_family = AF_INET; 46 | addr->sin_addr.s_addr = INADDR_ANY; 47 | addr->sin_port = htons(port); 48 | int rc = bind(s, (struct sockaddr *)addr, sizeof(struct sockaddr_in));; 49 | if (rc < 0) { 50 | exit(1); 51 | } 52 | } 53 | 54 | void echo_listen(struct echo_server_s* serv) { 55 | serv->socket = socket(AF_INET, SOCK_STREAM, 0); 56 | bind_localhost(serv->socket, &serv->addr, serv->port); 57 | serv->len = sizeof(serv->addr); 58 | listen(serv->socket, 10); 59 | } 60 | 61 | void read_client_socket(struct echo_session_s* session) { 62 | errno = 0; 63 | if (!session->buf) { 64 | session->buf = malloc(BUF_SIZE); 65 | } 66 | session->bytes = read(session->socket, session->buf, BUF_SIZE); 67 | if (session->bytes == 0) { 68 | session->active = 0; 69 | } 70 | } 71 | 72 | void write_client_socket(struct echo_session_s* session) { 73 | errno = 0; 74 | write(session->socket, session->buf, session->bytes); 75 | } 76 | 77 | void free_buffer(struct echo_session_s* session) { 78 | free(session->buf); 79 | session->buf = NULL; 80 | } 81 | 82 | void end_session(struct echo_session_s* session) { 83 | close(session->socket); 84 | free(session); 85 | } 86 | 87 | %} 88 | 89 | coroutine (await_t) echo_server_listen(struct echo_server_s*) { 90 | echo_listen; 91 | while {% 1 %} { 92 | yield {% fiber_await(arg->socket, EV_READ, -1.f); %}; //wait for accept readability 93 | accept_connection; 94 | } 95 | } 96 | 97 | coroutine (await_t) echo_session(struct echo_session_s*) { 98 | {% arg->active = 1; %} 99 | while {% arg->active %} { 100 | read_client_socket; 101 | while {% errno == EWOULDBLOCK %} { 102 | free_buffer; 103 | yield {% fiber_await(arg->socket, EV_READ, 10.f); %}; //await readability 104 | if {% fibererror %} { 105 | {% arg->active = 0; %} 106 | {% fibererror = 0; %} 107 | } else { 108 | read_client_socket; 109 | } 110 | } 111 | if {% arg->active %} { 112 | write_client_socket; 113 | while {% errno == EWOULDBLOCK %} { 114 | yield {% fiber_await(arg->socket, EV_WRITE, -1.f); %}; //await writeability 115 | write_client_socket; 116 | } 117 | } 118 | } 119 | end_session; 120 | } 121 | 122 | {% 123 | 124 | int main() { 125 | fiber_scheduler_init(); 126 | struct echo_server_s* serv = malloc(sizeof(struct echo_server_s)); 127 | serv->port = 8000; 128 | spawn_fiber(echo_server_listen, serv); 129 | fiber_scheduler_run(); 130 | free(serv); 131 | } 132 | 133 | %} 134 | -------------------------------------------------------------------------------- /src/compiler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "compiler.h" 5 | #include "parser.tab.h" 6 | 7 | #define reverse_list(type, list) \ 8 | type* head = list; \ 9 | type* next = head->next; \ 10 | head->next = NULL; \ 11 | while (next) { \ 12 | type* tmp = next->next; \ 13 | next->next = head; \ 14 | head = next; \ 15 | next = tmp; \ 16 | } \ 17 | 18 | statement_t* reverse_statements(statement_t* statements) { 19 | if (!statements) return NULL; 20 | reverse_list(statement_t, statements); 21 | statement_t* statement = head; 22 | while (statement) { 23 | if (statement->type == IF) { 24 | statement->s.if_.statements = reverse_statements(statement->s.if_.statements); 25 | statement->s.if_.else_statements = reverse_statements(statement->s.if_.else_statements); 26 | } else if (statement->type == WHILE) { 27 | statement->s.while_.statements = reverse_statements(statement->s.while_.statements); 28 | } 29 | statement = statement->next; 30 | } 31 | return head; 32 | } 33 | 34 | node_t* reverse(node_t* nodes) { 35 | reverse_list(node_t, nodes); 36 | node_t* node = head; 37 | while (node) { 38 | node->statements = reverse_statements(node->statements); 39 | node = node->next; 40 | } 41 | return head; 42 | } 43 | 44 | void output_statements(statement_t* statements, node_t* root, int call_id) { 45 | statement_t* statement = statements; 46 | node_t* node = NULL; 47 | while (statement) { 48 | switch (statement->type) { 49 | case IF: 50 | if (statement->s.if_.raw) { 51 | printf("if (%s) {\n", statement->s.if_.condition); 52 | } else { 53 | printf("if (%s(arg)) {\n", statement->s.if_.condition); 54 | } 55 | output_statements(statement->s.if_.statements, root, call_id); 56 | printf("}\n"); 57 | if (statement->s.if_.else_statements) { 58 | printf("else {\n"); 59 | output_statements(statement->s.if_.else_statements, root, call_id); 60 | printf("}\n"); 61 | } 62 | break; 63 | case WHILE: 64 | if (statement->s.while_.raw) { 65 | printf("while (%s) {\n", statement->s.while_.condition); 66 | } else { 67 | printf("while (%s(arg)) {\n", statement->s.while_.condition); 68 | } 69 | output_statements(statement->s.while_.statements, root, call_id); 70 | printf("}\n"); 71 | break; 72 | case EXEC: 73 | printf("%s(arg);\n", statement->s.string); 74 | break; 75 | case YIELD: 76 | printf("*state = %d%d;\n", call_id, statement->s.yield_.id); 77 | if (!statement->s.yield_.exp) { 78 | printf("return;\n"); 79 | } else if (statement->s.yield_.raw) { 80 | printf("return %s\n", statement->s.yield_.exp); 81 | } else { 82 | printf("return %s(arg);\n", statement->s.yield_.exp); 83 | } 84 | printf("case %d%d:\n", call_id, statement->s.yield_.id); 85 | break; 86 | case RAWC: 87 | printf("%s\n", statement->s.string); 88 | break; 89 | case CALL: 90 | node = root; 91 | while (node) { 92 | if ( 93 | node->type == SUBROUTINE && 94 | strcmp(node->val.subroutine.name, statement->s.call_.sub) == 0 95 | ) { 96 | output_statements(node->statements, root, statement->s.call_.id); 97 | break; 98 | } 99 | node = node->next; 100 | } 101 | break; 102 | } 103 | statement = statement->next; 104 | } 105 | } 106 | 107 | void compile(node_t* root) { 108 | root = reverse(root); 109 | node_t* node = root; 110 | while (node) { 111 | if (node->type == RAWC) { 112 | printf("%s\n", node->val.string); 113 | } else if (node->type == ASYNC) { 114 | statement_t* statement = node->statements; 115 | coroutine_t coro = node->val.coroutine; 116 | printf("%s call_%s(int* state, %s arg) {\n", coro.rettype, coro.name, coro.type); 117 | printf("switch (*state) {\n"); 118 | printf("case 0:\n"); 119 | output_statements(statement, root, 1); 120 | printf("}\n"); 121 | printf("*state = -1;\n"); 122 | if (strcmp("void", coro.rettype) == 0) { 123 | printf("return;\n"); 124 | } else { 125 | printf("%s ret;\n", coro.rettype); 126 | printf("return ret;\n"); 127 | } 128 | printf("}\n"); 129 | } 130 | node = node->next; 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /examples/http_parser.c: -------------------------------------------------------------------------------- 1 | #include "http_parser.h" 2 | 3 | #define HTTP_LWS 2 4 | #define HTTP_CR 3 5 | #define HTTP_CRLF 4 6 | 7 | #define HTTP_HEADER_END 5 8 | 9 | #define CONTENT_LENGTH_LOW "content-length" 10 | #define CONTENT_LENGTH_UP "CONTENT-LENGTH" 11 | 12 | http_token_t http_parse(http_parser_t* parser, char* input, int n) { 13 | for (int i = parser->start; i < n; ++i, parser->start = i + 1, parser->len++) { 14 | char c = input[i]; 15 | switch (parser->state) { 16 | case HTTP_METHOD: 17 | if (c == ' ') { 18 | http_token_t token = { 19 | .index = parser->token_start_index, 20 | .type = parser->state, 21 | .len = parser->len 22 | }; 23 | parser->state = HTTP_TARGET; 24 | parser->len = 0; 25 | parser->token_start_index = i + 1; 26 | return token; 27 | } 28 | break; 29 | case HTTP_TARGET: 30 | if (c == ' ') { 31 | http_token_t token = { 32 | .index = parser->token_start_index, 33 | .type = parser->state, 34 | .len = parser->len 35 | }; 36 | parser->state = HTTP_VERSION; 37 | parser->token_start_index = i + 1; 38 | parser->len = 0; 39 | return token; 40 | } 41 | break; 42 | case HTTP_VERSION: 43 | if (c == '\r') { 44 | parser->sub_state = HTTP_CR; 45 | return (http_token_t) { 46 | .index = parser->token_start_index, 47 | .type = HTTP_VERSION, 48 | .len = parser->len 49 | }; 50 | } else if (parser->sub_state == HTTP_CR && c == '\n') { 51 | parser->sub_state = 0; 52 | parser->len = 0; 53 | parser->token_start_index = i + 1; 54 | parser->state = HTTP_HEADER_KEY; 55 | } 56 | break; 57 | case HTTP_HEADER_KEY: 58 | if (c == ':') { 59 | parser->state = HTTP_HEADER_VALUE; 60 | parser->sub_state = HTTP_LWS; 61 | if (parser->len == parser->content_length_i + 1) parser->in_content_length = 1; 62 | parser->content_length_i = 0; 63 | return (http_token_t) { 64 | .index = parser->token_start_index, 65 | .type = HTTP_HEADER_KEY, 66 | .len = parser->len - 1 67 | }; 68 | } else if ( 69 | (c == CONTENT_LENGTH_UP[parser->content_length_i] || 70 | c == CONTENT_LENGTH_LOW[parser->content_length_i]) && 71 | parser->content_length_i < sizeof(CONTENT_LENGTH_LOW) - 1 72 | ) { 73 | parser->content_length_i++; 74 | } 75 | break; 76 | case HTTP_HEADER_VALUE: 77 | if (parser->sub_state == HTTP_LWS && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) { 78 | continue; 79 | } else if (parser->sub_state == HTTP_LWS) { 80 | parser->sub_state = 0; 81 | parser->len = 0; 82 | parser->token_start_index = i; 83 | if (parser->in_content_length) { 84 | parser->content_length *= 10; 85 | parser->content_length += c - '0'; 86 | } 87 | } else if (parser->sub_state != HTTP_LWS && c == '\r') { 88 | parser->sub_state = HTTP_CR; 89 | parser->state = HTTP_HEADER_END; 90 | parser->in_content_length = 0; 91 | return (http_token_t) { 92 | .index = parser->token_start_index, 93 | .type = HTTP_HEADER_VALUE, 94 | .len = parser->len 95 | }; 96 | } else if (parser->in_content_length) { 97 | parser->content_length *= 10; 98 | parser->content_length += c - '0'; 99 | } 100 | break; 101 | case HTTP_HEADER_END: 102 | if (parser->sub_state == 0 && c == '\r') { 103 | parser->sub_state = HTTP_CR; 104 | } else if (parser->sub_state == HTTP_CR && c == '\n') { 105 | parser->sub_state = HTTP_CRLF; 106 | } else if (parser->sub_state == HTTP_CRLF && c == '\r') { 107 | parser->sub_state = 0; 108 | return (http_token_t) { 109 | .index = i + 2, 110 | .type = HTTP_BODY, 111 | .len = parser->content_length 112 | }; 113 | } else if (parser->sub_state == HTTP_CRLF && c != '\r') { 114 | parser->sub_state = 0; 115 | parser->len = 0; 116 | parser->token_start_index = i; 117 | i--; 118 | parser->state = HTTP_HEADER_KEY; 119 | } 120 | break; 121 | 122 | } 123 | } 124 | return (http_token_t) { .index = 0, .type = HTTP_NONE, .len = 0 }; 125 | } 126 | -------------------------------------------------------------------------------- /src/parser.y: -------------------------------------------------------------------------------- 1 | %{ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "compiler.h" 7 | #include "parser.tab.h" 8 | 9 | #define YYDEBUG 1 10 | 11 | int yylex(); 12 | 13 | int await_id = 0; 14 | 15 | node_t* new_subroutine(char* name, statement_t* statements) { 16 | node_t* node = malloc(sizeof(node_t)); 17 | node->val.subroutine.name = name; 18 | node->statements = statements; 19 | node->type = SUBROUTINE; 20 | node->next = NULL; 21 | return node; 22 | } 23 | 24 | int whitespace(char c) { 25 | return c == ' ' || c == '\n' || c == '\t' || c == '\r'; 26 | } 27 | 28 | char* trim(char* string, int len) { 29 | int i = 0; 30 | while (string[i] && whitespace(string[i])) i++; 31 | int j = len - 1; 32 | while (j >= 0 && whitespace(string[j])) j--; 33 | string[j+1] = '\0'; 34 | return &string[i]; 35 | } 36 | 37 | node_t* new_coroutine(char* name, char* rettype, char* type, statement_t* statements) { 38 | node_t* node = malloc(sizeof(node_t)); 39 | node->val.coroutine.name = name; 40 | node->statements = statements; 41 | type[strlen(type) - 1] = '\0'; 42 | node->val.coroutine.type = type + 1; 43 | int retlen = strlen(rettype); 44 | rettype[retlen - 1] = '\0'; 45 | node->val.coroutine.rettype = trim(rettype + 1, retlen - 2); 46 | node->type = ASYNC; 47 | node->next = NULL; 48 | return node; 49 | } 50 | 51 | node_t* add_node(node_t* nodes, node_t* node) { 52 | node->next = nodes; 53 | return node; 54 | } 55 | 56 | statement_t* add_stmt(statement_t* statements, statement_t* statement) { 57 | if (statements) { 58 | statement->next = statements; 59 | } 60 | return statement; 61 | } 62 | 63 | statement_t* new_exec(char* fn) { 64 | statement_t* statement = malloc(sizeof(statement_t)); 65 | statement->s.string = fn; 66 | statement->type = EXEC; 67 | statement->next = NULL; 68 | return statement; 69 | } 70 | 71 | statement_t* new_yield(char* val, int raw) { 72 | statement_t* statement = malloc(sizeof(statement_t)); 73 | statement->type = YIELD; 74 | statement->s.yield_.id = await_id++; 75 | statement->s.yield_.exp = val; 76 | statement->s.yield_.raw = raw; 77 | statement->next = NULL; 78 | return statement; 79 | } 80 | 81 | statement_t* new_call(char* sub_name, int id) { 82 | statement_t* statement = malloc(sizeof(statement_t)); 83 | statement->type = CALL; 84 | statement->s.call_.sub = sub_name; 85 | statement->s.call_.id = id; 86 | statement->next = NULL; 87 | return statement; 88 | } 89 | 90 | statement_t* new_if(char* condition, statement_t* statements, statement_t* else_statements, int raw) { 91 | statement_t* statement = malloc(sizeof(statement_t)); 92 | statement->type = IF; 93 | statement->s.if_.condition = condition; 94 | statement->s.if_.raw = raw; 95 | statement->s.if_.statements = statements; 96 | statement->s.if_.else_statements = else_statements; 97 | statement->next = NULL; 98 | return statement; 99 | } 100 | 101 | statement_t* new_while(char* condition, statement_t* statements, int raw) { 102 | statement_t* statement = malloc(sizeof(statement_t)); 103 | statement->type = WHILE; 104 | statement->s.while_.raw = raw; 105 | statement->s.while_.condition = condition; 106 | statement->s.while_.statements = statements; 107 | statement->next = NULL; 108 | return statement; 109 | } 110 | 111 | statement_t* new_rawc_stmt(char* rawc) { 112 | statement_t* statement = malloc(sizeof(statement_t)); 113 | statement->type = RAWC; 114 | statement->s.string = rawc; 115 | statement->next = NULL; 116 | return statement; 117 | } 118 | 119 | node_t* new_rawc(char* rawc) { 120 | node_t* node = malloc(sizeof(node_t)); 121 | node->val.string = rawc; 122 | node->type = RAWC; 123 | node->statements = NULL; 124 | node->next = NULL; 125 | return node; 126 | } 127 | 128 | void yyerror (char const *s); 129 | %} 130 | 131 | %union { 132 | char* str; 133 | struct statement_s* statement; 134 | struct node_s* node; 135 | int num; 136 | } 137 | 138 | %token OPEN_BRACE CLOSE_BRACE SUBROUTINE ASYNC IF WHILE YIELD SEMICOLON ELSE EXEC 139 | %token CALL 140 | %token IDENT TYPE RAWC 141 | 142 | %type routine routines 143 | %type stmt stmts block else if while yield 144 | 145 | %% 146 | 147 | program: routines { compile($1); } 148 | 149 | routines: routines routine { $$ = add_node($1, $2); } 150 | | routine { $$ = $1; } 151 | ; 152 | 153 | routine: ASYNC TYPE IDENT TYPE block { $$ = new_coroutine($3, $2, $4, $5); } 154 | | SUBROUTINE IDENT block { $$ = new_subroutine($2, $3); } 155 | | RAWC { $$ = new_rawc($1); } 156 | ; 157 | 158 | block: OPEN_BRACE stmts CLOSE_BRACE { $$ = $2; } 159 | 160 | stmts: { $$ = NULL; } 161 | | stmts stmt { $$ = add_stmt($1, $2); } 162 | ; 163 | 164 | stmt: IDENT SEMICOLON { $$ = new_exec($1); } 165 | | yield { $$ = $1; } 166 | | if { $$ = $1; } 167 | | while { $$ = $1; } 168 | | CALL IDENT SEMICOLON { $$ = new_call($2, $1); } 169 | | RAWC { $$ = new_rawc_stmt($1); } 170 | ; 171 | 172 | if: IF IDENT block else { $$ = new_if($2, $3, $4, 0); } 173 | | IF RAWC block else { $$ = new_if($2, $3, $4, 1); } 174 | ; 175 | 176 | else: { $$ = NULL; } 177 | | ELSE block { $$ = $2; } 178 | ; 179 | 180 | while: WHILE IDENT block { $$ = new_while($2, $3, 0); } 181 | | WHILE RAWC block { $$ = new_while($2, $3, 1); } 182 | ; 183 | 184 | yield: YIELD SEMICOLON { $$ = new_yield(NULL, 0); } 185 | | YIELD IDENT SEMICOLON { $$ = new_yield($2, 0); } 186 | | YIELD RAWC SEMICOLON { $$ = new_yield($2, 1); } 187 | ; 188 | 189 | %% 190 | 191 | void yyerror(char const *s) { 192 | printf("%s\n", s); 193 | } 194 | 195 | int main() { 196 | return yyparse(); 197 | } 198 | -------------------------------------------------------------------------------- /examples/http_server.co: -------------------------------------------------------------------------------- 1 | {% 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #define FIBER_IMPL 10 | #include "fiber.h" 11 | #include "http_parser.h" 12 | #include "varray.h" 13 | 14 | #define BUF_SIZE 1024 15 | #define RESPONSE "HTTP/1.1 200 Ok\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nHello World!" 16 | 17 | varray_decl(http_token_t) 18 | varray_defn(http_token_t) 19 | 20 | struct http_session_s { 21 | http_parser_t parser; 22 | int socket; 23 | char* buf; 24 | int bytes; 25 | struct http_server_s* server; 26 | fiber_t fiber; 27 | http_token_t token; 28 | varray_t(http_token_t) tokens; 29 | char active; 30 | char paused; 31 | }; 32 | 33 | struct http_server_s { 34 | int socket; 35 | int port; 36 | socklen_t len; 37 | void (*request_handler)(struct http_session_s*); 38 | struct sockaddr_in addr; 39 | }; 40 | 41 | await_t call_http_server_listen(int* state, struct http_server_s* arg); 42 | await_t call_http_session(int* state, struct http_session_s* arg); 43 | await_t call_http_response(int* state, struct http_session_s* arg); 44 | 45 | declare_fiber(http_server_listen, struct http_server_s*); 46 | declare_fiber(http_session, struct http_session_s*); 47 | declare_fiber(http_response, struct http_session_s*); 48 | 49 | void accept_connections(struct http_server_s* arg) { 50 | while (errno != EWOULDBLOCK) { 51 | int sock = accept(arg->socket, (struct sockaddr *)&arg->addr, &arg->len); 52 | if (sock > 0) { 53 | struct http_session_s* session = malloc(sizeof(struct http_session_s)); 54 | *session = (struct http_session_s) { .socket = sock, .server = arg }; 55 | varray_init(http_token_t, &session->tokens, 32); 56 | int flags = fcntl(sock, F_GETFL, 0); 57 | fcntl(sock, F_SETFL, flags | O_NONBLOCK); 58 | spawn_fiber(http_session, session, session->fiber); 59 | } 60 | } 61 | errno = 0; 62 | } 63 | 64 | void bind_localhost(int s, struct sockaddr_in* addr, int port) { 65 | addr->sin_family = AF_INET; 66 | addr->sin_addr.s_addr = INADDR_ANY; 67 | addr->sin_port = htons(port); 68 | int rc = bind(s, (struct sockaddr *)addr, sizeof(struct sockaddr_in));; 69 | if (rc < 0) { 70 | exit(1); 71 | } 72 | } 73 | 74 | void http_listen(struct http_server_s* serv) { 75 | serv->socket = socket(AF_INET, SOCK_STREAM, 0); 76 | bind_localhost(serv->socket, &serv->addr, serv->port); 77 | serv->len = sizeof(serv->addr); 78 | listen(serv->socket, 128); 79 | } 80 | 81 | void read_client_socket(struct http_session_s* session) { 82 | errno = 0; 83 | if (!session->buf) { 84 | session->buf = malloc(BUF_SIZE); 85 | } 86 | int bytes = read(session->socket, session->buf + session->bytes, BUF_SIZE - session->bytes); 87 | if (bytes > 0) session->bytes += bytes; 88 | if (bytes == 0) { 89 | session->active = 0; 90 | } 91 | } 92 | 93 | void write_client_socket(struct http_session_s* session) { 94 | errno = 0; 95 | write(session->socket, RESPONSE, sizeof(RESPONSE) - 1); 96 | } 97 | 98 | void nop(struct http_session_s* session) { } 99 | 100 | void free_buffer(struct http_session_s* session) { 101 | free(session->buf); 102 | session->buf = NULL; 103 | } 104 | 105 | void end_session(struct http_session_s* session) { 106 | close(session->socket); 107 | free(session); 108 | } 109 | 110 | void parse_tokens(struct http_session_s* session) { 111 | static char const * names[] = { "METHOD", "TARGET", "VERSION", "HEADER_KEY", "HEADER_VALUE", "HEADER_END", "NONE", "BODY" }; 112 | http_token_t token; 113 | do { 114 | token = http_parse(&session->parser, session->buf, session->bytes); 115 | if (token.type != HTTP_NONE) { 116 | session->token = token; 117 | varray_push(http_token_t, &session->tokens, token); 118 | } 119 | //printf("%s: %.*s\n", names[token.type], token.len, session->buf + token.index); 120 | } while (token.type != HTTP_NONE); 121 | } 122 | 123 | void init_session(struct http_session_s* session) { 124 | session->parser = (http_parser_t){ }; 125 | session->paused = 0; 126 | session->bytes = 0; 127 | session->token = (http_token_t){ .type = HTTP_NONE }; 128 | session->tokens.size = 0; 129 | } 130 | 131 | void http_response_end(struct http_session_s* session) { 132 | if (session->paused) { 133 | session->paused = 0; 134 | fiber_resume(http_session, session->fiber); 135 | } 136 | } 137 | 138 | %} 139 | 140 | coroutine (await_t) http_server_listen(struct http_server_s*) { 141 | http_listen; 142 | while {% 1 %} { 143 | yield {% fiber_await(arg->socket, EV_READ, -1.f); %}; //wait for accept readability 144 | accept_connections; 145 | } 146 | } 147 | 148 | coroutine (await_t) http_session(struct http_session_s*) { 149 | {% arg->active = 1; %} 150 | while {% arg->active %} { 151 | init_session; 152 | while {% arg->token.type != HTTP_BODY && arg->active %} { 153 | call read_socket; 154 | parse_tokens; 155 | } 156 | if {% arg->token.len > 0 %} { 157 | while {% arg->bytes < arg->token.index + arg->token.len && arg->active %} { 158 | call read_socket; 159 | } 160 | } 161 | if {% arg->active %} { 162 | {% arg->server->request_handler(arg); %} 163 | if {% arg->paused %} { 164 | yield {% fiber_pause(); %}; 165 | nop; 166 | } 167 | } 168 | free_buffer; 169 | } 170 | end_session; 171 | } 172 | 173 | sub read_socket { 174 | read_client_socket; 175 | while {% errno == EWOULDBLOCK %} { 176 | yield {% fiber_await(arg->socket, EV_READ, 20.f); %}; //await readability 177 | if {% fibererror %} { 178 | {% arg->active = 0; %} 179 | {% fibererror = 0; %} 180 | } else { 181 | read_client_socket; 182 | } 183 | } 184 | } 185 | 186 | coroutine (await_t) http_response(struct http_session_s*) { 187 | write_client_socket; 188 | while {% errno == EWOULDBLOCK %} { 189 | {% arg->paused = 1; %} 190 | yield {% fiber_await(arg->socket, EV_WRITE, 20.f); %}; //await writeability 191 | write_client_socket; 192 | } 193 | http_response_end; 194 | } 195 | 196 | {% 197 | 198 | void handle_request(struct http_session_s* session) { 199 | spawn_fiber(http_response, session); 200 | } 201 | 202 | int main() { 203 | fiber_scheduler_init(); 204 | struct http_server_s* serv = malloc(sizeof(struct http_server_s)); 205 | serv->port = 8080; 206 | serv->request_handler = handle_request; 207 | spawn_fiber(http_server_listen, serv); 208 | fiber_scheduler_run(); 209 | free(serv); 210 | } 211 | 212 | %} 213 | --------------------------------------------------------------------------------