+1
.gitignore
+1
.gitignore
···
1
+
build
+44
Makefile
+44
Makefile
···
1
+
CC ?= cc
2
+
AR ?= ar
3
+
CFLAGS ?= -std=c11 -Wall -Wextra -Wpedantic -O2 -g
4
+
CPPFLAGS?= -Iinclude -Isrc
5
+
LDFLAGS ?=
6
+
LDLIBS ?=
7
+
8
+
BUILD := build
9
+
LIBNAME := prairie
10
+
11
+
SRC := $(wildcard src/*.c)
12
+
OBJ := $(patsubst src/%.c,$(BUILD)/%.o,$(SRC))
13
+
14
+
PUBLIC_HEADERS := $(wildcard include/prairie/*.h)
15
+
16
+
.PHONY: all clean test install uninstall
17
+
18
+
all: $(BUILD)/lib$(LIBNAME).a
19
+
20
+
# Build directory
21
+
$(BUILD):
22
+
@mkdir -p $(BUILD)
23
+
24
+
# Object files
25
+
$(BUILD)/%.o: src/%.c | $(BUILD)
26
+
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
27
+
28
+
# Static library
29
+
$(BUILD)/lib$(LIBNAME).a: $(OBJ)
30
+
$(AR) rcs $@ $^
31
+
32
+
clean:
33
+
rm -rf $(BUILD)
34
+
35
+
PREFIX ?= /usr/local
36
+
install: all
37
+
install -d $(DESTDIR)$(PREFIX)/include/prairie
38
+
install -m 0644 $(PUBLIC_HEADERS) $(DESTDIR)$(PREFIX)/include/prairie/
39
+
install -d $(DESTDIR)$(PREFIX)/lib
40
+
install -m 0644 $(BUILD)/lib$(LIBNAME).a $(DESTDIR)$(PREFIX)/lib/
41
+
42
+
uninstall:
43
+
rm -f $(DESTDIR)$(PREFIX)/lib/lib$(LIBNAME).a
44
+
rm -rf $(DESTDIR)$(PREFIX)/include/prairie
+40
include/prairie/lexer.h
+40
include/prairie/lexer.h
···
1
+
#include "stdbool.h"
2
+
3
+
#ifndef PRAIRIE_LEXER_H
4
+
#define PRAIRIE_LEXER_H
5
+
6
+
typedef enum {
7
+
PRAIRIE_TOKEN_IDENTIFIER = 0,
8
+
PRAIRIE_TOKEN_COLON = 1,
9
+
PRAIRIE_TOKEN_BODY = 2
10
+
} prairie_token_type_t;
11
+
12
+
typedef struct prairie_token_t {
13
+
prairie_token_type_t type;
14
+
char *lexeme;
15
+
struct prairie_token_t *next;
16
+
} prairie_token_t;
17
+
18
+
typedef enum {
19
+
PRAIRIE_LS_METHOD,
20
+
PRAIRIE_LS_ROUTE,
21
+
PRAIRIE_LS_PROTOCOL,
22
+
PRAIRIE_LS_FIELD_NAME,
23
+
PRAIRIE_LS_FIELD_VALUE,
24
+
PRAIRIE_LS_BODY
25
+
} prairie_lexer_state_t;
26
+
27
+
typedef struct {
28
+
prairie_token_t *token_start;
29
+
prairie_token_t *token_end;
30
+
char *acc;
31
+
prairie_lexer_state_t state;
32
+
} prairie_lexer_ctx_t;
33
+
34
+
prairie_token_t *prairie_lex(char *raw_file, int raw_file_length);
35
+
void prairie_print_token(prairie_token_t *token);
36
+
void prairie_print_tokens(prairie_token_t *token);
37
+
char *prairie_token_to_string(prairie_token_t *token);
38
+
char *prairie_token_type_to_string(prairie_token_type_t type);
39
+
40
+
#endif
+33
include/prairie/parser.h
+33
include/prairie/parser.h
···
1
+
#include "lexer.h"
2
+
3
+
#ifndef PRAIRIE_PARSER_H
4
+
#define PRAIRIE_PARSER_H
5
+
6
+
typedef enum { GET, POST } prairie_method_t;
7
+
8
+
typedef enum { HTTP_1_0, HTTP_1_1, HTTP_2 } prairie_protocol_t;
9
+
10
+
typedef struct prairie_header_t {
11
+
char *key;
12
+
char *value;
13
+
struct prairie_header_t *next;
14
+
} prairie_header_t;
15
+
16
+
typedef struct {
17
+
prairie_method_t method;
18
+
char *route;
19
+
prairie_protocol_t protocol;
20
+
char *body;
21
+
prairie_header_t *header_start;
22
+
prairie_header_t *header_end;
23
+
} prairie_response_t;
24
+
25
+
typedef struct {
26
+
prairie_token_t *current_token;
27
+
prairie_response_t *response;
28
+
} ParserContext;
29
+
30
+
prairie_response_t *prairie_parse(prairie_token_t *token_start);
31
+
void prairie_print_response(prairie_response_t *response);
32
+
33
+
#endif
+8
include/prairie/prairie.h
+8
include/prairie/prairie.h
+11
include/prairie/utils.h
+11
include/prairie/utils.h
+130
src/lexer.c
+130
src/lexer.c
···
1
+
#include "stdio.h"
2
+
#include "stdlib.h"
3
+
#include "string.h"
4
+
#include <prairie/lexer.h>
5
+
#include <prairie/utils.h>
6
+
7
+
void add_token(prairie_lexer_ctx_t *ctx, prairie_token_type_t type,
8
+
char *lexeme) {
9
+
prairie_token_t *token = malloc(sizeof(prairie_token_t));
10
+
token->type = type;
11
+
token->lexeme = lexeme;
12
+
13
+
if (ctx->token_end != NULL) {
14
+
ctx->token_end->next = token;
15
+
ctx->token_end = token;
16
+
} else {
17
+
ctx->token_end = token;
18
+
ctx->token_start = token;
19
+
}
20
+
}
21
+
22
+
void default_acc(prairie_lexer_ctx_t *ctx) {
23
+
ctx->acc = malloc(1);
24
+
ctx->acc[0] = '\0';
25
+
}
26
+
27
+
void pop_acc(prairie_lexer_ctx_t *ctx, prairie_token_type_t type) {
28
+
if (strcmp(ctx->acc, "") == 0)
29
+
return;
30
+
31
+
add_token(ctx, type, ctx->acc);
32
+
33
+
default_acc(ctx);
34
+
}
35
+
36
+
char *token_to_string_map[] = {"identifier", "colon", "body"};
37
+
38
+
char *prairie_token_to_string(prairie_token_t *token) {
39
+
return token_to_string_map[token->type];
40
+
}
41
+
char *prairie_token_type_to_string(prairie_token_type_t type) {
42
+
return token_to_string_map[type];
43
+
}
44
+
45
+
void prairie_print_token(prairie_token_t *token) {
46
+
int map_length = 3;
47
+
int max_value_length = 0;
48
+
49
+
for (int i = 0; i < map_length; i++) {
50
+
int value_length = strlen(token_to_string_map[i]);
51
+
if (value_length > max_value_length)
52
+
max_value_length = value_length;
53
+
}
54
+
55
+
printf("%s %s\n",
56
+
prairie_pad_right(token_to_string_map[token->type],
57
+
max_value_length + 1, ' '),
58
+
token->lexeme);
59
+
}
60
+
61
+
void prairie_print_tokens(prairie_token_t *token) {
62
+
prairie_token_t *tmp_token = token;
63
+
while (tmp_token != NULL) {
64
+
prairie_print_token(tmp_token);
65
+
tmp_token = tmp_token->next;
66
+
}
67
+
}
68
+
69
+
prairie_token_t *prairie_lex(char *raw_file, int raw_file_length) {
70
+
prairie_lexer_ctx_t *ctx = malloc(sizeof(prairie_lexer_ctx_t));
71
+
ctx->token_start = NULL;
72
+
ctx->token_end = NULL;
73
+
ctx->acc = NULL;
74
+
ctx->state = PRAIRIE_LS_METHOD;
75
+
default_acc(ctx);
76
+
77
+
for (int i = 0; i < raw_file_length; i++) {
78
+
switch (raw_file[i]) {
79
+
case '\n':
80
+
if (ctx->state != PRAIRIE_LS_BODY) {
81
+
if (ctx->state == PRAIRIE_LS_FIELD_NAME) {
82
+
ctx->state = PRAIRIE_LS_BODY;
83
+
break;
84
+
}
85
+
86
+
pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER);
87
+
ctx->state = PRAIRIE_LS_FIELD_NAME;
88
+
break;
89
+
}
90
+
case ' ':
91
+
if (ctx->state == PRAIRIE_LS_METHOD) {
92
+
pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER);
93
+
ctx->state = PRAIRIE_LS_ROUTE;
94
+
break;
95
+
}
96
+
if (ctx->state == PRAIRIE_LS_ROUTE) {
97
+
pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER);
98
+
ctx->state = PRAIRIE_LS_PROTOCOL;
99
+
break;
100
+
}
101
+
case ':':
102
+
if (ctx->state == PRAIRIE_LS_FIELD_NAME) {
103
+
i++;
104
+
pop_acc(ctx, PRAIRIE_TOKEN_IDENTIFIER);
105
+
add_token(ctx, PRAIRIE_TOKEN_COLON, ":");
106
+
ctx->state = PRAIRIE_LS_FIELD_VALUE;
107
+
break;
108
+
}
109
+
default:
110
+
int acc_length = strlen(ctx->acc);
111
+
char *tmp = realloc(ctx->acc, acc_length + 2);
112
+
if (!tmp) {
113
+
printf("failed to allocate accumulator in lexer");
114
+
return 0;
115
+
}
116
+
117
+
ctx->acc = tmp;
118
+
ctx->acc[acc_length] = raw_file[i];
119
+
ctx->acc[acc_length + 1] = '\0';
120
+
break;
121
+
}
122
+
}
123
+
124
+
if (ctx->state == PRAIRIE_LS_BODY)
125
+
pop_acc(ctx, PRAIRIE_TOKEN_BODY);
126
+
127
+
prairie_token_t *start = ctx->token_start;
128
+
free(ctx);
129
+
return start;
130
+
}
+136
src/parser.c
+136
src/parser.c
···
1
+
#include "stdarg.h"
2
+
#include "stdbool.h"
3
+
#include "stdio.h"
4
+
#include "stdlib.h"
5
+
#include "string.h"
6
+
#include <prairie/lexer.h>
7
+
#include <prairie/parser.h>
8
+
#include <prairie/utils.h>
9
+
10
+
void advance(ParserContext *ctx) {
11
+
ctx->current_token = ctx->current_token->next;
12
+
}
13
+
14
+
/**
15
+
* Returns true if the assertion passes
16
+
*/
17
+
bool assert(ParserContext *ctx, prairie_token_type_t type) {
18
+
if (ctx->current_token->type != type) {
19
+
printf("unexpected token, expected %s, found %s\n",
20
+
prairie_token_type_to_string(type),
21
+
prairie_token_to_string(ctx->current_token));
22
+
return false;
23
+
}
24
+
return true;
25
+
}
26
+
27
+
bool assert_advance(ParserContext *ctx, prairie_token_type_t type) {
28
+
if (assert(ctx, type)) {
29
+
advance(ctx);
30
+
return true;
31
+
}
32
+
return false;
33
+
}
34
+
35
+
prairie_method_t string_to_method(char *value) {
36
+
if (strcmp(value, "GET") == 0)
37
+
return GET;
38
+
if (strcmp(value, "POST") == 0)
39
+
return POST;
40
+
41
+
// Might want to do some error handling here but default > crash
42
+
return GET;
43
+
}
44
+
45
+
prairie_protocol_t string_to_protocol(char *value) {
46
+
if (strcmp(value, "HTTP/1.0") == 0)
47
+
return HTTP_1_0;
48
+
if (strcmp(value, "HTTP/1.1") == 0)
49
+
return HTTP_1_1;
50
+
if (strcmp(value, "HTTP/2") == 0)
51
+
return HTTP_2;
52
+
53
+
// Might want to do some error handling here but default > crash
54
+
return HTTP_1_1;
55
+
}
56
+
57
+
#define free_and_return \
58
+
{ \
59
+
printf("Error occured\n"); \
60
+
free(ctx); \
61
+
free(response); \
62
+
return NULL; \
63
+
}
64
+
65
+
void prairie_print_response(prairie_response_t *response) {
66
+
printf("%d %s %d\n", response->method, response->route, response->protocol);
67
+
prairie_header_t *header = response->header_start;
68
+
while (header != NULL) {
69
+
printf("%s: %s\n", header->key, header->value);
70
+
header = header->next;
71
+
}
72
+
printf("%s\n", response->body);
73
+
}
74
+
75
+
prairie_response_t *prairie_parse(prairie_token_t *token_start) {
76
+
prairie_response_t *response = malloc(sizeof(prairie_response_t));
77
+
ParserContext *ctx = malloc(sizeof(ParserContext));
78
+
ctx->current_token = token_start;
79
+
ctx->response = response;
80
+
81
+
if (!assert(ctx, PRAIRIE_TOKEN_IDENTIFIER))
82
+
free_and_return;
83
+
84
+
// printf("method: %s\n", ctx->current_token->lexeme);
85
+
ctx->response->method = string_to_method(ctx->current_token->lexeme);
86
+
advance(ctx);
87
+
88
+
if (!assert(ctx, PRAIRIE_TOKEN_IDENTIFIER))
89
+
free_and_return;
90
+
91
+
// printf("route: %s\n", ctx->current_token->lexeme);
92
+
ctx->response->route = ctx->current_token->lexeme;
93
+
advance(ctx);
94
+
95
+
if (!assert(ctx, PRAIRIE_TOKEN_IDENTIFIER))
96
+
free_and_return;
97
+
98
+
// printf("protocol: %s\n", ctx->current_token->lexeme);
99
+
ctx->response->protocol = string_to_protocol(ctx->current_token->lexeme);
100
+
advance(ctx);
101
+
102
+
while (ctx->current_token != NULL) {
103
+
if (ctx->current_token->type == PRAIRIE_TOKEN_IDENTIFIER) {
104
+
prairie_header_t *header = malloc(sizeof(prairie_header_t));
105
+
header->key = ctx->current_token->lexeme;
106
+
header->next = NULL;
107
+
advance(ctx);
108
+
109
+
if (!assert_advance(ctx, PRAIRIE_TOKEN_COLON))
110
+
free_and_return;
111
+
112
+
header->value = ctx->current_token->lexeme;
113
+
114
+
if (ctx->response->header_start == NULL) {
115
+
ctx->response->header_start = header;
116
+
ctx->response->header_end = header;
117
+
} else {
118
+
ctx->response->header_end->next = header;
119
+
ctx->response->header_end = header;
120
+
}
121
+
advance(ctx);
122
+
} else if (ctx->current_token->type == PRAIRIE_TOKEN_BODY) {
123
+
ctx->response->body = ctx->current_token->lexeme;
124
+
free(ctx);
125
+
126
+
return response;
127
+
} else {
128
+
printf("unexpected token. expected identifier or body, found %s\n",
129
+
prairie_token_to_string(ctx->current_token));
130
+
free_and_return;
131
+
}
132
+
}
133
+
134
+
free(ctx);
135
+
return response;
136
+
}
+11
src/prairie.c
+11
src/prairie.c
···
1
+
#include <prairie/lexer.h>
2
+
#include <prairie/parser.h>
3
+
4
+
prairie_response_t *prairie_make_response(char *raw, int length) {
5
+
prairie_token_t *token = prairie_lex(raw, length);
6
+
// print_tokens(token);
7
+
8
+
prairie_response_t *response = prairie_parse(token);
9
+
// print_response(response);
10
+
return response;
11
+
}
+42
src/utils.c
+42
src/utils.c
···
1
+
#include "stdbool.h"
2
+
#include "stdlib.h"
3
+
#include "string.h"
4
+
5
+
char *prairie_pad_right(char *value, int length, char padding) {
6
+
int value_length = strlen(value);
7
+
8
+
// Don't do any processing if the desired length is below the values length
9
+
if (length <= value_length)
10
+
return value;
11
+
12
+
char *padded_value = (char *)malloc(length + 1);
13
+
for (int i = 0; i < length; i++) {
14
+
if (i < value_length) {
15
+
padded_value[i] = value[i];
16
+
} else {
17
+
padded_value[i] = padding;
18
+
}
19
+
}
20
+
padded_value[length] = '\0';
21
+
22
+
return padded_value;
23
+
}
24
+
25
+
char *prairie_pad_left(char *value, int length, char padding) {
26
+
int value_length = strlen(value);
27
+
28
+
// Don't do any processing if the desired length is below the values length
29
+
if (length <= value_length)
30
+
return value;
31
+
32
+
char *padded_value = (char *)malloc(length + 1);
33
+
for (int i = 0; i < length; i++) {
34
+
if (i < length - value_length) {
35
+
padded_value[i] = padding;
36
+
} else {
37
+
padded_value[i] = value[i - (length - value_length)];
38
+
}
39
+
}
40
+
padded_value[length] = '\0';
41
+
return padded_value;
42
+
}