package nncl import "fmt" import "os" import "io" import "strings" const MAX_VAR_LENGTH=256 type Ncl struct { } func (ncl *Ncl) eval(s string) { } /* Token type and control flow constants */ const ( TCMD = iota TWORD TPART TERROR ) const ( FERROR = iota FNORMAL FRETURN FBREAK FAGAIN ) func isSpecial(c rune, quote bool) bool { return (c == '$' || (!quote && (c == '{' || c == '}' || c == ';' || c == '\r' || c == '\n')) || c == '[' || c == ']' || c == '"') } func isSpace(c rune) bool { return (c == ' ' || c == '\t') } func isEnd(c rune) bool { return (c == '\n' || c == '\r' || c == ';'); } func scan(in RuneScanner, line, col *int) (rune, error) { r, _, err := in.ReadRune() if r == '\n' { *col = 0 *rows = *rows + 1 } *col = *col + 1 return r, err } func next(in RuneScanner, token * string.Builder, quote *bool, line, col *int) (int, error) { var err error var c rune depth := 0 var open rune var end rune for c, err = scan(in, line, col); err != nil ; c, err = scan(in, line, col) { if !*quote { /* Skip leading spaces if not quoted */ for ; isSpace(c); c,err = scan(in, line, col) { if err != nil { return TERROR, err } } /* Terminate command if not quoted */ if isEnd(c) { token.WriteRune(c) return TCMD, nil } } if (c == '$') { /* Variable token, must not start with a space or quote */ c2, err := scan(in, line, col) if err != nil { return TERROR, err } if is_space(c2) { return TERROR, fmt.Errorf("Space not allowed after $.") } token.WriteRune(c) mode := *quote *quote = false r, err := next(in, token, quote); *quote = mode if r == TWORD && quote && err == nil { return TPART, nil } return r, err } if (c == '[' || (!*quote && c == '{')) { /* Interleaving pairs are not welcome, but it simplifies the code */ open = c end = '}' if open == '[' { end = ']' } for c,_,err, depth = in.ReadRune(), 1 ; depth != 0; c,_,err = in.ReadRune() { if err != nil { return TERROR, error } if c == open { depth++ } else if c == end { depth-- } token.WriteRune(c) } } else if c == '"' { *quote = !*quote; if (*quote) { return TPART; } if (n < 2 || (!ncl_is_space(s[1]) && !ncl_is_end(s[1]))) { return TERROR; } *from = *to = s + 1; return TWORD; } else if (*s == ']' || *s == '}') { /* Unbalanced bracket or brace */ return TERROR; } else { while (i < n && (*q || !ncl_is_space(s[i])) && !ncl_is_special(s[i], *q)) { i++; } } *to = s + i; if (i == n) { return TERROR; } if (*q) { return TPART; } return (ncl_is_space(s[i]) || ncl_is_end(s[i])) ? TWORD : TPART; } /* A helper parser struct and macro (requires C99) */ struct ncl_parser { const char *from; const char *to; const char *start; const char *end; int q; int token; }; #define ncl_each(s, len, skiperr) \ for (struct ncl_parser p = {NULL, NULL, (s), (s) + (len), 0, TERROR}; \ p.start < p.end && \ (((p.token = ncl_next(p.start, p.end - p.start, &p.from, &p.to, \ &p.q)) != TERROR) || \ (skiperr)); \ p.start = p.to) /* ------------------------------------------------------- */ /* ------------------------------------------------------- */ /* ------------------------------------------------------- */ /* ------------------------------------------------------- */ /* ------------------------------------------------------- */ typedef char ncl_value_t; const char *ncl_string(ncl_value_t *v) { return v; } int ncl_int(ncl_value_t *v) { return atoi(v); } int ncl_length(ncl_value_t *v) { return v == NULL ? 0 : strlen(v); } void ncl_free(ncl_value_t *v) { free(v); } ncl_value_t *ncl_append_string(ncl_value_t *v, s string) { size_t n = ncl_length(v); v = realloc(v, n + len + 1); memset((char *)ncl_string(v) + n, 0, len + 1); strncpy((char *)ncl_string(v) + n, s, len); return v; } ncl_value_t *ncl_append(ncl_value_t *v, ncl_value_t *tail) { v = ncl_append_string(v, ncl_string(tail), ncl_length(tail)); ncl_free(tail); return v; } ncl_value_t *ncl_alloc(s string) { return ncl_append_string(NULL, s, len); } ncl_value_t *ncl_dup(ncl_value_t *v) { return ncl_alloc(ncl_string(v), ncl_length(v)); } ncl_value_t *ncl_list_alloc() { return ncl_alloc("", 0); } int ncl_list_length(ncl_value_t *v) { int count = 0; ncl_each(ncl_string(v), ncl_length(v) + 1, 0) { if (p.token == TWORD) { count++; } } return count; } void ncl_list_free(ncl_value_t *v) { free(v); } ncl_value_t *ncl_list_at(ncl_value_t *v, int index) { int i = 0; ncl_each(ncl_string(v), ncl_length(v) + 1, 0) { if (p.token == TWORD) { if (i == index) { if (p.from[0] == '{') { return ncl_alloc(p.from + 1, p.to - p.from - 2); } return ncl_alloc(p.from, p.to - p.from); } i++; } } return NULL; } ncl_value_t *ncl_list_append(ncl_value_t *v, ncl_value_t *tail) { if (ncl_length(v) > 0) { v = ncl_append(v, ncl_alloc(" ", 2)); } if (ncl_length(tail) > 0) { int q = 0; const char *p; for (p = ncl_string(tail); *p; p++) { if (ncl_is_space(*p) || ncl_is_special(*p, 0)) { q = 1; break; } } if (q) { v = ncl_append(v, ncl_alloc("{", 1)); } v = ncl_append(v, ncl_dup(tail)); if (q) { v = ncl_append(v, ncl_alloc("}", 1)); } } else { v = ncl_append(v, ncl_alloc("{}", 2)); } return v; } /* ----------------------------- */ /* ----------------------------- */ /* ----------------------------- */ /* ----------------------------- */ typedef int (*ncl_cmd_fn_t)(struct ncl *, ncl_value_t *, void *); struct ncl_cmd { ncl_value_t *name; int arity; ncl_cmd_fn_t fn; void *arg; struct ncl_cmd *next; }; struct ncl_var { ncl_value_t *name; ncl_value_t *value; struct ncl_var *next; }; struct ncl_env { struct ncl_var *vars; struct ncl_env *parent; }; static struct ncl_env *ncl_env_alloc(struct ncl_env *parent) { struct ncl_env *env = malloc(sizeof(*env)); env->vars = NULL; env->parent = parent; return env; } static struct ncl_var *ncl_env_var(struct ncl_env *env, ncl_value_t *name) { struct ncl_var *var = malloc(sizeof(struct ncl_var)); var->name = ncl_dup(name); var->next = env->vars; var->value = ncl_alloc("", 0); env->vars = var; return var; } static struct ncl_env *ncl_env_free(struct ncl_env *env) { struct ncl_env *parent = env->parent; while (env->vars) { struct ncl_var *var = env->vars; env->vars = env->vars->next; ncl_free(var->name); ncl_free(var->value); free(var); } free(env); return parent; } struct ncl { struct ncl_env *env; struct ncl_cmd *cmds; ncl_value_t *result; }; ncl_value_t *ncl_var(struct ncl *ncl, ncl_value_t *name, ncl_value_t *v) { DBG("var(%s := %.*s)\n", ncl_string(name), ncl_length(v), ncl_string(v)); struct ncl_var *var; for (var = ncl->env->vars; var != NULL; var = var->next) { if (strcmp(var->name, ncl_string(name)) == 0) { break; } } if (var == NULL) { var = ncl_env_var(ncl->env, name); } if (v != NULL) { ncl_free(var->value); var->value = ncl_dup(v); ncl_free(v); } return var->value; } int ncl_result(struct ncl *ncl, int flow, ncl_value_t *result) { DBG("ncl_result %.*s, flow=%d\n", ncl_length(result), ncl_string(result), flow); ncl_free(ncl->result); ncl->result = result; return flow; } int ncl_subst(struct ncl *ncl, s string) { DBG("subst(%.*s)\n", (int)len, s); if (len == 0) { return ncl_result(ncl, FNORMAL, ncl_alloc("", 0)); } switch (s[0]) { case '{': if (len <= 1) { return ncl_result(ncl, FERROR, ncl_alloc("", 0)); } return ncl_result(ncl, FNORMAL, ncl_alloc(s + 1, len - 2)); case '$': { if (len >= MAX_VAR_LENGTH) { return ncl_result(ncl, FERROR, ncl_alloc("", 0)); } char buf[5 + MAX_VAR_LENGTH] = "set "; strncat(buf, s + 1, len - 1); return ncl_eval(ncl, buf, strlen(buf) + 1); } case '[': { ncl_value_t *expr = ncl_alloc(s + 1, len - 2); int r = ncl_eval(ncl, ncl_string(expr), ncl_length(expr) + 1); ncl_free(expr); return r; } default: return ncl_result(ncl, FNORMAL, ncl_alloc(s, len)); } } int ncl_eval(struct ncl *ncl, s string) { DBG("eval(%.*s)->\n", (int)len, s); ncl_value_t *list = ncl_list_alloc(); ncl_value_t *cur = NULL; ncl_each(s, len, 1) { DBG("ncl_next %d %.*s\n", p.token, (int)(p.to - p.from), p.from); switch (p.token) { case TERROR: DBG("eval: FERROR, lexer error\n"); return ncl_result(ncl, FERROR, ncl_alloc("", 0)); case TWORD: DBG("token %.*s, length=%d, cur=%p (3.1.1)\n", (int)(p.to - p.from), p.from, (int)(p.to - p.from), cur); if (cur != NULL) { ncl_subst(ncl, p.from, p.to - p.from); ncl_value_t *part = ncl_dup(ncl->result); cur = ncl_append(cur, part); } else { ncl_subst(ncl, p.from, p.to - p.from); cur = ncl_dup(ncl->result); } list = ncl_list_append(list, cur); ncl_free(cur); cur = NULL; break; case TPART: ncl_subst(ncl, p.from, p.to - p.from); ncl_value_t *part = ncl_dup(ncl->result); cur = ncl_append(cur, part); break; case TCMD: if (ncl_list_length(list) == 0) { ncl_result(ncl, FNORMAL, ncl_alloc("", 0)); } else { ncl_value_t *cmdname = ncl_list_at(list, 0); struct ncl_cmd *cmd = NULL; int r = FERROR; for (cmd = ncl->cmds; cmd != NULL; cmd = cmd->next) { if (strcmp(ncl_string(cmdname), ncl_string(cmd->name)) == 0) { if (cmd->arity == 0 || cmd->arity == ncl_list_length(list)) { r = cmd->fn(ncl, list, cmd->arg); break; } } } ncl_free(cmdname); if (cmd == NULL || r != FNORMAL) { ncl_list_free(list); return r; } } ncl_list_free(list); list = ncl_list_alloc(); break; } } ncl_list_free(list); return FNORMAL; } /* --------------------------------- */ /* --------------------------------- */ /* --------------------------------- */ /* --------------------------------- */ /* --------------------------------- */ void ncl_register(struct ncl *ncl, const char *name, ncl_cmd_fn_t fn, int arity, void *arg) { struct ncl_cmd *cmd = malloc(sizeof(struct ncl_cmd)); cmd->name = ncl_alloc(name, strlen(name)); cmd->fn = fn; cmd->arg = arg; cmd->arity = arity; cmd->next = ncl->cmds; ncl->cmds = cmd; } static int ncl_cmd_set(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; ncl_value_t *var = ncl_list_at(args, 1); ncl_value_t *val = ncl_list_at(args, 2); int r = ncl_result(ncl, FNORMAL, ncl_dup(ncl_var(ncl, var, val))); ncl_free(var); return r; } static int ncl_cmd_subst(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; ncl_value_t *s = ncl_list_at(args, 1); int r = ncl_subst(ncl, ncl_string(s), ncl_length(s)); ncl_free(s); return r; } #ifndef TCL_DISABLE_PUTS static int ncl_cmd_puts(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; ncl_value_t *text = ncl_list_at(args, 1); puts(ncl_string(text)); putchar('\n'); return ncl_result(ncl, FNORMAL, text); } #endif static int ncl_user_proc(struct ncl *ncl, ncl_value_t *args, void *arg) { ncl_value_t *code = (ncl_value_t *)arg; ncl_value_t *params = ncl_list_at(code, 2); ncl_value_t *body = ncl_list_at(code, 3); ncl->env = ncl_env_alloc(ncl->env); for (int i = 0; i < ncl_list_length(params); i++) { ncl_value_t *param = ncl_list_at(params, i); ncl_value_t *v = ncl_list_at(args, i + 1); ncl_var(ncl, param, v); ncl_free(param); } ncl_eval(ncl, ncl_string(body), ncl_length(body) + 1); ncl->env = ncl_env_free(ncl->env); ncl_free(params); ncl_free(body); return FNORMAL; } static int ncl_cmd_proc(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; ncl_value_t *name = ncl_list_at(args, 1); ncl_register(ncl, ncl_string(name), ncl_user_proc, 0, ncl_dup(args)); ncl_free(name); return ncl_result(ncl, FNORMAL, ncl_alloc("", 0)); } static int ncl_cmd_if(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; int i = 1; int n = ncl_list_length(args); int r = FNORMAL; while (i < n) { ncl_value_t *cond = ncl_list_at(args, i); ncl_value_t *branch = NULL; if (i + 1 < n) { branch = ncl_list_at(args, i + 1); } r = ncl_eval(ncl, ncl_string(cond), ncl_length(cond) + 1); ncl_free(cond); if (r != FNORMAL) { ncl_free(branch); break; } if (ncl_int(ncl->result)) { r = ncl_eval(ncl, ncl_string(branch), ncl_length(branch) + 1); ncl_free(branch); break; } i = i + 2; ncl_free(branch); } return r; } static int ncl_cmd_flow(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; int r = FERROR; ncl_value_t *flowval = ncl_list_at(args, 0); const char *flow = ncl_string(flowval); if (strcmp(flow, "break") == 0) { r = FBREAK; } else if (strcmp(flow, "continue") == 0) { r = FAGAIN; } else if (strcmp(flow, "return") == 0) { r = ncl_result(ncl, FRETURN, ncl_list_at(args, 1)); } ncl_free(flowval); return r; } static int ncl_cmd_while(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; ncl_value_t *cond = ncl_list_at(args, 1); ncl_value_t *loop = ncl_list_at(args, 2); int r; for (;;) { r = ncl_eval(ncl, ncl_string(cond), ncl_length(cond) + 1); if (r != FNORMAL) { ncl_free(cond); ncl_free(loop); return r; } if (!ncl_int(ncl->result)) { ncl_free(cond); ncl_free(loop); return FNORMAL; } int r = ncl_eval(ncl, ncl_string(loop), ncl_length(loop) + 1); switch (r) { case FBREAK: ncl_free(cond); ncl_free(loop); return FNORMAL; case FRETURN: ncl_free(cond); ncl_free(loop); return FRETURN; case FAGAIN: continue; case FERROR: ncl_free(cond); ncl_free(loop); return FERROR; } } } #ifndef TCL_DISABLE_MATH static int ncl_cmd_math(struct ncl *ncl, ncl_value_t *args, void *arg) { (void)arg; char buf[64]; ncl_value_t *opval = ncl_list_at(args, 0); ncl_value_t *aval = ncl_list_at(args, 1); ncl_value_t *bval = ncl_list_at(args, 2); const char *op = ncl_string(opval); int a = ncl_int(aval); int b = ncl_int(bval); int c = 0; if (op[0] == '+') { c = a + b; } else if (op[0] == '-') { c = a - b; } else if (op[0] == '*') { c = a * b; } else if (op[0] == '/') { c = a / b; } else if (op[0] == '>' && op[1] == '\0') { c = a > b; } else if (op[0] == '>' && op[1] == '=') { c = a >= b; } else if (op[0] == '<' && op[1] == '\0') { c = a < b; } else if (op[0] == '<' && op[1] == '=') { c = a <= b; } else if (op[0] == '=' && op[1] == '=') { c = a == b; } else if (op[0] == '!' && op[1] == '=') { c = a != b; } char *p = buf + sizeof(buf) - 1; char neg = (c < 0); *p-- = 0; if (neg) { c = -c; } do { *p-- = '0' + (c % 10); c = c / 10; } while (c > 0); if (neg) { *p-- = '-'; } p++; ncl_free(opval); ncl_free(aval); ncl_free(bval); return ncl_result(ncl, FNORMAL, ncl_alloc(p, strlen(p))); } #endif void ncl_init(struct ncl *ncl) { ncl->env = ncl_env_alloc(NULL); ncl->result = ncl_alloc("", 0); ncl->cmds = NULL; ncl_register(ncl, "set", ncl_cmd_set, 0, NULL); ncl_register(ncl, "subst", ncl_cmd_subst, 2, NULL); #ifndef TCL_DISABLE_PUTS ncl_register(ncl, "puts", ncl_cmd_puts, 2, NULL); #endif ncl_register(ncl, "proc", ncl_cmd_proc, 4, NULL); ncl_register(ncl, "if", ncl_cmd_if, 0, NULL); ncl_register(ncl, "while", ncl_cmd_while, 3, NULL); ncl_register(ncl, "return", ncl_cmd_flow, 0, NULL); ncl_register(ncl, "break", ncl_cmd_flow, 1, NULL); ncl_register(ncl, "continue", ncl_cmd_flow, 1, NULL); #ifndef TCL_DISABLE_MATH char *math[] = {"+", "-", "*", "/", ">", ">=", "<", "<=", "==", "!="}; for (unsigned int i = 0; i < (sizeof(math) / sizeof(math[0])); i++) { ncl_register(ncl, math[i], ncl_cmd_math, 3, NULL); } #endif } void ncl_destroy(struct ncl *ncl) { while (ncl->env) { ncl->env = ncl_env_free(ncl->env); } while (ncl->cmds) { struct ncl_cmd *cmd = ncl->cmds; ncl->cmds = ncl->cmds->next; ncl_free(cmd->name); free(cmd->arg); free(cmd); } ncl_free(ncl->result); } #ifndef TEST #define CHUNK 1024 int main() { struct ncl ncl; int buflen = CHUNK; char *buf = malloc(buflen); int i = 0; ncl_init(&ncl); while (1) { int inp = fgetc(stdin); if (i > buflen - 1) { buf = realloc(buf, buflen += CHUNK); } if (inp == 0 || inp == EOF) { break; } buf[i++] = inp; ncl_each(buf, i, 1) { if (p.token == TERROR && (p.to - buf) != i) { memset(buf, 0, buflen); i = 0; break; } else if (p.token == TCMD && *(p.from) != '\0') { int r = ncl_eval(&ncl, buf, strlen(buf)); if (r != FERROR) { printf("result> %.*s\n", ncl_length(ncl.result), ncl_string(ncl.result)); } else { printf("?!\n"); } memset(buf, 0, buflen); i = 0; break; } } } free(buf); if (i) { printf("incomplete input\n"); return -1; } return 0; } #endif