Browse Source

WIP peer node.

--unset 2 years ago
parent
commit
bb010b499b
4 changed files with 817 additions and 10 deletions
  1. 14 0
      cmd/bdjncl/main.go
  2. 6 0
      go.mod
  3. 728 0
      ncl/ncl.go
  4. 69 10
      peer/peer.go

+ 14 - 0
cmd/bdjncl/main.go

@@ -0,0 +1,14 @@
+package main
+
+import "time"
+import "log"
+import "src.eruta.nl/beoran/bdjncl/peer"
+
+func main() {
+	peer := peer.LocalPeer{}
+	peer.Start()
+	for {
+		log.Printf("bdjncl ok.")
+		time.Sleep(60 * time.Second)
+	}
+}

+ 6 - 0
go.mod

@@ -1,3 +1,9 @@
 module src.eruta.nl/beoran/bdjncl
 
 go 1.16
+
+require (
+	github.com/BurntSushi/toml v1.0.0 // indirect
+	github.com/alecthomas/hcl v0.2.0 // indirect
+	github.com/go-yaml/yaml v2.1.0+incompatible // indirect
+)

+ 728 - 0
ncl/ncl.go

@@ -0,0 +1,728 @@
+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

+ 69 - 10
peer/peer.go

@@ -6,11 +6,13 @@ import "net"
 import "net/url"
 import "src.eruta.nl/beoran/bdjncl/b1"
 import "src.eruta.nl/beoran/bdjncl/picol"
+import "src.eruta.nl/beoran/bdjncl/config"
 import "log"
 import "fmt"
 import "context"
 import "strconv"
 import "math/rand"
+import "time"
 
 const (
 	StateDisconnected = iota
@@ -21,30 +23,87 @@ const (
 )
 
 type Peer struct {
-	Config map[string]string
+	Conf map[string]string
 	context.Context
 	Out chan b1.Message
 	In  chan b1.Message
 	On  *url.URL
+	Cmd *url.URL
 	Id  int64
 	As  *url.URL
 }
 
 type LocalPeer struct {
+	config.Config
 	Peer
 	*picol.Interp
-	Server net.Listener
-	Client net.Conn
-	Peers  []Peer
+	Server  net.Listener
+	Command net.Listener
+	Client  net.Conn
+	Peers   []Peer
+}
+
+func (l LocalPeer) Start() error {
+	var err error
+	l.In = make(chan b1.Message)
+	l.Out = make(chan b1.Message)
+	l.Server, err = net.Listen("tcp", "0.0.0.0:8390")
+	if err != nil {
+		return err
+	}
+	go l.Serve()
+	l.Command, err = net.Listen("unix", "/tmp/bdjncl")
+	if err != nil {
+		return err
+	}
+	go l.Commander()
+	go l.Connect()
+	return nil
+}
+
+func (l LocalPeer) Connect() {
+	for {
+		time.Sleep(1 * time.Second)
+		conn, err := net.Dial("tcp", "localhost:8390")
+		if err != nil {
+			log.Printf("Could not accept connection: %s", err)
+		}
+		l.OnConnected(conn)
+	}
+}
+
+func (l LocalPeer) Serve() {
+	for {
+		conn, err := l.Server.Accept()
+		if err != nil {
+			log.Printf("Could not accept connection: %s", err)
+			time.Sleep(1 * time.Second)
+		} else {
+			go l.OnConnected(conn)
+		}
+	}
+}
+
+func (l LocalPeer) OnConnected(conn net.Conn) {
+	for {
+		log.Printf("Connected: %s", conn.RemoteAddr())
+		time.Sleep(1 * time.Second)
+	}
+}
+
+func (l LocalPeer) Commander() {
+	for {
+		time.Sleep(1 * time.Second)
+	}
 }
 
 func (p *Peer) PicolConfig(i *picol.Interp, argv []string, privdata interface{}) (string, error) {
 	if len(argv) > 2 {
-		p.Config[argv[1]] = argv[2]
+		p.Conf[argv[1]] = argv[2]
 	}
 
 	if len(argv) > 1 {
-		return p.Config[argv[1]], nil
+		return p.Conf[argv[1]], nil
 	}
 	return "", picol.ArityErr(i, argv[0], argv)
 }
@@ -77,7 +136,7 @@ func (p *LocalPeer) PrepareInterp() {
 }
 
 func (p *LocalPeer) ReadConfigName(fileName string) error {
-	p.Config = map[string]string{}
+	p.Conf = map[string]string{}
 
 	buf, err := ioutil.ReadFile(fileName)
 	if err != nil {
@@ -93,7 +152,7 @@ func (p *LocalPeer) ReadConfigName(fileName string) error {
 
 func (p *Peer) UpdateFromConfig() error {
 	var err error
-	if val, ok := p.Config["id"]; ok {
+	if val, ok := p.Conf["id"]; ok {
 		p.Id, err = strconv.ParseInt(val, 0, 64)
 		if err != nil {
 			return err
@@ -101,13 +160,13 @@ func (p *Peer) UpdateFromConfig() error {
 	} else {
 		p.Id = rand.Int63()
 	}
-	if val, ok := p.Config["on"]; ok {
+	if val, ok := p.Conf["on"]; ok {
 		p.On, err = url.Parse(val)
 		if err != nil {
 			return err
 		}
 	}
-	if val, ok := p.Config["as"]; ok {
+	if val, ok := p.Conf["as"]; ok {
 		p.As, err = url.Parse(val)
 		if err != nil {
 			return err