shithub: lpa

Download patch

ref: 7d2f1a2d83df21f0520180537eba7f930bbdb7ee
author: Peter Mikkelsen <peter@pmikkelsen.com>
date: Sat Jul 13 17:32:01 EDT 2024

Initial commit

--- /dev/null
+++ b/aplan.c
@@ -1,0 +1,32 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+Array *
+parseaplan(TokenList *tokens, char **errp)
+{
+	/* TODO: write a recursive descent parser for APLAN here. */
+	Array *val;
+
+	int ok = 1;
+	for(uvlong i = 0; i < tokens->count; i++)
+		ok &= tokens->tokens[i].tag == TokNumber;
+	if(!ok){
+		*errp = "can only parse simple constants";
+		return nil;
+	}
+
+	if(tokens->count == 1){
+		val = allocarray(TypeNumber, 0, 1);
+		setint(val, 0, tokens->tokens[0].num);
+	}else{
+		val = allocarray(TypeNumber, 1, tokens->count);
+		setshape(val, 0, tokens->count);
+		for(uvlong i = 0; i < tokens->count; i++)
+			setint(val, i, tokens->tokens[i].num);
+	}
+	return val;
+}
\ No newline at end of file
--- /dev/null
+++ b/array.c
@@ -1,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+/* This file is the only file that knows how arrays are stored.
+ * In theory, that allows us to experiment with other representations later.
+ */
+
+struct Array
+{
+	int rank;
+	usize *shape;
+	union {
+		void *data;
+		vlong *intdata;
+		Rune *chardata;
+	};
+};
+
+void 
+initarrays(void)
+{
+	dataspecs[DataArray].size = sizeof(Array);
+}
+
+Array *
+allocarray(int type, int rank, usize size)
+{
+	Array *a = alloc(DataArray);
+	a->rank = rank;
+
+	switch(type){
+	case TypeNumber:
+		size *= sizeof(vlong);
+		break;
+	case TypeChar:
+		size *= sizeof(Rune);
+		break;
+	}
+
+	a->shape = allocextra(a, (sizeof(usize) * rank) + size);
+	a->data = (void*)(a->shape+rank);
+
+	return a;
+}
+
+void
+setint(Array *a, usize offset, vlong v)
+{
+	a->intdata[offset] = v;
+}
+
+void
+setshape(Array *a, int dim, usize size)
+{
+	a->shape[dim] = size;
+}
\ No newline at end of file
--- /dev/null
+++ b/dat.h
@@ -1,0 +1,141 @@
+enum DataTag
+{
+	DataAux,
+	DataSession,
+	DataSessionList,
+	DataModule,
+	DataModuleList,
+	DataSymtab,
+	DataSymbol,
+	DataEnumeration,
+	DataTokenList,
+	DataArray,
+	DataAst,
+
+	DataMax,
+};
+
+typedef struct DataSpec DataSpec;
+struct DataSpec
+{
+	usize size;
+};
+extern DataSpec dataspecs[DataMax]; /* memory.c */
+
+typedef struct Symbol Symbol;
+typedef struct Symtab Symtab;
+
+struct Symbol
+{
+	char *name;
+	void *value;
+	Qid qsymbol;
+
+	Symtab *table;
+	uvlong id;
+};
+
+struct Symtab
+{
+	RWLock lock;
+
+	uvlong count;
+	Symbol **symbols;
+};
+
+typedef struct Module Module;
+struct Module
+{
+	uvlong id;
+	char *name;
+
+	Symtab *symtab;
+	Qid qsession;
+	Qid qmodule;
+};
+
+typedef struct ModuleList ModuleList;
+struct ModuleList
+{
+	RWLock lock;
+	uvlong count;
+	Module **modules;
+};
+
+typedef struct Session Session;	
+struct Session
+{
+	uvlong id;
+	char *name;
+
+	int active; /* is the session alive? */
+
+	ModuleList *modules;
+
+	/* file server stuff */
+	Qid qsession;
+	Qid qctl;
+	Qid qcons;
+	Qid qlog;
+	Qid qmodules;
+	Qid qthreads;
+
+	QLock loglock;
+	Rendez logwait;
+
+	uvlong logsize;
+	char *log;
+
+	Channel *input;
+};
+
+typedef struct Enumeration Enumeration;
+struct Enumeration
+{
+	uvlong count;
+	void **items;
+};
+
+enum TokenTag
+{
+	TokNumber,
+	TokName,
+	TokLparen,
+	TokRparen,
+	TokLbrack,
+	TokRbrack,
+	TokNewline,
+	TokDiamond,
+};
+
+typedef struct Token Token;
+struct Token
+{
+	int tag;
+	union {
+		vlong num; /* TokNumber */
+		char *name; /* TokName: UTF-8 encoded name */
+	};
+};
+
+typedef struct TokenList TokenList;
+struct TokenList
+{
+	uvlong count;
+	Token *tokens;
+};
+
+enum ArrayType
+{
+	TypeNumber,
+	TypeChar,
+};
+
+typedef struct Array Array;
+#pragma incomplete Array
+
+typedef struct Ast Ast;
+struct Ast
+{
+	int lol;
+};
\ No newline at end of file
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,55 @@
+/* aplan.c */
+Array *parseaplan(TokenList *, char **);
+
+/* array.c */
+void initarrays(void);
+Array *allocarray(int, int, usize);
+void setint(Array *, usize, vlong);
+void setshape(Array *, int, usize);
+
+/* fs.c */
+Qid freshobjqid(void);
+void startfs(char *, char *);
+
+/* memory.c */
+void *alloc(int);
+void setroot(void *, int);
+void *allocextra(void *, usize);
+
+/* module.c */
+Module *addmodule(Session *, char *);
+Enumeration *enummodules(Session *s);
+
+/* parse.c */
+Ast *parse(TokenList *, char **);
+
+/* scan.c */
+TokenList *scan(char *, char **);
+
+/* session.c */
+void initsessions(void);
+Session *allocsession(void);
+Enumeration *enumsessions(void);
+void appendlog(Session *s, char *data);
+
+/* symtab.c */
+Symtab *allocsymtab(int);
+uvlong sym(Symtab *, char *);
+char *symname(Symtab *, uvlong);
+void *symval(Symtab *, uvlong);
+Qid symqid(Symtab *, uvlong);
+void symset(Symtab *, uvlong, void *);
+Enumeration *enumsymbols(Symtab *);
+
+/* systemcmd.c */
+void systemcmd(Session *, char *, int);
+
+/* util.c */
+Enumeration *allocenum(uvlong);
+void trim(char *);
+
+/* value.c */
+char *printval(void *);
+void *parseval(char *, char **);
+
+void *init_quadio(void);
--- /dev/null
+++ b/fs.c
@@ -1,0 +1,620 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "dat.h"
+#include "fns.h"
+
+#define Eexist "file does not exist"
+#define Enodir "not a directory"
+#define Enotyet "not yet implemented"
+#define Eoffset "invalid offset"
+#define Ewrite "write prohibited"
+#define Ecreate "create prohibited"
+#define Eremove "remove prohibited"
+#define Einvalidname "invalid LPA name"
+
+Qid qroot;
+Qid qnew;
+char *username;
+
+enum {
+	Qroot,
+		Qnew,
+		Qsession,
+			Qctl,
+			Qcons,
+			Qlog,
+			Qmodules,
+				Qmodule,
+			Qthreads,
+	Qlpaobj
+};
+
+enum {
+	Fctl,
+	Fcons,
+	Flog,
+	Fmodules,
+	Fthreads,
+};
+
+enum {
+	Fnew,
+	Fsession,
+};
+
+typedef struct Aux Aux;
+struct Aux
+{
+	Session *session;
+	Module *module;
+	Symbol *symbol;
+	char *cachestr;
+};
+
+static Aux *
+allocaux(void)
+{
+	Aux *aux = alloc(DataAux);
+	setroot(aux, 1);
+	return aux;
+}
+
+#define QID_TYPE(qid) ((qid.path) & 0xFF)
+#define QID_PATH(qid) (qid.path >> 8)
+
+static Qid
+mkqid(int type, uvlong id)
+{
+	Qid qid;
+	qid.vers = 0;
+	qid.path = (type & 0xFF) | (id << 8);
+	switch(type){
+	case Qroot:
+	case Qsession:
+	case Qmodules:
+	case Qmodule:
+	case Qthreads:
+		qid.type = QTDIR;
+		break;
+	case Qnew:
+	case Qctl:
+	case Qcons:
+	case Qlog:
+	case Qlpaobj:
+		qid.type = QTFILE;
+		break;
+	}
+
+	if(type == Qcons)
+		qid.type |= QTAPPEND;
+	
+	return qid;
+}
+
+Qid
+freshobjqid(void)
+{
+	static int id = 0;
+	Qid qid = mkqid(Qlpaobj, id);
+	id++;
+	return qid;
+}
+
+static void
+mkfilestat(Dir *d, char *name, Qid qid, ulong mode)
+{
+	d->uid = estrdup9p(username);
+	d->gid = estrdup9p(username);
+	d->muid = estrdup9p(username);
+	d->mode = mode;
+	d->name = estrdup9p(name);
+	d->qid = qid;
+}
+
+static void
+mkdirstat(Dir *d, char *name, Qid qid)
+{
+	d->uid = estrdup9p(username);
+	d->gid = estrdup9p(username);
+	d->muid = estrdup9p(username);
+	d->mode = 0555|DMDIR;
+	d->name = estrdup9p(name);
+	d->qid = qid;
+}
+
+static int
+roottreegen(int n, Dir *d, void *aux)
+{
+	Enumeration *sessions = aux;
+	int done = 0;
+
+	if(n == Fnew) /* new */
+		mkfilestat(d, "new", qnew, 0444);
+	else{
+		n -= Fsession;
+		if(n < sessions->count){
+			Session *s = sessions->items[n];
+			mkdirstat(d, s->name, s->qsession);
+		}else
+			done = 1;
+	}
+
+	return done ? -1 : 0;
+}
+
+static int
+sessiongen(int n, Dir *d, void *aux)
+{
+	Session *s = aux;
+	int done = 0;
+
+	switch(n){
+	case Fctl:
+		mkfilestat(d, "ctl", s->qctl, 0666);
+		break;
+	case Fcons:
+		mkfilestat(d, "cons", s->qctl, DMAPPEND|0555);
+		d->length = s->logsize;
+		break;
+	case Flog:
+		mkfilestat(d, "log", s->qlog, 0444);
+		d->length = s->logsize;
+		break;
+	case Fmodules:
+		mkdirstat(d, "modules", s->qmodules);
+		break;
+/*	case Fthreads:
+		mkdirstat(d, "threads", s->qthreads);
+		break;
+*/
+	default:
+		done = 1;
+	}
+
+	return done ? -1 : 0;
+}
+
+static int
+modulesgen(int n, Dir *d, void *aux)
+{
+	Enumeration *modules = aux;
+	if(n == modules->count)
+		return -1;
+
+	Module *m = modules->items[n];
+	mkdirstat(d, m->name, m->qmodule);
+	return 0;
+}
+
+static int
+symbolsgen(int n, Dir *d, void *aux)
+{
+	Enumeration *symbols = aux;
+	if(n == symbols->count)
+		return -1;
+
+	Symbol *s = symbols->items[n];
+	mkfilestat(d, s->name, s->qsymbol, 0666);
+	return 0;
+}
+
+static char *
+requeststr(Req *r)
+{
+	char *buf;
+	r->ofcall.count = r->ifcall.count;
+	buf = emalloc9p(r->ifcall.count+1);
+	memcpy(buf, r->ifcall.data, r->ifcall.count);
+	buf[r->ifcall.count] = 0; /* make sure it is 0 terminated */
+	return buf;
+}
+
+static void
+sessioncons(Req *r)
+{
+	Aux *aux = r->fid->aux;
+	Session *s = aux->session;
+
+	srvrelease(r->srv);
+	if(r->ifcall.type == Tread){
+		qlock(&s->loglock);
+		if(r->ifcall.offset >= s->logsize)
+			rsleep(&s->logwait);
+		readbuf(r, s->log, s->logsize);
+		qunlock(&s->loglock);
+	}else{ /* Twrite */
+		char *buf = requeststr(r);
+		send(s->input, &buf);
+	}
+	srvacquire(r->srv);
+}
+
+static void
+sessionlog(Req *r)
+{
+	Aux *aux = r->fid->aux;
+	Session *s = aux->session;
+
+	srvrelease(r->srv);
+	qlock(&s->loglock);
+	readbuf(r, s->log, s->logsize);
+	qunlock(&s->loglock);
+	srvacquire(r->srv);
+}
+
+static void
+sessionctl(Req *r)
+{
+	Aux *aux = r->fid->aux;
+	Session *s = aux->session;
+	char *buf = requeststr(r);
+
+	srvrelease(r->srv);
+	systemcmd(s, buf, 1);
+	free(buf);
+	srvacquire(r->srv);
+}
+
+static char *
+symbolrw(Req *r)
+{
+	Aux *aux = r->fid->aux;
+	Symbol *s = aux->symbol;
+	char *err = nil;
+
+	if(r->ifcall.type == Tread){
+		/* Pretty print the value and readstr() it. */
+		if(aux->cachestr == nil)
+			aux->cachestr = printval(s->value);
+		readstr(r, aux->cachestr);
+		if(r->ofcall.count == 0){
+			free(aux->cachestr);
+			aux->cachestr = nil;
+		}
+	}else{ /* Twrite */
+		char *buf = requeststr(r);
+		void *v = parseval(buf, &err);
+		free(buf);
+		if(!err)
+			symset(s->table, s->id, v);
+	}
+	return err;
+}
+
+static void
+fsattach(Req *r)
+{
+	r->fid->qid = qroot;
+	r->ofcall.qid = r->fid->qid;
+
+	r->fid->aux = allocaux();
+
+	respond(r, nil);
+}
+
+static char *
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+	char *err = nil;
+	Enumeration *e = nil;
+	Aux *aux = fid->aux;
+	Session *s = aux->session;
+	Module *m = aux->module;
+
+	switch(QID_TYPE(fid->qid)){
+	case Qroot:
+		if(strcmp(name, "..") == 0)
+			*qid = fid->qid;
+		else if(strcmp(name, "new") == 0)
+			*qid = qnew;
+		else{
+			int found = 0;
+			e = enumsessions();
+			for(uvlong i = 0; i < e->count; i++){
+				Session *s = e->items[i];
+				if(strcmp(name, s->name) == 0){
+					*qid = s->qsession;
+					aux->session = s;
+					found = 1;
+					break;
+				}
+			}
+			if(!found)
+				err = Eexist;
+		}
+		break;
+	case Qsession:
+		if(strcmp(name, "..") == 0)
+			*qid = qroot;
+		else if(strcmp(name, "ctl") == 0)
+			*qid = s->qctl;
+		else if(strcmp(name, "cons") == 0)
+			*qid = s->qcons;
+		else if(strcmp(name, "log") == 0)
+			*qid = s->qlog;
+		else if(strcmp(name, "modules") == 0)
+			*qid = s->qmodules;
+/*		else if(strcmp(name, "threads") == 0)
+			*qid = s->qthreads;
+*/
+		else
+			err = Eexist;
+		break;
+	case Qmodules:
+		if(strcmp(name, "..") == 0)
+			*qid = s->qsession;
+		else{
+			int found = 0;
+			e = enummodules(s);
+			for(uvlong i = 0; i < e->count && !found; i++){
+				Module *m = e->items[i];
+				if(strcmp(name, m->name) == 0){
+					*qid = m->qmodule;
+					aux->module = m;
+					found = 1;
+				}
+			}
+			if(!found)
+				err = Eexist;
+		}
+		break;
+	case Qmodule:
+		if(strcmp(name, "..") == 0)
+			*qid = m->qsession;
+		else{
+			int found = 0;
+			e = enumsymbols(m->symtab);
+			for(uvlong i = 0; i < e->count && !found; i++){
+				Symbol *symb = e->items[i];
+				if(strcmp(name, symb->name) == 0){
+					*qid = symb->qsymbol;
+					aux->symbol = symb;
+					found = 1;
+				}
+			}
+			if(!found)
+				err = Eexist;
+		}
+		break;
+	case Qthreads:
+		if(strcmp(name, "..") == 0)
+			*qid = s->qsession;
+		else
+			err = Enotyet;
+		break;
+	default:
+		err = Enodir;
+		break;
+	}
+	if(e != nil)
+		setroot(e, 0);
+
+	return err;
+}
+
+static char *
+fsclone(Fid *old, Fid *new)
+{
+	new->aux = allocaux();
+
+	Aux *oldaux = old->aux;
+	Aux *newaux = new->aux;
+	memcpy(newaux, oldaux, sizeof(Aux));
+
+	return nil;
+}
+
+static void
+fsopen(Req *r)
+{
+	/* TODO check permissions */
+	char *err = nil;
+	if(QID_TYPE(r->fid->qid) == Qnew){
+		/* Create a new session */
+		Session *s = allocsession();
+		Module *m = addmodule(s, "main");
+
+		qnew.vers = s->id;
+		r->fid->qid = qnew;
+		r->ofcall.qid = qnew;
+
+		s->qsession = mkqid(Qsession, s->id);
+		s->qctl = mkqid(Qctl, s->id);
+		s->qcons = mkqid(Qcons, s->id);
+		s->qlog = mkqid(Qlog, s->id);
+		s->qmodules = mkqid(Qmodules, s->id);
+		s->qthreads = mkqid(Qthreads, s->id);
+
+		m->qsession = s->qsession;
+		m->qmodule = mkqid(Qmodule, m->id);
+	}
+
+	respond(r, err);
+}
+
+static void
+fsstat(Req *r)
+{
+	Aux *aux = r->fid->aux;
+	Session *s = aux->session;
+	Module *m = aux->module;
+	Symbol *symb = aux->symbol;
+	char *err = nil;
+
+	switch(QID_TYPE(r->fid->qid)){
+	case Qroot:
+		mkdirstat(&r->d, "/", qroot);
+		break;
+	case Qnew:
+		roottreegen(Fnew, &r->d, nil);
+		break;
+	case Qsession:
+		mkdirstat(&r->d, s->name, s->qsession);
+		break;
+	case Qctl:
+		sessiongen(Fctl, &r->d, s);
+		break;
+	case Qcons:
+		sessiongen(Fcons, &r->d, s);
+		break;
+	case Qlog:
+		sessiongen(Flog, &r->d, s);
+		break;
+	case Qmodules:
+		sessiongen(Fmodules, &r->d, s);
+		break;
+	case Qmodule:
+		mkdirstat(&r->d, m->name, m->qmodule);
+		break;
+	case Qthreads:
+		sessiongen(Fthreads, &r->d, s);
+		break;
+	case Qlpaobj:
+		mkfilestat(&r->d, symb->name, symb->qsymbol, 0444);
+		break;
+	default:
+		err = Enotyet;
+	}
+
+	respond(r, err);
+}
+
+static void
+fsread(Req *r)
+{
+	char buf[256];
+	Enumeration *e = nil;
+	Aux *aux = r->fid->aux;
+	char *err = nil;
+
+	switch(QID_TYPE(r->fid->qid)){
+	case Qroot:
+		e = enumsessions();
+		dirread9p(r, roottreegen, e);
+		break;
+	case Qsession:
+		dirread9p(r, sessiongen, aux->session);
+		break;
+	case Qmodules:
+		e = enummodules(aux->session);
+		dirread9p(r, modulesgen, e);
+		break;
+	case Qmodule:
+		e = enumsymbols(aux->module->symtab);
+		dirread9p(r, symbolsgen, e);
+		break;
+	case Qnew:
+		snprint(buf, sizeof(buf), "%uld\n", r->fid->qid.vers);
+		readstr(r, buf);
+		break;
+	case Qcons:
+		sessioncons(r);
+		break;
+	case Qlog:
+		sessionlog(r);
+		break;
+	case Qlpaobj:
+		err = symbolrw(r);
+		break;
+	default:
+		err = Enotyet;
+		break;
+	}
+	if(e != nil)
+		setroot(e, 0);
+	respond(r, err);
+}
+
+static void
+fswrite(Req *r)
+{
+	char *err = nil;
+
+	switch(QID_TYPE(r->fid->qid)){
+	case Qctl:
+		sessionctl(r);
+		break;
+	case Qcons:
+		sessioncons(r);
+		break;
+	case Qlpaobj:
+		err = symbolrw(r);
+		break;
+	default:
+		err = Ewrite;
+	}
+	respond(r, err);
+}
+
+static void
+fscreate(Req *r)
+{
+	char *err = nil;
+	Aux *aux = r->fid->aux;
+	Module *m = aux->module;
+	uvlong symb;
+
+	switch(QID_TYPE(r->fid->qid)){
+	case Qmodule: /* create a new symbol */
+		symb = sym(m->symtab, r->ifcall.name);
+		if(symb == -1)
+			err = Einvalidname;
+		r->fid->qid = r->ofcall.qid = symqid(m->symtab, symb);
+		break;
+	default:
+		err = Ecreate;
+	}
+	respond(r, err);
+}
+
+static void
+fsremove(Req *r)
+{
+	char *err;
+
+	switch(QID_TYPE(r->fid->qid)){
+	case Qlpaobj:
+		err = Enotyet;
+		break;
+	default:
+		err = Eremove;
+	}
+	respond(r, err);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+	if(fid->aux)
+		setroot(fid->aux, 0);
+}
+
+static Srv fs = {
+	.attach = fsattach,
+	.walk1 = fswalk1,
+	.clone = fsclone,
+	.open = fsopen,
+	.stat = fsstat,
+	.read = fsread,
+	.write = fswrite,
+	.create = fscreate,
+	.remove = fsremove,
+
+	.destroyfid = fsdestroyfid,
+};
+
+void
+startfs(char *name, char *mtpt)
+{
+	dataspecs[DataAux].size = sizeof(Aux);
+
+	username = getuser();
+	qroot = mkqid(Qroot, 0);
+	qnew = mkqid(Qnew, 0);
+
+	threadpostmountsrv(&fs, name, mtpt, MREPL);
+}
--- /dev/null
+++ b/lpa
@@ -1,0 +1,82 @@
+#!/bin/rc
+
+rfork ens
+
+id=0
+readonly=0
+printlist=0
+
+fn usage{
+	echo 'usage: lpa [-n session | -r session | -l]'
+	exit 'usage'
+}
+
+fn nosession{
+	echo 'session '^$id^' does not exist (or lpafs is not running)'
+	exit 'no such session'
+}
+
+while(~ $1 -*){
+	switch($1){
+	case -n
+		if(! ~ $id 0)
+			usage
+		id=$2
+		shift
+	case -r
+		if(! ~ $id 0)
+			usage
+		readonly=1
+		id=$2
+		shift
+	case -l
+		if(! ~ $id 0)
+			usage
+		printlist=1
+	case -*
+		usage
+	}
+	shift
+}
+
+if(! ~ $#* 0)
+	usage
+
+# Start LPA if it isn't already running
+if(! test -f /srv/lpa){
+	if(! ~ $id 0)
+		nosession
+	lpafs
+}
+if not
+	mount /srv/lpa /mnt/lpa
+
+if(~ $printlist 1){
+	echo `{cd /mnt/lpa; ls | grep -v '^new$'}
+	exit
+}
+
+if(~ $id 0)
+	id=`{cat /mnt/lpa/new}
+if not{
+	if(! test -d /mnt/lpa/$id/)
+		nosession
+}
+
+cd /mnt/lpa/$id
+label LPA session $id
+
+if(~ $readonly 1)
+	cat cons
+if not{
+	cat cons &
+	while(line=`''{read}){
+		n=`{tail -1l /dev/text | sed 's/^[ ]*//' | wc -r} # number of runes to delete
+		awk 'END {
+			for(i = 0; i < '^$n^'; i++)
+				printf("\b");
+		}' /dev/text
+		echo -n $line > cons
+	}
+}
+
--- /dev/null
+++ b/lpa.ms
@@ -1,0 +1,176 @@
+.FP lucidasans
+.TL
+LPA: A new APL system for Plan 9 (WORK IN PROGRESS)
+.AU
+Peter Mikkelsen
+.AB
+LPA is an implementation of APL for Plan 9, which aims to be a playground for experimenting with many new ideas, such as a file system for debugging, and constraint based programming.
+It implements most of the APL language as defined in the Extended APL standard, but with a fair amount of extensions as well.
+LPA draws inspiration from other programming languages such as Prolog and Erlang.
+
+.FS
+The name LPA was choosen due to an early idea, which was to provide some form of
+.B L ogic
+.B P rogramming
+in
+.B A PL.
+Even though logic programming in the Prolog sense is no longer a goal, and has been replaced with constraints, the name stuck.
+It is also APL spelled backwards.
+.FE
+.FS
+.B1
+This document is work in progress, and probably already out of date.
+.B2
+.AE
+
+.NH 1
+Introduction
+
+.NH 1
+Running LPA
+.LP
+LPA uses a file system to present objects (such as functions and arrays), such that it becomes possible to edit them using the standard tools.
+Unlike other APL systems, that means there is no need for an IDE at all.
+The system is started by running
+.P1
+lpafs
+.P2
+A single instance of LPA supports multiple
+.I sessions ,
+represented by different subdirectories in the filesystem.
+An advantage of letting a single instance of LPA handle multiple sessions is that memory can be shared between them, thereby lowering overall memory usage.
+By default, LPA mounts itself under
+.CW /mnt/lpa
+and presents the following structure:
+.P1
+/mnt/lpa
+	clone
+	1/
+		ctl
+		cons
+		modules/
+			main/
+				computeSums
+				years
+				...
+				prices
+				names
+			test/
+				assert
+				log
+				...
+			...
+		threads/
+			...
+	2/
+		ctl
+		...
+	...
+.P2
+The filesystem provides a full view of the running system.
+The structure under
+.CW threads/
+is primarily useful for debugging, and it is described in more detail in section 3.
+.PP
+In the top-level directory, we find a
+.CW clone
+file, and a directory for each active session, numbered automatically.
+New sessions are created by opening
+.CW clone
+and reading an integer, which is the number for the newly created session (and the name of the relevant subdirectory).
+After that, the session can be controlled and deleted by writing commands to the session's
+.CW ctl
+file.
+.PP
+Each session directory has a
+.CW ctl
+which understands messages that control the entire session, such as deletion, creating/importing new modules (see section 4.4), and saving/re-loading the session's state to disk.
+.FS
+The messages written to a session's
+.CW cons
+file are exactly those supported by the system-command syntax in the interpreter, such as
+.CW ")save /tmp/dump" ,
+.CW ")module ..."
+and
+.CW ")off"
+.FE
+The
+.CW cons
+file provides access to the
+.I REPL
+of the session, and can be accessed using:
+.P1
+con -C /mnt/apl/1/cons
+.P2
+or by running the
+.CW lpa
+script which automatically starts
+.CW lpafs
+if it isn't already running, creates a new session, and connects to it.
+The
+.CW lpa
+script accepts some optional arguments, to connect to an existing running session.
+It is only possible to have one active connection to a session's REPL, although there is no limit on the number of readers or writers to the other parts of a session's filesystem structure.
+.PP
+Each of the directories in the
+.CW modules/
+directory represents a module in the session.
+The one named
+.CW main
+is always present, and it is created when a session is created.
+In each of the module's directories, there are a file for each globally named object (array, function, etc.), which can be edited in any way the user wants.
+For example, to edit the function
+.CW computeSums
+in the
+.CW main
+module of session 1, taking advantage of the plumber:
+.P1
+B /mnt/lpa/1/modules/main/computeSums
+.P2
+Creating a new file is also allowed, and it introduces a new globally named object.
+Alternatively, new objects can be created by control messages to the
+.CW ctl
+file, and some control messages cause plumb messages to be sent as well.
+The definition of the object is updated once the file is written, and a write error is produced in case the syntax wasn't valid.
+Reading the file will return the text representation of the object at the time the file was opened.
+Deleting a file causes the corresponding object to be deleted as well.
+Access to non-global objects, such as local variables on the stack of a specific thread, is possible via the
+.CW threads/
+directory described in section 3.
+
+.NH 1,
+Debugging
+
+.NH 1
+Language extensions
+.LP
+
+.NH 2
+Array notation
+
+.NH 2
+Constraints
+
+.NH 2
+Concurrency features
+
+.NH 2
+Modules
+
+.NH 2
+Dictionaries
+
+.NH 1
+Implementation
+
+.NH 2
+Overview
+
+.NH 2
+Parsing and compiling
+
+.NH 2
+The VM instruction set
+
+.NH 2
+The workspace format/memory management
--- /dev/null
+++ b/main.c
@@ -1,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "dat.h"
+#include "fns.h"
+
+void
+usage(void)
+{
+	fprint(2, "usage: lpafs [-D] [-n name] [-m mtpt] \n");
+	exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	char *name = "lpa";
+	char *mtpt = "/mnt/lpa";
+
+	ARGBEGIN{
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 'n':
+		name = EARGF(usage());
+		break;
+	case 'D':
+		chatty9p++;
+		break;
+	default:
+		usage();
+	}ARGEND
+	if(argc != 0)
+		usage();
+
+	initarrays();
+	initsessions();
+
+	startfs(name, mtpt);
+	exits(nil);
+}
\ No newline at end of file
--- /dev/null
+++ b/memory.c
@@ -1,0 +1,86 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#include "dat.h"
+#include "fns.h"
+
+/* This version of the memory manager is very stupid,
+ * but it allows us to get started..
+ */
+typedef struct Allocation Allocation;
+struct Allocation
+{
+	usize size;
+	int tag;
+	int root;
+	void *buf;
+	void *extra;
+};
+
+uvlong nallocs;
+static Allocation **allocations; /* all allocations */
+
+DataSpec dataspecs[DataMax] = {
+	/* DataAux: setup in fs.c */
+	[DataSession] = {.size = sizeof(Session) },
+	/* DataSessionList: setup in session.c */
+	[DataModule] = {.size = sizeof(Module) },
+	[DataModuleList] = {.size = sizeof(ModuleList) },
+	[DataSymtab] = {.size = sizeof(Symtab) },
+	[DataSymbol] = {.size = sizeof(Symbol) },
+	[DataEnumeration] = {.size = sizeof(Enumeration) },
+	[DataTokenList] = {.size = sizeof(TokenList) },
+	[DataAst] = {.size = sizeof(Ast) },
+};
+
+void *
+alloc(int tag)
+{
+	usize size = dataspecs[tag].size;
+	Allocation *a = emalloc9p(sizeof(Allocation) + size);
+	a->size = size;
+	a->tag = tag;
+	a->root = 0;
+	a->buf = ((uchar*)a)+sizeof(Allocation);
+	a->extra = nil;
+	memset(a->buf, 0, size);
+
+	nallocs++;
+	allocations = erealloc9p(allocations, nallocs * sizeof(Allocation*));
+	allocations[nallocs-1] = a;
+
+	return a->buf;
+}
+
+static Allocation *
+allocptr(void *v)
+{
+	uchar *p = v;
+	p -= sizeof(Allocation);
+	return (Allocation*)p;
+}
+
+void
+setroot(void *d, int v)
+{
+	Allocation *a = allocptr(d);
+	a->root = v;
+}
+
+void *
+dataptr(void *d)
+{
+	Allocation *a = allocptr(d);
+	return a->buf;
+}
+
+void *
+allocextra(void *d, usize size)
+{
+	Allocation *a = allocptr(d);
+	a->extra = erealloc9p(a->extra, size);
+	return a->extra;
+}
\ No newline at end of file
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,36 @@
+</$objtype/mkfile
+
+TARG=lpafs
+SCRIPTS=lpa
+OFILES=\
+	aplan.$O\
+	array.$O\
+	fs.$O\
+	main.$O\
+	memory.$O\
+	module.$O\
+	parse.$O\
+	scan.$O\
+	session.$O\
+	symtab.$O\
+	systemcmd.$O\
+	util.$O\
+	value.$O\
+
+HFILES=\
+	dat.h\
+	fns.h\
+
+BIN=/$objtype/bin
+
+CLEANFILES=lpa.ps
+
+default:V: all lpa.ps
+
+install:
+	cp $SCRIPTS /rc/bin/
+
+lpa.ps: lpa.ms
+	cat lpa.ms | troff -ms | lp -dstdout > $target
+
+</sys/src/cmd/mkone
\ No newline at end of file
--- /dev/null
+++ b/module.c
@@ -1,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+Module *
+addmodule(Session *s, char *name)
+{
+	static uvlong id = 1;
+
+	Module *m = alloc(DataModule);
+	m->name = strdup(name);
+	m->symtab = allocsymtab(1);
+	m->id = id++;
+
+	wlock(&s->modules->lock);
+	s->modules->count++;
+	s->modules->modules = allocextra(s->modules, sizeof(Module *) * s->modules->count);
+	s->modules->modules[s->modules->count-1] = m;
+	wunlock(&s->modules->lock);
+
+	return m;
+}
+
+Enumeration *
+enummodules(Session *s)
+{
+	rlock(&s->modules->lock);
+	Enumeration *e = allocenum(s->modules->count);
+	for(uvlong i = 0; i < s->modules->count; i++)
+		e->items[i] = s->modules->modules[i];
+	runlock(&s->modules->lock);
+	return e;
+}
\ No newline at end of file
--- /dev/null
+++ b/parse.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+Ast *
+parse(TokenList *tokens, char **errp)
+{
+	/* Ast *ast = alloc(DataAst); */
+	USED(tokens);
+	*errp = "parsing not implemented yet";
+	return nil;
+}
\ No newline at end of file
--- /dev/null
+++ b/scan.c
@@ -1,0 +1,68 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+Token *
+newtok(TokenList *tokens, int tag)
+{
+	Token *new;
+
+	tokens->count++;
+	tokens->tokens = allocextra(tokens, sizeof(Token) * tokens->count);
+	new = tokens->tokens + (tokens->count-1);
+	new->tag = tag;
+
+	return new;
+}
+
+TokenList *
+scan(char *buf, char **errp)
+{
+	Rune r;
+	int n;
+	TokenList *tokens = alloc(DataTokenList);
+	Token *tok;
+	char *cp = buf;
+
+	while(*cp){
+		n = chartorune(&r, cp);
+		switch(r){
+		case '(':
+			newtok(tokens, TokLparen);
+			goto next;
+		case ')':
+			newtok(tokens, TokRparen);
+			goto next;
+		case '[':
+			newtok(tokens, TokLbrack);
+			goto next;
+		case ']':
+			newtok(tokens, TokRbrack);
+			goto next;
+		case '\n':
+			newtok(tokens, TokNewline);
+			goto next;
+		case L'⋄':
+			newtok(tokens, TokDiamond);
+			goto next;
+		}
+		if(isspacerune(r))
+			goto next;
+		if(isdigitrune(r)){
+			char *rest;
+			vlong num = strtoll(cp, &rest, 10);
+			n = rest - cp;
+			tok = newtok(tokens, TokNumber);
+			tok->num = num;
+			goto next;
+		}
+		*errp = "scan error";
+		return nil;
+next:
+		cp += n;
+	}
+	return tokens;
+}
\ No newline at end of file
--- /dev/null
+++ b/session.c
@@ -1,0 +1,110 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+typedef struct SessionList SessionList;
+struct SessionList
+{
+	uvlong count;
+	Session **sessions;
+};
+
+SessionList *sessions;
+
+void
+appendlog(Session *s, char *data)
+{
+	uvlong size = strlen(data);
+	qlock(&s->loglock);
+	s->logsize += size;
+	s->log = allocextra(s, s->logsize);
+	memcpy(&s->log[s->logsize-size], data, size);
+	s->qlog.vers++;
+	rwakeupall(&s->logwait);
+	qunlock(&s->loglock);
+}
+
+static void
+sessionproc(void *arg)
+{
+	char *prompt = "      "; /* 6-space prompt */
+	char *buf = nil;
+	Session *s = arg;
+	while(1){
+		appendlog(s, prompt);
+
+		free(buf);
+		recv(s->input, &buf);
+		appendlog(s, buf);
+
+		if(strlen(buf) > 0 && buf[0] == ')')
+			systemcmd(s, buf+1, 0);
+		else{
+			char *err = nil;
+			TokenList *tokens = scan(buf, &err);
+			if(err){
+error:
+				appendlog(s, err);
+				appendlog(s, "\n");
+				continue;
+			}
+			
+			Ast *ast = parse(tokens, &err);
+			if(err)
+				goto error;
+
+			USED(ast);
+			appendlog(s, "got an AST but can't evaluate it yet\n");
+		}
+	}
+}
+
+Session *
+allocsession(void)
+{
+	static uvlong id = 1;
+
+	Session *s = alloc(DataSession);
+
+	s->id = id++;
+	s->name = smprint("%ulld", s->id);
+	s->active = 1;
+
+	s->logwait.l = &s->loglock;
+	s->logsize = 0;
+	s->log = nil;
+
+	s->modules = alloc(DataModuleList);
+
+	s->input = chancreate(sizeof(char *), 0);
+
+	sessions->count++;
+	sessions->sessions = allocextra(sessions, sizeof(Session *) * sessions->count);
+	sessions->sessions[sessions->count-1] = s;
+
+	proccreate(sessionproc, s, 1024*1024);
+
+	return s;
+}
+
+Enumeration *
+enumsessions(void)
+{
+	Enumeration *e = allocenum(sessions->count);
+	for(uvlong i = 0; i < sessions->count; i++)
+		e->items[i] = sessions->sessions[i];
+
+	return e;
+}
+
+void
+initsessions(void)
+{
+	dataspecs[DataSessionList].size = sizeof(SessionList);
+
+	sessions = alloc(DataSessionList);
+	setroot(sessions, 1);
+}
\ No newline at end of file
--- /dev/null
+++ b/symtab.c
@@ -1,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+struct {
+	char *name;
+	void *(*init)(void);
+} defaultsyms[] = {
+	{ "⎕IO", init_quadio },
+};
+
+Symtab *
+allocsymtab(int defaults)
+{
+	Symtab *s = alloc(DataSymtab);
+	if(defaults){
+		for(uvlong i = 0; i < nelem(defaultsyms); i++){
+			uvlong symb = sym(s, defaultsyms[i].name);
+			symset(s, symb, defaultsyms[i].init());
+		}
+	}
+
+	return s;
+}
+
+uvlong
+sym(Symtab *s, char *name)
+{
+	uvlong id;
+	int new = 1;
+	rlock(&s->lock);
+	for(id = 0; id < s->count; id++){
+		if(strcmp(name, s->symbols[id]->name) == 0){
+			new = 0;
+			break;
+		}
+	}
+	runlock(&s->lock);
+	if(new){
+		/* check if the name is valid, or return -1 */
+		Symbol *newsym = alloc(DataSymbol);
+		newsym->name = strdup(name);
+		newsym->value = nil;
+		newsym->qsymbol = freshobjqid();
+		newsym->table = s;
+		newsym->id = id;
+
+		wlock(&s->lock);
+		s->count++;
+		s->symbols = allocextra(s, sizeof(Symbol *) * s->count);
+		s->symbols[id] = newsym;
+		wunlock(&s->lock);
+	}
+	return id;
+}
+
+char *
+symname(Symtab *s, uvlong id)
+{
+	char *name;
+	rlock(&s->lock);
+	name = s->symbols[id]->name;
+	runlock(&s->lock);
+	return name;
+}
+
+void *
+symval(Symtab *s, uvlong id)
+{
+	void *value;
+	rlock(&s->lock);
+	value = s->symbols[id]->value;
+	runlock(&s->lock);
+	return value;
+}
+
+Qid
+symqid(Symtab *s, uvlong id)
+{
+	Qid qid;
+	rlock(&s->lock);
+	qid = s->symbols[id]->qsymbol;
+	runlock(&s->lock);
+	return qid;
+}
+
+void
+symset(Symtab *s, uvlong id, void *newval)
+{
+	wlock(&s->lock);
+	s->symbols[id]->value = newval;
+	wunlock(&s->lock);
+}
+
+Enumeration *
+enumsymbols(Symtab *symtab)
+{
+	rlock(&symtab->lock);
+	Enumeration *e = allocenum(symtab->count);
+	for(uvlong i = 0; i < symtab->count; i++)
+		e->items[i] = symtab->symbols[i];
+	runlock(&symtab->lock);
+	return e;
+}
\ No newline at end of file
--- /dev/null
+++ b/systemcmd.c
@@ -1,0 +1,82 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <plumb.h>
+
+#include "dat.h"
+#include "fns.h"
+
+char *syscmd_off(Session *, char *);
+char *syscmd_ed(Session *, char *);
+
+struct {
+	char *name;
+	char *(*fn)(Session *, char *);
+} cmdtab[] = {
+	{ "off", syscmd_off },
+	{ "ed",	syscmd_ed },
+};
+
+void
+systemcmd(Session *s, char *cmd, int ctl)
+{
+	char *out;
+	char *parts[2];
+
+	for(vlong i = strlen(cmd)-1; i >= 0; i--){
+		if(cmd[i] != ' ')
+			break;
+		else
+			cmd[i] = 0;
+	}
+	if(getfields(cmd, parts, 2, 1, " \n") == 1)
+		parts[1] = "";
+
+	char *(*fn)(Session *, char *) = nil;
+	for(int i = 0; i < nelem(cmdtab) && fn == nil; i++)
+		if(strcmp(cmdtab[i].name, parts[0]) == 0)
+			fn = cmdtab[i].fn;
+
+	if(fn != nil)
+		out = fn(s, parts[1]);
+	else
+		out = smprint("invalid system command: %s", parts[0]);
+
+	if(!ctl && out){
+		appendlog(s, out); /* Otherwise do something that makes read from the ctl file get the response... */
+		appendlog(s, "\n");
+	}
+	free(out);
+}
+
+char *
+syscmd_off(Session *s, char *args)
+{
+	if(strcmp(args, "") != 0)
+		return smprint("unexpected: %s\n", args);
+
+	/* TODO force the lpa script's 'cat cons' to stop. */
+	s->active = 0;
+
+	return smprint("bye bye :)");
+}
+
+char *
+syscmd_ed(Session *s, char *name)
+{
+	char *resp = nil;
+	int fd = plumbopen("send", OWRITE);
+	if(fd < 0)
+		return smprint("plumb failed: %r");
+	trim(name);
+
+	/* create the symbol */
+	sym(s->modules->modules[0]->symtab, name); /* TODO: fix this and the line below. Name and module should be parsed.. */
+
+	char *path = smprint("/mnt/lpa/%ulld/modules/main/%s", s->id, name); 
+	if(plumbsendtext(fd, "lpa", "edit", "/", path) < 0)
+		resp = smprint("plumb failed: %r");
+	close(fd);
+	free(path);
+	return resp;
+}
\ No newline at end of file
--- /dev/null
+++ b/util.c
@@ -1,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+Enumeration *
+allocenum(uvlong count)
+{
+	Enumeration *e = alloc(DataEnumeration);
+	setroot(e, 1);
+	e->count = count;
+	e->items = allocextra(e, sizeof(void *) * count);
+	return e;
+}
+
+void
+trim(char *str)
+{
+	for(int i = strlen(str)-1; i > 0; i--){
+		if(str[i] != '\n')
+			break;
+		else
+			str[i] = 0;
+	}
+}
\ No newline at end of file
--- /dev/null
+++ b/value.c
@@ -1,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+
+#include "dat.h"
+#include "fns.h"
+
+/* Anything that can have a name in LPA: Arrays, functions, ... */
+char *
+printval(void *v)
+{
+	if(v)
+		return smprint("some value: %p :)", v);
+	else
+		return smprint("no value :(");
+}
+
+void *
+parseval(char *buf, char **errp)
+{
+	void *val = nil;
+	TokenList *tokens = scan(buf, errp);
+	if(tokens != nil){
+		/* Parse the tokens as a constant. TODO: Support function definitions as well... */
+		val = parseaplan(tokens, errp);
+	}
+	return val;
+}
+
+void *
+init_quadio(void)
+{
+	return nil;
+}
\ No newline at end of file