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