shithub: pico

Download patch

ref: cfbf70e39b53d3b7578c7e17b8b89b7fd24aa3f5
parent: 630acc0e941f3463a99a4ced0f1611023a69c235
author: qwx <qwx@sciops.net>
date: Thu Jan 30 08:02:01 EST 2020

reimplement in awk!

--- a/README
+++ b/README
@@ -1,14 +1,70 @@
-This is a hacked-up version of rsc's version of pico.
+pico: a reimplementation of rsc's implementation of pico
+========================================================
 
-Changes:
-- remove p9p shit and some bitrot
-- use mk to build binary instead of hardcoding CC/LD
-- arbitrary image sizes; set when loading or displaying an image; default
-  248x248
-- work on ABGR32 images; channels are accessed via a third 'z' coordinate
-- add &&, || operators
-- name nameless images as $1, $2,...
-- images are always written once to /tmp prior to any processing
-- fix numerous memory leaks
-- accept integers in octal and hexadecimal, as well as doubles
-- accept digits and utf in names
+A language for composing digital images
+---------------------------------------
+
+This does not really reimplement the pico[1] language, but builds on the same
+idea.
+
+rsc initially implemented a version in C and YACC[2], which parsed a simplified
+language based on pico.
+It essentially parses a simple syntax to output a C file which performed the
+specified image manipulations in an expression embedded in a simple nested loop.
+
+It was an elegant hack, and possibly worked on plan9port as well as on plan9,
+but suffered from a number of bugs and limitations preventing its use in
+practice.
+
+This repository contains in its initial commit an improved version, which
+fixed some of the bugs and added a number of features:
+
+- removal of plan9port code and fixes on some bitrot
+- numerous memory leaks fixed
+- use of mk(1) instead of hardcoding CC and LD
+- arbitrary image sizes, set from loaded images, or 248x248 by default as
+  before
+- ABGR32 images instead of GREY8 images, with channels accessible via a third
+  'z' coordinate
+- some parser changes: addition of &&, || operators, integers in octal and
+  hexadecimal notation, floating point numbers; accept digits and utf8 runes
+  in image names
+- always name all images to allow referencing them
+- some simple optimizations in the execution, such as not writing images
+  multiple times to disk, etc.
+
+However, several problems remained unfixed.
+In particular, due to the way it was specified, the parser allocated
+an exponential number of strings while processing input.
+
+This, and the need of a few other features, prompted a reimplementation,
+this time without YACC, also in the repository, and which was also
+unsatisfactory for a number of reasons.
+
+Several reimplementations later, the current pico "implementation" is merely
+an awk script, in less than half the size of the initial code.
+
+It does everything the previous versions do, and some more:
+
+- no more memory issues
+- excepting image coordinate specifications, the syntax is straight C and
+  almost any valid expression (and operator) is allowed; using libc functions
+  and definitions such as sqrt(2) or PI is also allowed
+- image coordinate specifications needn't always specify all channels
+- images are displayed via plumb(1) and subsequently page(1) instead of a
+  makeshift viewer
+- image names may be any string not containing C operators, whitespace and
+  not beginning with a digit
+- add a command for specifying default image size when no other images are
+  referenced
+- comments via #
+
+Some other features are planned as well.
+Manpage pending.
+
+
+References:
+-----------
+
+[1] http://doc.cat-v.org/unix/v10/10thEdMan/pico.pdf
+[2] Found in plan9 contrib at /n/sources/contrib/rsc/pico/
--- a/dat.h
+++ /dev/null
@@ -1,20 +1,0 @@
-typedef struct Sym Sym;
-
-extern char wdir[], pref[];
-extern char **tok;
-extern int ntok;
-
-struct Sym{
-	int ref;
-	char *iname;
-	char *cname;
-	char *name;
-	char *path;
-};
-extern Sym *sym;
-extern int nsym;
-extern int Δx, Δy;
-
-extern char *prolog, *prepstr, *tailstr;
-
-extern int quiet;
--- a/defs.c
+++ /dev/null
@@ -1,110 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include "dat.h"
-#include "fns.h"
-
-char* prolog = 
-"#include <u.h>\n"
-"#include <libc.h>\n"
-"#include <draw.h>\n"
-"#include <memdraw.h>\n"
-"\n"
-"Memimage*\n"
-"READ(char *file)\n"
-"{\n"
-"	int fd;\n"
-"	Memimage *m, *m1;\n"
-"	if((fd = open(file, OREAD)) < 0)\n"
-"		sysfatal(\"open: %r\");\n"
-"	if((m = readmemimage(fd)) == nil)\n"
-"		sysfatal(\"readmemimage: %r\");\n"
-"	close(fd);\n"
-"	if(m->chan != ABGR32){\n"
-"		m1 = allocmemimage(m->r, ABGR32);\n"
-"		memfillcolor(m1, DBlack);\n"
-"		memimagedraw(m1, m1->r, m, m->r.min, memopaque, ZP, S);\n"
-"		freememimage(m);\n"
-"		m = m1;\n"
-"	}\n"
-"	return m;\n"
-"}\n\n"
-"void\n"
-"WRITE(Memimage *m, char *file)\n"
-"{\n"
-"	int fd;\n"
-"	if((fd = create(file, OWRITE, 0666)) < 0)\n"
-"		sysfatal(\"create: %r\");\n"
-"	if(writememimage(fd, m) < 0)\n"
-"		sysfatal(\"writememimage: %r\");\n"
-"	close(fd);\n"
-"}\n\n"
-"int\n"
-"POW(int a, int b)\n"
-"{\n"
-"	int t;\n"
-"	if(b <= 0) return 1;\n"
-"	if(b == 1) return a;\n"
-"	t = POW(a, b/2);\n"
-"	t *= t;\n"
-"	if(b%2) t *= a;\n"
-"	return t;\n"
-"}\n"
-"\n"
-"int\n"
-"DIV(int a, int b)\n"
-"{\n"
-"	if(b == 0) return 0;\n"
-"	return a/b;\n"
-"}\n"
-"\n"
-"int\n"
-"MOD(int a, int b)\n"
-"{\n"
-"	if(b == 0) return 0;\n"
-"	return a%b;\n"
-"}\n"
-"\n"
-"#define Z 255\n"
-"\n"
-"uchar\n"
-"RIMAGE(Memimage *m, int x, int y, int z)\n"
-"{\n"
-"	if(x < 0 || y < 0 || z < 0 || x >= Dx(m->r) || y >= Dy(m->r) || z > 3) return 0;\n"
-"	return byteaddr(m, addpt(m->r.min, Pt(x,y)))[z];\n"
-"}\n"
-"\n"
-"uchar*\n"
-"WIMAGE(Memimage *m, int x, int y, int z)\n"
-"{\n"
-"	static uchar devnull;\n"
-"	if(x < 0 || y < 0 || z < 0 || x >= Dx(m->r) || y >= Dy(m->r) || z > 3) return &devnull;\n"
-"	return byteaddr(m, addpt(m->r.min, Pt(x,y))) + z;\n"
-"}\n"
-"\n"
-"#define CLIP(x) ((x) < 0 ? 0 : (x) > 255 ? 255 : (x))\n"
-"\n"
-"void main(void) {\n"
-"	int x, y, z, X, Y, T;\n"
-"	Memimage *﹩new;\n"
-"	if(memimageinit() < 0) sysfatal(\"initmemimage: %r\");\n"
-;
-
-char *prepstr =
-"	for(﹩i=﹩im; ﹩i<﹩im+nelem(﹩im); ﹩i++){\n"
-"		if(*﹩i == nil) continue;\n"
-"		if(X < Dx((*﹩i)->r)) X = Dx((*﹩i)->r);\n"
-"		if(Y < Dy((*﹩i)->r)) Y = Dy((*﹩i)->r);\n"
-"	}\n"
-"	if((﹩new = allocmemimage(Rect(0,0,X,Y), ABGR32)) == nil)\n"
-"		sysfatal(\"allocmemimage: %r\");\n"
-"	for(z=0; z<4; z++) for(y=0; y<Y; y++) for(x=0; x<X; x++){\n"
-;
-
-char *tailstr =
-";\n"
-"		*WIMAGE(﹩new, x,y,z) = CLIP(T);\n"
-"	}\n"
-"	WRITE(﹩new, out);\n"
-"	exits(nil);\n"
-"}\n"
-;
--- a/examples
+++ b/examples
@@ -1,12 +1,12 @@
 ; hget http://9front.org/img/9bind.png | png -t9 > /tmp/bind
 ; hget http://9front.org/img/ninefront.bit > /tmp/nine
 
-; pixo <<EOF
+; pico
 # read in images
-r nine /tmp/nine
-r bind /tmp/bind
+!r /tmp/nine nine
+!r /tmp/bind bind
 # combine two images at an offset
-bind + (y > 100 ? nine[x,y-100,z] : 0)
+bind + (y > 100 ? nine[x,y-100] : 0)
 # extract red and alpha channels
 z == 3 ? bind : z == 0 ? bind : 0
 # halve color intensity
@@ -15,15 +15,14 @@
 bind[x,y,0] + bind[x,y,1] + bind[x,y,2] == 0 && z == 3 ? 0 : bind
 # set black pixels to cyan
 bind[x,y,0] + bind[x,y,1] + bind[x,y,2] == 0 ? z == 0 ? 0 : z == 1 ? Z : z == 2 ? Z : bind : bind
-EOF
 
 
 # doug demo
-; pixo -q <<EOF
-r bind /tmp/bind
+; pico
+!r /tmp/bind bind
+# set default size to that of bind
+!s 598 879
 lerp = z != 3 ? (x < X/3 ? 0 : x > 2*X/3 ? 255 : (x-X/3)*Z/(X/3)) : Z
-rbind = bind[X-x,y,z]
+rbind = bind[X-x]	# same as bind[X-x,y,z]
 z != 3 ? (bind*lerp+rbind*(Z-lerp))/Z : Z
 z != 3 ? (rbind*lerp+bind*(Z-lerp))/Z : Z
-d
-EOF
--- a/exec.c
+++ /dev/null
@@ -1,119 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <plumb.h>
-#include "dat.h"
-#include "fns.h"
-
-char wdir[1024], pref[64];
-char **tok;
-int ntok;
-
-static int plumbfd, mkfd;
-
-void
-show(char *path)
-{
-	if(plumbfd < 0)
-		return;
-	if(plumbsendtext(plumbfd, "pixo", nil, wdir, path) < 0)
-		fprint(2, "plumbsendtext: %r\n");
-}
-
-static int
-system(char *cmd)
-{
-	int r, argc, pid;
-	char name[64], *argv[32];
-	Waitmsg *w;
-
-	argc = tokenize(cmd, argv, nelem(argv)-1);
-	argv[argc] = nil;
-	snprint(name, sizeof name, "/bin/%s", argv[0]);
-	switch(pid = fork()){
-	case -1:
-		fprint(2, "fork: %r\n");
-		return -1;
-	case 0:
-		USED(pid);
-		exec(name, argv);
-		sysfatal("execl: %r");
-	}
-	if((w = wait()) == nil){
-		fprint(2, "system: lost children\n");
-		return -1;
-	}
-	r = 0;
-	if(w->msg != nil && w->msg[0] != 0){
-		fprint(2, "system: failure: %s\n", w->msg);
-		r = -1;
-	}
-	free(w);
-	return r;
-}
-
-char *
-execute(void)
-{
-	int fd;
-	char **p, cmd[128];
-	Sym *s;
-	static char path[128];
-
-	snprint(path, sizeof path, "%s.c", pref);
-	if((fd = create(path, OWRITE, 0666)) < 0){
-		fprint(2, "create: %r");
-		return nil;
-	}
-	write(fd, prolog, strlen(prolog));
-	snprint(path, sizeof path, "%s.%d.bit", pref, nsym + 1);
-	fprint(fd,
-		"	Memimage **﹩i, *﹩im[%d];\n"
-		"	char *out = \"%s\";\n",
-		nsym > 0 ? nsym : 1, path);
-	for(s=sym; s<sym+nsym; s++)
-		if(s->ref)
-			fprint(fd, "	﹩im[%zd] = READ(\"%s\");\n",
-				s-sym, s->path);
-	fprint(fd,
-		"	X = %d;\n"
-		"	Y = %d;\n",
-		Δx, Δy);
-	write(fd, prepstr, strlen(prepstr));
-	fprint(fd, "		T = ");
-	for(p=tok; p<tok+ntok; p++)
-		write(fd, *p, strlen(*p));
-	write(fd, tailstr, strlen(tailstr));
-	close(fd);
-	snprint(cmd, sizeof cmd, "mk -f %s.mk %s", pref, pref);
-	if(system(cmd) < 0)
-		return nil;
-	return path;
-}
-
-static void
-mkfile(void)
-{
-	char path[64];
-
-	snprint(path, sizeof path, "%s.mk", pref);
-	if((mkfd = create(path, OWRITE|ORCLOSE, 0666)) < 0)
-		sysfatal("mkfile: %r");
-	fprint(mkfd,
-		"</$objtype/mkfile\n"
-		"%s:Q: %s.c\n"
-		"	$CC $CFLAGS -o %s.$O $prereq\n"
-		"	$LD -o $target %s.$O\n"
-		"	$target\n"
-		"	rm -f $target $prereq %s.$O\n",
-		pref, pref, pref, pref, pref);
-}
-
-void
-initfiles(void)
-{
-	getwd(wdir, sizeof wdir);
-	snprint(pref, sizeof pref, "/tmp/pixo.%d", getpid());
-	mkfile();
-	if((plumbfd = plumbopen("send", OWRITE)) < 0)
-		fprint(2, "plumbopen: %r\n");
-}
--- a/fns.h
+++ /dev/null
@@ -1,4 +1,0 @@
-void	show(char*);
-char*	execute(void);
-void	initfiles(void);
-void	parse(char*);
--- a/mkfile
+++ /dev/null
@@ -1,11 +1,0 @@
-</$objtype/mkfile
-BIN=$home/bin/$objtype
-TARG=pixo
-OFILES=\
-	defs.$O\
-	exec.$O\
-	parse.$O\
-	pixo.$O\
-
-HFILES=dat.h fns.h
-</sys/src/cmd/mkone
--- a/parse.c
+++ /dev/null
@@ -1,432 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <ctype.h>
-#include "dat.h"
-#include "fns.h"
-
-Sym *sym;
-int nsym;
-int Δx, Δy;
-
-static char*
-esmprint(char *fmt, ...)
-{
-	char *p;
-	va_list arg;
-	
-	va_start(arg, fmt);
-	p = vsmprint(fmt, arg);
-	va_end(arg);
-	if(p == nil)
-		sysfatal("smprint: %r");
-	return p;
-}
-
-static char *
-estrdup(char *s)
-{
-	if((s = strdup(s)) == nil)
-		sysfatal("estrdup: %r");
-	setmalloctag(s, getcallerpc(&s));
-	return s;
-}
-
-static void *
-erealloc(void *p, ulong n)
-{
-	if((p = realloc(p, n)) == nil)
-		sysfatal("realloc: %r");
-	setrealloctag(p, getcallerpc(&p));
-	return p;
-}
-
-static void *
-emalloc(ulong n)
-{
-	void *p;
-
-	if((p = mallocz(n, 1)) == nil)
-		sysfatal("emalloc: %r");
-	setmalloctag(p, getcallerpc(&n));
-	return p;
-}
-
-static void
-nuketok(void)
-{
-	char **p;
-
-	for(p=tok; p<tok+ntok; p++)
-		free(*p);
-	free(tok);
-	tok = nil;
-	ntok = 0;
-}
-
-static char **
-newtok(char *s)
-{
-	char **p;
-
-	tok = erealloc(tok, ++ntok * sizeof *tok);
-	p = tok + ntok - 1;
-	*p = s;
-	return p;
-}
-
-static char *
-addtok(char *s, int n)
-{
-	char **p;
-
-	p = newtok(s);
-	*p = emalloc(n + 1);
-	memcpy(*p, s, n);
-	return *p;
-}
-
-static Sym *
-newsym(char *name)
-{
-	Sym *s;
-
-	sym = erealloc(sym, ++nsym * sizeof *sym);
-	s = sym + nsym - 1;
-	s->iname = esmprint("$%zd", s - sym + 1);
-	s->cname = esmprint("﹩im[%zd]", s - sym);
-	s->name = name == nil ? esmprint("﹩%zd", s - sym + 1) : estrdup(name);
-	return s;
-}
-
-static Sym *
-getsym(char *name, int n)
-{
-	Sym *s;
-
-	if(n == 0)
-		n = strlen(name);
-	for(s=sym; s<sym+nsym; s++)
-		if(strncmp(s->name, name, n) == 0 && strlen(s->name) == n
-		|| strncmp(s->iname, name, n) == 0 && strlen(s->iname) == n)
-			return s;
-	return nil;
-}
-
-static Sym *
-addsym(char *name)
-{
-	Sym *s;
-
-	if((s = getsym(name, 0)) == nil)
-		s = newsym(name);
-	return s;
-}
-
-static void
-cleanup(void)
-{
-	Sym *s;
-
-	nuketok();
-	for(s=sym; s<sym+nsym; s++)
-		s->ref = 0;
-}
-
-static void
-fnwrite(int argc, char **argv)
-{
-	int n, fd, dfd;
-	uchar buf[65536];
-	Sym *s;
-
-	if(argc != 3){
-		fprint(2, "usage: w name path\n");
-		return;
-	}
-	if((s = getsym(argv[1], 0)) == nil){
-		fprint(2, "fnwrite: no such image %s\n", argv[1]);
-		return;
-	}
-	if(strcmp(argv[2], s->path) == 0){
-		fprint(2, "not overwriting image with itself\n");
-		return;
-	}
-	if((fd = open(s->path, OREAD)) < 0){
-		fprint(2, "open: %r\n");
-		return;
-	}
-	if((dfd = create(argv[2], OWRITE, 0666)) < 0){
-		fprint(2, "create: %r\n");
-		return;
-	}
-	while((n = read(fd, buf, sizeof buf)) > 0)
-		if(write(dfd, buf, n) != n){
-			n = -1;
-			break;
-		}
-	close(fd);
-	close(dfd);
-	if(n < 0)
-		fprint(2, "fnwrite: %r\n");
-}
-
-static void
-fnadd(int argc, char **argv)
-{
-	char *path;
-	Sym *s;
-
-	if(argc < 2 || argc > 3){
-		fprint(2, "usage: r name [path]\n");
-		return;
-	}
-	s = addsym(argv[1]);
-	path = argc < 3 ? argv[1] : argv[2];
-	if(access(path, OREAD) < 0){
-		fprint(2, "access %s: %r\n", path);
-		return;
-	}
-	s->path = esmprint("%s%s%s",
-		path[0] == '/' ? "" : wdir,
-		path[0] == '/' ? "" : "/",
-		path);
-}
-
-static void
-fndisplay(int argc, char **argv)
-{
-	int i;
-	Sym *s;
-
-	if(argc == 1){
-		if(nsym > 0)
-			show(sym[nsym-1].path);
-		return;
-	}
-	for(i=1; i<argc; i++){
-		if((s = getsym(argv[i], 0)) != nil)
-			show(s->path);
-		else
-			fprint(2, "fndisplay: no such image %s\n", argv[i]);
-	}
-}
-
-static void
-fnsize(int argc, char **argv)
-{
-	char *p;
-
-	if(argc != 3){
-		fprint(2, "usage: s width height\n");
-		return;
-	}
-	Δx = strtol(argv[1], &p, 0);
-	if(p == argv[1]){
-		fprint(2, "fnsize: invalid width\n");
-		return;
-	}
-	Δy = strtol(argv[2], &p, 0);
-	if(p == argv[1])
-		fprint(2, "fnsize: invalid height\n");
-}
-
-static void
-fnfiles(int, char **)
-{
-	Sym *s;
-
-	for(s=sym; s<sym+nsym; s++)
-		print("%s %s %s\n", s->iname, s->name, s->path);
-}
-
-static int
-command(char *p)
-{
-	struct{
-		char *name;
-		void (*fn)(int, char**);
-	} *cp, cmd[] = {
-		"f", fnfiles,
-		"s", fnsize,
-		"r", fnadd,
-		"w", fnwrite,
-		"d", fndisplay
-	};
-	int n;
-	char *f[16];
-
-	for(cp=cmd; cp<cmd+nelem(cmd); cp++){
-		n = strlen(cp->name);
-		if(strncmp(p, cp->name, n) == 0
-		&& (p[n] == 0 || isspace(p[n]))){
-			n = tokenize(p, f, nelem(f));
-			cp->fn(n, f);
-			return 1;
-		}
-	}
-	return 0;
-}
-
-static int
-isbuiltin(char *p, int n)
-{
-	char **fp, *fn[] = {
-		"X", "Y", "Z",
-		"x", "y", "z"
-	};
-
-	for(fp=fn; fp<fn+nelem(fn); fp++)
-		if(strncmp(p, *fp, n) == 0 && strlen(*fp) == n)
-			return 1;
-	return 0;
-}
-
-static int
-mkcoords(char *s, char *e)
-{
-	char *p, **cp, *c[2] = {nil};
-
-	while(isspace(*s))
-		s++;
-	for(p=s+1, cp=c; p<e; p++){
-		if(*p == ']'){
-			*p = 0;
-			break;
-		}else if(*p == ','){
-			if(cp >= c + nelem(c)){
-				fprint(2, "invalid index spec\n");
-				return -1;
-			}
-			*p = 0;
-			*cp++ = p + 1;
-		}
-	}
-	p = esmprint(", %s,%s,%s)", s+1,
-		c[0] != nil ? c[0] : "y",
-		c[1] != nil ? c[1] : "z");
-	newtok(p);
-	return 0;
-}
-
-static int
-getcoords(char *s)
-{
-	char *p;
-
-	for(p=s; *p!=']' && *p!=0; p++)
-		;
-	return *p == 0 ? -1 : p + 2 - s;
-}
-
-static int
-peekrune(char *p, Rune ro)
-{
-	Rune r;
-
-	while(isspace(*p))
-		p++;
-	chartorune(&r, p);
-	return r == ro;
-}
-
-static int
-notaword(Rune r)
-{
-	char *o, *op = "+-*/%?:^,&|<>=![], ()";
-
-	if(isdigitrune(r))
-		return 1;
-	for(o=op; o<op+strlen(op); o++)
-		if(r == *o)
-			return 1;
-	return 0;
-}
-
-static int
-getname(char *s)
-{
-	char *p, *q;
-	Rune r;
-
-	q = p = s + chartorune(&r, s);
-	while(r != 0 && !notaword(r) && !isspace(r)){
-		q = p;
-		p += chartorune(&r, p);
-	}
-	return q - s;
-}
-
-void
-parse(char *s)
-{
-	int n, m, nchar;
-	char *p, *new;
-	Rune r;
-	Sym *sp;
-
-	for(p=s, nchar=0, new=nil; *p != 0; p++){
-		if(isspace(*p))
-			continue;
-		if(nchar == 0 && command(p))
-			return;
-		if(*p == '#')
-			break;
-		nchar++;
-		n = chartorune(&r, p);
-		if(notaword(r))
-			goto next;
-		n = getname(p);
-		if(peekrune(p + n, '('))
-			goto next;
-		if(isbuiltin(p, n))
-			goto next;
-		if((sp = getsym(p, n)) == nil){
-			if(nchar > 1 || !peekrune(p + n, '=')){
-				s = emalloc(n + 1);
-				memcpy(s, p, n);
-				fprint(2, "unknown symbol %s\n", s);
-				free(s);
-				goto end;
-			}
-			new = emalloc(n + 1);
-			memcpy(new, p, n);
-			while(isspace(p[n]))
-				n++;
-			n++;
-			s = p + n;
-			goto next;
-		}
-		addtok(s, p - s);
-		sp->ref = 1;
-		newtok(estrdup("RIMAGE("));
-		newtok(estrdup(sp->cname));
-		s = p + n;
-		if(!peekrune(s, '[')){
-			newtok(estrdup(",x,y,z)"));
-			goto next;
-		}
-		if((m = getcoords(s)) < 0){
-			fprint(2, "syntax error: no matching ]\n");
-			goto end;
-		}
-		if(mkcoords(s, s + m - 1) < 0)
-			goto end;
-		s += m;
-		p += m - 1;
-	next:
-		p += n - 1;
-	}
-	if(nchar == 0)
-		return;
-	if(p > s)
-		addtok(s, p - s);
-	if((p = execute()) != nil){
-		sp = newsym(new);
-		sp->path = estrdup(p);
-		if(!quiet)
-			show(p);
-	}
-end:
-	free(new);
-	cleanup();
-}
--- /dev/null
+++ b/pico
@@ -1,0 +1,258 @@
+#!/bin/rc
+fn clean{
+	rm -f /tmp/pico.^$pid^*
+}
+fn sigexit{
+	clean
+}
+fn sigint{
+	clean
+}
+
+awk '
+function show(s){
+	system("plumb -d image " path[s])
+}
+
+function newname(file, name){
+	if(name == "")
+		name = "$" length(ref)+1
+	if(!(name in ref)){
+		ref[name] = ""
+		idx[name] = length(ref)
+	}
+	path[name] = file
+	new = name
+}
+
+function command(){
+	if($1 == "!d"){
+		if(NF > 1){
+			if($2 in path)
+				show($2)
+			else
+				print "unknown name " $2
+		}else if(new != "")
+			show(new)
+		else
+			print "nothing to show"
+	}else if($1 == "!r"){
+		if(NF < 2 || NF > 3)
+			print "usage: !r path [name]"
+		else
+			newname($2, NF < 3 ? "" : $3)
+	}else if($1 == "!w"){
+		if(NF < 3)
+			print "usage: !w name path"
+		else if($2 in path)
+			system("cp " path[$2] " " $3)
+		else
+			print "unknown name " $2
+	}else if($1 == "!f"){
+		for(i in ref)
+			print idx[i] "\t" i "\t" path[i]
+	}else if($1 == "!s"){
+		if(NF < 3)
+			print "usage: !s x y"
+		else{
+			X = $2
+			Y = $3
+		}
+	}else
+		print "unknown command"
+}
+
+BEGIN{
+	X = 0
+	Y = 0
+	opx = "!-\\/:-@\\[-\\^`\\{-~"
+	namex = "[^0-9" opx "][^" opx "]*"
+	pref="/tmp/pico.'^$pid^'"
+	mkfile = pref ".mk"
+	libfile = pref ".lib.c"
+	cfile = pref ".c"
+
+	print \
+"</$objtype/mkfile\n" \
+"%.$O:Q:	%.c\n" \
+"	$CC $CFLAGS -o $target $stem.c\n" \
+pref ":Q:	" pref ".lib.$O " pref ".$O\n" \
+"	$LD -o $target $prereq\n" \
+"	$target\n" \
+"	rm -f $target " pref ".$O\n" \
+	>mkfile
+
+	print \
+"#include <u.h>\n" \
+"#include <libc.h>\n" \
+"#include <draw.h>\n" \
+"#include <memdraw.h>\n" \
+"\n" \
+"Memimage*\n" \
+"﹩read(char *file)\n" \
+"{\n" \
+"	int fd;\n" \
+"	Memimage *m, *m1;\n" \
+"	if((fd = open(file, OREAD)) < 0)\n" \
+"		sysfatal(\"open: %r\");\n" \
+"	if((m = readmemimage(fd)) == nil)\n" \
+"		sysfatal(\"readmemimage: %r\");\n" \
+"	close(fd);\n" \
+"	if(m->chan != ABGR32){\n" \
+"		m1 = allocmemimage(m->r, ABGR32);\n" \
+"		memfillcolor(m1, DBlack);\n" \
+"		memimagedraw(m1, m1->r, m, m->r.min, memopaque, ZP, S);\n" \
+"		freememimage(m);\n" \
+"		m = m1;\n" \
+"	}\n" \
+"	return m;\n" \
+"}\n\n" \
+"void\n" \
+"﹩write(Memimage *m, char *file)\n" \
+"{\n" \
+"	int fd;\n" \
+"	if((fd = create(file, OWRITE, 0666)) < 0)\n" \
+"		sysfatal(\"create: %r\");\n" \
+"	if(writememimage(fd, m) < 0)\n" \
+"		sysfatal(\"writememimage: %r\");\n" \
+"	close(fd);\n" \
+"}\n\n" \
+"uchar*\n" \
+"﹩byte(Memimage *m, int x, int y, int z)\n" \
+"{\n" \
+"	static uchar devnull;\n" \
+"	if(x < 0 || y < 0 || z < 0 || x >= Dx(m->r) || y >= Dy(m->r) || z > 3) return &devnull;\n" \
+"	return byteaddr(m, addpt(m->r.min, Pt(x,y))) + z;\n" \
+"}" \
+	>libfile
+
+	prog = \
+"#include <u.h>\n" \
+"#include <libc.h>\n" \
+"#include <draw.h>\n" \
+"#include <memdraw.h>\n" \
+"Memimage*	﹩read(char*);\n" \
+"void	﹩write(Memimage*, char*);\n" \
+"uchar*	﹩byte(Memimage*, int, int, int);\n" \
+"#define Z 255\n" \
+"#define ﹩clip(x) ((x) < 0 ? 0 : (x) > 255 ? 255 : (x))\n" \
+"void\n" \
+"main(void)\n" \
+"{\n" \
+"	int x, y, z, X, Y, T;\n" \
+"	Memimage *﹩new;\n" \
+"	if(memimageinit() < 0)\n" \
+"		sysfatal(\"initmemimage: %r\");"
+
+	prep = \
+"	for(﹩i=﹩im; ﹩i<﹩im+nelem(﹩im); ﹩i++){\n" \
+"		if(*﹩i == nil) continue;\n" \
+"		if(X < Dx((*﹩i)->r)) X = Dx((*﹩i)->r);\n" \
+"		if(Y < Dy((*﹩i)->r)) Y = Dy((*﹩i)->r);\n" \
+"	}\n" \
+"	if((﹩new = allocmemimage(Rect(0,0,X,Y), ABGR32)) == nil)\n" \
+"		sysfatal(\"allocmemimage: %r\");\n" \
+"	for(z=0; z<4; z++) for(y=0; y<Y; y++) for(x=0; x<X; x++){"
+
+	footer = \
+"		*﹩byte(﹩new, x,y,z) = ﹩clip(T);\n" \
+"	}\n" \
+"	﹩write(﹩new, out);\n" \
+"	exits(nil);\n" \
+"}"
+}
+{
+	if($1 ~ /^!.$/){
+		command()
+		next
+	}
+	gsub("[ 	]", "", $0)
+	gsub("(" namex "|=)", " & ", $0)
+	while((i = match($0, "[" opx "] +=")) != 0)
+		$0 = substr($0, 1, i) substr($0, i+RLENGTH-1)
+	gsub("[\\(\\[\\],#]", " & ", $0)
+	ntok = split($0, tok)
+	expr = ""
+	new = ""
+	gotsym = 0
+	ncomma = 0
+	for(i in ref)
+		ref[i] = ""
+	for(i=1; i<=ntok; i++){
+		if(tok[i] ~ namex && tok[i] !~ "^[XxYyZz]$"){
+			if(gotsym){
+				print "invalid syntax"
+				next
+			}else if(i < ntok && tok[i+1] == "(")
+				expr = expr " " tok[i]
+			else if(tok[i] in ref){
+				ref[tok[i]] = 1
+				expr = expr " *﹩byte(﹩im[" idx[tok[i]] "], "
+				if(i < ntok && tok[i+1] == "["){
+					i++
+					gotsym = 1
+				}else
+					expr = expr "x,y,z)"
+			}else{
+				if(i == 1 && ntok >= 3 && tok[2] == "=")
+					new = tok[i]
+				else
+					expr = expr " " tok[i]
+			}
+		}else if(tok[i] == "="){
+			if(i != 2){
+				print "invalid assignment"
+				next
+			}
+		}else if(tok[i] == "["){
+			print "invalid index"
+			next
+		}else if(tok[i] == "#")
+			break
+		else if(gotsym){
+			if(tok[i] == "]"){
+				if(ncomma == 0)
+					tok[i] = ",y,z)"
+				else if(ncomma == 1)
+					tok[i] = ",z)"
+				else
+					tok[i] = ")"
+				ncomma = 0
+				gotsym = 0
+			}else if(tok[i] == ","){
+				if(ncomma > 1){
+					print "invalid index"
+					next
+				}
+				ncomma++
+			}
+			expr = expr " " tok[i]
+		}else
+			expr = expr " " tok[i]
+	}
+	if(expr == "")
+		next
+	if(gotsym){
+		print "invalid index"
+		next
+	}
+	out = pref "." length(ref)+1 ".bit"
+	print prog >cfile
+	print \
+"\tMemimage **﹩i, *﹩im[" length(ref)+1 "];\n" \
+"\tchar *out = \"" out "\";\n" \
+"\tX = " X ";\n" \
+"\tY = " Y ";" >>cfile
+	for(i in ref)
+		if(ref[i])
+			print "\t﹩im[" idx[i] "] = ﹩read(\"" path[i] "\");\n" >>cfile
+	print prep >>cfile
+	print "\t\tT = " expr ";" >>cfile
+	print footer >>cfile
+	close(cfile)
+	if(system("mk -f " mkfile " " pref) != "0")
+		next
+	newname(out, new)
+	show(new)
+}
+'
--- a/pixo.c
+++ /dev/null
@@ -1,30 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <ctype.h>
-#include <bio.h>
-#include "dat.h"
-#include "fns.h"
-
-int quiet;
-
-void
-main(int argc, char **argv)
-{
-	char *s;
-	Biobuf *bf;
-
-	ARGBEGIN{
-	case 'q': quiet = 1; break;
-	}ARGEND
-	initfiles();
-	if((bf = Bfdopen(0, OREAD)) == nil)
-		sysfatal("Bfdopen: %r");
-	for(;;){
-		if(!quiet)
-			print("→ ");
-		if((s = Brdstr(bf, '\n', 1)) == nil)
-			break;
-		parse(s);
-		free(s);
-	}
-}
--