initial commit
This commit is contained in:
commit
603a06ec39
12 changed files with 947 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
bin
|
||||
src/trie.c
|
13
Makefile
Normal file
13
Makefile
Normal file
|
@ -0,0 +1,13 @@
|
|||
make: trie $(wildcard src/*.c)
|
||||
mkdir -p bin
|
||||
gcc src/*.c -Wall -Wextra -pedantic -ggdb -o bin/sysh
|
||||
|
||||
trie: triegen.py commands
|
||||
python triegen.py commands src/trie.c
|
||||
|
||||
clean:
|
||||
rm -f src/trie.c
|
||||
rm -rf bin
|
||||
|
||||
run: make
|
||||
./bin/sysh
|
333
commands
Normal file
333
commands
Normal file
|
@ -0,0 +1,333 @@
|
|||
read 0
|
||||
write 1
|
||||
open 2
|
||||
close 3
|
||||
stat 4
|
||||
fstat 5
|
||||
lstat 6
|
||||
poll 7
|
||||
lseek 8
|
||||
mmap 9
|
||||
mprotect 10
|
||||
munmap 11
|
||||
brk 12
|
||||
rt_sigaction 13
|
||||
rt_sigprocmask 14
|
||||
rt_sigreturn 15
|
||||
ioctl 16
|
||||
pread64 17
|
||||
pwrite64 18
|
||||
readv 19
|
||||
writev 20
|
||||
access 21
|
||||
pipe 22
|
||||
select 23
|
||||
sched_yield 24
|
||||
mremap 25
|
||||
msync 26
|
||||
mincore 27
|
||||
madvise 28
|
||||
shmget 29
|
||||
shmat 30
|
||||
shmctl 31
|
||||
dup 32
|
||||
dup2 33
|
||||
pause 34
|
||||
nanosleep 35
|
||||
getitimer 36
|
||||
alarm 37
|
||||
setitimer 38
|
||||
getpid 39
|
||||
sendfile 40
|
||||
socket 41
|
||||
connect 42
|
||||
accept 43
|
||||
sendto 44
|
||||
recvfrom 45
|
||||
sendmsg 46
|
||||
recvmsg 47
|
||||
shutdown 48
|
||||
bind 49
|
||||
listen 50
|
||||
getsockname 51
|
||||
getpeername 52
|
||||
socketpair 53
|
||||
setsockopt 54
|
||||
getsockopt 55
|
||||
clone 56
|
||||
fork 57
|
||||
vfork 58
|
||||
execve 59
|
||||
exit 60
|
||||
wait4 61
|
||||
kill 62
|
||||
uname 63
|
||||
semget 64
|
||||
semop 65
|
||||
semctl 66
|
||||
shmdt 67
|
||||
msgget 68
|
||||
msgsnd 69
|
||||
msgrcv 70
|
||||
msgctl 71
|
||||
fcntl 72
|
||||
flock 73
|
||||
fsync 74
|
||||
fdatasync 75
|
||||
truncate 76
|
||||
ftruncate 77
|
||||
getdents 78
|
||||
getcwd 79
|
||||
chdir 80
|
||||
fchdir 81
|
||||
rename 82
|
||||
mkdir 83
|
||||
rmdir 84
|
||||
creat 85
|
||||
link 86
|
||||
unlink 87
|
||||
symlink 88
|
||||
readlink 89
|
||||
chmod 90
|
||||
fchmod 91
|
||||
chown 92
|
||||
fchown 93
|
||||
lchown 94
|
||||
umask 95
|
||||
gettimeofday 96
|
||||
getrlimit 97
|
||||
getrusage 98
|
||||
sysinfo 99
|
||||
times 100
|
||||
ptrace 101
|
||||
getuid 102
|
||||
syslog 103
|
||||
getgid 104
|
||||
setuid 105
|
||||
setgid 106
|
||||
geteuid 107
|
||||
getegid 108
|
||||
setpgid 109
|
||||
getppid 110
|
||||
getpgrp 111
|
||||
setsid 112
|
||||
setreuid 113
|
||||
setregid 114
|
||||
getgroups 115
|
||||
setgroups 116
|
||||
setresuid 117
|
||||
getresuid 118
|
||||
setresgid 119
|
||||
getresgid 120
|
||||
getpgid 121
|
||||
setfsuid 122
|
||||
setfsgid 123
|
||||
getsid 124
|
||||
capget 125
|
||||
capset 126
|
||||
rt_sigpending 127
|
||||
rt_sigtimedwait 128
|
||||
rt_sigqueueinfo 129
|
||||
rt_sigsuspend 130
|
||||
sigaltstack 131
|
||||
utime 132
|
||||
mknod 133
|
||||
uselib 134
|
||||
personality 135
|
||||
ustat 136
|
||||
statfs 137
|
||||
fstatfs 138
|
||||
sysfs 139
|
||||
getpriority 140
|
||||
setpriority 141
|
||||
sched_setparam 142
|
||||
sched_getparam 143
|
||||
sched_setscheduler 144
|
||||
sched_getscheduler 145
|
||||
sched_get_priority_max 146
|
||||
sched_get_priority_min 147
|
||||
sched_rr_get_interval 148
|
||||
mlock 149
|
||||
munlock 150
|
||||
mlockall 151
|
||||
munlockall 152
|
||||
vhangup 153
|
||||
modify_ldt 154
|
||||
pivot_root 155
|
||||
_sysctl 156
|
||||
prctl 157
|
||||
arch_prctl 158
|
||||
adjtimex 159
|
||||
setrlimit 160
|
||||
chroot 161
|
||||
sync 162
|
||||
acct 163
|
||||
settimeofday 164
|
||||
mount 165
|
||||
umount2 166
|
||||
swapon 167
|
||||
swapoff 168
|
||||
reboot 169
|
||||
sethostname 170
|
||||
setdomainname 171
|
||||
iopl 172
|
||||
ioperm 173
|
||||
create_module 174
|
||||
init_module 175
|
||||
delete_module 176
|
||||
get_kernel_syms 177
|
||||
query_module 178
|
||||
quotactl 179
|
||||
nfsservctl 180
|
||||
getpmsg 181
|
||||
putpmsg 182
|
||||
afs_syscall 183
|
||||
tuxcall 184
|
||||
security 185
|
||||
gettid 186
|
||||
readahead 187
|
||||
setxattr 188
|
||||
lsetxattr 189
|
||||
fsetxattr 190
|
||||
getxattr 191
|
||||
lgetxattr 192
|
||||
fgetxattr 193
|
||||
listxattr 194
|
||||
llistxattr 195
|
||||
flistxattr 196
|
||||
removexattr 197
|
||||
lremovexattr 198
|
||||
fremovexattr 199
|
||||
tkill 200
|
||||
time 201
|
||||
futex 202
|
||||
sched_setaffinity 203
|
||||
sched_getaffinity 204
|
||||
set_thread_area 205
|
||||
io_setup 206
|
||||
io_destroy 207
|
||||
io_getevents 208
|
||||
io_submit 209
|
||||
io_cancel 210
|
||||
get_thread_area 211
|
||||
lookup_dcookie 212
|
||||
epoll_create 213
|
||||
epoll_ctl_old 214
|
||||
epoll_wait_old 215
|
||||
remap_file_pages 216
|
||||
getdents64 217
|
||||
set_tid_address 218
|
||||
restart_syscall 219
|
||||
semtimedop 220
|
||||
fadvise64 221
|
||||
timer_create 222
|
||||
timer_settime 223
|
||||
timer_gettime 224
|
||||
timer_getoverrun 225
|
||||
timer_delete 226
|
||||
clock_settime 227
|
||||
clock_gettime 228
|
||||
clock_getres 229
|
||||
clock_nanosleep 230
|
||||
exit_group 231
|
||||
epoll_wait 232
|
||||
epoll_ctl 233
|
||||
tgkill 234
|
||||
utimes 235
|
||||
vserver 236
|
||||
mbind 237
|
||||
set_mempolicy 238
|
||||
get_mempolicy 239
|
||||
mq_open 240
|
||||
mq_unlink 241
|
||||
mq_timedsend 242
|
||||
mq_timedreceive 243
|
||||
mq_notify 244
|
||||
mq_getsetattr 245
|
||||
kexec_load 246
|
||||
waitid 247
|
||||
add_key 248
|
||||
request_key 249
|
||||
keyctl 250
|
||||
ioprio_set 251
|
||||
ioprio_get 252
|
||||
inotify_init 253
|
||||
inotify_add_watch 254
|
||||
inotify_rm_watch 255
|
||||
migrate_pages 256
|
||||
openat 257
|
||||
mkdirat 258
|
||||
mknodat 259
|
||||
fchownat 260
|
||||
futimesat 261
|
||||
newfstatat 262
|
||||
unlinkat 263
|
||||
renameat 264
|
||||
linkat 265
|
||||
symlinkat 266
|
||||
readlinkat 267
|
||||
fchmodat 268
|
||||
faccessat 269
|
||||
pselect6 270
|
||||
ppoll 271
|
||||
unshare 272
|
||||
set_robust_list 273
|
||||
get_robust_list 274
|
||||
splice 275
|
||||
tee 276
|
||||
sync_file_range 277
|
||||
vmsplice 278
|
||||
move_pages 279
|
||||
utimensat 280
|
||||
epoll_pwait 281
|
||||
signalfd 282
|
||||
timerfd_create 283
|
||||
eventfd 284
|
||||
fallocate 285
|
||||
timerfd_settime 286
|
||||
timerfd_gettime 287
|
||||
accept4 288
|
||||
signalfd4 289
|
||||
eventfd2 290
|
||||
epoll_create1 291
|
||||
dup3 292
|
||||
pipe2 293
|
||||
inotify_init1 294
|
||||
preadv 295
|
||||
pwritev 296
|
||||
rt_tgsigqueueinfo 297
|
||||
perf_event_open 298
|
||||
recvmmsg 299
|
||||
fanotify_init 300
|
||||
fanotify_mark 301
|
||||
prlimit64 302
|
||||
name_to_handle_at 303
|
||||
open_by_handle_at 304
|
||||
clock_adjtime 305
|
||||
syncfs 306
|
||||
sendmmsg 307
|
||||
setns 308
|
||||
getcpu 309
|
||||
process_vm_readv 310
|
||||
process_vm_writev 311
|
||||
kcmp 312
|
||||
finit_module 313
|
||||
sched_setattr 314
|
||||
sched_getattr 315
|
||||
renameat2 316
|
||||
seccomp 317
|
||||
getrandom 318
|
||||
memfd_create 319
|
||||
kexec_file_load 320
|
||||
bpf 321
|
||||
execveat 322
|
||||
userfaultfd 323
|
||||
membarrier 324
|
||||
mlock2 325
|
||||
copy_file_range 326
|
||||
preadv2 327
|
||||
pwritev2 328
|
||||
pkey_mprotect 329
|
||||
pkey_alloc 330
|
||||
pkey_free 331
|
||||
statx 332
|
63
src/eval.c
Normal file
63
src/eval.c
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "eval.h"
|
||||
#include "parser.h"
|
||||
#include "scanner.h"
|
||||
|
||||
static long eval_syscall(Line* line) {
|
||||
if(line->len > 6) {
|
||||
fprintf(stderr, "sysh: too many args for syscall\n");
|
||||
return 0;
|
||||
}
|
||||
long args[6] = {0,0,0,0,0,0};
|
||||
bool cloned[6] = {0,0,0,0,0,0};
|
||||
for(int i = 0; i < line->len; i++) {
|
||||
Argument arg = line->args[i];
|
||||
if(arg.type == ARG_NUM) {
|
||||
args[i] = arg.as.num;
|
||||
} else if(arg.type == ARG_STR) {
|
||||
int len = strlen(arg.as.str);
|
||||
char* buf = malloc(len + 1);
|
||||
strcpy(buf, arg.as.str);
|
||||
args[i] = (long)buf;
|
||||
cloned[i] = true;
|
||||
} else {
|
||||
for(int j = 0; j < line->len; j++) {
|
||||
if(cloned[j]) free((void*)args[j]);
|
||||
}
|
||||
fprintf(stderr, "sysh: invalid arg type\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
long result = syscall(line->id, args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||
for(int i = 0; i < line->len; i++) {
|
||||
if(cloned[i]) free((void*)args[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static long eval_line(Line* line) {
|
||||
if(line->id >= 0) {
|
||||
return eval_syscall(line);
|
||||
} else {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
long eval_block(Block* block) {
|
||||
long result = 0;
|
||||
for(int i = 0; i < block->len; i++) {
|
||||
result = eval_line(&block->lines[i]);
|
||||
if(errno > 0) {
|
||||
fprintf(stderr, "E%d: %s\n", errno, strerror(errno));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
5
src/eval.h
Normal file
5
src/eval.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
long eval_block(Block* block);
|
34
src/main.c
Normal file
34
src/main.c
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "eval.h"
|
||||
#include "parser.h"
|
||||
#include "scanner.h"
|
||||
|
||||
#define LINE_LEN 1024
|
||||
#define PROMPT "[%ld]sysh$ "
|
||||
#define EPROMPT "[E]sysh$ "
|
||||
|
||||
void repl() {
|
||||
char buf[LINE_LEN];
|
||||
printf(PROMPT, 0L);
|
||||
while(fgets(buf, LINE_LEN, stdin)) {
|
||||
Scanner sc = init_scanner(buf);
|
||||
BlockResult br = parse(&sc);
|
||||
if(!br.is_ok) {
|
||||
printf("sysh: %s\n", br.as.err);
|
||||
printf(EPROMPT);
|
||||
} else if(br.as.ok.len > 0) {
|
||||
long result = eval_block(&br.as.ok);
|
||||
printf(PROMPT, result);
|
||||
block_free(&br.as.ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
repl();
|
||||
return 0;
|
||||
}
|
138
src/parser.c
Normal file
138
src/parser.c
Normal file
|
@ -0,0 +1,138 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "parser.h"
|
||||
#include "scanner.h"
|
||||
#include "trie.h"
|
||||
|
||||
void block_init(Block* b) {
|
||||
b->len = 0;
|
||||
b->capacity = 0;
|
||||
b->lines = NULL;
|
||||
}
|
||||
|
||||
void block_add(Block* b, Line l) {
|
||||
if(b->capacity <= b->len) {
|
||||
int new_capacity = (b->capacity == 0 ? 8 : 2*(b->capacity));
|
||||
b->lines = realloc(b->lines, new_capacity * sizeof(Line));
|
||||
b->capacity = new_capacity;
|
||||
}
|
||||
b->lines[b->len] = l;
|
||||
b->len++;
|
||||
}
|
||||
|
||||
void block_free(Block* b) {
|
||||
for(int i = 0; i < b->len; i++) {
|
||||
line_free(&b->lines[i]);
|
||||
}
|
||||
free(b->lines);
|
||||
block_init(b);
|
||||
}
|
||||
|
||||
void line_init(Line* l, long id) {
|
||||
l->id = id;
|
||||
l->len = 0;
|
||||
l->capacity = 0;
|
||||
l->args = NULL;
|
||||
}
|
||||
|
||||
void line_add(Line* l, Argument arg) {
|
||||
if(l->capacity <= l->len) {
|
||||
int new_capacity = (l->capacity == 0 ? 8 : 2*(l->capacity));
|
||||
l->args = realloc(l->args, new_capacity * sizeof(Argument));
|
||||
l->capacity = new_capacity;
|
||||
}
|
||||
l->args[l->len] = arg;
|
||||
l->len++;
|
||||
}
|
||||
|
||||
void line_free(Line* line) {
|
||||
for(int i = 0; i < line->len; i++) {
|
||||
switch(line->args[i].type) {
|
||||
case ARG_BLOCK:
|
||||
block_free(&line->args[i].as.block);
|
||||
break;
|
||||
case ARG_STR:
|
||||
case ARG_CMD:
|
||||
case ARG_VAR:
|
||||
free((char*)line->args[i].as.str);
|
||||
break;
|
||||
case ARG_NUM:
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(line->args);
|
||||
}
|
||||
|
||||
|
||||
static LineResult parse_line(Scanner* sc, int id) {
|
||||
Line line;
|
||||
line_init(&line, id);
|
||||
while(true) {
|
||||
Token tok = scanner_next(sc);
|
||||
switch(tok.type) {
|
||||
case TOK_EOF:
|
||||
case TOK_EOL:
|
||||
return OK(line, LineResult);
|
||||
case TOK_ERR:
|
||||
line_free(&line);
|
||||
return ERR(tok.as.str, LineResult);
|
||||
case TOK_INT:
|
||||
line_add(&line, (Argument){.type = ARG_NUM, .as.num = tok.as.num});
|
||||
break;
|
||||
case TOK_VAR:
|
||||
line_add(&line, (Argument){.type = ARG_VAR, .as.str = tok.as.str});
|
||||
break;
|
||||
case TOK_STR:
|
||||
line_add(&line, (Argument){.type = ARG_STR, .as.str = tok.as.str});
|
||||
break;
|
||||
case TOK_CMD:
|
||||
line_add(&line, (Argument){.type = ARG_CMD, .as.str = tok.as.str});
|
||||
break;
|
||||
default:
|
||||
line_free(&line);
|
||||
return ERR("unexpected token", LineResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BlockResult parse_block(Scanner* sc, bool braced) {
|
||||
Block block;
|
||||
block_init(&block);
|
||||
while(true) {
|
||||
Token tok = scanner_next(sc);
|
||||
if((!braced && tok.type == TOK_EOF) || (braced && tok.type == TOK_RBRACE)) {
|
||||
return OK(block, BlockResult);
|
||||
}
|
||||
switch(tok.type) {
|
||||
case TOK_ERR:
|
||||
block_free(&block);
|
||||
return ERR(tok.as.str, BlockResult);
|
||||
case TOK_EOL:
|
||||
continue;
|
||||
case TOK_CMD: {
|
||||
long id = trie_get(tok.as.str);
|
||||
token_free(&tok);
|
||||
if(id == -1) {
|
||||
block_free(&block);
|
||||
return ERR("invalid syscall or command name", BlockResult);
|
||||
}
|
||||
LineResult sr = parse_line(sc, id);
|
||||
if(!sr.is_ok) {
|
||||
block_free(&block);
|
||||
return ERR(sr.as.err, BlockResult);
|
||||
}
|
||||
block_add(&block, sr.as.ok);
|
||||
} break;
|
||||
default:
|
||||
block_free(&block);
|
||||
return ERR("unexpected token", BlockResult);
|
||||
}
|
||||
}
|
||||
return OK(block, BlockResult);
|
||||
}
|
||||
|
||||
BlockResult parse(Scanner* sc) {
|
||||
return parse_block(sc, false);
|
||||
}
|
53
src/parser.h
Normal file
53
src/parser.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "scanner.h"
|
||||
|
||||
#define RESULT(T, E) struct { bool is_ok; union { T ok; E err; } as; }
|
||||
#define OK(val, R) (R){.is_ok = 1, .as.ok = (val) }
|
||||
#define ERR(val, R) (R){.is_ok = 0, .as.err = (val) }
|
||||
|
||||
typedef struct Argument_s Argument;
|
||||
|
||||
typedef struct {
|
||||
long id;
|
||||
int len;
|
||||
int capacity;
|
||||
Argument* args;
|
||||
} Line;
|
||||
|
||||
typedef struct {
|
||||
int len;
|
||||
int capacity;
|
||||
Line* lines;
|
||||
} Block;
|
||||
|
||||
typedef enum {
|
||||
ARG_BLOCK,
|
||||
ARG_STR,
|
||||
ARG_NUM,
|
||||
ARG_VAR,
|
||||
ARG_CMD,
|
||||
} ArgType;
|
||||
|
||||
struct Argument_s {
|
||||
ArgType type;
|
||||
union {
|
||||
Block block;
|
||||
const char* str;
|
||||
long num;
|
||||
} as;
|
||||
};
|
||||
|
||||
typedef RESULT(Block, const char*) BlockResult;
|
||||
typedef RESULT(Line, const char*) LineResult;
|
||||
|
||||
void block_init(Block* b);
|
||||
void block_add(Block* b, Line l);
|
||||
void block_free(Block* b);
|
||||
|
||||
void line_init(Line* l, long id);
|
||||
void line_add(Line* l, Argument a);
|
||||
void line_free(Line* l);
|
||||
|
||||
BlockResult parse(Scanner* sc);
|
196
src/scanner.c
Normal file
196
src/scanner.c
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "scanner.h"
|
||||
|
||||
// Based heavily on the scanner implementation
|
||||
// from Crafting Interpreters by Robert Nystrom
|
||||
|
||||
Scanner init_scanner(char *src) {
|
||||
return (Scanner){.start=src, .current=src, .eof=(*src == '\0')};
|
||||
}
|
||||
|
||||
void token_free(Token* tok) {
|
||||
if(tok->type == TOK_STR || tok->type == TOK_CMD || tok->type == TOK_VAR) {
|
||||
free((char*)(tok->as.str));
|
||||
}
|
||||
}
|
||||
|
||||
static char peek(const Scanner* sc) {
|
||||
return *sc->current;
|
||||
}
|
||||
|
||||
static char next(Scanner* sc) {
|
||||
if(*sc->current == '\0') {
|
||||
sc->eof = true;
|
||||
return '\0';
|
||||
}
|
||||
char c = *sc->current;
|
||||
sc->current++;
|
||||
return c;
|
||||
}
|
||||
|
||||
static bool is_alnum(char c) {
|
||||
return (c >= 'a' && c <= 'z')
|
||||
|| (c >= 'A' && c <= 'Z')
|
||||
|| (c >= '0' && c <= '9') || c == '_';
|
||||
}
|
||||
|
||||
static bool is_digit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
static Token err_token(const char* msg) {
|
||||
Token token = {
|
||||
.type = TOK_ERR,
|
||||
.as.str = msg,
|
||||
};
|
||||
return token;
|
||||
}
|
||||
|
||||
static void skip_ws(Scanner* sc) {
|
||||
while(true) {
|
||||
char c = peek(sc);
|
||||
switch(c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
next(sc);
|
||||
break;
|
||||
case '#':
|
||||
while(peek(sc) != '\n' && peek(sc) != '\0') {
|
||||
next(sc);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Token scan_string(Scanner* sc) {
|
||||
while(peek(sc) != '\0' && peek(sc) != '\'') next(sc);
|
||||
if(peek(sc) == '\0') return err_token("EOF while scanning raw string");
|
||||
next(sc);
|
||||
|
||||
int len = sc->current - sc->start - 2;
|
||||
char* buf = malloc((len + 1) * sizeof(char));
|
||||
memcpy(buf, sc->start + 1, len);
|
||||
buf[len] = '\0';
|
||||
return (Token){
|
||||
.type = TOK_STR,
|
||||
.as.str = buf,
|
||||
};
|
||||
}
|
||||
|
||||
static char* add_char(char* buf, int* len, int* capacity, char new) {
|
||||
if(*len == *capacity) {
|
||||
int new_capacity = (*capacity == 0 ? 8 : 2*(*capacity));
|
||||
buf = realloc(buf, new_capacity);
|
||||
*capacity = new_capacity;
|
||||
}
|
||||
buf[*len] = new;
|
||||
(*len)++;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static Token scan_escape_string(Scanner* sc) {
|
||||
char* buf = NULL;
|
||||
int len = 0;
|
||||
int capacity = 0;
|
||||
char c;
|
||||
while(true) {
|
||||
c = next(sc);
|
||||
if(c == '"') break;
|
||||
if(c == '\0') {
|
||||
free(buf);
|
||||
return err_token("EOF while scanning double-quoted string");
|
||||
}
|
||||
if(c == '\\') {
|
||||
switch(next(sc)) {
|
||||
case '\\': buf = add_char(buf, &len, &capacity, '\\'); break;
|
||||
case '"': buf = add_char(buf, &len, &capacity, '"'); break;
|
||||
case 'n': buf = add_char(buf, &len, &capacity, '\n'); break;
|
||||
case 'r': buf = add_char(buf, &len, &capacity, '\r'); break;
|
||||
case 't': buf = add_char(buf, &len, &capacity, '\t'); break;
|
||||
case '0': buf = add_char(buf, &len, &capacity, '\0'); break;
|
||||
default: {
|
||||
free(buf);
|
||||
return err_token("unknown escape sequence");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf = add_char(buf, &len, &capacity, c);
|
||||
}
|
||||
}
|
||||
|
||||
buf = add_char(buf, &len, &capacity, '\0');
|
||||
buf = realloc(buf, len);
|
||||
|
||||
return (Token){
|
||||
.type = TOK_STR,
|
||||
.as.str = buf,
|
||||
};
|
||||
}
|
||||
|
||||
static Token scan_var(Scanner* sc) {
|
||||
while(is_alnum(peek(sc))) next(sc);
|
||||
|
||||
int len = sc->current - sc->start - 1;
|
||||
char* buf = malloc((len + 1) * sizeof(char));
|
||||
memcpy(buf, sc->start + 1, len);
|
||||
buf[len] = '\0';
|
||||
return (Token){
|
||||
.type = TOK_VAR,
|
||||
.as.str = buf,
|
||||
};
|
||||
}
|
||||
|
||||
static Token scan_cmd(Scanner* sc) {
|
||||
while(is_alnum(peek(sc))) next(sc);
|
||||
|
||||
int len = sc->current - sc->start;
|
||||
char* buf = malloc((len + 1) * sizeof(char));
|
||||
memcpy(buf, sc->start, len);
|
||||
buf[len] = '\0';
|
||||
return (Token){
|
||||
.type = TOK_CMD,
|
||||
.as.str = buf,
|
||||
};
|
||||
}
|
||||
|
||||
static Token scan_num(Scanner* sc) {
|
||||
while(is_digit(peek(sc))) next(sc);
|
||||
// TODO base
|
||||
int len = sc->current - sc->start;
|
||||
char buf[len+1];
|
||||
memcpy(buf, sc->start, len);
|
||||
buf[len] = '\0';
|
||||
long num = strtol(buf, NULL, 10);
|
||||
return (Token){
|
||||
.type = TOK_INT,
|
||||
.as.num = num,
|
||||
};
|
||||
}
|
||||
|
||||
Token scanner_next(Scanner* sc) {
|
||||
skip_ws(sc);
|
||||
sc->start = sc->current;
|
||||
char c = next(sc);
|
||||
if(c == '-' || is_digit(c)) {
|
||||
return scan_num(sc);
|
||||
}
|
||||
if(c == '.' || is_alnum(c)) {
|
||||
return scan_cmd(sc);
|
||||
}
|
||||
switch(c) {
|
||||
case '\0': return (Token){.type = TOK_EOF};
|
||||
case '\n':
|
||||
case ';': return (Token){.type = TOK_EOL};
|
||||
case '{': return (Token){.type = TOK_LBRACE};
|
||||
case '}': return (Token){.type = TOK_RBRACE};
|
||||
case '$': return scan_var(sc);
|
||||
case '\'': return scan_string(sc);
|
||||
case '\"': return scan_escape_string(sc);
|
||||
default: return err_token("Unexpected character");
|
||||
}
|
||||
}
|
33
src/scanner.h
Normal file
33
src/scanner.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
TOK_ERR,
|
||||
TOK_EOF,
|
||||
TOK_EOL,
|
||||
TOK_CMD,
|
||||
TOK_STR,
|
||||
TOK_INT,
|
||||
TOK_VAR,
|
||||
TOK_LBRACE,
|
||||
TOK_RBRACE,
|
||||
} TokenType;
|
||||
|
||||
// TOK_STR, TOK_CMD, TOK_VAR contain allocated data, the rest do not
|
||||
typedef struct {
|
||||
TokenType type;
|
||||
union {
|
||||
const char* str;
|
||||
long num;
|
||||
} as;
|
||||
} Token;
|
||||
|
||||
typedef struct {
|
||||
char* start;
|
||||
char* current;
|
||||
bool eof;
|
||||
} Scanner;
|
||||
|
||||
Scanner init_scanner(char* src);
|
||||
Token scanner_next(Scanner* sc);
|
||||
|
||||
void token_free(Token* tok);
|
3
src/trie.h
Normal file
3
src/trie.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
int trie_get(const char* key);
|
74
triegen.py
Executable file
74
triegen.py
Executable file
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Python script to generate a trie using switch statements in C
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Not enough arguments. Usage: triegen.py <input> <output>")
|
||||
sys.exit(1)
|
||||
|
||||
input_file = sys.argv[1]
|
||||
output_file = sys.argv[2]
|
||||
|
||||
with open(input_file, 'r') as f:
|
||||
data = [re.split('\s+', l.strip()) for l in f.read().split('\n') if len(l.strip()) > 0]
|
||||
|
||||
trie = {}
|
||||
|
||||
for line in data:
|
||||
key = line[0] + '\0'
|
||||
val = int(line[1])
|
||||
trie_local = trie
|
||||
for c in key:
|
||||
if c == '\0':
|
||||
trie_local[c] = val
|
||||
elif trie_local.get(c) != None:
|
||||
trie_local = trie_local[c]
|
||||
else:
|
||||
trie_local[c] = {}
|
||||
trie_local = trie_local[c]
|
||||
|
||||
def matches_exact(trie, start):
|
||||
if len(trie) != 1:
|
||||
return False
|
||||
if trie.get('\0') != None:
|
||||
if(start):
|
||||
return False
|
||||
return ('', trie.get('\0'))
|
||||
k, v = list(trie.items())[0]
|
||||
res = matches_exact(v, False)
|
||||
if res == False:
|
||||
return False
|
||||
return (k + res[0], res[1])
|
||||
|
||||
def write_trie(f, trie, depth):
|
||||
if line := matches_exact(trie, True):
|
||||
if len(line[0]) == 1:
|
||||
f.write('if(key[%s] == \'%s\') { return %s; } break;\n' % (depth, line[0], line[1]))
|
||||
else:
|
||||
f.write('if(strcmp(key + %s, "%s") == 0) { return %s; } break;\n' % (depth, line[0], line[1]))
|
||||
return
|
||||
ws = " " * (depth + 2)
|
||||
f.write("switch(key[%s]) {\n" % (depth))
|
||||
for k, v in trie.items():
|
||||
f.write("%scase %s: " % (ws, repr(k)))
|
||||
if k == '\0':
|
||||
f.write("return %s;\n" % (v))
|
||||
else:
|
||||
write_trie(f, v, depth + 1)
|
||||
f.write("%s}" % (" " * (depth + 1)))
|
||||
if depth != 0:
|
||||
f.write(" break;")
|
||||
f.write("\n")
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write("#include <string.h>\n")
|
||||
f.write("#include \"trie.h\"\n\n")
|
||||
f.write("/* auto-generated by triegen.py */\n\n")
|
||||
f.write("int trie_get(const char* key) {\n ")
|
||||
write_trie(f, trie, 0)
|
||||
f.write(" return -1;\n}\n")
|
||||
|
||||
|
Loading…
Reference in a new issue