aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/base/arg.c71
-rw-r--r--src/base/bufio/dump.c66
-rw-r--r--src/base/bufio/get.c17
-rw-r--r--src/base/bufio/internal.h4
-rw-r--r--src/base/bufio/read.c36
-rw-r--r--src/base/bufio/reader.c28
-rw-r--r--src/base/bufio/refill.h28
-rw-r--r--src/base/bufio/rules.mk5
-rw-r--r--src/base/bufio/unget.c18
-rw-r--r--src/base/coro/coro.c43
-rw-r--r--src/base/coro/internal.h15
-rw-r--r--src/base/coro/rules.mk3
-rw-r--r--src/base/coro/unix_x64.s113
-rw-r--r--src/base/error/errorf.c13
-rw-r--r--src/base/error/exits.c11
-rw-r--r--src/base/error/internal.h3
-rw-r--r--src/base/error/panicf.c16
-rw-r--r--src/base/error/rules.mk6
-rw-r--r--src/base/error/verrorf.c9
-rw-r--r--src/base/error/vpanicf.c11
-rw-r--r--src/base/flate/internal.h39
-rw-r--r--src/base/flate/read.c41
-rw-r--r--src/base/flate/reader.c59
-rw-r--r--src/base/flate/rules.mk6
-rw-r--r--src/base/flate/write.c48
-rw-r--r--src/base/flate/writer.c57
-rw-r--r--src/base/fs/internal.h18
-rw-r--r--src/base/fs/rules.mk3
-rw-r--r--src/base/fs/walk.c119
-rw-r--r--src/base/fs/walker.c39
-rw-r--r--src/base/gz/flush.c7
-rw-r--r--src/base/gz/get.c17
-rw-r--r--src/base/gz/interface.c12
-rw-r--r--src/base/gz/internal.h6
-rw-r--r--src/base/gz/open.c13
-rw-r--r--src/base/gz/printf.c15
-rw-r--r--src/base/gz/put.c7
-rw-r--r--src/base/gz/putstring.c8
-rw-r--r--src/base/gz/read.c16
-rw-r--r--src/base/gz/rules.mk11
-rw-r--r--src/base/gz/seek.c7
-rw-r--r--src/base/gz/write.c7
-rw-r--r--src/base/io/fd.c7
-rw-r--r--src/base/io/flush.c7
-rw-r--r--src/base/io/get.c7
-rw-r--r--src/base/io/interface.c70
-rw-r--r--src/base/io/internal.h4
-rw-r--r--src/base/io/open.c13
-rw-r--r--src/base/io/putbyte.c7
-rw-r--r--src/base/io/putstring.c7
-rw-r--r--src/base/io/read.c7
-rw-r--r--src/base/io/readln.c12
-rw-r--r--src/base/io/rules.mk14
-rw-r--r--src/base/io/seek.c7
-rw-r--r--src/base/io/stat.c7
-rw-r--r--src/base/io/tell.c7
-rw-r--r--src/base/io/unget.c7
-rw-r--r--src/base/io/write.c7
-rw-r--r--src/base/mem/arena.c119
-rw-r--r--src/base/mem/buffer.c45
-rw-r--r--src/base/mem/interface.c36
-rw-r--r--src/base/mem/internal.h4
-rw-r--r--src/base/mem/rules.mk5
-rw-r--r--src/base/mem/set64.c13
-rw-r--r--src/base/mmap/internal.h5
-rw-r--r--src/base/mmap/mmap.c39
-rw-r--r--src/base/mmap/rules.mk2
-rw-r--r--src/base/os/basename.c10
-rw-r--r--src/base/os/exists.c7
-rw-r--r--src/base/os/internal.h4
-rw-r--r--src/base/os/rules.mk4
-rw-r--r--src/base/os/sep.c14
-rw-r--r--src/base/rng/base.c24
-rw-r--r--src/base/rng/bernoulli.c7
-rw-r--r--src/base/rng/exponential.c11
-rw-r--r--src/base/rng/internal.h19
-rw-r--r--src/base/rng/normal.c77
-rw-r--r--src/base/rng/poisson.c126
-rw-r--r--src/base/rng/random.c33
-rw-r--r--src/base/rng/rules.mk7
-rw-r--r--src/base/rules.mk37
-rw-r--r--src/base/sort/double.c12
-rw-r--r--src/base/sort/float.c12
-rw-r--r--src/base/sort/int.c12
-rw-r--r--src/base/sort/int16.c12
-rw-r--r--src/base/sort/int32.c12
-rw-r--r--src/base/sort/int64.c12
-rw-r--r--src/base/sort/int8.c12
-rw-r--r--src/base/sort/internal.h5
-rw-r--r--src/base/sort/rules.mk14
-rw-r--r--src/base/sort/string.c12
-rw-r--r--src/base/sort/uint.c12
-rw-r--r--src/base/sort/uint16.c12
-rw-r--r--src/base/sort/uint32.c12
-rw-r--r--src/base/sort/uint64.c12
-rw-r--r--src/base/sort/uint8.c12
-rw-r--r--src/base/string/append.c53
-rw-r--r--src/base/string/appendf.c31
-rw-r--r--src/base/string/clear.c9
-rw-r--r--src/base/string/copyn.c11
-rw-r--r--src/base/string/equals.c12
-rw-r--r--src/base/string/find.c11
-rw-r--r--src/base/string/fit.c20
-rw-r--r--src/base/string/free.c8
-rw-r--r--src/base/string/grow.c33
-rw-r--r--src/base/string/internal.h12
-rw-r--r--src/base/string/join.c16
-rw-r--r--src/base/string/len.c17
-rw-r--r--src/base/string/lower.c12
-rw-r--r--src/base/string/make.c53
-rw-r--r--src/base/string/makef.c25
-rw-r--r--src/base/string/read.c12
-rw-r--r--src/base/string/replace.c26
-rw-r--r--src/base/string/rules.mk19
-rw-r--r--src/base/string/split.c39
-rw-r--r--src/base/string/upper.c12
-rw-r--r--src/base/test.c170
-rw-r--r--src/cmd/cc/ast.c2139
-rw-r--r--src/cmd/cc/bits.c114
-rw-r--r--src/cmd/cc/cc.c409
-rw-r--r--src/cmd/cc/cc.h806
-rw-r--r--src/cmd/cc/lex.c873
-rw-r--r--src/cmd/cc/pp.c1125
-rw-r--r--src/cmd/cc/rules.mk21
-rw-r--r--src/cmd/cc/scratch.c7
-rw-r--r--src/cmd/cc/util.c21
-rw-r--r--src/cmd/dwm/LICENSE37
-rw-r--r--src/cmd/dwm/client.c657
-rw-r--r--src/cmd/dwm/config.h141
-rw-r--r--src/cmd/dwm/drw.c376
-rw-r--r--src/cmd/dwm/dwm.c1185
-rw-r--r--src/cmd/dwm/dwm.h384
-rw-r--r--src/cmd/dwm/hook.c489
-rw-r--r--src/cmd/dwm/rules.mk29
-rw-r--r--src/cmd/dwm/util.c66
-rw-r--r--src/cmd/filter/filter.c104
-rw-r--r--src/cmd/filter/rules.mk14
-rw-r--r--src/cmd/ic/LICENSE23
-rw-r--r--src/cmd/ic/ic.1100
-rw-r--r--src/cmd/ic/ic.c878
-rw-r--r--src/cmd/ic/rules.mk14
-rw-r--r--src/cmd/ic/strlcpy.c32
-rw-r--r--src/cmd/menu/LICENSE30
-rw-r--r--src/cmd/menu/config.h25
-rw-r--r--src/cmd/menu/drw.c428
-rw-r--r--src/cmd/menu/drw.h57
-rw-r--r--src/cmd/menu/menu.c765
-rw-r--r--src/cmd/menu/menu.h40
-rw-r--r--src/cmd/menu/rules.mk27
-rw-r--r--src/cmd/menu/util.c30
-rw-r--r--src/cmd/rc/code.c277
-rw-r--r--src/cmd/rc/exec.c1267
-rw-r--r--src/cmd/rc/exec.h47
-rw-r--r--src/cmd/rc/input.c1679
-rw-r--r--src/cmd/rc/io.c437
-rw-r--r--src/cmd/rc/job.c91
-rw-r--r--src/cmd/rc/lex.c394
-rw-r--r--src/cmd/rc/main.c66
-rw-r--r--src/cmd/rc/parse.c2059
-rw-r--r--src/cmd/rc/parse.h141
-rw-r--r--src/cmd/rc/prompt.c36
-rw-r--r--src/cmd/rc/rc.h263
-rw-r--r--src/cmd/rc/rules.mk32
-rw-r--r--src/cmd/rc/syntax.y147
-rw-r--r--src/cmd/rc/sys.c137
-rw-r--r--src/cmd/rc/tree.c111
-rw-r--r--src/cmd/rc/util.c65
-rw-r--r--src/cmd/rc/var.c336
-rw-r--r--src/cmd/rc/wait.c247
-rw-r--r--src/cmd/rules.mk32
-rw-r--r--src/cmd/term/LICENSE34
-rw-r--r--src/cmd/term/config.h474
-rw-r--r--src/cmd/term/hb.c147
-rw-r--r--src/cmd/term/nonspacing.h89
-rw-r--r--src/cmd/term/rules.mk26
-rw-r--r--src/cmd/term/term.c2417
-rw-r--r--src/cmd/term/term.h316
-rw-r--r--src/cmd/term/term.info250
-rw-r--r--src/cmd/term/util.c30
-rw-r--r--src/cmd/term/wide.h65
-rw-r--r--src/cmd/term/x.c2070
-rw-r--r--src/cmd/walk/rules.mk15
-rw-r--r--src/cmd/walk/walk.c84
-rw-r--r--src/cmd/wm/arg.c0
-rw-r--r--src/cmd/wm/client.c274
-rw-r--r--src/cmd/wm/config.h70
-rw-r--r--src/cmd/wm/input.c316
-rw-r--r--src/cmd/wm/layer.c107
-rw-r--r--src/cmd/wm/main.c177
-rw-r--r--src/cmd/wm/monitor.c386
-rwxr-xr-xsrc/cmd/wm/protocol/sync6
-rw-r--r--src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml390
-rw-r--r--src/cmd/wm/render.c160
-rw-r--r--src/cmd/wm/rules.mk62
-rw-r--r--src/cmd/wm/util.c99
-rw-r--r--src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c93
-rw-r--r--src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h564
-rw-r--r--src/cmd/wm/wm.h350
-rw-r--r--src/cmd/wm/xdg-shell-protocol.c181
-rw-r--r--src/cmd/wm/xdg-shell-protocol.h1676
-rw-r--r--src/cmd/wm/xdg.c118
-rw-r--r--src/libbio/align.c178
-rw-r--r--src/libbio/fasta.c393
-rw-r--r--src/libbio/newick.c414
-rw-r--r--src/libbio/phylo.c427
-rw-r--r--src/libbio/rules.mk24
-rw-r--r--src/libbio/simulate.c120
-rw-r--r--src/libbio/test.c283
-rw-r--r--src/libc/rules.mk20
-rw-r--r--src/libc/stdio.c59
-rw-r--r--src/libc/string.c80
-rw-r--r--src/libfmt/buffer.c60
-rw-r--r--src/libfmt/do.c730
-rw-r--r--src/libfmt/esprint.c14
-rw-r--r--src/libfmt/float.c1077
-rw-r--r--src/libfmt/fprint.c14
-rw-r--r--src/libfmt/internal.h17
-rw-r--r--src/libfmt/locale.c16
-rw-r--r--src/libfmt/nsprint.c14
-rw-r--r--src/libfmt/open.c34
-rw-r--r--src/libfmt/print.c13
-rw-r--r--src/libfmt/rules.mk35
-rw-r--r--src/libfmt/sprint.c19
-rw-r--r--src/libfmt/test.c72
-rw-r--r--src/libfmt/vesprint.c26
-rw-r--r--src/libfmt/vfprint.c19
-rw-r--r--src/libfmt/vnsprint.c26
-rw-r--r--src/libfmt/vprint.c19
-rw-r--r--src/libfmt/vwrite.c26
-rw-r--r--src/libfmt/write.c22
-rw-r--r--src/libmath/basic.c531
-rw-r--r--src/libmath/blas.c63
-rw-r--r--src/libmath/blas1.c58
-rw-r--r--src/libmath/blas1body215
-rw-r--r--src/libmath/blas2.c222
-rw-r--r--src/libmath/blas2body256
-rw-r--r--src/libmath/blas3.c279
-rw-r--r--src/libmath/lapack.c0
-rw-r--r--src/libmath/linalg.c63
-rw-r--r--src/libmath/loop.h114
-rw-r--r--src/libmath/matrix.c176
-rw-r--r--src/libmath/rules.mk27
-rw-r--r--src/libmath/test.c471
-rw-r--r--src/libsre/lex.c246
-rw-r--r--src/libsre/sre.h93
-rw-r--r--src/libterm/term.c489
-rw-r--r--src/libterm/term.h270
-rw-r--r--src/libterm/window.c408
-rw-r--r--src/libutf/canfit.c23
-rw-r--r--src/libutf/decode.c98
-rw-r--r--src/libutf/decodeprev.c60
-rw-r--r--src/libutf/encode.c69
-rw-r--r--src/libutf/find.c31
-rw-r--r--src/libutf/findlast.c32
-rw-r--r--src/libutf/internal.h38
-rw-r--r--src/libutf/len.c21
-rw-r--r--src/libutf/rules.mk76
-rw-r--r--src/libutf/runelen.c8
-rw-r--r--src/libutf/vendor/common.c220
-rw-r--r--src/libutf/vendor/common.h46
-rw-r--r--src/libutf/vendor/mkgraphemedata.c24
-rw-r--r--src/libutf/vendor/mkrunetype.c388
-rw-r--r--src/libutf/vendor/mkrunewidth.c325
-rw-r--r--src/nixos/rules.mk0
-rw-r--r--src/rules.mk23
265 files changed, 42755 insertions, 0 deletions
diff --git a/src/base/arg.c b/src/base/arg.c
new file mode 100644
index 0000000..269043e
--- /dev/null
+++ b/src/base/arg.c
@@ -0,0 +1,71 @@
+#include <u.h>
+#include <base.h>
+
+// NOTE: this utf8 bit is copied from libunicode to remove the hard dependency just for ARG_BEGIN.
+
+#define UTFmax 4
+#define RuneSync 0x80u
+#define RuneSelf 0x80u
+#define RuneErr 0xFFFDu
+#define RuneMax 0x10FFFFu
+#define RuneMask 0x1FFFFFu
+
+#define Bit(i) (7-(i))
+/* N 0's preceded by i 1's e.g. T(Bit(2)) is 1100 0000 */
+#define Tbyte(i) (((1 << (Bit(i)+1))-1) ^ 0xFF)
+/* 0000 0000 0000 0111 1111 1111 */
+#define RuneX(i) ((1 << (Bit(i) + ((i)-1)*Bitx))-1)
+enum
+{
+ Bitx = Bit(1),
+ Tx = Tbyte(1),
+ Rune1 = (1 << (Bit(0)+0*Bitx)) - 1,
+
+ Maskx = (1 << Bitx) - 1, /* 0011 1111 */
+ Testx = Maskx ^ 0xff, /* 1100 0000 */
+
+ SurrogateMin = 0xD800,
+ SurrogateMax = 0xDFFF,
+ Bad = RuneErr,
+};
+
+
+int
+arg·bytetorune(uint32* r, byte* s)
+{
+ int c[4], i;
+ uint32 l;
+
+ c[0] = *(ubyte*)(s);
+ if(c[0] < Tx) {
+ *r = c[0];
+ return 1;
+ }
+
+ l = c[0];
+ for(i = 1; i < UTFmax; i++) {
+ c[i] = *(ubyte*)(s+i);
+ c[i] ^= Tx;
+ if (c[i] & Testx) goto bad;
+
+ l = (l << Bitx) | c[i];
+ if(c[0] < Tbyte(i + 2)) {
+ l &= RuneX(i + 1);
+ if (i == 1) {
+ if (c[0] < Tbyte(2) || l <= Rune1)
+ goto bad;
+ } else if (l <= RuneX(i) || l > RuneMax)
+ goto bad;
+ if (i == 2 && SurrogateMin <= l && l <= SurrogateMax)
+ goto bad;
+
+ *r = l;
+ return i + 1;
+ }
+ }
+bad:
+ *r = RuneErr;
+ return 1;
+}
+
+char *argv0;
diff --git a/src/base/bufio/dump.c b/src/base/bufio/dump.c
new file mode 100644
index 0000000..0b527e2
--- /dev/null
+++ b/src/base/bufio/dump.c
@@ -0,0 +1,66 @@
+// -----------------------------------------------------------------------
+// reader
+
+#if 0
+rune
+bufio·getrune(io·Buffer *buf)
+{
+ ubyte b;
+ int i;
+ byte str[UTFmax+1];
+ rune r;
+
+ // NOTE: I'm worried about the sign here...
+ b = bufio·getbyte(buf);
+ if (b < RuneSelf) {
+ buf->runesize = 1;
+ return b;
+ }
+
+ i = 0;
+ str[i++] = b;
+
+nextbyte:
+ b = bufio·getbyte(buf);
+ if (b < 0) return b;
+ if (i >= arrlen(str)) return RuneErr;
+ str[i++] = b;
+ if (!utf8·fullrune(str, i))
+ goto nextbyte;
+
+ buf->runesize = utf8·bytetorune(&r, str);
+ if (r == RuneErr && b == 1) {
+ errorf("illegal UTF-8 sequence");
+ for (; i >= 0; i--)
+ errorf("%s%.2x", i > 0 ? " " : "", *(ubyte*)(str+i));
+ errorf("\n");
+
+ buf->runesize = 0;
+ } else
+ for (; i > buf->runesize; i--)
+ bufio·ungetbyte(buf, str[i]);
+
+ return r;
+}
+
+// TODO: Check that we are given the correct rune!
+error
+bufio·ungetrune(io·Buffer *buf, rune r)
+{
+ if (buf->state & bufio·rdr) {
+ errorf("attempted to unget on non-active reader");
+ return bufio·err;
+ }
+
+ if (buf->pos == buf->buf) {
+ errorf("attempted to unget past end of buffer");
+ return bufio·err;
+ }
+
+ buf->pos -= buf->runesize;
+ return 0;
+}
+#endif
+
+// -----------------------------------------------------------------------
+// writer
diff --git a/src/base/bufio/get.c b/src/base/bufio/get.c
new file mode 100644
index 0000000..9f10c88
--- /dev/null
+++ b/src/base/bufio/get.c
@@ -0,0 +1,17 @@
+#include "internal.h"
+#include "refill.h"
+
+int
+bufio·getbyte(io·Buffer *buf)
+{
+getbyte:
+ if(buf->pos < buf->end)
+ return *buf->pos++;
+
+ memmove(buf->buf, buf->end - bufio·ungets, bufio·ungets);
+
+ if(refill(buf) <= 0)
+ return bufio·eof;
+
+ goto getbyte;
+}
diff --git a/src/base/bufio/internal.h b/src/base/bufio/internal.h
new file mode 100644
index 0000000..302c035
--- /dev/null
+++ b/src/base/bufio/internal.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
diff --git a/src/base/bufio/read.c b/src/base/bufio/read.c
new file mode 100644
index 0000000..09a9f83
--- /dev/null
+++ b/src/base/bufio/read.c
@@ -0,0 +1,36 @@
+#include "internal.h"
+#include "refill.h"
+
+int
+bufio·read(io·Buffer *buf, int sz, int n, void *out)
+{
+ byte *wtr;
+ int nr, rem, diff;
+
+ if(n == 0 || buf->state & bufio·end)
+ return bufio·err;
+
+ assert(buf->state & bufio·rdr);
+
+ wtr = out;
+ rem = n*sz;
+
+ while(rem > 0){
+ diff = buf->end - buf->pos;
+ nr = MIN(diff, rem);
+ if(!nr){
+ if(buf->state & bufio·end)
+ break;
+ if(refill(buf) <= 0)
+ break;
+
+ continue;
+ }
+ memmove(wtr, buf->pos, nr);
+ wtr += nr;
+ buf->pos += nr;
+ rem -= nr;
+ }
+
+ return n - rem/sz;
+}
diff --git a/src/base/bufio/reader.c b/src/base/bufio/reader.c
new file mode 100644
index 0000000..afdaf60
--- /dev/null
+++ b/src/base/bufio/reader.c
@@ -0,0 +1,28 @@
+#include "internal.h"
+
+error
+bufio·initreader(io·Buffer *buf, io·Reader rdr, void *h)
+{
+ if (buf->state) {
+ errorf("attemped to initialize an active buffer, state is '%d'", buf->state);
+ return bufio·err;
+ }
+ buf->state = bufio·rdr;
+ buf->runesize = 0;
+ buf->h = h;
+ buf->rdr = rdr;
+ buf->beg = buf->buf + bufio·ungets;
+ buf->pos = buf->beg;
+ buf->end = buf->pos;
+ buf->size = bufio·size - bufio·ungets;
+
+ return 0;
+}
+
+void
+bufio·finireader(io·Buffer *buf)
+{
+ buf->state = bufio·nil;
+ buf->runesize = 0;
+ buf->rdr = (io·Reader){ .read = nil };
+}
diff --git a/src/base/bufio/refill.h b/src/base/bufio/refill.h
new file mode 100644
index 0000000..41e357e
--- /dev/null
+++ b/src/base/bufio/refill.h
@@ -0,0 +1,28 @@
+int
+refill(io·Buffer *buf)
+{
+ int n;
+
+ if(buf->state & bufio·end)
+ return bufio·err;
+
+ memcpy(buf->buf, buf->pos - bufio·ungets, bufio·ungets);
+
+ n = buf->rdr.read(buf->h, 1, buf->size, buf->beg);
+ if(n < 0)
+ return bufio·err;
+ if(n == 0){
+ buf->state |= bufio·end;
+ return 0;
+ }
+
+ buf->pos = buf->beg;
+ buf->end = buf->pos + n;
+
+ // TEST: put a physical EOF byte at the end
+ // this would allow for an unget operation
+ if(n < buf->size)
+ *buf->end++ = EOF;
+
+ return n;
+}
diff --git a/src/base/bufio/rules.mk b/src/base/bufio/rules.mk
new file mode 100644
index 0000000..84f283f
--- /dev/null
+++ b/src/base/bufio/rules.mk
@@ -0,0 +1,5 @@
+SRCS_$(d)+=\
+ $(d)/bufio/get.c\
+ $(d)/bufio/read.c\
+ $(d)/bufio/reader.c\
+ $(d)/bufio/unget.c\
diff --git a/src/base/bufio/unget.c b/src/base/bufio/unget.c
new file mode 100644
index 0000000..3fd16de
--- /dev/null
+++ b/src/base/bufio/unget.c
@@ -0,0 +1,18 @@
+#include "internal.h"
+
+error
+bufio·ungetbyte(io·Buffer *buf, byte c)
+{
+ if(!(buf->state & bufio·rdr)) {
+ errorf("attempted to unget on non-active reader");
+ return bufio·err;
+ }
+
+ if(buf->pos == buf->buf) {
+ errorf("attempted to unget past end of buffer");
+ return bufio·err;
+ }
+
+ buf->pos--;
+ return 0;
+}
diff --git a/src/base/coro/coro.c b/src/base/coro/coro.c
new file mode 100644
index 0000000..2255c99
--- /dev/null
+++ b/src/base/coro/coro.c
@@ -0,0 +1,43 @@
+#include "internal.h"
+
+/* Co-routine context */
+Coro*
+coro·make(uintptr stk, uintptr (*func)(Coro*, uintptr))
+{
+ if (!func) return nil;
+ if (stk == 0) stk = 8192;
+
+ byte *block = malloc(stk);
+ Coro *co = (Coro*)&block[stk - sizeof(Coro)];
+ co->bp = block;
+ co->size = stk;
+
+ _newcoro(co, func, co);
+ return co;
+}
+
+error
+coro·free(Coro *co)
+{
+ enum
+ {
+ NIL,
+ GOOD,
+ EMPTY,
+ LOST,
+ };
+
+ if (!co) return NIL;
+ if (!co->bp) return LOST;
+ if (co->size == 0) return EMPTY;
+
+ free(co->bp);
+
+ return GOOD;
+}
+
+uintptr
+coro·yield(Coro *c, uintptr arg)
+{
+ return _coroyield(c, arg);
+}
diff --git a/src/base/coro/internal.h b/src/base/coro/internal.h
new file mode 100644
index 0000000..f57d27b
--- /dev/null
+++ b/src/base/coro/internal.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+
+extern void _newcoro(Coro *co, uintptr (*func)(Coro*, uintptr), void *stk);
+extern uintptr _coroyield(Coro *co, uintptr arg);
+
+struct Coro
+{
+ void *sp;
+ void *bp;
+ uintptr size;
+ void *user;
+};
diff --git a/src/base/coro/rules.mk b/src/base/coro/rules.mk
new file mode 100644
index 0000000..c2ee89f
--- /dev/null
+++ b/src/base/coro/rules.mk
@@ -0,0 +1,3 @@
+SRCS_$(d)+=\
+ $(d)/coro/coro.c\
+ $(d)/coro/unix_x64.s\
diff --git a/src/base/coro/unix_x64.s b/src/base/coro/unix_x64.s
new file mode 100644
index 0000000..d7de2a2
--- /dev/null
+++ b/src/base/coro/unix_x64.s
@@ -0,0 +1,113 @@
+; Nicholas Noll 2019
+;
+; ===================================================================
+%use altreg
+
+ bits 64
+ default rel
+ global _newcoro
+ global _coroyield
+
+; ===================================================================
+ section .text
+; -------------------------------------------------------------------
+
+%assign L.coro -8
+%assign L.func -16
+
+coroinit:
+ mov R7, [RBP + L.coro]
+ mov R6, R0
+ call [RBP + L.func]
+
+rerun:
+ mov R7, [RBP + L.coro]
+ mov R6, R0
+ call _coroyield
+ jmp rerun
+
+; -------------------------------------------------------------------
+; # Register Mapping
+;
+; R0 R1 R2 R3 R4 R5 R6 R7 R8 ...
+; RAX RCX RDX RBX RSP RBP RSI RDI R8 ...
+;
+; # Sys V calling convention
+; func(R7, R6, R2, R1, R8, R9, Z0-7): R0
+;
+; # Stack layout of an in-flight coro
+; *coro
+; *func
+; *bp (base pointer of stack)
+; ....... STACK .........
+; Saved Clobbers
+;
+; ###
+; Stack layout of an init coro
+; Stores the func pointer to init
+; Stores the clobber registers.
+;
+; L.coro [8]
+; L.func [7]
+; coroinit [6]
+; RBP [5]
+; R3 [4]
+; R12 [3]
+; R13 [2]
+; R14 [1]
+; R15 [0]
+
+%define WORDSZ 8
+%define NSAVES 9
+
+; coro *coro·new(co *coro, fn func, bp *stack)
+_newcoro:
+ lea R0, [coroinit] ; Store address of init function
+ lea R1, [R2 - NSAVES*WORDSZ] ; Store offset address of stack
+
+ mov [R1 + 8*WORDSZ], R7 ; Store context pointer
+ mov [R1 + 7*WORDSZ], R6 ; Store function pointer
+ mov [R1 + 6*WORDSZ], R0 ; Store initializer pointer
+ mov [R1 + 5*WORDSZ], R2 ; Store stack base pointer
+
+ xor R0, R0
+
+ ; Start of mutable stack
+ ; Blank out the clobbers
+ mov [R1 + 4*WORDSZ], R0 ; R3
+ mov [R1 + 3*WORDSZ], R0 ; R12
+ mov [R1 + 2*WORDSZ], R0 ; R13
+ mov [R1 + 1*WORDSZ], R0 ; R14
+ mov [R1 + 0*WORDSZ], R0 ; R15
+
+ mov [R7], R1
+ ret
+
+; Saves register state
+%macro pushclobs 0
+ push RBP
+ push R3
+ push R12
+ push R13
+ push R14
+ push R15
+%endmacro
+
+; Restores register state
+%macro popclobs 0
+ pop R15
+ pop R14
+ pop R13
+ pop R12
+ pop R3
+ pop RBP
+%endmacro
+
+; uintptr coro.yield(co *coro, data uintptr)
+_coroyield:
+ pushclobs
+ mov R0, R6 ; Move return value into return register.
+ xchg RSP, [R7] ; Atomically swap the stack pointer with the yieldee.
+ popclobs
+
+ ret
diff --git a/src/base/error/errorf.c b/src/base/error/errorf.c
new file mode 100644
index 0000000..193dd9d
--- /dev/null
+++ b/src/base/error/errorf.c
@@ -0,0 +1,13 @@
+#include "internal.h"
+
+void
+errorf(byte* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, fmt, args);
+
+ va_end(args);
+}
diff --git a/src/base/error/exits.c b/src/base/error/exits.c
new file mode 100644
index 0000000..6be7d3b
--- /dev/null
+++ b/src/base/error/exits.c
@@ -0,0 +1,11 @@
+#include "internal.h"
+
+void
+exits(char *s)
+{
+ if(s == nil || *s == 0)
+ exit(0);
+
+ fputs(s, stderr);
+ exit(1);
+}
diff --git a/src/base/error/internal.h b/src/base/error/internal.h
new file mode 100644
index 0000000..88a8895
--- /dev/null
+++ b/src/base/error/internal.h
@@ -0,0 +1,3 @@
+#include <u.h>
+#include <base.h>
+
diff --git a/src/base/error/panicf.c b/src/base/error/panicf.c
new file mode 100644
index 0000000..d698576
--- /dev/null
+++ b/src/base/error/panicf.c
@@ -0,0 +1,16 @@
+#include "internal.h"
+
+void
+panicf(byte* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ printf("panic: ");
+ vprintf(fmt, args);
+ printf("\n");
+
+ va_end(args);
+
+ exit(1);
+}
diff --git a/src/base/error/rules.mk b/src/base/error/rules.mk
new file mode 100644
index 0000000..e3a9ce0
--- /dev/null
+++ b/src/base/error/rules.mk
@@ -0,0 +1,6 @@
+SRCS_$(d)+=\
+ $(d)/error/exits.c \
+ $(d)/error/errorf.c \
+ $(d)/error/panicf.c \
+ $(d)/error/verrorf.c \
+ $(d)/error/vpanicf.c \
diff --git a/src/base/error/verrorf.c b/src/base/error/verrorf.c
new file mode 100644
index 0000000..15af064
--- /dev/null
+++ b/src/base/error/verrorf.c
@@ -0,0 +1,9 @@
+#include "internal.h"
+
+void
+verrorf(byte* fmt, va_list args)
+{
+ printf("error: ");
+ vprintf(fmt, args);
+ printf("\n");
+}
diff --git a/src/base/error/vpanicf.c b/src/base/error/vpanicf.c
new file mode 100644
index 0000000..bea97ac
--- /dev/null
+++ b/src/base/error/vpanicf.c
@@ -0,0 +1,11 @@
+#include "internal.h"
+
+void
+vpanicf(byte* fmt, va_list args)
+{
+ printf("panic: ");
+ vprintf(fmt, args);
+ printf("\n");
+
+ exit(1);
+}
diff --git a/src/base/flate/internal.h b/src/base/flate/internal.h
new file mode 100644
index 0000000..794c7c2
--- /dev/null
+++ b/src/base/flate/internal.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+
+#include <zlib.h>
+
+typedef struct buffer
+{
+ union {
+ struct z_stream_s;
+ z_stream z;
+ };
+
+ ubyte buf[4098];
+} buffer;
+
+typedef struct flate·Reader
+{
+ io·Reader rdr;
+ void* impl;
+
+ union {
+ struct buffer;
+ buffer b;
+ };
+} flate·Reader;
+
+typedef struct flate·Writer
+{
+ io·Writer wtr;
+ void* impl;
+
+ union {
+ struct buffer;
+ buffer b;
+ };
+} flate·Writer;
+
diff --git a/src/base/flate/read.c b/src/base/flate/read.c
new file mode 100644
index 0000000..9a42070
--- /dev/null
+++ b/src/base/flate/read.c
@@ -0,0 +1,41 @@
+#include "internal.h"
+
+int
+flate·read(flate·Reader *rdr, int sz, int n, void *buf)
+{
+ int r;
+ int err;
+ flate·Reader zrdr;
+
+ zrdr = *rdr;
+ zrdr.next_out = buf;
+ zrdr.avail_out = n*sz;
+
+READ:
+ err = inflate(&zrdr.b.z, Z_STREAM_END);
+ switch (err) {
+ case Z_OK:
+ return n;
+
+ case Z_STREAM_END:
+ r = zrdr.next_out - (ubyte*)buf;
+ n -= r;
+ zrdr.avail_in = zrdr.rdr.read(zrdr.impl, 1, arrlen(zrdr.buf), zrdr.buf);
+ if (!zrdr.avail_in) {
+ return r;
+ }
+ zrdr.next_in = zrdr.buf;
+ goto READ;
+
+ case Z_NEED_DICT:
+ errorf("zlib: need input dictionary");
+ goto ERROR;
+
+ case Z_STREAM_ERROR:
+ errorf("zlib: inconsistent stream structure");
+ goto ERROR;
+ }
+ERROR:
+ flate·closereader(rdr);
+ return -1;
+}
diff --git a/src/base/flate/reader.c b/src/base/flate/reader.c
new file mode 100644
index 0000000..84f0d80
--- /dev/null
+++ b/src/base/flate/reader.c
@@ -0,0 +1,59 @@
+#include "internal.h"
+
+flate·Reader*
+flate·openreader(io·Reader rdr, void* r, mem·Allocator mem, void* m)
+{
+ error err;
+ flate·Reader *zrdr;
+
+ zrdr = mem.alloc(m, 1, sizeof(*zrdr));
+
+ zrdr->zalloc = (void *(*)(void *, unsigned int, unsigned int))mem.alloc;
+ zrdr->zfree = mem.free;
+ zrdr->opaque = m;
+ zrdr->avail_in = rdr.read(r, 1, arrlen(zrdr->buf), zrdr->buf);
+ zrdr->next_in = zrdr->buf;
+
+ err = inflateInit(&zrdr->b.z);
+
+ switch (err) {
+ case Z_OK:
+ return zrdr;
+
+ case Z_MEM_ERROR:
+ errorf("zlib: not enough memory");
+ goto ERROR;
+
+ case Z_VERSION_ERROR:
+ errorf("zlib: incompatible version");
+ goto ERROR;
+
+ case Z_STREAM_ERROR:
+ errorf("zlib: incorrect input parameters");
+ goto ERROR;
+
+ default:
+ errorf("zlib: unrecognized error code");
+ }
+ERROR:
+ errorf("zlib: msg: %s", zrdr->msg);
+ mem.free(m, zrdr);
+ return nil;
+}
+
+error
+flate·closereader(flate·Reader *rdr)
+{
+ int err;
+ flate·Reader zrdr;
+
+ zrdr = *rdr;
+ err = inflateEnd(&zrdr.b.z);
+ if (err != Z_OK) {
+ errorf("zlib: failed to cleanup");
+ return err;
+ }
+ rdr->zfree(rdr->opaque, rdr);
+
+ return 0;
+}
diff --git a/src/base/flate/rules.mk b/src/base/flate/rules.mk
new file mode 100644
index 0000000..54d8c14
--- /dev/null
+++ b/src/base/flate/rules.mk
@@ -0,0 +1,6 @@
+SRCS_$(d)+=\
+ $(d)/flate/read.c\
+ $(d)/flate/reader.c\
+ $(d)/flate/write.c\
+ $(d)/flate/writer.c\
+ $(d)/flate/writer.c\
diff --git a/src/base/flate/write.c b/src/base/flate/write.c
new file mode 100644
index 0000000..3f07b94
--- /dev/null
+++ b/src/base/flate/write.c
@@ -0,0 +1,48 @@
+#include "internal.h"
+
+int
+flate·write(flate·Writer *wtr, int sz, int n, void *buf)
+{
+ int r;
+ int err;
+ flate·Writer zwtr;
+
+ zwtr = *wtr;
+ zwtr.next_out = buf;
+DEFLATE:
+ zwtr.avail_out = n*sz;
+ err = deflate(&zwtr.z, Z_NO_FLUSH);
+
+ switch (err) {
+ case Z_STREAM_END:
+ return n;
+
+ case Z_OK:
+ r = (zwtr.next_out - (ubyte*)buf)/sz;
+ n -= r;
+ if (!n) {
+ return r;
+ }
+ buf += n;
+ goto DEFLATE;
+
+ case Z_STREAM_ERROR:
+ errorf("zlib: bad input");
+ goto ERROR;
+
+ case Z_BUF_ERROR:
+ if (!zwtr.avail_in) {
+ zwtr.avail_in += zwtr.wtr.write(zwtr.impl, 1, arrlen(zwtr.buf), buf);
+ if (!zwtr.avail_in) {
+ errorf("reader: failed read");
+ goto ERROR;
+ }
+ goto DEFLATE;
+ }
+ }
+
+ return 0;
+ERROR:
+ errorf("zlib: %s", zwtr.msg);
+ return -1;
+}
diff --git a/src/base/flate/writer.c b/src/base/flate/writer.c
new file mode 100644
index 0000000..f339ae0
--- /dev/null
+++ b/src/base/flate/writer.c
@@ -0,0 +1,57 @@
+#include "internal.h"
+
+flate·Writer*
+flate·openwriter(io·Writer wtr, void* w, mem·Allocator mem, void* m)
+{
+ error err;
+ flate·Writer *zwtr;
+
+ zwtr = mem.alloc(m, 1, sizeof(*zwtr));
+ zwtr->zalloc = (void *(*)(void *, unsigned int, unsigned int))mem.alloc;
+ zwtr->zfree = mem.free;
+ zwtr->opaque = m;
+ zwtr->avail_in = 0;
+
+ err = deflateInit(&zwtr->b.z, Z_DEFAULT_COMPRESSION);
+
+ switch (err) {
+ case Z_OK:
+ return zwtr;
+
+ case Z_MEM_ERROR:
+ errorf("zlib: not enough memory");
+ goto ERROR;
+
+ case Z_VERSION_ERROR:
+ errorf("zlib: incompatible version");
+ goto ERROR;
+
+ case Z_STREAM_ERROR:
+ errorf("zlib: incorrect compression level");
+ goto ERROR;
+
+ default:
+ errorf("zlib: unrecognized error code");
+ }
+ERROR:
+ errorf("zlib: msg: %s", zwtr->msg);
+ mem.free(m, zwtr);
+ return nil;
+}
+
+error
+flate·closewriter(flate·Writer *wtr)
+{
+ int err;
+ flate·Writer zwtr;
+
+ zwtr = *wtr;
+ err = deflateEnd(&zwtr.b.z);
+ if (err != Z_OK) {
+ errorf("zlib: failed to cleanup");
+ return err;
+ }
+ zwtr.zfree(zwtr.opaque, wtr);
+
+ return 0;
+}
diff --git a/src/base/fs/internal.h b/src/base/fs/internal.h
new file mode 100644
index 0000000..7fde093
--- /dev/null
+++ b/src/base/fs/internal.h
@@ -0,0 +1,18 @@
+#include <u.h>
+#include <base.h>
+#include <base/macro/map.h>
+#include <dirent.h>
+
+/*
+ * path history
+ */
+struct Key
+{
+ ino_t ino;
+ dev_t dev;
+};
+
+struct fs·History
+{
+ SET_STRUCT_BODY(struct Key);
+};
diff --git a/src/base/fs/rules.mk b/src/base/fs/rules.mk
new file mode 100644
index 0000000..3927ae3
--- /dev/null
+++ b/src/base/fs/rules.mk
@@ -0,0 +1,3 @@
+SRCS_$(d)+=\
+ $(d)/fs/walk.c\
+ $(d)/fs/walker.c\
diff --git a/src/base/fs/walk.c b/src/base/fs/walk.c
new file mode 100644
index 0000000..d528896
--- /dev/null
+++ b/src/base/fs/walk.c
@@ -0,0 +1,119 @@
+#include "internal.h"
+
+#define hash(k) ((int32)k.ino ^ (int32)k.dev)
+#define equal(k1, k2) (k1.ino == k2.ino && k1.dev == k2.dev)
+
+static
+int
+morehistory(fs·History *h, int n)
+{
+ SET_GROW(h, struct Key, n, hash, sys·Memory, nil);
+}
+
+static
+int
+addentry(fs·History *h, struct Key key, int *err)
+{
+ SET_PUT(h, key, hash, equal, morehistory, err);
+}
+
+static
+void
+forget(fs·History *h)
+{
+ if (!h)
+ return;
+
+ SET_RESET(h);
+}
+
+void
+fs·walk(fs·Walker *fs)
+{
+ char *e, *b;
+ DIR *dir;
+ int new, fd, ofd, flags;
+ fs·History *h;
+ struct dirent *d;
+ io·Stat cwd;
+ struct fs·Entry *it;
+
+ flags = 0;
+ if(fs->flags & fs·nolinks)
+ flags |= AT_SYMLINK_NOFOLLOW;
+
+ /* get info for base relative to current fd */
+ if(fstatat(fs->fd, fs->base, &cwd, flags) < 0){
+ if(fs->flags & fs·verbose)
+ errorf("stat: %s", fs->path);
+ return;
+ }
+
+ /* if we hit a file, finish! */
+ if(!S_ISDIR(cwd.st_mode)) {
+ fs->func(fs->data, fs->base, fs->path, &cwd);
+ return;
+ }
+
+ /* have we been here before? (cycle detection) */
+ /* if not, add to our path history */
+ if (!(fs->flags & fs·nolinks)) {
+ addentry(fs->hist, (struct Key){.dev=cwd.st_dev, .ino=cwd.st_ino}, &new);
+ if (!new)
+ return;
+ }
+
+ /*
+ * operate on directory first if preorder traversal
+ * truncate recursion if callback returns an error code
+ */
+ if (fs->flags & fs·preorder) {
+ if (fs->func(fs->data, fs->base, fs->path, &cwd))
+ return;
+ }
+
+ /* open directory */
+ if(!fs->max || fs->lev + 1 < fs->max) {
+ fd = openat(fs->fd, fs->base, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (fd < 0)
+ errorf("open %s:", fs->path);
+
+ if (!(dir=fdopendir(fd))) {
+ if(fs->flags & fs·verbose)
+ errorf("fdopendir: %s", fs->path);
+ return;
+ }
+
+ ofd = fs->fd, fs->fd = fd;
+
+ /* traverse children */
+ e = fs->end, b = fs->base;
+ if (fs->end[-1] != '/')
+ *fs->end++ = '/';
+
+ fs->base = fs->end;
+ while((d = readdir(dir))) {
+ if(*d->d_name == '.')
+ if(d->d_name[1] == 0 || /* . */
+ (d->d_name[1] == '.' && d->d_name[2] == 0)) /* .. */
+ continue;
+
+ fs->end = str·copyn(fs->base, d->d_name, arrend(fs->path) - fs->base);
+
+ fs->lev++;
+ fs·walk(fs);
+ fs->lev--;
+ }
+ *e = 0;
+ fs->fd = ofd;
+ fs->end = e, fs->base = b;
+ closedir(dir);
+ }
+
+ /* operate on directory if postorder (default) traversal */
+ if (!(fs->flags & fs·preorder))
+ fs->func(fs->data, fs->base, fs->path, &cwd);
+
+ if (!fs->lev)
+ forget(fs->hist);
+}
diff --git a/src/base/fs/walker.c b/src/base/fs/walker.c
new file mode 100644
index 0000000..65ff391
--- /dev/null
+++ b/src/base/fs/walker.c
@@ -0,0 +1,39 @@
+#include "internal.h"
+
+static
+void
+delete(fs·History *h)
+{
+ SET_FREE(h, sys·Memory, nil);
+}
+
+int
+fs·init(fs·Walker *fs, char *path)
+{
+ fs->base = fs->end = fs->path;
+
+ if(!path || !path[0]){
+ path = getcwd(fs->path, arrlen(fs->path));
+ if (!path)
+ return 1;
+ fs->end += strlen(path);
+ }else
+ fs->end = str·copyn(fs->base, path, arrlen(fs->path));
+
+ if(fs->path[0] != '/')
+ fs->fd = AT_FDCWD;
+
+ if(!fs->hist && !(fs->flags & fs·nolinks))
+ fs->hist = calloc(1, sizeof(*fs->hist));
+
+ return 0;
+}
+
+void
+fs·fini(fs·Walker *fs)
+{
+ if(fs->hist){
+ delete(fs->hist);
+ free(fs->hist);
+ }
+}
diff --git a/src/base/gz/flush.c b/src/base/gz/flush.c
new file mode 100644
index 0000000..011a3ab
--- /dev/null
+++ b/src/base/gz/flush.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+error
+gz·flush(gz·Stream *s)
+{
+ return gzflush(s, Z_FINISH);
+}
diff --git a/src/base/gz/get.c b/src/base/gz/get.c
new file mode 100644
index 0000000..24ba23a
--- /dev/null
+++ b/src/base/gz/get.c
@@ -0,0 +1,17 @@
+#include "internal.h"
+
+byte
+gz·getbyte(gz·Stream *s)
+{
+ // NOTE: Can't call macro
+ byte b[2];
+ gzread(s, b, 1);
+
+ return b[0];
+}
+
+error
+gz·ungetbyte(gz·Stream *s, byte c)
+{
+ return gzungetc(c, s);
+}
diff --git a/src/base/gz/interface.c b/src/base/gz/interface.c
new file mode 100644
index 0000000..15b8f10
--- /dev/null
+++ b/src/base/gz/interface.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+io·Reader gz·Reader = (io·Reader){ gz·read };
+io·Peeker gz·Peeker = (io·Peeker){ gz·getbyte, gz·ungetbyte };
+io·Seeker gz·Seeker = (io·Seeker){ gz·seek, gz·tell };
+io·PeekReader gz·Peekreader = (io·PeekReader){ gz·read, gz·getbyte, gz·ungetbyte };
+
+io·Writer gz·Writer = (io·Writer){ gz·write };
+io·Putter gz·Putter = (io·Putter){ gz·putbyte, gz·putstring };
+io·PutWriter gz·PutWriter = (io·PutWriter){ gz·write, gz·putbyte, gz·putstring };
+
+io·ReadWriter gz·ReadWriter = (io·ReadWriter){ gz·read, gz·write };
diff --git a/src/base/gz/internal.h b/src/base/gz/internal.h
new file mode 100644
index 0000000..6a268c4
--- /dev/null
+++ b/src/base/gz/internal.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+
+#include <zlib.h>
diff --git a/src/base/gz/open.c b/src/base/gz/open.c
new file mode 100644
index 0000000..c84ce5e
--- /dev/null
+++ b/src/base/gz/open.c
@@ -0,0 +1,13 @@
+#include "internal.h"
+
+gz·Stream*
+gz·open(byte *path, byte *mode)
+{
+ return gzopen(path, mode);
+}
+
+error
+gz·close(gz·Stream* s)
+{
+ return gzclose(s);
+}
diff --git a/src/base/gz/printf.c b/src/base/gz/printf.c
new file mode 100644
index 0000000..d7f75cf
--- /dev/null
+++ b/src/base/gz/printf.c
@@ -0,0 +1,15 @@
+#include "internal.h"
+
+int
+gz·printf(gz·Stream *s, byte *fmt, ...)
+{
+ error err;
+
+ va_list args;
+ va_start(args, fmt);
+ err = gzprintf(s, fmt, args);
+ va_end(args);
+
+ return err;
+}
+
diff --git a/src/base/gz/put.c b/src/base/gz/put.c
new file mode 100644
index 0000000..fa9807d
--- /dev/null
+++ b/src/base/gz/put.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+error
+gz·putbyte(gz·Stream *s, byte c)
+{
+ return gzputc(s, c);
+}
diff --git a/src/base/gz/putstring.c b/src/base/gz/putstring.c
new file mode 100644
index 0000000..64ff470
--- /dev/null
+++ b/src/base/gz/putstring.c
@@ -0,0 +1,8 @@
+#include "internal.h"
+
+error
+gz·putstring(gz·Stream *s, byte *str)
+{
+ return gzputs(s, str);
+}
+
diff --git a/src/base/gz/read.c b/src/base/gz/read.c
new file mode 100644
index 0000000..112fe4d
--- /dev/null
+++ b/src/base/gz/read.c
@@ -0,0 +1,16 @@
+#include "internal.h"
+
+int
+gz·read(gz·Stream *s, int sz, int n, void* buf)
+{
+ return gzread(s, buf, n*sz);
+}
+
+int
+gz·readln(gz·Stream *s, int n, byte *buf)
+{
+ byte* b;
+ b = gzgets(s, buf, n);
+
+ return strlen(b);
+}
diff --git a/src/base/gz/rules.mk b/src/base/gz/rules.mk
new file mode 100644
index 0000000..a933291
--- /dev/null
+++ b/src/base/gz/rules.mk
@@ -0,0 +1,11 @@
+SRCS_$(d)+=\
+ $(d)/gz/flush.c\
+ $(d)/gz/get.c\
+ $(d)/gz/interface.c\
+ $(d)/gz/open.c\
+ $(d)/gz/printf.c\
+ $(d)/gz/put.c\
+ $(d)/gz/putstring.c\
+ $(d)/gz/read.c\
+ $(d)/gz/seek.c\
+ $(d)/gz/write.c\
diff --git a/src/base/gz/seek.c b/src/base/gz/seek.c
new file mode 100644
index 0000000..328886d
--- /dev/null
+++ b/src/base/gz/seek.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+gz·seek(gz·Stream *s, long off, enum SeekPos whence)
+{
+ return gzseek(s, off, whence);
+}
diff --git a/src/base/gz/write.c b/src/base/gz/write.c
new file mode 100644
index 0000000..862d833
--- /dev/null
+++ b/src/base/gz/write.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+gz·write(gz·Stream *s, int sz, int n, void* buf)
+{
+ return gzwrite(s, buf, n*sz);
+}
diff --git a/src/base/io/fd.c b/src/base/io/fd.c
new file mode 100644
index 0000000..ded1b02
--- /dev/null
+++ b/src/base/io/fd.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+io·fd(io·Stream *s)
+{
+ return fileno(s);
+}
diff --git a/src/base/io/flush.c b/src/base/io/flush.c
new file mode 100644
index 0000000..0f1217a
--- /dev/null
+++ b/src/base/io/flush.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+io·flush(io·Stream *s)
+{
+ return fflush(s);
+}
diff --git a/src/base/io/get.c b/src/base/io/get.c
new file mode 100644
index 0000000..d4e52f8
--- /dev/null
+++ b/src/base/io/get.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+byte
+io·getbyte(io·Stream *s)
+{
+ return fgetc(s);
+}
diff --git a/src/base/io/interface.c b/src/base/io/interface.c
new file mode 100644
index 0000000..bead9e1
--- /dev/null
+++ b/src/base/io/interface.c
@@ -0,0 +1,70 @@
+#include "internal.h"
+
+static
+int
+·read(void *rdr, int size, int n, void *buf)
+{
+ return io·read((io·Stream *)rdr, size, n, buf);
+}
+
+static
+byte
+·get(void *rdr)
+{
+ return io·getbyte((io·Stream *)rdr);
+}
+
+static
+error
+·unget(void *rdr, byte c)
+{
+ return io·ungetbyte((io·Stream *)rdr, c);
+}
+
+static
+int
+·write(void *wtr, int sz, int n, void *buf)
+{
+ return io·write((io·Stream *)wtr, sz, n, buf);
+}
+
+static
+error
+·put(void *wtr, byte c)
+{
+ return io·putbyte((io·Stream *)wtr, c);
+}
+
+static
+int
+·puts(void *wtr, string s)
+{
+ return io·putstring((io·Stream *)wtr, s);
+}
+
+static
+int
+·seek(void *skr, long off, enum SeekPos whence)
+{
+ return io·seek((io·Stream *)skr, off, whence);
+}
+
+static
+long
+·tell(void *skr)
+{
+ return io·tell((io·Stream *)skr);
+}
+
+/* actual interfaces */
+io·Reader sys·Reader = (io·Reader){ ·read };
+io·Seeker sys·Seeker = (io·Seeker){ ·seek, ·tell };
+io·Peeker sys·Peeker = (io·Peeker){ ·get, ·unget };
+io·SeekReader sys·SeekReader = (io·SeekReader){ ·seek, ·tell, ·read };
+io·PeekReader sys·PeekReader = (io·PeekReader){ ·read, ·get, ·unget };
+
+io·Writer sys·Writer = (io·Writer){ ·write };
+io·Putter sys·Putter = (io·Putter){ ·put, ·puts };
+io·PutWriter sys·PutWriter = (io·PutWriter){ ·write, ·put, ·puts };
+
+io·ReadWriter sys·ReadWriter = (io·ReadWriter){ ·read, ·write };
diff --git a/src/base/io/internal.h b/src/base/io/internal.h
new file mode 100644
index 0000000..302c035
--- /dev/null
+++ b/src/base/io/internal.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
diff --git a/src/base/io/open.c b/src/base/io/open.c
new file mode 100644
index 0000000..e50e334
--- /dev/null
+++ b/src/base/io/open.c
@@ -0,0 +1,13 @@
+#include "internal.h"
+
+io·Stream*
+io·open(byte *name, byte *mode)
+{
+ return fopen(name, mode);
+}
+
+error
+io·close(io·Stream *s)
+{
+ return fclose(s);
+}
diff --git a/src/base/io/putbyte.c b/src/base/io/putbyte.c
new file mode 100644
index 0000000..2350a8d
--- /dev/null
+++ b/src/base/io/putbyte.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+io·putbyte(io·Stream *s, byte c)
+{
+ return fputc(c, s);
+}
diff --git a/src/base/io/putstring.c b/src/base/io/putstring.c
new file mode 100644
index 0000000..53fa993
--- /dev/null
+++ b/src/base/io/putstring.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+io·putstring(io·Stream *s, string str)
+{
+ return fputs(str, s);
+}
diff --git a/src/base/io/read.c b/src/base/io/read.c
new file mode 100644
index 0000000..b0ed3d2
--- /dev/null
+++ b/src/base/io/read.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+io·read(io·Stream *s, int sz, int n, void *buf)
+{
+ return fread(buf, sz, n, s);
+}
diff --git a/src/base/io/readln.c b/src/base/io/readln.c
new file mode 100644
index 0000000..283472d
--- /dev/null
+++ b/src/base/io/readln.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+int
+io·readln(io·Stream *s, int n, byte* buf)
+{
+ byte* b;
+ b = fgets(buf, n+1, s);
+ if(b == nil)
+ return -1;
+
+ return strlen(buf);
+}
diff --git a/src/base/io/rules.mk b/src/base/io/rules.mk
new file mode 100644
index 0000000..2e03ca5
--- /dev/null
+++ b/src/base/io/rules.mk
@@ -0,0 +1,14 @@
+SRCS_$(d)+=\
+ $(d)/io/fd.c\
+ $(d)/io/flush.c\
+ $(d)/io/interface.c\
+ $(d)/io/open.c\
+ $(d)/io/putbyte.c\
+ $(d)/io/putstring.c\
+ $(d)/io/read.c\
+ $(d)/io/readln.c\
+ $(d)/io/seek.c\
+ $(d)/io/stat.c\
+ $(d)/io/tell.c\
+ $(d)/io/unget.c\
+ $(d)/io/write.c\
diff --git a/src/base/io/seek.c b/src/base/io/seek.c
new file mode 100644
index 0000000..d0e7488
--- /dev/null
+++ b/src/base/io/seek.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+io·seek(io·Stream *s, long off, enum SeekPos origin)
+{
+ return fseek(s, off, origin);
+}
diff --git a/src/base/io/stat.c b/src/base/io/stat.c
new file mode 100644
index 0000000..d86f1ee
--- /dev/null
+++ b/src/base/io/stat.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+error
+io·stat(io·Stream *s, io·Stat *buf)
+{
+ return fstat(fileno(s), buf);
+}
diff --git a/src/base/io/tell.c b/src/base/io/tell.c
new file mode 100644
index 0000000..1c50439
--- /dev/null
+++ b/src/base/io/tell.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+long
+io·tell(io·Stream *s)
+{
+ return ftell(s);
+}
diff --git a/src/base/io/unget.c b/src/base/io/unget.c
new file mode 100644
index 0000000..5ec3536
--- /dev/null
+++ b/src/base/io/unget.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+error
+io·ungetbyte(io·Stream *s, byte c)
+{
+ return ungetc(c, s);
+}
diff --git a/src/base/io/write.c b/src/base/io/write.c
new file mode 100644
index 0000000..63df664
--- /dev/null
+++ b/src/base/io/write.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+io·write(io·Stream *s, int sz, int n, void *buf)
+{
+ return fwrite(buf, sz, n, s);
+}
diff --git a/src/base/mem/arena.c b/src/base/mem/arena.c
new file mode 100644
index 0000000..b2ce044
--- /dev/null
+++ b/src/base/mem/arena.c
@@ -0,0 +1,119 @@
+#include "internal.h"
+
+#define ARENA_ALIGN 8
+#define ARENA_BLOCK_SIZE 1024 * 1024
+
+#define ALIGN_DOWN(n, a) ((n) & ~((a)-1))
+#define ALIGN_UP(n, a) ALIGN_DOWN((n) + (a)-1, (a))
+#define ALIGN_DOWN_PTR(p, a) ((void*)ALIGN_DOWN((uintptr)(p), (a)))
+#define ALIGN_UP_PTR(p, a) ((void*)ALIGN_UP((uintptr)(p), (a)))
+
+struct Block
+{
+ struct Block *next;
+ byte buf[];
+};
+
+struct mem·Arena
+{
+ void *heap;
+ mem·Allocator mem;
+
+ byte *off;
+ byte *end;
+ struct Block *curr;
+ struct Block first;
+};
+
+static
+void*
+·arenaalloc(void *heap, uint n, ulong size)
+{
+ return mem·arenaalloc(heap, n, size);
+}
+
+static
+void
+·arenafree(void *heap, void *ptr)
+{
+ /* no-op */
+}
+
+mem·Allocator mem·ArenaAllocator = {
+ .alloc = ·arenaalloc,
+ .free = ·arenafree,
+};
+
+
+static
+void
+grow(mem·Arena *a, vlong min)
+{
+ uintptr size;
+ struct Block *blk;
+
+ size = ALIGN_UP(MAX(min, ARENA_BLOCK_SIZE), ARENA_ALIGN);
+ blk = a->mem.alloc(a->heap, 1, sizeof(*blk) + size);
+ a->off = blk->buf;
+ a->end = a->off + size;
+
+ assert(a->curr->next == nil);
+ assert(a->off == ALIGN_DOWN_PTR(a->off, ARENA_ALIGN));
+
+ a->curr->next = blk;
+ a->curr = blk;
+}
+
+mem·Arena*
+mem·makearena(mem·Allocator from, void *impl)
+{
+ mem·Arena *a = from.alloc(impl, 1, sizeof(*a) + ARENA_BLOCK_SIZE);
+ a->mem = from;
+ a->heap = impl;
+ a->off = a->first.buf;
+ a->end = a->first.buf + ARENA_BLOCK_SIZE;
+ a->curr = &a->first;
+ a->first.next = nil;
+
+ return a;
+}
+
+void
+mem·freearena(mem·Arena *a)
+{
+ struct Block *it, *next;
+
+ it = a->first.next;
+ while (it != nil) {
+ next = it->next;
+ a->mem.free(a->heap, it);
+ it = next;
+ }
+
+ a->mem.free(a->heap, a);
+}
+
+void*
+mem·arenaalloc(mem·Arena *a, uint n, ulong size)
+{
+ if(!n) {
+ return nil;
+ }
+
+ void *ptr;
+ // TODO(nnoll): check for overflow
+ size = n * size;
+
+ if (size > (ulong)(a->end - a->off)) {
+ grow(a, size);
+ assert(size <= (uintptr)(a->end - a->off));
+ }
+
+ ptr = a->off;
+ a->off = ALIGN_UP_PTR(a->off + size, ARENA_ALIGN);
+
+ assert(a->off <= a->end);
+ assert(ptr == ALIGN_DOWN_PTR(ptr, ARENA_ALIGN));
+
+ return ptr;
+}
diff --git a/src/base/mem/buffer.c b/src/base/mem/buffer.c
new file mode 100644
index 0000000..b684d35
--- /dev/null
+++ b/src/base/mem/buffer.c
@@ -0,0 +1,45 @@
+#include "internal.h"
+
+/* Grow to particular size */
+void*
+·bufgrow(void* buf, vlong newLen, vlong eltsize)
+{
+ assert(bufcap(buf) <= (SIZE_MAX - 1) / 2);
+
+ vlong newCap = MAX(16, MAX(1 + 2 * bufcap(buf), newLen));
+
+ assert(newLen <= newCap);
+ assert(newCap <= (SIZE_MAX - offsetof(BufHdr, buf)) / eltsize);
+
+ vlong newSize = offsetof(BufHdr, buf) + newCap * eltsize;
+
+ BufHdr* newHdr;
+ if (buf) {
+ newHdr = bufhdr(buf);
+ newHdr = (BufHdr*)realloc((void*)newHdr, newSize);
+ } else {
+ newHdr = (BufHdr*)malloc(newSize);
+ newHdr->len = 0;
+ }
+
+ newHdr->cap = newCap;
+ return (void*)newHdr->buf;
+}
+
+/* Pop out a value */
+void
+·bufdel(void *buf, int i, vlong eltsize)
+{
+ int n;
+ byte *b;
+ byte stk[1024];
+ assert(eltsize < sizeof(stk));
+
+ b = (byte*)buf;
+ if(n = buflen(buf), i < n) {
+ memcpy(stk, b+eltsize*i, eltsize);
+ memcpy(b+eltsize*i, b+eltsize*(i+1), eltsize*(n-i-1));
+ memcpy(b+eltsize*(n-1), stk, eltsize);
+ }
+ bufhdr(buf)->len--;
+}
diff --git a/src/base/mem/interface.c b/src/base/mem/interface.c
new file mode 100644
index 0000000..4d7d1ce
--- /dev/null
+++ b/src/base/mem/interface.c
@@ -0,0 +1,36 @@
+#include "internal.h"
+
+static
+void
+·free(void *_, void *ptr) {
+ return free(ptr);
+}
+
+static
+void *
+·alloc(void *_, uint n, ulong size) {
+ return malloc(n*size);
+}
+
+static
+void *
+·calloc(void *_, uint n, ulong size) {
+ return calloc(n, size);
+}
+
+static
+void *
+·realloc(void *_, void *ptr, uint n, ulong size) {
+ return realloc(ptr, n*size);
+}
+
+mem·Allocator sys·Memory = {
+ .alloc = ·calloc,
+ .free = ·free
+};
+
+mem·Reallocator sys·FullMemory = {
+ .alloc = ·calloc,
+ .realloc = ·realloc,
+ .free = ·free
+};
diff --git a/src/base/mem/internal.h b/src/base/mem/internal.h
new file mode 100644
index 0000000..302c035
--- /dev/null
+++ b/src/base/mem/internal.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
diff --git a/src/base/mem/rules.mk b/src/base/mem/rules.mk
new file mode 100644
index 0000000..b912d0c
--- /dev/null
+++ b/src/base/mem/rules.mk
@@ -0,0 +1,5 @@
+SRCS_$(d)+=\
+ $(d)/mem/arena.c\
+ $(d)/mem/buffer.c\
+ $(d)/mem/interface.c\
+ $(d)/mem/set64.c\
diff --git a/src/base/mem/set64.c b/src/base/mem/set64.c
new file mode 100644
index 0000000..464b3ad
--- /dev/null
+++ b/src/base/mem/set64.c
@@ -0,0 +1,13 @@
+#include "internal.h"
+
+void
+mem·set64(void *dst, uint64 val, uintptr size)
+{
+ intptr i;
+
+ for(i = 0; i < (size & (~7)); i += 8)
+ memcpy((byte*)dst + i, &val, 8);
+
+ for(; i < size; i++)
+ ((byte*)dst)[i] = ((byte*)&val)[i&7];
+}
diff --git a/src/base/mmap/internal.h b/src/base/mmap/internal.h
new file mode 100644
index 0000000..7606c7e
--- /dev/null
+++ b/src/base/mmap/internal.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+#include <sys/mman.h>
diff --git a/src/base/mmap/mmap.c b/src/base/mmap/mmap.c
new file mode 100644
index 0000000..ce3011c
--- /dev/null
+++ b/src/base/mmap/mmap.c
@@ -0,0 +1,39 @@
+#include "internal.h"
+
+mmap·Reader
+mmap·open(byte *filename)
+{
+ int fd;
+ int err;
+ void *buf;
+ io·Stream *s;
+ io·Stat st;
+
+ s = io·open(filename, "r");
+ fd = io·fd(s);
+ err = io·stat(s, &st);
+ if(err){
+ errorf("file stat: error code %d", err);
+ goto ERROR;
+ }
+
+ buf = mmap(nil, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if(!buf){
+ errorf("mmap: failed");
+ goto ERROR;
+ }
+ // NOTE: posix systems require that reference kept to mmap file after fd is closed
+ io·close(s);
+ return (mmap·Reader){.len=st.st_size, .b=buf};
+
+ERROR:
+ io·close(s);
+ return (mmap·Reader){ 0 };
+}
+
+error
+mmap·close(mmap·Reader rdr)
+{
+ munmap(rdr.b, rdr.len);
+ return 0;
+}
diff --git a/src/base/mmap/rules.mk b/src/base/mmap/rules.mk
new file mode 100644
index 0000000..fb3cab5
--- /dev/null
+++ b/src/base/mmap/rules.mk
@@ -0,0 +1,2 @@
+SRCS_$(d)+=\
+ $(d)/mmap/mmap.c\
diff --git a/src/base/os/basename.c b/src/base/os/basename.c
new file mode 100644
index 0000000..b5bb343
--- /dev/null
+++ b/src/base/os/basename.c
@@ -0,0 +1,10 @@
+#include "internal.h"
+
+char*
+os·basename(char *path)
+{
+ char *sep;
+
+ sep = strrchr(path, os·sep());
+ return (sep == nil) ? path : sep+1;
+}
diff --git a/src/base/os/exists.c b/src/base/os/exists.c
new file mode 100644
index 0000000..a3c8935
--- /dev/null
+++ b/src/base/os/exists.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+int
+os·exists(byte *path, int flag)
+{
+ return access(path, flag) == 0;
+}
diff --git a/src/base/os/internal.h b/src/base/os/internal.h
new file mode 100644
index 0000000..302c035
--- /dev/null
+++ b/src/base/os/internal.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
diff --git a/src/base/os/rules.mk b/src/base/os/rules.mk
new file mode 100644
index 0000000..bf1e71d
--- /dev/null
+++ b/src/base/os/rules.mk
@@ -0,0 +1,4 @@
+SRCS_$(d)+=\
+ $(d)/os/basename.c\
+ $(d)/os/exists.c\
+ $(d)/os/sep.c\
diff --git a/src/base/os/sep.c b/src/base/os/sep.c
new file mode 100644
index 0000000..750e627
--- /dev/null
+++ b/src/base/os/sep.c
@@ -0,0 +1,14 @@
+#include "internal.h"
+
+int
+os·sep(void)
+{
+#if defined(UNIX) || defined(__linux__)
+ return '/';
+#elif defined(WIN32)
+ return '\\';
+#else
+ panicf("unrecognized operating system");
+ return '\0';
+#endif
+}
diff --git a/src/base/rng/base.c b/src/base/rng/base.c
new file mode 100644
index 0000000..9ec496e
--- /dev/null
+++ b/src/base/rng/base.c
@@ -0,0 +1,24 @@
+#include "internal.h"
+
+static uint64
+splitmix64(struct Mix *state)
+{
+ uint64 result = state->s;
+
+ state->s = result + 0x9E3779B97f4A7C15;
+ result = (result ^ (result >> 30)) * 0xBF58476D1CE4E5B9;
+ result = (result ^ (result >> 27)) * 0x94D049BB133111EB;
+ return result ^ (result >> 31);
+}
+
+int
+rng·init(uint64 seed)
+{
+ int i;
+ Mix smstate = {seed};
+
+ for(i=0; i < 4; i++)
+ rng·RNG.s[i] = splitmix64(&smstate);
+
+ return 0;
+}
diff --git a/src/base/rng/bernoulli.c b/src/base/rng/bernoulli.c
new file mode 100644
index 0000000..02f531e
--- /dev/null
+++ b/src/base/rng/bernoulli.c
@@ -0,0 +1,7 @@
+#include "internal.h"
+
+bool
+rng·bernoulli(double f)
+{
+ return rng·random() < f;
+}
diff --git a/src/base/rng/exponential.c b/src/base/rng/exponential.c
new file mode 100644
index 0000000..c07e007
--- /dev/null
+++ b/src/base/rng/exponential.c
@@ -0,0 +1,11 @@
+#include "internal.h"
+
+/* Returns a random float64 between 0 and 1 */
+double
+rng·exponential(double lambda)
+{
+ double f;
+
+ f = rng·random();
+ return -log(1 - f)/lambda;
+}
diff --git a/src/base/rng/internal.h b/src/base/rng/internal.h
new file mode 100644
index 0000000..9cf5f41
--- /dev/null
+++ b/src/base/rng/internal.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+
+#define rol64(x, k) ((x) << (k) | ((x) >> (64-(k))))
+
+typedef struct Rng
+{
+ uint64 s[4];
+} Rng;
+
+typedef struct Mix
+{
+ uint64 s;
+} Mix;
+
+
+extern Rng rng·RNG;
diff --git a/src/base/rng/normal.c b/src/base/rng/normal.c
new file mode 100644
index 0000000..aab5731
--- /dev/null
+++ b/src/base/rng/normal.c
@@ -0,0 +1,77 @@
+#include "internal.h"
+
+static inline double
+erfinv(double x)
+{
+ /* useful constants */
+ static double
+ a0 = 1.1975323115670912564578e0, a1 = 4.7072688112383978012285e1,
+ a2 = 6.9706266534389598238465e2, a3 = 4.8548868893843886794648e3,
+ a4 = 1.6235862515167575384252e4, a5 = 2.3782041382114385731252e4,
+ a6 = 1.1819493347062294404278e4, a7 = 8.8709406962545514830200e2,
+
+ b0 = 1.0000000000000000000e0, b1 = 4.2313330701600911252e1,
+ b2 = 6.8718700749205790830e2, b3 = 5.3941960214247511077e3,
+ b4 = 2.1213794301586595867e4, b5 = 3.9307895800092710610e4,
+ b6 = 2.8729085735721942674e4, b7 = 5.2264952788528545610e3,
+
+ c0 = 1.42343711074968357734e0, c1 = 4.63033784615654529590e0,
+ c2 = 5.76949722146069140550e0, c3 = 3.64784832476320460504e0,
+ c4 = 1.27045825245236838258e0, c5 = 2.41780725177450611770e-1,
+ c6 = 2.27238449892691845833e-2, c7 = 7.74545014278341407640e-4,
+
+ d0 = 1.4142135623730950488016887e0, d1 = 2.9036514445419946173133295e0,
+ d2 = 2.3707661626024532365971225e0, d3 = 9.7547832001787427186894837e-1,
+ d4 = 2.0945065210512749128288442e-1, d5 = 2.1494160384252876777097297e-2,
+ d6 = 7.7441459065157709165577218e-4, d7 = 1.4859850019840355905497876e-9,
+
+ e0 = 6.65790464350110377720e0, e1 = 5.46378491116411436990e0,
+ e2 = 1.78482653991729133580e0, e3 = 2.96560571828504891230e-1,
+ e4 = 2.65321895265761230930e-2, e5 = 1.24266094738807843860e-3,
+ e6 = 2.71155556874348757815e-5, e7 = 2.01033439929228813265e-7,
+
+ f0 = 1.414213562373095048801689e0, f1 = 8.482908416595164588112026e-1,
+ f2 = 1.936480946950659106176712e-1, f3 = 2.103693768272068968719679e-2,
+ f4 = 1.112800997078859844711555e-3, f5 = 2.611088405080593625138020e-5,
+ f6 = 2.010321207683943062279931e-7, f7 = 2.891024605872965461538222e-15,
+
+ Ln2 = 0.693147180559945309417232121458176568075500134360255254120680009;
+
+ int s;
+ double r, z1, z2;
+
+ if(x < 0) {
+ s = -1;
+ x = -x;
+ } else {
+ s = +1;
+ }
+
+ if(x <= 0.85) {
+ r = 0.180625 - 0.25*x*x;
+ z1 = ((((((a7*r+a6)*r+a5)*r+a4)*r+a3)*r+a2)*r+a1)*r + a0;
+ z2 = ((((((b7*r+b6)*r+b5)*r+b4)*r+b3)*r+b2)*r+b1)*r + b0;
+ return s*(x*z1) / z2;
+ }
+ r = sqrt(Ln2 - log(1.0-x));
+ if(r <= 5.0) {
+ r -= 1.6;
+ z1 = ((((((c7*r+c6)*r+c5)*r+c4)*r+c3)*r+c2)*r+c1)*r + c0;
+ z2 = ((((((d7*r+d6)*r+d5)*r+d4)*r+d3)*r+d2)*r+d1)*r + d0;
+ } else {
+ r -= 5.0;
+ z1 = ((((((e7*r+e6)*r+e5)*r+e4)*r+e3)*r+e2)*r+e1)*r + e0;
+ z2 = ((((((f7*r+f6)*r+f5)*r+f4)*r+f3)*r+f2)*r+f1)*r + f0;
+ }
+
+ return s*z1/z2;
+}
+
+double
+rng·normal(void)
+{
+ double f;
+ f = rng·random();
+
+ return sqrt(2)*erfinv(2*f-1);
+}
diff --git a/src/base/rng/poisson.c b/src/base/rng/poisson.c
new file mode 100644
index 0000000..3ec15c9
--- /dev/null
+++ b/src/base/rng/poisson.c
@@ -0,0 +1,126 @@
+#include "internal.h"
+
+/*
+ * Ahrens, J. H., & Dieter, U. (1982).
+ * Computer Generation of Poisson Deviates from Modified Normal Distributions.
+ */
+static double factorial[10] = {1., 1., 2., 6., 24., 120., 720., 5040., 40320., 362880.};
+static double coeffs[9] = {
+ -.500000000, +.333333333, -.249999856,
+ +.200011780, -.166684875, +.142187833,
+ -.124196313, +.125005956, -.114265030,
+};
+
+static inline
+double
+log1pmx(double x, double off)
+{
+ int i;
+ double r, t;
+
+ if(-0.25 < x && x < 0.25) {
+ r = 0;
+ t = 1;
+ for(i=0;i<arrlen(coeffs);i++) {
+ r += coeffs[i]*t;
+ t *= x;
+ }
+
+ return x*x*r;
+ }
+ return log(1+x) - off;
+}
+
+static inline
+double
+procf(double mu, double s, int64 K, double *px, double *py, double *fx, double *fy)
+{
+ double d, V, X;
+ double w, b1, b2, c1, c2, c3, c0, c;
+
+ w = 0.3989422804014327/s;
+ b1 = 0.041666666666666664/mu;
+ b2 = 0.3*b1*b1;
+ c3 = 0.14285714285714285*b1*b2;
+ c2 = b2 - 15.*c3;
+ c1 = b1 - 6.*b2 + 45.*c3;
+ c0 = 1 - b1 + 3.*b2 - 15.*c3;
+ c = .1069/mu;
+
+ if(K < 10) {
+ *px = -mu;
+ *py = pow(mu,K) / factorial[K];
+ }else{
+ d = 0.08333333333333333/K;
+ d = d - 4.8*d*d*d;
+ V = (mu - K) / K;
+
+ *px = K*log1pmx(V,mu-K) - d;
+ *py = 0.3989422804014327/sqrt(K);
+ }
+
+ X = (K - mu + 0.5)/s;
+ *fx = -0.5*X*X;
+ *fy = w*(((c3*X*X + c2)*X*X + c1)*X*X + c0);
+
+ return c;
+}
+
+static inline
+uint64
+bigpoisson(double mu)
+{
+ int64 L,K;
+ double G,s,d,U,E,T;
+ double px,py,fx,fy,c;
+
+ s = sqrt(mu);
+ d = 6*mu*mu;
+ L = floor(mu - 1.1484);
+
+stepN:
+ G = mu + s*rng·normal();
+ K = floor(G);
+ if(K<0)
+ goto stepP;
+stepI:
+ if(K>=L)
+ return K;
+stepS:
+ U = rng·random();
+ if(d*U >= (mu-K)*(mu-K)*(mu-K))
+ return K;
+stepP:
+ if(G < 0)
+ goto stepE;
+stepQ:
+ c = procf(mu, s, K, &px, &py, &fx, &fy);
+stepE:
+ E = rng·exponential(1.0);
+ U = rng·random();
+ U = U + U - 1;
+ T = 1.8 + copysign(E,U);
+ if(T < 0.6744)
+ goto stepE;
+ K = floor(mu + s*T);
+ c = procf(mu, s, K, &px, &py, &fx, &fy);
+stepH:
+ if(c*fabs(U) > (py*exp(px + E) - fy*exp(fx + E)))
+ goto stepE;
+ return K;
+}
+
+uint64
+rng·poisson(double mean)
+{
+ int64 n;
+ double z;
+
+ if(mean<10.0) {
+ for(n=0, z=rng·exponential(1.0); z<mean; ++n, z+=rng·exponential(1.0))
+ ;
+ return n;
+ }
+
+ return bigpoisson(mean);
+}
diff --git a/src/base/rng/random.c b/src/base/rng/random.c
new file mode 100644
index 0000000..bd1bd6b
--- /dev/null
+++ b/src/base/rng/random.c
@@ -0,0 +1,33 @@
+#include "internal.h"
+
+static uint64
+xoshiro256ss(Rng *state)
+{
+ uint64 *s = state->s;
+ uint64 result = rol64(s[1] * 5, 7) * 9;
+ uint64 t = s[1] << 17;
+
+ s[2] ^= s[0];
+ s[3] ^= s[1];
+ s[1] ^= s[2];
+ s[0] ^= s[3];
+
+ s[2] ^= t;
+ s[3] = rol64(s[3], 45);
+
+ return result;
+}
+
+double
+rng·random(void)
+{
+ uint64 r = xoshiro256ss(&rng·RNG);
+ return (double)r / (double)UINT64_MAX;
+}
+
+uint64
+rng·randi(int max)
+{
+ uint64 r = xoshiro256ss(&rng·RNG);
+ return r % max;
+}
diff --git a/src/base/rng/rules.mk b/src/base/rng/rules.mk
new file mode 100644
index 0000000..407b1bf
--- /dev/null
+++ b/src/base/rng/rules.mk
@@ -0,0 +1,7 @@
+SRCS_$(d)+=\
+ $(d)/rng/base.c\
+ $(d)/rng/bernoulli.c\
+ $(d)/rng/exponential.c\
+ $(d)/rng/normal.c\
+ $(d)/rng/poisson.c\
+ $(d)/rng/random.c\
diff --git a/src/base/rules.mk b/src/base/rules.mk
new file mode 100644
index 0000000..847e4d8
--- /dev/null
+++ b/src/base/rules.mk
@@ -0,0 +1,37 @@
+include share/push.mk
+
+# Iterate through subdirectory tree
+
+# local sources
+SRCS_$(d):=\
+ $(d)/arg.c
+include $(d)/bufio/rules.mk
+include $(d)/coro/rules.mk
+include $(d)/error/rules.mk
+include $(d)/flate/rules.mk
+include $(d)/fs/rules.mk
+include $(d)/gz/rules.mk
+include $(d)/io/rules.mk
+include $(d)/mem/rules.mk
+include $(d)/mmap/rules.mk
+include $(d)/os/rules.mk
+include $(d)/rng/rules.mk
+include $(d)/sort/rules.mk
+include $(d)/string/rules.mk
+CHECK_$(d):=\
+ $(d)/test.c
+
+# outputs
+LIBS_$(d) := $(d)/base.a
+BINS_$(d) :=
+
+include share/paths.mk
+
+$(LIBS_$(d)): $(OBJS_$(d))
+ $(ARCHIVE)
+
+$(TEST_$(d)): TCLIBS := $(LIBS_$(d))
+$(TEST_$(d)): $(UNIT_$(d)) $(LIBS_$(d))
+ $(LINK)
+
+include share/pop.mk
diff --git a/src/base/sort/double.c b/src/base/sort/double.c
new file mode 100644
index 0000000..c3feac2
--- /dev/null
+++ b/src/base/sort/double.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·double(uintptr sz, double arr[])
+{
+ double tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/float.c b/src/base/sort/float.c
new file mode 100644
index 0000000..57bd482
--- /dev/null
+++ b/src/base/sort/float.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·float(uintptr sz, float arr[])
+{
+ float tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/int.c b/src/base/sort/int.c
new file mode 100644
index 0000000..33e1def
--- /dev/null
+++ b/src/base/sort/int.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·int(uintptr sz, int arr[])
+{
+ int tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/int16.c b/src/base/sort/int16.c
new file mode 100644
index 0000000..072a3eb
--- /dev/null
+++ b/src/base/sort/int16.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·int16(uintptr sz, int16 arr[])
+{
+ int16 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/int32.c b/src/base/sort/int32.c
new file mode 100644
index 0000000..27b3b7b
--- /dev/null
+++ b/src/base/sort/int32.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·int32(uintptr sz, int32 arr[])
+{
+ int32 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/int64.c b/src/base/sort/int64.c
new file mode 100644
index 0000000..b3fa5d4
--- /dev/null
+++ b/src/base/sort/int64.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·int64(uintptr sz, int64 arr[])
+{
+ int64 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/int8.c b/src/base/sort/int8.c
new file mode 100644
index 0000000..5848e6e
--- /dev/null
+++ b/src/base/sort/int8.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·int8(uintptr sz, int8 arr[])
+{
+ int8 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/internal.h b/src/base/sort/internal.h
new file mode 100644
index 0000000..ac569de
--- /dev/null
+++ b/src/base/sort/internal.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+#include <base/macro/qsort.h>
diff --git a/src/base/sort/rules.mk b/src/base/sort/rules.mk
new file mode 100644
index 0000000..780d6ea
--- /dev/null
+++ b/src/base/sort/rules.mk
@@ -0,0 +1,14 @@
+SRCS_$(d)+=\
+ $(d)/sort/double.c\
+ $(d)/sort/float.c\
+ $(d)/sort/int.c\
+ $(d)/sort/int16.c\
+ $(d)/sort/int32.c\
+ $(d)/sort/int64.c\
+ $(d)/sort/int8.c\
+ $(d)/sort/string.c\
+ $(d)/sort/uint.c\
+ $(d)/sort/uint16.c\
+ $(d)/sort/uint32.c\
+ $(d)/sort/uint64.c\
+ $(d)/sort/uint8.c\
diff --git a/src/base/sort/string.c b/src/base/sort/string.c
new file mode 100644
index 0000000..b511efa
--- /dev/null
+++ b/src/base/sort/string.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·string(uintptr sz, byte* arr[])
+{
+ byte *tmp;
+#define LESS(i, j) (strcmp(arr[i], arr[j]) < 0)
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/uint.c b/src/base/sort/uint.c
new file mode 100644
index 0000000..5b27330
--- /dev/null
+++ b/src/base/sort/uint.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·uint(uintptr sz, uint arr[])
+{
+ uint tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/uint16.c b/src/base/sort/uint16.c
new file mode 100644
index 0000000..2b635b4
--- /dev/null
+++ b/src/base/sort/uint16.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·uint16(uintptr sz, uint16 arr[])
+{
+ uint16 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/uint32.c b/src/base/sort/uint32.c
new file mode 100644
index 0000000..99a58cf
--- /dev/null
+++ b/src/base/sort/uint32.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·uint32(uintptr sz, uint32 arr[])
+{
+ uint32 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/uint64.c b/src/base/sort/uint64.c
new file mode 100644
index 0000000..2769825
--- /dev/null
+++ b/src/base/sort/uint64.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·uint64(uintptr sz, uint64 arr[])
+{
+ uint64 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/sort/uint8.c b/src/base/sort/uint8.c
new file mode 100644
index 0000000..ff02b3c
--- /dev/null
+++ b/src/base/sort/uint8.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+void
+sort·uint8(uintptr sz, uint8 arr[])
+{
+ uint8 tmp;
+#define LESS(i, j) (arr[i] < arr[j])
+#define SWAP(i, j) (tmp = arr[i], arr[i] = arr[j], arr[j] = tmp)
+ QSORT(sz, LESS, SWAP);
+#undef SWAP
+#undef LESS
+}
diff --git a/src/base/string/append.c b/src/base/string/append.c
new file mode 100644
index 0000000..d4d0396
--- /dev/null
+++ b/src/base/string/append.c
@@ -0,0 +1,53 @@
+#include "internal.h"
+
+// append will append the given null terminated c string to the string data
+// structure. this variant can append a substring of length len of the given
+// string to our buffer. the result is reallocated if not enough room is present
+// in the buffer.
+int
+str·appendlen(string *s, vlong n, const byte* b)
+{
+ /*
+ bl = strlen(b);
+ if (n > bl) panicf("attempted to make a substring longer than string");
+ */
+
+ str·grow(s, n);
+ if(*s == nil)
+ return 0;
+
+ Hdr* h = (Hdr*)(*s - sizeof(Hdr));
+
+ memcpy(*s + str·len(*s), b, n);
+ h->len += n;
+ (*s)[h->len] = '\0';
+
+ return n;
+}
+
+// append will append the given null terminated c string to the string data
+// structure. this variant will append the entire string.
+int
+str·append(string *s, const byte* b)
+{
+ return str·appendlen(s, strlen(b), b);
+}
+
+// appendbyte will append the given byte to our string.
+// NOTE: as the byte is on the stack, it is not null-terminated.
+// can not pass to the above functions.
+int
+str·appendbyte(string *s, const byte b)
+{
+ str·grow(s, 1);
+ if(*s == nil)
+ return 0;
+
+ Hdr* h = (Hdr*)(*s - sizeof(Hdr));
+
+ *(*s + str·len(*s)) = b;
+ h->len++;
+ (*s)[h->len] = '\0'; // NOTE: I don't think an explicit zero is required..?
+
+ return 1;
+}
diff --git a/src/base/string/appendf.c b/src/base/string/appendf.c
new file mode 100644
index 0000000..4b8d76c
--- /dev/null
+++ b/src/base/string/appendf.c
@@ -0,0 +1,31 @@
+#include "internal.h"
+
+/*
+ * appendf will append the given formatted string to our buffer.
+ * returns the newly minted string
+ */
+
+int
+str·appendf(string *s, const byte* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ int remain = str·cap(*s) - str·len(*s);
+ int n = vsnprintf(*s + str·len(*s), remain + 1, fmt, args);
+ va_end(args);
+
+ if(n > remain){
+ // If the first write was incomplete, we overwite the data again.
+ str·grow(s, n);
+ va_list args;
+ va_start(args, fmt);
+ n = vsnprintf(*s + str·len(*s), n + 1, fmt, args);
+ assert(n - remain <= str·cap(*s));
+ va_end(args);
+ }
+
+ Hdr* h = (Hdr*)(*s - sizeof(Hdr));
+ h->len += n;
+
+ return n;
+}
diff --git a/src/base/string/clear.c b/src/base/string/clear.c
new file mode 100644
index 0000000..986f809
--- /dev/null
+++ b/src/base/string/clear.c
@@ -0,0 +1,9 @@
+#include "internal.h"
+
+void
+str·clear(string *s)
+{
+ Hdr* h = (Hdr*)(*s - sizeof(Hdr));
+ h->len = 0;
+ *s[0] = '\0';
+}
diff --git a/src/base/string/copyn.c b/src/base/string/copyn.c
new file mode 100644
index 0000000..09c2879
--- /dev/null
+++ b/src/base/string/copyn.c
@@ -0,0 +1,11 @@
+#include "internal.h"
+
+char *
+str·copyn(char *dst, char *src, int n)
+{
+ while(*src && n-- > 0)
+ *dst++ = *src++;
+
+ *dst = 0;
+ return dst;
+}
diff --git a/src/base/string/equals.c b/src/base/string/equals.c
new file mode 100644
index 0000000..a975cf5
--- /dev/null
+++ b/src/base/string/equals.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+// Equals returns true if string s and t are equivalent.
+bool
+str·equals(const string s, const string t)
+{
+ vlong sL = str·len(s);
+ vlong tL = str·len(t);
+ if (sL != tL) return false;
+
+ return memcmp(s, t, sL) == 0;
+}
diff --git a/src/base/string/find.c b/src/base/string/find.c
new file mode 100644
index 0000000..20f990e
--- /dev/null
+++ b/src/base/string/find.c
@@ -0,0 +1,11 @@
+#include "internal.h"
+
+// find will find the first occurence of
+// substr in the string returns -1 if nothing was found.
+int
+str·find(string s, const byte* substr)
+{
+ byte* loc = strstr(s, substr);
+ if (loc == nil) return -1;
+ return (int)(loc - s);
+}
diff --git a/src/base/string/fit.c b/src/base/string/fit.c
new file mode 100644
index 0000000..56ab041
--- /dev/null
+++ b/src/base/string/fit.c
@@ -0,0 +1,20 @@
+#include "internal.h"
+
+// fit reallocates the string such that the buffer is exactly sized for the
+// buffer. if the capacity equals the length, then the function is a noop. the
+// byte array is unchanged.
+void
+str·fit(string *s)
+{
+ Hdr* h;
+ vlong cap = str·cap(*s);
+ vlong len = str·len(*s);
+
+ if (cap == len) return;
+
+ h = (Hdr*)(s - sizeof(Hdr));
+ h = realloc(h, sizeof(*h) + len + 1);
+ h->cap = len;
+
+ *s = h->buf;
+}
diff --git a/src/base/string/free.c b/src/base/string/free.c
new file mode 100644
index 0000000..7b5ee98
--- /dev/null
+++ b/src/base/string/free.c
@@ -0,0 +1,8 @@
+#include "internal.h"
+
+// free returns memory associated to the buffer.
+void
+str·free(string s)
+{
+ free(s - sizeof(Hdr));
+}
diff --git a/src/base/string/grow.c b/src/base/string/grow.c
new file mode 100644
index 0000000..39a9d2f
--- /dev/null
+++ b/src/base/string/grow.c
@@ -0,0 +1,33 @@
+#include "internal.h"
+
+// grow ensures that the string can encompass at least delta bytes.
+// if it already can, this is a no op.
+// if it can't, the string will be reallocated.
+void
+str·grow(string *s, vlong delta)
+{
+ Hdr *h, *newh;
+ vlong cap = str·cap(*s);
+ vlong len = str·len(*s);
+ assert(cap >= len); // To prevent unsigned behavior
+
+ if (cap - len >= delta) return;
+
+ h = (Hdr*)(*s - sizeof(Hdr));
+
+ vlong newCap = cap + delta;
+ assert(newCap >= cap); // To prevent unsigned behavior
+ if (newCap < MAX_STRING_ALLOC) {
+ newCap *= 2;
+ } else
+ newCap += MAX_STRING_ALLOC;
+
+ newh = (Hdr*)realloc(h, sizeof(*h) + newCap + 1);
+ if (newh == nil) return;
+
+ memset(newh->buf + len, '\0', newCap - len);
+ newh->cap = newCap;
+ newh->len = len;
+
+ *s = newh->buf;
+}
diff --git a/src/base/string/internal.h b/src/base/string/internal.h
new file mode 100644
index 0000000..8c16c64
--- /dev/null
+++ b/src/base/string/internal.h
@@ -0,0 +1,12 @@
+#pragma once
+#include <u.h>
+#include <base.h>
+
+#define MAX_STRING_ALLOC 1024 * 1024
+
+typedef struct Hdr
+{
+ vlong len;
+ vlong cap;
+ byte buf[];
+} Hdr;
diff --git a/src/base/string/join.c b/src/base/string/join.c
new file mode 100644
index 0000000..fb97b6c
--- /dev/null
+++ b/src/base/string/join.c
@@ -0,0 +1,16 @@
+#include "internal.h"
+
+string
+str·join(vlong len, byte** fields, const byte* sep)
+{
+ string s = str·makecap("", 0, 10);
+ int j = 0;
+
+ for (j = 0; j < len; j++) {
+ str·append(&s, fields[j]);
+ if (j < len - 1)
+ str·appendlen(&s, 1, sep);
+ }
+
+ return s;
+}
diff --git a/src/base/string/len.c b/src/base/string/len.c
new file mode 100644
index 0000000..5e42919
--- /dev/null
+++ b/src/base/string/len.c
@@ -0,0 +1,17 @@
+#include "internal.h"
+
+// len returns the length of the string.
+int
+str·len(const string s)
+{
+ Hdr* h = (Hdr*)(s - sizeof(Hdr));
+ return h->len;
+}
+
+// cap returns the capacity of the string buffer.
+int
+str·cap(const string s)
+{
+ Hdr* h = (Hdr*)(s - sizeof(Hdr));
+ return h->cap;
+}
diff --git a/src/base/string/lower.c b/src/base/string/lower.c
new file mode 100644
index 0000000..c6935f8
--- /dev/null
+++ b/src/base/string/lower.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+// lower will force all runes in the string to be lowercase
+void
+str·lower(string s)
+{
+ byte *b, *e;
+ b = s;
+ e = b + str·len(s);
+ while (b++ != e)
+ *b = tolower(*b);
+}
diff --git a/src/base/string/make.c b/src/base/string/make.c
new file mode 100644
index 0000000..eb71543
--- /dev/null
+++ b/src/base/string/make.c
@@ -0,0 +1,53 @@
+#include "internal.h"
+
+// new returns a new dynamic string object, initialized from the given c string.
+// len defines the length of the c substring that we will copy into our buffer.
+// the backing buffer will have capacity cap.
+string
+str·makecap(const byte *s, vlong len, vlong cap)
+{
+ struct Hdr* h;
+
+ h = malloc(sizeof(*h) + cap + 1);
+ if (s == nil) memset(h, 0, sizeof(*h));
+
+ if (h == nil) return nil; // Allocation failed.
+
+ h->len = (s == nil) ? 0 : len;
+ h->cap = cap;
+
+ if (cap < h->len) goto cleanup;
+
+ if (s != nil && cap > 0) {
+ memcpy(h->buf, s, h->len);
+ memset(h->buf + h->len, '\0', h->cap - h->len + 1);
+ }
+
+ return h->buf;
+
+cleanup:
+ free(h);
+ panicf("Attempted to create a string with less capacity than length");
+ return nil;
+}
+
+// new returns a new dynamic string object, initialized from the given c string.
+// the backing buffer capacity is equivalent to the string length.
+string
+str·makelen(const byte *s, vlong len)
+{
+ vlong sl = (!s) ? 0 : strlen(s);
+ if (sl < len) panicf("attempted to take a bigger substring than string length");
+
+ vlong cap = (len == 0) ? 1 : len;
+ return str·makecap(s, len, cap);
+}
+
+// new returns a new dynamic string object, initialized from the given c string.
+// the backing buffer capacity is equivalent to the string length.
+string
+str·make(const byte *s)
+{
+ vlong len = (!s) ? 0 : strlen(s);
+ return str·makelen(s, len);
+}
diff --git a/src/base/string/makef.c b/src/base/string/makef.c
new file mode 100644
index 0000000..8fb9c38
--- /dev/null
+++ b/src/base/string/makef.c
@@ -0,0 +1,25 @@
+#include "internal.h"
+
+// Newf returns a new dynamic string object
+string
+str·makef(const byte *fmt, ...)
+{
+ vlong n;
+ string s;
+ va_list args;
+
+ va_start(args, fmt);
+ n = vsnprintf(nil, 0, fmt, args);
+ va_end(args);
+
+ s = str·makecap(nil, 0, n);
+
+ va_start(args, fmt);
+ vsnprintf(s, n + 1, fmt, args);
+ va_end(args);
+
+ Hdr* h = (Hdr*)(s - sizeof(Hdr));
+ h->len = n;
+
+ return s;
+}
diff --git a/src/base/string/read.c b/src/base/string/read.c
new file mode 100644
index 0000000..df2028f
--- /dev/null
+++ b/src/base/string/read.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+int
+str·read(string s, int size, int n, void *buf)
+{
+ int len;
+
+ len = MIN(n * size, str·len(s));
+ memcpy(buf, s, len);
+
+ return len;
+}
diff --git a/src/base/string/replace.c b/src/base/string/replace.c
new file mode 100644
index 0000000..127daed
--- /dev/null
+++ b/src/base/string/replace.c
@@ -0,0 +1,26 @@
+#include "internal.h"
+
+// replace will replace all occurences of the given bytes 'from' to bytes 'to'
+// edits are done in place and modify the string.
+// NOTE: as of now strings from and to must be the same size.
+void
+str·replace(string s, const byte* from, const byte* to)
+{
+ vlong fromL = strlen(from);
+ vlong toL = strlen(to);
+ if (toL != fromL) { panicf("different sized replacement string not supported"); }
+
+ vlong l = str·len(s);
+ vlong i = l;
+ vlong j = l;
+
+ for (i = 0; i < l; i++) {
+ for (j = 0; j < toL; j++) {
+ if (s[i] == from[j]) {
+ s[i] = to[j];
+ break;
+ }
+ }
+ }
+}
+
diff --git a/src/base/string/rules.mk b/src/base/string/rules.mk
new file mode 100644
index 0000000..e517ca5
--- /dev/null
+++ b/src/base/string/rules.mk
@@ -0,0 +1,19 @@
+SRCS_$(d)+=\
+ $(d)/string/append.c\
+ $(d)/string/appendf.c\
+ $(d)/string/clear.c\
+ $(d)/string/copyn.c\
+ $(d)/string/equals.c\
+ $(d)/string/find.c\
+ $(d)/string/fit.c\
+ $(d)/string/free.c\
+ $(d)/string/grow.c\
+ $(d)/string/join.c\
+ $(d)/string/len.c\
+ $(d)/string/lower.c\
+ $(d)/string/make.c\
+ $(d)/string/makef.c\
+ $(d)/string/read.c\
+ $(d)/string/replace.c\
+ $(d)/string/split.c\
+ $(d)/string/upper.c\
diff --git a/src/base/string/split.c b/src/base/string/split.c
new file mode 100644
index 0000000..2aa68b4
--- /dev/null
+++ b/src/base/string/split.c
@@ -0,0 +1,39 @@
+#include "internal.h"
+
+// split will split the string by the given token.
+// returns a stretchy buffer of strings that result from the partition.
+// it is the caller's responsibility to clean the memory.
+string*
+str·split(string s, const byte* tok)
+{
+ string* fields = nil;
+ vlong start = 0;
+
+ vlong sL = str·len(s);
+ vlong tokL = strlen(tok);
+ if (sL == 0 || tokL == 0) return nil;
+
+ buffit(fields, 5);
+
+ for (vlong i = 0; i < sL - tokL; i++) {
+ if ((tokL == 1 && s[i] == tokL) || !memcmp(s + i, tok, tokL)) {
+ bufpush(fields, str·makelen(s + start, i - start));
+ if (fields[buflen(fields) - 1] == nil) goto cleanup;
+
+ start = i + tokL;
+ i += tokL - 1;
+ }
+ }
+
+ bufpush(fields, str·makelen(s + start, sL - start));
+
+ return fields;
+
+cleanup:
+ for (vlong i = 0; i < buflen(fields); i++) {
+ str·free(fields[i]);
+ }
+ buffree(fields);
+ return nil;
+}
+
diff --git a/src/base/string/upper.c b/src/base/string/upper.c
new file mode 100644
index 0000000..ab692c1
--- /dev/null
+++ b/src/base/string/upper.c
@@ -0,0 +1,12 @@
+#include "internal.h"
+
+// Upper will force all runes in the string to be uppercase.
+void
+str·upper(string s)
+{
+ byte *b, *e;
+ b = s;
+ e = b + str·len(s);
+ while (b++ != e)
+ *b = toupper(*b);
+}
diff --git a/src/base/test.c b/src/base/test.c
new file mode 100644
index 0000000..a29be1d
--- /dev/null
+++ b/src/base/test.c
@@ -0,0 +1,170 @@
+#include <u.h>
+#include <base.h>
+#include <base/macro/map.h>
+
+#include <time.h>
+
+uintptr
+printtest(Coro *c, uintptr d)
+{
+ printf("--> Recieved %lu\n", d);
+ d = coro·yield(c, d+10);
+ printf("--> Now %lu\n", d);
+
+ return d;
+}
+
+uintptr
+sequence(Coro *c, uintptr start)
+{
+ int d = start;
+ for (;;) {
+ coro·yield(c, d++);
+ }
+
+ return d;
+}
+
+struct PrimeMsg
+{
+ Coro *seq;
+ int p;
+};
+
+uintptr
+filter(Coro *c, uintptr data)
+{
+ int x, p;
+ Coro *seq;
+ struct PrimeMsg *msg;
+
+ // Need to copy relevant variables onto the local stack
+ // Data is volatile.
+ msg = (struct PrimeMsg*)data;
+ seq = msg->seq;
+ p = msg->p;
+
+ for (;;) {
+ x = coro·yield(seq, x);
+ if (x % p != 0) {
+ x = coro·yield(c, x);
+ }
+ }
+
+ return 0;
+}
+
+error
+test·coro()
+{
+ int i;
+ Coro *c[4];
+ uintptr d;
+
+ printf("Starting singleton test\n");
+
+ for (i = 0; i < arrlen(c); i++) {
+ c[i] = coro·make(0, &printtest);
+ }
+
+ /* Singleton test */
+ d = 0;
+ for (i = 0; i < 10; i++) {
+ d = coro·yield(c[0], d);
+ }
+
+ printf("Starting triplet test\n");
+
+ /* Triplet test */
+ for (i = 0; i < 10; i++) {
+ d = coro·yield(c[1], d);
+ d = coro·yield(c[2], d+100);
+ d = coro·yield(c[3], d+200);
+ }
+
+ for (i = 0; i < arrlen(c); i++) {
+ coro·free(c[i]);
+ }
+
+ /* Prime sieve */
+ printf("Starting prime test\n");
+ uintptr num;
+ Coro *cur, *seq[50];
+
+ num = 2;
+ seq[0] = coro·make(4096, &sequence);
+ cur = *seq;
+
+ num = coro·yield(cur, num);
+ for (i = 1; i < arrlen(seq); i++) {
+ seq[i] = coro·make(4096, &filter);
+ struct PrimeMsg msg = {
+ .seq = cur,
+ .p = num,
+ };
+ cur = seq[i];
+ num = coro·yield(cur, (uintptr)&msg);
+ printf("--> prime number %lu\n", num);
+ }
+ return 0;
+}
+
+int
+less(void* a, void* b)
+{
+ int ai, bi;
+ ai = *(int*)a;
+ bi = *(int*)b;
+
+ return ai - bi;
+}
+
+error
+test·sort()
+{
+ clock_t t;
+ int i, test[10000];
+ for (i = 0; i < arrlen(test); i++) {
+ test[i] = rand();
+ }
+
+ t = clock();
+ sort·int(arrlen(test), test);
+ t = clock() - t;
+ printf("inlined code took %f ms to execute\n", 1000.*t/CLOCKS_PER_SEC);
+
+ for (i = 0; i < arrlen(test); i++) {
+ test[i] = rand();
+ }
+
+ t = clock();
+ qsort(test, arrlen(test), sizeof(int), (int (*)(const void *, const void *))less);
+ t = clock() - t;
+ printf("std qsort code took %f ms to execute\n", 1000.*t/CLOCKS_PER_SEC);
+
+ /*
+ for (i = 1; i < arrlen(test); i++) {
+ if (test[i] >= test[i-1]) {
+ printf("%d is less that %d\n", test[i], test[i-1]);
+ } else {
+ printf("ERROR: %d is NOT less that %d\n", test[i], test[i-1]);
+ }
+ }
+ */
+
+ return 0;
+}
+
+error
+main()
+{
+ error err;
+#if 0
+ if (err = test·coro(), err) {
+ errorf("test fail: coroutine");
+ }
+#endif
+ if (err = test·sort(), err) {
+ errorf("test fail: coroutine");
+ }
+}
diff --git a/src/cmd/cc/ast.c b/src/cmd/cc/ast.c
new file mode 100644
index 0000000..4330bcc
--- /dev/null
+++ b/src/cmd/cc/ast.c
@@ -0,0 +1,2139 @@
+#include "cc.h"
+
+// -----------------------------------------------------------------------
+// helper macros
+
+#define alloc(ptr) (ptr) = mem·arenaalloc(C.heap, 1, sizeof *(ptr))
+#define copyarray(dst, arr, n) (dst) = mem·arenaalloc(C.heap, (n), sizeof *(arr)), memcpy((dst), (arr), n * sizeof *(arr))
+#define movearray(dst, arr, n) copyarray(dst,arr,n), free(arr)
+
+#define attop(prs) ((uintptr)prs->sp == (uintptr)prs->spstk)
+#define peek(p, i) (p->tok[i])
+#define iskw(t, k) (((t).kind == Akeywd) && (t).val.i == (k))
+#define advance(p, l) (p->tok[0] = p->tok[1], p->tok[1] = lex(l), p->tok[0])
+
+#define Bit(i) (1 << (i))
+
+// -----------------------------------------------------------------------
+// helper functions
+
+static
+string
+nameof(Name *n)
+{
+ switch (n->kind) {
+ /* 0 corresponds to no state - i.e. an abstract name */
+ case Nnil:
+ return nil;
+ case Nident:
+ return n->ident;
+ case Nparen:
+ return nameof(n->paren->name);
+ case Nindex:
+ case Ncall:
+ return nameof(n->sfx.name);
+ }
+ panicf("unreachable");
+ return nil;
+}
+
+static
+void
+openscope(Parser *p)
+{
+ if (++p->sp >= arrend(p->spstk))
+ panicf("scope stack overflow");
+}
+
+/*
+ * TODO: save the symbol table with the ast node
+ * write a "copy(move)scope"
+ */
+
+static
+void
+closescope(Parser *p)
+{
+ if (p->sp <= p->spstk)
+ panicf("scope stack underflow");
+
+ forgetall(&p->sp->objs);
+ forgetall(&p->sp->tags);
+ p->sp--;
+}
+
+/* temporary stack helpers */
+static
+Name*
+getname(Parser *p)
+{
+ if (p->nm >= arrend(p->nmstk))
+ panicf("name stack overflow");
+ return p->nm++;
+}
+
+static void putdtor(Parser *p, Dtor *dt);
+
+static
+void
+putname(Parser *p, Name *n)
+{
+ if (p->nm <= p->nmstk)
+ panicf("name stack underflow");
+
+ switch (n->kind) {
+ case Nnil:
+ case Nident:
+ break;
+ case Nparen:
+ putdtor(p, n->paren);
+ break;
+ case Nindex:
+ case Ncall:
+ putname(p, n->sfx.name);
+ break;
+ default:
+ panicf("unrecognized name kind");
+ }
+ *p->nm-- = (Name){0};
+}
+
+static
+Ptr*
+getptr(Parser *p)
+{
+ if (p->pt >= arrend(p->ptstk))
+ panicf("pointer stack overflow");
+
+ return p->pt++;
+}
+
+static
+void
+putptr(Parser *p, Ptr *ptr)
+{
+ if (p->pt <= p->ptstk)
+ panicf("pointer stack underflow");
+
+ while ((ptr = ptr->link))
+ putptr(p, ptr);
+
+ *p->pt-- = (Ptr){0};
+}
+
+
+static
+Dtor*
+getdtor(Parser *p)
+{
+ if (p->dt >= arrend(p->dtstk))
+ panicf("dtor stack overflow");
+
+ p->dt->name = getname(p);
+ return p->dt++;
+}
+
+static
+void
+putdtor(Parser *p, Dtor *dt)
+{
+ if (p->dt <= p->dtstk)
+ panicf("dtor stack underflow");
+
+ /* release the pointer overflow if we had to use it */
+ if (p->dt->ptr.link)
+ putptr(p, p->dt->ptr.link);
+
+ /* the dtor could encompass multiple names hierarchically */
+ putname(p, dt->name);
+ *p->dt-- = (Dtor){0};
+}
+
+/* TODO: This will fail for forward declarations */
+static
+void
+declareobj(Parser *p, Decl *d)
+{
+ Sym *sym;
+ string ident;
+ uint32 kind;
+ struct Decls *link;
+
+ switch (d->kind) {
+ case Dfunc:
+ case Dvar:
+ kind = Svar;
+ goto one;
+ case Dtype:
+ kind = Stype;
+ one:
+ ident = d->name;
+ break;
+
+ case Dvars:
+ kind = Svar;
+ goto many;
+ case Dtypes:
+ kind = Stype;
+ many:
+ while (link = &d->list, link != nil) {
+ ident = link->name;
+ sym = lookup(&p->sp->objs, ident);
+ if (sym) {
+ errorat(peek(p, 0).pos, "redeclaration of name '%s' in object space", ident);
+ return;
+ }
+ sym = define(&p->sp->objs, ident, kind);
+ if (kind == Svar)
+ sym->obj = d;
+ else
+ sym->type = d->type;
+ }
+ break;
+
+ default:
+ panicf("unrecognized node kind %d. expected declaration", d->kind);
+ }
+ sym = lookup(&p->sp->objs, ident);
+ if (sym) {
+ errorat(peek(p, 0).pos, "redeclaration of name '%s' in object space", ident);
+ return;
+ }
+ sym = define(&p->sp->objs, ident, kind);
+ if (kind == Svar)
+ sym->obj = d;
+ else
+ sym->type = d->type;
+}
+
+/* enters the object identifier space */
+static
+void
+declareenum(Parser *p, int n, string *elts, Expr *vals)
+{
+ int i;
+ Sym *s;
+
+ for (i = 0; i < n; i++) {
+ s = lookup(&p->sp->objs, elts[i]);
+ if (s) {
+ errorat(peek(p, 0).pos, "redeclaration of name %s in object space", elts[i]);
+ continue;
+ }
+ s = define(&p->sp->objs, elts[i], Senum);
+ s->val = vals + i;
+ }
+}
+
+static
+void
+declaretag(Parser *p, uint32 t, string name)
+{
+ Sym *sym;
+ sym = lookup(&p->sp->tags, name);
+ if (sym) {
+ errorat(peek(p, 0).pos, "redeclaration of name '%s' in tag space", name);
+ return;
+ }
+
+ sym = define(&p->sp->tags, name, Stype);
+ sym->type = t;
+}
+
+static
+Sym *
+lookupobj(Parser *p, string ident)
+{
+ Sym *sym;
+ Scope *it;
+
+ it = p->sp;
+ do {
+ sym = lookup(&it->objs, ident);
+ } while (sym == nil && --it >= p->spstk);
+
+ return sym;
+}
+
+static
+Sym *
+lookuptag(Parser *p, string ident)
+{
+ Sym *sym;
+ Scope *it;
+
+ it = p->sp;
+ do {
+ sym = lookup(&it->tags, ident);
+ } while (sym == nil && --it >= p->spstk);
+
+ return sym;
+}
+
+static
+int
+nomatch(Token t, vlong kind)
+{
+ if (t.kind == kind)
+ return 0;
+
+ if (t.kind == Akeywd)
+ errorat(t.pos, "expected token '%s', instead found keyword '%s'", tokens[kind], keywords[t.val.i]);
+ else
+ errorat(t.pos, "expected token '%s', instead found '%s'", tokens[kind], tokens[t.kind]);
+ return 1;
+}
+
+// -----------------------------------------------------------------------
+// needed forward declarations
+
+static error spec(Parser *, Lexer *, uint64 *);
+static uint32 basetype(Parser *, Lexer *, uint64 *s);
+static string namedecl(Parser *, Lexer *, uint32 *, int);
+static uint32 typename(Parser *, Lexer *, uint32 *);
+
+static error dtor(Parser *p, Lexer *lx, Dtor *d, int ab);
+static uint32 typeofdtor(Dtor *, uint32);
+
+
+static Decl *decl(Parser *, Lexer *);
+
+static Expr *ternary(Parser *, Lexer *);
+static Expr *expr(Parser *, Lexer *);
+
+static error blkstmt(Parser *, Lexer *, Stmt **);
+
+
+// -----------------------------------------------------------------------
+// expressions
+
+#define MAKEX(x, state) alloc((x)), (x)->kind = X##state
+
+static
+Expr*
+primary(Parser *p, Lexer *lx)
+{
+ int k;
+ Expr *x;
+ Token t;
+ Pos b;
+
+ t = peek(p, 0);
+ b = t.pos;
+ switch (k = (t.kind & Vmask)) {
+ case Aident:
+ MAKEX(x, ident);
+ x->pos.beg = b;
+ x->pos.end = lx->pos;
+ x->name = t.val.s;
+ break;
+
+ case Alit:
+ MAKEX(x, lit);
+ x->pos.beg = b;
+ x->pos.end = lx->pos;
+ x->val.kind = t.kind & ~Vmask;
+ x->val.v = t.val;
+ break;
+
+ case Alparen:
+ advance(p, lx);
+ x = expr(p, lx);
+ t = peek(p, 0);
+ if (nomatch(t, Arparen)) {
+ errorat(lx->pos, "unterminated paren expression");
+ goto Bad;
+ }
+ break;
+
+ default:
+ panicf("unreachable");
+ }
+
+ advance(p, lx);
+ return x;
+Bad:
+ errorat(lx->pos, "unable to parse operand expression");
+ return nil;
+}
+
+static
+int
+istypename(Parser *p, Token t)
+{
+ Sym *sym;
+
+ if (t.kind == Akeywd && (Kconst <= t.val.i && t.val.i <= Kenum))
+ return 1;
+ if (t.kind == Aident) {
+ sym = lookupobj(p, t.val.s);
+ return (sym != nil) && sym->kind == Stype;
+ }
+
+ return 0;
+}
+
+static Expr* initx(Parser *p, Lexer *lx);
+
+static
+Expr*
+initlist(Parser *p, Lexer *lx)
+{
+ Token t;
+ int c, n;
+ Expr *x, **a;
+ struct Key *k;
+
+ MAKEX(x, initlist);
+ x->pos.beg = lx->pos;
+ x->init.n = 0;
+ if (t.kind == Arbrace) {
+ x->init.k = nil;
+ x->init.v = nil;
+ return x;
+ }
+
+ c = 0;
+ n = 0;
+ a = nil;
+ k = nil;
+Key0:
+ if (n >= c) {
+ c += 20;
+ k = realloc(k, c * sizeof(*k));
+ a = realloc(a, c * sizeof(*a));
+ }
+Key1:
+ switch (t.kind) {
+ case Adot:
+ t = advance(p, lx);
+ if (t.kind != Aident) {
+ errorat(t.pos, "dot designator must be followed by identifier");
+ goto Bad;
+ }
+ k[n++] = (struct Key) {
+ .kind = (uint32)x->init.n,
+ .s = t.val.s,
+ };
+ t = advance(p, lx);
+ goto Key0;
+
+ case Albrakt:
+ t = advance(p, lx);
+ k[n++] = (struct Key) {
+ .kind = (uint32)x->init.n | (1ULL << 32),
+ .x = expr(p, lx),
+ };
+ t = peek(p, 0);
+ goto Key0;
+
+ case Aeq:
+ t = advance(p, lx);
+ /* fallthrough */
+ default:
+ a[x->init.n++] = initx(p, lx);
+
+ t = peek(p, 0);
+ switch (t.kind) {
+ case Arbrace:
+ break;
+ case Acomma:
+ advance(p, lx);
+ /* fallthrough */
+ default:
+ goto Key0;
+ }
+ break;
+
+ case Acomma:
+ t = advance(p, lx);
+ break;
+ }
+ movearray(x->init.k, k, n);
+ movearray(x->init.v, a, x->init.n);
+ return x;
+Bad:
+ errorat(t.pos, "could not parse initializer list");
+ return nil;
+}
+
+static
+Expr*
+initx(Parser *p, Lexer *lx)
+{
+ Expr *x;
+ Token t;
+
+ t = peek(p, 0);
+ if (t.kind != Albrace)
+ return ternary(p, lx);
+
+ advance(p, lx);
+ x = initlist(p, lx);
+ t = peek(p, 0);
+ if (nomatch(t, Arbrace)) {
+ errorat(t.pos, "unmatched brace in initializer list, found %s instead", tokens[t.kind]);
+ advance(p, lx);
+ }
+
+ return x;
+}
+
+static
+Expr*
+postfix(Parser *p, Lexer *lx)
+{
+ Pos b;
+ Token t;
+ int c, n;
+ uint32 type, qual;
+ Expr *x, *y, **a;
+
+ t = peek(p, 0);
+ if (t.kind == Alparen)
+ if (istypename(p, peek(p, 1))) {
+ t = advance(p, lx);
+ type = typename(p, lx, &qual);
+ t = peek(p, 0);
+ if (nomatch(t, Arparen)) {
+ errorat(lx->pos, "unmatched paren: found %s instead", tokens[t.kind]);
+ goto Bad;
+ }
+ t = advance(p, lx);
+ if (nomatch(t, Albrace)) {
+ errorat(lx->pos, "bad initializer list: found %s", tokens[t.kind]);
+ goto Bad;
+ }
+
+ x = initlist(p, lx);
+
+ t = peek(p, 0);
+ if (nomatch(t, Arbrace)) {
+ errorat(lx->pos, "unmatched brace: found %s instead", tokens[t.kind]);
+ goto Bad;
+ }
+
+ x->type = type;
+ x->qual = qual;
+ return x;
+ }
+
+ x = primary(p, lx);
+ t = peek(p, 0);
+ for (;;) {
+ b = x->pos.beg;
+ switch (t.kind) {
+ case Ainc:
+ MAKEX(y, postinc);
+ goto Postfix;
+ case Adec:
+ MAKEX(y, postdec);
+ Postfix:
+ y->pos.beg = b;
+ y->pos.end = lx->pos;
+ y->unary.post = x;
+ x = y, y = nil;
+ break;
+
+ case Adot:
+ MAKEX(y, self);
+ goto Select;
+ case Aarrow:
+ MAKEX(y, selp);
+ Select:
+ t = advance(p, lx);
+ if (t.kind != Aident) {
+ errorat(t.pos, "invalid operand of selector expression");
+ goto Bad;
+ }
+ y->pos.beg = b;
+ y->pos.end = lx->pos;
+
+ y->idx.f = t.val.s;
+ y->idx.x = x;
+ x = y, y = nil;
+ break;
+
+ case Albrakt:
+ t = advance(p, lx);
+ if (t.kind == Arbrakt) {
+ errorat(t.pos, "empty index expression");
+ goto Bad;
+ }
+ MAKEX(y, index);
+ y->idx.x = x;
+ y->idx.i = expr(p, lx);
+
+ t = peek(p, 0);
+ if (t.kind != Albrakt) {
+ errorat(t.pos, "malformed index expression");
+ goto Bad;
+ }
+
+ x = y, y = nil;
+ break;
+
+ case Alparen:
+ t = advance(p, lx);
+ MAKEX(y, call);
+ y->call.fn = x;
+ y->pos.beg = b;
+ y->call.n = 0;
+ if (t.kind == Arparen) {
+ y->call.arg = nil;
+ goto Endfunc;
+ }
+ c = 0;
+ a = nil;
+ Arg:
+ if (y->call.n >= c) {
+ c += 20;
+ a = realloc(a, c * sizeof(*a));
+ }
+ a[y->call.n++] = expr(p, lx);
+ t = peek(p, 0);
+ if (t.kind == Acomma) {
+ advance(p, lx);
+ goto Arg;
+ }
+ if (t.kind != Arparen) {
+ errorat(t.pos, "invalid token '%s' found in call argument");
+ goto Bad;
+ }
+ movearray(y->call.arg, a, y->call.n);
+ Endfunc:
+ y->pos.end = lx->pos;
+ x = y, y = nil;
+ break;
+
+ default:
+ return x;
+ }
+ t = advance(p, lx);
+ }
+ return x;
+Bad:
+ errorat(lx->pos, "failed to parse primary expression");
+ return nil;
+}
+
+static
+uint32
+typename(Parser *p, Lexer *lx, uint32 *spec)
+{
+ uint32 base;
+ uint64 s;
+
+ base = basetype(p, lx, &s);
+ if (!base) {
+ errorat(lx->pos, "failed to parse type name specifiers");
+ return 0;
+ }
+ *spec = (uint32)s;
+ namedecl(p, lx, &base, 1);
+
+ return base;
+}
+
+static Expr* cast(Parser *p, Lexer *lx);
+
+static
+Expr*
+unary(Parser *p, Lexer *lx)
+{
+ Expr *x;
+ Token t;
+
+ t = peek(p, 0);
+ switch (t.kind) {
+ case Ainc: MAKEX(x, preinc); goto Prefix;
+ case Adec: MAKEX(x, predec); /* fallthrough */
+ Prefix:
+ advance(p, lx);
+ x->pos.beg = t.pos;
+ x->unary.pre = unary(p, lx);
+ x->pos.end = x->unary.pre->pos.end;
+ return x;
+
+ case Aneg: MAKEX(x, neg); goto Unary;
+ case Aand: MAKEX(x, ref); goto Unary;
+ case Anot: MAKEX(x, not); goto Unary;
+ case Astar: MAKEX(x, star); goto Unary;
+ case Aadd: MAKEX(x, plus); goto Unary;
+ case Asub: MAKEX(x, minus); /* fallthrough */
+ Unary:
+ advance(p, lx);
+ x->pos.beg = t.pos;
+ x->unary.pre = cast(p, lx);
+ x->pos.end = x->unary.pre->pos.end;
+ return x;
+
+ case Akeywd:
+ switch (t.val.i) {
+ case Ksizeof:
+ MAKEX(x, sizeof);
+ goto Key;
+ case Kalignof:
+ MAKEX(x, alignof);
+ /* fallthrough */
+ Key:
+ t = advance(p, lx);
+ if (t.kind == Alparen)
+ if (istypename(p, peek(p, 1))) {
+ t = advance(p, lx);
+ x->info.type = 0;
+ x->info.of.type = typename(p, lx, &x->info.of.qual);
+
+ t = peek(p, 0);
+ if (nomatch(t, Arparen)) {
+ errorat(t.pos, "missing paren for size/alignof statement");
+ goto Bad;
+ }
+ advance(p, lx);
+ return x;
+ }
+
+ x->info.type = 1;
+ x->info.x = unary(p, lx);
+ return x;
+
+ default:
+ ;
+ }
+ /* fallthrough */
+ default:
+ return postfix(p, lx);
+ }
+Bad:
+ return nil;
+}
+
+static
+Expr*
+cast(Parser *p, Lexer *lx)
+{
+ Expr *x;
+ Token t;
+
+ t = peek(p, 0);
+ if (t.kind == Alparen && istypename(p, peek(p,1))) {
+ t = advance(p, lx);
+ MAKEX(x, cast);
+
+ x->pos.beg = t.pos;
+ x->cast.to.type = typename(p, lx, &x->cast.to.qual);
+ if (!x->cast.to.type) {
+ errorat(lx->pos, "invalid type operand of cast");
+ goto Bad;
+ }
+
+ t = peek(p, 0);
+ if (nomatch(t, Arparen)) {
+ errorat(lx->pos, "missing closing paren after cast expression");
+ goto Bad;
+ }
+ advance(p, lx);
+
+ x->cast.x = cast(p, lx);
+ x->pos.beg = lx->pos;
+ return x;
+ }
+ return unary(p, lx);
+
+Bad:
+ errorat(lx->pos, "failed to parse cast expression");
+ return nil;
+}
+
+/* static data for binary operators */
+#define OPERATORS \
+ OPERATOR(Astar, 10, Xmul) \
+ OPERATOR(Adiv, 10, Xdiv) \
+ OPERATOR(Amod, 10, Xmod) \
+ OPERATOR(Aadd, 9, Xadd) \
+ OPERATOR(Asub, 9, Xsub) \
+ OPERATOR(Alsft, 8, Xlsft) \
+ OPERATOR(Arsft, 8, Xrsft) \
+ OPERATOR(Agteq, 7, Xgteq) \
+ OPERATOR(Alteq, 7, Xlteq) \
+ OPERATOR(Alt, 7, Xlt) \
+ OPERATOR(Agt, 7, Xgt) \
+ OPERATOR(Aeq, 6, Xeql) \
+ OPERATOR(Aneq, 6, Xneq) \
+ OPERATOR(Aand, 5, Xand) \
+ OPERATOR(Axor, 4, Xxor) \
+ OPERATOR(Aor, 3, Xor) \
+ OPERATOR(Aandand, 2, Xandand) \
+ OPERATOR(Aoror, 1, Xoror)
+
+static int prectab[NUM_TOKENS] =
+{
+#define OPERATOR(a, b, c) [a] = b,
+ OPERATORS
+#undef OPERATOR
+};
+
+static int optab[NUM_TOKENS] =
+{
+#define OPERATOR(a, b, c) [a] = c,
+ OPERATORS
+#undef OPERATOR
+};
+#undef OPERATORS
+
+static
+Expr*
+binary(Parser *p, Lexer *lx, int prec)
+{
+ Token t;
+ int k, np;
+ Expr *l, *x;
+
+ l = cast(p, lx);
+ for (;;) {
+ t = peek(p, 0);
+ k = t.kind;
+ np = prectab[k];
+ if (np < prec)
+ return l;
+
+ alloc(x);
+ t = advance(p, lx);
+
+ x->pos.beg = l->pos.beg;
+ x->kind = optab[k];
+ x->binary.l = l;
+ x->binary.r = binary(p, lx, np + 1);
+ x->pos.end = x->binary.r->pos.end;
+
+ l = x;
+ }
+ return l;
+Bad:
+ errorat(t.pos, "failed to parse expression");
+ return nil;
+}
+
+static
+Expr*
+ternary(Parser *p, Lexer *lx)
+{
+ Pos b;
+ Token t;
+ Expr *x, *y;
+
+ x = binary(p, lx, 1);
+ t = peek(p, 0);
+ b = t.pos;
+
+ switch (t.kind) {
+ case Aqmark:
+ t = advance(p, lx);
+ y = x;
+ MAKEX(x, ternary);
+ x->pos.beg = b;
+ x->kind = Xternary;
+ x->cond.c = y;
+ x->cond.t = expr(p, lx);
+
+ t = peek(p, 0);
+ if (nomatch(t, Acolon)) {
+ errorat(t.pos, "ternary expression missing ':'");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ x->cond.e = expr(p, lx);
+ x->pos.end = lx->pos;
+ break;
+
+ case Aasn: MAKEX(y, asn); goto Assign;
+ case Aorasn: MAKEX(y, orasn); goto Assign;
+ case Axorasn: MAKEX(y, xorasn); goto Assign;
+ case Aandasn: MAKEX(y, andasn); goto Assign;
+ case Asubasn: MAKEX(y, subasn); goto Assign;
+ case Amulasn: MAKEX(y, mulasn); goto Assign;
+ case Adivasn: MAKEX(y, divasn); goto Assign;
+ case Amodasn: MAKEX(y, modasn); goto Assign;
+ case Alsftasn: MAKEX(y, lsftasn); goto Assign;
+ case Arsftasn: MAKEX(y, rsftasn); goto Assign;
+ Assign:
+ advance(p, lx);
+
+ y->asn.l = x;
+ y->asn.r = ternary(p, lx);
+ x = y;
+ x->pos.beg = b;
+ x->pos.end = lx->pos;
+ break;
+ default:
+ ;
+ }
+
+ return x;
+Bad:
+ errorat(lx->pos, "failing expression parse");
+ return nil;
+}
+
+static
+Expr*
+expr(Parser *p, Lexer *lx)
+{
+ Pos b;
+ Token t;
+ Expr *x, *y;
+
+ x = ternary(p, lx);
+ while (t = peek(p, 0), t.kind == Acomma) {
+ advance(p, lx);
+ y = x;
+ MAKEX(x, comma);
+ x->pos.beg = y->pos.beg;
+ x->comma.x[0] = y;
+ x->comma.x[1] = ternary(p, lx);
+ x->pos.end = lx->pos;
+ y = nil;
+ }
+
+ return x;
+}
+
+// -----------------------------------------------------------------------
+// statements
+
+static
+struct Node*
+stmt(Parser *p, Lexer *lx)
+{
+ int k;
+ Stmt *s;
+ Sym *sym;
+ Token t;
+
+ t = peek(p, 0);
+ k = t.kind;
+
+ /* intercept decl before allocating a statement */
+ if (k == Aident) {
+ if (peek(p, 1).kind == Acolon)
+ goto Tlabel;
+ sym = lookupobj(p, t.val.s);
+ if (!sym) {
+ errorat(lx->pos, "unrecognized type identifier '%s'", t.val.s);
+ goto Bad;
+ }
+
+ if (sym->kind == Stype)
+ goto Tdecl;
+ if (sym->kind == Svar) {
+ alloc(s);
+ s->pos.beg = lx->pos;
+ goto Texpr;
+ }
+
+ errorat(lx->pos, "bad symbol type used as type identifier");
+ goto Bad;
+ }
+
+ if (k == Akeywd) {
+ if ((Kauto <= t.val.i && t.val.i <= Ktypedef) || (Kconst <= t.val.i && t.val.i <= Kenum)) {
+ Tdecl:
+ return (Node *)decl(p, lx);
+ }
+ }
+
+ alloc(s);
+ s->pos.beg = lx->pos;
+
+ switch (k) {
+ case Akeywd:
+ switch (k = t.val.i) {
+ case Kif:
+ t = advance(p, lx);
+ s->kind = Sif;
+
+ if (nomatch(t, Alparen)) {
+ errorat(lx->pos, "missing opening paren before if conditional");
+ goto Bad;
+ }
+ s->br.cond = expr(p, lx);
+ if (nomatch(t, Arparen)) {
+ errorat(lx->pos, "missing closing paren after if conditional");
+ goto Bad;
+ }
+ s->br.body = stmt(p, lx);
+
+ t = peek(p, 0);
+ if (iskw(t, Kelse))
+ s->br.orelse = stmt(p, lx);
+ else
+ s->br.orelse = nil;
+
+ break;
+
+ case Kswitch:
+ t = advance(p, lx);
+ s->kind = Sswitch;
+
+ if (nomatch(t, Alparen)) {
+ errorat(lx->pos, "missing opening paren before switch conditional");
+ goto Bad;
+ }
+ s->br.cond = expr(p, lx);
+ if (nomatch(t, Arparen)) {
+ errorat(lx->pos, "missing closing paren after switch conditional");
+ goto Bad;
+ }
+ s->br.body = stmt(p, lx);
+ s->br.orelse = nil;
+
+ break;
+
+ case Kfor:
+ t = advance(p, lx);
+ s->kind = Sfor;
+
+ if (nomatch(t, Alparen)) {
+ errorat(lx->pos, "missing opening paren before for loop preamble");
+ goto Bad;
+ }
+
+ if (t.kind == Asemi)
+ s->loop.init = nil;
+ else {
+ // TODO: test for declaration
+ s->loop.init = (Node *)expr(p, lx);
+ }
+
+ if (nomatch(t, Asemi)) {
+ errorat(lx->pos, "missing semicolon");
+ goto Bad;
+ }
+
+ if (t.kind == Asemi)
+ s->loop.cond = nil;
+ else
+ s->loop.cond = expr(p, lx);
+
+ if (nomatch(t, Asemi)) {
+ errorat(lx->pos, "missing semicolon");
+ goto Bad;
+ }
+
+ if (t.kind == Asemi)
+ s->loop.step = nil;
+ else
+ s->loop.step = expr(p, lx);
+
+ if (nomatch(t, Alparen)) {
+ errorat(lx->pos, "missing closing paren after for loop preamble");
+ goto Bad;
+ }
+ s->loop.body = stmt(p, lx);
+ break;
+
+ case Kwhile:
+ t = advance(p, lx);
+ s->kind = Swhile;
+ if (nomatch(t, Alparen)) {
+ errorat(lx->pos, "missing opening paren before while loop conditional");
+ goto Bad;
+ }
+ s->loop.cond = expr(p, lx);
+ if (nomatch(t, Arparen)) {
+ errorat(t.pos, "missing closing paren after while loop conditional");
+ goto Bad;
+ }
+
+ s->loop.init = nil;
+ s->loop.step = nil;
+ s->loop.body = stmt(p, lx);
+ break;
+
+ case Kdo:
+ t = advance(p, lx);
+ s->kind = Sdo;
+ s->loop.body = stmt(p, lx);
+
+ if (!iskw(t, Kwhile)) {
+ errorat(t.pos, "missing while statement conditional after do body");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ if (nomatch(t, Alparen)) {
+ errorat(t.pos, "missing open paren after while conditional");
+ goto Bad;
+ }
+
+ s->loop.init = nil;
+ s->loop.step = nil;
+ s->loop.cond = expr(p, lx);
+ break;
+
+ case Kgoto:
+ t = advance(p, lx);
+ s->kind = Sgoto;
+ if (t.kind != Aident) {
+ errorat(t.pos, "invalid argument to goto");
+ goto Bad;
+ }
+ s->jmp.lbl = t.val.s;
+ t = advance(p, lx);
+ if (nomatch(t, Asemi)) {
+ errorat(t.pos, "missing semicolon after goto");
+ goto Bad;
+ }
+ advance(p, lx);
+ break;
+
+ case Kcontinue:
+ t = advance(p, lx);
+ s->kind = Scontin;
+ s->jmp.lbl = nil;
+ s->jmp.x = nil;
+ if (nomatch(t, Asemi)) {
+ errorat(t.pos, "missing semicolon after continue");
+ goto Bad;
+ }
+ advance(p, lx);
+ break;
+
+ case Kbreak:
+ t = advance(p, lx);
+ s->kind = Sbreak;
+ s->jmp.lbl = nil;
+ s->jmp.x = nil;
+ if (nomatch(t, Asemi)) {
+ errorat(t.pos, "missing semicolon after break");
+ goto Bad;
+ }
+ advance(p, lx);
+ break;
+
+ case Kreturn:
+ t = advance(p, lx);
+ s->kind = Sreturn;
+
+ s->jmp.lbl = nil;
+ s->jmp.x = (t.kind == Asemi) ? nil : expr(p, lx);
+
+ t = peek(p, 0);
+ if (nomatch(t, Asemi)) {
+ errorat(t.pos, "missing semicolon after return statement");
+ goto Bad;
+ }
+ advance(p, lx);
+ break;
+
+ case Kcase:
+ t = advance(p, lx);
+ s->kind = Scase;
+ s->lbl.x = expr(p, lx);
+ if (nomatch(t, Acolon)) {
+ errorat(t.pos, "missing colon after default label");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ s->lbl.stmt = stmt(p, lx);
+ break;
+
+ case Kdefault:
+ t = advance(p, lx);
+ s->kind = Scase;
+ s->lbl.x = nil;
+ if (nomatch(t, Acolon)) {
+ errorat(t.pos, "missing colon after default label");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ s->lbl.stmt = stmt(p, lx);
+ break;
+
+ default:
+ panicf("unexpected statement keyword %s", keywords[k]);
+ }
+ break;
+ case Albrace:
+ s->kind = Sblock;
+ openscope(p);
+ if (blkstmt(p, lx, &s)) {
+ errorat(lx->pos, "failed to parse block statement");
+ goto Bad;
+ }
+ closescope(p);
+ break;
+
+ case Asemi:
+ t = advance(p, lx);
+ s->kind = Sempty;
+ break;
+
+ case Aident:
+ Tlabel:
+ t = advance(p, lx);
+ s->kind = Slabel;
+ if (nomatch(t, Acolon)) {
+ errorat(t.pos, "missing colon after labelled block");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ s->lbl.stmt = stmt(p, lx);
+ break;
+
+ default:
+ Texpr:
+ s->kind = Sexpr;
+ s->x = expr(p, lx);
+
+ t = peek(p, 0);
+ if (nomatch(t, Asemi)) {
+ errorat(t.pos, "missing semicolon after statement expression");
+ goto Bad;
+ }
+ advance(p, lx);
+ }
+
+ s->pos.end = lx->pos;
+ return (Node *)s;
+Bad:
+ errorat(lx->pos, "failed to parse statement");
+ return nil;
+}
+
+static
+error
+blkstmt(Parser *p, Lexer *lx, Stmt **s)
+{
+ Token t;
+ int len;
+ int cap;
+ Node **ns;
+
+ alloc(*s);
+ (*s)->kind = Sblock;
+ (*s)->pos.beg = lx->pos;
+
+ t = peek(p, 0);
+ if (nomatch(t, Albrace))
+ goto Bad;
+ t = advance(p, lx);
+
+ len = 0, cap = 20;
+ ns = malloc(cap*sizeof(*ns));
+ while (t.kind != Arbrace) {
+ if (cap == len) {
+ cap += 20;
+ ns = realloc(ns, cap*sizeof(*ns));
+ }
+ ns[len++] = stmt(p, lx);
+ t = peek(p, 0);
+ }
+ advance(p, lx);
+
+ (*s)->pos.end = lx->pos;
+ (*s)->blk.n = len;
+ movearray((*s)->blk.item, ns, len);
+ return 0;
+Bad:
+ errorat(lx->pos, "failed to parse block statement");
+ free(ns);
+ return 1;
+}
+
+// -----------------------------------------------------------------------
+// types
+
+uint32
+ptrtype(uint32 base, uint32 qual)
+{
+ uint32 i;
+ Type *t;
+
+ i = type();
+ t = C.type.info + i;
+ t->kind = Tptr;
+ t->ptr.base = base;
+ t->ptr.qual = qual;
+ t->size = pointer.size;
+ t->align = pointer.align;
+ t->sign = pointer.sign;
+
+ return i;
+}
+
+uint32
+arraytype(uint32 base, uint32 qual, Expr *ix)
+{
+ int i, n;
+ Type *t;
+
+ /* TODO: evaluate the length */
+ n = 10;
+ i = type();
+ t = C.type.info + i;
+ t->kind = Tarray;
+ t->ptr.base = base;
+ t->size = n * C.type.info[base].size;
+ t->align = C.type.info[base].align;
+ t->sign = 0;
+
+ return i;
+}
+
+uint32
+functype(uint32 ret, int n, Field *args, int dots)
+{
+ uint32 i;
+ Type *t;
+
+ i = type();
+ t = C.type.info + i;
+ t->kind = Tfunc;
+ t->size = pointer.size;
+ t->align = pointer.align;
+ t->sign = pointer.sign;
+
+ t->func.ret = ret;
+ t->func.n = n;
+ t->func.arg = args;
+ t->func.dots = dots;
+
+ return i;
+}
+
+#define ALIGN_DOWN(n, a) ((n) & ~((a)-1))
+#define ALIGN_UP(n, a) ALIGN_DOWN((n) + (a)-1, (a))
+uint32
+structtype(int n, Field *field, Expr *bits)
+{
+ uint32 i;
+ Type *t;
+ Field *f, *e;
+
+ i = type();
+ t = C.type.info + i;
+ t->kind = Tstruct;
+ t->size = 0;
+ t->align = 0;
+ for (f = field, e = field+n; f != e; ++f) {
+ t->size += C.type.info[f->type].size + ALIGN_UP(t->size, C.type.info[f->type].align);
+ t->align = MAX(t->align, C.type.info[f->type].align);
+ }
+ t->aggr.len = n;
+ t->aggr.f = field;
+ t->aggr.x = bits;
+
+ return i;
+}
+
+uint32
+uniontype(int n, Field *field, Expr *bits)
+{
+ uint32 i;
+ Type *t;
+ Field *f, *e;
+
+ i = type();
+ t = C.type.info + i;
+ t->kind = Tstruct;
+ t->size = 0;
+ t->align = 0;
+ for (f = field, e = field+n; f != e; ++f) {
+ t->size = MAX(t->size, C.type.info[f->type].size);
+ t->align = MAX(t->align, C.type.info[f->type].align);
+ }
+ t->aggr.len = n;
+ t->aggr.f = field;
+ t->aggr.x = bits;
+
+ return i;
+}
+
+uint32
+enumtype(int n, string *elts, Expr *vals)
+{
+ uint32 i;
+ Type *t;
+ Field *f, *e;
+
+ i = type();
+ t = C.type.info + i;
+ t->kind = Tenum;
+ /* TODO: dont hardcode int32 */
+ t->size = 4;
+ t->align = 4;
+ t->enm.len = n;
+ t->enm.elt = elts;
+ t->enm.val = vals;
+
+ return i;
+}
+#undef ALIGN_UP
+#undef ALIGN_DOWN
+
+/* unpacking C declarations into sensible types */
+static
+uint32
+typeofname(Name *name, uint32 base)
+{
+ switch (name->kind) {
+ /* Nnil corresponds to an abstract declarator (i.e. no identifier) */
+ case Nnil:
+ case Nident:
+ return base;
+ case Nparen:
+ return typeofdtor(name->paren, base);
+ case Nindex:
+ return typeofname(name->sfx.name, arraytype(base, name->sfx.idx.q, name->sfx.idx.x));
+ case Ncall:
+ return typeofname(name->sfx.name, functype(base, name->sfx.call.n, name->sfx.call.arg, name->sfx.call.dots));
+ default:
+ panicf("unreachable");
+ }
+ return 0;
+}
+
+static
+uint32
+typeofdtor(Dtor *decl, uint32 base)
+{
+ int n;
+ Ptr *p;
+ uint64 b, tmp;
+
+ n = 0;
+ p = &decl->ptr;
+ b = p->kind;
+ while (b & 1) {
+ base = ptrtype(base, b >> 1);
+ if (++n >= 8) {
+ p = p->link;
+ b = p->kind;
+ } else {
+ b >>= 6;
+ }
+ }
+
+ return typeofname(decl->name, base);
+}
+
+static
+uint32
+basetype(Parser *p, Lexer *lx, uint64 *s)
+{
+ int n;
+ uint64 m;
+
+ if (spec(p, lx, s)) {
+ errorat(lx->pos, "failed to parse type specifier");
+ return 0;
+ }
+
+ m = (((*s<<32)>>32) & ~(MaskQul|MaskMem|MaskFcn));
+ for (n = 0; n < arrlen(validtypespec); n++) {
+ if (validtypespec[n] == m) {
+ if (indextypespec[n] < 0) {
+ m = *s >> 32;
+ if (!m) {
+ errorat(lx->pos, "not a valid type identifier");
+ return 0;
+ }
+ return m;
+ }
+ return indextypespec[n];
+ }
+ }
+
+ errorat(lx->pos, "invalid type specifier");
+ return 0;
+}
+
+static
+string
+namedecl(Parser *p, Lexer *lx, uint32 *base, int noname)
+{
+ Dtor *dt;
+ string name;
+ Type *t;
+
+ dt = getdtor(p);
+ name = nil;
+ if (dtor(p, lx, dt, noname)) {
+ errorat(lx->pos, "invalid declarator");
+ goto End;
+ }
+ if (!noname || noname == 2 && dt->name->kind)
+ name = nameof(dt->name);
+
+ *base = typeofdtor(dt, *base);
+ putdtor(p, dt);
+ return name;
+End:
+ putdtor(p, dt);
+ return nil;
+}
+
+// -----------------------------------------------------------------------
+// declarations
+
+static
+uint32
+enumerate(Parser *p, Lexer *lx, string name, int kind)
+{
+ int i, n;
+ uint64 s;
+ uint32 t;
+ Token tk;
+ /* TODO: think of a better soln */
+ string nm[1024], *elts;
+ Expr *cx[1024], *vals;
+
+ for (n = 0; tk.kind != Arbrace && n < arrlen(nm); n++) {
+ if (tk.kind != Aident) {
+ errorat(tk.pos, "invalid token %s in enum declaration", tokens[tk.kind]);
+ goto Bad;
+ }
+ nm[n] = tk.val.s;
+ cx[n] = nil;
+
+ tk = advance(p, lx);
+ switch(tk.kind) {
+ case Aeq:
+ advance(p, lx);
+ cx[n] = expr(p, lx);
+ tk = peek(p, 0);
+ if (tk.kind != Acomma)
+ continue;
+ /* fallthrough */
+ case Acomma:
+ tk = advance(p, lx);
+ }
+ }
+ copyarray(elts, nm, n);
+ copyarray(vals, cx, n);
+
+ t = enumtype(n, elts, vals);
+ declareenum(p, n, elts, vals);
+ return t;
+Bad:
+ errorat(tk.pos, "failed to parse enum declaration");
+ return 0;
+}
+
+static
+uint32
+aggregate(Parser *p, Lexer *lx, string name, int kind)
+{
+ int n;
+ uint64 s;
+ Token tk;
+ /* TODO: think of a better soln */
+ static Field fs[1024];
+ Field *f;
+ static Expr *cx[1024];
+ Expr *x;
+
+ for (n = 0, tk = peek(p, 0); tk.kind != Arbrace && n < arrlen(fs); n++) {
+ fs[n].type = basetype(p, lx, &s);
+ fs[n].qual = (uint32)(s & ~(MaskTyp|MaskInt|MaskFlt));
+ Field:
+ fs[n].name = namedecl(p, lx, &fs[n].type, 0);
+ tk = peek(p, 0);
+ switch (tk.kind) {
+ case Acolon:
+ advance(p, lx);
+ cx[n] = expr(p, lx);
+ tk = peek(p, 0);
+ if (tk.kind == Asemi) {
+ tk = advance(p, lx);
+ continue;
+ }
+ if (tk.kind != Acomma) {
+ errorat(tk.pos, "unrecognized token %s in struct field declaration", tokens[tk.kind]);
+ goto Bad;
+ }
+ /* fallthrough */
+ case Acomma:
+ advance(p, lx);
+ n++;
+ goto Field;
+
+ case Asemi:
+ tk = advance(p, lx);
+ continue;
+
+ default:
+ errorat(tk.pos, "unrecognized token %s in struct field declaration", tokens[tk.kind]);
+ goto Bad;
+ }
+ }
+ copyarray(f, fs, n);
+ copyarray(x, cx, n);
+ return (kind == Tstruct) ? structtype(n, f, x) : uniontype(n, f, x);
+Bad:
+ errorat(tk.pos, "failed to parse aggregate declaration");
+ return 0;
+}
+
+static
+error
+spec(Parser *p, Lexer *lx, uint64 *spec)
+{
+ Token t;
+ int n, i;
+ Sym *typ;
+ string name;
+ uint32 tag;
+ uint64 s, sm;
+ static uint32 (*aggrfunc[2])(Parser *, Lexer *, string , int) = {aggregate, enumerate};
+
+ s = 0;
+ while (t = peek(p, 0), t.kind >= Aident) {
+ /* typename */
+ if (t.kind == Aident) {
+ typ = lookupobj(p, t.val.s);
+ if (!typ || (typ && typ->kind != Stype))
+ break;
+
+ sm = typ->type;
+ s |= (sm << 32 | Tname);
+ advance(p, lx);
+ continue;
+ }
+
+ /* keyword */
+ switch (n = t.val.i) {
+ case Kauto: case Kregister: case Kstatic: case Kextern: case Ktypedef: case Ktls:
+ if (s & MaskMem) {
+ errorat(lx->pos, "multiple storage class specifiers: second was %s", keywords[n]);
+ goto Bad;
+ }
+ break;
+
+ case Kinline: case Knoret:
+ if (s & Bit(n))
+ warnat(lx->pos, "duplicate %s function specifier", keywords[n]);
+ break;
+
+ case Kconst: case Kvolatile:
+ if (s & Bit(n))
+ warnat(lx->pos, "duplicate %s specifier found in declaration", keywords[n]);
+ break;
+
+ case Ksigned: case Kunsigned:
+ if (s & MaskSgn) {
+ if (s & Bit(n)) {
+ warnat(lx->pos, "duplicated storage class specifier: second was %s", keywords[n]);
+ break;
+ }
+ errorat(lx->pos, "multiple storage class specifiers");
+ goto Bad;
+ }
+ break;
+
+ case Kshort:
+ if (s & Tshort) {
+ warnat(lx->pos, "duplicated short specifier");
+ break;
+ }
+ break;
+
+ case Klong:
+ if ((s >> Klong) & 2) {
+ errorat(lx->pos, "cannot chain three or more long specifiers");
+ goto Bad;
+ }
+ s += Bit(n);
+ t = advance(p, lx);
+ continue;
+
+ case Kvoid: case Kchar: case Kint: case Kfloat: case Kdouble:
+ if (s & MaskTyp) {
+ errorat(lx->pos, "more than one base type specified");
+ goto Bad;
+ }
+ break;
+
+ case Kstruct: case Kunion:
+ i = 0;
+ goto Aggr;
+ case Kenum:
+ i = 1;
+ Aggr:
+ if (s & (Tstruct | Tunion | Tenum)) {
+ errorat(lx->pos, "more than one aggregate/enum type specified");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ if (t.kind != Aident && t.kind != Albrace) {
+ errorat(t.pos, "enum specifier missing valid declaration");
+ goto Bad;
+ }
+
+ /* NOTE: This offset is needed to correctly obtain Tstruct */
+ n++;
+ name = nil;
+ tag = 0;
+ if (t.kind == Aident) {
+ name = t.val.s;
+ t = advance(p, lx);
+ }
+ if (t.kind == Albrace) {
+ /* TODO: we need check if the name exists. */
+ t = advance(p, lx);
+ /* NOTE: This depends on the enum order. KEEP IN SYNC */
+ tag = aggrfunc[i](p, lx, name, Bit(n));
+ if (t = peek(p, 0), nomatch(t, Arbrace)) {
+ errorat(t.pos, "invalid token %s in aggregate/enum declaration", tokens[t.kind]);
+ goto Bad;
+ }
+ /* high bits encode the type index */
+ s |= (uint64)tag << 32;
+ }
+ /* TODO: if name does not exist, enter in an incomplete type! */
+ if (name)
+ declaretag(p, tag, name);
+
+ break;
+
+ default:
+ errorat(t.pos, "invalid keyword '%s' found in declaration specifier", keywords[n]);
+ }
+
+ s |= Bit(n);
+ advance(p, lx);
+ }
+
+ *spec = s;
+ return 0;
+
+Bad:
+ /* TODO: serialize bitflags to string for nice error message */
+ errorat(lx->pos, "ignoring specifier");
+ *spec = Sbad;
+ return 1;
+}
+
+/*
+ * name declaration
+ * see dtor for valid values of ab
+ */
+static
+error
+name(Parser *p, Lexer *lx, Name **nmp, int ab)
+{
+ Token t;
+ int n, k;
+ uint64 s;
+ Sym *sym;
+ Name *nm, *tmp;
+
+ /* max args = 100 */
+ struct Field args[100];
+
+ nm = *nmp;
+ t = peek(p, 0);
+ switch (k = t.kind) {
+ case Aident:
+ if (ab == 1) {
+ errorat(t.pos, "identifier not allowed in abstract declarator");
+ goto Bad;
+ }
+ nm->kind = Nident;
+ nm->ident = t.val.s;
+ break;
+
+ case Alparen:
+ advance(p, lx);
+ nm->kind = Nparen;
+ nm->paren = getdtor(p);
+ if (dtor(p, lx, nm->paren, ab)) {
+ putdtor(p, nm->paren);
+ nm->paren = nil;
+ errorat(lx->pos, "invalid declarator in parenthesis");
+ goto Bad;
+ }
+
+ t = peek(p, 0);
+ if (nomatch(t, Arparen)) {
+ putdtor(p, nm->paren);
+ nm->paren = nil;
+ errorat(lx->pos, "missing closing paren in declarator");
+ goto Bad;
+ }
+ break;
+
+ case Albrakt:
+ if (ab)
+ goto Sfx;
+ errorat(lx->pos, "missing identifier in non-abstract declarator");
+ /* fallthrough */
+ default:
+ if (ab)
+ goto Sfx;
+ errorat(lx->pos, "invalid token '%s' in name declaration", tokens[k]);
+ goto Bad;
+ }
+
+ t = advance(p, lx);
+Sfx:
+ for (;;) {
+ switch (k = t.kind) {
+ case Albrakt:
+ tmp = getname(p);
+ tmp->kind = Nindex;
+ tmp->sfx.name = nm;
+
+ nm = tmp, tmp = nil;
+
+ t = advance(p, lx);
+ if (t.kind == Arbrakt) {
+ nm->sfx.idx.q = 0;
+ Iend:
+ nm->sfx.idx.x = nil;
+ t = advance(p, lx);
+ break;
+ }
+ if (t.kind == Astar) {
+ nm->sfx.idx.q = -1;
+ IStar:
+ nm->sfx.idx.x = nil;
+ t = advance(p, lx);
+ if (t.kind != Arbrakt) {
+ errorat(t.pos, "invalid '*' syntax in index expression");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ break;
+ }
+
+ if (spec(p, lx, &s)) {
+ errorat(lx->pos, "invalid type qualifier list in index expression");
+ goto Bad;
+ }
+
+ nm->sfx.idx.q = (uint32)s;
+ t = peek(p, 0);
+
+ if (t.kind == Astar)
+ goto IStar;
+
+ if (t.kind == Arbrakt)
+ goto Iend;
+
+ nm->sfx.idx.x = expr(p, lx);
+
+ t = peek(p, 0);
+ if (nomatch(t, Arbrakt)) {
+ errorat(t.pos, "unterminated index expression");
+ goto Bad;
+ }
+
+ t = advance(p, lx);
+ continue;
+
+ case Alparen:
+ tmp = getname(p);
+ tmp->kind = Ncall;
+ tmp->sfx.name = nm;
+
+ nm = tmp, tmp = nil;
+
+ t = advance(p, lx);
+ nm->sfx.call.n = 0;
+ switch (t.kind) {
+ case Arparen:
+ nm->sfx.call.arg = nil;
+ break;
+
+ case Aident:
+ sym = lookupobj(p, t.val.s);
+ if (!sym || (sym && sym->kind != Stype)) {
+ while (t.kind == Aident) {
+ if (nm->sfx.call.n >= arrlen(args))
+ panicf("ident stack overflow");
+ args[nm->sfx.call.n++] = (struct Field) {
+ .qual = 0,
+ .type = 0,
+ .name = t.val.s,
+ };
+ t = advance(p, lx);
+ }
+ if (nomatch(t, Arparen)) {
+ errorat(t.pos, "token '%s' found in function parameter identifier list");
+ goto Bad;
+ }
+ copyarray(nm->sfx.call.arg, args, nm->sfx.call.n);
+ break;
+ }
+ goto ParamLoop;
+
+ case Akeywd:
+ if (t.val.i < Kconst || t.val.i > Kenum) {
+ errorat(t.pos, "invalid keyword %s inside function signature");
+ goto Bad;
+ }
+
+ ParamLoop:
+ if (nm->sfx.call.n >= arrlen(args)-1)
+ panicf("out of argument buffer");
+
+ args[nm->sfx.call.n].type = basetype(p, lx, &s);
+ if (!args[nm->sfx.call.n].type) {
+ errorat(lx->pos, "could not parse base type in function call");
+ goto Bad;
+ }
+
+ args[nm->sfx.call.n].qual = (uint32)s & ~(MaskTyp|MaskInt|MaskFlt);
+ args[nm->sfx.call.n].name = namedecl(p, lx, &args[nm->sfx.call.n].type, 2);
+
+ nm->sfx.call.n++;
+ if ((t = peek(p, 0)).kind == Acomma) {
+ advance(p, lx);
+ goto ParamLoop;
+ }
+
+ if (t.kind == Aellip) {
+ nm->sfx.call.dots = 1;
+ t = advance(p, lx);
+ }
+
+ if (nomatch(t, Arparen)) {
+ errorat(t.pos, "token '%s' found in function parameter list");
+ goto Bad;
+ }
+ copyarray(nm->sfx.call.arg, args, nm->sfx.call.n);
+ break;
+
+ default:
+ errorat(t.pos, "invalid token %s inside function call signature", tokens[t.kind]);
+ goto Bad;
+ }
+
+ t = advance(p, lx);
+ continue;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ *nmp = nm;
+ return 0;
+Bad:
+ return 1;
+}
+
+/* pointer kind is partitioned into 8x6 regions
+ * ab => abstract
+ * @ 0: must have identifier
+ * @ 1: must not have identifier
+ * @ 2: don't care
+ * else: undefined
+ */
+static
+error
+dtor(Parser *p, Lexer *lx, Dtor *d, int ab)
+{
+ int n, k;
+ error err;
+ Token t;
+ Dtor *link;
+ Ptr *ptr, *x;
+
+ err = 1;
+
+ ptr = &d->ptr;
+ ptr->kind = 0;
+ ptr->link = nil;
+
+ t = peek(p, 0);
+ if (t.kind != Astar) {
+ if (ab || t.kind == Aident || t.kind == Arparen)
+ goto Name;
+ goto Bad;
+ }
+ n = 0;
+Ptr:
+ ptr->kind |= Bit(n);
+ advance(p, lx);
+Key:
+ t = peek(p, 0);
+ switch (k = t.kind) {
+ case Akeywd:
+ if (Kconst <= t.val.i && t.val.i <= Katomic)
+ ptr->kind |= Bit(6*n + (t.val.i - Kconst + 1));
+ else {
+ errorat(lx->pos, "invalid keyword '%s' modifies pointer", keywords[t.val.i]);
+ goto Bad;
+ }
+ advance(p, lx);
+ goto Key;
+
+ case Astar:
+ if (++n >= 8) {
+ x = getptr(p);
+ x->kind = 0;
+ x->link = nil;
+ ptr->link = x;
+ ptr = x;
+ n = 0;
+ }
+ goto Ptr;
+
+ case Aident:
+ case Alparen:
+ goto Name;
+
+ default:
+ if (ab)
+ goto Name;
+ errorat(lx->pos, "invalid token '%s' modifies pointer specification", tokens[t.kind]);
+ goto Bad;
+ }
+Name:
+ return name(p, lx, &d->name, ab);
+Bad:
+ return err;
+}
+
+static
+Decl *
+decl(Parser *p, Lexer *lx)
+{
+ uint64 s;
+ Token t;
+ Decl *d;
+ Expr *x;
+ string name;
+ struct Decls *ds;
+ uint32 base, type;
+
+ alloc(d);
+
+ d->kind = 0;
+ d->pos.beg = lx->pos;
+
+ base = basetype(p, lx, &s);
+ if (!base) {
+ errorat(lx->pos, "could not parse type declaration");
+ goto Bad;
+ }
+
+ x = nil;
+ d->spec = (uint32)s & ~(MaskInt|MaskFlt|MaskTyp);
+ d->type = base;
+ d->name = namedecl(p, lx, &d->type, 0);
+ /* TODO: think about functions (both decls and defs) */
+ d->kind = (s & Mtype) ? Dtype : Dvar;
+
+ switch (t = peek(p, 0), t.kind) {
+ case Aeq:
+ if (s & Mtype) {
+ errorat(d->pos.beg, "initialization of type not allowed");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ x = initx(p, lx);
+ d->kind = Dvar;
+ if (t.kind != Acomma) {
+ d->init = x;
+ goto Semi;
+ }
+ /* fallthrough */
+ case Acomma:
+ d->kind |= Dlist;
+ d->list.init = x;
+ /* move singleton data over */
+ name = d->name;
+ type = d->type;
+ d->list.name = name;
+ d->list.type = type;
+ ds = &d->list;
+ /* iterate until we hit end of list */
+ while (t.kind == Acomma) {
+ t = advance(p, lx);
+
+ alloc(ds->link);
+ ds = ds->link;
+ ds->type = base;
+ ds->name = namedecl(p, lx, &ds->type, 0);
+
+ t = peek(p, 0);
+ if (t.kind == Aeq) {
+ t = advance(p, lx);
+ ds->init = initx(p, lx);
+ } else
+ ds->init = nil;
+ }
+ goto Semi;
+
+ case Albrace:
+ d->kind = Dfunc;
+ alloc(d->body);
+
+ if (!attop(p)) {
+ errorat(lx->pos, "nested function declarations are illegal");
+ goto Bad;
+ }
+
+ if (C.type.info[d->type].kind != Tfunc) {
+ errorat(lx->pos, "attempted to define function body for non function type");
+ goto Bad;
+ }
+
+ openscope(p);
+ if (blkstmt(p, lx, &d->body)) {
+ errorat(lx->pos, "failed to parse function body");
+ goto Bad;
+ }
+ closescope(p);
+ break;
+
+ default:
+ Semi:
+ if (nomatch(t, Asemi)) {
+ errorat(t.pos, "no semicolon after declaration");
+ goto Bad;
+ }
+ t = advance(p, lx);
+ }
+
+ d->pos.end = lx->pos;
+ declareobj(p, d);
+ return d;
+Bad:
+ errorat(lx->pos, "failed to parse top level declaration");
+ return nil;
+}
+
+// -----------------------------------------------------------------------
+// top level api
+
+void
+setup(Parser *p, Lexer *lx)
+{
+ advance(p,lx);
+ advance(p,lx);
+
+ /* define all builtin typedefs */
+ declareobj(p, &C.builtin.vargs);
+}
+
+error
+parse(Parser *p, Lexer *lx)
+{
+ Token tok;
+
+ setup(p, lx);
+ while ((tok = peek(p, 0)), tok.kind > Aeof) {
+ if (p->ast.len >= p->ast.cap) {
+ p->ast.cap += 20;
+ p->ast.decls = realloc(p->ast.decls, p->ast.cap*sizeof(*p->ast.decls));
+ }
+ p->ast.decls[p->ast.len++] = decl(p, lx);
+ }
+
+ return 0;
+}
diff --git a/src/cmd/cc/bits.c b/src/cmd/cc/bits.c
new file mode 100644
index 0000000..4b405dc
--- /dev/null
+++ b/src/cmd/cc/bits.c
@@ -0,0 +1,114 @@
+#include "cc.h"
+
+// -----------------------------------------------------------------------
+// Architecture
+
+enum
+{
+ archx64,
+ numarch,
+};
+
+// -----------------------------------------------------------------------
+// Types
+
+/*
+ * enumerated type specifers
+ * see https://en.wikipedia.org/wiki/C_data_types
+ */
+#define VOID X(Tvoid, 2)
+
+#define BOOL X(Tbool, 3)
+#define CHAR X(Tchar, 4)
+#define SCHAR X(Tsign|Tchar, 5)
+#define UCHAR X(Tunsign|Tchar, 6)
+
+#define SHORT X(Tshort, 7), X(Tshort|Tint, 7)
+#define SSHORT X(Tsign|Tshort, 8), X(Tsign|Tshort|Tint, 8)
+#define USHORT X(Tunsign|Tshort, 9), X(Tunsign|Tshort|Tint, 9)
+
+#define INT X(0, 10), X(Tint, 10)
+#define SINT X(Tsign, 11), X(Tsign|Tint, 11)
+#define UINT X(Tunsign, 12), X(Tunsign|Tint, 12)
+
+#define LONG X(Tlong, 13), X(Tlong|Tint, 13)
+#define SLONG X(Tsign|Tlong, 14), X(Tsign|Tlong|Tint, 14)
+#define ULONG X(Tunsign|Tlong, 15), X(Tunsign|Tlong|Tint, 15)
+
+#define VLONG X(Tvlong, 16), X(Tvlong|Tint, 16)
+#define SVLONG X(Tsign|Tvlong, 17), X(Tsign|Tvlong|Tint, 17)
+#define UVLONG X(Tunsign|Tvlong, 18), X(Tunsign|Tvlong|Tint, 18)
+
+#define FLOAT X(Tfloat, 19)
+#define DOUBLE X(Tdouble, 20)
+#define LONGDB X(Tlong|Tdouble, 21)
+#define COMPLEX X(Tcmplx, 22)
+#define IMAGINARY X(Timag, 23)
+
+/* fixed width definitions */
+#define DEF(sz, aln, mx, sgn) {.size=sz, .align=aln, .max=mx, .sign=sgn }
+
+#define INT8 DEF(1, 1, 0x7fff, 0)
+#define UINT8 DEF(1, 1, 0xffff, 1)
+
+#define INT16 DEF(2, 2, 0x7fff, 0)
+#define UINT16 DEF(2, 2, 0xffff, 1)
+
+#define INT32 DEF(4, 4, 0x7fffffff, 0)
+#define UINT32 DEF(4, 4, 0xffffffff, 1)
+
+#define INT64 DEF(8, 8, 0x7fffffffffffffff, 0)
+#define UINT64 DEF(8, 8, 0xffffffffffffffff, 1)
+
+/* architecture specific definitions */
+// TODO: max value should be able to take floats
+#define TYPES \
+ TYPE(DEF(0, 0, 0, 0), VOID) \
+ TYPE(INT8, BOOL) \
+ TYPE(UINT8, CHAR) \
+ TYPE(INT8, SCHAR) \
+ TYPE(UINT8, UCHAR) \
+ TYPE(INT16, SHORT) \
+ TYPE(INT16, SSHORT) \
+ TYPE(UINT16, USHORT) \
+ TYPE(INT32, INT) \
+ TYPE(INT32, SINT) \
+ TYPE(UINT32, UINT) \
+ TYPE(INT64, LONG) \
+ TYPE(INT64, SLONG) \
+ TYPE(UINT64, ULONG) \
+ TYPE(INT64, VLONG) \
+ TYPE(INT64, SVLONG) \
+ TYPE(UINT64, UVLONG) \
+ TYPE(DEF(4, 4, 0, 0), FLOAT) \
+ TYPE(DEF(8, 8, 0, 0), DOUBLE) \
+ TYPE(DEF(16, 16, 0, 0), LONGDB) \
+ TYPE(DEF(8, 8, 0, 0), COMPLEX) \
+ TYPE(DEF(4, 4, 0, 0), IMAGINARY) \
+
+Type pointer = {.size=8, .align=8, .max=0xffffffffffffffff, .sign=0};
+
+/* pack architecture specific definitions into exported arrays */
+#define TYPE(a, ...) a,
+Type basetypes[] = {
+ { 0 }, /* sentinel value for bad types */
+ { 0 }, /* sentinel value for variadic args */
+ TYPES
+};
+#undef TYPE
+
+#define TYPE(a, ...) __VA_ARGS__,
+#define X(a, b) a
+uint64 validtypespec[38] = {
+ TYPES
+ Tstruct, Tunion, Tenum, Tname,
+};
+#undef X
+
+#define X(a, b) b
+int indextypespec[38] = {
+ TYPES
+ -1, -1, -1, -1,
+};
+#undef X
+#undef TYPE
diff --git a/src/cmd/cc/cc.c b/src/cmd/cc/cc.c
new file mode 100644
index 0000000..8ad0022
--- /dev/null
+++ b/src/cmd/cc/cc.c
@@ -0,0 +1,409 @@
+#include "cc.h"
+#include <libn/macro/map.h>
+
+// -----------------------------------------------------------------------
+// string interning
+
+/* jenkins' one at a time hash */
+static
+int32
+hash_string(byte* s)
+{
+ int32 h;
+
+ h = 0;
+ if (s != nil) {
+ for (; *s; ++s) {
+ h += *s;
+ h = (h << 10);
+ h = (h >> 6);
+ }
+ }
+
+ h += (h << 3);
+ h ^= (h >> 11);
+ h += (h >> 11);
+
+ return h;
+}
+
+static
+int
+streq(byte *s, byte *t)
+{
+ if (s == nil) {
+ if (t == nil)
+ return 1;
+ else
+ return 0;
+ }
+
+ return (t == nil) ? 0 : strcmp(s, t) == 0;
+}
+
+#define HASH(s) hash_string(s)
+#define EQUAL(s, t) (streq(s, t))
+static
+int
+getstr(string key, int *ok)
+{
+ int idx;
+ MAP_GET(idx, (&C.strs), key, HASH, EQUAL);
+
+ *ok = idx < C.strs.n_buckets;
+ return idx;
+}
+
+static
+void
+·free(void* _, void* ptr) {
+ return free(ptr);
+}
+
+static
+void *
+·alloc(void* _, uint n, ulong size) {
+ return malloc(n*size);
+}
+
+static
+void *
+·calloc(void* _, uint n, ulong size) {
+ return calloc(n, size);
+}
+
+static
+int
+morestrtab(StrTab *tab, int n)
+{
+ MAP_GROW(tab, string, int32, n, HASH, ·calloc, ·free, nil);
+}
+
+static
+int
+putstr(byte *s, error *err)
+{
+ int sz;
+ sz = C.strs.size;
+ MAP_PUT((&C.strs), s, sz, HASH, EQUAL, morestrtab, err);
+}
+#undef HASH
+#undef EQUAL
+
+int32
+intern(byte **s)
+{
+ int i, ok;
+
+ i = getstr(*s, &ok);
+ if (ok) {
+ *s = C.strs.keys[i];
+ goto END;
+ }
+
+ *s = str·make(*s);
+ i = putstr(*s, &ok);
+ C.strs.vals[i] = C.strs.size - 1;
+
+END:
+ return C.strs.vals[i];
+}
+
+// -----------------------------------------------------------------------
+// type interning
+
+/* TODO: intern types for memory savings */
+int
+type()
+{
+ if (C.type.len >= C.type.cap) {
+ C.type.cap += 100;
+ C.type.info = realloc(C.type.info, C.type.cap * sizeof(*C.type.info));
+ }
+
+ return C.type.len++;
+}
+
+// -----------------------------------------------------------------------
+// universal compiler builtins
+
+#define KEYWORD(a, b) b,
+byte *keywords[NUM_KEYWORDS] = { KEYWORDS };
+#undef KEYWORD
+
+#define DIRECTIVE(a, b, c) b,
+byte *directives[NUM_DIRECTIVES] = { DIRECTIVES };
+#undef DIRECTIVE
+
+struct Compiler C = { 0 };
+
+// -----------------------------------------------------------------------
+// cli flag handlers
+
+void
+pushinclude(byte *dirs)
+{
+ string d, s, *it, *end;
+
+ while (*dirs != '\0') {
+ d = strchr(dirs, ' ');
+ if (d != nil)
+ *d = '\0';
+
+ s = dirs;
+ intern(&s);
+ for (it = C.inc.dir, end = it + C.inc.len; it != end; ++it) {
+ if ((uintptr)s == (uintptr)(*it))
+ goto Nextdir;
+ }
+
+ if (C.inc.len == C.inc.cap) {
+ C.inc.cap += 20;
+ C.inc.dir = realloc(C.inc.dir, C.inc.cap*sizeof(*C.inc.dir));
+ }
+ C.inc.dir[C.inc.len++] = s;
+Nextdir:
+ if (d == nil)
+ break;
+ dirs = d + 1;
+ }
+}
+
+// -----------------------------------------------------------------------
+// error reporting
+
+void
+errorat(Pos x, byte *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ printf("error:%s:%d:%d: ", os·basename(x.path), x.line, x.col);
+ vprintf(fmt, args);
+ printf("\n");
+
+ va_end(args);
+ assert(0);
+}
+
+void
+warnat(Pos x, byte *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ printf("warning:%s:%d:%d: ", os·basename(x.path), x.line, x.col);
+ vprintf(fmt, args);
+ printf("\n");
+
+ va_end(args);
+}
+
+// -----------------------------------------------------------------------
+// main point of entry
+
+void
+init(void)
+{
+ int i;
+
+ for (i = 0; i < arrlen(keywords); i++)
+ intern(&keywords[i]);
+
+ for (i = 0; i < arrlen(directives); i++)
+ intern(&directives[i]);
+
+ C.heap = mem·makearena(mem·sys, nil);
+
+ /* compiler definitions */
+ C.def.len = 0;
+ C.def.cap = 100;
+ C.def.val = calloc(C.def.cap, sizeof(*C.def.val));
+
+ /* compiler include paths */
+ C.inc.len = 0;
+ C.inc.cap = 100;
+ C.inc.dir = calloc(C.inc.cap, sizeof(*C.inc.dir));
+ C.inc.dir[C.inc.len++] = ".";
+
+ C.outfile = nil;
+
+ /* type info */
+ C.type.len = arrlen(basetypes);
+ C.type.cap = 100 + arrlen(basetypes);
+ C.type.info = calloc(C.type.cap, sizeof(*C.type.info));
+
+ memcpy(C.type.info, basetypes, C.type.len * sizeof(*C.type.info));
+
+ /* builtins */
+ C.builtin.vargs = (Decl) {
+ .pos = (Range) {
+ .beg = {
+ .col = 0,
+ .line = 0,
+ .path = "<builtin>",
+ },
+ .end = {
+ .col = 0,
+ .line = 0,
+ .path = "<builtin>",
+ },
+ },
+ .kind = Dtype,
+ .spec = Mtype,
+ .type = 1,
+ .name = "__builtin_va_list",
+ };
+
+ intern(&C.builtin.vargs.name);
+}
+
+void
+initlx(Lexer *lx)
+{
+ int i;
+
+ memset(lx, 0, sizeof(*lx));
+ lx->b = lx->buf;
+
+ /* predefine macros */
+ dodefine(lx, "__LINE__");
+ dodefine(lx, "__FILE__");
+ lx->macline = (uintptr)lookup(&lx->sym, "__LINE__");
+ lx->macfile = (uintptr)lookup(&lx->sym, "__FILE__");
+
+ for (i = 0; i < C.def.len; i++)
+ dodefine(lx, C.def.val[i]);
+
+ lx->omit.len = 0;
+ lx->omit.cap = 100;
+ lx->omit.path = calloc(lx->omit.cap, sizeof(*C.inc.dir));
+
+ lx->new = lx->iostk;
+ lx->new->link = nil;
+ memset(lx->iostk, 0, sizeof(lx->iostk));
+
+ lx->sym = (SymTab){ 0 };
+}
+
+void
+freelx(Lexer *lx)
+{
+ free(lx->omit.path);
+}
+
+void
+initp(Parser *p)
+{
+ /* initialize temporary buffers */
+ memset(p->spstk, 0, sizeof(p->spstk));
+ memset(p->nmstk, 0, sizeof(p->nmstk));
+ memset(p->dtstk, 0, sizeof(p->dtstk));
+ memset(p->ptstk, 0, sizeof(p->ptstk));
+
+ p->sp = p->spstk;
+ p->nm = p->nmstk;
+ p->dt = p->dtstk;
+ p->pt = p->ptstk;
+
+ /* initialize ast */
+ p->ast.cap = 0;
+ p->ast.len = 0;
+ p->ast.decls = nil;
+}
+
+error
+compile(byte *path)
+{
+ Lexer lx;
+ Parser p;
+ error err;
+ byte *sep, out[400];
+
+ intern(&path);
+ strcpy(out, path);
+
+ sep = utf8·findrrune(out, '/');
+ if (sep)
+ *sep++ = '\0';
+ else
+ sep = out;
+
+ if (!C.outfile) {
+ C.outfile = sep;
+ if (C.outfile) {
+ if ((sep = utf8·findrrune(C.outfile, '.'))) {
+ sep[0] = '.';
+ sep[1] = 'o';
+ sep[2] = '\0';
+ }
+ } else {
+ C.outfile = "/dev/null";
+ }
+ }
+
+ initlx(&lx);
+ initp(&p);
+
+ lx.io = openio(&lx, path);
+ lx.pos = (Pos){
+ .path = path,
+ .line = 1,
+ .col = 1,
+ };
+
+ err = parse(&p, &lx);
+ freelx(&lx);
+ return err;
+}
+
+error
+main(int argc, byte *argv[])
+{
+ byte *a, *src;
+ int err;
+
+ init();
+
+ ARGBEGIN {
+ case 'o':
+ C.outfile = ARGF();
+ break;
+
+ case 'D':
+ a = ARGF();
+ if (a) {
+ intern(&a);
+ if (C.def.len >= C.def.cap) {
+ C.def.cap += 20;
+ C.def.val = realloc(C.def.val, C.def.cap * sizeof(*C.def.val));
+ }
+ C.def.val[C.def.len++] = a;
+ }
+ break;
+
+ case 'I':
+ a = ARGF();
+ if (a)
+ pushinclude(a);
+ break;
+ } ARGEND
+
+ if (argc < 1 && C.outfile == nil) {
+ printf("usage: cc [-options] files\n");
+ exit(1);
+ }
+
+ // NOTE: This is just for my comfort during debugging.
+ pushinclude("/home/nolln/root/include");
+ pushinclude("/home/nolln/root/include/vendor/libc");
+
+ src = (argc == 0) ? "<stdin>" : argv[0];
+ intern(&src);
+
+ if ((err = compile(src)), err) {
+ exit(2);
+ }
+
+ exit(0);
+}
diff --git a/src/cmd/cc/cc.h b/src/cmd/cc/cc.h
new file mode 100644
index 0000000..8fc5f73
--- /dev/null
+++ b/src/cmd/cc/cc.h
@@ -0,0 +1,806 @@
+#pragma once
+
+#include <u.h>
+#include <libn.h>
+
+#define iota(x) 1 << (x)
+
+/* core types */
+typedef struct Io Io;
+typedef struct Pos Pos;
+typedef struct Range Range;
+typedef struct Token Token;
+
+typedef struct Lexer Lexer;
+
+typedef struct Sym Sym;
+typedef struct Type Type;
+typedef struct Scope Scope;
+
+typedef struct Parser Parser;
+
+typedef struct Ptr Ptr;
+typedef struct Name Name;
+typedef struct Dtor Dtor;
+typedef struct Field Field;
+
+typedef struct Node Node;
+typedef struct Decl Decl;
+typedef struct Stmt Stmt;
+typedef struct Expr Expr;
+
+typedef struct SymTab SymTab;
+typedef struct StrTab StrTab;
+
+typedef struct Compiler Compiler;
+
+/* keywords of language */
+#define KEYWORDS \
+ KEYWORD(Kauto,"auto") \
+ KEYWORD(Kregister,"register") \
+ KEYWORD(Kstatic,"static") \
+ KEYWORD(Kextern,"extern") \
+ KEYWORD(Ktls,"thread_local") \
+ KEYWORD(Ktypedef,"typedef") \
+ KEYWORD(Kinline,"inline") \
+ KEYWORD(Knoret,"_Noreturn") \
+ KEYWORD(Kconst,"const") \
+ KEYWORD(Kvolatile,"volatile") \
+ KEYWORD(Krestrict,"restrict") \
+ KEYWORD(Katomic,"_Atomic") \
+ KEYWORD(Ksigned,"signed") \
+ KEYWORD(Kunsigned,"unsigned") \
+ KEYWORD(Kvoid,"void") \
+ KEYWORD(Kbool,"_Bool") \
+ KEYWORD(Kchar,"char") \
+ KEYWORD(Kfloat,"float") \
+ KEYWORD(Kdouble,"double") \
+ KEYWORD(Kcomplex,"complex") \
+ KEYWORD(Kimaginary,"imaginary") \
+ KEYWORD(Kint,"int") \
+ KEYWORD(Kshort,"short") \
+ KEYWORD(Klong,"long") \
+ KEYWORD(Kstruct,"struct") \
+ KEYWORD(Kunion,"union") \
+ KEYWORD(Kenum,"enum") \
+ KEYWORD(Kfor,"for") \
+ KEYWORD(Kdo,"do") \
+ KEYWORD(Kwhile,"while") \
+ KEYWORD(Kcontinue,"continue") \
+ KEYWORD(Kif,"if") \
+ KEYWORD(Kelse,"else") \
+ KEYWORD(Kswitch,"switch") \
+ KEYWORD(Kcase,"case") \
+ KEYWORD(Kdefault,"default") \
+ KEYWORD(Kbreak,"break") \
+ KEYWORD(Kgoto,"goto") \
+ KEYWORD(Kreturn,"return") \
+ KEYWORD(Ksizeof,"sizeof") \
+ KEYWORD(Kalignof,"alignof") \
+ KEYWORD(Kalignas,"alignas")
+
+#define KEYWORD(a, b) a,
+enum { KEYWORDS NUM_KEYWORDS };
+#undef KEYWORD
+
+extern byte *keywords[NUM_KEYWORDS];
+
+// -----------------------------------------------------------------------
+// lexing: byte stream -> tokens
+// pre-processor built in
+
+/* source position: error reporting */
+struct Pos
+{
+ int col;
+ int line;
+ string path;
+};
+
+
+struct Range
+{
+ Pos beg;
+ Pos end;
+};
+
+void errorat(Pos x, byte *fmt, ...);
+void warnat(Pos x, byte *fmt, ...);
+
+/* pre-processor */
+#define DIRECTIVES \
+ DIRECTIVE(Dpragma,"pragma", ppprag) \
+ DIRECTIVE(Dinclude,"include", ppinc) \
+ DIRECTIVE(Ddefine,"define", ppdef) \
+ DIRECTIVE(Dundef,"undef", ppund) \
+ DIRECTIVE(Dif,"if", ppif0) \
+ DIRECTIVE(Delif,"elif", ppif1) \
+ DIRECTIVE(Delse, "else", ppif1) \
+ DIRECTIVE(Difdef,"ifdef", ppif2) \
+ DIRECTIVE(Difndef,"ifndef", ppif3) \
+ DIRECTIVE(Dendif,"endif", ppend)
+
+#define DIRECTIVE(a, b, c) a,
+enum { DIRECTIVES NUM_DIRECTIVES };
+#undef DIRECTIVE
+
+extern byte *directives[NUM_DIRECTIVES];
+
+error domacro(Lexer*);
+error dodefine(Lexer *lx, string s);
+int expandmacro(Lexer *lx, Sym *s, byte *dst);
+
+extern error (*macros[NUM_DIRECTIVES])(Lexer*);
+
+/* tokenization of byte stream */
+#define TOKENS \
+ TOK(Anil,"nil") \
+ TOK(Aeof,"eof") \
+ TOK(Aeq, "==") \
+ TOK(Aneq, "!=") \
+ TOK(Anot, "!") \
+ TOK(Aneg, "~") \
+ TOK(Axor, "^") \
+ TOK(Aor, "|") \
+ TOK(Aand, "&") \
+ TOK(Aoror, "||") \
+ TOK(Aandand, "&&") \
+ TOK(Aadd,"+") \
+ TOK(Asub,"-") \
+ TOK(Astar,"*") \
+ TOK(Adiv,"/") \
+ TOK(Amod,"%") \
+ TOK(Agt,">") \
+ TOK(Alt,"<") \
+ TOK(Agteq,">=") \
+ TOK(Alteq,"<=") \
+ TOK(Alsft,"<<") \
+ TOK(Arsft,">>") \
+ TOK(Ainc,"++") \
+ TOK(Adec,"--") \
+ TOK(Aasn,"=") \
+ TOK(Aorasn,"|=") \
+ TOK(Axorasn,"^=") \
+ TOK(Aandasn,"&=") \
+ TOK(Aaddasn,"+=") \
+ TOK(Asubasn,"-=") \
+ TOK(Amulasn,"*=") \
+ TOK(Adivasn,"/=") \
+ TOK(Amodasn,"%=") \
+ TOK(Alsftasn,"<<=") \
+ TOK(Arsftasn,">>=") \
+ TOK(Acomma,",") \
+ TOK(Acolon,":") \
+ TOK(Asemi,";") \
+ TOK(Alparen,"(") \
+ TOK(Arparen,")") \
+ TOK(Albrace,"{") \
+ TOK(Arbrace,"}") \
+ TOK(Albrakt,"[") \
+ TOK(Arbrakt,"]") \
+ TOK(Adot,".") \
+ TOK(Aarrow,"->") \
+ TOK(Aqmark,"?") \
+ TOK(Aellip,"...") \
+ TOK(Alit,"<literal>") \
+ TOK(Aident,"<identifier>") \
+ TOK(Akeywd,"<keyword>") \
+
+#define TOK(a, b) a,
+enum
+{
+ TOKENS
+ NUM_TOKENS,
+
+ Vchar = iota(8),
+ Vrune = iota(9),
+ Vint = iota(10),
+ Vlong = iota(11),
+ Vvlong = iota(12),
+ Vun = iota(13),
+ Vfloat = iota(14),
+ Vstr = iota(15),
+ Vwstr = iota(16),
+
+ Vmask = Vchar - 1,
+};
+#undef TOK
+
+extern byte *tokens[NUM_TOKENS];
+
+/* TODO: store literals in a big val */
+union Val
+{
+ byte *s;
+ double f;
+ vlong i;
+ uvlong ui;
+ int32 c;
+ uint32 uc;
+ rune r;
+};
+
+struct Token
+{
+ uint32 kind;
+ Pos pos;
+ union Val val;
+};
+
+enum
+{
+ Svar = iota(1),
+ Sfunc = iota(2),
+ Stype = iota(3),
+ Stag = iota(4),
+ Senum = iota(5),
+ Slabl = iota(6),
+ Smacro = iota(7),
+};
+
+struct Sym
+{
+ uint32 kind;
+ string name;
+ union {
+ string macro;
+ Decl *obj;
+ int32 type;
+ Stmt *blk;
+ Expr *val;
+ };
+};
+
+struct SymTab
+{
+ int32 n_buckets;
+ int32 size;
+ int32 n_occupied;
+ int32 upper_bound;
+ int32 *flags;
+ string *keys;
+ Sym **vals;
+};
+
+Sym *define(SymTab *tab, string ident, uint32 kind);
+Sym *lookup(SymTab *tab, string ident);
+error forget(SymTab *tab, string ident);
+void forgetall(SymTab *tab);
+
+enum
+{
+ IOnil = iota(0),
+ IOfile = iota(1),
+ IObuff = iota(2),
+};
+
+struct Io
+{
+ io·Buffer rdr;
+ string path;
+ uint32 kind;
+ union {
+ Stream *f;
+ byte *b;
+ };
+
+ Pos store;
+ struct Io *link;
+};
+
+struct Lexer
+{
+ Pos pos;
+ SymTab sym;
+ byte *b;
+ byte buf[2*1024];
+
+ /* predefined dynamic macros */
+ uintptr macfile;
+ uintptr macline;
+
+ /* i/o data */
+ Io *io, *new;
+ Io iostk[100];
+ struct {
+ int cap;
+ int len;
+ string *path;
+ } omit;
+};
+
+/* lex.c functions */
+Token lex(Lexer *);
+
+int getbyte(Lexer *);
+int getnsbyte(Lexer *l);
+rune getrune(Lexer *);
+byte ungetbyte(Lexer *);
+rune ungetrune(Lexer *, rune r);
+
+Io* openio(Lexer *lx, byte *path);
+void pushio(Lexer *lx, Io *new);
+void popio(Lexer *lx);
+
+void puttok(Token);
+
+// -----------------------------------------------------------------------
+// parsing & type resolution
+// tokens -> ast
+
+/* parent data */
+struct Node
+{
+ Range pos;
+ uint32 kind;
+};
+
+/* ast types */
+enum
+{
+ Nbad,
+ /* labels */
+ Sempty, Slabel, Scase,
+ Sblock,
+ Sexpr, Sdecl,
+ Sselect,
+ /* loops */
+ Sfor, Swhile, Sdo,
+ /* jumps */
+ Sgoto, Scontin, Sbreak, Sreturn,
+ /* forks */
+ Sif, Sswitch,
+
+
+ /* assignments */
+ Xasn, Xmulasn, Xdivasn, Xmodasn, Xsubasn, Xaddasn,
+ Xlsftasn, Xrsftasn, Xandasn, Xxorasn, Xorasn,
+ /* conditional */
+ Xternary,
+ /* unary prefix ops */
+ Xref, Xstar, Xplus, Xminus, Xneg, Xnot, Xsizeof, Xalignof, Xpreinc, Xpredec,
+ Xcast,
+ /* unary postfix ops */
+ Xpostinc, Xpostdec, Xindex, Xcall, Xselp, Xself, Xinitlist,
+ /* binary ops */
+ Xoror, Xandand, Xor, Xxor, Xand, Xneq, Xeql, Xgt, Xlt, Xgteq, Xlteq, Xlsft, Xrsft,
+ Xadd, Xsub, Xmul, Xdiv, Xmod,
+ /* primary */
+ Xparen, Xident, Xlit,
+ /* lists */
+ Xcomma,
+
+
+ Dvar,
+ Dfunc,
+ Dtype,
+ Dlist = iota(20),
+ Dvars = Dvar | Dlist,
+ Dtypes = Dtype | Dlist,
+
+ /* names (don't interact w/ final AST) */
+ Nnil = 0,
+ Nident,
+ Nparen,
+ Nindex,
+ Ncall,
+};
+
+/* expressions */
+enum
+{
+ Keynil,
+ Keyidx,
+ Keysel,
+};
+
+struct Key
+{
+ uint kind : 2;
+ union {
+ Expr *x;
+ string s;
+ };
+};
+
+struct Expr
+{
+ struct Node;
+ uint32 qual;
+ uint32 type;
+ union {
+ string name;
+ struct {
+ uint64 kind;
+ union {
+ union Val;
+ union Val v;
+ };
+ } val;
+ struct {
+ int n;
+ struct Key *k;
+ Expr *v;
+ } init;
+ Expr *x;
+ struct {
+ Expr *l;
+ Expr *r;
+ } asn;
+ struct {
+ Expr *c;
+ Expr *t;
+ Expr *e;
+ } cond;
+ struct {
+ Expr *x;
+ union {
+ Expr *i;
+ string f;
+ };
+ } idx;
+ struct {
+ Expr *fn;
+ int n;
+ Expr **arg;
+ } call;
+ union {
+ Expr *pre;
+ Expr *post;
+ } unary;
+ struct {
+ int type : 1;
+ union {
+ struct {
+ uint32 qual;
+ uint32 type;
+ } of;
+ Expr *x;
+ };
+ } info;
+ struct {
+ struct {
+ uint32 qual;
+ uint32 type;
+ } to;
+ Expr *x;
+ } cast;
+ struct {
+ Expr *l;
+ Expr *r;
+ } binary;
+ struct {
+ Expr *x[2];
+ } comma;
+ };
+};
+
+
+/* statements */
+struct Stmt
+{
+ struct Node;
+ union {
+ struct {
+ union {
+ string ident;
+ Expr *x;
+ };
+ Node *stmt;
+ } lbl;
+ struct {
+ long n;
+ struct Node **item;
+ } blk;
+ Expr *x;
+ struct {
+ Node *init;
+ Expr *cond;
+ Expr *step;
+ Node *body;
+ } loop;
+ union{
+ string lbl;
+ Expr *x;
+ } jmp;
+ struct {
+ Expr *cond;
+ Node *body;
+ Node *orelse;
+ } br;
+ };
+};
+
+/* declarations */
+
+/*
+ * specifiers
+ * the design is the following:
+ * type info is held w/in a 64 bit integer.
+ * the bottom 32 bits are associated to specializations
+ * the top 32 bits index into a type-info array held by the compiler.
+ */
+enum
+{
+ /* memory */
+ Mauto = iota(Kauto),
+ Mstatic = iota(Kstatic),
+ Mreg = iota(Kregister),
+ Mtls = iota(Ktls),
+ Mtype = iota(Ktypedef),
+ Mextern = iota(Kextern),
+
+ MaskMem = Mauto | Mstatic | Mreg | Mtls | Mtype | Mextern,
+
+ /* qualifiers */
+ Qconst = iota(Kconst),
+ Qrestr = iota(Krestrict),
+ Qvoltl = iota(Kvolatile),
+ Qatom = iota(Katomic),
+
+ MaskQul = Qconst | Qrestr | Qvoltl | Qatom,
+
+ Finlne = iota(Kinline),
+ Fnoret = iota(Knoret),
+
+ MaskFcn = Finlne | Fnoret,
+
+ /* types */
+ Tsign = iota(Ksigned),
+ Tunsign = iota(Kunsigned),
+
+ MaskSgn = Tsign | Tunsign,
+
+ Tvoid = iota(Kvoid),
+ Tfloat = iota(Kfloat),
+ Tdouble = iota(Kdouble),
+ Tcmplx = iota(Kcomplex),
+ Timag = iota(Kimaginary),
+
+ MaskFlt = Tfloat | Tdouble | Tcmplx | Timag,
+
+ Tchar = iota(Kchar),
+ Tbool = iota(Kbool),
+
+ Tshort = iota(Kshort),
+ Tint = iota(Kint),
+ Tlong = iota(Klong),
+ Tvlong = iota(Klong+1),
+
+ MaskInt = Tshort | Tint | Tlong | Tvlong,
+ MaskTyp = Tvoid | Tbool | Tchar | Tint | Tfloat | Timag | Tcmplx,
+ /*
+ * NOTE IMPORTANT: vlong takes over the struct bit place
+ * DON'T MOVE KEYWORDS WITHOUT REORGANIZING
+ */
+ Tstruct = iota(Kstruct+1),
+ Tunion = iota(Kunion+1),
+ Tenum = iota(Kenum+1),
+ Tname = iota(Kenum+2),
+
+ Sbad = -1,
+};
+
+/* intermediate nodes */
+struct Ptr
+{
+ uint64 kind;
+ Ptr *link;
+};
+
+struct Name
+{
+ uint32 kind;
+ union {
+ string ident;
+ struct Dtor *paren;
+ struct {
+ Name *name;
+ union {
+ struct {
+ uint32 q;
+ Expr *x;
+ } idx;
+ struct {
+ int n;
+ int dots : 1;
+ Field *arg;
+ } call;
+ };
+ } sfx;
+ };
+};
+
+struct Dtor
+{
+ Ptr ptr;
+ Name *name;
+};
+
+/* final ast node */
+
+struct Field
+{
+ uint32 qual;
+ uint32 type;
+ string name;
+};
+
+struct Decls
+{
+ string name;
+ uint32 type;
+ Expr *init;
+ struct Decls *link;
+};
+
+
+struct Decl
+{
+ struct Node;
+ uint32 spec;
+ union {
+ struct {
+ string name;
+ uint32 type;
+ union {
+ Stmt *body;
+ Expr *init;
+ };
+ };
+ struct Decls list;
+ };
+};
+
+enum
+{
+ Tbad,
+ Tbase,
+ Tdef,
+ Tptr,
+ Tarray,
+ Tfunc,
+};
+
+/* types */
+struct Type
+{
+ uint32 kind;
+ Sym *sym;
+ uintptr size;
+ uintptr max;
+ uint16 align : 8;
+ uint8 sign : 2;
+ union {
+ struct {
+ uint32 qual;
+ uint32 base;
+ } ptr;
+ struct {
+ int len;
+ uint32 qual;
+ uint32 *elt;
+ } arr;
+ struct {
+ int len;
+ Field *f;
+ Expr *x;
+ } aggr;
+ struct {
+ int len;
+ string *elt;
+ Expr *val;
+ } enm;
+ struct {
+ uint32 ret;
+ int n;
+ int dots : 1;
+ Field *arg;
+ } func;
+ };
+};
+
+/* platform specific */
+extern Type pointer;
+extern Type basetypes[24];
+/* mandated by C standard */
+extern uint64 validtypespec[38];
+extern int indextypespec[38];
+
+struct Scope
+{
+ SymTab tags;
+ SymTab objs;
+};
+
+struct Parser
+{
+ Token tok[2];
+ struct {
+ int cap;
+ int len;
+ Decl **decls;
+ } ast;
+
+ /* static buffers/stacks */
+ Scope *sp;
+ Scope spstk[40];
+
+ Name *nm;
+ Name nmstk[40];
+
+ Ptr *pt;
+ Ptr ptstk[10];
+
+ Dtor *dt;
+ Dtor dtstk[40];
+};
+
+/* ast.c functions */
+error parse(Parser *, Lexer *);
+
+// -----------------------------------------------------------------------
+// global compiler data
+
+struct StrTab
+{
+ int32 n_buckets;
+ int32 size;
+ int32 n_occupied;
+ int32 upper_bound;
+ int32 *flags;
+ string *keys;
+ int32 *vals;
+};
+
+#if 0
+struct TypeSet
+{
+ int32 n_buckets;
+ int32 size;
+ int32 n_occupied;
+ int32 upper_bound;
+ int32 *flags;
+ Type **keys;
+};
+#endif
+
+/* main data */
+struct Compiler
+{
+ mem·Arena *heap;
+ StrTab strs;
+ string outfile;
+
+ struct {
+ int cap;
+ int len;
+ string *val;
+ } def;
+
+ struct {
+ int cap;
+ int len;
+ string *dir;
+ } inc;
+
+ struct {
+ int cap;
+ int len;
+ Type *info;
+ } type;
+
+ /* TODO: make array */
+ struct {
+ Decl vargs;
+ } builtin;
+};
+
+extern Compiler C;
+
+/* cc.c functions */
+void init();
+int32 intern(byte **str);
+int32 type();
+
+#undef iota
diff --git a/src/cmd/cc/lex.c b/src/cmd/cc/lex.c
new file mode 100644
index 0000000..33fc5d0
--- /dev/null
+++ b/src/cmd/cc/lex.c
@@ -0,0 +1,873 @@
+#include "cc.h"
+#include <libn/macro/map.h>
+
+// -----------------------------------------------------------------------
+// printing functions
+
+void
+puttok(Token tok)
+{
+ if (tok.kind < Alit)
+ printf("%s", tokens[tok.kind]);
+ else if (tok.kind & Alit) {
+ if (tok.kind & Vchar)
+ if (tok.kind & Vint)
+ if (tok.kind & Vlong)
+ if (tok.kind & Vvlong)
+ printf("literal <%lld>", tok.val.i);
+ if (tok.kind & Vfloat)
+ printf("literal <%f>", tok.val.f);
+ printf("literal <%s>", tok.val.s);
+ } else
+ printf("ident <%s>", tok.val.s);
+}
+
+// -----------------------------------------------------------------------
+// io buffer management
+
+#define asrdr(x) (io·Reader){(int (*)(void *, int, int, void *))x}
+
+// path should be absolute
+Io*
+openio(Lexer *lx, byte *path)
+{
+ string *it, *end;
+
+ intern(&path);
+
+ // See if we have already opened file;
+ // If so, and it hasn't been flagged return it
+ for (it = lx->omit.path, end = it + lx->omit.len; it < end; ++it) {
+ if ((uintptr)(*it) == (uintptr)(path))
+ return nil;
+ }
+
+ // TODO: See if we have already loaded the file
+
+ if ((lx->new - lx->iostk) >= arrlen(lx->iostk)-1)
+ panicf("out of I/O space!");
+
+ lx->new->f = io·open(path, "r");
+ if (!lx->new->f)
+ panicf("file %s not found", path);
+
+ lx->new->kind = IOfile;
+ lx->new->path = path;
+ bufio·initreader(&lx->new->rdr, asrdr(io·read), lx->new->f);
+
+ return lx->new++;
+}
+
+static
+Io*
+makeio(Lexer *lx, byte *name)
+{
+ if ((lx->new - lx->iostk) >= arrlen(lx->iostk)-1)
+ panicf("out of I/O space!");
+
+ lx->new->path = name;
+ lx->new->rdr = (io·Buffer) {
+ .state = bufio·rdr | bufio·end,
+ .runesize = 0,
+ .h = nil,
+ .size = bufio·size,
+ .beg = lx->new->rdr.buf + bufio·ungets,
+ .pos = lx->new->rdr.buf + bufio·ungets,
+ .end = lx->new->rdr.buf + bufio·ungets,
+ };
+ lx->new->b = lx->new->rdr.beg;
+
+ return lx->new++;
+}
+#undef asrdr
+
+static
+void
+freeio(Lexer *lx, Io *io)
+{
+ if (io->kind & IOfile) {
+ io·close(io->f);
+ }
+
+ io->rdr.state = 0;
+ io->kind = 0;
+ io->link = nil;
+ io->path = nil;
+ io->store = (Pos){ 0 };
+ io->path = "<empty>";
+}
+
+void
+pushio(Lexer *lx, Io *new)
+{
+ new->link = lx->io;
+ lx->io->store = lx->pos;
+ lx->io = new;
+
+ lx->pos = (Pos){
+ .line = 1,
+ .col = 1,
+ .path = new->path,
+ };
+}
+
+void
+popio(Lexer *lx)
+{
+ Io *prev;
+
+ assert(lx->io == lx->new-1);
+ --lx->new;
+
+ prev = lx->io->link;
+ freeio(lx, lx->io);
+
+ lx->io = prev;
+ if (!prev) {
+ return;
+ }
+
+ lx->pos = prev->store;
+}
+
+// -----------------------------------------------------------------------
+// simple wrappers
+
+int
+getbyte(Lexer *lx)
+{
+ return bufio·getbyte(&lx->io->rdr);
+}
+
+int
+getnsbyte(Lexer *lx)
+{
+ int b;
+ b = getbyte(lx);
+ for (;;) {
+ if (b == EOF) {
+ if (lx->io->link) {
+ popio(lx);
+ assert(lx->io);
+ b = getbyte(lx);
+ continue;
+ } else
+ return b;
+ }
+ if (b >= RuneSelf || !isspace(b))
+ return b;
+ if (b == '\n')
+ return b;
+ b = getbyte(lx);
+ }
+ return b;
+}
+
+rune
+getrune(Lexer *lx)
+{
+ return bufio·getrune(&lx->io->rdr);
+}
+
+byte
+ungetbyte(Lexer *lx)
+{
+ byte b;
+ return bufio·ungetbyte(&lx->io->rdr, b);
+}
+
+rune
+ungetrune(Lexer *l, rune r)
+{
+ return bufio·ungetrune(&l->io->rdr, r);
+}
+
+// -----------------------------------------------------------------------
+// main lexer
+
+#define TOK(a, b) b,
+byte *tokens[NUM_TOKENS] = { TOKENS };
+#undef TOK
+
+static uint8 Atoi[256] =
+{
+ ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5,
+ ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['a'] = 10, ['A'] = 10,
+ ['b'] = 11, ['B'] = 11, ['c'] = 12, ['C'] = 12, ['d'] = 13, ['D'] = 13,
+ ['e'] = 14, ['E'] = 14, ['f'] = 15, ['F'] = 15,
+};
+
+static
+error
+escapechar(Lexer *lx, int x, int islong, int esc, vlong *val)
+{
+ int i, u, c;
+ vlong l;
+
+ c = getrune(lx);
+
+ switch (c) {
+ case '\\':
+ break;
+ case EOF:
+ errorat(lx->pos, "EOF in string");
+ return 1;
+ case '\n':
+ errorat(lx->pos, "newline in string");
+ return 1;
+ default:
+ if (c == x)
+ return 1;
+ *val = c;
+ return 0;
+ }
+
+ u = 0;
+ c = getrune(lx);
+
+ switch(c) {
+ case 'x':
+ i = islong ? 4 : 2;
+ goto hex;
+
+ case 'u':
+ i = islong ? 8 : 4;
+ u = 1;
+ goto hex;
+
+ case 'U':
+ i = 8;
+ u = 1;
+ goto hex;
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ i = islong ? 4 : 2;
+ goto oct;
+
+ case 'a': c = '\a'; break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case '\\':c = '\\'; break;
+
+ default:
+ if(c != x) errorat(lx->pos, "unknown escape sequence: %c", c);
+ }
+ *val = c;
+ return 0;
+
+hex:
+ l = 0;
+ for(; i > 0; i--) {
+ c = getbyte(lx);
+ if (c >= '0' && c <= '9') {
+ l = l*16 + c-'0';
+ continue;
+ }
+ if (c >= 'a' && c <= 'f') {
+ l = l*16 + c-'a' + 10;
+ continue;
+ }
+ if (c >= 'A' && c <= 'F') {
+ l = l*16 + c-'A' + 10;
+ continue;
+ }
+ ungetbyte(lx);
+ break;
+ }
+ if (u && (l > RuneMax || (0xd800 <= l && l < 0xe000))) {
+ errorat(lx->pos, "invalid unicode code point in escape sequence: %#llx", l);
+ l = RuneErr;
+ }
+ *val = l;
+ if (esc)
+ *val |= RuneMask + 1;
+ return 0;
+
+oct:
+ l = c - '0';
+ for (; i > 0; i--) {
+ c = getbyte(lx);
+ if (c >= '0' && c <= '7') {
+ l = l*8 + c-'0';
+ continue;
+ }
+ ungetbyte(lx);
+ break;
+ }
+ if (l > 255) errorat(lx->pos, "octal escape value > 255: %d", l);
+
+ *val = l;
+ if (esc)
+ *val |= RuneMask + 1;
+ return 0;
+}
+
+#define CASE1(stmt1, kind1) \
+ case stmt1: \
+ tok.kind = kind1; \
+ goto Return
+
+#define CASE2(stmt1, kind1, b1, kind2) \
+ case stmt1: \
+ tok.kind = kind1; \
+ b = getbyte(lx); \
+ if (b == b1) \
+ tok.kind = kind2; \
+ else \
+ ungetbyte(lx); \
+ goto Return
+
+#define CASE3(stmt1, kind1, b1, kind2, b2, kind3) \
+ case stmt1: \
+ tok.kind = kind1; \
+ b = getbyte(lx); \
+ if (b == b1) \
+ tok.kind = kind2; \
+ else if (b == b2) \
+ tok.kind = kind3; \
+ else \
+ ungetbyte(lx); \
+ goto Return
+
+#define CASE4(stmt1, kind1, b1, kind2, b2, kind3, b3, type4) \
+ case stmt1: \
+ tok.kind = kind1; \
+ b = getbyte(lx); \
+ if (b == b1) \
+ tok.kind = kind2; \
+ else if (b == b2) \
+ tok.kind = kind3; \
+ else if (b == b3) \
+ tok.kind = type4; \
+ else \
+ ungetbyte(lx); \
+ goto Return
+
+
+Token
+lex(Lexer *lx)
+{
+ int b, n, f;
+ vlong v, _;
+ rune r;
+ string s;
+ double d;
+ byte *e;
+ Token tok;
+ Sym *sym;
+ Io *io;
+
+GetByte:
+ b = getbyte(lx);
+Dispatch:
+ tok.pos = lx->pos;
+
+ if ((b != EOF && b >= RuneSelf) || b == '_')
+ goto Talpha;
+ if (isalpha(b)) {
+ if (b != 'L')
+ goto Talpha;
+
+ n = b;
+ b = getbyte(lx);
+ if (b == '\'') {
+ if (escapechar(lx, '\'', 1, 0, &v))
+ b = '\'';
+ if (!escapechar(lx, '\'', 1, 0, &_)) {
+ errorat(lx->pos, "missing ' at end of character constant");
+ }
+ tok.kind = Alit | Vrune;
+ tok.val.r = v;
+ goto Return;
+ }
+ if (b == '"')
+ goto TLstr;
+ ungetbyte(lx);
+ b = n;
+
+ goto Talpha;
+ }
+ if (isdigit(b))
+ goto Tnum;
+
+ switch (b) {
+ case '\n':
+ lx->pos.line++;
+ case ' ': case '\r': case '\t': case '\v': case '\f':
+ while (b = getbyte(lx), isspace(b))
+ if (b == '\n')
+ lx->pos.line++;
+ goto Dispatch;
+
+ case '\\':
+ b = getbyte(lx);
+ if (b != '\n')
+ errorat(lx->pos, "'\\' without a trailing newline");
+ goto GetByte;
+
+ Tchar:
+ case '\'':
+ if (escapechar(lx, '\'', 0, 0, &v)) {
+ errorat(lx->pos, "empty literal or escaped ' in char literal");
+ v = '\'';
+ }
+ if (!escapechar(lx, '\'', 0, 0, &_)) {
+ errorat(lx->pos, "missing '");
+ ungetbyte(lx);
+ }
+
+ if (v > 0xff) {
+ errorat(lx->pos, "overflowed character literal");
+ v = 0;
+ }
+ tok.kind = Alit | Vchar;
+ tok.val.c = v;
+ goto Return;
+
+ case '"':
+ s = str·makecap("", 0, 8);
+ for (;;) {
+ if (escapechar(lx, '"', 0, 1, &v))
+ break;
+
+ if (v & (RuneMask + 1))
+ str·appendbyte(&s, v);
+ else {
+ r = v;
+ b = utf8·runelen(r);
+ utf8·runetobyte(lx->buf, &r);
+ str·appendlen(&s, b, lx->buf);
+ }
+ }
+ tok.kind = Alit | Vstr;
+ tok.val.s = s;
+ intern(&tok.val.s);
+
+ str·free(s);
+ goto Return;
+
+ TLstr:
+ s = str·makecap("", 0, 8);
+ // NOTE: this violates strict aliasing
+ for (;;) {
+ if (escapechar(lx, '"', 1, 0, &v))
+ break;
+ str·appendlen(&s, sizeof(wchar_t), (byte*)&v);
+ }
+ tok.kind = Alit | Vwstr;
+ tok.val.s = s;
+ intern(&tok.val.s);
+
+ str·free(s);
+ goto Return;
+
+ case '.':
+ tok.kind = Adot;
+ b = getbyte(lx);
+
+ if (isdigit(b)) {
+ // *lx->b++ = b;
+ goto Tflt;
+ } else if (b == '.') {
+ b = getbyte(lx);
+ if (b != '.') {
+ errorat(lx->pos, "invalid token '..'");
+ tok.kind = Aellip;
+ break;
+ }
+ }
+ ungetbyte(lx);
+ goto Return;
+
+ case '<':
+ tok.kind = Alt;
+ b = getbyte(lx);
+
+ if (b == '<') {
+ tok.kind = Alsft;
+ b = getbyte(lx);
+ if (b == '=')
+ tok.kind = Alsftasn;
+ else
+ ungetbyte(lx);
+ } else if (b == '=')
+ tok.kind = Alteq;
+ else
+ ungetbyte(lx);
+ goto Return;
+
+ case '>':
+ tok.kind = Agt;
+ b = getbyte(lx);
+
+ if (b == '>') {
+ tok.kind = Arsft;
+ b = getbyte(lx);
+ if (b == '=')
+ tok.kind = Arsftasn;
+ else
+ ungetbyte(lx);
+ } else if (b == '=')
+ tok.kind = Agteq;
+ else
+ ungetbyte(lx);
+ goto Return;
+
+ case '/':
+ tok.kind = Adiv;
+ b = getbyte(lx);
+
+ if (b == '=')
+ tok.kind = Adivasn;
+ else if (b == '/') {
+ while (b != EOF && b != '\n')
+ b = getbyte(lx);
+ goto Dispatch;
+ } else if (b == '*') {
+ int level = 1;
+ b = getbyte(lx);
+ while (b != EOF && level > 0) {
+ if (b == '/') {
+ b = getbyte(lx);
+ if (b == '*')
+ level++;
+ } else if (b == '*') {
+ b = getbyte(lx);
+ if (b == '/')
+ level--;
+ }
+ if (b == '\n')
+ lx->pos.line++;
+ b = getbyte(lx);
+ }
+ goto Dispatch;
+ } else
+ ungetbyte(lx);
+ goto Return;
+
+ case '#':
+ if (domacro(lx)) {
+ tok.kind = Anil;
+ errorat(lx->pos, "failed to perform preprocessor directive");
+ return tok;
+ }
+ goto GetByte;
+
+ case EOF:
+ popio(lx);
+ if (lx->io)
+ goto GetByte;
+ tok.kind = Aeof;
+ goto Return;
+
+ CASE1('(', Alparen);
+ CASE1(')', Arparen);
+ CASE1('{', Albrace);
+ CASE1('}', Arbrace);
+ CASE1('[', Albrakt);
+ CASE1(']', Arbrakt);
+ CASE1(',', Acomma);
+ CASE1('?', Aqmark);
+ CASE1(';', Asemi);
+ CASE1('~', Aneg);
+ CASE1(':', Acolon);
+ CASE2('^', Axor, '=', Axorasn);
+ CASE2('!', Anot, '=', Aneq);
+ CASE2('*', Astar,'=', Amulasn);
+ CASE2('=', Aasn, '=', Aeq);
+ CASE2('%', Amod, '=', Amodasn);
+ CASE3('+', Aadd, '=', Aaddasn, '+', Ainc);
+ CASE3('&', Aand, '=', Aandasn, '&', Aandand);
+ CASE3('|', Aor, '=', Aorasn, '|', Aoror);
+ CASE4('-', Asub, '=', Asubasn, '-', Adec, '>', Aarrow);
+
+ Tnum:
+ e = lx->buf + arrlen(lx->buf);
+ do {
+ if (lx->b >= e) {
+ errorat(lx->pos, "number overflows lexer buffer");
+ goto Nospace;
+ }
+ *lx->b++ = b;
+ } while (b = getbyte(lx), isdigit(b) || b == '_');
+
+ if (b == '.' || tolower(b) == 'e')
+ goto Tflt;
+ Tint:
+ n = 10;
+ s = lx->buf;
+ if (*s == '0') {
+ switch (b) {
+ case 'x': n = 16; break;
+ case 'b': n = 2; break;
+ case 'o': n = 8; break;
+ default: goto Rint;
+ }
+ lx->b = s;
+ /* reparse number, now with base info */
+ while (b = getbyte(lx), (isdigit(b) ||
+ ('a' <= b && b <= 'f') ||
+ ('A' <= b && b <= 'F') ||
+ b == '_'))
+ *lx->b++ = b;
+ }
+ Rint:
+ v = 0;
+ r = b;
+ for (; s != lx->b ; s++) {
+ b = *s;
+ if (b == '_') continue;
+
+ f = Atoi[b];
+ if (f == 0 && b != '0')
+ break;
+
+ if (f >= n) {
+ errorat(lx->pos, "digit '%c' out of range for base %d", b, n);
+ f = 0;
+ }
+
+ if (v > (UINT64_MAX - f) / n) {
+ errorat(lx->pos, "integer literal overflow");
+ v = 0;
+ break;
+ }
+
+ v = v * n + f;
+ }
+
+ b = r;
+ tok.kind = Alit;
+ tok.val.i = v;
+
+ if (b == 'u' || b == 'U') {
+ tok.kind |= Vun;
+ b = getbyte(lx);
+ }
+ if (b == 'l' || b == 'L') {
+ r = getbyte(lx);
+ if (r == 'l' || r == 'L') {
+ if (r != b)
+ errorat(lx->pos, "mismatched case on long long integer suffix");
+ tok.kind |= Vvlong;
+ r = getbyte(lx);
+ } else
+ tok.kind |= Vlong;
+
+ if (r == 'u' || r == 'U') {
+ if (tok.kind & Vun)
+ errorat(lx->pos, "multiple unsigned designators on integer suffix");
+ tok.kind |= Vun;
+ goto Return;
+ }
+
+ ungetbyte(lx);
+ goto Return;
+ }
+
+ tok.kind |= Vint;
+ ungetbyte(lx);
+ goto Return;
+
+ Tflt:
+ if (b == '.') {
+ *lx->b++ = b;
+ b = getbyte(lx);
+ }
+
+ while (isdigit(b)) {
+ *lx->b++ = b;
+
+ if (lx->b >= e) {
+ errorat(lx->pos, "number overflows lexer buffer");
+ goto Nospace;
+ }
+ }
+
+ if (tolower(b) == 'e') {
+ b = getbyte(lx);
+ if (b == '-' || b == '+')
+ b = getbyte(lx);
+
+ if (!isdigit(b))
+ errorat(lx->pos, "expected number after exponent, found %c", b);
+
+ do {
+ *lx->b++ = b;
+ } while (b = getbyte(lx), isdigit(b));
+ }
+ *lx->b = '\0';
+ d = strtod(lx->buf, nil);
+ ungetbyte(lx);
+
+ tok.kind = Alit | Vfloat;
+ tok.val.f = d;
+
+ goto Return;
+
+ Talpha:
+ s = lx->buf;
+ e = lx->buf + arrlen(lx->buf);
+ for (;;) {
+ if (s >= e) {
+ errorat(lx->pos, "identifier too long for buffer: %s", s);
+ goto Nospace;
+ }
+ if (b != EOF && b >= RuneSelf) {
+ ungetbyte(lx);
+ r = getrune(lx);
+ if (!utf8·isletter(r) && !utf8·isdigit(r) && r != 0xb7) {
+ errorat(lx->pos, "invalid identifier character %d", r);
+ }
+ s += utf8·runetobyte(s, &r);
+ } else if (!isalnum(b) && b != '_')
+ break;
+ else
+ *s++ = b;
+ b = getbyte(lx);
+ }
+ *s = '\0';
+ ungetbyte(lx);
+
+ tok.kind = Aident;
+ tok.val.s = lx->buf;
+
+ n = intern(&tok.val.s);
+ if (n < arrlen(keywords)) {
+ tok.kind = Akeywd;
+ tok.val.i = n;
+ goto Return;
+ }
+
+ sym = lookup(&lx->sym, tok.val.s);
+ if (sym && ((uintptr)sym->name != (uintptr)lx->io->path)) {
+ if ((uintptr)sym == lx->macline) {
+ tok.kind = Alit | Vint;
+ tok.val.i = lx->pos.line;
+ goto Return;
+ }
+ if ((uintptr)sym == lx->macfile) {
+ tok.kind = Alit | Vstr;
+ tok.val.s = lx->pos.path;
+ goto Return;
+ }
+ io = makeio(lx, sym->name);
+ io->rdr.end += expandmacro(lx, sym, io->b);
+ printf("EXPANDED %s: %s\n", sym->name, io->rdr.beg);
+ *io->rdr.end++ = EOF;
+ pushio(lx, io);
+ goto GetByte;
+ }
+ goto Return;
+
+ default:
+ tok.kind = Anil;
+ errorat(lx->pos, "invalid token, crashing");
+ abort();
+ }
+
+Return:
+ lx->b = lx->buf;
+ return tok;
+
+Nospace:
+ panicf("aborting compilation");
+ exit(1);
+}
+
+#undef CASE4
+#undef CASE3
+#undef CASE2
+#undef CASE1
+
+// -----------------------------------------------------------------------
+// symbol tables
+
+#define PTR_HASH(p) (uintptr)(p)
+#define PTR_EQUAL(p1, p2) ((uintptr)(p1) == (uintptr)(p2))
+
+static
+void
+·free(void* _, void* ptr) {
+ return free(ptr);
+}
+
+static
+void *
+·alloc(void* _, uint n, ulong size) {
+ return malloc(n*size);
+}
+
+static
+void *
+·calloc(void* _, uint n, ulong size) {
+ return calloc(n, size);
+}
+
+static
+int
+moresymtab(SymTab *tab, int n)
+{
+ MAP_GROW(tab, string, Sym*, n, PTR_HASH, sys·Memory, nil);
+}
+
+static
+int
+putsym(SymTab *tab, Sym *sym, error *err)
+{
+ MAP_PUT(tab, sym->name, sym, PTR_HASH, PTR_EQUAL, moresymtab, err);
+}
+
+Sym*
+define(SymTab *tab, string name, uint32 kind)
+{
+ int i;
+ Sym *sym;
+ error err;
+
+ sym = mem·arenaalloc(C.heap, 1, sizeof(*sym));
+ sym->name = name;
+ sym->kind = kind;
+
+ i = putsym(tab, sym, &err);
+ tab->vals[i] = sym;
+
+ return sym;
+}
+
+Sym*
+lookup(SymTab *tab, string ident)
+{
+ int idx;
+ MAP_GET(idx, tab, ident, PTR_HASH, PTR_EQUAL);
+
+ if (idx < tab->n_buckets)
+ return tab->vals[idx];
+
+ return nil;
+}
+
+
+error
+forget(SymTab *tab, string ident)
+{
+ int idx;
+ MAP_GET(idx, tab, ident, PTR_HASH, PTR_EQUAL);
+
+ if (idx < tab->n_buckets) {
+ MAP_DEL(tab, idx);
+ return 0;
+ }
+ return 1;
+}
+
+void
+forgetall(SymTab *tab)
+{
+ MAP_RESET(tab);
+}
diff --git a/src/cmd/cc/pp.c b/src/cmd/cc/pp.c
new file mode 100644
index 0000000..57c3501
--- /dev/null
+++ b/src/cmd/cc/pp.c
@@ -0,0 +1,1125 @@
+#include "cc.h"
+
+// -----------------------------------------------------------------------
+// helper functions
+
+static
+void
+pushomit(Lexer *lx, string omit)
+{
+ if (lx->omit.len == lx->omit.cap) {
+ lx->omit.cap += 20;
+ lx->omit.path = realloc(lx->omit.path, lx->omit.cap*sizeof(*lx->omit.path));
+ }
+ lx->omit.path[lx->omit.len++] = omit;
+}
+
+// NOTE: The iterator of lexer lx->b IS NOT reset.
+// Its the caller's responsibility.
+static
+string
+ident(Lexer *lx)
+{
+ int b;
+ byte *s;
+
+ b = getnsbyte(lx);
+ if (!isalpha(b) && b != '_' && b < RuneSelf) {
+ ungetbyte(lx);
+ return "";
+ }
+
+ s = lx->b;
+ for (;;) {
+ *lx->b++ = b;
+ b = getbyte(lx);
+ if (isalnum(b) || b == '_' || b >= RuneSelf)
+ continue;
+ ungetbyte(lx);
+ break;
+ }
+ *lx->b++ = '\0';
+
+ return s;
+}
+
+static
+string
+identdots(Lexer *lx, int *dots)
+{
+ int c;
+ byte *s;
+
+ s = ident(lx);
+ if (*s != '\0')
+ return s;
+
+ c = getnsbyte(lx);
+ if (c != '.') {
+ ungetbyte(lx);
+ return s;
+ }
+
+ if (getbyte(lx) != '.' || getbyte(lx) != '.')
+ errorat(lx->pos, "incorrect '...' token in macro");
+
+ *dots = 1;
+ // TODO: should only run intern once...
+ s = "__VA_ARGS__";
+ intern(&s);
+ return s;
+}
+
+static
+Sym*
+defmacro(Lexer *lx, string name, string macro)
+{
+ Sym *mac;
+
+ // printf("DEFINING MACRO %s ON LINE %d, file %s\n", name, lx->pos.line, os·basename(lx->pos.path));
+ mac = define(&lx->sym, name, Smacro);
+ mac->macro = macro;
+
+ return mac;
+}
+
+static vlong evalmacro(Lexer *lx, byte prec);
+
+static
+vlong
+opand(Lexer *lx)
+{
+ int b;
+ vlong v;
+ string s;
+ Token tok;
+ Sym *sym;
+
+ b = getnsbyte(lx);
+ if (b == '\n') {
+ errorat(lx->pos, "new line in macro expression");
+ return 0;
+ }
+ ungetbyte(lx);
+
+ tok = lex(lx);
+
+ switch (tok.kind & Vmask) {
+ case Aneg:
+ return ~opand(lx);
+
+ case Anot:
+ return !opand(lx);
+
+ case Alparen:
+ v = evalmacro(lx, 1);
+ tok = lex(lx);
+ if (!(tok.kind & Arparen)) {
+ errorat(lx->pos, "unbalanced parenthesis in macro expression");
+ return 0;
+ }
+ return v;
+
+ case Alit:
+ switch (tok.kind & ~Vmask) {
+ case Vint: case Vlong: case Vvlong:
+ return tok.val.i;
+ case Vun|Vint : case Vun|Vlong : case Vun|Vvlong:
+ return tok.val.ui;
+ case Vrune:
+ return tok.val.r;
+ case Vchar:
+ return tok.val.c;
+ default:
+ errorat(lx->pos, "invalid literal of type '%s' in conditional macro", tokens[tok.kind & ~Vmask]);
+ return 0;
+ }
+
+ case Aident:
+ sym = lookup(&lx->sym, tok.val.s);
+ if (!sym) {
+ /* calling lex directly would expand the operand here
+ * manually lex the result
+ */
+ if (strcmp(tok.val.s, "defined") == 0) {
+ b = getnsbyte(lx);
+ if (b == '\n') {
+ errorat(lx->pos, "new line in defined operand");
+ return 0;
+ }
+ s = lx->buf;
+ if (b == '(') {
+ b = getnsbyte(lx);
+ while (b != ')') {
+ if (b == '\n') {
+ errorat(lx->pos, "new line inside defined operand");
+ return 0;
+ }
+ if (b == '(') {
+ errorat(lx->pos, "nested parens not allowed inside defined operator");
+ return 0;
+ }
+ if (!isspace(b))
+ *s++ = b;
+ b = getbyte(lx);
+ }
+ } else {
+ while (!isspace(b)) {
+ *s++ = b;
+ b = getbyte(lx);
+
+ if (b == '\n') {
+ errorat(lx->pos, "new line inside defined operand");
+ return 0;
+ }
+ }
+ }
+ *s = '\0';
+ s = lx->buf;
+ intern(&s);
+ return lookup(&lx->sym, s) != nil;
+ }
+ return 0;
+ }
+ panicf("unreachable");
+ return 1;
+
+ default:
+ errorat(lx->pos, "opand: invalid token found in macro conditional: '%s'", tokens[tok.kind & Vmask]);
+ return 0;
+ }
+}
+
+// recursively evaluates a macro
+// reduced set of operators allowed here
+static
+vlong
+evalmacro(Lexer *lx, byte prec)
+{
+ int b;
+ vlong l, r;
+ Token tok;
+
+ l = opand(lx);
+ for (;;) {
+ b = getnsbyte(lx);
+ // NOTE: Either this or we pass in what are stopping byte is
+ // New line should always stop us...
+ // Is there any case where we SHOULDN'T STOP ON ')'?
+ if (b == '\n' || b == ')') {
+ ungetbyte(lx);
+ break;
+ }
+ ungetbyte(lx);
+
+ tok = lex(lx);
+ // simplified jump table of precedence
+ // unpacked to evaluate inline
+ switch (tok.kind & Vmask) {
+ case Astar:
+ if (prec > 10) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 10 + 1);
+ l = l * r;
+ continue;
+
+ case Adiv:
+ if (prec > 10) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 10 + 1);
+ l = l / r;
+ continue;
+
+ case Amod:
+ if (prec > 10) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 10 + 1);
+ l = l % r;
+ continue;
+
+ case Aadd:
+ if (prec > 9) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 9 + 1);
+ l = l + r;
+ continue;
+
+ case Asub:
+ if (prec > 9) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 9 + 1);
+ l = l - r;
+ continue;
+
+ case Alsft:
+ if (prec > 8) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 8 + 1);
+ l = l << r;
+ continue;
+
+ case Arsft:
+ if (prec > 8) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 8 + 1);
+ l = l >> r;
+ continue;
+
+ case Alt:
+ if (prec > 7) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 7 + 1);
+ l = l < r;
+ continue;
+
+ case Agt:
+ if (prec > 7) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 7 + 1);
+ l = l > r;
+ continue;
+
+ case Agteq:
+ if (prec > 7) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 7 + 1);
+ l = l >= r;
+ continue;
+
+ case Alteq:
+ if (prec > 7) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 7 + 1);
+ l = l >= r;
+ continue;
+
+ case Aeq:
+ if (prec > 6) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 6 + 1);
+ l = l == r;
+ continue;
+
+ case Aneq:
+ if (prec > 6) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 6 + 1);
+ l = l != r;
+ continue;
+
+ case Aand:
+ if (prec > 5) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 5 + 1);
+ l = l & r;
+ continue;
+
+ case Axor:
+ if (prec > 4) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 4 + 1);
+ l = l ^ r;
+ continue;
+
+ case Aor:
+ if (prec > 3) {
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 3 + 1);
+ l = l | r;
+ continue;
+
+ case Aandand:
+ if (prec > 2) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 2 + 1);
+ l = l && r;
+ continue;
+
+ case Aoror:
+ if (prec > 1) {
+ ungetbyte(lx);
+ ungetbyte(lx);
+ return l;
+ }
+ r = evalmacro(lx, 1 + 1);
+ l = l || r;
+ continue;
+
+ default:
+ errorat(lx->pos, "eval: invalid token found in macro conditional '%s'", tokens[tok.kind & Vmask]);
+ abort();
+ return 0;
+ }
+ }
+
+ return l;
+}
+
+// -----------------------------------------------------------------------
+// preprocessor magic numbers
+
+enum
+{
+ PPbeg = 0x02,
+ PParg = 0x03,
+ PPcat = 0x04,
+ PPstr = 0x05,
+
+ PPnarg = 30,
+};
+
+#define PPvar 0x80u
+
+// -----------------------------------------------------------------------
+// preprocessor functions
+
+/* #endif */
+static
+error
+ppend(Lexer *lx)
+{
+ int b;
+ do {
+ b = getnsbyte(lx);
+ } while (b > 0 && b != '\n');
+
+ if (b == '\n')
+ lx->pos.line++;
+
+ return 0;
+}
+
+
+/* #undef */
+static
+error
+ppund(Lexer *lx)
+{
+ string s;
+ error err;
+
+ s = ident(lx);
+ intern(&s);
+ lx->b = lx->buf;
+
+ err = forget(&lx->sym, s);
+ if (err)
+ warnat(lx->pos, "attempting to undefine unrecognized symbol '%s'", s);
+
+ ppend(lx);
+ return 0;
+}
+
+/* #define */
+static
+error
+ppdef(Lexer *lx)
+{
+ int b;
+ Sym *sym;
+ int i, j, n, dot;
+ string s, a, base, end, buf, args[PPnarg];
+
+ s = ident(lx);
+ if (!s) {
+ errorat(lx->pos, "failed to parse defined identifer");
+ goto Bad;
+ }
+ intern(&s);
+ printf("DEFINING %s\n", s);
+ lx->b = lx->buf;
+
+ sym = lookup(&lx->sym, s);
+ if (sym)
+ warnat(lx->pos, "macro redefined: '%s'", sym->name);
+
+ n = 0;
+ dot = 0;
+ b = getbyte(lx);
+ if (b == '(') {
+ b = getnsbyte(lx);
+ if (b != ')') {
+ ungetbyte(lx);
+ for (;;) {
+ // NOTE: This is a pointer into the lx->buffer.
+ // Can't reset lx->b while we hold the args!
+ a = identdots(lx, &dot);
+ if (a == nil) {
+ errorat(lx->pos, "macro syntax error: improper argument");
+ goto Bad;
+ }
+ if (n >= PPnarg) {
+ errorat(lx->pos, "macro syntax error: too many arguments: %d > %d", n, PPnarg);
+ goto Bad;
+ }
+
+ args[n++] = a;
+ b = getnsbyte(lx);
+
+ if (b == ')')
+ break;
+ if (b != ',') {
+ errorat(lx->pos, "macro syntax error: bad token in argument '%b'", b);
+ goto Bad;
+ }
+ }
+ }
+ b = getbyte(lx);
+ }
+
+ if (isspace(b))
+ if (b != '\n')
+ b = getnsbyte(lx);
+
+ base = lx->b;
+ end = lx->buf + arrlen(lx->buf);
+ if (base >= end) {
+ errorat(lx->pos, "out of macro buffer space!");
+ goto Bad;
+ }
+ buf = str·makef("%c%c", n, PPbeg);
+ for (;;) {
+ if (isalpha(b) || b == '_') {
+ lx->b = base;
+ *lx->b++ = b;
+
+ b = getbyte(lx);
+ while (isalnum(b) || b == '_') {
+ *lx->b++ = b;
+ if (lx->b >= end) {
+ errorat(lx->pos, "out of macro buffer space!");
+ goto Bad;
+ }
+ b = getbyte(lx);
+ }
+ *lx->b++ = '\0';
+
+ for (i = 0; i < n; i++) {
+ if (strcmp(base, args[i]) == 0) {
+ goto Arg;
+ }
+ }
+ str·appendlen(&buf, (lx->b - base - 1), base);
+ continue;
+ Arg:
+ str·appendbyte(&buf, PParg);
+ str·appendbyte(&buf, 'a' + i);
+ continue;
+ }
+
+ if (b == '/') {
+ b = getbyte(lx);
+ if (b == '/') {
+ while (b = getbyte(lx), b != '\n');
+ continue;
+ }
+ if (b == '*') {
+ b = getbyte(lx);
+ for (;;) {
+ if (b == '*') {
+ b = getbyte(lx);
+ if (b != '/')
+ continue;
+ b = getbyte(lx);
+ break;
+ }
+ if (b == '\n') {
+ errorat(lx->pos, "comment and newline found in define statement of %s", s);
+ break;
+ }
+ b = getbyte(lx);
+ }
+ continue;
+ }
+ str·appendbyte(&buf, '/');
+ continue;
+ }
+
+ if (b == '\\') {
+ b = getbyte(lx);
+ /* unix */
+ if (b == '\n') {
+ lx->pos.line++;
+ b = getbyte(lx);
+ continue;
+ }
+ /* windows */
+ if (b == '\r') {
+ b = getbyte(lx);
+ if (b == '\n') {
+ lx->pos.line++;
+ b = getbyte(lx);
+ continue;
+ }
+ }
+ str·appendbyte(&buf, '\\');
+ }
+ if (b == '\n') {
+ lx->pos.line++;
+ break;
+ }
+
+ if (b == '#') {
+ b = getnsbyte(lx);
+ if (b == '#') {
+ str·appendbyte(&buf, PPcat);
+ b = getbyte(lx);
+ continue;
+ }
+
+ lx->b = base;
+ while (isalnum(b) || b == '_') {
+ *lx->b++ = b;
+ b = getbyte(lx);
+ }
+ *lx->b = '\0';
+
+ for (i = 0; i < n; i++) {
+ if (strcmp(base, args[i]) == 0)
+ goto Str;
+ }
+ errorat(lx->pos, "macro operator '#' must be followed by a valid variable identifier");
+ goto Bad;
+ Str:
+ str·appendbyte(&buf, PPstr);
+ str·appendbyte(&buf, 'a' + i);
+ continue;
+ }
+
+ str·appendbyte(&buf, b);
+ b = getbyte(lx);
+ if (b == EOF) {
+ errorat(lx->pos, "eof found in macro '%s'", s);
+ goto Bad;
+ }
+ }
+ if (dot)
+ *buf |= PPvar;
+
+ lx->b = lx->buf;
+ sym = defmacro(lx, s, buf);
+ return 0;
+Bad:
+ errorat(lx->pos, "failed parse of #define macro '%s'", s);
+ lx->b = lx->buf;
+ ppend(lx);
+ return 1;
+}
+
+/* macro expansion */
+int
+expandmacro(Lexer *lx, Sym *s, byte *dst)
+{
+ int n, lv, nargs, dots;
+ byte b, *it, *e, *arg[PPnarg];
+
+ /* not a function macro */
+ if (s->macro[0] == '\0') {
+ if (s->macro[1] != PPbeg) {
+ errorat(lx->pos, "malformed macro");
+ goto Bad;
+ }
+ strcpy(dst, s->macro + 2);
+ return str·len(s->macro)-2;
+ }
+ dots = (ubyte)s->macro[0] & PPvar;
+ nargs = (ubyte)s->macro[0] & (~PPvar);
+
+ b = getnsbyte(lx);
+ if (b != '(') {
+ errorat(lx->pos, "macro function not given arguments");
+ goto Bad;
+ }
+
+ n = 0;
+ b = getbyte(lx);
+ if (b != ')') {
+ ungetbyte(lx);
+ lv = 0;
+ lx->b = lx->buf;
+ e = lx->buf + arrlen(lx->buf) - 4;
+ arg[n++] = lx->buf;
+ for (;;) {
+ if (lx->b >= e)
+ goto Nospace;
+ b = getbyte(lx);
+ if (b == '"')
+ for (;;) {
+ if (lx->b >= e)
+ goto Nospace;
+ *lx->b++ = b;
+ b = getbyte(lx);
+ if (b == '\\') {
+ *lx->b++ = b;
+ b = getbyte(lx);
+ continue;
+ }
+ if (b == '\n') {
+ errorat(lx->pos, "newline found in arguments: macro '%s'", s->name);
+ goto Bad;
+ }
+ if (b == '"')
+ break;
+ }
+ if (b == '\'')
+ for (;;) {
+ if (lx->b >= e)
+ goto Nospace;
+ *lx->b++ = b;
+ b = getbyte(lx);
+ if (b == '\\') {
+ *lx->b++ = b;
+ b = getbyte(lx);
+ continue;
+ }
+ if (b == '\n') {
+ errorat(lx->pos, "newline found in arguments: macro '%s'", s->name);
+ goto Bad;
+ }
+ if (b == '"')
+ break;
+ }
+ if (b == '/') {
+ b = getbyte(lx);
+ switch(b) {
+ case '*':
+ for (;;) {
+ b = getbyte(lx);
+ if (b == '*') {
+ b = getbyte(lx);
+ if (b == '/')
+ break;
+ }
+ }
+ *lx->b++ = ' ';
+ continue;
+ case '/':
+ while ((b = getbyte(lx)) != '\n')
+ ;
+ break;
+
+ default:
+ ungetbyte(lx);
+ b = '/';
+ }
+ }
+ if (lv == 0) {
+ if (b == ',') {
+ if (n == nargs && dots) {
+ *lx->b++ = ',';
+ continue;
+ }
+ *lx->b++ = '\0';
+ arg[n++] = lx->b;
+ if (n > nargs)
+ break;
+ continue;
+ }
+ if (b == ')')
+ break;
+ }
+ if (b == '\n')
+ b = ' ';
+ *lx->b++ = b;
+ if (b == '(')
+ lv++;
+ if (b == ')')
+ lv--;
+ }
+ *lx->b = '\0';
+ }
+
+ if (n != nargs) {
+ errorat(lx->pos, "number of arguments don't match macro definition: %s", s->name);
+ *dst = '\0';
+ goto Bad;
+ }
+
+ if (s->macro[1] != PPbeg) {
+ errorat(lx->pos, "corrupted macro buffer: %s", s->name);
+ *dst = '\0';
+ goto Bad;
+ }
+
+ it = s->macro+2;
+ e = dst;
+ for (;;) {
+ b = *it++;
+ if (b == '\n')
+ b = ' ';
+ switch (b) {
+ case PParg:
+ b = *it++;
+ b -= 'a';
+ if (b < 0 && b > n) {
+ errorat(lx->pos, "malformed macro index: %s", s->name);
+ goto Bad;
+ }
+ strcpy(dst, arg[b]);
+ dst += strlen(arg[b]);
+
+ break;
+
+ case PPstr:
+ b = *it++;
+ b -= 'a';
+ if (b < 0 && b > n) {
+ errorat(lx->pos, "malformed macro index: %s", s->name);
+ goto Bad;
+ }
+ *dst++ = '"';
+ strcpy(dst, arg[b]);
+ *dst++ = '"';
+
+ break;
+
+ case PPcat:
+ continue;
+
+ case '\0':
+ goto End;
+
+ default:
+ *dst++ = b;
+ continue;
+ }
+ }
+End:
+ *dst = '\0';
+ return dst - e;
+Nospace:
+ errorat(lx->pos, "out of memory during macro expansion %s", s->name);
+Bad:
+ ppend(lx);
+ lx->b = lx->buf;
+ errorat(lx->pos, "failed to expand macro %s", s->name);
+ return -1;
+}
+
+/* #include */
+static
+error
+ppinc(Lexer *lx)
+{
+ int i;
+ byte b, end;
+ string s;
+
+ Stream *f;
+ Io *io;
+
+ b = getnsbyte(lx);
+ if (b != '"') {
+ end = b;
+ if (b != '<') {
+ errorat(lx->pos, "unrecognized token '%c' in include directive", b);
+ goto Bad;
+ }
+ end = '>';
+ } else
+ end = '"';
+
+ lx->b = lx->buf;
+ for (;;) {
+ b = getbyte(lx);
+ if (b == end)
+ break;
+ if (b == '\n') {
+ errorat(lx->pos, "hit end of line before include directive completed");
+ goto Bad;
+ }
+ *lx->b++ = b;
+ }
+ *lx->b = '\0';
+ s = lx->buf;
+ intern(&s); // NOTE: we could use this to see if we already have the file
+
+ lx->b = lx->buf;
+ for (i = 0; i < C.inc.len; i++) {
+ if (i == 0 && end == '>')
+ continue;
+
+ strcpy(lx->buf, C.inc.dir[i]);
+ strcat(lx->buf, "/");
+
+ if (strcmp(lx->buf, "./") == 0)
+ lx->buf[0] = '\0';
+ strcat(lx->buf, s);
+
+ if (os·exists(lx->buf, ReadOK)) {
+ break;
+ }
+ }
+ if (i == C.inc.len) {
+ errorat(lx->pos, "could not find file '%s' on standard include search path", s);
+ goto Bad;
+ }
+
+ io = openio(lx, lx->buf);
+ if (io != nil) {
+ pushio(lx, io);
+ }
+
+ return 0;
+
+Bad:
+ ungetbyte(lx);
+ lx->b = lx->buf;
+ errorat(lx->pos, "failed include");
+ ppend(lx);
+ return 1;
+}
+
+/* #pragma */
+static
+error
+ppprag(Lexer *lx)
+{
+ string s;
+
+ s = ident(lx);
+ if (s == nil) {
+ errorat(lx->pos, "failed to parse pragma identifier");
+ goto Bad;
+ }
+ lx->b = lx->buf;
+ if (strcmp(s, "once") == 0) {
+ pushomit(lx, lx->io->path);
+ return 0;
+ }
+Bad:
+ lx->b = lx->buf;
+ errorat(lx->pos, "unrecognized pragma '%s'", s);
+ ppend(lx);
+ return 1;
+}
+
+/* all #if statements */
+static
+error
+ppif(Lexer *lx, int f)
+{
+ Sym *sym;
+ string s;
+ int c, l, b;
+
+Eval:
+ if (f == 0) {
+ b = evalmacro(lx, 1);
+ if (b) {
+ ppend(lx);
+ return 0;
+ }
+ goto Skip;
+ }
+
+ if (f == 1)
+ goto Skip;
+
+ s = ident(lx);
+ if (s == nil) {
+ errorat(lx->pos, "failed to parse preprocessor identifier");
+ goto Bad;
+ }
+ intern(&s);
+ lx->b = lx->buf;
+
+ sym = lookup(&lx->sym, s);
+ if ((!sym && (f == 3)) || (sym && (f == 2)))
+ return 0;
+
+Skip:
+ b = 1;
+ l = 0;
+ for (;;) {
+ c = getbyte(lx);
+ if (c != '#') {
+ if (!isspace(c))
+ b = 0;
+ if (c == '\n') {
+ lx->pos.line++;
+ b = 1;
+ }
+ if (c == EOF) {
+ errorat(lx->pos, "EOF hit while skipping if block. Missing endif");
+ goto Bad;
+ }
+ continue;
+ }
+ if (!b)
+ continue;
+ s = ident(lx);
+ lx->b = lx->buf;
+ if (!s)
+ continue;
+
+ if (l == 0 && (strcmp(s, "elif") == 0)) {
+ f = 0;
+ goto Eval;
+ }
+
+ if (strcmp(s, "endif") == 0) {
+ if (l) {
+ l--;
+ continue;
+ }
+ ppend(lx);
+ return 0;
+ }
+ if (strcmp(s, "if") == 0 ||
+ strcmp(s, "ifdef") == 0 ||
+ strcmp(s, "ifndef") == 0) {
+ l++;
+ continue;
+ }
+
+ if (l == 0 && f != 1 && strcmp(s, "else") == 0) {
+ return 0;
+ }
+ }
+
+Bad:
+ lx->b = lx->buf;
+ errorat(lx->pos, "bad syntax in preprocessor conditional directive");
+ ppend(lx);
+ return 1;
+}
+
+/* #if */
+static
+error
+ppif0(Lexer *lx)
+{
+ return ppif(lx, 0);
+}
+
+/* #else */
+static
+error
+ppif1(Lexer *lx)
+{
+ return ppif(lx, 1);
+}
+
+/* #ifdef */
+static
+error
+ppif2(Lexer *lx)
+{
+ return ppif(lx, 2);
+}
+
+/* #ifndef */
+static
+error
+ppif3(Lexer *lx)
+{
+ return ppif(lx, 3);
+}
+
+// -----------------------------------------------------------------------
+// dispatch function
+
+#define DIRECTIVE(a, b, c) c,
+error (*macros[NUM_DIRECTIVES])(Lexer*) = { DIRECTIVES };
+#undef DIRECTIVE
+
+/* reads an identifier into the lexer's buffer */
+/* caller must intern */
+
+error
+domacro(Lexer *lx)
+{
+ int n;
+ error err;
+ string s;
+
+ s = ident(lx);
+ intern(&s);
+ lx->b = lx->buf;
+ for (n = 0; n < NUM_DIRECTIVES; n++) {
+ if ((uintptr)s == (uintptr)directives[n]) {
+ goto Do;
+ }
+ }
+ errorat(lx->pos, "unrecognized directive name '%s'", s);
+ return 1;
+Do:
+ err = macros[n](lx);
+ return err;
+}
+
+error
+dodefine(Lexer *lx, string s)
+{
+ int n;
+ byte *c, *def;
+ Sym *sym;
+
+ strcpy(lx->buf, s);
+ c = strchr(lx->buf, '=');
+ if (c) {
+ *c++ = '\0';
+ sym = lookup(&lx->sym, lx->buf);
+ if (sym) {
+ errorf("redefinition of symbol '%s'", sym->name);
+ return 1;
+ }
+ sym = define(&lx->sym, lx->buf, Smacro);
+ n = strlen(c) + 2;
+ sym->macro = str·makelen("", n);
+ str·appendbyte(&sym->macro, '\0');
+ str·append(&sym->macro, c);
+ } else {
+ sym = lookup(&lx->sym, lx->buf);
+ if (sym) {
+ errorf("redefinition of symbol '%s'", sym->name);
+ return 1;
+ }
+ sym = define(&lx->sym, s, Smacro);
+ sym->macro = "\00\02";
+ }
+
+ return 0;
+}
diff --git a/src/cmd/cc/rules.mk b/src/cmd/cc/rules.mk
new file mode 100644
index 0000000..b7b4688
--- /dev/null
+++ b/src/cmd/cc/rules.mk
@@ -0,0 +1,21 @@
+include share/push.mk
+
+# local sources
+SRCS_$(d):=\
+ $(d)/pp.c\
+ $(d)/lex.c\
+ $(d)/ast.c\
+ $(d)/bits.c\
+ $(d)/cc.c
+TEST_$(d) :=
+
+# outputs
+BINS_$(d) := $(d)/cc
+
+include share/paths.mk
+
+# Local rules
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/cc/scratch.c b/src/cmd/cc/scratch.c
new file mode 100644
index 0000000..b37d9a5
--- /dev/null
+++ b/src/cmd/cc/scratch.c
@@ -0,0 +1,7 @@
+#define XXX(a, b, c) a ## b ## c
+
+int
+main()
+{
+ XXX(d, e, f);
+}
diff --git a/src/cmd/cc/util.c b/src/cmd/cc/util.c
new file mode 100644
index 0000000..cca16f2
--- /dev/null
+++ b/src/cmd/cc/util.c
@@ -0,0 +1,21 @@
+#include "cc.h"
+
+void
+·free(void* _, void* ptr) {
+ return free(ptr);
+}
+
+void *
+·alloc(void* _, uint n, ulong size) {
+ return malloc(n*size);
+}
+
+void *
+·calloc(void* _, uint n, ulong size) {
+ return calloc(n, size);
+}
+
+void *
+·realloc(void* _, void *ptr, uint n, ulong size) {
+ return realloc(ptr, n*size);
+}
diff --git a/src/cmd/dwm/LICENSE b/src/cmd/dwm/LICENSE
new file mode 100644
index 0000000..d221f09
--- /dev/null
+++ b/src/cmd/dwm/LICENSE
@@ -0,0 +1,37 @@
+MIT/X Consortium License
+
+© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+© 2006-2009 Jukka Salmi <jukka at salmi dot ch>
+© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
+© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com>
+© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com>
+© 2007-2009 Christof Musik <christof at sendfax dot de>
+© 2007-2009 Premysl Hruby <dfenze at gmail dot com>
+© 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
+© 2008 Martin Hurton <martin dot hurton at gmail dot com>
+© 2008 Neale Pickett <neale dot woozle dot org>
+© 2009 Mate Nagy <mnagy at port70 dot net>
+© 2010-2016 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2011 Christoph Lohmann <20h@r-36.net>
+© 2015-2016 Quentin Rameau <quinq@fifth.space>
+© 2015-2016 Eric Pruitt <eric.pruitt@gmail.com>
+© 2016-2017 Markus Teich <markus.teich@stusta.mhn.de>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/src/cmd/dwm/client.c b/src/cmd/dwm/client.c
new file mode 100644
index 0000000..fa04f5f
--- /dev/null
+++ b/src/cmd/dwm/client.c
@@ -0,0 +1,657 @@
+#include "dwm.h"
+
+void
+applyrules(Client *c)
+{
+ char *class, *instance;
+ uint i;
+ Rule *r;
+ Monitor *m;
+ XClassHint ch = { nil, nil };
+
+ c->isfloating = 0;
+ c->tags = 0;
+ c->noswallow = -1;
+
+ /* rule matching */
+ XGetClassHint(dpy, c->win, &ch);
+ class = ch.res_class ? ch.res_class : broken;
+ instance = ch.res_name ? ch.res_name : broken;
+
+ for (i = 0; i < arrlen(rules); i++) {
+ r = &rules[i];
+ if ((!r->title || strstr(c->name, r->title))
+ && (!r->class || strstr(class, r->class))
+ && (!r->instance || strstr(instance, r->instance)))
+ {
+ c->isterm = r->isterm;
+ c->noswallow = r->noswallow;
+ c->isfloating = r->isfloating;
+ c->tags |= r->tags;
+ for (m = mons; m && m->num != r->monitor; m = m->next)
+ ;
+ if (m)
+ c->mon = m;
+ }
+ }
+ if (ch.res_class)
+ XFree(ch.res_class);
+ if (ch.res_name)
+ XFree(ch.res_name);
+ c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags];
+}
+
+int
+applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact)
+{
+ int baseismin;
+ Monitor *m = c->mon;
+
+ /* set minimum possible */
+ *w = MAX(1, *w);
+ *h = MAX(1, *h);
+ if (interact) {
+ if (*x > sw)
+ *x = sw - WIDTH(c);
+ if (*y > sh)
+ *y = sh - HEIGHT(c);
+ if (*x + *w + 2 * c->bw < 0)
+ *x = 0;
+ if (*y + *h + 2 * c->bw < 0)
+ *y = 0;
+ } else {
+ if (*x >= m->wx + m->ww)
+ *x = m->wx + m->ww - WIDTH(c);
+ if (*y >= m->wy + m->wh)
+ *y = m->wy + m->wh - HEIGHT(c);
+ if (*x + *w + 2 * c->bw <= m->wx)
+ *x = m->wx;
+ if (*y + *h + 2 * c->bw <= m->wy)
+ *y = m->wy;
+ }
+ if (*h < bh)
+ *h = bh;
+ if (*w < bh)
+ *w = bh;
+ if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) {
+ /* see last two sentences in ICCCM 4.1.2.3 */
+ baseismin = c->basew == c->minw && c->baseh == c->minh;
+ if (!baseismin) { /* temporarily remove base dimensions */
+ *w -= c->basew;
+ *h -= c->baseh;
+ }
+ /* adjust for aspect limits */
+ if (c->mina > 0 && c->maxa > 0) {
+ if (c->maxa < (float)*w / *h)
+ *w = *h * c->maxa + 0.5;
+ else if (c->mina < (float)*h / *w)
+ *h = *w * c->mina + 0.5;
+ }
+ if (baseismin) { /* increment calculation requires this */
+ *w -= c->basew;
+ *h -= c->baseh;
+ }
+ /* adjust for increment value */
+ if (c->incw)
+ *w -= *w % c->incw;
+ if (c->inch)
+ *h -= *h % c->inch;
+ /* restore base dimensions */
+ *w = MAX(*w + c->basew, c->minw);
+ *h = MAX(*h + c->baseh, c->minh);
+ if (c->maxw)
+ *w = MIN(*w, c->maxw);
+ if (c->maxh)
+ *h = MIN(*h, c->maxh);
+ }
+ return *x != c->x || *y != c->y || *w != c->w || *h != c->h;
+}
+
+void
+attach(Client *c)
+{
+ c->next = c->mon->clients;
+ c->mon->clients = c;
+}
+
+void
+enqueue(Client *c)
+{
+ Client *l;
+
+ for (l = c->mon->clients; l && l->next; l = l->next)
+ ;
+
+ if (l) {
+ l->next = c;
+ c->next = nil;
+ }
+}
+
+void
+attachbottom(Client *c)
+{
+ Client **tc;
+ c->next = nil;
+ for (tc = &c->mon->clients; *tc; tc = &(*tc)->next)
+ ;
+ *tc = c;
+}
+
+void
+attachstack(Client *c)
+{
+ c->snext = c->mon->stack;
+ c->mon->stack = c;
+}
+
+void
+enqueuestack(Client *c)
+{
+ Client *l;
+ for (l = c->mon->clients; l && l->next; l = l->next)
+ ;
+
+ if (l) {
+ l->snext = c;
+ c->snext = nil;
+ }
+}
+
+void
+configure(Client *c)
+{
+ XConfigureEvent ce;
+
+ ce.type = ConfigureNotify;
+ ce.display = dpy;
+ ce.event = c->win;
+ ce.window = c->win;
+ ce.x = c->x;
+ ce.y = c->y;
+ ce.width = c->w;
+ ce.height = c->h;
+ ce.border_width = c->bw;
+ ce.above = None;
+ ce.override_redirect = False;
+ XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
+}
+
+void
+detach(Client *c)
+{
+ Client **tc;
+
+ for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next)
+ ;
+ *tc = c->next;
+}
+
+void
+detachstack(Client *c)
+{
+ Client **tc, *t;
+
+ for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext)
+ ;
+
+ *tc = c->snext;
+
+ if (c == c->mon->sel) {
+ for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext)
+ ;
+ c->mon->sel = t;
+ }
+}
+
+void
+focus(Client *c)
+{
+ if (!c || !ISVISIBLE(c))
+ for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext)
+ ;
+ if (selmon->sel && selmon->sel != c)
+ unfocus(selmon->sel, 0);
+ if (c) {
+ if (c->mon != selmon)
+ selmon = c->mon;
+ if (c->isurgent)
+ seturgent(c, 0);
+ detachstack(c);
+ attachstack(c);
+ grabbuttons(c, 1);
+ XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel);
+ setfocus(c);
+ } else {
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+ selmon->sel = c;
+ drawbars();
+}
+
+Atom
+getatomprop(Client *c, Atom prop)
+{
+ int di;
+ unsigned long dl;
+ uchar *p = nil;
+ Atom da, atom = None;
+
+ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM,
+ &da, &di, &dl, &dl, &p) == Success && p) {
+ atom = *(Atom *)p;
+ XFree(p);
+ }
+ return atom;
+}
+
+void
+grabbuttons(Client *c, int focused)
+{
+ updatenumlockmask();
+ {
+ uint i, j;
+ uint modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+ if (!focused)
+ XGrabButton(dpy, AnyButton, AnyModifier, c->win, False,
+ BUTTONMASK, GrabModeSync, GrabModeSync, None, None);
+ for (i = 0; i < arrlen(buttons); i++)
+ if (buttons[i].click == ClkClientWin)
+ for (j = 0; j < arrlen(modifiers); j++)
+ XGrabButton(dpy, buttons[i].button,
+ buttons[i].mask | modifiers[j],
+ c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ }
+}
+
+Client *
+nexttiled(Client *c)
+{
+ for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next)
+ ;
+ return c;
+}
+
+void
+pop(Client *c)
+{
+ detach(c);
+ attach(c);
+ focus(c);
+ arrange(c->mon);
+}
+
+void
+resize(Client *c, int x, int y, int w, int h, int interact)
+{
+ if (applysizehints(c, &x, &y, &w, &h, interact))
+ resizeclient(c, x, y, w, h);
+}
+
+
+void
+resizeclient(Client *c, int x, int y, int w, int h)
+{
+ XWindowChanges wc;
+
+ c->oldx = c->x; c->x = wc.x = x;
+ c->oldy = c->y; c->y = wc.y = y;
+ c->oldw = c->w; c->w = wc.width = w;
+ c->oldh = c->h; c->h = wc.height = h;
+ wc.border_width = c->bw;
+ XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
+ configure(c);
+ XSync(dpy, False);
+}
+
+void
+sendtomon(Client *c, Monitor *m)
+{
+ if (c->mon == m)
+ return;
+ unfocus(c, 1);
+ detach(c);
+ detachstack(c);
+ c->mon = m;
+ c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */
+ /* attach(c); */
+ attachbottom(c);
+ attachstack(c);
+ focus(nil);
+ arrange(nil);
+}
+
+void
+setclientstate(Client *c, long state)
+{
+ long data[] = { state, None };
+
+ XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32,
+ PropModeReplace, (uchar *)data, 2);
+}
+
+int
+sendevent(Client *c, Atom proto)
+{
+ int n;
+ Atom *protocols;
+ int exists = 0;
+ XEvent ev;
+
+ if (XGetWMProtocols(dpy, c->win, &protocols, &n)) {
+ while (!exists && n--)
+ exists = protocols[n] == proto;
+ XFree(protocols);
+ }
+ if (exists) {
+ ev.type = ClientMessage;
+ ev.xclient.window = c->win;
+ ev.xclient.message_type = wmatom[WMProtocols];
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0] = proto;
+ ev.xclient.data.l[1] = CurrentTime;
+ XSendEvent(dpy, c->win, False, NoEventMask, &ev);
+ }
+ return exists;
+}
+
+void
+setfocus(Client *c)
+{
+ if (!c->neverfocus) {
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
+ XChangeProperty(dpy, root, netatom[NetActiveWindow],
+ XA_WINDOW, 32, PropModeReplace,
+ (uchar *) &(c->win), 1);
+ }
+ sendevent(c, wmatom[WMTakeFocus]);
+}
+
+void
+setfullscreen(Client *c, int fullscreen)
+{
+ static uint32 opacity = 0xFFFFFFFFul;
+ if (fullscreen && !c->isfullscreen) {
+ XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
+ PropModeReplace, (uchar*)&netatom[NetWMFullscreen], 1);
+ XChangeProperty(dpy, c->win, netatom[NetWMWindowOpacity], XA_CARDINAL, 32, PropModeReplace, (uchar *)&opacity, 1L);
+
+ c->isfullscreen = 1;
+ c->oldstate = c->isfloating;
+ c->oldbw = c->bw;
+ c->bw = 0;
+ c->isfloating = 1;
+
+ resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
+
+ XRaiseWindow(dpy, c->win);
+ } else if (!fullscreen && c->isfullscreen){
+ XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
+ PropModeReplace, (uchar*)nil, 0);
+ XDeleteProperty(dpy, c->win, netatom[NetWMWindowOpacity]);
+
+ c->isfullscreen = 0;
+ c->isfloating = c->oldstate;
+ c->bw = c->oldbw;
+ c->x = c->oldx;
+ c->y = c->oldy;
+ c->w = c->oldw;
+ c->h = c->oldh;
+ resizeclient(c, c->x, c->y, c->w, c->h);
+ arrange(c->mon);
+ }
+}
+
+void
+seturgent(Client *c, int urg)
+{
+ XWMHints *wmh;
+
+ c->isurgent = urg;
+ if (!(wmh = XGetWMHints(dpy, c->win)))
+ return;
+ wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint);
+ XSetWMHints(dpy, c->win, wmh);
+ XFree(wmh);
+}
+
+void
+showhide(Client *c)
+{
+ if (!c)
+ return;
+ if (ISVISIBLE(c)) {
+ /* show clients top down */
+ XMoveWindow(dpy, c->win, c->x, c->y);
+ if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen)
+ resize(c, c->x, c->y, c->w, c->h, 0);
+ showhide(c->snext);
+ } else {
+ /* hide clients bottom up */
+ showhide(c->snext);
+ XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y);
+ }
+}
+
+void
+swallow(Client *p, Client *c)
+{
+ Client *s;
+
+
+ if (c->noswallow > 0 || c->isterm)
+ return;
+ if (c->noswallow < 0 && !swallowfloating && c->isfloating)
+ return;
+
+ detach(c);
+ detachstack(c);
+
+ setclientstate(c, WithdrawnState);
+ XUnmapWindow(dpy, p->win);
+
+ p->swallowing = c;
+ c->mon = p->mon;
+
+ Window w = p->win;
+ p->win = c->win;
+ c->win = w;
+
+ XChangeProperty(dpy, c->win, netatom[NetClientList], XA_WINDOW, 32, PropModeReplace,
+ (unsigned char *) &(p->win), 1);
+
+ updatetitle(p);
+ s = scanner ? c : p;
+ XMoveResizeWindow(dpy, p->win, s->x, s->y, s->w, s->h);
+ arrange(p->mon);
+ configure(p);
+ updateclientlist();
+}
+
+Client *
+termof(Client *w)
+{
+ Client *c;
+ Monitor *m;
+
+ if (!w->pid || w->isterm)
+ return NULL;
+
+ for (m = mons; m; m = m->next) {
+ for (c = m->clients; c; c = c->next) {
+ if (c->isterm && !c->swallowing && c->pid && isdescendent(c->pid, w->pid))
+ return c;
+ }
+ }
+
+ return NULL;
+}
+
+void
+unfocus(Client *c, int setfocus)
+{
+ if (!c)
+ return;
+ grabbuttons(c, 0);
+ XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel);
+ if (setfocus) {
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+}
+
+void
+unmanage(Client *c, int destroyed)
+{
+ Client *s;
+ Monitor *m = c->mon;
+ XWindowChanges wc;
+
+ if (c->swallowing) {
+ unswallow(c);
+ return;
+ }
+
+ s = swallowing(c->win);
+ if (s) {
+ free(s->swallowing);
+ s->swallowing = nil;
+ arrange(m);
+ focus(nil);
+ return;
+ }
+
+
+ detach(c);
+ detachstack(c);
+
+ if (!destroyed) {
+ wc.border_width = c->oldbw;
+ XGrabServer(dpy); /* avoid race conditions */
+ XSetErrorHandler(xerrordummy);
+ XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+ setclientstate(c, WithdrawnState);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XUngrabServer(dpy);
+ }
+ free(c);
+ focus(nil);
+ updateclientlist();
+ arrange(m);
+
+ if (!s) {
+ // arrange(m);
+ focus(nil);
+ updateclientlist();
+ }
+}
+
+void
+unswallow(Client *c)
+{
+ c->win = c->swallowing->win;
+
+ free(c->swallowing);
+ c->swallowing = nil;
+
+ XDeleteProperty(dpy, c->win, netatom[NetClientList]);
+
+ /* unfullscreen the client */
+ setfullscreen(c, 0);
+ updatetitle(c);
+ arrange(c->mon);
+ XMapWindow(dpy, c->win);
+ XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
+ setclientstate(c, NormalState);
+ focus(nil);
+ arrange(c->mon);
+}
+
+
+void
+updatesizehints(Client *c)
+{
+ long msize;
+ XSizeHints size;
+
+ if (!XGetWMNormalHints(dpy, c->win, &size, &msize))
+ /* size is uninitialized, ensure that size.flags aren't used */
+ size.flags = PSize;
+ if (size.flags & PBaseSize) {
+ c->basew = size.base_width;
+ c->baseh = size.base_height;
+ } else if (size.flags & PMinSize) {
+ c->basew = size.min_width;
+ c->baseh = size.min_height;
+ } else
+ c->basew = c->baseh = 0;
+ if (size.flags & PResizeInc) {
+ c->incw = size.width_inc;
+ c->inch = size.height_inc;
+ } else
+ c->incw = c->inch = 0;
+ if (size.flags & PMaxSize) {
+ c->maxw = size.max_width;
+ c->maxh = size.max_height;
+ } else
+ c->maxw = c->maxh = 0;
+ if (size.flags & PMinSize) {
+ c->minw = size.min_width;
+ c->minh = size.min_height;
+ } else if (size.flags & PBaseSize) {
+ c->minw = size.base_width;
+ c->minh = size.base_height;
+ } else
+ c->minw = c->minh = 0;
+ if (size.flags & PAspect) {
+ c->mina = (float)size.min_aspect.y / size.min_aspect.x;
+ c->maxa = (float)size.max_aspect.x / size.max_aspect.y;
+ } else
+ c->maxa = c->mina = 0.0;
+ c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh);
+}
+
+void
+updatetitle(Client *c)
+{
+ if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
+ gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
+ if (c->name[0] == '\0') /* hack to mark broken clients */
+ strcpy(c->name, broken);
+}
+
+void
+updatewindowtype(Client *c)
+{
+ Atom state = getatomprop(c, netatom[NetWMState]);
+ Atom wtype = getatomprop(c, netatom[NetWMWindowType]);
+
+ if (state == netatom[NetWMFullscreen])
+ setfullscreen(c, 1);
+ if (wtype == netatom[NetWMWindowTypeDialog])
+ c->isfloating = 1;
+}
+
+void
+updatewmhints(Client *c)
+{
+ XWMHints *wmh;
+
+ if ((wmh = XGetWMHints(dpy, c->win))) {
+ if (c == selmon->sel && wmh->flags & XUrgencyHint) {
+ wmh->flags &= ~XUrgencyHint;
+ XSetWMHints(dpy, c->win, wmh);
+ } else
+ c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0;
+ if (wmh->flags & InputHint)
+ c->neverfocus = !wmh->input;
+ else
+ c->neverfocus = 0;
+ XFree(wmh);
+ }
+}
diff --git a/src/cmd/dwm/config.h b/src/cmd/dwm/config.h
new file mode 100644
index 0000000..1f82b1f
--- /dev/null
+++ b/src/cmd/dwm/config.h
@@ -0,0 +1,141 @@
+/* See LICENSE file for copyright and license details. */
+#define VERSION "1"
+
+/* appearance */
+static uint borderpx = 2; /* border pixel of windows */
+static uint gapx = 4; /* gaps between windows */
+static uint snap = 32; /* snap pixel */
+static int swallowfloating = 0; /* 1 will swallow floating by default */
+static int showbar = 1; /* 0 means no bar */
+static int topbar = 1; /* 0 means bottom bar */
+static char *fonts[] = { "consolas:size=16" };
+static char col_gray1[] = "#504945";
+static char col_gray2[] = "#282828";
+static char col_gray3[] = "#fbf1c7";
+static char col_gray4[] = "#504945";
+static char col_cyan[] = "#83a598";
+static char *colors[][3] =
+{
+ /* fg bg border */
+ [SchemeNorm] = { col_gray3, col_gray1, col_gray2 },
+ [SchemeSel] = { col_gray4, col_cyan, col_cyan },
+};
+
+/* tagging */
+static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+static Rule rules[] = {
+ /* xprop(1):
+ * WM_CLASS(STRING) = instance, class
+ * WM_NAME(STRING) = title
+ */
+ /* class instance title tags mask isfloating isterminal noswallow monitor */
+ { "Gimp", nil, nil, 0, 1, 0, 0, -1 },
+ { "Inkscape", nil, nil, 0, 1, 0, 0, -1 },
+ { "zoom", nil, nil, 0, 1, 0, 0, -1 },
+ { "qutebrowser", nil, nil, 0, 0, 0, 0, -1 },
+ { "term-256color", nil, nil, 0, 0, 1, -1, -1 },
+};
+
+/* layout(s) */
+static float mfact = 0.55; /* factor of master area size [0.05..0.95] */
+static int nmaster = 1; /* number of clients in master area */
+static int resizehints = 1; /* 1 means respect size hints in tiled resizals */
+
+static Layout layouts[] = {
+ /* symbol arrange function */
+ { "[]=", tile }, /* first entry is default */
+ { "><>", nil }, /* no layout function means floating behavior */
+ { "[M]", monocle },
+};
+
+/* key definitions */
+#define MODKEY Mod4Mask
+#define TAGKEYS(KEY,TAG) \
+ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \
+ { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} },
+
+/* commands */
+static char *menucmd[] = { "menu_run", nil };
+static char *termcmd[] = { "term", nil };
+static char *webscmd[] = { "qutebrowser", nil };
+static char scratchname[] = "scratchpad";
+static char *scratchcmd[] = { "term", "-t", scratchname, "-g", "120x34", nil };
+static char *upvolcmd[] = { "vol", "+5%", nil };
+static char *lovolcmd[] = { "vol", "-5%", nil };
+static char *novolcmd[] = { "vol", "mute", nil };
+
+#define XK_lovol XF86XK_AudioLowerVolume
+#define XK_upvol XF86XK_AudioRaiseVolume
+#define XK_novol XF86XK_AudioMute
+
+static Key keys[] = {
+ /* modifier key function argument */
+ { MODKEY, XK_d, spawn, {.v = menucmd } },
+ { MODKEY, XK_Return, spawn, {.v = termcmd } },
+ { MODKEY, XK_q, spawn, {.v = webscmd } },
+ { 0, XK_upvol, spawn, {.v = upvolcmd} },
+ { 0, XK_lovol, spawn, {.v = lovolcmd} },
+ { 0, XK_novol, spawn, {.v = novolcmd} },
+ { MODKEY, XK_s, togglescratch, {.v = scratchcmd} },
+ { MODKEY, XK_b, togglebar, {0} },
+ { MODKEY, XK_f, togglefocus, {0} },
+ { MODKEY, XK_Up, focusstack, {.i = +1 } },
+ { MODKEY, XK_Down, focusstack, {.i = -1 } },
+ { MODKEY|ShiftMask, XK_Up, rotatestack, {.i = +1 } },
+ { MODKEY|ShiftMask, XK_Down, rotatestack, {.i = -1 } },
+ { MODKEY, XK_i, incnmaster, {.i = +1 } },
+ { MODKEY, XK_o, incnmaster, {.i = -1 } },
+ { MODKEY, XK_h, focusdirection, {.i = 'l'} },
+ { MODKEY, XK_l, focusdirection, {.i = 'r'} },
+ { MODKEY, XK_k, focusdirection, {.i = 'u'} },
+ { MODKEY, XK_j, focusdirection, {.i = 'd'} },
+ { MODKEY|ShiftMask, XK_h, setmfact, {.f = -0.05} },
+ { MODKEY|ShiftMask, XK_l, setmfact, {.f = +0.05} },
+ { MODKEY|ShiftMask, XK_k, rotatestack, {.i = -1 } },
+ { MODKEY|ShiftMask, XK_j, rotatestack, {.i = +1 } },
+ { MODKEY|ShiftMask, XK_Return, zoom, {0} },
+ { MODKEY, XK_Tab, view, {0} },
+ { MODKEY|ShiftMask, XK_q, killclient, {0} },
+ { MODKEY|ShiftMask, XK_t, setlayout, {.v = &layouts[0]} },
+ { MODKEY|ShiftMask, XK_f, setlayout, {.v = &layouts[1]} },
+ { MODKEY|ShiftMask, XK_m, setlayout, {.v = &layouts[2]} },
+ { MODKEY, XK_space, setlayout, {0} },
+ { MODKEY|ShiftMask, XK_space, togglefloating, {0} },
+ { MODKEY, XK_0, view, {.ui = ~0 } },
+ { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } },
+ { MODKEY, XK_comma, focusmon, {.i = -1 } },
+ { MODKEY, XK_period, focusmon, {.i = +1 } },
+ { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } },
+ { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } },
+ TAGKEYS( XK_1, 0)
+ TAGKEYS( XK_2, 1)
+ TAGKEYS( XK_3, 2)
+ TAGKEYS( XK_4, 3)
+ TAGKEYS( XK_5, 4)
+ TAGKEYS( XK_6, 5)
+ TAGKEYS( XK_7, 6)
+ TAGKEYS( XK_8, 7)
+ TAGKEYS( XK_9, 8)
+ { MODKEY|ShiftMask, XK_e, quit, {0} },
+};
+
+/* button definitions */
+/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */
+static Button buttons[] = {
+ /* click event mask button function argument */
+ { ClkLtSymbol, 0, Button1, setlayout, {0} },
+ { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} },
+ { ClkWinTitle, 0, Button2, zoom, {0} },
+ { ClkStatusText, 0, Button2, spawn, {.v = termcmd } },
+ { ClkClientWin, MODKEY, Button1, movemouse, {0} },
+ { ClkClientWin, MODKEY, Button2, togglefloating, {0} },
+ { ClkClientWin, MODKEY, Button3, resizemouse, {0} },
+ { ClkTagBar, 0, Button1, view, {0} },
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+ { ClkTagBar, MODKEY, Button3, toggletag, {0} },
+};
+
diff --git a/src/cmd/dwm/drw.c b/src/cmd/dwm/drw.c
new file mode 100644
index 0000000..a6d6902
--- /dev/null
+++ b/src/cmd/dwm/drw.c
@@ -0,0 +1,376 @@
+/* See LICENSE file for copyright and license details. */
+#include "dwm.h"
+
+Drw *
+drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
+{
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
+ drw->gc = XCreateGC(dpy, root, 0, NULL);
+ XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ return drw;
+}
+
+void
+drw_resize(Drw *drw, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ drw->w = w;
+ drw->h = h;
+ if (drw->drawable)
+ XFreePixmap(drw->dpy, drw->drawable);
+ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
+}
+
+void
+drw_free(Drw *drw)
+{
+ XFreePixmap(drw->dpy, drw->drawable);
+ XFreeGC(drw->dpy, drw->gc);
+ free(drw);
+}
+
+/* This function is an implementation detail. Library users should use
+ * drw_fontset_create instead.
+ */
+static Fnt *
+xfont_create(Drw *drw, char *fontname, FcPattern *fontpattern)
+{
+ Fnt *font;
+ XftFont *xfont = NULL;
+ FcPattern *pattern = NULL;
+
+ if (fontname) {
+ /* Using the pattern found at font->xfont->pattern does not yield the
+ * same substitution results as using the pattern returned by
+ * FcNameParse; using the latter results in the desired fallback
+ * behaviour whereas the former just results in missing-character
+ * rectangles being drawn, at least with some fonts. */
+ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
+ fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
+ return NULL;
+ }
+ if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
+ fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+ } else if (fontpattern) {
+ if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
+ fprintf(stderr, "error, cannot load font from pattern.\n");
+ return NULL;
+ }
+ } else {
+ fatal("no font specified.");
+ }
+
+ /* Do not allow using color fonts. This is a workaround for a BadLength
+ * error from Xft with color glyphs. Modelled on the Xterm workaround. See
+ * https://bugzilla.redhat.com/show_bug.cgi?id=1498269
+ * https://lists.suckless.org/dev/1701/30932.html
+ * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
+ * and lots more all over the internet.
+ */
+ FcBool iscol;
+ if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+
+ font = ecalloc(1, sizeof(Fnt));
+ font->xfont = xfont;
+ font->pattern = pattern;
+ font->h = xfont->ascent + xfont->descent;
+ font->dpy = drw->dpy;
+
+ return font;
+}
+
+static void
+xfont_free(Fnt *font)
+{
+ if (!font)
+ return;
+ if (font->pattern)
+ FcPatternDestroy(font->pattern);
+ XftFontClose(font->dpy, font->xfont);
+ free(font);
+}
+
+Fnt*
+drw_fontset_create(Drw* drw, char *fonts[], size_t fontcount)
+{
+ Fnt *cur, *ret = NULL;
+ size_t i;
+
+ if (!drw || !fonts)
+ return NULL;
+
+ for (i = 1; i <= fontcount; i++) {
+ if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
+ cur->next = ret;
+ ret = cur;
+ }
+ }
+ return (drw->fonts = ret);
+}
+
+void
+drw_fontset_free(Fnt *font)
+{
+ if (font) {
+ drw_fontset_free(font->next);
+ xfont_free(font);
+ }
+}
+
+void
+drw_clr_create(Drw *drw, Clr *dest, char *clrname)
+{
+ if (!drw || !dest || !clrname)
+ return;
+
+ if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen),
+ clrname, dest))
+ fatal("error, cannot allocate color '%s'", clrname);
+}
+
+/* Wrapper to create color schemes. The caller has to call free(3) on the
+ * returned color scheme when done using it. */
+Clr *
+drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount)
+{
+ size_t i;
+ Clr *ret;
+
+ /* need at least two colors for a scheme */
+ if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
+ return NULL;
+
+ for (i = 0; i < clrcount; i++)
+ drw_clr_create(drw, &ret[i], clrnames[i]);
+ return ret;
+}
+
+void
+drw_setfontset(Drw *drw, Fnt *set)
+{
+ if (drw)
+ drw->fonts = set;
+}
+
+void
+drw_setscheme(Drw *drw, Clr *scm)
+{
+ if (drw)
+ drw->scheme = scm;
+}
+
+void
+drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
+{
+ if (!drw || !drw->scheme)
+ return;
+ XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
+ if (filled)
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ else
+ XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
+}
+
+int
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, char *text, int invert)
+{
+ char buf[1024];
+ int ty;
+ unsigned int ew;
+ XftDraw *d = NULL;
+ Fnt *usedfont, *curfont, *nextfont;
+ size_t i, len;
+ int utf8strlen, utf8charlen, render = x || y || w || h;
+ rune utf8codepoint = 0;
+ char *utf8str;
+ FcCharSet *fccharset;
+ FcPattern *fcpattern;
+ FcPattern *match;
+ XftResult result;
+ int charexists = 0;
+
+ if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
+ return 0;
+
+ if (!render) {
+ w = ~w;
+ } else {
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ d = XftDrawCreate(drw->dpy, drw->drawable,
+ DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen));
+ x += lpad;
+ w -= lpad;
+ }
+
+ usedfont = drw->fonts;
+ while (1) {
+ utf8strlen = 0;
+ utf8str = text;
+ nextfont = NULL;
+ while (*text) {
+ utf8charlen = utf8·decode(text, &utf8codepoint);
+ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
+ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
+ if (charexists) {
+ if (curfont == usedfont) {
+ utf8strlen += utf8charlen;
+ text += utf8charlen;
+ } else {
+ nextfont = curfont;
+ }
+ break;
+ }
+ }
+
+ if (!charexists || nextfont)
+ break;
+ else
+ charexists = 0;
+ }
+
+ if (utf8strlen) {
+ drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
+ /* shorten text if necessary */
+ for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--)
+ drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
+
+ if (len) {
+ memcpy(buf, utf8str, len);
+ buf[len] = '\0';
+ if (len < utf8strlen)
+ for (i = len; i && i > len - 3; buf[--i] = '.')
+ ; /* NOP */
+
+ if (render) {
+ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
+ XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
+ usedfont->xfont, x, ty, (XftChar8 *)buf, len);
+ }
+ x += ew;
+ w -= ew;
+ }
+ }
+
+ if (!*text) {
+ break;
+ } else if (nextfont) {
+ charexists = 0;
+ usedfont = nextfont;
+ } else {
+ /* Regardless of whether or not a fallback font is found, the
+ * character must be drawn. */
+ charexists = 1;
+
+ fccharset = FcCharSetCreate();
+ FcCharSetAddChar(fccharset, utf8codepoint);
+
+ if (!drw->fonts->pattern) {
+ /* Refer to the comment in xfont_create for more information. */
+ fatal("the first font in the cache must be loaded from a font string.");
+ }
+
+ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
+ FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
+
+ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+
+ FcCharSetDestroy(fccharset);
+ FcPatternDestroy(fcpattern);
+
+ if (match) {
+ usedfont = xfont_create(drw, NULL, match);
+ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
+ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
+ ; /* NOP */
+ curfont->next = usedfont;
+ } else {
+ xfont_free(usedfont);
+ usedfont = drw->fonts;
+ }
+ }
+ }
+ }
+ if (d)
+ XftDrawDestroy(d);
+
+ return x + (render ? w : 0);
+}
+
+void
+drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
+ XSync(drw->dpy, False);
+}
+
+unsigned int
+drw_fontset_getwidth(Drw *drw, char *text)
+{
+ if (!drw || !drw->fonts || !text)
+ return 0;
+ return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
+}
+
+void
+drw_font_getexts(Fnt *font, char *text, unsigned int len, unsigned int *w, unsigned int *h)
+{
+ XGlyphInfo ext;
+
+ if (!font || !text)
+ return;
+
+ XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
+ if (w)
+ *w = ext.xOff;
+ if (h)
+ *h = font->h;
+}
+
+Cur *
+drw_cur_create(Drw *drw, int shape)
+{
+ Cur *cur;
+
+ if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
+ return NULL;
+
+ cur->cursor = XCreateFontCursor(drw->dpy, shape);
+
+ return cur;
+}
+
+void
+drw_cur_free(Drw *drw, Cur *cursor)
+{
+ if (!cursor)
+ return;
+
+ XFreeCursor(drw->dpy, cursor->cursor);
+ free(cursor);
+}
diff --git a/src/cmd/dwm/dwm.c b/src/cmd/dwm/dwm.c
new file mode 100644
index 0000000..0567650
--- /dev/null
+++ b/src/cmd/dwm/dwm.c
@@ -0,0 +1,1185 @@
+#include "dwm.h"
+
+/* global variables */
+char broken[] = "<broken>";
+char stext[256];
+int scanner;
+int screen;
+int sw, sh; /* X display screen geometry width, height */
+int bh, blw = 0; /* bar geometry */
+int lrpad; /* sum of left and right padding for text */
+int (*xerrorxlib)(Display *, XErrorEvent *);
+uint numlockmask = 0;
+void (*handler[LASTEvent]) (XEvent *) = {
+ [ButtonPress] = buttonpress,
+ [ClientMessage] = clientmessage,
+ [ConfigureRequest] = configurerequest,
+ [ConfigureNotify] = configurenotify,
+ [DestroyNotify] = destroynotify,
+ [EnterNotify] = enternotify,
+ [Expose] = expose,
+ [FocusIn] = focusin,
+ [KeyPress] = keypress,
+ [MappingNotify] = mappingnotify,
+ [MapRequest] = maprequest,
+ [MotionNotify] = motionnotify,
+ [PropertyNotify] = propertynotify,
+ [UnmapNotify] = unmapnotify
+};
+
+xcb_connection_t *xcon;
+
+Atom wmatom[WMLast] = {0}, netatom[NetLast] = {0};
+int running = 1;
+Cur *cursor[MouseLast] = {0};
+Clr **scheme = nil;
+Display *dpy = nil;
+Drw *drw = nil;
+Monitor *mons = nil, *selmon = nil;
+Window root = {0}, wmcheckwin = {0};
+
+/* compile-time check if all tags fit into an uint bit array. */
+struct NumTags { char limitexceeded[arrlen(tags) > 31 ? -1 : 1]; };
+
+/* function implementations */
+void
+arrange(Monitor *m)
+{
+ if (m)
+ showhide(m->stack);
+ else for (m = mons; m; m = m->next)
+ showhide(m->stack);
+ if (m) {
+ arrangemon(m);
+ restack(m);
+ } else for (m = mons; m; m = m->next)
+ arrangemon(m);
+}
+
+void
+arrangemon(Monitor *m)
+{
+ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol);
+ if (m->lt[m->sellt]->arrange)
+ m->lt[m->sellt]->arrange(m);
+}
+
+void
+buttonpress(XEvent *e)
+{
+ uint i, x, click;
+ Arg arg = {0};
+ Client *c;
+ Monitor *m;
+ XButtonPressedEvent *ev = &e->xbutton;
+
+ click = ClkRootWin;
+ /* focus monitor if necessary */
+ if ((m = wintomon(ev->window)) && m != selmon) {
+ unfocus(selmon->sel, 1);
+ selmon = m;
+ focus(nil);
+ }
+ if (ev->window == selmon->barwin) {
+ i = x = 0;
+ do
+ x += TEXTW(tags[i]);
+ while (ev->x >= x && ++i < arrlen(tags));
+ if (i < arrlen(tags)) {
+ click = ClkTagBar;
+ arg.ui = 1 << i;
+ } else if (ev->x < x + blw)
+ click = ClkLtSymbol;
+ else if (ev->x > selmon->ww - TEXTW(stext))
+ click = ClkStatusText;
+ else
+ click = ClkWinTitle;
+ } else if ((c = wintoclient(ev->window))) {
+ focus(c);
+ restack(selmon);
+ XAllowEvents(dpy, ReplayPointer, CurrentTime);
+ click = ClkClientWin;
+ }
+ for (i = 0; i < arrlen(buttons); i++)
+ if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button
+ && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state))
+ buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg);
+}
+
+void
+checkotherwm(void)
+{
+ xerrorxlib = XSetErrorHandler(xerrorstart);
+ /* this causes an error if some other window manager is running */
+ XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XSync(dpy, False);
+}
+
+void
+cleanup(void)
+{
+ Arg a = {.ui = ~0};
+ Layout foo = { "", nil };
+ Monitor *m;
+ size_t i;
+
+ view(&a);
+ selmon->lt[selmon->sellt] = &foo;
+ for (m = mons; m; m = m->next)
+ while (m->stack)
+ unmanage(m->stack, 0);
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ while (mons)
+ cleanupmon(mons);
+ for (i = 0; i < MouseLast; i++)
+ drw_cur_free(drw, cursor[i]);
+ for (i = 0; i < arrlen(colors); i++)
+ free(scheme[i]);
+ XDestroyWindow(dpy, wmcheckwin);
+ drw_free(drw);
+ XSync(dpy, False);
+ XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+}
+
+void
+cleanupmon(Monitor *mon)
+{
+ Monitor *m;
+
+ if (mon == mons)
+ mons = mons->next;
+ else {
+ for (m = mons; m && m->next != mon; m = m->next);
+ m->next = mon->next;
+ }
+ XUnmapWindow(dpy, mon->barwin);
+ XDestroyWindow(dpy, mon->barwin);
+ free(mon);
+}
+
+void
+clientmessage(XEvent *e)
+{
+ XClientMessageEvent *cme = &e->xclient;
+ Client *c = wintoclient(cme->window);
+
+ if (!c)
+ return;
+ if (cme->message_type == netatom[NetWMState]) {
+ if (cme->data.l[1] == netatom[NetWMFullscreen]
+ || cme->data.l[2] == netatom[NetWMFullscreen])
+ setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */
+ || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen)));
+ } else if (cme->message_type == netatom[NetActiveWindow]) {
+ if (c != selmon->sel && !c->isurgent)
+ seturgent(c, 1);
+ }
+}
+
+void
+configurenotify(XEvent *e)
+{
+ Monitor *m;
+ Client *c;
+ XConfigureEvent *ev = &e->xconfigure;
+ int dirty;
+
+ /* TODO: updategeom handling sucks, needs to be simplified */
+ if (ev->window == root) {
+ dirty = (sw != ev->width || sh != ev->height);
+ sw = ev->width;
+ sh = ev->height;
+ if (updategeom() || dirty) {
+ drw_resize(drw, sw, bh);
+ updatebars();
+ for (m = mons; m; m = m->next) {
+ for (c = m->clients; c; c = c->next)
+ if (c->isfullscreen)
+ resizeclient(c, m->mx, m->my, m->mw, m->mh);
+ XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh);
+ }
+ focus(nil);
+ arrange(nil);
+ }
+ }
+}
+
+void
+configurerequest(XEvent *e)
+{
+ Client *c;
+ Monitor *m;
+ XConfigureRequestEvent *ev = &e->xconfigurerequest;
+ XWindowChanges wc;
+
+ if ((c = wintoclient(ev->window))) {
+ if (ev->value_mask & CWBorderWidth)
+ c->bw = ev->border_width;
+ else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) {
+ m = c->mon;
+ if (ev->value_mask & CWX) {
+ c->oldx = c->x;
+ c->x = m->mx + ev->x;
+ }
+ if (ev->value_mask & CWY) {
+ c->oldy = c->y;
+ c->y = m->my + ev->y;
+ }
+ if (ev->value_mask & CWWidth) {
+ c->oldw = c->w;
+ c->w = ev->width;
+ }
+ if (ev->value_mask & CWHeight) {
+ c->oldh = c->h;
+ c->h = ev->height;
+ }
+ if ((c->x + c->w) > m->mx + m->mw && c->isfloating)
+ c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */
+ if ((c->y + c->h) > m->my + m->mh && c->isfloating)
+ c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */
+ if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight)))
+ configure(c);
+ if (ISVISIBLE(c))
+ XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
+ } else
+ configure(c);
+ } else {
+ wc.x = ev->x;
+ wc.y = ev->y;
+ wc.width = ev->width;
+ wc.height = ev->height;
+ wc.border_width = ev->border_width;
+ wc.sibling = ev->above;
+ wc.stack_mode = ev->detail;
+ XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
+ }
+ XSync(dpy, False);
+}
+
+Monitor *
+createmon(void)
+{
+ Monitor *m;
+
+ m = ecalloc(1, sizeof(Monitor));
+ m->tagset[0] = m->tagset[1] = 1;
+ m->mfact = mfact;
+ m->nmaster = nmaster;
+ m->showbar = showbar;
+ m->topbar = topbar;
+ m->lt[0] = &layouts[0];
+ m->lt[1] = &layouts[1 % arrlen(layouts)];
+ strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
+ return m;
+}
+
+void
+destroynotify(XEvent *e)
+{
+ Client *c;
+ XDestroyWindowEvent *ev = &e->xdestroywindow;
+
+ if ((c = wintoclient(ev->window)))
+ unmanage(c, 1);
+ else if ((c = swallowing(ev->window)))
+ unmanage(c->swallowing, 1);
+}
+
+Monitor *
+dirtomon(int dir)
+{
+ Monitor *m = nil;
+
+ if (dir > 0) {
+ if (!(m = selmon->next))
+ m = mons;
+ } else if (selmon == mons)
+ for (m = mons; m->next; m = m->next);
+ else
+ for (m = mons; m->next != selmon; m = m->next);
+ return m;
+}
+
+void
+drawbar(Monitor *m)
+{
+ int x, w, tw = 0;
+ int boxs = drw->fonts->h / 9;
+ int boxw = drw->fonts->h / 6 + 2;
+ uint i, occ = 0, urg = 0;
+ Client *c;
+
+ /* draw status first so it can be overdrawn by tags later */
+ if(m == selmon) { /* status is only drawn on selected monitor */
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */
+ drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0);
+ }
+
+ for(c = m->clients; c; c = c->next) {
+ occ |= c->tags;
+ if (c->isurgent)
+ urg |= c->tags;
+ }
+ x = 0;
+ for(i = 0; i < arrlen(tags); i++) {
+ w = TEXTW(tags[i]);
+ drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i);
+ if (occ & 1 << i)
+ drw_rect(drw, x + boxs, boxs, boxw, boxw,
+ m == selmon && selmon->sel && selmon->sel->tags & 1 << i,
+ urg & 1 << i);
+ x += w;
+ }
+ w = blw = TEXTW(m->ltsymbol);
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0);
+
+ if((w = m->ww - tw - x) > bh) {
+ if (m->sel) {
+ drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0);
+ if (m->sel->isfloating)
+ drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0);
+ } else {
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, x, 0, w, bh, 1, 1);
+ }
+ }
+ drw_map(drw, m->barwin, 0, 0, m->ww, bh);
+}
+
+void
+drawbars(void)
+{
+ Monitor *m;
+
+ for (m = mons; m; m = m->next)
+ drawbar(m);
+}
+
+void
+enternotify(XEvent *e)
+{
+ Client *c;
+ Monitor *m;
+ XCrossingEvent *ev = &e->xcrossing;
+
+ if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root)
+ return;
+ c = wintoclient(ev->window);
+ m = c ? c->mon : wintomon(ev->window);
+ if (m != selmon) {
+ unfocus(selmon->sel, 1);
+ selmon = m;
+ } else if (!c || c == selmon->sel)
+ return;
+ focus(c);
+}
+
+void
+expose(XEvent *e)
+{
+ Monitor *m;
+ XExposeEvent *ev = &e->xexpose;
+
+ if (ev->count == 0 && (m = wintomon(ev->window)))
+ drawbar(m);
+}
+
+/* there are some broken focus acquiring clients needing extra handling */
+void
+focusin(XEvent *e)
+{
+ XFocusChangeEvent *ev = &e->xfocus;
+
+ if (selmon->sel && ev->window != selmon->sel->win)
+ setfocus(selmon->sel);
+}
+
+int
+getrootptr(int *x, int *y)
+{
+ int di;
+ uint dui;
+ Window dummy;
+
+ return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui);
+}
+
+long
+getstate(Window w)
+{
+ int format;
+ long result = -1;
+ unsigned char *p = nil;
+ unsigned long n, extra;
+ Atom real;
+
+ if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState],
+ &real, &format, &n, &extra, (unsigned char **)&p) != Success)
+ return -1;
+ if (n != 0)
+ result = *p;
+ XFree(p);
+ return result;
+}
+
+int
+gettextprop(Window w, Atom atom, char *text, uint size)
+{
+ char **list = nil;
+ int n;
+ XTextProperty name;
+
+ if (!text || size == 0)
+ return 0;
+ text[0] = '\0';
+ if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems)
+ return 0;
+ if (name.encoding == XA_STRING)
+ strncpy(text, (char *)name.value, size - 1);
+ else {
+ if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) {
+ strncpy(text, *list, size - 1);
+ XFreeStringList(list);
+ }
+ }
+ text[size - 1] = '\0';
+ XFree(name.value);
+ return 1;
+}
+
+void
+grabkeys(void)
+{
+ updatenumlockmask();
+ {
+ uint i, j;
+ uint modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
+ KeyCode code;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ for (i = 0; i < arrlen(keys); i++)
+ if ((code = XKeysymToKeycode(dpy, keys[i].keysym)))
+ for (j = 0; j < arrlen(modifiers); j++)
+ XGrabKey(dpy, code, keys[i].mod | modifiers[j], root,
+ True, GrabModeAsync, GrabModeAsync);
+ }
+}
+
+static
+int
+isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info)
+{
+ while (n--)
+ if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org
+ && unique[n].width == info->width && unique[n].height == info->height)
+ return 0;
+ return 1;
+}
+
+void
+keypress(XEvent *e)
+{
+ uint i;
+ KeySym keysym;
+ XKeyEvent *ev;
+
+ ev = &e->xkey;
+ keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0);
+ for (i = 0; i < arrlen(keys); i++)
+ if (keysym == keys[i].keysym
+ && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)
+ && keys[i].func)
+ keys[i].func(&(keys[i].arg));
+}
+
+void
+manage(Window w, XWindowAttributes *wa)
+{
+ Client *c, *t = nil, *term = nil;
+ Window trans = None;
+ XWindowChanges wc;
+
+ c = ecalloc(1, sizeof(Client));
+ c->win = w;
+ c->pid = winpid(w);
+ /* geometry */
+ c->x = c->oldx = wa->x;
+ c->y = c->oldy = wa->y;
+ c->w = c->oldw = wa->width;
+ c->h = c->oldh = wa->height;
+ c->oldbw = wa->border_width;
+
+ updatetitle(c);
+ if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) {
+ c->mon = t->mon;
+ c->tags = t->tags;
+ } else {
+ c->mon = selmon;
+ applyrules(c);
+ term = termof(c);
+ }
+
+ if (c->x + WIDTH(c) > c->mon->mx + c->mon->mw)
+ c->x = c->mon->mx + c->mon->mw - WIDTH(c);
+ if (c->y + HEIGHT(c) > c->mon->my + c->mon->mh)
+ c->y = c->mon->my + c->mon->mh - HEIGHT(c);
+ c->x = MAX(c->x, c->mon->mx);
+ /* only fix client y-offset, if the client center might cover the bar */
+ c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx)
+ && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my);
+ c->bw = borderpx;
+
+ selmon->tagset[selmon->seltags] &= ~scratchtag;
+ if(!strcmp(c->name, scratchname)) {
+ c->mon->tagset[c->mon->seltags] |= c->tags = scratchtag;
+ c->isfloating = 1;
+ c->x = c->mon->wx + (c->mon->ww / 2 - WIDTH(c) / 2);
+ c->y = c->mon->wy + (c->mon->wh / 2 - HEIGHT(c) / 2);
+ }
+
+ wc.border_width = c->bw;
+ XConfigureWindow(dpy, w, CWBorderWidth, &wc);
+ XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel);
+ configure(c); /* propagates border_width, if size doesn't change */
+ updatewindowtype(c);
+ updatesizehints(c);
+ updatewmhints(c);
+ XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
+ grabbuttons(c, 0);
+
+ if (!c->isfloating)
+ c->isfloating = c->oldstate = trans != None || c->isfixed;
+ if (c->isfloating)
+ XRaiseWindow(dpy, c->win);
+
+ /* attach(c); */
+ attachbottom(c);
+ attachstack(c);
+
+ XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend,
+ (unsigned char *) &(c->win), 1);
+ XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */
+ setclientstate(c, NormalState);
+ if (c->mon == selmon)
+ unfocus(selmon->sel, 0);
+ c->mon->sel = c;
+ arrange(c->mon);
+ XMapWindow(dpy, c->win);
+ if (term)
+ swallow(term, c);
+ focus(nil);
+}
+
+void
+mappingnotify(XEvent *e)
+{
+ XMappingEvent *ev = &e->xmapping;
+
+ XRefreshKeyboardMapping(ev);
+ if (ev->request == MappingKeyboard)
+ grabkeys();
+}
+
+void
+maprequest(XEvent *e)
+{
+ static XWindowAttributes wa;
+ XMapRequestEvent *ev = &e->xmaprequest;
+
+ if (!XGetWindowAttributes(dpy, ev->window, &wa))
+ return;
+ if (wa.override_redirect)
+ return;
+ if (!wintoclient(ev->window))
+ manage(ev->window, &wa);
+}
+
+void
+monocle(Monitor *m)
+{
+ uint n = 0;
+ Client *c;
+
+ for (c = m->clients; c; c = c->next)
+ if (ISVISIBLE(c))
+ n++;
+ if (n > 0) /* override layout symbol */
+ snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n);
+ for (c = nexttiled(m->clients); c; c = nexttiled(c->next))
+ resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0);
+}
+
+void
+motionnotify(XEvent *e)
+{
+ static Monitor *mon = nil;
+ Monitor *m;
+ XMotionEvent *ev = &e->xmotion;
+
+ if (ev->window != root)
+ return;
+ if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) {
+ unfocus(selmon->sel, 1);
+ selmon = m;
+ focus(nil);
+ }
+ mon = m;
+}
+
+void
+propertynotify(XEvent *e)
+{
+ Client *c;
+ Window trans;
+ XPropertyEvent *ev = &e->xproperty;
+
+ if ((ev->window == root) && (ev->atom == XA_WM_NAME))
+ updatestatus();
+ else if (ev->state == PropertyDelete)
+ return; /* ignore */
+ else if ((c = wintoclient(ev->window))) {
+ switch(ev->atom) {
+ default: break;
+ case XA_WM_TRANSIENT_FOR:
+ if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) &&
+ (c->isfloating = (wintoclient(trans)) != nil))
+ arrange(c->mon);
+ break;
+ case XA_WM_NORMAL_HINTS:
+ updatesizehints(c);
+ break;
+ case XA_WM_HINTS:
+ updatewmhints(c);
+ drawbars();
+ break;
+ }
+ if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) {
+ updatetitle(c);
+ if (c == c->mon->sel)
+ drawbar(c->mon);
+ }
+ if (ev->atom == netatom[NetWMWindowType])
+ updatewindowtype(c);
+ }
+}
+
+Monitor *
+recttomon(int x, int y, int w, int h)
+{
+ Monitor *m, *r = selmon;
+ int a, area = 0;
+
+ for (m = mons; m; m = m->next)
+ if ((a = INTERSECT(x, y, w, h, m)) > area) {
+ area = a;
+ r = m;
+ }
+ return r;
+}
+
+void
+restack(Monitor *m)
+{
+ Client *c;
+ XEvent ev;
+ XWindowChanges wc;
+
+ drawbar(m);
+ if (!m->sel)
+ return;
+ if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
+ XRaiseWindow(dpy, m->sel->win);
+ if (m->lt[m->sellt]->arrange) {
+ wc.stack_mode = Below;
+ wc.sibling = m->barwin;
+ for (c = m->stack; c; c = c->snext)
+ if (!c->isfloating && ISVISIBLE(c)) {
+ XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc);
+ wc.sibling = c->win;
+ }
+ }
+ XSync(dpy, False);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+}
+
+void
+run(void)
+{
+ XEvent ev;
+ /* main event loop */
+ XSync(dpy, False);
+ while (running && !XNextEvent(dpy, &ev))
+ if (handler[ev.type])
+ handler[ev.type](&ev); /* call handler */
+}
+
+void
+scan(void)
+{
+ uint i, num;
+ Window d1, d2, *wins = nil;
+ XWindowAttributes wa;
+ char swin[256];
+
+ scanner = 1;
+
+ if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
+ for (i = 0; i < num; i++) {
+ if (!XGetWindowAttributes(dpy, wins[i], &wa)
+ || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
+ continue;
+ if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)
+ manage(wins[i], &wa);
+ else if (gettextprop(wins[i], netatom[NetClientList], swin, sizeof swin))
+ manage(wins[i], &wa);
+ }
+ for (i = 0; i < num; i++) { /* now the transients */
+ if (!XGetWindowAttributes(dpy, wins[i], &wa))
+ continue;
+ if (XGetTransientForHint(dpy, wins[i], &d1)
+ && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState))
+ manage(wins[i], &wa);
+ }
+ if (wins)
+ XFree(wins);
+ }
+
+ scanner = 0;
+}
+
+void
+setup(void)
+{
+ int i;
+ XSetWindowAttributes wa;
+ Atom utf8string;
+
+ /* clean up any zombies immediately */
+ sigchld(0);
+
+ /* init screen */
+ screen = DefaultScreen(dpy);
+ sw = DisplayWidth(dpy, screen);
+ sh = DisplayHeight(dpy, screen);
+ root = RootWindow(dpy, screen);
+ drw = drw_create(dpy, screen, root, sw, sh);
+ if (!drw_fontset_create(drw, fonts, arrlen(fonts)))
+ fatal("no fonts could be loaded.");
+
+ lrpad = drw->fonts->h;
+ bh = drw->fonts->h + 2;
+ updategeom();
+
+ /* init atoms */
+ utf8string = XInternAtom(dpy, "UTF8_STRING", False);
+ wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
+ wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+ wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False);
+ wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
+
+ netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
+ netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
+ netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
+ netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False);
+ netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
+ netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
+ netatom[NetWMWindowOpacity] = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False);
+ netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+ netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
+
+ /* init cursors */
+ cursor[MouseNormal] = drw_cur_create(drw, XC_left_ptr);
+ cursor[MouseResize] = drw_cur_create(drw, XC_sizing);
+ cursor[MouseMove] = drw_cur_create(drw, XC_fleur);
+
+ /* init appearance */
+ scheme = ecalloc(arrlen(colors), sizeof(Clr *));
+ for (i = 0; i < arrlen(colors); i++)
+ scheme[i] = drw_scm_create(drw, colors[i], 3);
+
+ /* init bars */
+ updatebars();
+ updatestatus();
+
+ /* supporting window for NetWMCheck */
+ wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (uchar *) &wmcheckwin, 1);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
+ PropModeReplace, (uchar *) "dwm", 3);
+ XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (uchar *) &wmcheckwin, 1);
+ /* EWMH support per view */
+ XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
+ PropModeReplace, (uchar *) netatom, NetLast);
+ XDeleteProperty(dpy, root, netatom[NetClientList]);
+ /* select events */
+ wa.cursor = cursor[MouseNormal]->cursor;
+ wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask
+ |ButtonPressMask|PointerMotionMask|EnterWindowMask
+ |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask;
+ XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
+ XSelectInput(dpy, root, wa.event_mask);
+ grabkeys();
+ focus(nil);
+}
+
+
+void
+sigchld(int unused)
+{
+ if (signal(SIGCHLD, sigchld) == SIG_ERR)
+ fatal("can't install SIGCHLD handler:");
+ while (0 < waitpid(-1, nil, WNOHANG));
+}
+
+Client *
+swallowing(Window w)
+{
+ Client *c;
+ Monitor *m;
+
+ for (m = mons; m; m = m->next) {
+ for (c = m->clients; c; c = c->next) {
+ if (c->swallowing && c->swallowing->win == w)
+ return c;
+ }
+ }
+
+ return nil;
+}
+
+void
+tile(Monitor *m)
+{
+ uint i, n, h, r, mw, my, ty;
+ Client *c;
+
+ for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++)
+ ;
+
+ if (n == 0)
+ return;
+
+ if (n > m->nmaster)
+ mw = m->nmaster ? (m->ww+gapx) * m->mfact : 0;
+ else
+ mw = m->ww - gapx;
+
+ for (i = 0, my = ty = gapx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++)
+ if (i < m->nmaster) {
+ r = MIN(n, m->nmaster) - i;
+ h = (m->wh - my)/r - gapx;
+ resize(c, m->wx + gapx, m->wy + my, mw - (2*c->bw) - gapx, h - (2*c->bw), 0);
+ if (my + HEIGHT(c) + gapx < m->wh)
+ my += HEIGHT(c) + gapx;
+ } else {
+ r = (n-i);
+ h = (m->wh - ty)/r - gapx;
+ resize(c, m->wx + mw + gapx, m->wy + ty, m->ww - mw - (2*c->bw) - (2*gapx), h - (2*c->bw), 0);
+ if (ty + HEIGHT(c) + gapx < m->wh)
+ ty += HEIGHT(c) + gapx;
+ }
+}
+
+void
+unmapnotify(XEvent *e)
+{
+ Client *c;
+ XUnmapEvent *ev = &e->xunmap;
+
+ if ((c = wintoclient(ev->window))) {
+ if (ev->send_event)
+ setclientstate(c, WithdrawnState);
+ else
+ unmanage(c, 0);
+ }
+}
+
+void
+updatebars(void)
+{
+ Monitor *m;
+ XSetWindowAttributes wa = {
+ .override_redirect = True,
+ .background_pixmap = ParentRelative,
+ .event_mask = ButtonPressMask|ExposureMask
+ };
+ XClassHint ch = {"dwm", "dwm"};
+ for (m = mons; m; m = m->next) {
+ if (m->barwin)
+ continue;
+ m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen),
+ CopyFromParent, DefaultVisual(dpy, screen),
+ CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa);
+ XDefineCursor(dpy, m->barwin, cursor[MouseNormal]->cursor);
+ XMapRaised(dpy, m->barwin);
+ XSetClassHint(dpy, m->barwin, &ch);
+ }
+}
+
+void
+updatebarpos(Monitor *m)
+{
+ m->wy = m->my;
+ m->wh = m->mh;
+ if (m->showbar) {
+ m->wh -= bh;
+ m->by = m->topbar ? m->wy : m->wy + m->wh;
+ m->wy = m->topbar ? m->wy + bh : m->wy;
+ } else
+ m->by = -bh;
+}
+
+void
+updateclientlist()
+{
+ Client *c;
+ Monitor *m;
+
+ XDeleteProperty(dpy, root, netatom[NetClientList]);
+ for (m = mons; m; m = m->next)
+ for (c = m->clients; c; c = c->next)
+ XChangeProperty(dpy, root, netatom[NetClientList],
+ XA_WINDOW, 32, PropModeAppend,
+ (unsigned char *) &(c->win), 1);
+}
+
+int
+updategeom(void)
+{
+ int dirty = 0;
+
+ if (XineramaIsActive(dpy)) {
+ int i, j, n, nn;
+ Client *c;
+ Monitor *m;
+ XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn);
+ XineramaScreenInfo *unique = nil;
+
+ for (n = 0, m = mons; m; m = m->next, n++);
+ /* only consider unique geometries as separate screens */
+ unique = ecalloc(nn, sizeof(XineramaScreenInfo));
+ for (i = 0, j = 0; i < nn; i++)
+ if (isuniquegeom(unique, j, &info[i]))
+ memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo));
+ XFree(info);
+ nn = j;
+ if (n <= nn) { /* new monitors available */
+ for (i = 0; i < (nn - n); i++) {
+ for (m = mons; m && m->next; m = m->next);
+ if (m)
+ m->next = createmon();
+ else
+ mons = createmon();
+ }
+ for (i = 0, m = mons; i < nn && m; m = m->next, i++)
+ if (i >= n
+ || unique[i].x_org != m->mx || unique[i].y_org != m->my
+ || unique[i].width != m->mw || unique[i].height != m->mh)
+ {
+ dirty = 1;
+ m->num = i;
+ m->mx = m->wx = unique[i].x_org;
+ m->my = m->wy = unique[i].y_org;
+ m->mw = m->ww = unique[i].width;
+ m->mh = m->wh = unique[i].height;
+ updatebarpos(m);
+ }
+ } else { /* less monitors available nn < n */
+ for (i = nn; i < n; i++) {
+ for (m = mons; m && m->next; m = m->next);
+ while ((c = m->clients)) {
+ dirty = 1;
+ m->clients = c->next;
+ detachstack(c);
+ c->mon = mons;
+ /* attach(c); */
+ attachbottom(c);
+ attachstack(c);
+ }
+ if (m == selmon)
+ selmon = mons;
+ cleanupmon(m);
+ }
+ }
+ free(unique);
+ } else
+ { /* default monitor setup */
+ if (!mons)
+ mons = createmon();
+ if (mons->mw != sw || mons->mh != sh) {
+ dirty = 1;
+ mons->mw = mons->ww = sw;
+ mons->mh = mons->wh = sh;
+ updatebarpos(mons);
+ }
+ }
+ if (dirty) {
+ selmon = mons;
+ selmon = wintomon(root);
+ }
+ return dirty;
+}
+
+void
+updatenumlockmask(void)
+{
+ uint i, j;
+ XModifierKeymap *modmap;
+
+ numlockmask = 0;
+ modmap = XGetModifierMapping(dpy);
+ for (i = 0; i < 8; i++)
+ for (j = 0; j < modmap->max_keypermod; j++)
+ if (modmap->modifiermap[i * modmap->max_keypermod + j]
+ == XKeysymToKeycode(dpy, XK_Num_Lock))
+ numlockmask = (1 << i);
+ XFreeModifiermap(modmap);
+}
+
+void
+updatestatus(void)
+{
+ if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext)))
+ strcpy(stext, "dwm-"VERSION);
+ drawbar(selmon);
+}
+
+pid_t
+winpid(Window w)
+{
+ pid_t result = 0;
+
+ xcb_res_client_id_spec_t spec = {0};
+ spec.client = w;
+ spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID;
+
+ xcb_generic_error_t *e = NULL;
+ xcb_res_query_client_ids_cookie_t c = xcb_res_query_client_ids(xcon, 1, &spec);
+ xcb_res_query_client_ids_reply_t *r = xcb_res_query_client_ids_reply(xcon, c, &e);
+
+ if (!r)
+ return (pid_t)0;
+
+ xcb_res_client_id_value_iterator_t i = xcb_res_query_client_ids_ids_iterator(r);
+ for (; i.rem; xcb_res_client_id_value_next(&i)) {
+ spec = i.data->spec;
+ if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) {
+ uint32_t *t = xcb_res_client_id_value_value(i.data);
+ result = *t;
+ break;
+ }
+ }
+
+ free(r);
+
+ if (result == (pid_t)-1)
+ result = 0;
+
+ return result;
+}
+
+Client *
+wintoclient(Window w)
+{
+ Client *c;
+ Monitor *m;
+
+ for (m = mons; m; m = m->next)
+ for (c = m->clients; c; c = c->next)
+ if (c->win == w)
+ return c;
+ return nil;
+}
+
+Monitor *
+wintomon(Window w)
+{
+ int x, y;
+ Client *c;
+ Monitor *m;
+
+ if (w == root && getrootptr(&x, &y))
+ return recttomon(x, y, 1, 1);
+ for (m = mons; m; m = m->next)
+ if (w == m->barwin)
+ return m;
+ if ((c = wintoclient(w)))
+ return c->mon;
+ return selmon;
+}
+
+/* There's no way to check accesses to destroyed windows, thus those cases are
+ * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
+ * default error handler, which may call exit. */
+int
+xerror(Display *dpy, XErrorEvent *ee)
+{
+ if (ee->error_code == BadWindow
+ || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
+ || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
+ || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
+ || (ee->request_code == X_GrabButton && ee->error_code == BadAccess)
+ || (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
+ || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
+ return 0;
+ fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
+ ee->request_code, ee->error_code);
+ return xerrorxlib(dpy, ee); /* may call exit */
+}
+
+int
+xerrordummy(Display *dpy, XErrorEvent *ee)
+{
+ return 0;
+}
+
+/* Startup Error handler to check if another window manager
+ * is already running. */
+int
+xerrorstart(Display *dpy, XErrorEvent *ee)
+{
+ fatal("dwm: another window manager is already running");
+ return -1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc == 2 && !strcmp("-v", argv[1]))
+ fatal("dwm-"VERSION);
+ else if (argc != 1)
+ fatal("usage: dwm [-v]");
+ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+ fputs("warning: no locale support\n", stderr);
+ if (!(dpy = XOpenDisplay(nil)))
+ fatal("dwm: cannot open display");
+ if (!(xcon = XGetXCBConnection(dpy)))
+ fatal("dwm: cannot get xcb connection");
+
+ checkotherwm();
+ setup();
+
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath proc exec", nil) == -1)
+ fatal("pledge");
+#endif /* __OpenBSD__ */
+
+ scan();
+ run();
+ cleanup();
+
+ XCloseDisplay(dpy);
+ return 0;
+}
diff --git a/src/cmd/dwm/dwm.h b/src/cmd/dwm/dwm.h
new file mode 100644
index 0000000..afec1f2
--- /dev/null
+++ b/src/cmd/dwm/dwm.h
@@ -0,0 +1,384 @@
+/* See LICENSE file for copyright and license details. */
+#pragma once
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+
+#include <errno.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+#include <X11/Xproto.h>
+#include <X11/Xutil.h>
+#include <X11/Xlib-xcb.h>
+#include <xcb/res.h>
+#include <X11/extensions/Xinerama.h>
+#include <X11/Xft/Xft.h>
+#include <X11/XF86keysym.h>
+
+/* macros */
+#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
+#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))
+#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \
+ * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy)))
+#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]))
+#define MOUSEMASK (BUTTONMASK|PointerMotionMask)
+#define WIDTH(X) ((X)->w + 2 * (X)->bw)
+#define HEIGHT(X) ((X)->h + 2 * (X)->bw)
+#define TAGMASK ((1 << arrlen(tags)) - 1)
+#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+
+
+/* enums */
+enum
+{
+ MouseNormal,
+ MouseResize,
+ MouseMove,
+ MouseLast,
+}; /* mouse states */
+
+enum
+{
+ SchemeNorm,
+ SchemeSel
+}; /* color schemes */
+
+enum
+{
+ NetSupported,
+ NetWMName,
+ NetWMState,
+ NetWMCheck,
+ NetWMFullscreen,
+ NetActiveWindow,
+ NetWMWindowType,
+ NetWMWindowTypeDialog,
+ NetWMWindowOpacity,
+ NetClientList,
+ NetLast
+}; /* EWMH atoms */
+
+enum
+{
+ WMProtocols,
+ WMDelete,
+ WMState,
+ WMTakeFocus,
+ WMLast
+}; /* default atoms */
+
+enum
+{
+ ClkTagBar,
+ ClkLtSymbol,
+ ClkStatusText,
+ ClkWinTitle,
+ ClkClientWin,
+ ClkRootWin,
+ ClkLast
+}; /* clicks */
+
+enum
+{
+ ColFg,
+ ColBg,
+ ColBorder
+}; /* color scheme index */
+
+typedef struct Monitor Monitor;
+typedef struct Layout Layout;
+typedef struct Client Client;
+typedef struct Keyboard Keyboard;
+typedef struct Button Button;
+typedef struct Key Key;
+typedef struct Rule Rule;
+typedef union Arg Arg;
+
+union Arg {
+ int i;
+ uint ui;
+ float f;
+ void *v;
+};
+
+struct Button {
+ uint click;
+ uint mask;
+ uint button;
+ void (*func)(Arg *arg);
+ Arg arg;
+};
+
+struct Client {
+ char name[256];
+ float mina, maxa;
+ int x, y, w, h;
+ int oldx, oldy, oldw, oldh;
+ int basew, baseh, incw, inch, maxw, maxh, minw, minh;
+ int bw, oldbw;
+ uint tags;
+ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, isterm, noswallow;
+ pid_t pid;
+ Client *next;
+ Client *snext;
+ Client *swallowing;
+ Monitor *mon;
+ Window win;
+};
+
+struct Key {
+ uint mod;
+ KeySym keysym;
+ void (*func)(Arg *);
+ Arg arg;
+};
+
+struct Layout {
+ char *symbol;
+ void (*arrange)(Monitor *);
+};
+
+struct Monitor {
+ char ltsymbol[16];
+ float mfact;
+ int nmaster;
+ int num;
+ int by; /* bar geometry */
+ int mx, my, mw, mh; /* screen size */
+ int wx, wy, ww, wh; /* window area */
+ uint seltags;
+ uint sellt;
+ uint tagset[2];
+ int showbar;
+ int topbar;
+ Client *clients;
+ Client *sel;
+ Client *stack;
+ Monitor *next;
+ Window barwin;
+ Layout *lt[2];
+};
+
+struct Rule {
+ char *class;
+ char *instance;
+ char *title;
+ uint tags;
+ int isfloating;
+ int isterm;
+ int noswallow;
+ int monitor;
+};
+
+/* draw.c */
+typedef struct {
+ Cursor cursor;
+} Cur;
+
+typedef struct Fnt {
+ Display *dpy;
+ uint h;
+ XftFont *xfont;
+ FcPattern *pattern;
+ struct Fnt *next;
+} Fnt;
+
+typedef XftColor Clr;
+
+typedef struct {
+ uint w, h;
+ Display *dpy;
+ int screen;
+ Window root;
+ Drawable drawable;
+ GC gc;
+ Clr *scheme;
+ Fnt *fonts;
+} Drw;
+
+/* global state */
+
+extern char broken[];
+extern char stext[256];
+extern int scanner;
+extern int screen;
+extern int sw, sh;
+extern int bh, blw;
+extern int lrpad;
+extern int (*xerrorxlib)(Display *, XErrorEvent *);
+extern uint numlockmask;
+extern void (*handler[LASTEvent]) (XEvent *);
+extern int scratchtag;
+
+extern xcb_connection_t *xcon;
+
+extern Atom wmatom[WMLast], netatom[NetLast];
+extern int running;
+extern Cur *cursor[MouseLast];
+extern Clr **scheme;
+extern Display *dpy;
+extern Drw *drw;
+extern Monitor *mons, *selmon;
+extern Window root, wmcheckwin;
+
+// -----------------------------------------------------------------------
+// function declarations
+
+// TODO: remove declarations that don't require global existence...
+void applyrules(Client *c);
+int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact);
+void arrange(Monitor *m);
+void arrangemon(Monitor *m);
+void attach(Client *c);
+void enqueue(Client *c);
+void attachbottom(Client *c);
+void attachstack(Client *c);
+void enqueuestack(Client *c);
+void buttonpress(XEvent *e);
+void checkotherwm(void);
+void cleanup(void);
+void cleanupmon(Monitor *mon);
+void clientmessage(XEvent *e);
+void configure(Client *c);
+void configurenotify(XEvent *e);
+void configurerequest(XEvent *e);
+Monitor *createmon(void);
+void destroynotify(XEvent *e);
+void detach(Client *c);
+void detachstack(Client *c);
+Monitor *dirtomon(int dir);
+void drawbar(Monitor *m);
+void drawbars(void);
+void enternotify(XEvent *e);
+void expose(XEvent *e);
+void focus(Client *c);
+void focusin(XEvent *e);
+void focusmon(Arg *arg);
+void focusstack(Arg *arg);
+void focusdirection(Arg *arg);
+void rotatestack(Arg *arg);
+Atom getatomprop(Client *c, Atom prop);
+int getrootptr(int *x, int *y);
+long getstate(Window w);
+int gettextprop(Window w, Atom atom, char *text, uint size);
+void grabbuttons(Client *c, int focused);
+void grabkeys(void);
+void incnmaster(Arg *arg);
+void keypress(XEvent *e);
+void killclient(Arg *arg);
+void manage(Window w, XWindowAttributes *wa);
+void mappingnotify(XEvent *e);
+void maprequest(XEvent *e);
+void monocle(Monitor *m);
+void motionnotify(XEvent *e);
+void movemouse(Arg *arg);
+Client *nexttiled(Client *c);
+void pop(Client *);
+void propertynotify(XEvent *e);
+void quit(Arg *arg);
+Monitor *recttomon(int x, int y, int w, int h);
+void resize(Client *c, int x, int y, int w, int h, int interact);
+void resizeclient(Client *c, int x, int y, int w, int h);
+void resizemouse(Arg *arg);
+void restack(Monitor *m);
+void run(void);
+void scan(void);
+int sendevent(Client *c, Atom proto);
+void sendtomon(Client *c, Monitor *m);
+void setclientstate(Client *c, long state);
+void setfocus(Client *c);
+void setfullscreen(Client *c, int fullscreen);
+void setlayout(Arg *arg);
+void setmfact(Arg *arg);
+void setup(void);
+void seturgent(Client *c, int urg);
+void showhide(Client *c);
+void sigchld(int unused);
+void swallow(Client *p, Client *c);
+Client *swallowing(Window w);
+void spawn(Arg *arg);
+void tag(Arg *arg);
+void tagmon(Arg *arg);
+Client *termof(Client *c);
+void tile(Monitor *);
+void togglebar(Arg *arg);
+void togglefocus(Arg *arg);
+void togglefloating(Arg *arg);
+void togglescratch(Arg *arg);
+void toggletag(Arg *arg);
+void toggleview(Arg *arg);
+void unfocus(Client *c, int setfocus);
+void unmanage(Client *c, int destroyed);
+void unmapnotify(XEvent *e);
+void unswallow(Client *c);
+void updatebarpos(Monitor *m);
+void updatebars(void);
+void updateclientlist(void);
+int updategeom(void);
+void updatenumlockmask(void);
+void updatesizehints(Client *c);
+void updatestatus(void);
+void updatetitle(Client *c);
+void updatewindowtype(Client *c);
+void updatewmhints(Client *c);
+void view(Arg *arg);
+pid_t winpid(Window w);
+Client *wintoclient(Window w);
+Monitor *wintomon(Window w);
+int xerror(Display *dpy, XErrorEvent *ee);
+int xerrordummy(Display *dpy, XErrorEvent *ee);
+int xerrorstart(Display *dpy, XErrorEvent *ee);
+void zoom(Arg *arg);
+
+#include "config.h"
+
+/* draw.c */
+
+/* Drawable abstraction */
+Drw *drw_create(Display *dpy, int screen, Window win, uint w, uint h);
+void drw_resize(Drw *drw, uint w, uint h);
+void drw_free(Drw *drw);
+
+/* Fnt abstraction */
+Fnt *drw_fontset_create(Drw* drw, char *fonts[], size_t fontcount);
+void drw_fontset_free(Fnt* set);
+uint drw_fontset_getwidth(Drw *drw, char *text);
+void drw_font_getexts(Fnt *font, char *text, uint len, uint *w, uint *h);
+
+/* Colorscheme abstraction */
+void drw_clr_create(Drw *drw, Clr *dest, char *clrname);
+Clr *drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount);
+
+/* Cursor abstraction */
+Cur *drw_cur_create(Drw *drw, int shape);
+void drw_cur_free(Drw *drw, Cur *cursor);
+
+/* Drawing context manipulation */
+void drw_setfontset(Drw *drw, Fnt *set);
+void drw_setscheme(Drw *drw, Clr *scm);
+
+/* Drawing functions */
+void drw_rect(Drw *drw, int x, int y, uint w, uint h, int filled, int invert);
+int drw_text(Drw *drw, int x, int y, uint w, uint h, uint lpad, char *text, int invert);
+
+/* Map functions */
+void drw_map(Drw *drw, Window win, int x, int y, uint w, uint h);
+
+/* util.c */
+void fatal(char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);
+pid_t getparentproc(pid_t p);
+pid_t isdescendent(pid_t p, pid_t c);
diff --git a/src/cmd/dwm/hook.c b/src/cmd/dwm/hook.c
new file mode 100644
index 0000000..9758965
--- /dev/null
+++ b/src/cmd/dwm/hook.c
@@ -0,0 +1,489 @@
+#include "dwm.h"
+
+int scratchtag = 1 << arrlen(tags);
+
+void
+focusmon(Arg *arg)
+{
+ Monitor *m;
+
+ if (!mons->next)
+ return;
+ if ((m = dirtomon(arg->i)) == selmon)
+ return;
+ unfocus(selmon->sel, 0);
+ selmon = m;
+ focus(nil);
+}
+
+void
+focusstack(Arg *arg)
+{
+ Client *c = nil, *i;
+
+ if (!selmon->sel)
+ return;
+ if (arg->i > 0) {
+ for(c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next);
+ if(!c)
+ for(c = selmon->clients; c && !ISVISIBLE(c); c = c->next);
+ } else {
+ for(i = selmon->clients; i != selmon->sel; i = i->next)
+ if(ISVISIBLE(i))
+ c = i;
+ if(!c)
+ for(; i; i = i->next)
+ if (ISVISIBLE(i))
+ c = i;
+ }
+ if(c) {
+ focus(c);
+ restack(selmon);
+ }
+}
+
+void
+focusdirection(Arg *arg)
+{
+ Monitor *m;
+ Client *it, *c;
+ int x, y, cx, cy;
+
+ if(!selmon || !selmon->sel)
+ return;
+
+ c = selmon->sel;
+ x = c->x, y = c->y;
+
+ c = nil;
+ switch(arg->i) {
+ case 'l':
+ cx = INT_MIN;
+ cy = y;
+ for(m=mons; m; m=m->next) {
+ for(it=m->clients; it; it = it->next) {
+ if(ISVISIBLE(it) && (it->x < x)) {
+ if((it->x > cx) || ((it->x == cx) && abs(y-it->y) < abs(y-cy))) {
+ c = it;
+ cx = it->x;
+ cy = it->y;
+ }
+ }
+ }
+ }
+ break;
+
+ case 'r':
+ cx = INT_MAX;
+ cy = y;
+ for(m=mons; m; m=m->next) {
+ for(it=m->clients; it; it = it->next) {
+ if(ISVISIBLE(it) && (it->x > x)) {
+ if((it->x < cx) || ((it->x == cx) && abs(y-it->y) < abs(y-cy))) {
+ c = it;
+ cx = it->x;
+ cy = it->y;
+ }
+ }
+ }
+ }
+ break;
+
+ case 'u':
+ cx = x;
+ cy = INT_MIN;
+ for(m=mons; m; m=m->next) {
+ for(it=m->clients; it; it = it->next) {
+ if(ISVISIBLE(it) && (it->y < y)) {
+ if((it->y > cy) || ((it->y == cy) && abs(x-it->x) < abs(x-cx))) {
+ c = it;
+ cx = it->x;
+ cy = it->y;
+ }
+ }
+ }
+ }
+ break;
+
+ case 'd':
+ cx = x;
+ cy = INT_MAX;
+ for(m=mons; m; m=m->next) {
+ for(it=m->clients; it; it = it->next) {
+ if(ISVISIBLE(it) && (it->y > y)) {
+ if((it->y < cy) || ((it->y == cy) && abs(x-it->x) < abs(x-cx))) {
+ c = it;
+ cx = it->x;
+ cy = it->y;
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ ;
+ }
+
+ if(c) {
+ focus(c);
+ restack(selmon);
+ if(c->mon != selmon)
+ restack(c->mon);
+ }
+}
+
+void
+rotatestack(Arg *arg)
+{
+ Client *c = nil, *f;
+
+ if (!selmon->sel)
+ return;
+
+ f = selmon->sel;
+ if (arg->i > 0) {
+ for (c = nexttiled(selmon->clients); c && nexttiled(c->next); c = nexttiled(c->next))
+ ;
+
+ if (c) {
+ detach(c);
+ attach(c);
+ detachstack(c);
+ attachstack(c);
+ }
+ } else {
+ if ((c = nexttiled(selmon->clients))) {
+ detach(c);
+ enqueue(c);
+ detachstack(c);
+ enqueuestack(c);
+ }
+ }
+
+ if (c) {
+ arrange(selmon);
+ focus(f);
+ restack(selmon);
+ }
+}
+
+
+void
+incnmaster(Arg *arg)
+{
+ selmon->nmaster = MAX(selmon->nmaster + arg->i, 0);
+ arrange(selmon);
+}
+
+void
+killclient(Arg *arg)
+{
+ if (!selmon->sel)
+ return;
+ if (!sendevent(selmon->sel, wmatom[WMDelete])) {
+ XGrabServer(dpy);
+ XSetErrorHandler(xerrordummy);
+ XSetCloseDownMode(dpy, DestroyAll);
+ XKillClient(dpy, selmon->sel->win);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XUngrabServer(dpy);
+ }
+}
+
+void
+movemouse(Arg *arg)
+{
+ int x, y, ocx, ocy, nx, ny;
+ Client *c;
+ Monitor *m;
+ XEvent ev;
+ Time lasttime = 0;
+
+ if (!(c = selmon->sel))
+ return;
+ if (c->isfullscreen) /* no support moving fullscreen windows by mouse */
+ return;
+ restack(selmon);
+ ocx = c->x;
+ ocy = c->y;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[MouseMove]->cursor, CurrentTime) != GrabSuccess)
+ return;
+ if (!getrootptr(&x, &y))
+ return;
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+ continue;
+ lasttime = ev.xmotion.time;
+
+ nx = ocx + (ev.xmotion.x - x);
+ ny = ocy + (ev.xmotion.y - y);
+ if (abs(selmon->wx - nx) < snap)
+ nx = selmon->wx;
+ else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap)
+ nx = selmon->wx + selmon->ww - WIDTH(c);
+ if (abs(selmon->wy - ny) < snap)
+ ny = selmon->wy;
+ else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap)
+ ny = selmon->wy + selmon->wh - HEIGHT(c);
+ if (!c->isfloating && selmon->lt[selmon->sellt]->arrange
+ && (abs(nx - c->x) > snap || abs(ny - c->y) > snap))
+ togglefloating(nil);
+ if (!selmon->lt[selmon->sellt]->arrange || c->isfloating)
+ resize(c, nx, ny, c->w, c->h, 1);
+ break;
+ }
+ } while (ev.type != ButtonRelease);
+ XUngrabPointer(dpy, CurrentTime);
+ if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
+ sendtomon(c, m);
+ selmon = m;
+ focus(nil);
+ }
+}
+
+void
+quit(Arg *arg)
+{
+ running = 0;
+}
+
+void
+resizemouse(Arg *arg)
+{
+ int ocx, ocy, nw, nh;
+ Client *c;
+ Monitor *m;
+ XEvent ev;
+ Time lasttime = 0;
+
+ if (!(c = selmon->sel))
+ return;
+ if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */
+ return;
+ restack(selmon);
+ ocx = c->x;
+ ocy = c->y;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[MouseResize]->cursor, CurrentTime) != GrabSuccess)
+ return;
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+ continue;
+ lasttime = ev.xmotion.time;
+
+ nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1);
+ nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1);
+ if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww
+ && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh)
+ {
+ if (!c->isfloating && selmon->lt[selmon->sellt]->arrange
+ && (abs(nw - c->w) > snap || abs(nh - c->h) > snap))
+ togglefloating(nil);
+ }
+ if (!selmon->lt[selmon->sellt]->arrange || c->isfloating)
+ resize(c, c->x, c->y, nw, nh, 1);
+ break;
+ }
+ } while (ev.type != ButtonRelease);
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
+ XUngrabPointer(dpy, CurrentTime);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+ if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
+ sendtomon(c, m);
+ selmon = m;
+ focus(nil);
+ }
+}
+
+void
+setlayout(Arg *arg)
+{
+ if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt])
+ selmon->sellt ^= 1;
+ if (arg && arg->v)
+ selmon->lt[selmon->sellt] = (Layout *)arg->v;
+ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol);
+ if (selmon->sel)
+ arrange(selmon);
+ else
+ drawbar(selmon);
+}
+
+/* arg > 1.0 will set mfact absolutely */
+void
+setmfact(Arg *arg)
+{
+ float f;
+
+ if (!arg || !selmon->lt[selmon->sellt]->arrange)
+ return;
+ f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0;
+ if (f < 0.05 || f > 0.95)
+ return;
+ selmon->mfact = f;
+ arrange(selmon);
+}
+
+void
+spawn(Arg *arg)
+{
+ selmon->tagset[selmon->seltags] &= ~scratchtag;
+
+ if (fork() == 0) {
+ if (dpy)
+ close(ConnectionNumber(dpy));
+ setsid();
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]);
+ perror(" failed");
+ exit(EXIT_SUCCESS);
+ }
+}
+
+void
+tag(Arg *arg)
+{
+ if (selmon->sel && arg->ui & TAGMASK) {
+ selmon->sel->tags = arg->ui & TAGMASK;
+ focus(nil);
+ arrange(selmon);
+ }
+}
+
+void
+tagmon(Arg *arg)
+{
+ if (!selmon->sel || !mons->next)
+ return;
+ sendtomon(selmon->sel, dirtomon(arg->i));
+}
+
+void
+togglebar(Arg *arg)
+{
+ selmon->showbar = !selmon->showbar;
+ updatebarpos(selmon);
+ XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh);
+ arrange(selmon);
+}
+
+void
+togglefloating(Arg *arg)
+{
+ if (!selmon->sel)
+ return;
+ if (selmon->sel->isfullscreen) /* no support for fullscreen windows */
+ return;
+ selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed;
+ if (selmon->sel->isfloating)
+ resize(selmon->sel, selmon->sel->x, selmon->sel->y,
+ selmon->sel->w, selmon->sel->h, 0);
+ arrange(selmon);
+}
+
+void
+togglescratch(Arg *arg)
+{
+ Client *c;
+ uint f = 0;
+
+ for(c = selmon->clients; c && !(f = (c->tags & scratchtag)); c = c->next)
+ ;
+
+ if(f) {
+ f = selmon->tagset[selmon->seltags] ^ scratchtag;
+ if(f) {
+ selmon->tagset[selmon->seltags] = f;
+ focus(nil);
+ arrange(selmon);
+ }
+ if(ISVISIBLE(c)) {
+ focus(c);
+ restack(selmon);
+ }
+ } else
+ spawn(arg);
+
+}
+
+void
+toggletag(Arg *arg)
+{
+ uint newtags;
+ if (!selmon->sel)
+ return;
+
+ newtags = selmon->sel->tags ^ (arg->ui & TAGMASK);
+ if (newtags) {
+ selmon->sel->tags = newtags;
+ focus(nil);
+ arrange(selmon);
+ }
+}
+
+void
+togglefocus(Arg *arg)
+{
+ if (selmon->sel)
+ setfullscreen(selmon->sel, !selmon->sel->isfullscreen);
+
+ togglebar(arg);
+}
+
+void
+toggleview(Arg *arg)
+{
+ uint newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK);
+
+ if (newtagset) {
+ selmon->tagset[selmon->seltags] = newtagset;
+ focus(nil);
+ arrange(selmon);
+ }
+}
+
+void
+view(Arg *arg)
+{
+ if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags])
+ return;
+ selmon->seltags ^= 1; /* toggle sel tagset */
+ if (arg->ui & TAGMASK)
+ selmon->tagset[selmon->seltags] = arg->ui & TAGMASK;
+ focus(nil);
+ arrange(selmon);
+}
+
+void
+zoom(Arg *arg)
+{
+ Client *c = selmon->sel;
+
+ if (!selmon->lt[selmon->sellt]->arrange
+ || (selmon->sel && selmon->sel->isfloating))
+ return;
+ if (c == nexttiled(selmon->clients))
+ if (!c || !(c = nexttiled(c->next)))
+ return;
+ pop(c);
+}
diff --git a/src/cmd/dwm/rules.mk b/src/cmd/dwm/rules.mk
new file mode 100644
index 0000000..a840217
--- /dev/null
+++ b/src/cmd/dwm/rules.mk
@@ -0,0 +1,29 @@
+include share/push.mk
+
+# local sources
+SRCS_$(d):=\
+ $(d)/drw.c\
+ $(d)/hook.c\
+ $(d)/client.c\
+ $(d)/util.c\
+ $(d)/dwm.c
+
+# outputs
+BINS_$(d) := $(d)/dwm
+
+include share/paths.mk
+
+# Local rules
+include share/dynamic.mk
+$(BINS_$(d)): TCFLAGS=\
+ `$(PKG) --cflags fontconfig`\
+ `$(PKG) --cflags freetype2`
+$(BINS_$(d)): TCLIBS=\
+ `$(PKG) --libs fontconfig`\
+ `$(PKG) --libs freetype2`\
+ -lX11 -lXinerama -lXft -lX11-xcb -lxcb -lxcb-res
+
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libutf/libutf.a $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/dwm/util.c b/src/cmd/dwm/util.c
new file mode 100644
index 0000000..0db71cc
--- /dev/null
+++ b/src/cmd/dwm/util.c
@@ -0,0 +1,66 @@
+/* See LICENSE file for copyright and license details. */
+#include "dwm.h"
+
+void
+fatal(char *fmt, ...) {
+ va_list args;
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ if(fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ exit(1);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+
+ if (!(p = calloc(nmemb, size)))
+ fatal("calloc:");
+ return p;
+}
+
+pid_t
+getparentprocess(pid_t p)
+{
+ uint v = 0;
+
+#if defined(__linux__)
+ io·Stream *f;
+ char buf[256];
+ snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p);
+
+ if (!(f = fopen(buf, "r")))
+ return (pid_t)0;
+
+ if (fscanf(f, "%*u %*s %*c %u", (unsigned *)&v) != 1)
+ v = (pid_t)0;
+ fclose(f);
+#elif defined(__FreeBSD__)
+ struct kinfo_proc *proc = kinfo_getproc(p);
+ if (!proc)
+ return (pid_t)0;
+
+ v = proc->ki_ppid;
+ free(proc);
+#endif
+ return (pid_t)v;
+}
+
+int
+isdescendent(pid_t p, pid_t c)
+{
+ while (p != c && c != 0)
+ c = getparentprocess(c);
+
+ return (int)c;
+}
diff --git a/src/cmd/filter/filter.c b/src/cmd/filter/filter.c
new file mode 100644
index 0000000..abc9a88
--- /dev/null
+++ b/src/cmd/filter/filter.c
@@ -0,0 +1,104 @@
+/* See LICENSE file for copyright and license details. */
+#include <u.h>
+#include <base.h>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define FLAG(x) (flag[(x)-'a'])
+
+static void filter(const char *, const char *);
+static void usage(void);
+
+static int match = 0;
+static int flag[26];
+static struct stat old, new;
+
+static
+void
+filter(const char *path, const char *name)
+{
+ struct stat st, ln;
+
+ if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */
+ && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */
+ && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */
+ && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */
+ && (!FLAG('e') || access(path, F_OK) == 0) /* exists */
+ && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */
+ && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */
+ && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */
+ && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */
+ && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */
+ && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */
+ && (!FLAG('r') || access(path, R_OK) == 0) /* readable */
+ && (!FLAG('s') || st.st_size > 0) /* not empty */
+ && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */
+ && (!FLAG('w') || access(path, W_OK) == 0) /* writable */
+ && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */
+ if (FLAG('q'))
+ exit(0);
+ match = 1;
+ puts(name);
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] "
+ "[-n file] [-o file] [file...]\n", argv0);
+ exit(2); /* like test(1) return > 1 on error */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct dirent *d;
+ char path[PATH_MAX], *line = NULL, *file;
+ size_t linesiz = 0;
+ ssize_t n;
+ DIR *dir;
+ int r;
+
+ ARGBEGIN {
+ case 'n': /* newer than file */
+ case 'o': /* older than file */
+ file = EARGF(usage());
+ if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old))))
+ perror(file);
+ break;
+ default:
+ /* miscellaneous operators */
+ if (strchr("abcdefghlpqrsuvwx", ARGC()))
+ FLAG(ARGC()) = 1;
+ else
+ usage(); /* unknown flag */
+ } ARGEND;
+
+ if (!argc) {
+ /* read list from stdin */
+ while ((n = getline(&line, &linesiz, stdin)) > 0) {
+ if (n && line[n - 1] == '\n')
+ line[n - 1] = '\0';
+ filter(line, line);
+ }
+ free(line);
+ } else {
+ for (; argc; argc--, argv++) {
+ if (FLAG('l') && (dir = opendir(*argv))) {
+ /* filter directory contents */
+ while ((d = readdir(dir))) {
+ r = snprintf(path, sizeof path, "%s/%s",
+ *argv, d->d_name);
+ if (r >= 0 && (size_t)r < sizeof path)
+ filter(path, d->d_name);
+ }
+ closedir(dir);
+ } else {
+ filter(*argv, *argv);
+ }
+ }
+ }
+ return match ? 0 : 1;
+}
diff --git a/src/cmd/filter/rules.mk b/src/cmd/filter/rules.mk
new file mode 100644
index 0000000..13ddd56
--- /dev/null
+++ b/src/cmd/filter/rules.mk
@@ -0,0 +1,14 @@
+include share/push.mk
+
+# local sources
+SRCS_$(d):=$(d)/filter.c
+# outputs
+BINS_$(d):=$(d)/filter
+
+include share/paths.mk
+
+# Local rules
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/ic/LICENSE b/src/cmd/ic/LICENSE
new file mode 100644
index 0000000..a5816a8
--- /dev/null
+++ b/src/cmd/ic/LICENSE
@@ -0,0 +1,23 @@
+MIT/X Consortium License
+
+(C)opyright 2014-2018 Hiltjo Posthuma <hiltjo at codemadness dot org>
+(C)opyright 2005-2006 Anselm R. Garbe <garbeam@wmii.de>
+(C)opyright 2005-2011 Nico Golde <nico at ngolde dot de>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/src/cmd/ic/ic.1 b/src/cmd/ic/ic.1
new file mode 100644
index 0000000..3302dad
--- /dev/null
+++ b/src/cmd/ic/ic.1
@@ -0,0 +1,100 @@
+.TH II 1 ic\-VERSION
+.SH NAME
+ic \- irc it or irc improved
+.SH DESCRIPTION
+.B ic
+is a minimalistic FIFO and filesystem based IRC client.
+It creates an irc directory tree with server, channel and
+nick name directories.
+In every directory a FIFO file (in) and normal file (out)
+is placed. This will be for example ~/irc/irc.freenode.net/.
+The in file is used to communicate with the servers and the out
+files includes the server messages. For every channel and every nick
+name there will be new in and out files.
+The basic idea of this is to be able to communicate with an IRC
+server with basic command line tools.
+For example if you will join a channel just do echo "/j #channel" > in
+and ic creates a new channel directory with in and out file.
+.SH SYNOPSIS
+.B ic
+.RB < \-s
+.IR servername >
+.RB [ \-p
+.IR port ]
+.RB [ \-k
+.IR "environment variable" ]
+.RB [ \-i
+.IR prefix ]
+.RB [ \-n
+.IR nickname ]
+.RB [ \-f
+.IR realname ]
+.RB < \-u
+.IR sockname >
+.SH OPTIONS
+.TP
+.BI \-s " servername"
+server to connect to, for example: irc.freenode.net
+.TP
+.BI \-u " sockname"
+connect to a UNIX domain socket instead of directly to a server.
+.TP
+.BI \-p " port"
+lets you override the default port (6667)
+.TP
+.BI \-k " environment variable"
+lets you specify an environment variable that contains your IRC password, e.g. IIPASS="foobar" ic -k IIPASS.
+This is done in order to prevent other users from eavesdropping the server password via the process list.
+.TP
+.BI \-i " prefix"
+lets you override the default irc path (~/irc)
+.TP
+.BI \-n " nickname"
+lets you override the default nick ($USER)
+.TP
+.BI \-f " realname"
+lets you specify your real name associated with your nick
+.SH DIRECTORIES
+.TP
+.B ~/irc
+In this directory the irc tree will be created. In this directory you
+will find a directory for your server (default: irc.freenode.net) in
+which the FIFO and the output file will be stored.
+If you join a channel a new directory with the name of the channel
+will be created in the ~/irc/$servername/ directory.
+.SH COMMANDS
+.TP
+.BI /a " [<message>]"
+mark yourself as away
+.TP
+.BI /j " #channel/nickname [<message>]"
+join a channel or open private conversation with user
+.TP
+.BI /l " [reason]"
+leave a channel or query
+.TP
+.BI /n " nick"
+change the nick name
+.TP
+.BI /q " [reason]"
+quit ic
+.TP
+.BI /t " topic"
+set the topic of a channel
+.SH RAW COMMANDS
+.LP
+Everything which is not a command will be posted into the channel or to the server.
+So if you need /who just write /WHO as described in RFC#1459 to the server in FIFO.
+.SH SSL PROTOCOL SUPPORT
+.LP
+For TLS/SSL protocol support you can connect to a local tunnel, for example with stunnel or socat.
+.SH CONTACT
+.LP
+Subscribe to the mailinglist and write to dev (at) suckless (dot) org for suggestions, fixes, etc.
+.SH AUTHORS
+ic engineers, see LICENSE file
+.SH SEE ALSO
+.BR echo (1),
+.BR tail (1)
+.SH BUGS
+Please report them!
diff --git a/src/cmd/ic/ic.c b/src/cmd/ic/ic.c
new file mode 100644
index 0000000..7fc37d8
--- /dev/null
+++ b/src/cmd/ic/ic.c
@@ -0,0 +1,878 @@
+/* See LICENSE file for license details. */
+#include <u.h>
+#include <base.h>
+
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <time.h>
+#include <signal.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+
+size_t strlcpy(char *, const char *, size_t);
+
+#define IRC_CHANNEL_MAX 200
+#define IRC_MSG_MAX 512 /* guaranteed to be <= than PIPE_BUF */
+#define PING_TIMEOUT 300
+
+enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST };
+
+typedef struct Channel Channel;
+struct Channel {
+ int fdin;
+ char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */
+ char inpath[PATH_MAX]; /* input path */
+ char outpath[PATH_MAX]; /* output path */
+ Channel *next;
+};
+
+static Channel * channel_add(const char *);
+static Channel * channel_find(const char *);
+static Channel * channel_join(const char *);
+static void channel_leave(Channel *);
+static Channel * channel_new(const char *);
+static void channel_normalize_name(char *);
+static void channel_normalize_path(char *);
+static int channel_open(Channel *);
+static void channel_print(Channel *, const char *);
+static int channel_reopen(Channel *);
+static void channel_rm(Channel *);
+
+static void create_dirtree(const char *);
+static void create_filepath(char *, size_t, const char *, const char *, const char *);
+static void ewritestr(int, const char *);
+static void handle_channels_input(int, Channel *);
+static void handle_server_output(int);
+static int isnumeric(const char *);
+static void loginkey(int, const char *);
+static void loginuser(int, const char *, const char *);
+static void proc_channels_input(int, Channel *, char *);
+static void proc_channels_privmsg(int, Channel *, char *);
+static void proc_server_cmd(int, char *);
+static int read_line(int, char *, size_t);
+static void run(int, const char *);
+static void setup(void);
+static void sighandler(int);
+static int tcpopen(const char *, const char *);
+static size_t tokenize(char **, size_t, char *, int);
+static int udsopen(const char *);
+static void usage(void);
+
+static int isrunning = 1;
+static time_t last_response = 0;
+static Channel *channels = nil;
+static Channel *channelmaster = nil;
+static char nick[32], _nick[arrlen(nick)]; /* active nickname at runtime */
+static char ircpath[PATH_MAX]; /* irc dir (-i) */
+static char msg[IRC_MSG_MAX]; /* message buf used for communication */
+
+static
+void
+usage(void)
+{
+ fprintf(stderr, "usage: %s <-s host> [-i <irc dir>] [-p <port>] "
+ "[-u <sockname>] [-n <nick>] [-k <password>] "
+ "[-f <fullname>]\n", argv0);
+ exit(1);
+}
+
+static
+void
+ewritestr(int fd, const char *s)
+{
+ size_t len, off = 0;
+ int w = -1;
+
+ len = strlen(s);
+ for (off = 0; off < len; off += w) {
+ if ((w = write(fd, s + off, len - off)) == -1)
+ break;
+ off += w;
+ }
+ if (w == -1) {
+ fprintf(stderr, "%s: write: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+}
+
+/* creates directories bottom-up, if necessary */
+static
+void
+create_dirtree(const char *dir)
+{
+ char tmp[PATH_MAX], *p;
+ struct stat st;
+ size_t len;
+
+ strlcpy(tmp, dir, sizeof(tmp));
+ len = strlen(tmp);
+ if (len > 0 && tmp[len - 1] == '/')
+ tmp[len - 1] = '\0';
+
+ if ((stat(tmp, &st) != -1) && S_ISDIR(st.st_mode))
+ return; /* dir exists */
+
+ for (p = tmp + 1; *p; p++) {
+ if (*p != '/')
+ continue;
+ *p = '\0';
+ mkdir(tmp, S_IRWXU);
+ *p = '/';
+ }
+ mkdir(tmp, S_IRWXU);
+}
+
+static
+void
+channel_normalize_path(char *s)
+{
+ for (; *s; s++) {
+ if (isalpha((unsigned char)*s))
+ *s = tolower((unsigned char)*s);
+ else if (!isdigit((unsigned char)*s) && !strchr(".#&+!-", *s))
+ *s = '_';
+ }
+}
+
+static
+void
+channel_normalize_name(char *s)
+{
+ char *p;
+
+ while (*s == '&' || *s == '#')
+ s++;
+ for (p = s; *s; s++) {
+ if (!strchr(" ,&#\x07", *s)) {
+ *p = *s;
+ p++;
+ }
+ }
+ *p = '\0';
+}
+
+static
+void
+create_filepath(char *filepath, size_t len, const char *path,
+ const char *channel, const char *suffix)
+{
+ int r;
+
+ if (channel[0]) {
+ r = snprintf(filepath, len, "%s/%s", path, channel);
+ if (r < 0 || (size_t)r >= len)
+ goto error;
+ create_dirtree(filepath);
+ r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix);
+ if (r < 0 || (size_t)r >= len)
+ goto error;
+ } else {
+ r = snprintf(filepath, len, "%s/%s", path, suffix);
+ if (r < 0 || (size_t)r >= len)
+ goto error;
+ }
+ return;
+
+error:
+ fprintf(stderr, "%s: path to irc directory too long\n", argv0);
+ exit(1);
+}
+
+static
+int
+channel_open(Channel *c)
+{
+ int fd;
+ struct stat st;
+
+ /* make "in" fifo if it doesn't exist already. */
+ if (lstat(c->inpath, &st) != -1) {
+ if (!(st.st_mode & S_IFIFO))
+ return -1;
+ } else if (mkfifo(c->inpath, S_IRWXU)) {
+ return -1;
+ }
+ c->fdin = -1;
+ fd = open(c->inpath, O_RDONLY | O_NONBLOCK, 0);
+ if (fd == -1)
+ return -1;
+ c->fdin = fd;
+
+ return 0;
+}
+
+static
+int
+channel_reopen(Channel *c)
+{
+ if (c->fdin > 2) {
+ close(c->fdin);
+ c->fdin = -1;
+ }
+ return channel_open(c);
+}
+
+static
+Channel *
+channel_new(const char *name)
+{
+ Channel *c;
+ char channelpath[PATH_MAX];
+
+ strlcpy(channelpath, name, sizeof(channelpath));
+ channel_normalize_path(channelpath);
+
+ if (!(c = calloc(1, sizeof(Channel)))) {
+ fprintf(stderr, "%s: calloc: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+
+ strlcpy(c->name, name, sizeof(c->name));
+ channel_normalize_name(c->name);
+
+ create_filepath(c->inpath, sizeof(c->inpath), ircpath,
+ channelpath, "in");
+ create_filepath(c->outpath, sizeof(c->outpath), ircpath,
+ channelpath, "out");
+ return c;
+}
+
+static
+Channel *
+channel_find(const char *name)
+{
+ Channel *c;
+ char chan[IRC_CHANNEL_MAX];
+
+ strlcpy(chan, name, sizeof(chan));
+ channel_normalize_name(chan);
+ for (c = channels; c; c = c->next) {
+ if (!strcmp(chan, c->name))
+ return c; /* already handled */
+ }
+ return nil;
+}
+
+static
+Channel *
+channel_add(const char *name)
+{
+ Channel *c;
+
+ c = channel_new(name);
+ if (channel_open(c) == -1) {
+ fprintf(stderr, "%s: cannot create channel: %s: %s\n",
+ argv0, name, strerror(errno));
+ free(c);
+ return nil;
+ }
+ if (!channels) {
+ channels = c;
+ } else {
+ c->next = channels;
+ channels = c;
+ }
+ return c;
+}
+
+static
+Channel *
+channel_join(const char *name)
+{
+ Channel *c;
+
+ if (!(c = channel_find(name)))
+ c = channel_add(name);
+ return c;
+}
+
+static
+void
+channel_rm(Channel *c)
+{
+ Channel *p;
+
+ if (channels == c) {
+ channels = channels->next;
+ } else {
+ for (p = channels; p && p->next != c; p = p->next)
+ ;
+ if (p && p->next == c)
+ p->next = c->next;
+ }
+ free(c);
+}
+
+static
+void
+channel_leave(Channel *c)
+{
+ if (c->fdin > 2) {
+ close(c->fdin);
+ c->fdin = -1;
+ }
+ /* remove "in" file on leaving the channel */
+ unlink(c->inpath);
+ channel_rm(c);
+}
+
+static
+void
+loginkey(int ircfd, const char *key)
+{
+ snprintf(msg, sizeof(msg), "PASS %s\r\n", key);
+ ewritestr(ircfd, msg);
+}
+
+static
+void
+loginuser(int ircfd, const char *host, const char *fullname)
+{
+ snprintf(msg, sizeof(msg), "NICK %s\r\nUSER %s localhost %s :%s\r\n",
+ nick, nick, host, fullname);
+ puts(msg);
+ ewritestr(ircfd, msg);
+}
+
+static
+int
+udsopen(const char *uds)
+{
+ struct sockaddr_un sun;
+ size_t len;
+ int fd;
+
+ if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+
+ sun.sun_family = AF_UNIX;
+ if(strlcpy(sun.sun_path, uds, sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
+ fprintf(stderr, "%s: UNIX domain socket path truncation\n", argv0);
+ exit(1);
+ }
+ len = strlen(sun.sun_path) + 1 + sizeof(sun.sun_family);
+ if (connect(fd, (struct sockaddr *)&sun, len) == -1) {
+ fprintf(stderr, "%s: connect: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+ return fd;
+}
+
+static
+int
+tcpopen(const char *host, const char *service)
+{
+ struct addrinfo hints, *res = nil, *rp;
+ int fd = -1, e;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
+ hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ((e = getaddrinfo(host, service, &hints, &res))) {
+ fprintf(stderr, "%s: getaddrinfo: %s\n", argv0, gai_strerror(e));
+ exit(1);
+ }
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1)
+ continue;
+ if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
+ close(fd);
+ fd = -1;
+ continue;
+ }
+ break; /* success */
+ }
+ if (fd == -1) {
+ fprintf(stderr, "%s: could not connect to %s:%s: %s\n",
+ argv0, host, service, strerror(errno));
+ exit(1);
+ }
+
+ freeaddrinfo(res);
+ return fd;
+}
+
+static
+int
+isnumeric(const char *s)
+{
+ errno = 0;
+ strtol(s, nil, 10);
+ return errno == 0;
+}
+
+static
+size_t
+tokenize(char **result, size_t reslen, char *str, int delim)
+{
+ char *p = nil, *n = nil;
+ size_t i = 0;
+
+ for (n = str; *n == ' '; n++)
+ ;
+ p = n;
+ while (*n != '\0') {
+ if (i >= reslen)
+ return 0;
+ if (i > TOK_CHAN - TOK_CMD && result[0] && isnumeric(result[0]))
+ delim = ':'; /* workaround non-RFC compliant messages */
+ if (*n == delim) {
+ *n = '\0';
+ result[i++] = p;
+ p = ++n;
+ } else {
+ n++;
+ }
+ }
+ /* add last entry */
+ if (i < reslen && p < n && p && *p)
+ result[i++] = p;
+ return i; /* number of tokens */
+}
+
+static
+void
+channel_print(Channel *c, const char *buf)
+{
+ FILE *fp = nil;
+ time_t t = time(nil);
+
+ if (!(fp = fopen(c->outpath, "a")))
+ return;
+ fprintf(fp, "%lu %s\n", (unsigned long)t, buf);
+ fclose(fp);
+}
+
+static
+void
+proc_channels_privmsg(int ircfd, Channel *c, char *buf)
+{
+ snprintf(msg, sizeof(msg), "<%s> %s", nick, buf);
+ channel_print(c, msg);
+ snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", c->name, buf);
+ ewritestr(ircfd, msg);
+}
+
+static
+void
+proc_channels_input(int ircfd, Channel *c, char *buf)
+{
+ char *p = nil;
+ size_t buflen;
+
+ if (buf[0] == '\0')
+ return;
+ if (buf[0] != '/') {
+ proc_channels_privmsg(ircfd, c, buf);
+ return;
+ }
+
+ msg[0] = '\0';
+ if ((buflen = strlen(buf)) < 2)
+ return;
+ if (buf[2] == ' ' || buf[2] == '\0') {
+ switch (buf[1]) {
+ case 'j': /* join */
+ if (buflen < 3)
+ return;
+ if ((p = strchr(&buf[3], ' '))) /* password parameter */
+ *p = '\0';
+ if ((buf[3] == '#') || (buf[3] == '&') || (buf[3] == '+') ||
+ (buf[3] == '!'))
+ {
+ /* password protected channel */
+ if (p)
+ snprintf(msg, sizeof(msg), "JOIN %s %s\r\n", &buf[3], p + 1);
+ else
+ snprintf(msg, sizeof(msg), "JOIN %s\r\n", &buf[3]);
+ channel_join(&buf[3]);
+ } else if (p) {
+ if ((c = channel_join(&buf[3])))
+ proc_channels_privmsg(ircfd, c, p + 1);
+ return;
+ }
+ break;
+ case 't': /* topic */
+ if (buflen >= 3)
+ snprintf(msg, sizeof(msg), "TOPIC %s :%s\r\n", c->name, &buf[3]);
+ break;
+ case 'a': /* away */
+ if (buflen >= 3) {
+ snprintf(msg, sizeof(msg), "-!- %s is away \"%s\"", nick, &buf[3]);
+ channel_print(c, msg);
+ }
+ if (buflen >= 3)
+ snprintf(msg, sizeof(msg), "AWAY :%s\r\n", &buf[3]);
+ else
+ snprintf(msg, sizeof(msg), "AWAY\r\n");
+ break;
+ case 'n': /* change nick */
+ if (buflen >= 3) {
+ strlcpy(_nick, &buf[3], sizeof(_nick));
+ snprintf(msg, sizeof(msg), "NICK %s\r\n", &buf[3]);
+ }
+ break;
+ case 'l': /* leave */
+ if (c == channelmaster)
+ return;
+ if (buflen >= 3)
+ snprintf(msg, sizeof(msg), "PART %s :%s\r\n", c->name, &buf[3]);
+ else
+ snprintf(msg, sizeof(msg),
+ "PART %s :leaving\r\n", c->name);
+ ewritestr(ircfd, msg);
+ channel_leave(c);
+ return;
+ break;
+ case 'q': /* quit */
+ if (buflen >= 3)
+ snprintf(msg, sizeof(msg), "QUIT :%s\r\n", &buf[3]);
+ else
+ snprintf(msg, sizeof(msg),
+ "QUIT %s\r\n", "bye");
+ ewritestr(ircfd, msg);
+ isrunning = 0;
+ return;
+ break;
+ default: /* raw IRC command */
+ snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
+ break;
+ }
+ } else {
+ /* raw IRC command */
+ snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
+ }
+ if (msg[0] != '\0')
+ ewritestr(ircfd, msg);
+}
+
+static
+void
+proc_server_cmd(int fd, char *buf)
+{
+ Channel *c;
+ const char *channel;
+ char *argv[TOK_LAST], *cmd = nil, *p = nil;
+ unsigned int i;
+
+ if (!buf || buf[0] == '\0')
+ return;
+
+ /* clear tokens */
+ for (i = 0; i < TOK_LAST; i++)
+ argv[i] = nil;
+
+ /* check prefix */
+ if (buf[0] == ':') {
+ if (!(p = strchr(buf, ' ')))
+ return;
+ *p = '\0';
+ for (++p; *p == ' '; p++)
+ ;
+ cmd = p;
+ argv[TOK_NICKSRV] = &buf[1];
+ if ((p = strchr(buf, '!'))) {
+ *p = '\0';
+ argv[TOK_USER] = ++p;
+ }
+ } else {
+ cmd = buf;
+ }
+
+ /* remove CRLFs */
+ for (p = cmd; p && *p != '\0'; p++) {
+ if (*p == '\r' || *p == '\n')
+ *p = '\0';
+ }
+
+ if ((p = strchr(cmd, ':'))) {
+ *p = '\0';
+ argv[TOK_TEXT] = ++p;
+ }
+
+ tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' ');
+
+ if (!argv[TOK_CMD] || !strcmp("PONG", argv[TOK_CMD])) {
+ return;
+ } else if (!strcmp("PING", argv[TOK_CMD])) {
+ snprintf(msg, sizeof(msg), "PONG %s\r\n", argv[TOK_TEXT]);
+ ewritestr(fd, msg);
+ return;
+ } else if (!argv[TOK_NICKSRV] || !argv[TOK_USER]) {
+ /* server command */
+ snprintf(msg, sizeof(msg), "%s%s",
+ argv[TOK_ARG] ? argv[TOK_ARG] : "",
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+ channel_print(channelmaster, msg);
+ return; /* don't process further */
+ } else if (!strcmp("ERROR", argv[TOK_CMD]))
+ snprintf(msg, sizeof(msg), "-!- error %s",
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown");
+ else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || argv[TOK_TEXT])) {
+ if (argv[TOK_TEXT])
+ argv[TOK_CHAN] = argv[TOK_TEXT];
+ snprintf(msg, sizeof(msg), "-!- %s(%s) has joined %s",
+ argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
+ } else if (!strcmp("PART", argv[TOK_CMD]) && argv[TOK_CHAN]) {
+ snprintf(msg, sizeof(msg), "-!- %s(%s) has left %s",
+ argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
+ /* if user itself leaves, don't write to channel (don't reopen channel). */
+ if (!strcmp(argv[TOK_NICKSRV], nick))
+ return;
+ } else if (!strcmp("MODE", argv[TOK_CMD])) {
+ snprintf(msg, sizeof(msg), "-!- %s changed mode/%s -> %s %s",
+ argv[TOK_NICKSRV],
+ argv[TOK_CHAN] ? argv[TOK_CHAN] : "",
+ argv[TOK_ARG] ? argv[TOK_ARG] : "",
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+ } else if (!strcmp("QUIT", argv[TOK_CMD])) {
+ snprintf(msg, sizeof(msg), "-!- %s(%s) has quit \"%s\"",
+ argv[TOK_NICKSRV], argv[TOK_USER],
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+ } else if (!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] &&
+ !strcmp(_nick, argv[TOK_TEXT])) {
+ strlcpy(nick, _nick, sizeof(nick));
+ snprintf(msg, sizeof(msg), "-!- changed nick to \"%s\"", nick);
+ channel_print(channelmaster, msg);
+ } else if (!strcmp("NICK", argv[TOK_CMD]) && argv[TOK_TEXT]) {
+ snprintf(msg, sizeof(msg), "-!- %s changed nick to %s",
+ argv[TOK_NICKSRV], argv[TOK_TEXT]);
+ } else if (!strcmp("TOPIC", argv[TOK_CMD])) {
+ snprintf(msg, sizeof(msg), "-!- %s changed topic to \"%s\"",
+ argv[TOK_NICKSRV],
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+ } else if (!strcmp("KICK", argv[TOK_CMD]) && argv[TOK_ARG]) {
+ snprintf(msg, sizeof(msg), "-!- %s kicked %s (\"%s\")",
+ argv[TOK_NICKSRV], argv[TOK_ARG],
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+ } else if (!strcmp("NOTICE", argv[TOK_CMD])) {
+ snprintf(msg, sizeof(msg), "-!- \"%s\"",
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+ } else if (!strcmp("PRIVMSG", argv[TOK_CMD])) {
+ snprintf(msg, sizeof(msg), "<%s> %s", argv[TOK_NICKSRV],
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+ } else {
+ return; /* can't read this message */
+ }
+ if (argv[TOK_CHAN] && !strcmp(argv[TOK_CHAN], nick))
+ channel = argv[TOK_NICKSRV];
+ else
+ channel = argv[TOK_CHAN];
+
+ if (!channel || channel[0] == '\0')
+ c = channelmaster;
+ else
+ c = channel_join(channel);
+ if (c)
+ channel_print(c, msg);
+}
+
+static
+int
+read_line(int fd, char *buf, size_t bufsiz)
+{
+ size_t i = 0;
+ char c = '\0';
+
+ do {
+ if (read(fd, &c, sizeof(char)) != sizeof(char))
+ return -1;
+ buf[i++] = c;
+ } while (c != '\n' && i < bufsiz);
+ buf[i - 1] = '\0'; /* eliminates '\n' */
+ return 0;
+}
+
+static
+void
+handle_channels_input(int ircfd, Channel *c)
+{
+ char buf[IRC_MSG_MAX];
+
+ if(read_line(c->fdin, buf, sizeof(buf)) == -1) {
+ if(channel_reopen(c) == -1)
+ channel_rm(c);
+ return;
+ }
+ proc_channels_input(ircfd, c, buf);
+}
+
+static
+void
+handle_server_output(int ircfd)
+{
+ char buf[IRC_MSG_MAX];
+
+ if (read_line(ircfd, buf, sizeof(buf)) == -1) {
+ fprintf(stderr, "%s: remote host closed connection: %s\n",
+ argv0, strerror(errno));
+ exit(1);
+ }
+ fprintf(stdout, "%lu %s\n", (unsigned long)time(nil), buf);
+ fflush(stdout);
+ proc_server_cmd(ircfd, buf);
+}
+
+static
+void
+sighandler(int sig)
+{
+ if (sig == SIGTERM || sig == SIGINT)
+ isrunning = 0;
+}
+
+static
+void
+setup(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+ sigaction(SIGTERM, &sa, nil);
+ sigaction(SIGINT, &sa, nil);
+}
+
+static
+void
+run(int ircfd, const char *host)
+{
+ Channel *c, *tmp;
+ fd_set rdset;
+ struct timeval tv;
+ char ping_msg[IRC_MSG_MAX];
+ int r, maxfd;
+
+ snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host);
+ while(isrunning) {
+ maxfd = ircfd;
+ FD_ZERO(&rdset);
+ FD_SET(ircfd, &rdset);
+ for (c = channels; c; c = c->next) {
+ if (c->fdin > maxfd)
+ maxfd = c->fdin;
+ FD_SET(c->fdin, &rdset);
+ }
+ memset(&tv, 0, sizeof(tv));
+ tv.tv_sec = 120;
+ r = select(maxfd + 1, &rdset, 0, 0, &tv);
+ if(r < 0){
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s: select: %s\n", argv0, strerror(errno));
+ exit(1);
+ }else if(r == 0){
+ if (time(nil) - last_response >= PING_TIMEOUT) {
+ channel_print(channelmaster, "-!- ii shutting down: ping timeout");
+ exit(2); /* status code 2 for timeout */
+ }
+ ewritestr(ircfd, ping_msg);
+ continue;
+ }
+ if(FD_ISSET(ircfd, &rdset)) {
+ handle_server_output(ircfd);
+ last_response = time(nil);
+ }
+ for(c = channels; c; c = tmp) {
+ tmp = c->next;
+ if (FD_ISSET(c->fdin, &rdset))
+ handle_channels_input(ircfd, c);
+ }
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ Channel *c, *tmp;
+ struct passwd *spw;
+ const char *key = nil, *fullname = nil, *host = "";
+ const char *uds = nil, *service = "6667";
+ char prefix[PATH_MAX];
+ int ircfd, r;
+
+ /* use nickname and home dir of user by default */
+ if(!(spw = getpwuid(getuid()))) {
+ fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+ strlcpy(nick, spw->pw_name, sizeof(nick));
+ snprintf(prefix, sizeof(prefix), "%s/irc", spw->pw_dir);
+
+ ARGBEGIN {
+ case 'f':
+ fullname = EARGF(usage());
+ break;
+ case 'i':
+ strlcpy(prefix, EARGF(usage()), sizeof(prefix));
+ break;
+ case 'k':
+ key = getenv(EARGF(usage()));
+ break;
+ case 'n':
+ strlcpy(nick, EARGF(usage()), sizeof(nick));
+ break;
+ case 'p':
+ service = EARGF(usage());
+ break;
+ case 's':
+ host = EARGF(usage());
+ break;
+ case 'u':
+ uds = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ } ARGEND
+
+ if(!*host)
+ usage();
+
+ if(uds)
+ ircfd = udsopen(uds);
+ else
+ ircfd = tcpopen(host, service);
+
+#ifdef __OpenBSD__
+ /* OpenBSD pledge(2) support */
+ if (pledge("stdio rpath wpath cpath dpath", nil) == -1) {
+ fprintf(stderr, "%s: pledge: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+#endif
+
+ r = snprintf(ircpath, sizeof(ircpath), "%s/%s", prefix, host);
+ if (r < 0 || (size_t)r >= sizeof(ircpath)) {
+ fprintf(stderr, "%s: path to irc directory too long\n", argv0);
+ exit(1);
+ }
+ create_dirtree(ircpath);
+
+ channelmaster = channel_add(""); /* master channel */
+ if(key)
+ loginkey(ircfd, key);
+ loginuser(ircfd, host, fullname && *fullname ? fullname : nick);
+ setup();
+ run(ircfd, host);
+ if(channelmaster)
+ channel_leave(channelmaster);
+
+ for(c = channels; c; c = tmp) {
+ tmp = c->next;
+ channel_leave(c);
+ }
+
+ return 0;
+}
diff --git a/src/cmd/ic/rules.mk b/src/cmd/ic/rules.mk
new file mode 100644
index 0000000..d001c01
--- /dev/null
+++ b/src/cmd/ic/rules.mk
@@ -0,0 +1,14 @@
+include share/push.mk
+# Iterate through subdirectory tree
+
+# Local sources
+SRCS_$(d):=$(d)/strlcpy.c $(d)/ic.c
+BINS_$(d):=$(d)/ic
+
+include share/paths.mk
+
+# Local rules
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/ic/strlcpy.c b/src/cmd/ic/strlcpy.c
new file mode 100644
index 0000000..5af7906
--- /dev/null
+++ b/src/cmd/ic/strlcpy.c
@@ -0,0 +1,32 @@
+/* Taken from OpenBSD */
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if(n != 0) {
+ while(--n != 0) {
+ if((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if(n == 0) {
+ if(siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while(*s++)
+ ;
+ }
+ return s - src - 1; /* count does not include NUL */
+}
diff --git a/src/cmd/menu/LICENSE b/src/cmd/menu/LICENSE
new file mode 100644
index 0000000..9762166
--- /dev/null
+++ b/src/cmd/menu/LICENSE
@@ -0,0 +1,30 @@
+MIT/X Consortium License
+
+© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
+© 2006-2007 Michał Janeczek <janeczek@gmail.com>
+© 2007 Kris Maglione <jg@suckless.org>
+© 2009 Gottox <gottox@s01.de>
+© 2009 Markus Schnalke <meillo@marmaro.de>
+© 2009 Evan Gates <evan.gates@gmail.com>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2014-2019 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2015-2019 Quentin Rameau <quinq@fifth.space>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/src/cmd/menu/config.h b/src/cmd/menu/config.h
new file mode 100644
index 0000000..9bfd5b3
--- /dev/null
+++ b/src/cmd/menu/config.h
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+/* Default settings; can be overriden by command line. */
+#define VERSION "1.0"
+
+static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
+/* -fn option overrides fonts[0]; default X11 font or font set */
+static const char *fonts[] = {
+ "consolas:size=16"
+};
+
+static const char *prompt = "cmds"; /* -p option; prompt to the left of input field */
+static const char *colors[SchemeLast][2] = {
+ /* fg bg */
+ [SchemeNorm] = { "#fbf1c7", "#504945" },
+ [SchemeSel] = { "#504945", "#83a598" },
+ [SchemeOut] = { "#000000", "#00ffff" },
+};
+/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+static unsigned int lines = 0;
+
+/*
+ * Characters not considered part of a word while deleting words
+ * e.g. " /?\"&[]"
+ */
+static const char worddelimiters[] = " ";
diff --git a/src/cmd/menu/drw.c b/src/cmd/menu/drw.c
new file mode 100644
index 0000000..162fe40
--- /dev/null
+++ b/src/cmd/menu/drw.c
@@ -0,0 +1,428 @@
+/* See LICENSE file for copyright and license details. */
+#include "menu.h"
+
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+
+static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+static long
+utf8decodebyte(const char c, size_t *i)
+{
+ for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
+ if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
+ return (unsigned char)c & ~utfmask[*i];
+ return 0;
+}
+
+static size_t
+utf8validate(long *u, size_t i)
+{
+ if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = RuneErr;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+ return i;
+}
+
+static size_t
+utf8decode(const char *c, long *u, size_t clen)
+{
+ size_t i, j, len, type;
+ long udecoded;
+
+ *u = RuneErr;
+ if (!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+
+ return len;
+}
+
+Drw *
+drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
+{
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
+ drw->gc = XCreateGC(dpy, root, 0, NULL);
+ XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ return drw;
+}
+
+void
+drw_resize(Drw *drw, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ drw->w = w;
+ drw->h = h;
+ if (drw->drawable)
+ XFreePixmap(drw->dpy, drw->drawable);
+ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
+}
+
+void
+drw_free(Drw *drw)
+{
+ XFreePixmap(drw->dpy, drw->drawable);
+ XFreeGC(drw->dpy, drw->gc);
+ free(drw);
+}
+
+/* This function is an implementation detail. Library users should use
+ * drw_fontset_create instead.
+ */
+static Fnt *
+xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
+{
+ Fnt *font;
+ XftFont *xfont = NULL;
+ FcPattern *pattern = NULL;
+
+ if (fontname) {
+ /* Using the pattern found at font->xfont->pattern does not yield the
+ * same substitution results as using the pattern returned by
+ * FcNameParse; using the latter results in the desired fallback
+ * behaviour whereas the former just results in missing-character
+ * rectangles being drawn, at least with some fonts. */
+ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
+ fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
+ return NULL;
+ }
+ if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
+ fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+ } else if (fontpattern) {
+ if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
+ fprintf(stderr, "error, cannot load font from pattern.\n");
+ return NULL;
+ }
+ } else {
+ fatal("no font specified.");
+ }
+
+ /* Do not allow using color fonts. This is a workaround for a BadLength
+ * error from Xft with color glyphs. Modelled on the Xterm workaround. See
+ * https://bugzilla.redhat.com/show_bug.cgi?id=1498269
+ * https://lists.suckless.org/dev/1701/30932.html
+ * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
+ * and lots more all over the internet.
+ */
+ FcBool iscol;
+ if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+
+ font = ecalloc(1, sizeof(Fnt));
+ font->xfont = xfont;
+ font->pattern = pattern;
+ font->h = xfont->ascent + xfont->descent;
+ font->dpy = drw->dpy;
+
+ return font;
+}
+
+static void
+xfont_free(Fnt *font)
+{
+ if (!font)
+ return;
+ if (font->pattern)
+ FcPatternDestroy(font->pattern);
+ XftFontClose(font->dpy, font->xfont);
+ free(font);
+}
+
+Fnt*
+drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
+{
+ Fnt *cur, *ret = NULL;
+ size_t i;
+
+ if (!drw || !fonts)
+ return NULL;
+
+ for (i = 1; i <= fontcount; i++) {
+ if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
+ cur->next = ret;
+ ret = cur;
+ }
+ }
+ return (drw->fonts = ret);
+}
+
+void
+drw_fontset_free(Fnt *font)
+{
+ if (font) {
+ drw_fontset_free(font->next);
+ xfont_free(font);
+ }
+}
+
+void
+drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
+{
+ if (!drw || !dest || !clrname)
+ return;
+
+ if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen),
+ clrname, dest))
+ fatal("error, cannot allocate color '%s'", clrname);
+}
+
+/* Wrapper to create color schemes. The caller has to call free(3) on the
+ * returned color scheme when done using it. */
+Clr *
+drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
+{
+ size_t i;
+ Clr *ret;
+
+ /* need at least two colors for a scheme */
+ if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
+ return NULL;
+
+ for (i = 0; i < clrcount; i++)
+ drw_clr_create(drw, &ret[i], clrnames[i]);
+ return ret;
+}
+
+void
+drw_setfontset(Drw *drw, Fnt *set)
+{
+ if (drw)
+ drw->fonts = set;
+}
+
+void
+drw_setscheme(Drw *drw, Clr *scm)
+{
+ if (drw)
+ drw->scheme = scm;
+}
+
+void
+drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
+{
+ if (!drw || !drw->scheme)
+ return;
+ XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
+ if (filled)
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ else
+ XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
+}
+
+int
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
+{
+ char buf[1024];
+ int ty;
+ unsigned int ew;
+ XftDraw *d = NULL;
+ Fnt *usedfont, *curfont, *nextfont;
+ size_t i, len;
+ int utf8strlen, utf8charlen, render = x || y || w || h;
+ long utf8codepoint = 0;
+ const char *utf8str;
+ FcCharSet *fccharset;
+ FcPattern *fcpattern;
+ FcPattern *match;
+ XftResult result;
+ int charexists = 0;
+
+ if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
+ return 0;
+
+ if (!render) {
+ w = ~w;
+ } else {
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ d = XftDrawCreate(drw->dpy, drw->drawable,
+ DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen));
+ x += lpad;
+ w -= lpad;
+ }
+
+ usedfont = drw->fonts;
+ while (1) {
+ utf8strlen = 0;
+ utf8str = text;
+ nextfont = NULL;
+ while (*text) {
+ utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
+ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
+ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
+ if (charexists) {
+ if (curfont == usedfont) {
+ utf8strlen += utf8charlen;
+ text += utf8charlen;
+ } else {
+ nextfont = curfont;
+ }
+ break;
+ }
+ }
+
+ if (!charexists || nextfont)
+ break;
+ else
+ charexists = 0;
+ }
+
+ if (utf8strlen) {
+ drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
+ /* shorten text if necessary */
+ for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--)
+ drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
+
+ if (len) {
+ memcpy(buf, utf8str, len);
+ buf[len] = '\0';
+ if (len < utf8strlen)
+ for (i = len; i && i > len - 3; buf[--i] = '.')
+ ; /* NOP */
+
+ if (render) {
+ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
+ XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
+ usedfont->xfont, x, ty, (XftChar8 *)buf, len);
+ }
+ x += ew;
+ w -= ew;
+ }
+ }
+
+ if (!*text) {
+ break;
+ } else if (nextfont) {
+ charexists = 0;
+ usedfont = nextfont;
+ } else {
+ /* Regardless of whether or not a fallback font is found, the
+ * character must be drawn. */
+ charexists = 1;
+
+ fccharset = FcCharSetCreate();
+ FcCharSetAddChar(fccharset, utf8codepoint);
+
+ if (!drw->fonts->pattern) {
+ /* Refer to the comment in xfont_create for more information. */
+ fatal("the first font in the cache must be loaded from a font string.");
+ }
+
+ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
+ FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
+
+ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+
+ FcCharSetDestroy(fccharset);
+ FcPatternDestroy(fcpattern);
+
+ if (match) {
+ usedfont = xfont_create(drw, NULL, match);
+ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
+ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
+ ; /* NOP */
+ curfont->next = usedfont;
+ } else {
+ xfont_free(usedfont);
+ usedfont = drw->fonts;
+ }
+ }
+ }
+ }
+ if (d)
+ XftDrawDestroy(d);
+
+ return x + (render ? w : 0);
+}
+
+void
+drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
+ XSync(drw->dpy, False);
+}
+
+unsigned int
+drw_fontset_getwidth(Drw *drw, const char *text)
+{
+ if (!drw || !drw->fonts || !text)
+ return 0;
+ return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
+}
+
+void
+drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
+{
+ XGlyphInfo ext;
+
+ if (!font || !text)
+ return;
+
+ XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
+ if (w)
+ *w = ext.xOff;
+ if (h)
+ *h = font->h;
+}
+
+Cur *
+drw_cur_create(Drw *drw, int shape)
+{
+ Cur *cur;
+
+ if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
+ return NULL;
+
+ cur->cursor = XCreateFontCursor(drw->dpy, shape);
+
+ return cur;
+}
+
+void
+drw_cur_free(Drw *drw, Cur *cursor)
+{
+ if (!cursor)
+ return;
+
+ XFreeCursor(drw->dpy, cursor->cursor);
+ free(cursor);
+}
diff --git a/src/cmd/menu/drw.h b/src/cmd/menu/drw.h
new file mode 100644
index 0000000..4c67419
--- /dev/null
+++ b/src/cmd/menu/drw.h
@@ -0,0 +1,57 @@
+/* See LICENSE file for copyright and license details. */
+
+typedef struct {
+ Cursor cursor;
+} Cur;
+
+typedef struct Fnt {
+ Display *dpy;
+ unsigned int h;
+ XftFont *xfont;
+ FcPattern *pattern;
+ struct Fnt *next;
+} Fnt;
+
+enum { ColFg, ColBg }; /* Clr scheme index */
+typedef XftColor Clr;
+
+typedef struct {
+ unsigned int w, h;
+ Display *dpy;
+ int screen;
+ Window root;
+ Drawable drawable;
+ GC gc;
+ Clr *scheme;
+ Fnt *fonts;
+} Drw;
+
+/* Drawable abstraction */
+Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
+void drw_resize(Drw *drw, unsigned int w, unsigned int h);
+void drw_free(Drw *drw);
+
+/* Fnt abstraction */
+Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount);
+void drw_fontset_free(Fnt* set);
+unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
+void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
+
+/* Colorscheme abstraction */
+void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
+Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
+
+/* Cursor abstraction */
+Cur *drw_cur_create(Drw *drw, int shape);
+void drw_cur_free(Drw *drw, Cur *cursor);
+
+/* Drawing context manipulation */
+void drw_setfontset(Drw *drw, Fnt *set);
+void drw_setscheme(Drw *drw, Clr *scm);
+
+/* Drawing functions */
+void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
+int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
+
+/* Map functions */
+void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
diff --git a/src/cmd/menu/menu.c b/src/cmd/menu/menu.c
new file mode 100644
index 0000000..e6e4bb2
--- /dev/null
+++ b/src/cmd/menu/menu.c
@@ -0,0 +1,765 @@
+#include "menu.h"
+
+static char text[BUFSIZ] = "";
+static char *embed;
+static int bh, mw, mh;
+static int inputw = 0, promptw, passwd = 0;
+static int lrpad; /* sum of left and right padding */
+static size_t cursor;
+static struct item *items = nil;
+static struct item *matches, *matchend;
+static struct item *prev, *curr, *next, *sel;
+static int mon = -1, screen;
+
+static Atom clip, utf8;
+static Display *dpy;
+static Window root, parentwin, win;
+static XIC xic;
+
+static Drw *drw;
+static Clr *scheme[SchemeLast];
+
+#include "config.h"
+
+static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+static char *(*fstrstr)(const char *, const char *) = strstr;
+
+static
+void
+appenditem(struct item *item, struct item **list, struct item **last)
+{
+ if (*last)
+ (*last)->right = item;
+ else
+ *list = item;
+
+ item->left = *last;
+ item->right = nil;
+ *last = item;
+}
+
+static
+void
+calcoffsets(void)
+{
+ int i, n;
+
+ if (lines > 0)
+ n = lines * bh;
+ else
+ n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
+ /* calculate which items will begin the next page and previous page */
+ for (i = 0, next = curr; next; next = next->right)
+ if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
+ break;
+ for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
+ if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n)
+ break;
+}
+
+static
+void
+cleanup(void)
+{
+ size_t i;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ for (i = 0; i < SchemeLast; i++)
+ free(scheme[i]);
+ drw_free(drw);
+ XSync(dpy, False);
+ XCloseDisplay(dpy);
+}
+
+static
+char *
+cistrstr(const char *s, const char *sub)
+{
+ size_t len;
+
+ for (len = strlen(sub); *s; s++)
+ if (!strncasecmp(s, sub, len))
+ return (char *)s;
+ return nil;
+}
+
+static
+int
+drawitem(struct item *item, int x, int y, int w)
+{
+ if (item == sel)
+ drw_setscheme(drw, scheme[SchemeSel]);
+ else if (item->out)
+ drw_setscheme(drw, scheme[SchemeOut]);
+ else
+ drw_setscheme(drw, scheme[SchemeNorm]);
+
+ return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
+}
+
+static
+void
+drawmenu(void)
+{
+ uint curpos;
+ struct item *item;
+ int x = 0, y = 0, w;
+ char *censort;
+
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, 0, 0, mw, mh, 1, 1);
+
+ if (prompt && *prompt) {
+ drw_setscheme(drw, scheme[SchemeSel]);
+ x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
+ }
+ /* draw input field */
+ w = (lines > 0 || !matches) ? mw - x : inputw;
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
+ if(passwd){
+ censort = ecalloc(1, sizeof(text));
+ memset(censort, '.', strlen(text));
+ drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0);
+ free(censort);
+ }
+
+ curpos = TEXTW(text) - TEXTW(&text[cursor]);
+ if ((curpos += lrpad / 2 - 1) < w) {
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
+ }
+
+ if (lines > 0) {
+ /* draw vertical list */
+ for (item = curr; item != next; item = item->right)
+ drawitem(item, x, y += bh, mw - x);
+ } else if (matches) {
+ /* draw horizontal list */
+ x += inputw;
+ w = TEXTW("<");
+ if (curr->left) {
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0);
+ }
+ x += w;
+ for (item = curr; item != next; item = item->right)
+ x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">")));
+ if (next) {
+ w = TEXTW(">");
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
+ }
+ }
+ drw_map(drw, win, 0, 0, mw, mh);
+}
+
+static
+void
+grabfocus(void)
+{
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
+ Window focuswin;
+ int i, revertwin;
+
+ for (i = 0; i < 100; ++i) {
+ XGetInputFocus(dpy, &focuswin, &revertwin);
+ if (focuswin == win)
+ return;
+ XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
+ nanosleep(&ts, nil);
+ }
+ fatal("cannot grab focus");
+}
+
+static
+void
+grabkeyboard(void)
+{
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
+ int i;
+
+ if (embed)
+ return;
+ /* try to grab keyboard, we may have to wait for another process to ungrab */
+ for (i = 0; i < 1000; i++) {
+ if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
+ GrabModeAsync, CurrentTime) == GrabSuccess)
+ return;
+ nanosleep(&ts, nil);
+ }
+ fatal("cannot grab keyboard");
+}
+
+static
+void
+match(void)
+{
+ static char **tokv = nil;
+ static int tokn = 0;
+
+ char buf[sizeof text], *s;
+ int i, tokc = 0;
+ size_t len, textsize;
+ struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
+
+ strcpy(buf, text);
+ /* separate input text into tokens to be matched individually */
+ for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(nil, " "))
+ if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
+ fatal("cannot realloc %u bytes:", tokn * sizeof *tokv);
+ len = tokc ? strlen(tokv[0]) : 0;
+
+ matches = lprefix = lsubstr = matchend = prefixend = substrend = nil;
+ textsize = strlen(text) + 1;
+ for (item = items; item && item->text; item++) {
+ for (i = 0; i < tokc; i++)
+ if (!fstrstr(item->text, tokv[i]))
+ break;
+ if (i != tokc) /* not all tokens match */
+ continue;
+ /* exact matches go first, then prefixes, then substrings */
+ if (!tokc || !fstrncmp(text, item->text, textsize))
+ appenditem(item, &matches, &matchend);
+ else if (!fstrncmp(tokv[0], item->text, len))
+ appenditem(item, &lprefix, &prefixend);
+ else
+ appenditem(item, &lsubstr, &substrend);
+ }
+ if (lprefix) {
+ if (matches) {
+ matchend->right = lprefix;
+ lprefix->left = matchend;
+ } else
+ matches = lprefix;
+ matchend = prefixend;
+ }
+ if (lsubstr) {
+ if (matches) {
+ matchend->right = lsubstr;
+ lsubstr->left = matchend;
+ } else
+ matches = lsubstr;
+ matchend = substrend;
+ }
+ curr = sel = matches;
+ calcoffsets();
+}
+
+static
+void
+insert(const char *str, ssize_t n)
+{
+ if (strlen(text) + n > sizeof text - 1)
+ return;
+ /* move existing text out of the way, insert new text, and update cursor */
+ memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
+ if (n > 0)
+ memcpy(&text[cursor], str, n);
+ cursor += n;
+ match();
+}
+
+static
+size_t
+nextrune(int inc)
+{
+ ssize_t n;
+
+ /* return location of next utf8 rune in the given direction (+1 or -1) */
+ for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
+ ;
+ return n;
+}
+
+static
+void
+movewordedge(int dir)
+{
+ if (dir < 0) { /* move cursor to the start of the word*/
+ while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+ cursor = nextrune(-1);
+ while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+ cursor = nextrune(-1);
+ } else { /* move cursor to the end of the word */
+ while (text[cursor] && strchr(worddelimiters, text[cursor]))
+ cursor = nextrune(+1);
+ while (text[cursor] && !strchr(worddelimiters, text[cursor]))
+ cursor = nextrune(+1);
+ }
+}
+
+static
+void
+keypress(XKeyEvent *ev)
+{
+ char buf[32];
+ int len;
+ KeySym ksym;
+ Status status;
+
+ len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
+ switch (status) {
+ default: /* XLookupNone, XBufferOverflow */
+ return;
+ case XLookupChars:
+ goto insert;
+ case XLookupKeySym:
+ case XLookupBoth:
+ break;
+ }
+
+ if (ev->state & ControlMask) {
+ switch(ksym) {
+ case XK_a: ksym = XK_Home; break;
+ case XK_b: ksym = XK_Left; break;
+ case XK_c: ksym = XK_Escape; break;
+ case XK_d: ksym = XK_Delete; break;
+ case XK_e: ksym = XK_End; break;
+ case XK_f: ksym = XK_Right; break;
+ case XK_g: ksym = XK_Escape; break;
+ case XK_h: ksym = XK_BackSpace; break;
+ case XK_i: ksym = XK_Tab; break;
+ case XK_j: /* fallthrough */
+ case XK_J: /* fallthrough */
+ case XK_m: /* fallthrough */
+ case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
+ case XK_n: ksym = XK_Down; break;
+ case XK_p: ksym = XK_Up; break;
+
+ case XK_k: /* delete right */
+ text[cursor] = '\0';
+ match();
+ break;
+ case XK_u: /* delete left */
+ insert(nil, 0 - cursor);
+ break;
+ case XK_w: /* delete word */
+ while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+ insert(nil, nextrune(-1) - cursor);
+ while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+ insert(nil, nextrune(-1) - cursor);
+ break;
+ case XK_y: /* paste selection */
+ case XK_Y:
+ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+ utf8, utf8, win, CurrentTime);
+ return;
+ case XK_Left:
+ movewordedge(-1);
+ goto draw;
+ case XK_Right:
+ movewordedge(+1);
+ goto draw;
+ case XK_Return:
+ case XK_KP_Enter:
+ break;
+ case XK_bracketleft:
+ cleanup();
+ exit(1);
+ default:
+ return;
+ }
+ } else if (ev->state & Mod1Mask) {
+ switch(ksym) {
+ case XK_b:
+ movewordedge(-1);
+ goto draw;
+ case XK_f:
+ movewordedge(+1);
+ goto draw;
+ case XK_g: ksym = XK_Home; break;
+ case XK_G: ksym = XK_End; break;
+ case XK_h: ksym = XK_Up; break;
+ case XK_j: ksym = XK_Next; break;
+ case XK_k: ksym = XK_Prior; break;
+ case XK_l: ksym = XK_Down; break;
+ default:
+ return;
+ }
+ }
+
+ switch(ksym) {
+ default:
+insert:
+ if (!iscntrl(*buf))
+ insert(buf, len);
+ break;
+ case XK_Delete:
+ if (text[cursor] == '\0')
+ return;
+ cursor = nextrune(+1);
+ /* fallthrough */
+ case XK_BackSpace:
+ if (cursor == 0)
+ return;
+ insert(nil, nextrune(-1) - cursor);
+ break;
+ case XK_End:
+ if (text[cursor] != '\0') {
+ cursor = strlen(text);
+ break;
+ }
+ if (next) {
+ /* jump to end of list and position items in reverse */
+ curr = matchend;
+ calcoffsets();
+ curr = prev;
+ calcoffsets();
+ while (next && (curr = curr->right))
+ calcoffsets();
+ }
+ sel = matchend;
+ break;
+ case XK_Escape:
+ cleanup();
+ exit(1);
+ case XK_Home:
+ if (sel == matches) {
+ cursor = 0;
+ break;
+ }
+ sel = curr = matches;
+ calcoffsets();
+ break;
+ case XK_Left:
+ if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
+ cursor = nextrune(-1);
+ break;
+ }
+ if (lines > 0)
+ return;
+ /* fallthrough */
+ case XK_Up:
+ if (sel && sel->left && (sel = sel->left)->right == curr) {
+ curr = prev;
+ calcoffsets();
+ }
+ break;
+ case XK_Next:
+ if (!next)
+ return;
+ sel = curr = next;
+ calcoffsets();
+ break;
+ case XK_Prior:
+ if (!prev)
+ return;
+ sel = curr = prev;
+ calcoffsets();
+ break;
+ case XK_Return:
+ case XK_KP_Enter:
+ puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+ if (!(ev->state & ControlMask)) {
+ cleanup();
+ exit(0);
+ }
+ if (sel)
+ sel->out = 1;
+ break;
+ case XK_Right:
+ if (text[cursor] != '\0') {
+ cursor = nextrune(+1);
+ break;
+ }
+ if (lines > 0)
+ return;
+ /* fallthrough */
+ case XK_Down:
+ if (sel && sel->right && (sel = sel->right) == next) {
+ curr = next;
+ calcoffsets();
+ }
+ break;
+ case XK_Tab:
+ if (!sel)
+ return;
+ strncpy(text, sel->text, sizeof text - 1);
+ text[sizeof text - 1] = '\0';
+ cursor = strlen(text);
+ match();
+ break;
+ }
+
+draw:
+ drawmenu();
+}
+
+static
+void
+paste(void)
+{
+ char *p, *q;
+ int di;
+ unsigned long dl;
+ Atom da;
+
+ /* we have been given the current selection, now insert it into input */
+ if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
+ utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
+ == Success && p) {
+ insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
+ XFree(p);
+ }
+ drawmenu();
+}
+
+static
+void
+readstdin(void)
+{
+ char buf[sizeof text], *p;
+ size_t i, imax = 0, size = 0;
+ uint tmpmax = 0;
+ if(passwd){
+ inputw = lines = 0;
+ return;
+ }
+
+ /* read each line from stdin and add it to the item list */
+ for (i = 0; fgets(buf, sizeof buf, stdin); i++) {
+ if (i + 1 >= size / sizeof *items)
+ if (!(items = realloc(items, (size += BUFSIZ))))
+ fatal("cannot realloc %u bytes:", size);
+ if ((p = strchr(buf, '\n')))
+ *p = '\0';
+ if (!(items[i].text = strdup(buf)))
+ fatal("cannot strdup %u bytes:", strlen(buf) + 1);
+ items[i].out = 0;
+ drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, nil);
+ if (tmpmax > inputw) {
+ inputw = tmpmax;
+ imax = i;
+ }
+ }
+ if (items)
+ items[i].text = nil;
+ inputw = items ? TEXTW(items[imax].text) : 0;
+ lines = MIN(lines, i);
+}
+
+static
+void
+run(void)
+{
+ XEvent ev;
+
+ while (!XNextEvent(dpy, &ev)) {
+ if (XFilterEvent(&ev, win))
+ continue;
+ switch(ev.type) {
+ case DestroyNotify:
+ if (ev.xdestroywindow.window != win)
+ break;
+ cleanup();
+ exit(1);
+ case Expose:
+ if (ev.xexpose.count == 0)
+ drw_map(drw, win, 0, 0, mw, mh);
+ break;
+ case FocusIn:
+ /* regrab focus from parent window */
+ if (ev.xfocus.window != win)
+ grabfocus();
+ break;
+ case KeyPress:
+ keypress(&ev.xkey);
+ break;
+ case SelectionNotify:
+ if (ev.xselection.property == utf8)
+ paste();
+ break;
+ case VisibilityNotify:
+ if (ev.xvisibility.state != VisibilityUnobscured)
+ XRaiseWindow(dpy, win);
+ break;
+ }
+ }
+}
+
+static
+void
+setup(void)
+{
+ int x, y, i, j;
+ uint du;
+ XSetWindowAttributes swa;
+ XIM xim;
+ Window w, dw, *dws;
+ XWindowAttributes wa;
+ XClassHint ch = {"menu", "menu"};
+ XineramaScreenInfo *info;
+ Window pw;
+ int a, di, n, area = 0;
+
+ /* init appearance */
+ for (j = 0; j < SchemeLast; j++)
+ scheme[j] = drw_scm_create(drw, colors[j], 2);
+
+ clip = XInternAtom(dpy, "CLIPBOARD", False);
+ utf8 = XInternAtom(dpy, "UTF8_STRING", False);
+
+ /* calculate menu geometry */
+ bh = drw->fonts->h + 2;
+ lines = MAX(lines, 0);
+ mh = (lines + 1) * bh;
+ i = 0;
+ if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
+ XGetInputFocus(dpy, &w, &di);
+ if (mon >= 0 && mon < n)
+ i = mon;
+ else if (w != root && w != PointerRoot && w != None) {
+ /* find top-level window containing current input focus */
+ do {
+ if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
+ XFree(dws);
+ } while (w != root && w != pw);
+ /* find xinerama screen with which the window intersects most */
+ if (XGetWindowAttributes(dpy, pw, &wa))
+ for (j = 0; j < n; j++)
+ if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
+ area = a;
+ i = j;
+ }
+ }
+ /* no focused window is on screen, so use pointer location instead */
+ if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
+ for (i = 0; i < n; i++)
+ if (INTERSECT(x, y, 1, 1, info[i]))
+ break;
+
+ x = info[i].x_org;
+ y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
+ mw = info[i].width;
+ XFree(info);
+ } else
+ {
+ if (!XGetWindowAttributes(dpy, parentwin, &wa))
+ fatal("could not get embedding window attributes: 0x%lx",
+ parentwin);
+ x = 0;
+ y = topbar ? 0 : wa.height - mh;
+ mw = wa.width;
+ }
+ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
+ inputw = MIN(inputw, mw/3);
+ match();
+
+ /* create menu window */
+ swa.override_redirect = True;
+ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
+ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
+ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
+ CopyFromParent, CopyFromParent, CopyFromParent,
+ CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
+ XSetClassHint(dpy, win, &ch);
+
+
+ /* input methods */
+ if ((xim = XOpenIM(dpy, nil, nil, nil)) == nil)
+ fatal("XOpenIM failed: could not open input device");
+
+ xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, win, XNFocusWindow, win, nil);
+
+ XMapRaised(dpy, win);
+ if (embed) {
+ XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
+ if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
+ for (i = 0; i < du && dws[i] != win; ++i)
+ XSelectInput(dpy, dws[i], FocusChangeMask);
+ XFree(dws);
+ }
+ grabfocus();
+ }
+ drw_resize(drw, mw, mh);
+ drawmenu();
+}
+
+static
+void
+usage(void)
+{
+ fputs("usage: menu [-bfivP] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
+ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ XWindowAttributes wa;
+ int i, fast = 0;
+
+ for (i = 1; i < argc; i++)
+ /* these options take no arguments */
+ if (!strcmp(argv[i], "-v")) { /* prints version information */
+ puts("menu-"VERSION);
+ exit(0);
+ } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
+ topbar = 0;
+ else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
+ fast = 1;
+ else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
+ fstrncmp = strncasecmp;
+ fstrstr = cistrstr;
+ } else if (!strcmp(argv[i], "-P")) {
+ passwd = 1;
+ } else if (i + 1 == argc)
+ usage();
+ /* these options take one argument */
+ else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
+ lines = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-m"))
+ mon = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */
+ prompt = argv[++i];
+ else if (!strcmp(argv[i], "-fn")) /* font or font set */
+ fonts[0] = argv[++i];
+ else if (!strcmp(argv[i], "-nb")) /* normal background color */
+ colors[SchemeNorm][ColBg] = argv[++i];
+ else if (!strcmp(argv[i], "-nf")) /* normal foreground color */
+ colors[SchemeNorm][ColFg] = argv[++i];
+ else if (!strcmp(argv[i], "-sb")) /* selected background color */
+ colors[SchemeSel][ColBg] = argv[++i];
+ else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
+ colors[SchemeSel][ColFg] = argv[++i];
+ else if (!strcmp(argv[i], "-w")) /* embedding window id */
+ embed = argv[++i];
+ else
+ usage();
+
+ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+ fputs("warning: no locale support\n", stderr);
+ if (!(dpy = XOpenDisplay(nil)))
+ fatal("cannot open display");
+ screen = DefaultScreen(dpy);
+ root = RootWindow(dpy, screen);
+ if (!embed || !(parentwin = strtol(embed, nil, 0)))
+ parentwin = root;
+ if (!XGetWindowAttributes(dpy, parentwin, &wa))
+ fatal("could not get embedding window attributes: 0x%lx",
+ parentwin);
+ drw = drw_create(dpy, screen, root, wa.width, wa.height);
+ if (!drw_fontset_create(drw, fonts, arrlen(fonts)))
+ fatal("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath", nil) == -1)
+ fatal("pledge");
+#endif
+
+ if (fast && !isatty(0)) {
+ grabkeyboard();
+ readstdin();
+ } else {
+ readstdin();
+ grabkeyboard();
+ }
+ setup();
+ run();
+
+ return 1; /* unreachable */
+}
diff --git a/src/cmd/menu/menu.h b/src/cmd/menu/menu.h
new file mode 100644
index 0000000..f4345bb
--- /dev/null
+++ b/src/cmd/menu/menu.h
@@ -0,0 +1,40 @@
+/* See LICENSE file for copyright and license details. */
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+
+#include <time.h>
+#include <locale.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xinerama.h>
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+
+/* macros */
+#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \
+ * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
+#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+
+
+/* enums */
+enum {
+ SchemeNorm,
+ SchemeSel,
+ SchemeOut,
+ SchemeLast
+}; /* color schemes */
+
+struct item {
+ char *text;
+ struct item *left, *right;
+ int out;
+};
+
+/* util.c */
+void fatal(const char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);
diff --git a/src/cmd/menu/rules.mk b/src/cmd/menu/rules.mk
new file mode 100644
index 0000000..f9b59aa
--- /dev/null
+++ b/src/cmd/menu/rules.mk
@@ -0,0 +1,27 @@
+include share/push.mk
+# Iterate through subdirectory tree
+
+# Local sources
+SRCS_$(d):=\
+ $(d)/menu.c\
+ $(d)/drw.c\
+ $(d)/util.c
+
+#outputs
+BINS_$(d):=$(d)/menu
+
+include share/paths.mk
+
+# Local rules
+include share/dynamic.mk
+
+$(BINS_$(d)): TCLIBS=\
+ -lfontconfig -lXft -lXinerama -lX11
+$(BINS_$(d)): TCINCS=\
+ `$(PKG) --cflags fontconfig`\
+ `$(PKG) --cflags freetype2`
+
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/menu/util.c b/src/cmd/menu/util.c
new file mode 100644
index 0000000..14bfe1c
--- /dev/null
+++ b/src/cmd/menu/util.c
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include "menu.h"
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+
+ if (!(p = calloc(nmemb, size)))
+ fatal("calloc:");
+ return p;
+}
+
+void
+fatal(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ exit(1);
+}
diff --git a/src/cmd/rc/code.c b/src/cmd/rc/code.c
new file mode 100644
index 0000000..786f284
--- /dev/null
+++ b/src/cmd/rc/code.c
@@ -0,0 +1,277 @@
+#include "rc.h"
+#include "parse.h"
+#include "exec.h"
+
+// -----------------------------------------------------------------------
+// types
+
+struct Interpreter
+{
+ int i, cap;
+ Code *code;
+};
+
+Code *compiled = nil;
+static struct Interpreter interpreter;
+#define emiti(x) ((void)(interpreter.i != interpreter.cap || grow()), interpreter.code[interpreter.i].i = (x), interpreter.i++)
+#define emitf(x) ((void)(interpreter.i != interpreter.cap || grow()), interpreter.code[interpreter.i].f = (x), interpreter.i++)
+#define emits(x) ((void)(interpreter.i != interpreter.cap || grow()), interpreter.code[interpreter.i].s = (x), interpreter.i++)
+
+static
+int
+grow(void)
+{
+ interpreter.cap += 100;
+ interpreter.code = erealloc(interpreter.code, sizeof(*interpreter.code)*interpreter.cap);
+ memset(interpreter.code+interpreter.cap-100, 0, 100*sizeof(*interpreter.code));
+
+ return 0;
+}
+
+static
+void
+storepc(int a)
+{
+ if(interpreter.i <= a || a < 0)
+ fatal("bad address %d in interpreter", a);
+
+ interpreter.code[a].i = interpreter.i;
+}
+
+static
+void
+walk(Tree *node)
+{
+ Tree *n;
+ int addr1, addr2;
+
+ if(!node)
+ return;
+
+ switch(node->type){
+ default:
+ print(shell.err, "bad type %d in interpreter walk\n", node->type);
+ fatal("crashing\n");
+ break;
+
+ case '$':
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xdollar);
+ break;
+
+ case Tcount:
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xcount);
+ break;
+
+ case Tjoin:
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xjoin);
+ break;
+
+ case Tindex:
+ emitf(Xmark);
+ walk(node->child[1]);
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xindex);
+ break;
+
+ case ';':
+ walk(node->child[0]);
+ walk(node->child[1]);
+ break;
+
+ case '^':
+ emitf(Xmark);
+ walk(node->child[1]);
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xconcatenate);
+ break;
+
+ case Tandand:
+ walk(node->child[0]);
+ emitf(Xtrue);
+ addr1 = emiti(0);
+ walk(node->child[1]);
+ storepc(addr1);
+ break;
+
+ case Toror:
+ walk(node->child[0]);
+ emitf(Xfalse);
+ addr1 = emiti(0);
+ walk(node->child[1]);
+ storepc(addr1);
+ break;
+
+ case Targs:
+ walk(node->child[1]);
+ walk(node->child[0]);
+ break;
+
+ case Tparen: case Tblock:
+ walk(node->child[0]);
+ break;
+
+ case Tbasic:
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xbasic);
+ break;
+
+ case Tbang:
+ walk(node->child[0]);
+ emitf(Xbang);
+
+ case Tword:
+ emitf(Xword);
+ emits(strdup(node->str));
+ break;
+
+ case Twords:
+ walk(node->child[1]);
+ walk(node->child[0]);
+ break;
+
+ case '=':
+ for(n=node; node && node->type == '='; node = node->child[2])
+ ;
+ if(node){
+ for(node=n; node->type=='='; node = node->child[2]){
+ emitf(Xmark);
+ walk(node->child[1]);
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xlocal);
+ }
+ walk(node);
+ for(node=n; node->type=='='; node = node->child[2])
+ emitf(Xunlocal);
+ }else{
+ for(node=n; node; node=node->child[2]){
+ emitf(Xmark);
+ walk(node->child[1]);
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xassign);
+ }
+ }
+ node = n;
+ break;
+ /* control structures */
+ case Twhile:
+ addr1 = interpreter.i; // head of loop
+ walk(node->child[0]);
+ if(addr1 == interpreter.i)
+ fatal("TODO");
+ emitf(Xtrue);
+ addr2 = emiti(0); // goto end of loop
+
+ walk(node->child[1]);
+ emitf(Xgoto);
+ emiti(addr1); // goto top of loop
+ storepc(addr2);
+ break;
+
+ case Tfor:
+ emitf(Xmark);
+ if(node->child[1]){ // for( x in X )
+ walk(node->child[1]);
+ // emitf(Xglob)
+ }else{ // for(X)
+ fatal("TODO");
+ }
+ emitf(Xmark); // null initial value for Xlocal
+ emitf(Xmark);
+ walk(node->child[0]);
+ emitf(Xlocal);
+
+ addr1 = emitf(Xfor);
+ addr2 = emiti(0);
+
+ walk(node->child[2]);
+ emitf(Xgoto);
+ emiti(addr1);
+ storepc(addr2);
+ emitf(Xunlocal);
+ break;
+
+ /* forks */
+ case '&':
+ emitf(Xasync);
+ addr1 = emiti(0);
+ walk(node->child[0]);
+ emitf(Xexit);
+ storepc(addr1);
+ break;
+
+ case Tsubshell:
+ emitf(Xsubshell);
+ addr1 = emiti(0);
+ walk(node->child[0]);
+ emitf(Xexit);
+ storepc(addr1);
+ break;
+
+ case Tpipe:
+ emitf(Xpipe);
+
+ emiti(node->redir.fd[0]);
+ emiti(node->redir.fd[1]);
+ addr1 = emiti(0);
+ addr2 = emiti(0);
+
+ walk(node->child[0]);
+ emitf(Xexit);
+ storepc(addr1);
+
+ walk(node->child[1]);
+ emitf(Xreturn);
+ storepc(addr2);
+
+ emitf(Xpipewait);
+
+ break;
+ }
+}
+
+// -----------------------------------------------------------------------
+// main exports
+
+void
+freecode(Code *c)
+{
+ if(--c[0].i!=0)
+ return;
+ efree(c);
+}
+
+Code *
+copycode(Code *c)
+{
+ c[0].i++;
+ return c;
+}
+
+int
+compile(Tree *node)
+{
+ flush(shell.err);
+
+ interpreter.i = 0;
+ interpreter.code = emalloc(100*sizeof(*interpreter.code));
+ emiti(0); // reference count: no thread owns code yet
+
+ walk(node);
+
+ emitf(Xreturn);
+ emitf(nil);
+
+ compiled = interpreter.code;
+ return 0;
+}
diff --git a/src/cmd/rc/exec.c b/src/cmd/rc/exec.c
new file mode 100644
index 0000000..5baaf1a
--- /dev/null
+++ b/src/cmd/rc/exec.c
@@ -0,0 +1,1267 @@
+#include "rc.h"
+#include "exec.h"
+
+#include <sys/wait.h>
+
+int yyparse(void);
+
+struct Builtin{
+ char *name;
+ void (*func)(void);
+};
+
+struct State {
+ int async;
+};
+
+static struct State state;
+
+// -----------------------------------------------------------------------
+// globals
+
+static Word nullpath = { .str="", .link=nil };
+
+struct Builtin builtin[]={
+ {"cd", xcd},
+ {".", xdot},
+ {"echo", xecho},
+ {"exit", xexit},
+ {"fg", xfg},
+ {"jobs", xjob},
+ 0,
+};
+
+// -----------------------------------------------------------------------
+// internal
+
+/* words and lists */
+
+static
+void
+pushword(char *str)
+{
+ if(!runner->args)
+ fatal("attempt to push on empty argument stack\n");
+
+ runner->args->word = makeword(str, runner->args->word);
+}
+
+static
+void
+popword(void)
+{
+ Word *w;
+ if(!runner->args)
+ fatal("tried to pop word on empty argument stack\n");
+
+ w = runner->args->word;
+ if(!w)
+ fatal("tried to pop word but nothing there\n");
+
+ runner->args->word = w->link;
+ efree(w->str);
+ efree(w);
+}
+
+static
+Word*
+copywords(Word *a, Word *tail)
+{
+ Word *v = nil, **end;
+
+ for(end=&v; a; a = a->link,end=&(*end)->link)
+ *end = makeword(a->str, nil);
+ *end = tail;
+
+ return v;
+}
+
+static
+void
+freewords(Word *w)
+{
+ Word *n;
+ while(w){
+ efree(w->str);
+ n = w->link;
+ efree(w);
+ w = n;
+ }
+}
+
+static
+void
+freelist(Word *w)
+{
+ Word *n;
+ while(w){
+ n = w->link;
+ efree(w->str);
+ efree(w);
+ w = n;
+ }
+
+}
+
+static
+void
+pushlist(void)
+{
+ List *stack = emalloc(sizeof(*stack));
+
+ stack->word = nil;
+ stack->link = runner->args;
+
+ runner->args = stack;
+}
+
+static
+void
+poplist(void)
+{
+ List *stack = runner->args;
+ if(!stack)
+ fatal("attempted to pop an empty argument stack\n");
+
+ freelist(stack->word);
+ runner->args = stack->link;
+ efree(stack);
+}
+
+/* system interop */
+static
+Word*
+path(char *w)
+{
+ Word *path;
+
+ if(strncmp(w, "/", 1)==0
+ || strncmp(w, "./", 2)==0
+ || strncmp(w, "../", 3)==0
+ || (path = var("path")->val)==0)
+ path=&nullpath;
+
+ return path;
+}
+
+static inline
+void
+undoredirs(void)
+{
+ while(runner->redir.end != runner->redir.start)
+ Xpopredir();
+}
+
+static inline
+int
+exitsnext(void)
+{
+ Code *c = &runner->code.exe[runner->code.i];
+ while(c->f == Xpopredir)
+ c++;
+
+ return c->f == Xexit;
+}
+
+static inline
+void
+defaultsignal(void)
+{
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTSTP, SIG_DFL);
+ signal(SIGTTIN, SIG_DFL);
+ signal(SIGTTOU, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+}
+
+static inline
+void
+setpid(Thread *job, int pid)
+{
+ job->pid = pid;
+ if(job->pgid <= 0){
+ job->pgid = pid;
+ addjob(job);
+ }
+
+ setpgid(pid, job->pgid);
+}
+
+/* fork/execute helpers */
+
+static inline
+void
+initchild(Thread *job, int fg)
+{
+ int pid = getpid();
+ setpid(job, pid);
+
+ if(job->flag.user){
+ if(fg)
+ tcsetpgrp(0, job->pgid);
+ else
+ job->flag.user = 0;
+ defaultsignal();
+ }
+
+ clearwait(job);
+}
+
+static inline
+void
+initparent(Thread *job, int pid, int fg)
+{
+ setpid(job, pid);
+
+ if(job->flag.user){
+ if(!fg){
+ tcsetpgrp(0, job->pgid);
+ job->flag.user = 0;
+ }
+ }
+ addwait(job, pid);
+}
+
+static
+void
+xx(void)
+{
+ popword(); // "exec"
+ if(!runner->args->word){
+ Xerror("empty argument list");
+ return;
+ }
+
+ redirect(runner->redir.end);
+ execute(runner->args->word, path(runner->args->word->str));
+ poplist();
+}
+
+static
+int
+xforkx(void)
+{
+ int n, pid;
+
+ switch(pid=fork()){
+ case -1:
+ Xerror("try again\n");
+ return -1;
+ case 0: // child
+ initchild(runner, 1);
+
+ pushword("exec");
+ xx();
+
+ exit(2); // NOTE: unreachable: xx does not return
+ default: // parent
+ initparent(runner, pid, 0);
+
+ return pid;
+ }
+}
+
+/* redirections */
+void
+pushredir(int type, int from, int to)
+{
+ Redir *r = emalloc(sizeof(*r));
+
+ r->type = type;
+ r->from = from;
+ r->to = to;
+
+ r->link = runner->redir.end, runner->redir.end = r;
+}
+
+/* byte code */
+static
+void
+run(Code *c, int pc, Var *local, int inherit)
+{
+ Thread *new = emalloc(sizeof(*new));
+
+ new->code.i = pc;
+ new->code.exe = copycode(c);
+
+ new->cmd.path = nil;
+ new->cmd.io = nil;
+
+ new->args = nil;
+ new->local = local;
+
+ new->flag.eof = 0;
+ if(runner){
+ new->pid = runner->pid;
+ new->flag.user = runner->flag.user;
+ new->redir.end = new->redir.start = runner->redir.end;
+ }else{
+ new->pid = shell.pid;
+ new->flag.user = shell.interactive;
+ new->redir.end = new->redir.start = nil;
+ }
+
+ new->wait.status = 0;
+ new->wait.len = 0;
+ new->wait.cap = 0;
+ new->wait.on = nil;
+
+ new->status = 0;
+ if(inherit)
+ new->pgid = runner->pgid;
+ else
+ new->pgid = -1;
+
+ new->line = 0;
+ new->caller = runner;
+ new->link = nil;
+
+ runner = new;
+}
+
+// -----------------------------------------------------------------------
+// exported builtins
+
+// XXX: find a better place for these
+Word*
+makeword(char *str, Word *link)
+{
+ Word *w = emalloc(sizeof(*w));
+
+ w->str = strdup(str);
+ w->link = link;
+
+ return w;
+}
+
+void
+freeword(Word *word)
+{
+ Word *n;
+
+ while(word){
+ efree(word->str);
+ n = word->link;
+ efree(word);
+ word = n;
+ }
+}
+
+int
+count(Word *w)
+{
+ int n;
+ for(n = 0; w; n++)
+ w = w->link;
+ return n;
+}
+
+// -----------------------------------------------------------------------
+// builtins
+
+void
+xecho(void)
+{
+ int fd;
+ Word *arg;
+ char *b, *s, buf[128];
+
+ fd = mapfd(1);
+ b = buf;
+
+ popword(); // echo
+
+ // TODO: controllable flags here
+ arg = runner->args->word;
+printword:
+ s = arg->str;
+ while(*s){
+ *b++ = *s++;
+ if(b == arrend(buf)-2) // always have 2 bytes available
+ write(fd, buf, arrlen(buf)-2), b = buf;
+ }
+
+ arg = arg->link;
+ if(arg){
+ *b++ = ' ';
+ goto printword;
+ }else{
+ *b++ = '\n';
+ *b++ = 0;
+ /* fallthrough */
+ }
+ write(fd, buf, b-buf);
+
+ poplist();
+}
+
+void
+xexit(void)
+{
+ Word *arg;
+
+ popword(); // exit
+ arg = runner->args->word;
+ switch(count(arg)){
+ default:
+ print(shell.err, "invalid number of arguments to exit, exiting anyways\n");
+ case 0:
+ Xexit();
+ }
+ /* unreachable */
+}
+
+void
+xcd(void)
+{
+ Word *arg;
+ Word *cdpath;
+ char dir[512];
+
+ popword(); // cd
+
+ arg = runner->args->word;
+ switch(count(arg)){
+ default:
+ print(shell.err, "usage: cd [directory]\n");
+ break;
+ case 0:
+ arg = var("home")->val;
+ if(count(arg) >= 1){
+ if(chdir(arg->str) < 0)
+ print(shell.err, "failed cd: %s\n", strerror(errno));
+ }else{
+ print(shell.err, "ambiguous cd: $home empty\n");
+ }
+ break;
+
+ case 1:
+ // TODO: add cdpath
+ cdpath = &nullpath;
+ for(; cdpath; cdpath = cdpath->link){
+ strcpy(dir, cdpath->str);
+ if(dir[0])
+ strcat(dir,"/");
+ strcat(dir, arg->str);
+ if(chdir(dir) < 0){
+ print(shell.err, "failed cd %s: %s\n", dir, strerror(errno));
+ }
+ break;
+ }
+ break;
+ }
+
+ poplist();
+}
+
+static Code dotcmd[14] =
+{
+ [0] = {.i = 0},
+ [1] = {.f = Xmark},
+ [2] = {.f = Xword},
+ [3] = {.s = "0"},
+ [4] = {.f = Xlocal},
+ [5] = {.f = Xmark},
+ [6] = {.f = Xword},
+ [7] = {.s = "*"},
+ [8] = {.f = Xlocal},
+ [9] = {.f = Xreadcmd},
+ [10] = {.f = Xunlocal},
+ [11] = {.f = Xunlocal},
+ [12] = {.f = Xreturn},
+};
+
+void
+xdot(void)
+{
+ Word *p;
+ List *argv;
+ char *base;
+ int fd, iflag = 0;
+ Thread *old;
+ char file[512];
+
+ popword(); // "."
+#if 0
+ if(proc->args->word && strcmp(proc->args->word->str, "-i")==0){
+ iflag = 1;
+ popword();
+ }
+#endif
+ /* get input file */
+ if(!runner->args->word){
+ Xerror("usage: . [-i] file [arg ...]\n");
+ return;
+ }
+
+ base = strdup(runner->args->word->str);
+ popword();
+ for(fd=-1, p=path(base); p; p = p->link){
+ strcpy(file, p->str);
+
+ if(file[0])
+ strcat(file, "/");
+ strcat(file, base);
+
+ if((fd = open(file, 0))>=0)
+ break;
+ }
+
+ if(fd<0){
+ print(shell.err, "failed open: %s: ", base);
+ return;
+ }
+ /* set up for a new command loop */
+ old = runner; // store pointer to old code
+ run(dotcmd, 1, nil, 0);
+
+ /* operations on new command stack */
+ pushredir(Rclose, fd, 0);
+ runner->cmd.path = base;
+ runner->cmd.io = openfd(fd);
+
+ /* push $* value */
+ pushlist();
+ runner->args->word = old->args->word;
+
+ /* free caller's copy of $* */
+ argv = old->args;
+ old->args = argv->link;
+ efree(argv);
+
+ /* push $0 value */
+ pushlist();
+ pushword(base);
+ //ndot++;
+}
+
+void
+xjob(void)
+{
+ int i;
+ Thread *job;
+
+ for(i=0, job = shell.jobs; job; job = job->link, i++)
+ report(job,i);
+
+ poplist();
+}
+
+void
+xfg(void)
+{
+ int i;
+ Thread *job, *old;
+
+ popword(); // fg
+
+ /* get input job id */
+ if(!runner->args->word){
+ print(shell.err, "usage: fg [pid|\%num]\n");
+ poplist();
+ return;
+ }
+
+ i = atoi(runner->args->word->str);
+ popword(); // [pid|num]
+
+ for(job=shell.jobs; i > 0; job=job->link, --i)
+ ;
+
+ poplist(); // this goes here?
+
+ wakeup(job);
+ job->caller = runner, runner = job; // XXX: can this leave zombies?
+ foreground(job, 1);
+}
+
+void
+xboot(int argc, char *argv[])
+{
+ int i;
+ Code bootstrap[32];
+ char num[12];
+
+ i = 0;
+ bootstrap[i++].i = 1;
+ bootstrap[i++].f = Xmark;
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s="*";
+ bootstrap[i++].f = Xassign;
+ bootstrap[i++].f = Xmark;
+ bootstrap[i++].f = Xmark;
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s="*";
+ bootstrap[i++].f = Xdollar;
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s = "/dev/stdin";
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s=".";
+ bootstrap[i++].f = Xbasic;
+ bootstrap[i++].f = Xexit;
+ bootstrap[i].i = 0;
+
+ run(bootstrap, 1, nil, 0);
+ runner->pid = runner->pgid = shell.pid;
+ pushlist(); // prime bootstrap argv
+
+ argv0 = strdup(argv[0]);
+ for(i = argc-1; i > 0; --i)
+ pushword(argv[i]);
+
+ /* main interpreter loop */
+ for(;;){
+ runner->code.i++;
+ (*runner->code.exe[runner->code.i-1].f)();
+ }
+}
+
+// -----------------------------------------------------------------------
+// exported interpreter bytecode
+
+void
+Xmark(void)
+{
+ pushlist();
+}
+
+void
+Xword(void)
+{
+ pushword(runner->code.exe[runner->code.i++].s);
+}
+
+void
+Xtrue(void)
+{
+ if(!runner->status){
+ assert(runner->wait.status == Pdone);
+ runner->code.i++;
+ deljob(runner);
+ runner->pgid = -1;
+ }else
+ runner->code.i = runner->code.exe[runner->code.i].i;
+}
+
+void
+Xfalse(void)
+{
+ if(runner->status){
+ assert(runner->wait.status == Pdone);
+ runner->code.i++;
+ deljob(runner);
+ runner->pgid = -1;
+ } else
+ runner->code.i = runner->code.exe[runner->code.i].i;
+}
+
+void
+Xgoto(void)
+{
+ runner->code.i = runner->code.exe[runner->code.i].i;
+}
+
+void
+Xfor(void)
+{
+ if(!runner->args->word){
+ poplist();
+ runner->code.i = runner->code.exe[runner->code.i].i;
+ }else{
+ freelist(runner->local->val);
+
+ runner->local->val = runner->args->word;
+ runner->local->new = 1;
+ runner->args->word = runner->args->word->link;
+
+ runner->local->val->link = nil;
+ runner->code.i++;
+ }
+
+}
+
+static
+Word*
+catlist(Word *l, Word *r, Word *tail)
+{
+ Word *w;
+ char *buf;
+
+ if(l->link || r->link)
+ tail = catlist( (!l->link)?l:l->link, (!r->link)?r:r->link, tail);
+
+ buf = emalloc(strlen(l->str)+strlen(r->str)+1);
+ strcpy(buf, l->str);
+ strcat(buf, r->str);
+
+ w = makeword(buf, tail);
+ efree(buf);
+
+ return w;
+}
+
+void
+Xconcatenate(void)
+{
+ int rn, ln;
+ Word *l = runner->args->word;
+ Word *r = runner->args->link->word;
+ Word *w = runner->args->link->link->word;
+
+ ln = count(l), rn = count(r);
+ if(ln != 0 || rn != 0) {
+ if(ln == 0 || rn == 0){
+ Xerror("null list in concatenation\n");
+ return;
+ }
+ if(ln != 1 && rn != 1 && ln != rn) {
+ Xerror("mismatched list lengths in concatenation\n");
+ return;
+ }
+ w = catlist(l, r, w);
+ }
+
+ poplist();
+ poplist();
+ runner->args->word = w;
+}
+
+void
+Xdollar(void)
+{
+ int n;
+ char *s, *t;
+ Word *a, *star;
+
+ if(count(runner->args->word)!=1){
+ Xerror("variable name not singleton!\n");
+ return;
+ }
+ s = runner->args->word->str;
+ // deglob(s);
+ n = 0;
+
+ for(t = s;'0'<=*t && *t<='9';t++)
+ n = n*10+*t-'0';
+
+ a = runner->args->link->word;
+
+ if(n==0 || *t)
+ a = copywords(var(s)->val, a);
+ else{
+ star = var("*")->val;
+ if(star && 1<=n && n<=count(star)){
+ while(--n)
+ star = star->link;
+
+ a = makeword(star->str, a);
+ }
+ }
+
+ poplist();
+ runner->args->word = a;
+}
+
+static
+Word*
+cpwords(Word *array, Word *tail, int n)
+{
+ Word *cp, **end;
+
+ cp = nil, end = &cp;
+ while(n-- > 0){
+ *end = makeword(array->str, nil);
+ end = &(*end)->link;
+ array = array->link;
+ }
+ *end = tail;
+
+ return cp;
+}
+
+
+static
+Word*
+getindex(Word *array, int len, Word *index, Word *tail)
+{
+ char *s;
+ int n, m;
+ if(!index)
+ return tail;
+
+ tail = getindex(array, len, index->link, tail);
+
+ s = index->str;
+ //deglob(s)
+
+ m = 0, n = 0;
+ while('0' <= *s && *s <= '9')
+ n = 10*n + (*s++ - '0');
+ if(*s == '-'){
+ if(*++s == 0)
+ m = len - n;
+ else{
+ while('0' <= *s && *s <= '9')
+ m = 10*m + (*s++ - '0');
+ m -= n;
+ }
+ }
+
+ if(n<1 || n > len || m < 0)
+ return tail;
+ if(n+m > len)
+ m = len-n;
+ while(--n > 0)
+ array = array->link;
+ return cpwords(array, tail, m+1);
+}
+
+void
+Xindex(void)
+{
+ char *s;
+ Word *val, *ret;
+
+ if(count(runner->args->word) != 1){
+ Xerror("variable name not a singleton");
+ return;
+ }
+ s = runner->args->word->str;
+ //deglob(s)
+ val = var(s)->val;
+ poplist();
+
+ ret = runner->args->link->word; // pointer to next stack frame
+ ret = getindex(val, count(val), runner->args->word, ret);
+ poplist();
+
+ // push result back on stack
+ runner->args->word = ret;
+}
+
+void
+Xjoin(void)
+{
+ int n;
+ char *s;
+ Word *arg, *elt;
+
+ if(count(runner->args->word) != 1){
+ Xerror("variable name is not singleton\n");
+ return;
+ }
+
+ s = runner->args->word->str;
+ // deglob(s)
+
+ arg = var(s)->val;
+ poplist();
+
+ n = count(arg);
+ if(n==0){
+ pushword("");
+ return;
+ }
+
+ for(elt = arg; elt; elt=elt->link)
+ n += strlen(elt->str);
+
+ s = emalloc(n);
+ if(arg){
+ strcpy(s, arg->str);
+ for(elt = arg->link; elt; elt = elt->link){
+ strcat(s, " ");
+ strcat(s, elt->str);
+ }
+ }else
+ s[0] = 0;
+
+ pushword(s);
+ efree(s);
+}
+
+void
+Xassign(void)
+{
+ Var *v;
+
+ if(count(runner->args->word)!=1){
+ Xerror("variable name not singleton!\n");
+ return;
+ }
+ //deglob(runq->argv->words->word);
+ v = var(runner->args->word->str);
+ poplist();
+
+ //globlist();
+ freewords(v->val);
+ v->val = runner->args->word;
+ v->new = 1;
+ if(v->update)
+ v->update(v);
+
+ runner->args->word = nil;
+ poplist();
+}
+
+void
+Xreadcmd(void)
+{
+ Thread *root;
+ Word *prompt;
+
+ flush(shell.err);
+ root = runner;
+
+ resetprompt();
+
+ if(yyparse()){
+ // resource cleanup?
+ if(runner->flag.eof)
+ Xreturn();
+ else
+ --root->code.i;
+ }else{
+ --root->code.i; /* re-execute Xreadcmd after codebuf runs */
+ run(compiled, 1, root->local, 0);
+ }
+
+ killzombies();
+ freeparsetree();
+}
+
+void
+Xlocal(void)
+{
+ if(count(runner->args->word)!=1){
+ Xerror("variable name must be singleton\n");
+ return;
+ }
+ //deglob(shell->args->word->str);
+
+ runner->local = makevar(strdup(runner->args->word->str), runner->local);
+ runner->local->val = copywords(runner->args->link->word, nil);
+ runner->local->new = 1;
+
+ poplist();
+ poplist();
+}
+
+void
+Xunlocal(void)
+{
+ Var *v = runner->local, *hide;
+ if(!v)
+ fatal("Xunlocal: no locals!\n", 0);
+
+ runner->local = v->link;
+ hide = var(v->name);
+ hide->new = 1;
+
+ efree(v->name);
+ freewords(v->val);
+ efree(v);
+}
+
+void
+Xasync(void)
+{
+ int pid;
+ /*
+ int null = open("/dev/null", 0);
+ if(!null){
+ Xerror("can not open /dev/null\n");
+ return;
+ }
+ */
+
+ switch(pid=fork()){
+ case -1:
+ // close(null);
+ Xerror("fork failed: try again");
+ break;
+
+ case 0: // child in background
+ initchild(runner,0);
+ /* pushredir(Ropen, null, 0); */
+
+ run(runner->code.exe, runner->code.i+1, runner->local, 0);
+ runner->caller = nil;
+ runner->flag.user = 0;
+ break;
+
+ default: // parent in foreground
+ initparent(runner,pid,1);
+ // close(null);
+
+ runner->code.i = runner->code.exe[runner->code.i].i; /* jump to end of async command */
+ /* don't wait: continue running */
+ }
+}
+
+void
+Xsubshell(void)
+{
+ int pid, user;
+
+ user = runner->flag.user;
+ switch(pid=fork()){
+ case -1:
+ Xerror("fork failed: try again");
+ break;
+
+ case 0: // child
+ initchild(runner, 1);
+ run(runner->code.exe, runner->code.i+1, runner->local, 1);
+ runner->caller = nil;
+ break;
+
+ default: // parent
+ initparent(runner, pid, 0); // relinquish control
+ waitfor(runner, pid); // wait until child finishes
+ if(user){
+ tcsetpgrp(0, shell.pid);
+ runner->flag.user = 1; // take control
+ }
+
+ runner->code.i = runner->code.exe[runner->code.i].i; // jump to end of subshell command and continue execution
+ }
+}
+
+void
+Xpipewait(void)
+{
+ foreground(runner, 0);
+}
+
+void
+Xpipe(void)
+{
+ Thread *orig;
+ int pc, pid, lfd, rfd, pfd[2];
+
+ orig = runner;
+ pc = orig->code.i;
+ lfd = orig->code.exe[pc++].i;
+ rfd = orig->code.exe[pc++].i;
+
+ if(pipe(pfd)<0){
+ Xerror("can't get pipe\n");
+ return;
+ }
+
+ switch(pid=fork()){
+ case -1:
+ Xerror("try again");
+ break;
+ case 0: // child
+ initchild(runner,1);
+
+ /* child 0 (writer) forked process */
+ run(runner->code.exe, pc+2, runner->local, 1);
+ runner->caller = nil;
+
+ close(pfd[0]);
+ pushredir(Ropen, pfd[1], lfd);
+ break;
+
+ default: // parent
+ initparent(runner,pid,0);
+
+ /* child 1 (reader) subprocess*/
+ run(runner->code.exe, runner->code.exe[pc].i, runner->local, 1);
+
+ close(pfd[1]);
+ pushredir(Ropen, pfd[0], rfd);
+
+ orig->code.i = orig->code.exe[pc+1].i;
+ break;
+ }
+}
+
+void
+Xbasic(void)
+{
+ Var *v;
+ Word *arg;
+ int pid, status;
+ struct Builtin *b;
+
+ arg = runner->args->word;
+ if(!arg){
+ Xerror("empty argument list\n");
+ return;
+ }
+
+ v = var(arg->str);
+ if(v->func){
+ return;
+ }
+
+ // see if it matches a builtin
+ for(b = builtin; b->name; b++){
+ if(strcmp(b->name, arg->str)==0){
+ b->func();
+ return;
+ }
+ }
+
+ /* if we are here then it's an external command */
+ if(exitsnext()){ // if we exit immediately, no need to fork
+ pushword("exec");
+ xx();
+ Xexit();
+ }
+
+ // run the external command
+ if((pid = xforkx()) < 0) {
+ Xerror("try again");
+ return;
+ }
+
+ poplist();
+ foreground(runner, 0); // waits for child
+}
+
+void
+Xcount(void)
+{
+ Word *arg;
+ char *str, num[12];
+
+ if(count(runner->args->word) != 1){
+ Xerror("variable name not a singleton\n");
+ return;
+ }
+
+ str = runner->args->word->str;
+ arg = var(str)->val;
+ poplist();
+
+ itoa(num, count(arg));
+ pushword(num);
+}
+
+void
+Xflat(void)
+{
+ int len;
+ char *str;
+ Word *arg, *a;
+
+ if(count(runner->args->word)!=1){
+ Xerror("variable name is not a singleton\n");
+ return;
+ }
+
+ str = runner->args->word->str;
+ arg = var(str)->val;
+ poplist();
+
+ len = count(arg);
+ if(!len){
+ pushword("");
+ return;
+ }
+
+ for(a=arg; a; a=a->link)
+ len += strlen(a->str);
+
+ str = emalloc(len);
+ if(arg){
+ strcpy(str, arg->str);
+ for(a = arg->link; a; a = a->link){
+ strcat(str," ");
+ strcat(str,a->str);
+ }
+ }else
+ str[0] = 0;
+
+ pushword(str);
+ efree(str);
+}
+
+void
+Xbang(void)
+{
+ if(runner->status)
+ runner->status = 0;
+ else
+ runner->status = 1;
+}
+
+void
+Xpopredir(void)
+{
+ Redir *r = runner->redir.end;
+ if(!r)
+ fatal("attempted to pop a nil redir\n");
+
+ runner->redir.end = runner->redir.end->link;
+ if(r->type==Ropen)
+ close(r->from);
+
+ efree(r);
+}
+
+void
+Xreturn(void)
+{
+ Thread *curr = runner;
+
+ switch(curr->wait.status){
+ /*
+ * If our job is still running or suspended we must:
+ * 1. move program one step back to rerun Xreturn upon recall
+ * 2. return to our calling thread
+ * 3. don't free!
+ */
+ case Prun:
+ report(curr, 0);
+ curr->flag.user = 0;
+ case Pstop:
+ curr->code.i--;
+ runner = curr->caller;
+ curr->caller = nil; // detach job
+ return;
+ /*
+ * If our job has finished:
+ * 1. remove from our list
+ * 2. continue to clean up its memory
+ */
+ case Pdone:
+ deljob(curr);
+ /* fallthrough */
+ default:
+ ;
+ }
+
+ undoredirs();
+
+ while(curr->args)
+ poplist();
+ freecode(curr->code.exe);
+ efree(curr->wait.on);
+
+ runner = curr->caller;
+ efree(curr);
+ if(!runner)
+ exit(0);
+}
+
+void
+Xexit(void)
+{
+ exit(runner->status);
+}
+
+void
+Xerror(char *msg)
+{
+ print(shell.err, "rc: %s", msg);
+ flush(shell.err);
+ while(!runner->flag.user)
+ Xreturn();
+}
+
diff --git a/src/cmd/rc/exec.h b/src/cmd/rc/exec.h
new file mode 100644
index 0000000..a3a6ae9
--- /dev/null
+++ b/src/cmd/rc/exec.h
@@ -0,0 +1,47 @@
+#pragma once
+
+/*
+ * opcode routines
+ * arguments on stack (...)
+ * arguments in line [...]
+ * code in line with jump around {...}
+ */
+
+void Xmark(void); // Xmark marks stack location for word
+void Xindex(void); // Xindex
+void Xlocal(void); // Xlocal(name,val) create local variable, assign value
+void Xunlocal(void); // Xunlocal delete local variable
+void Xdollar(void); // Xdollar(name) get value of name
+void Xtrue(void); // Xtrue{...} execute {} if true
+void Xfalse(void); // Xfalse{...} execute {} if false
+void Xgoto(void); // Xgoto[addr] goto address
+void Xfor(void); // Xfor(var, list){... Xreturn}
+void Xreadcmd(void); //
+void Xassign(void);
+void Xbang(void);
+void Xasync(void);
+void Xbasic(void); // Xbasic(args) run command and wait for result
+void Xsubshell(void);
+void Xword(void);
+void Xjoin(void);
+void Xconcatenate(void);
+void Xcount(void);
+void Xflat(void);
+void Xpipe(void);
+void Xpipewait(void);
+void Xpopredir(void);
+
+void Xreturn(void);
+void Xexit(void);
+
+void Xerror(char*);
+
+/* builtin commands */
+void xcd(void);
+void xdot(void);
+void xecho(void);
+void xexit(void);
+void xfg(void);
+void xjob(void);
+
+void xboot(int argc, char *argv[]);
diff --git a/src/cmd/rc/input.c b/src/cmd/rc/input.c
new file mode 100644
index 0000000..cc2383d
--- /dev/null
+++ b/src/cmd/rc/input.c
@@ -0,0 +1,1679 @@
+#include "rc.h"
+
+#include <termios.h>
+#include <sys/ioctl.h>
+
+/* don't change order of these without modifying matrix */
+enum
+{
+ NonPrintable,
+ Alnum,
+ Punctuation,
+ Space
+};
+
+static int ascii[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+struct Mode
+{
+ ushort raw : 1;
+ ushort multiline : 1;
+ ushort mask : 1;
+ ushort defer : 1;
+ struct {
+ ushort on : 1;
+ ushort insert : 1;
+ } vi ;
+};
+
+/*
+ * the structure represents the state during line editing.
+ * we pass this state to functions implementing specific editing functionalities
+ */
+struct TerminalState
+{
+ int ifd; /* terminal stdin file descriptor. */
+ int ofd; /* terminal stdout file descriptor. */
+
+ struct{
+ char *s; /* raw UTF-8 bytes */
+ int len; /* number of bytes in prompt */
+ int size; /* number of (printed) runes in prompt */
+ } prompt;
+
+ struct{
+ intptr cap; /* capacity of edit buffer */
+ intptr len; /* current number of bytes stored */
+ intptr pos; /* position within edit buffer */
+ char *buf;
+ } edit; /* edit buffer */
+
+ struct{
+ intptr cap; /* number of columns in terminal */
+ intptr len; /* current edited line length (in runes) */
+ intptr pos; /* current cursor position (in runes) */
+ intptr old; /* previous refresh cursor position (in runes) */
+ } cursor;
+
+ struct{
+ intptr cap;
+ intptr len;
+ char *buf;
+ } yank; /* yank buffer */
+
+ intptr maxrows; /* maximum num of rows used so far (multiline mode) */
+ intptr history; /* index of history we are currently editing */
+};
+
+/*
+ * line history (circular buffer)
+ */
+struct History
+{
+ char **bot, **top, *entry[1024];
+};
+
+/* globals */
+static struct Mode mode;
+static struct History history;
+static struct termios originalterm;
+
+enum
+{
+ KeyNil = 0, /* nil */
+ KeyCtrlA = 1, /* Ctrl+a */
+ KeyCtrlB = 2, /* Ctrl-b */
+ KeyCtrlC = 3, /* Ctrl-c */
+ KeyCtrlD = 4, /* Ctrl-d */
+ KeyCtrlE = 5, /* Ctrl-e */
+ KeyCtrlF = 6, /* Ctrl-f */
+ KeyCtrlH = 8, /* Ctrl-h */
+ KeyTab = 9, /* Tab */
+ KeyCtrlK = 11, /* Ctrl+k */
+ KeyCtrlL = 12, /* Ctrl+l */
+ KeyEnter = 13, /* Enter */
+ KeyCtrlN = 14, /* Ctrl-n */
+ KeyCtrlP = 16, /* Ctrl-p */
+ KeyCtrlT = 20, /* Ctrl-t */
+ KeyCtrlU = 21, /* Ctrl+u */
+ KeyCtrlW = 23, /* Ctrl+w */
+ KeyEsc = 27, /* Escape */
+ KeyBackspace = 127 /* Backspace */
+};
+
+static void doatexit(void);
+
+/* vi operations */
+typedef struct
+{
+ intptr buffer;
+ intptr cursor;
+} Position;
+
+typedef Position (*Noun)(struct TerminalState*, int);
+typedef void (*Verb)(struct TerminalState*, Position);
+
+static
+int
+runetype(rune r)
+{
+ if(r<128)
+ return ascii[r];
+ if(utf8·isspace(r))
+ return Space;
+ if(utf8·isdigit(r) || utf8·isalpha(r))
+ return Alnum;
+ if(utf8·ispunct(r))
+ return Punctuation;
+
+ return NonPrintable;
+}
+
+static
+void
+normalcursor(int fd)
+{
+ write(fd,"\e[2 q",5);
+}
+
+static
+void
+insertcursor(int fd)
+{
+ write(fd,"\e[6 q",5);
+}
+
+/* raw mode: 1960 magic shit. */
+static
+int
+enterraw(int fd)
+{
+ struct termios raw;
+
+ if(!shell.interactive)
+ goto fatal;
+
+ if(!mode.defer){
+ atexit(doatexit);
+ mode.defer = 1;
+ }
+ if(tcgetattr(fd,&originalterm) == -1)
+ goto fatal;
+
+ raw = originalterm; /* modify the original mode */
+
+ /* input modes: no break, no CR to NL, no parity check, no strip char,
+ * no start/stop output control. */
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ /* output modes - disable post processing */
+ raw.c_oflag &= ~(OPOST);
+ /* control modes - set 8 bit chars */
+ raw.c_cflag |= (CS8);
+ /* local modes - choing off, canonical off, no extended functions,
+ * no signal chars (^Z,^C) */
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ /* control chars - set return condition: min number of bytes and timer.
+ * We want read to return every single byte, without timeout. */
+ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
+
+ /* put terminal in raw mode after flushing */
+ if(tcsetattr(fd,TCSAFLUSH,&raw) < 0)
+ goto fatal;
+
+ mode.raw = 1;
+ return 1;
+
+fatal:
+ errno = ENOTTY;
+ return 0;
+}
+
+static
+void
+exitraw(int fd)
+{
+ /* don't even check the return value as it's too late. */
+ if(mode.raw && tcsetattr(fd,TCSAFLUSH,&originalterm) != -1)
+ mode.raw = 0;
+}
+
+/* use the esc [6n escape sequence to query the horizontal cursor position
+ * and return it. on error -1 is returned, on success the position of the
+ * cursor. */
+static
+int
+cursorposition(int ifd, int ofd)
+{
+ char buf[32];
+ int cols, rows;
+ unsigned int i = 0;
+
+ /* Report cursor location */
+ if(write(ofd, "\x1b[6n", 4) != 4)
+ return -1;
+
+ /* Read the response: ESC [ rows ; cols R */
+ while(i < sizeof(buf)-1) {
+ if(read(ifd,buf+i,1) != 1)
+ break;
+ if(buf[i] == 'R')
+ break;
+ i++;
+ }
+ buf[i] = '\0';
+
+ /* Parse it. */
+ if(buf[0] != KeyEsc || buf[1] != '[')
+ return -1;
+ if(sscanf(buf+2,"%d;%d",&rows,&cols) != 2)
+ return -1;
+
+ return cols;
+}
+
+/* try to get the number of columns in the current terminal, or assume 80 if it fails. */
+static
+int
+columns(int ifd, int ofd)
+{
+ struct winsize ws;
+
+ if(ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0){
+ /* ioctl() failed. Try to query the terminal itself. */
+ int start, cols;
+
+ /* Get the initial position so we can restore it later. */
+ start = cursorposition(ifd,ofd);
+ if(start == -1)
+ goto failed;
+
+ /* Go to right margin and get position. */
+ if(write(ofd,"\x1b[999C",6) != 6)
+ goto failed;
+ cols = cursorposition(ifd,ofd);
+ if(cols == -1)
+ goto failed;
+
+ /* Restore position. */
+ if(cols > start){
+ char esc[32];
+ snprintf(esc,32,"\x1b[%dD",cols-start);
+ if(write(ofd,esc,strlen(esc)) == -1)
+ ;
+ }
+ return cols;
+ }else
+ return ws.ws_col;
+
+failed:
+ return 80;
+}
+
+static
+void
+clear(void)
+{
+ if(write(1,"\x1b[H\x1b[2J",7) <= 0)
+ ;
+}
+
+/* beep: used for completion when there is nothing to complete or when all
+ * the choices were already shown. */
+static
+void
+beep(void)
+{
+ fprintf(stderr, "\x7");
+ fflush(stderr);
+}
+
+// -----------------------------------------------------------------------
+// command history
+
+void
+inithistory(void)
+{
+ history.bot = history.top = history.entry;
+}
+
+int
+addhistory(char *line)
+{
+ char *copy;
+
+ copy = strdup(line);
+ if(!copy)
+ return 0;
+
+ *history.top++ = copy;
+ if(history.top == arrend(history.entry))
+ history.top = history.entry;
+
+ if(history.top == history.bot){
+ efree(history.bot);
+ history.bot++;
+ }
+
+ return 1;
+}
+
+static
+void
+pophistory(void)
+{
+ if(--history.top < history.entry)
+ history.top = arrend(history.entry)-1;
+ efree(*history.top);
+}
+
+static void refreshline(struct TerminalState *);
+
+static
+char **
+currenthistory(struct TerminalState *term, intptr *size)
+{
+ char **entry;
+ intptr len, head;
+
+ if(history.top > history.bot){
+ len = history.top - history.bot;
+ entry = history.top - term->history - 1;
+ }else if(history.top < history.bot){
+ len = (arrend(history.entry) - history.bot) + (history.top - history.entry);
+ if((head=history.top - history.entry) < term->history)
+ entry = arrend(history.entry) - head;
+ else
+ entry = history.top - term->history - 1;
+ }else
+ return nil;
+
+ *size = len;
+ return entry;
+}
+
+static
+void
+usehistory(struct TerminalState *term, int d)
+{
+ rune r;
+ intptr w, len;
+ char *b, *e, **entry;
+
+ if(!(entry = currenthistory(term, &len)))
+ return;
+
+ efree(*entry);
+ *entry = strdup(term->edit.buf);
+
+ term->history += d;
+ if(term->history < 0){
+ term->history = 0;
+ return;
+ }else if(term->history >= len){
+ term->history = len - 1;
+ return;
+ }
+ entry = currenthistory(term, &len);
+
+ strncpy(term->edit.buf, *entry, term->edit.cap);
+ term->edit.buf[term->edit.cap-1] = 0;
+
+ /* update cursor/buffer positions */
+ term->edit.len = term->edit.pos = strlen(term->edit.buf);
+ for(w=0, b=term->edit.buf, e=term->edit.buf+term->edit.len; b < e; ){
+ b += utf8·decode(b, &r);
+ w += utf8·runewidth(r);
+ }
+ term->cursor.len = term->cursor.pos = w;
+
+ refreshline(term);
+}
+
+// -----------------------------------------------------------------------
+// line editing
+
+/*
+ * we define a very simple "append buffer" structure, that is an heap
+ * allocated string where we can append to. this is useful in order to
+ * write all the escape sequences in a buffer and flush them to the standard
+ * output in a single call, to avoid flickering effects.
+ */
+
+struct Buffer
+{
+ int len;
+ char *b;
+};
+
+static
+void
+initbuffer(struct Buffer *ab)
+{
+ ab->b = nil;
+ ab->len = 0;
+}
+
+static
+void
+append(struct Buffer *ab, const char *s, int len)
+{
+ char *new = realloc(ab->b,ab->len+len);
+
+ if (new == nil) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static
+void
+freebuffer(struct Buffer *ab)
+{
+ free(ab->b);
+}
+
+/* single line low level line refresh.
+ *
+ * rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static
+void
+refreshsingleline(struct TerminalState *term)
+{
+ char esc[64];
+ struct Buffer ab;
+
+ int n, w;
+ rune r;
+ int fd = term->ofd;
+ intptr off = term->prompt.size;
+ char *buf = term->edit.buf;
+ intptr len = term->edit.len;
+ intptr pos = term->cursor.pos;
+ intptr col = term->cursor.len;
+
+ while((off+pos) >= term->cursor.cap){
+ n = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+
+ buf+=n, len-=n;
+ pos-=w, col-=w;
+ }
+
+ assert(buf <= term->edit.buf + len);
+
+ while(off+col > term->cursor.cap){
+ n = utf8·decodeprev(buf+len-1, &r);
+ w = utf8·runewidth(r);
+
+ len-=n, col-=w;
+ }
+ assert(len >= 0);
+
+ initbuffer(&ab); // TODO: do we need so much malloc pressure?
+
+ /* move cursor to left edge */
+ snprintf(esc,64,"\r");
+ append(&ab,"\r",1);
+
+ /* write the prompt and the current buffer content */
+ append(&ab, term->prompt.s, term->prompt.len);
+
+ if(mode.mask == 1)
+ while(len--)
+ append(&ab,"*",1);
+ else
+ append(&ab,buf,len);
+
+ snprintf(esc,64,"\x1b[0K"); // erase to right
+ append(&ab,esc,strlen(esc));
+
+ snprintf(esc,64,"\r\x1b[%dC", (int)(off+pos)); // move cursor to original position
+ append(&ab,esc,strlen(esc));
+
+ if(write(fd,ab.b,ab.len) == -1) /* can't recover from write error. */
+ ;
+
+ freebuffer(&ab);
+}
+
+/* multi line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static
+void
+refreshmultilines(struct TerminalState *term)
+{
+#if 0
+ char esc[64];
+ int plen = term->plen;
+ int rows = (plen+term->len+term->cols-1)/term->cols; /* rows used by current buf. */
+ int rpos = (plen+term->oldpos+term->cols)/term->cols; /* cursor relative row. */
+ int rpos2; /* rpos after refresh. */
+ int col; /* colum position, zero-based. */
+ int i;
+ int old_rows = term->maxrows;
+ int fd = term->ofd, j;
+ struct Buffer ab;
+
+ /* Update maxrows if needed. */
+ if(rows > (int)term->maxrows)
+ term->maxrows = rows;
+
+ /* First step: clear all the lines used before. To do so start by
+ * going to the last row. */
+ initbuffer(&ab);
+ if(old_rows-rpos > 0){
+ snprintf(esc,64,"\x1b[%dB", old_rows-rpos);
+ append(&ab,esc,strlen(esc));
+ }
+
+ /* Now for every row clear it, go up. */
+ for(j = 0; j < old_rows-1; j++){
+ snprintf(esc,64,"\r\x1b[0K\x1b[1A");
+ append(&ab,esc,strlen(esc));
+ }
+
+ /* clean the top line. */
+ snprintf(esc,64,"\r\x1b[0K");
+ append(&ab,esc,strlen(esc));
+
+ /* Write the prompt and the current buffer content */
+ append(&ab,term->prompt,strlen(term->prompt));
+ if(mode.mask == 1){
+ for(i = 0; i < term->len; i++) append(&ab,"*",1);
+ }else
+ append(&ab,term->buf,term->len);
+
+ /* If we are at the very end of the screen with our prompt, we need to
+ * emit a newline and move the prompt to the first column. */
+ if(term->pos && term->pos == term->len && (term->pos+plen) % term->cols == 0) {
+ append(&ab,"\n",1);
+ snprintf(esc,64,"\r");
+ append(&ab,esc,strlen(esc));
+ rows++;
+ if(rows > (int)term->maxrows)
+ term->maxrows = rows;
+ }
+
+ /* Move cursor to right position. */
+ rpos2 = (plen+term->pos+term->cols)/term->cols; /* current cursor relative row. */
+
+ /* Go up till we reach the expected positon. */
+ if(rows-rpos2 > 0){
+ snprintf(esc,64,"\x1b[%dA", rows-rpos2);
+ append(&ab,esc,strlen(esc));
+ }
+
+ /* Set column. */
+ col = (plen+(int)term->pos) % (int)term->cols;
+ if(col)
+ snprintf(esc,64,"\r\x1b[%dC", col);
+ else
+ snprintf(esc,64,"\r");
+ append(&ab,esc,strlen(esc));
+
+ term->oldpos = term->pos;
+
+ if(write(fd,ab.b,ab.len) == -1) /* Can't recover from write error. */
+ ;
+
+ freebuffer(&ab);
+#endif
+}
+
+/* Calls the two low level functions refreshSingleLine() or
+ * refreshMultiLine() according to the selected mode. */
+static
+void
+refreshline(struct TerminalState *term)
+{
+ if(mode.multiline)
+ refreshmultilines(term);
+ else
+ refreshsingleline(term);
+}
+
+/* insert the rune 'c' at cursor current position.
+ * on error writing to the terminal -1 is returned, otherwise 0. */
+int
+insertrune(struct TerminalState *term, int n, char *c)
+{
+ int w;
+ rune r;
+
+ utf8·decode(c, &r);
+ w = utf8·runewidth(r);
+
+ if(term->edit.len + n <= term->edit.cap){
+ if(term->edit.pos == term->edit.len){
+ memcpy(term->edit.buf+term->edit.pos, c, n);
+
+ term->edit.pos += n, term->edit.len += n;
+ term->cursor.pos += w, term->cursor.len += w;
+
+ term->edit.buf[term->edit.len] = '\0';
+
+ if(!mode.multiline && ((term->prompt.size+term->cursor.pos+n) <= term->cursor.cap)){
+ if(mode.mask){
+ c = "*";
+ n = 1;
+ }
+ if(write(term->ofd, c, n) == -1)
+ return 0;
+ }
+ refreshline(term);
+ }else{
+ memmove(term->edit.buf+term->edit.pos+n, term->edit.buf+term->edit.pos, term->edit.len-term->edit.pos);
+ memcpy(term->edit.buf+term->edit.pos, c, n);
+
+ term->edit.pos += n, term->edit.len += n;
+ term->cursor.pos += w, term->cursor.len += w;
+
+ term->edit.buf[term->edit.len] = '\0';
+ refreshline(term);
+ }
+ }
+
+ return 1;
+}
+
+int
+insertbytes(struct TerminalState *term, int len, char *buf)
+{
+ int nr;
+ if(term->edit.len + len > term->edit.cap){
+ len = term->edit.cap - term->edit.len;
+ buf[len] = 0;
+ }
+ nr = utf8·len(buf);
+
+ if(term->edit.pos == term->cursor.len){
+ memcpy(term->edit.buf+term->edit.len, buf, len);
+
+ term->edit.pos += len, term->edit.len += len;
+ term->cursor.pos += nr, term->cursor.len += nr;
+
+ // XXX: transfer the modeline here?
+ term->edit.buf[term->edit.len] = '\0';
+ refreshline(term);
+ }else{
+ memmove(term->edit.buf+term->edit.pos+len,term->edit.buf+term->edit.pos,term->edit.len-term->edit.pos);
+ memcpy(term->edit.buf+term->edit.pos, buf, len);
+
+ term->edit.pos += len, term->edit.len += len;
+ term->cursor.pos += nr, term->cursor.len += nr;
+
+ term->edit.buf[term->edit.len] = '\0';
+ refreshline(term);
+ }
+
+ return 1;
+}
+
+// -----------------------------------------------------------------------
+// vi functionality
+
+/* modes */
+
+static
+void
+normalmode(int fd)
+{
+ mode.vi.insert = 0;
+ normalcursor(fd);
+}
+
+static
+void
+insertmode(int fd)
+{
+ mode.vi.insert = 1;
+ insertcursor(fd);
+}
+
+/* actions */
+
+static
+void
+move(struct TerminalState *term, Position to)
+{
+ if(to.buffer != term->edit.pos){
+ term->edit.pos = to.buffer;
+ term->cursor.pos = to.cursor;
+ refreshline(term);
+ }
+}
+
+static
+void
+yank(struct TerminalState *term, Position to)
+{
+ intptr len, off;
+
+ if(to.buffer == term->edit.pos)
+ return; // noop
+
+ if(to.buffer > term->edit.pos){
+ len = to.buffer - term->edit.pos;
+ off = term->edit.pos;
+ }else{
+ len = term->edit.pos - to.buffer;
+ off = to.buffer;
+ }
+
+ if(term->yank.cap < len+1){
+ efree(term->yank.buf);
+ term->yank.cap = len+1;
+ term->yank.buf = emalloc(len+1);
+ }
+ term->yank.len = len;
+ memcpy(term->yank.buf, term->edit.buf+off, len);
+ term->yank.buf[len] = 0;
+}
+
+static
+void
+delete(struct TerminalState *term, Position to)
+{
+ intptr diff;
+
+ // delete characters in front of us (exclusive)
+ if(to.buffer > term->edit.pos){
+ diff = to.buffer - term->edit.pos;
+ memmove(term->edit.buf+term->edit.pos, term->edit.buf+to.buffer, term->edit.len-to.buffer+1);
+ term->edit.len -= diff;
+
+ diff = to.cursor - term->cursor.pos;
+ goto refresh;
+ }
+
+ // delete characters behind us
+ if(to.buffer < term->edit.pos){
+ diff = term->edit.pos - to.buffer;
+ memmove(term->edit.buf+to.buffer, term->edit.buf+term->edit.pos, term->edit.len-term->edit.pos+1);
+ term->edit.pos = to.buffer;
+ term->edit.len -= diff;
+
+ diff = term->cursor.pos - to.cursor;
+ term->cursor.pos = to.cursor;
+ goto refresh;
+ }
+ // do nothing
+ return;
+
+refresh:
+ term->cursor.len -= diff;
+ refreshline(term);
+}
+/* movements */
+
+#define CURRENT(term) (Position){ .buffer=(term)->edit.pos, .cursor=(term)->cursor.pos };
+
+// move cursor to the left n boxes
+static
+Position
+left(struct TerminalState *term, int n)
+{
+ rune r;
+ int w, d;
+ Position pos = CURRENT(term);
+ char *buf = term->edit.buf + term->edit.pos;
+
+ d = 0;
+ while(n > 0 && buf > term->edit.buf){
+ buf -= utf8·decodeprev(buf-1, &r);
+
+ w = utf8·runewidth(r);
+ n -= w;
+ d += w;
+ }
+
+ pos.cursor = MAX(pos.cursor-d, 0);
+ pos.buffer = MAX(buf-term->edit.buf, 0);
+ return pos;
+}
+
+// move cursor to the right n boxes
+static
+Position
+right(struct TerminalState *term, int n)
+{
+ rune r;
+ int w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n > 0 && buf < end){
+ buf += utf8·decode(buf, &r);
+
+ w = utf8·runewidth(r);
+ n -= w;
+ d += w;
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+static
+Position
+prevword(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, w, b, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+
+ d = 0;
+ while(n-- > 0 && buf > term->edit.buf){
+ eatspace:
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+ if((c=runetype(r)) == Space){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ goto eatspace;
+ }
+
+ eatword:
+ if(runetype(r) == c){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MAX(pos.cursor-d, 0);
+ pos.buffer = MAX(buf-term->edit.buf, 0);
+ return pos;
+}
+
+static
+Position
+nextword(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf < end){
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ c = runetype(r);
+ eatword:
+ if(runetype(r) == c){
+ buf += b;
+ d += w;
+
+ if(buf >= end)
+ break;
+
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ goto eatword;
+ }
+ eatspace:
+ while((c=runetype(r)) == Space){
+ buf += b;
+ d += w;
+
+ if(buf >= end)
+ break;
+
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ goto eatspace;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+
+static
+Position
+prevWord(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, w, b, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+
+ d = 0;
+ while(n-- > 0 && buf > term->edit.buf){
+ eatspace:
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+ if((c=runetype(r)) == Space){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ goto eatspace;
+ }
+
+ eatword:
+ if((c=runetype(r)) != Space){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MAX(pos.cursor-d, 0);
+ pos.buffer = MAX(buf-term->edit.buf, 0);
+ return pos;
+}
+
+static
+Position
+nextWord(struct TerminalState *term, int n)
+{
+ rune r;
+ int b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf < end){
+ eatword:
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ if(runetype(r) != Space){
+ buf += b;
+ d += w;
+
+ if(buf > end)
+ break;
+
+ goto eatword;
+ }
+
+ eatspace:
+ if(runetype(r) == Space){
+ buf += b;
+ d += w;
+
+ if(buf > end)
+ break;
+
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+
+ goto eatspace;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+static
+Position
+nextend(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf+1 < end){
+ eatspace:
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+ while((c=runetype(r)) == Space){
+ buf += b;
+ d += w;
+
+ if(buf+1 >= end)
+ break;
+
+ goto eatspace;
+ }
+ eatword:
+ if(runetype(r) == c){
+ buf += b;
+ d += w;
+
+ if(buf+1 >= end)
+ break;
+
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+static
+Position
+nextEnd(struct TerminalState *term, int n)
+{
+ rune r;
+ int b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf+1 < end){
+ eatspace:
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+ if(runetype(r) == Space){
+ buf += b;
+ d += w;
+
+ if(buf+1 > end)
+ break;
+
+ goto eatspace;
+ }
+
+ eatword:
+ if(runetype(r) != Space){
+ buf += b;
+ d += w;
+
+ if(buf+1 > end)
+ break;
+
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+#define HOME(term) (Position){0}
+#define END(term) (Position){(term)->edit.len, (term)->cursor.len}
+
+static
+int
+vi(struct TerminalState *term, char c)
+{
+ int n = 1;
+ Verb verb = move;
+
+action:
+ switch(c){
+ /* # of repeats */
+ case '1': case '2': case '3':
+ case '4': case '5': case '6':
+ case '7': case '8': case '9':
+ n = 0;
+ while('0' <= c && c <= '9'){
+ n = 10*n + (c-'0');
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ }
+ goto action;
+
+ /* composable actions */
+ case 'l': verb(term, right(term, n)); break;
+ case 'h': verb(term, left(term, n)); break;
+ case '0': verb(term, HOME(term)); break;
+ case '$': verb(term, END(term)); break;
+ case 'b': verb(term, prevword(term,n)); break;
+ case 'B': verb(term, prevWord(term,n)); break;
+ case 'w': verb(term, nextword(term,n)); break;
+ case 'W': verb(term, nextWord(term,n)); break;
+ case 'e': verb(term, nextend(term,n)); break;
+ case 'E': verb(term, nextEnd(term,n)); break;
+
+ /* verb switches */
+ case 'd': // delete
+ verb = delete;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ /* special cases */
+ switch(c){
+ case 'd':
+ move(term, HOME(term));
+ delete(term, END(term));
+ return 0;
+ default:
+ goto action;
+ }
+ case 'y': // yank
+ verb = yank;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ /* special cases */
+ switch(c){
+ case 'y':
+ if(term->yank.cap < term->edit.len+1){
+ efree(term->yank.buf);
+ term->yank.len = term->edit.len;
+ term->yank.cap = term->edit.len+1;
+ term->yank.buf = emalloc(term->yank.cap);
+ }
+ memcpy(term->yank.buf, term->edit.buf, term->edit.len+1);
+ break;
+ default:
+ goto action;
+ }
+ break;
+
+ case 'p': // put
+ insertbytes(term, term->yank.len, term->yank.buf);
+ refreshline(term);
+ return 0;
+
+ /* special cases
+ * sadly I don't know a better way than to have these checks for move
+ * the vi language doesn't fully compose
+ */
+ case 'i': insertmode:
+ if(verb != move) goto unrecognized;
+ insertmode(term->ofd);
+ break;
+
+ case 'I':
+ if(verb != move) goto unrecognized;
+ move(term, HOME(term));
+ goto insertmode;
+
+ case 'a':
+ if(verb != move) goto unrecognized;
+ if(term->edit.pos < term->edit.len){
+ term->edit.pos++;
+ refreshline(term);
+ }
+ goto insertmode;
+
+ case 'A':
+ if(verb != move) goto unrecognized;
+ move(term, END(term));
+ goto insertmode;
+
+ case 'x':
+ if(verb != move) goto unrecognized;
+ delete(term, right(term, 1));
+ break;
+
+ case 'X':
+ if(verb != move) goto unrecognized;
+ delete(term, left(term, 1));
+ break;
+
+ case 'r':
+ if(verb != move) goto unrecognized;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ if(c < ' ')
+ break;
+ term->edit.buf[term->edit.pos] = c;
+ refreshline(term);
+ break;
+
+ // TODO: replace mode?
+
+ case 'c':
+ if(verb != move) goto unrecognized;
+ insertmode(term->ofd);
+ verb = delete;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ goto action;
+
+ case 'C':
+ if(verb != move) goto unrecognized;
+ insertmode(term->ofd);
+ goto deleteln;
+
+ case 'D':
+ if(verb != move) goto unrecognized;
+ deleteln:
+ term->edit.len = term->edit.pos;
+ term->edit.buf[term->edit.pos] = 0;
+ refreshline(term);
+ break;
+
+ default: unrecognized:
+ beep();
+ break;
+ }
+
+ return 0;
+}
+#undef END
+
+#define END(term) (Position){(term).edit.len, (term).cursor.len}
+
+static
+int
+size(char *s)
+{
+ rune c;
+ int n, len = 0;;
+ while((c=*s)){
+ if(c == '\033'){
+ n = 1;
+ esccode:
+ c = s[n];
+ if(!c) // we hit end of string in the middle of parsing an escape code!
+ return len;
+ if(c == 'm'){
+ s += n + 1;
+ continue; // outer loop
+ }
+ n++;
+ goto esccode;
+ }
+ n = utf8·decode(s, &c);
+ s += n;
+ len += utf8·runewidth(c);
+ }
+ return len;
+}
+
+/* this function is the core of the line editing capability of linenoise.
+ * it expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned asap to read().
+ *
+ * the resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * the function returns the length of the current buffer. */
+static
+int
+interact(int ifd, int ofd, char *buf, intptr len, char *prompt)
+{
+ int n, aux;
+ char esc[3];
+ char c[UTFmax+1] = { 0 };
+ rune r;
+
+ struct TerminalState term;
+ /*
+ * populate the state that we pass to functions implementing
+ * specific editing functionalities
+ */
+ term.ifd = ifd;
+ term.ofd = ofd;
+
+ term.edit.buf = buf;
+ term.edit.cap = len;
+ term.edit.len = 0;
+ term.edit.pos = 0;
+
+ term.prompt.s = prompt;
+ term.prompt.len = strlen(prompt);
+ term.prompt.size = size(prompt);
+
+ term.cursor.pos = 0;
+ term.cursor.len = 0;
+ term.cursor.cap = columns(ifd, ofd);
+
+ term.maxrows = 0;
+ term.history = 0;
+
+ term.yank.buf = nil;
+ term.yank.cap = term.yank.len = 0;
+
+ /* buffer starts empty. */
+ term.edit.buf[0] = '\0';
+ term.edit.cap--; /* make sure there is always space for the nulterm */
+
+ /* push current (empty) command onto history stack */
+ addhistory("");
+
+ if(write(term.ofd,prompt,term.prompt.len) == -1)
+ return -1;
+
+ for(;;){
+ n = read(term.ifd,c,1);
+ if(n <= 0)
+ goto finish;
+
+ /* partition input by rune */
+ if(utf8·onebyte(c[0])){
+ r = c[0];
+ }else if(utf8·twobyte(c[0])){
+ n = read(term.ifd,c+1,1);
+ if(n < 1 || (n=utf8·decode(c, &r)) != 2)
+ goto finish;
+ }else if(utf8·threebyte(c[0])){
+ n = read(term.ifd,c+1,2);
+ if(n < 2 || (n=utf8·decode(c, &r)) != 3)
+ goto finish;
+ }else if(utf8·fourbyte(c[0])){
+ n = read(term.ifd,c+1,3);
+ if(n < 3 || (n=utf8·decode(c, &r)) != 4)
+ goto finish;
+ }else
+ goto finish;
+
+ switch(r){
+ case KeyEnter:
+ pophistory();
+ if(mode.multiline)
+ move(&term, END(term));
+ goto finish;
+
+ case KeyCtrlC:
+ errno = EAGAIN;
+ return -1;
+
+ case KeyBackspace:
+ case KeyCtrlH:
+ delete(&term, left(&term, 1));
+ break;
+
+ case KeyCtrlD:
+ if(term.edit.len > 0)
+ delete(&term, right(&term, 1));
+ break;
+
+ case KeyCtrlT:
+ if(term.edit.pos > 0 && term.edit.pos < term.edit.len){
+ aux = buf[term.edit.pos-1];
+
+ buf[term.edit.pos-1] = buf[term.edit.pos];
+ buf[term.edit.pos] = aux;
+
+ if(term.edit.pos != term.edit.len-1)
+ term.edit.pos++;
+
+ refreshline(&term);
+ }
+ break;
+
+ case KeyCtrlB:
+ move(&term, left(&term, 1));
+ break;
+
+ case KeyCtrlF: /* ctrl-f */
+ move(&term, right(&term, 1));
+ break;
+
+ case KeyCtrlP: /* ctrl-p */
+ usehistory(&term, +1);
+ break;
+
+ case KeyCtrlN: /* ctrl-n */
+ usehistory(&term, -1);
+ break;
+
+ case KeyEsc: /* escape sequence */
+ /*
+ * try to read two bytes representing the escape sequence.
+ * if we read less than 2 and we are in vi mode, interpret as command
+ *
+ * NOTE: we could do a timed read here
+ */
+ switch(read(term.ifd,esc,2)){
+ case 0:
+ if(mode.vi.on){
+ if(mode.vi.insert){
+ normalmode(term.ofd);
+ if(term.edit.pos > 0){
+ --term.edit.pos;
+ refreshline(&term);
+ }
+ continue;
+ }
+ }
+ case 1:
+ if(mode.vi.on){
+ if(mode.vi.insert){
+ normalmode(term.ofd);
+ if(vi(&term,esc[0]) < 0){
+ term.edit.len = -1;
+ goto finish;
+ }
+ continue;
+ }
+ }
+ default: // 2
+ ;
+ }
+
+ /* ESC [ sequences. */
+ if(esc[0] == '['){
+ if(0 <= esc[1] && esc[1] <= '9'){
+ /* extended escape, read additional byte. */
+ if(read(term.ifd,esc+2,1) == -1)
+ break;
+
+ if(esc[2] == '~'){
+ switch(esc[1]){
+ case '3': /* delete key. */
+ delete(&term, left(&term,1));
+ break;
+ }
+ }
+ }else{
+ switch(esc[1]) {
+ case 'A': /* up */
+ usehistory(&term, +1);
+ break;
+ case 'B': /* down */
+ usehistory(&term, -1);
+ break;
+ case 'C': /* right */
+ move(&term, right(&term, 1));
+ break;
+ case 'D': /* left */
+ move(&term, left(&term, 1));
+ break;
+ case 'H': /* home */
+ move(&term, HOME(term));
+ break;
+ case 'F': /* end*/
+ move(&term, END(term));
+ break;
+ }
+ }
+ }
+ /* ESC O sequences. */
+ else if(esc[0] == 'O'){
+ switch(esc[1]) {
+ case 'H': /* home */
+ move(&term, HOME(term));
+ break;
+ case 'F': /* end*/
+ move(&term, END(term));
+ break;
+ }
+ }
+ break;
+
+ default:
+ if(mode.vi.on && !mode.vi.insert && n == 1){
+ if(vi(&term,c[0]) < 0){
+ term.edit.len = -1;
+ goto finish;
+ }
+ }else if(!insertrune(&term,n,c)){
+ term.edit.len = -1;
+ goto finish;
+ }
+
+ break;
+
+ case KeyCtrlU: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ term.edit.pos = term.edit.len = 0;
+ term.cursor.pos = term.cursor.len = 0;
+ refreshline(&term);
+ break;
+
+ case KeyCtrlK: /* Ctrl+k, delete from current to end of line. */
+ buf[term.edit.pos] = '\0';
+ term.edit.len = term.edit.pos;
+ term.cursor.len = term.cursor.pos;
+ refreshline(&term);
+ break;
+
+ case KeyCtrlA: /* Ctrl+a, go to the start of the line */
+ move(&term, HOME(term));
+ break;
+
+ case KeyCtrlE: /* ctrl+e, go to the end of the line */
+ move(&term, END(term));
+ break;
+
+ case KeyCtrlL: /* ctrl+term, clear screen */
+ clear();
+ refreshline(&term);
+ break;
+
+ case KeyCtrlW: /* ctrl+w, delete previous word */
+ delete(&term, prevword(&term,1));
+ break;
+ }
+ }
+finish:
+ efree(term.yank.buf);
+ return term.edit.len;
+}
+
+/*
+ * this special mode is used by linenoise in order to print scan codes
+ * on screen for debugging / development purposes. It is implemented
+ * by the linenoise_example program using the --keycodes option.
+ */
+void
+printkeycode(void)
+{
+ int n;
+ char c, quit[4];
+
+ printf("entering debugging mode. printing key codes.\n"
+ "press keys to see scan codes. type 'quit' at any time to exit.\n");
+
+ if(!enterraw(0))
+ return;
+
+ memset(quit,' ',4);
+
+ for(;;){
+ n = read(0,&c,1);
+ if(n <= 0)
+ continue;
+ memmove(quit,quit+1,sizeof(quit)-1); // shift string to left
+ quit[arrlen(quit)-1] = c; /* Insert current char on the right. */
+
+ if(memcmp(quit,"quit",sizeof(quit)) == 0)
+ break;
+
+ printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c, (int)c);
+ printf("\r"); /* go to left edge manually, we are in raw mode. */
+ fflush(stdout);
+ }
+ exitraw(0);
+}
+
+/*
+ * this function calls the line editing function edit() using the stdin set in raw mode
+ */
+static
+int
+raw(char *buf, intptr len, char *prompt)
+{
+ int n;
+
+ if(!len){
+ errno = EINVAL;
+ return -1;
+ }
+
+ // XXX: should we not hardcode stdin and stdout fd?
+ if(!enterraw(0)) return -1;
+ n = interact(0, 1, buf, len, prompt);
+ exitraw(0);
+
+ return n;
+}
+
+/*
+ * called when readline() is called with the standard
+ * input file descriptor not attached to a TTY. For example when the
+ * program is called in pipe or with a file redirected to its standard input
+ * in this case, we want to be able to return the line regardless of its length
+ */
+static
+int
+notty(void)
+{
+ int c;
+
+ for(;;){
+ c = fgetc(stdin);
+ put(&runner->cmd.io, c);
+ }
+}
+
+void
+enablevi(void)
+{
+ mode.vi.on = 1;
+ insertmode(1);
+}
+
+/*
+ * The high level function that is the main API.
+ * This function checks if the terminal has basic capabilities and later
+ * either calls the line editing function or uses dummy fgets() so that
+ * you will be able to type something even in the most desperate of the
+ * conditions.
+ */
+int
+readline(char *prompt)
+{
+ int n;
+
+ // reset the command buffer
+ runner->cmd.io->e = runner->cmd.io->b = runner->cmd.io->buf;
+
+ if(!shell.interactive)
+ return notty();
+
+ if((n = raw(runner->cmd.io->e, runner->cmd.io->cap-1, prompt)) == -1)
+ return 0;
+ runner->cmd.io->e += n;
+
+ /* insert a newline character at the end */
+ put(&runner->cmd.io, '\n');
+
+ return 1;
+}
+
+/* At exit we'll try to fix the terminal to the initial conditions. */
+static
+void
+doatexit(void)
+{
+ exitraw(0);
+ normalcursor(1);
+}
diff --git a/src/cmd/rc/io.c b/src/cmd/rc/io.c
new file mode 100644
index 0000000..dc81c2e
--- /dev/null
+++ b/src/cmd/rc/io.c
@@ -0,0 +1,437 @@
+#include "rc.h"
+#include "parse.h"
+
+#define CAP0 512
+
+Io*
+openfd(int fd)
+{
+ Io *io = emalloc(sizeof(*io) + CAP0);
+
+ io->fd = fd;
+ io->cap = CAP0;
+ io->b = io->e = io->buf;
+ io->s = nil;
+
+ return io;
+}
+
+Io*
+openstr(void)
+{
+ char *s;
+ Io *io = emalloc(sizeof(*io) + CAP0);
+
+ io->fd = -1;
+ io->cap = CAP0;
+ io->b = io->s = emalloc(101);
+ io->e = io->b+100;
+
+ for(s = io->b; s<=io->e; s++)
+ *s=0;
+
+ return io;
+}
+
+#if 0
+/*
+ * open a corebuffer to read. EOF occurs after reading len characters from buf
+ */
+
+Io*
+opencore(char *s, int len)
+{
+ Io *io = emalloc(sizeof(*io));
+ char *buf = emalloc(len);
+ io->fd = -1 /*open("/dev/null", 0)*/;
+ io->b = io->s = buf;
+ io->e = buf+len;
+ memcpy(buf, s, len);
+
+ return io;
+}
+#endif
+
+void
+iorewind(Io *io)
+{
+ if(io->fd==-1)
+ io->b = io->s;
+ else{
+ io->b = io->e = io->buf;
+ lseek(io->fd, 0L, 0);
+ }
+}
+
+void
+terminate(Io *io)
+{
+ if(io->fd>=0)
+ close(io->fd);
+ if(io->s)
+ efree(io->s);
+
+ efree((char *)io);
+}
+
+static
+int
+refill(Io *io)
+{
+ int n;
+
+ if(io->fd==-1 || (n = read(io->fd, io->buf, io->cap))<=0)
+ return EOF;
+
+ io->b = io->buf;
+ io->e = io->buf+n;
+
+ return *io->b++&0xff;
+}
+
+
+void
+flush(Io *io)
+{
+ int n;
+ char *s;
+
+ if(io->s){
+ n = io->e-io->s;
+ io->s = realloc(io->s, n+101);
+ if(io->s==0)
+ panicf("Can't realloc %d bytes in flush!", n+101);
+ io->b = io->s+n;
+ io->e = io->b+100;
+ for(s = io->b;s<=io->e;s++) *s='\0';
+ }else{
+ n = io->b-io->buf;
+ if(n && write(io->fd, io->buf, n) < 0)
+ write(3, "write error\n", 12);
+ io->b = io->buf;
+ io->e = io->buf + io->cap;
+ }
+}
+
+
+static
+void
+printchar(Io *io, int c)
+{
+ if(io->b==io->e)
+ flush(io);
+
+ *io->b++=c;
+}
+
+void
+printquote(Io *io, char *s)
+{
+ printchar(io, '\'');
+ for(;*s;s++)
+ if(*s=='\'')
+ print(io, "''");
+ else printchar(io, *s);
+ printchar(io, '\'');
+}
+
+void
+printstr(Io *io, char *s)
+{
+ if(s==0)
+ s="(null)";
+ while(*s) printchar(io, *s++);
+}
+
+void
+printword(Io *io, char *s)
+{
+ char *t;
+
+ for(t = s;*t;t++)
+ if(!iswordchar(*t))
+ break;
+
+ if(t==s || *t)
+ printquote(io, s);
+ else
+ printstr(io, s);
+}
+
+void
+printptr(Io *io, void *v)
+{
+ int n;
+ uintptr p;
+
+ p = (uintptr)v;
+ if(sizeof(uintptr) == sizeof(uvlong) && p>>32)
+ for(n = 60;n>=32;n-=4) printchar(io, "0123456789ABCDEF"[(p>>n)&0xF]);
+
+ for(n = 28;n>=0;n-=4) printchar(io, "0123456789ABCDEF"[(p>>n)&0xF]);
+}
+
+static
+void
+printint(Io *io, int n)
+{
+ if(n<0){
+ if(n!=INT_MIN){
+ printchar(io, '-');
+ printint(io, -n);
+ return;
+ }
+ /* n is two's complement minimum integer */
+ n = -(INT_MIN+1);
+ printchar(io, '-');
+ printint(io, n/10);
+ printchar(io, n%10+'1');
+ return;
+ }
+ if(n>9)
+ printint(io, n/10);
+ printchar(io, n%10+'0');
+}
+
+static
+void
+printoct(Io *io, unsigned n)
+{
+ if(n>7)
+ printoct(io, n>>3);
+ printchar(io, (n&7)+'0');
+}
+
+static
+void
+printval(Io *io, Word *a)
+{
+ if(a){
+ while(a->link && a->link->str){
+ printword(io, a->str);
+ printchar(io, ' ');
+ a = a->link;
+ }
+ printword(io, a->str);
+ }
+}
+
+#define C0 t->child[0]
+#define C1 t->child[1]
+#define C2 t->child[2]
+
+static
+void
+printtree(Io *io, Tree *t)
+{
+ if(!t)
+ return;
+
+ switch(t->type){
+ default: print(io, "bad(%d)[%p %p %p]", t->type, C0, C1, C2); break;
+ case '$': print(io,"$%t",C0); break;
+ case '&': print(io,"%t&",C0); break;
+ case '^': print(io,"%t^%t",C0,C1); break;
+ case '`': print(io,"`%t",C0); break;
+
+ case Tbasic: print(io, "%t", C0); break;
+ case Tbang: print(io, "!%t", C0); break;
+ case Tblock: print(io, "{%t}", C0); break;
+ case Tcount: print(io, "$#%t", C0); break;
+ case Tparen: print(io, "(%t)", C0); break;
+ case Tjoin: print(io,"$\"%t",C0); break;
+ case Tindex: print(io, "%t(%t)",C0); break;
+ case Tsubshell: print(io, "@ %t",C0); break;
+ //case Ttwiddle: print(io, "~ %t %t", C0, C1); break;
+
+ case Toror:
+ case Tandand:
+
+ case Targs:
+ if(!C0)
+ print(io, "%t", C1);
+ else if(!C1)
+ print(io, "%t", C0);
+ else
+ print(io, "%t %t", C0, C1);
+ break;
+
+ case ';':
+ if(C0){
+ if(C1)
+ print(io, "%t;%t", C0, C1);
+ else
+ print(io, "%t", C0);
+ }else
+ print(io, "%t", C1);
+ break;
+
+ case Twords:
+ if(C0)
+ print(io, "%t", C0);
+ print(io, "%t", C1);
+
+ case Tword:
+ if(t->quoted)
+ print(io, "%Q", t->str);
+ print(io, "%q", t->str);
+ break;
+
+ case '=':
+ print(io, "%t=%t", C0, C1);
+ if(C2)
+ print(io, " %t", C2);
+ break;
+
+ case Tdup:
+ if(t->redir.type == Rdupfd)
+ print(io, ">[%d=%d]", t->redir.fd[1], t->redir.fd[0]);
+ else
+ print(io, ">[%d=]", t->redir.fd[0]);
+ print(io, "%t", C1);
+ break;
+
+ case Tredir:
+ switch(t->redir.type){
+ case Rhere:
+ printchar(io, '<');
+ case Rread:
+ printchar(io, '<');
+ goto readfd;
+ case Rrdwr:
+ printchar(io, '<');
+ printchar(io, '>');
+ readfd:
+ if(t->redir.fd[0]!=0)
+ print(io, "[%d]", t->redir.fd[0]);
+ break;
+ case Rappend:
+ printchar(io, '>');
+ goto writefd;
+ case Rwrite:
+ printchar(io, '>');
+ printchar(io, '>');
+ writefd:
+ if(t->redir.fd[0]!=1)
+ print(io, "[%d]", t->redir.fd[0]);
+ break;
+ }
+ print(io, "%t", C0);
+ if(C1)
+ print(io, " %t", C1);
+ break;
+
+ case Tpipe:
+ print(io, "%t|", C0);
+ if(t->redir.fd[1]==0){
+ if(t->redir.fd[0]!=1)
+ print(io, "[%d]", t->redir.fd[0]);
+ }
+ else
+ print(io, "[%d=%d]", t->redir.fd[0], t->redir.fd[1]);
+ print(io, "%t", C1);
+ break;
+ }
+}
+
+#undef C0
+#undef C1
+#undef C2
+
+// -----------------------------------------------------------------------
+// exports
+
+/* readers */
+int
+get(Io *io)
+{
+ if(io->b==io->e)
+ return refill(io);
+
+ return *io->b++ & 0xFF;
+}
+
+/* writers */
+int
+put(Io **iop, char c)
+{
+ int nb, ne, nc;
+ Io *io = *iop;
+ char *e = io->b + io->cap;
+
+ if(io->e == e){
+ nb = io->b - io->buf;
+ ne = io->e - io->buf;
+ nc = 2*io->cap;
+
+ if(!(io = erealloc(io, sizeof(*io)+nc)))
+ return 0;
+
+ io->b = io->buf + nb;
+ io->e = io->buf + ne;
+ io->cap = nc;
+
+ *iop = io;
+ }
+
+ *io->e++ = c;
+ return 1;
+}
+
+/* printers */
+static int pfmtnest;
+
+void
+print(Io *io, char *fmt, ...)
+{
+ va_list args;
+ char err[ERRMAX];
+
+ va_start(args, fmt);
+ pfmtnest++;
+
+ for(;*fmt;fmt++)
+ if(*fmt!='%')
+ printchar(io, *fmt);
+ else
+ switch(*++fmt){
+ case '\0':
+ va_end(args);
+ return;
+ case 'c':
+ printchar(io, va_arg(args, int));
+ break;
+ case 'd':
+ printint(io, va_arg(args, int));
+ break;
+ case 'o':
+ printoct(io, va_arg(args, unsigned));
+ break;
+ case 'p':
+ printptr(io, va_arg(args, void*));
+ break;
+ case 'Q':
+ printquote(io, va_arg(args, char *));
+ break;
+ case 'q':
+ printword(io, va_arg(args, char *));
+ break;
+ case 's':
+ printstr(io, va_arg(args, char *));
+ break;
+ case 't':
+ printtree(io, va_arg(args, struct Tree *));
+ break;
+ case 'v':
+ printval(io, va_arg(args, struct Word *));
+ break;
+ default:
+ printchar(io, *fmt);
+ break;
+ }
+
+ va_end(args);
+
+ if(--pfmtnest==0)
+ flush(io);
+}
diff --git a/src/cmd/rc/job.c b/src/cmd/rc/job.c
new file mode 100644
index 0000000..1587951
--- /dev/null
+++ b/src/cmd/rc/job.c
@@ -0,0 +1,91 @@
+#include "rc.h"
+
+#include <signal.h>
+#include <termios.h>
+
+// -----------------------------------------------------------------------
+// exports
+
+Thread *
+getjob(int pid, int *index)
+{
+ int i;
+ Thread *job;
+ for(i=0,job=shell.jobs; job && job->pid != pid; i++, job=job->link)
+ ;
+
+ return job;
+}
+
+void
+report(Thread *job, int index)
+{
+ switch(job->wait.status){
+ case Pdone:
+ print(shell.err, "job %d [%d]: done\n", index, job->pid);
+ break;
+ case Pstop:
+ print(shell.err, "job %d [%d]: suspended\n", index, job->pid);
+ break;
+ case Pagain:
+ print(shell.err, "job %d [%d]: continued\n", index, job->pid);
+ break;
+ case Prun:
+ print(shell.err, "job %d [%d]: running\n", index, job->pid);
+ break;
+ default:
+ fatal("bad wait status: %d\n", job->wait.status);
+ }
+}
+
+void
+wakeup(Thread *job)
+{
+ int i;
+ job->wait.status = Prun;
+ for(i=0; i < job->wait.len; i++){
+ if(job->wait.on[i].status == Pstop)
+ job->wait.on[i].status = Prun;
+ }
+
+ tcsetpgrp(0, job->pgid);
+}
+
+void
+foreground(Thread *job, int now)
+{
+ Thread *caller = job->caller;
+ if(now){
+ if(kill(-job->pgid, SIGCONT) < 0)
+ perror("kill[SIGCONT]");
+ }
+
+ waitall(job);
+ /*
+ * reset state if we have a caller
+ * otherwise we will exit anyways
+ */
+ if(caller && caller->flag.user){
+ tcsetpgrp(0, caller->pid);
+ job->flag.user = 1;
+ }
+}
+
+void
+addjob(Thread *job)
+{
+ job->link = shell.jobs;
+ shell.jobs = job;
+ job->wait.status = Prun;
+}
+
+void
+deljob(Thread *job)
+{
+ Thread **jp;
+
+ for(jp = &shell.jobs; *jp && *jp != job; jp = &(*jp)->link)
+ ;
+
+ *jp = job->link;
+}
diff --git a/src/cmd/rc/lex.c b/src/cmd/rc/lex.c
new file mode 100644
index 0000000..9ca2453
--- /dev/null
+++ b/src/cmd/rc/lex.c
@@ -0,0 +1,394 @@
+#include "rc.h"
+#include "parse.h"
+
+static int advance(void);
+
+// -----------------------------------------------------------------------
+// lexer
+
+struct Lexer
+{
+ int c[2];
+ ushort doprompt;
+ ushort hadword;
+ ushort haddollar;
+ ushort inquote;
+ char buf[BUFSIZ];
+};
+
+static struct Lexer lexer = { .c={0, EOF}, .doprompt=1 };
+
+#define put1(b) lexer.buf[0] = (b), lexer.buf[1] = 0;
+#define put2(b0,b1) lexer.buf[0] = (b0), lexer.buf[1] = (b1), lexer.buf[2] = 0;
+#define put3(b0,b1,b2) lexer.buf[0] = (b0), lexer.buf[1] = (b1), lexer.buf[2] = b2, lexer.buf[3] = 0;
+
+void
+yyerror(const char *msg)
+{
+ print(shell.err, "rc:%d: ", runner->line);
+
+ if(lexer.buf[0] && lexer.buf[0]!='\n')
+ print(shell.err, "%q: ", lexer.buf);
+
+ print(shell.err, "%s\n", msg);
+ flush(shell.err);
+
+ lexer.hadword = 0;
+ lexer.haddollar = 0;
+
+ /* consume remaining tokens */
+ while(lexer.c[0] !='\n' && lexer.c[0] != EOF)
+ advance();
+}
+
+int
+readc(void)
+{
+ int c;
+ static int peek = EOF;
+
+ if(peek!=EOF){
+ c = peek;
+ peek = EOF;
+ return c;
+ }
+
+ if(runner->flag.eof)
+ return EOF;
+
+ if(!prompt(&lexer.doprompt))
+ exit(1); // XXX: hack for signal handling right now...
+
+ c = get(runner->cmd.io);
+ lexer.doprompt = lexer.doprompt || c=='\n' || c==EOF;
+
+ if(c==EOF)
+ runner->flag.eof = 1;
+
+ return c;
+}
+
+static
+int
+peekc(void)
+{
+ if(lexer.c[1] == EOF)
+ lexer.c[1] = readc();
+
+ return lexer.c[1];
+}
+
+static
+int
+advance(void)
+{
+ int c = peekc();
+ lexer.c[0] = lexer.c[1], lexer.c[1] = EOF;
+
+ return c;
+}
+
+static
+void
+skipws(void)
+{
+ int c;
+ for(;;){
+ c = peekc();
+ if(c== ' ' || c == '\t')
+ advance();
+ else
+ return;
+ }
+}
+
+static
+void
+skipnl(void)
+{
+ int c;
+ for(;;){
+ c = peekc();
+ if(c== ' ' || c == '\t' || c == '\n')
+ advance();
+ else
+ return;
+ }
+}
+
+static
+int
+nextis(int c)
+{
+ if(peekc()==c){
+ advance();
+ return 1;
+ }
+ return 0;
+}
+
+static
+char *
+putbyte(char *buf, int c)
+{
+ if(!buf)
+ return buf;
+
+ if(buf == arrend(lexer.buf)){
+ fatal("lexer: out of buffer space");
+ return nil;
+ }
+ *buf++ = c;
+ return buf;
+}
+
+static
+char *
+putrune(char *buf, int c)
+{
+ buf = putbyte(buf, c);
+ if(utf8·onebyte(c))
+ return buf;
+ if(utf8·twobyte(c))
+ return putbyte(buf,advance());
+ if(utf8·threebyte(c)){
+ buf = putbyte(buf,advance());
+ return putbyte(buf,advance());
+ }
+ if(utf8·fourbyte(c)){
+ buf = putbyte(buf,advance());
+ buf = putbyte(buf,advance());
+ return putbyte(buf,advance());
+ }
+ fatal("malformed utf8 stream");
+
+ return nil;
+}
+
+// -----------------------------------------------------------------------
+// exported functions
+
+// TODO: turn into static tables
+int
+iswordchar(int c)
+{
+ return !strchr("\n \t#;&|^$=`'{}()<>", c) && c!=EOF;
+}
+
+int
+isidentchar(int c)
+{
+ return c>' ' && !strchr("!\"#$%&'()+,-./:;<=>?@[\\]^`{|}~", c);
+}
+
+int
+yylex(void)
+{
+ int c, d = peekc();
+ Tree *node;
+ char *w = lexer.buf;
+
+ yylval.tree = nil;
+
+ /* inject tokens */
+ if(lexer.hadword){
+ lexer.hadword = 0;
+ if(d=='('){
+ advance();
+ strcpy(lexer.buf, "( [Tindex]");
+ return Tindex;
+ }
+ if(iswordchar(d) || d=='\'' || d=='`' || d=='$' || d=='"'){
+ strcpy(lexer.buf, "^");
+ return '^';
+ }
+ }
+
+ lexer.inquote = 0;
+
+ skipws();
+ switch(c=advance()){
+ case EOF:
+ lexer.haddollar = 0;
+ put3('E','O','F');
+ return EOF;
+
+ case '$':
+ lexer.haddollar = 1;
+ if(nextis('#')){
+ put2('$','#');
+ return Tcount;
+ }
+ if(nextis('^')){
+ put2('$','^');
+ return Tjoin;
+ }
+ put1('$');
+ return '$';
+
+ case '@':
+ lexer.haddollar = 0;
+ put1('@');
+ return Tsubshell;
+
+ case '!':
+ lexer.haddollar = 0;
+ put1('!');
+ return Tbang;
+
+ case '&':
+ lexer.haddollar = 0;
+ if(nextis('&')){
+ put2('&','&');
+ return Tandand;
+ }
+ put1('&');
+ return '&';
+
+ case '|':
+ lexer.haddollar = 0;
+ if(nextis('|')){
+ put2('|','|');
+ return Toror;
+ }
+ node = maketree();
+ *w++ = '|';
+
+ node->type = Tpipe;
+ node->redir.fd[0] = 1;
+ node->redir.fd[1] = 0;
+ goto redir;
+
+ case '>':
+ lexer.haddollar = 0;
+ node = maketree();
+ *w++ = '>';
+ node->type = Tredir;
+
+ if(nextis('>')){
+ node->redir.type = Rappend;
+ *w++ = '>';
+ }else
+ node->redir.type = Rwrite;
+ node->redir.fd[0] = 1;
+ goto redir;
+
+ case '<':
+ lexer.haddollar = 0;
+ node = maketree();
+ *w++ = '<';
+ node->type = Tredir;
+
+ if(nextis('<')){
+ node->redir.type = Rhere;
+ *w++ = '<';
+ }else if(nextis('>')){
+ node->redir.type = Rrdwr;
+ *w++ = '>';
+ }else{
+ node->redir.type = Rread;
+ }
+ node->redir.fd[0] = 0;
+ /* fallthrough */
+ redir:
+ if(nextis('[')){
+ *w++='[';
+ c = advance();
+ *w++ = c;
+ if(c < '0' || '9' < c){
+ badredir:
+ *w = 0;
+ yyerror(node->type == Tpipe ? "pipe syntax" : "redirection syntax");
+ return EOF;
+ }
+ node->redir.fd[0] = 0;
+ do{
+ node->redir.fd[0] = 10*node->redir.fd[0]+(c-'0');
+ *w++ = c;
+ c = advance();
+ }while('0'<=c && c<='9');
+
+ if(c == '='){
+ *w++ = '=';
+ if(node->type==Tredir)
+ node->type = Tdup;
+ c = advance();
+ }
+ if(c < '0' || '9' < c){
+ if(node->type == Tpipe)
+ goto badredir;
+ node->redir.type = Rclose;
+ }else{
+ node->redir.type = Rdupfd;
+ node->redir.fd[1] = node->redir.fd[0];
+ node->redir.fd[0] = 0;
+ do{
+ node->redir.fd[0] = 10*node->redir.fd[0]+(c-'0');
+ *w++ = c;
+ c = advance();
+ }while('0'<=c && c<='9');
+ }
+ if(c != ']' || (node->type == Tdup && (node->redir.type = Rhere || node->redir.type == Rappend)))
+ goto badredir;
+ *w++ = ']';
+ }
+ *w++ = 0;
+ yylval.tree = node;
+
+ return node->type;
+
+ case '\'':
+ lexer.hadword = 1;
+ lexer.inquote = 1;
+ lexer.haddollar = 0;
+ for(;;){
+ c = advance();
+ if(c==EOF)
+ break;
+
+ if(c=='\''){
+ if(peekc()!='\'')
+ break;
+ advance();
+ }
+ w = putrune(w, c);
+ }
+ if(w)
+ *w = 0;
+ node = token(Tword, lexer.buf);
+ node->quoted = 1;
+ return node->type;
+
+ default:
+ ;
+ }
+ if(!iswordchar(c)){
+ put1(c);
+ lexer.haddollar = 0;
+ return c;
+ }
+
+ for(;;){
+ w = putrune(w, c);
+ c = peekc();
+ if(lexer.haddollar ? !isidentchar(c) : !iswordchar(c))
+ break;
+ advance();
+ }
+
+ lexer.hadword = 1;
+ lexer.haddollar = 0;
+ if(w)
+ *w = 0;
+
+ node = token(Tword, lexer.buf);
+ if((c=iskeyword(lexer.buf))){
+ node->type = c;
+ lexer.hadword = 0;
+ }
+
+ node->quoted = 0;
+
+ yylval.tree = node;
+ return node->type;
+}
diff --git a/src/cmd/rc/main.c b/src/cmd/rc/main.c
new file mode 100644
index 0000000..2c0aa42
--- /dev/null
+++ b/src/cmd/rc/main.c
@@ -0,0 +1,66 @@
+#include "rc.h"
+#include "parse.h"
+#include "exec.h"
+
+#include <signal.h>
+#include <termios.h>
+
+// -----------------------------------------------------------------------
+// globals
+
+Thread *runner = nil;
+Shell shell = { 0 };
+
+// -----------------------------------------------------------------------
+// functions
+
+void
+initshell(void)
+{
+ if((shell.interactive=isatty(0))){
+ while(tcgetpgrp(0) != (shell.pid = getpgrp()))
+ kill(-shell.pid, SIGTTIN);
+
+ /* ignore job control signals */
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTTOU, SIG_IGN);
+ /*
+ * NOTE: if SIGCHLD is set to SIG_IGN then
+ * 1. children that terminate do not become zombies
+ * 2. call a to wait() will block until all children have terminated
+ * 3. the call to wait will fail with errno == ECHILD
+ * see for discussion:
+ * https://stackoverflow.com/questions/1608017/no-child-process-error-from-waitpid-when-waiting-for-process-group
+ */
+ // signal(SIGCHLD, SIG_IGN);
+
+ /* take control */
+ shell.pid = getpid();
+ if(setpgid(shell.pid, shell.pid)<0)
+ fatal("could not put shell in its own process group");
+
+ tcsetpgrp(shell.pid, shell.pid);
+ }
+}
+
+// -----------------------------------------------------------------------
+// main point of entry
+
+int
+main(int argc, char *argv[])
+{
+ shell.err = openfd(2);
+
+ initenv();
+ initpath();
+ initkeywords();
+ initshell();
+ inithistory();
+
+ enablevi();
+ xboot(argc, argv);
+ /* unreachable */
+}
diff --git a/src/cmd/rc/parse.c b/src/cmd/rc/parse.c
new file mode 100644
index 0000000..1b29d41
--- /dev/null
+++ b/src/cmd/rc/parse.c
@@ -0,0 +1,2059 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30802
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.8.2"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* First part of user prologue. */
+#line 7 "sys/cmd/rc/syntax.y"
+
+ #include "rc.h"
+
+ int yylex(void);
+ void yyerror(const char *);
+
+#line 78 "sys/cmd/rc/parse.c"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+#include "parse.h"
+/* Symbol kind. */
+enum yysymbol_kind_t
+{
+ YYSYMBOL_YYEMPTY = -2,
+ YYSYMBOL_YYEOF = 0, /* "end of file" */
+ YYSYMBOL_YYerror = 1, /* error */
+ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */
+ YYSYMBOL_Tfor = 3, /* Tfor */
+ YYSYMBOL_Tin = 4, /* Tin */
+ YYSYMBOL_Twhile = 5, /* Twhile */
+ YYSYMBOL_Tif = 6, /* Tif */
+ YYSYMBOL_Telse = 7, /* Telse */
+ YYSYMBOL_Tswitch = 8, /* Tswitch */
+ YYSYMBOL_Tcase = 9, /* Tcase */
+ YYSYMBOL_Tcasebody = 10, /* Tcasebody */
+ YYSYMBOL_Ttwiddle = 11, /* Ttwiddle */
+ YYSYMBOL_Tbang = 12, /* Tbang */
+ YYSYMBOL_Tsubshell = 13, /* Tsubshell */
+ YYSYMBOL_Tfunc = 14, /* Tfunc */
+ YYSYMBOL_Tredir = 15, /* Tredir */
+ YYSYMBOL_Tdup = 16, /* Tdup */
+ YYSYMBOL_Tpipe = 17, /* Tpipe */
+ YYSYMBOL_Tindex = 18, /* Tindex */
+ YYSYMBOL_Tbasic = 19, /* Tbasic */
+ YYSYMBOL_Targs = 20, /* Targs */
+ YYSYMBOL_Tword = 21, /* Tword */
+ YYSYMBOL_Twords = 22, /* Twords */
+ YYSYMBOL_Tparen = 23, /* Tparen */
+ YYSYMBOL_Tblock = 24, /* Tblock */
+ YYSYMBOL_25_ = 25, /* ')' */
+ YYSYMBOL_Tandand = 26, /* Tandand */
+ YYSYMBOL_Toror = 27, /* Toror */
+ YYSYMBOL_28_n_ = 28, /* '\n' */
+ YYSYMBOL_29_ = 29, /* '^' */
+ YYSYMBOL_30_ = 30, /* '$' */
+ YYSYMBOL_Tcount = 31, /* Tcount */
+ YYSYMBOL_Tjoin = 32, /* Tjoin */
+ YYSYMBOL_33_ = 33, /* '(' */
+ YYSYMBOL_34_ = 34, /* '{' */
+ YYSYMBOL_35_ = 35, /* '}' */
+ YYSYMBOL_36_ = 36, /* ';' */
+ YYSYMBOL_37_ = 37, /* '&' */
+ YYSYMBOL_38_ = 38, /* '=' */
+ YYSYMBOL_39_ = 39, /* '`' */
+ YYSYMBOL_YYACCEPT = 40, /* $accept */
+ YYSYMBOL_rc = 41, /* rc */
+ YYSYMBOL_line = 42, /* line */
+ YYSYMBOL_body = 43, /* body */
+ YYSYMBOL_paren = 44, /* paren */
+ YYSYMBOL_block = 45, /* block */
+ YYSYMBOL_cmds = 46, /* cmds */
+ YYSYMBOL_cmdsln = 47, /* cmdsln */
+ YYSYMBOL_ifbody = 48, /* ifbody */
+ YYSYMBOL_case = 49, /* case */
+ YYSYMBOL_casebody = 50, /* casebody */
+ YYSYMBOL_assign = 51, /* assign */
+ YYSYMBOL_redir = 52, /* redir */
+ YYSYMBOL_epilog = 53, /* epilog */
+ YYSYMBOL_cmd = 54, /* cmd */
+ YYSYMBOL_basic = 55, /* basic */
+ YYSYMBOL_atom = 56, /* atom */
+ YYSYMBOL_word = 57, /* word */
+ YYSYMBOL_executable = 58, /* executable */
+ YYSYMBOL_nonkeyword = 59, /* nonkeyword */
+ YYSYMBOL_keyword = 60, /* keyword */
+ YYSYMBOL_words = 61, /* words */
+ YYSYMBOL_wordsnl = 62, /* wordsnl */
+ YYSYMBOL_nl = 63 /* nl */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_uint8 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if 1
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* 1 */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yy_state_t yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 56
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 478
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 40
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 24
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 73
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 129
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 283
+
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ (0 <= (YYX) && (YYX) <= YYMAXUTOK \
+ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \
+ : YYSYMBOL_YYUNDEF)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_int8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 28, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 30, 2, 37, 2,
+ 33, 25, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 36,
+ 2, 38, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 29, 2, 39, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 34, 2, 35, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 26, 27, 31, 32
+};
+
+#if YYDEBUG
+/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_uint8 yyrline[] =
+{
+ 0, 38, 38, 39, 42, 43, 46, 47, 50, 53,
+ 56, 57, 60, 61, 64, 65, 68, 69, 72, 73,
+ 74, 77, 80, 81, 84, 85, 88, 89, 90, 91,
+ 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
+ 102, 105, 106, 107, 110, 111, 114, 115, 118, 119,
+ 122, 123, 124, 125, 126, 127, 128, 132, 132, 132,
+ 132, 132, 132, 132, 132, 132, 132, 135, 136, 139,
+ 140, 141, 143, 145
+};
+#endif
+
+/** Accessing symbol of state STATE. */
+#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State])
+
+#if 1
+/* The user-facing name of the symbol whose (internal) number is
+ YYSYMBOL. No bounds checking. */
+static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
+
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "\"end of file\"", "error", "\"invalid token\"", "Tfor", "Tin",
+ "Twhile", "Tif", "Telse", "Tswitch", "Tcase", "Tcasebody", "Ttwiddle",
+ "Tbang", "Tsubshell", "Tfunc", "Tredir", "Tdup", "Tpipe", "Tindex",
+ "Tbasic", "Targs", "Tword", "Twords", "Tparen", "Tblock", "')'",
+ "Tandand", "Toror", "'\\n'", "'^'", "'$'", "Tcount", "Tjoin", "'('",
+ "'{'", "'}'", "';'", "'&'", "'='", "'`'", "$accept", "rc", "line",
+ "body", "paren", "block", "cmds", "cmdsln", "ifbody", "case", "casebody",
+ "assign", "redir", "epilog", "cmd", "basic", "atom", "word",
+ "executable", "nonkeyword", "keyword", "words", "wordsnl", "nl", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#define YYPACT_NINF (-82)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-3)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int16 yypact[] =
+{
+ 121, -17, -2, -2, 5, 439, 439, 343, -82, -82,
+ 343, 343, 343, -82, 439, -23, 45, 32, 11, 439,
+ 439, 439, 13, 158, -14, -82, 343, 439, -82, -82,
+ 343, 30, 30, -82, -82, -82, -82, -82, -82, -82,
+ -82, -82, -82, -82, 34, -82, -82, 47, -82, -82,
+ 195, 41, -82, 439, 54, -82, -82, -82, 11, -82,
+ -82, 30, 30, -82, -82, -82, -82, -82, -82, 34,
+ 343, 343, 19, 44, 375, 375, 4, 343, -82, -82,
+ -82, 34, -82, -82, -82, -82, 375, 375, 375, -82,
+ 34, -82, -82, -82, -82, 29, 77, -82, 29, -82,
+ -82, 269, -82, 30, 30, 306, 375, -82, 25, -82,
+ 34, -82, 29, 375, 407, 375, 29, -82, 407, 407,
+ 48, 54, 29, 232, -82, -82, -82, -82, -82
+};
+
+/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_int8 yydefact[] =
+{
+ 26, 0, 0, 0, 0, 26, 26, 0, 22, 50,
+ 0, 0, 0, 69, 26, 0, 0, 0, 24, 26,
+ 26, 26, 4, 27, 41, 48, 0, 26, 72, 72,
+ 0, 34, 35, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 46, 23, 44, 45, 51, 54, 55,
+ 0, 0, 12, 26, 6, 56, 1, 3, 24, 28,
+ 5, 33, 32, 72, 72, 72, 10, 11, 43, 42,
+ 0, 0, 0, 0, 26, 26, 0, 0, 67, 53,
+ 70, 71, 9, 7, 13, 25, 26, 26, 26, 49,
+ 21, 67, 72, 8, 73, 38, 24, 39, 14, 72,
+ 47, 0, 29, 30, 31, 0, 26, 72, 0, 52,
+ 68, 72, 36, 26, 26, 26, 15, 67, 26, 26,
+ 0, 18, 37, 0, 20, 19, 40, 17, 16
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -82, -82, 75, -19, 93, -11, 18, -52, -82, -82,
+ -21, -82, -1, 42, 0, -82, -9, 28, -82, 2,
+ -82, -81, -82, -22
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ 0, 16, 17, 51, 28, 18, 52, 53, 97, 119,
+ 120, 20, 21, 59, 54, 23, 43, 110, 24, 25,
+ 46, 101, 50, 74
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_int16 yytable[] =
+{
+ 22, 47, 48, 49, 55, 31, 32, 75, 73, 45,
+ 105, 14, 45, 45, 45, 70, 26, 58, 19, 22,
+ 61, 62, 68, 91, 71, 45, 7, 8, 45, 99,
+ 63, 27, 45, 77, 83, 44, 123, 19, 30, 64,
+ 65, 86, 87, 88, 92, 56, 63, 63, 77, 66,
+ 67, 69, 45, 94, 72, 64, 65, 58, 76, 114,
+ 57, 89, 118, 77, 96, 78, 118, 118, 100, 93,
+ 106, 63, 45, 45, 95, 98, 82, 108, 81, 45,
+ 64, 65, 84, 126, 107, 113, 102, 103, 104, 115,
+ 66, 67, 7, 8, 60, 58, 29, 124, 125, 90,
+ 85, 0, 0, 45, 0, 0, 112, 45, 0, 0,
+ 0, 0, 0, 116, 121, 122, 0, 0, 121, 121,
+ 0, -2, 0, 0, 1, 45, 2, 3, 0, 4,
+ 0, 0, 0, 5, 6, 0, 7, 8, 0, 0,
+ 0, 0, 9, 0, 0, 0, 0, 0, 0, 0,
+ 0, 10, 11, 12, 13, 14, 0, 0, 0, 0,
+ 15, 33, 34, 35, 36, 37, 38, 39, 0, 0,
+ 40, 41, 42, 7, 8, 0, 0, 0, 0, 9,
+ 0, 0, 0, 0, 0, 0, 0, 0, 10, 11,
+ 12, 13, 0, 0, 0, 0, 0, 15, 33, 34,
+ 35, 36, 37, 38, 39, 0, 0, 40, 41, 42,
+ 0, 0, 0, 0, 0, 0, 9, 0, 0, 0,
+ 79, 0, 0, 80, 0, 10, 11, 12, 13, 0,
+ 0, 0, 0, 0, 15, 33, 34, 35, 36, 37,
+ 38, 39, 0, 0, 40, 41, 42, 0, 0, 0,
+ 0, 0, 0, 9, 0, 0, 0, 0, 0, 0,
+ 127, 0, 10, 11, 12, 13, 0, 0, 128, 0,
+ 0, 15, 33, 34, 35, 36, 37, 38, 39, 0,
+ 0, 40, 41, 42, 0, 0, 0, 0, 0, 0,
+ 9, 0, 0, 0, 109, 0, 0, 0, 0, 10,
+ 11, 12, 13, 0, 0, 0, 0, 0, 15, 33,
+ 34, 35, 36, 37, 38, 39, 0, 0, 40, 41,
+ 42, 0, 0, 0, 0, 0, 0, 9, 0, 0,
+ 0, 111, 0, 0, 0, 0, 10, 11, 12, 13,
+ 0, 0, 0, 0, 0, 15, 33, 34, 35, 36,
+ 37, 38, 39, 0, 0, 40, 41, 42, 0, 0,
+ 0, 0, 0, 0, 9, 0, 0, 0, 0, 0,
+ 0, 0, 0, 10, 11, 12, 13, 0, 1, 0,
+ 2, 3, 15, 4, 0, 0, 0, 5, 6, 0,
+ 7, 8, 0, 0, 0, 0, 9, 0, 0, 0,
+ 0, 0, 0, 94, 0, 10, 11, 12, 13, 14,
+ 1, 0, 2, 3, 15, 4, 117, 0, 0, 5,
+ 6, 0, 7, 8, 0, 0, 0, 0, 9, 0,
+ 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
+ 13, 14, 1, 0, 2, 3, 15, 4, 0, 0,
+ 0, 5, 6, 0, 7, 8, 0, 0, 0, 0,
+ 9, 0, 0, 0, 0, 0, 0, 0, 0, 10,
+ 11, 12, 13, 14, 0, 0, 0, 0, 15
+};
+
+static const yytype_int8 yycheck[] =
+{
+ 0, 10, 11, 12, 15, 5, 6, 29, 27, 7,
+ 91, 34, 10, 11, 12, 29, 33, 18, 0, 19,
+ 20, 21, 23, 4, 38, 23, 15, 16, 26, 25,
+ 17, 33, 30, 29, 53, 7, 117, 19, 33, 26,
+ 27, 63, 64, 65, 25, 0, 17, 17, 29, 36,
+ 37, 23, 50, 28, 26, 26, 27, 58, 30, 34,
+ 28, 70, 114, 29, 75, 18, 118, 119, 77, 25,
+ 92, 17, 70, 71, 74, 75, 35, 99, 50, 77,
+ 26, 27, 28, 35, 7, 107, 86, 87, 88, 111,
+ 36, 37, 15, 16, 19, 96, 3, 118, 119, 71,
+ 58, -1, -1, 101, -1, -1, 106, 105, -1, -1,
+ -1, -1, -1, 113, 114, 115, -1, -1, 118, 119,
+ -1, 0, -1, -1, 3, 123, 5, 6, -1, 8,
+ -1, -1, -1, 12, 13, -1, 15, 16, -1, -1,
+ -1, -1, 21, -1, -1, -1, -1, -1, -1, -1,
+ -1, 30, 31, 32, 33, 34, -1, -1, -1, -1,
+ 39, 3, 4, 5, 6, 7, 8, 9, -1, -1,
+ 12, 13, 14, 15, 16, -1, -1, -1, -1, 21,
+ -1, -1, -1, -1, -1, -1, -1, -1, 30, 31,
+ 32, 33, -1, -1, -1, -1, -1, 39, 3, 4,
+ 5, 6, 7, 8, 9, -1, -1, 12, 13, 14,
+ -1, -1, -1, -1, -1, -1, 21, -1, -1, -1,
+ 25, -1, -1, 28, -1, 30, 31, 32, 33, -1,
+ -1, -1, -1, -1, 39, 3, 4, 5, 6, 7,
+ 8, 9, -1, -1, 12, 13, 14, -1, -1, -1,
+ -1, -1, -1, 21, -1, -1, -1, -1, -1, -1,
+ 28, -1, 30, 31, 32, 33, -1, -1, 36, -1,
+ -1, 39, 3, 4, 5, 6, 7, 8, 9, -1,
+ -1, 12, 13, 14, -1, -1, -1, -1, -1, -1,
+ 21, -1, -1, -1, 25, -1, -1, -1, -1, 30,
+ 31, 32, 33, -1, -1, -1, -1, -1, 39, 3,
+ 4, 5, 6, 7, 8, 9, -1, -1, 12, 13,
+ 14, -1, -1, -1, -1, -1, -1, 21, -1, -1,
+ -1, 25, -1, -1, -1, -1, 30, 31, 32, 33,
+ -1, -1, -1, -1, -1, 39, 3, 4, 5, 6,
+ 7, 8, 9, -1, -1, 12, 13, 14, -1, -1,
+ -1, -1, -1, -1, 21, -1, -1, -1, -1, -1,
+ -1, -1, -1, 30, 31, 32, 33, -1, 3, -1,
+ 5, 6, 39, 8, -1, -1, -1, 12, 13, -1,
+ 15, 16, -1, -1, -1, -1, 21, -1, -1, -1,
+ -1, -1, -1, 28, -1, 30, 31, 32, 33, 34,
+ 3, -1, 5, 6, 39, 8, 9, -1, -1, 12,
+ 13, -1, 15, 16, -1, -1, -1, -1, 21, -1,
+ -1, -1, -1, -1, -1, -1, -1, 30, 31, 32,
+ 33, 34, 3, -1, 5, 6, 39, 8, -1, -1,
+ -1, 12, 13, -1, 15, 16, -1, -1, -1, -1,
+ 21, -1, -1, -1, -1, -1, -1, -1, -1, 30,
+ 31, 32, 33, 34, -1, -1, -1, -1, 39
+};
+
+/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ state STATE-NUM. */
+static const yytype_int8 yystos[] =
+{
+ 0, 3, 5, 6, 8, 12, 13, 15, 16, 21,
+ 30, 31, 32, 33, 34, 39, 41, 42, 45, 46,
+ 51, 52, 54, 55, 58, 59, 33, 33, 44, 44,
+ 33, 54, 54, 3, 4, 5, 6, 7, 8, 9,
+ 12, 13, 14, 56, 57, 59, 60, 56, 56, 56,
+ 62, 43, 46, 47, 54, 45, 0, 28, 52, 53,
+ 42, 54, 54, 17, 26, 27, 36, 37, 52, 57,
+ 29, 38, 57, 43, 63, 63, 57, 29, 18, 25,
+ 28, 57, 35, 43, 28, 53, 63, 63, 63, 56,
+ 57, 4, 25, 25, 28, 54, 45, 48, 54, 25,
+ 56, 61, 54, 54, 54, 61, 63, 7, 63, 25,
+ 57, 25, 54, 63, 34, 63, 54, 9, 47, 49,
+ 50, 54, 54, 61, 50, 50, 35, 28, 36
+};
+
+/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr1[] =
+{
+ 0, 40, 41, 41, 42, 42, 43, 43, 44, 45,
+ 46, 46, 47, 47, 48, 48, 49, 49, 50, 50,
+ 50, 51, 52, 52, 53, 53, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 55, 55, 55, 56, 56, 57, 57, 58, 58,
+ 59, 59, 59, 59, 59, 59, 59, 60, 60, 60,
+ 60, 60, 60, 60, 60, 60, 60, 61, 61, 62,
+ 62, 62, 63, 63
+};
+
+/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 0, 2, 1, 2, 1, 2, 3, 3,
+ 2, 2, 1, 2, 1, 4, 3, 3, 1, 2,
+ 2, 3, 1, 2, 0, 2, 0, 1, 2, 4,
+ 4, 4, 2, 2, 2, 2, 6, 8, 4, 4,
+ 8, 1, 2, 2, 1, 1, 1, 3, 1, 3,
+ 1, 2, 5, 3, 2, 2, 2, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 0, 2, 0,
+ 2, 2, 0, 2
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYNOMEM goto yyexhaustedlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (!yyvaluep)
+ return;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ yy_symbol_value_print (yyo, yykind, yyvaluep);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)]);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+/* Context of a parse error. */
+typedef struct
+{
+ yy_state_t *yyssp;
+ yysymbol_kind_t yytoken;
+} yypcontext_t;
+
+/* Put in YYARG at most YYARGN of the expected tokens given the
+ current YYCTX, and return the number of tokens stored in YYARG. If
+ YYARG is null, return the number of expected tokens (guaranteed to
+ be less than YYNTOKENS). Return YYENOMEM on memory exhaustion.
+ Return 0 if there are more than YYARGN expected tokens, yet fill
+ YYARG up to YYARGN. */
+static int
+yypcontext_expected_tokens (const yypcontext_t *yyctx,
+ yysymbol_kind_t yyarg[], int yyargn)
+{
+ /* Actual size of YYARG. */
+ int yycount = 0;
+ int yyn = yypact[+*yyctx->yyssp];
+ if (!yypact_value_is_default (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ int yyxbegin = yyn < 0 ? -yyn : 0;
+ /* Stay within bounds of both yycheck and yytname. */
+ int yychecklim = YYLAST - yyn + 1;
+ int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ int yyx;
+ for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck[yyx + yyn] == yyx && yyx != YYSYMBOL_YYerror
+ && !yytable_value_is_error (yytable[yyx + yyn]))
+ {
+ if (!yyarg)
+ ++yycount;
+ else if (yycount == yyargn)
+ return 0;
+ else
+ yyarg[yycount++] = YY_CAST (yysymbol_kind_t, yyx);
+ }
+ }
+ if (yyarg && yycount == 0 && 0 < yyargn)
+ yyarg[0] = YYSYMBOL_YYEMPTY;
+ return yycount;
+}
+
+
+
+
+#ifndef yystrlen
+# if defined __GLIBC__ && defined _STRING_H
+# define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S)))
+# else
+/* Return the length of YYSTR. */
+static YYPTRDIFF_T
+yystrlen (const char *yystr)
+{
+ YYPTRDIFF_T yylen;
+ for (yylen = 0; yystr[yylen]; yylen++)
+ continue;
+ return yylen;
+}
+# endif
+#endif
+
+#ifndef yystpcpy
+# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+# define yystpcpy stpcpy
+# else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+ YYDEST. */
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+{
+ char *yyd = yydest;
+ const char *yys = yysrc;
+
+ while ((*yyd++ = *yys++) != '\0')
+ continue;
+
+ return yyd - 1;
+}
+# endif
+#endif
+
+#ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+ quotes and backslashes, so that it's suitable for yyerror. The
+ heuristic is that double-quoting is unnecessary unless the string
+ contains an apostrophe, a comma, or backslash (other than
+ backslash-backslash). YYSTR is taken from yytname. If YYRES is
+ null, do not copy; instead, return the length of what the result
+ would have been. */
+static YYPTRDIFF_T
+yytnamerr (char *yyres, const char *yystr)
+{
+ if (*yystr == '"')
+ {
+ YYPTRDIFF_T yyn = 0;
+ char const *yyp = yystr;
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ else
+ goto append;
+
+ append:
+ default:
+ if (yyres)
+ yyres[yyn] = *yyp;
+ yyn++;
+ break;
+
+ case '"':
+ if (yyres)
+ yyres[yyn] = '\0';
+ return yyn;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ if (yyres)
+ return yystpcpy (yyres, yystr) - yyres;
+ else
+ return yystrlen (yystr);
+}
+#endif
+
+
+static int
+yy_syntax_error_arguments (const yypcontext_t *yyctx,
+ yysymbol_kind_t yyarg[], int yyargn)
+{
+ /* Actual size of YYARG. */
+ int yycount = 0;
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yychar) is if
+ this state is a consistent state with a default action. Thus,
+ detecting the absence of a lookahead is sufficient to determine
+ that there is no unexpected or expected token to report. In that
+ case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is a
+ consistent state with a default action. There might have been a
+ previous inconsistent state, consistent state with a non-default
+ action, or user semantic action that manipulated yychar.
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+ if (yyctx->yytoken != YYSYMBOL_YYEMPTY)
+ {
+ int yyn;
+ if (yyarg)
+ yyarg[yycount] = yyctx->yytoken;
+ ++yycount;
+ yyn = yypcontext_expected_tokens (yyctx,
+ yyarg ? yyarg + 1 : yyarg, yyargn - 1);
+ if (yyn == YYENOMEM)
+ return YYENOMEM;
+ else
+ yycount += yyn;
+ }
+ return yycount;
+}
+
+/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
+ about the unexpected token YYTOKEN for the state stack whose top is
+ YYSSP.
+
+ Return 0 if *YYMSG was successfully written. Return -1 if *YYMSG is
+ not large enough to hold the message. In that case, also set
+ *YYMSG_ALLOC to the required number of bytes. Return YYENOMEM if the
+ required number of bytes is too large to store. */
+static int
+yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg,
+ const yypcontext_t *yyctx)
+{
+ enum { YYARGS_MAX = 5 };
+ /* Internationalized format string. */
+ const char *yyformat = YY_NULLPTR;
+ /* Arguments of yyformat: reported tokens (one for the "unexpected",
+ one per "expected"). */
+ yysymbol_kind_t yyarg[YYARGS_MAX];
+ /* Cumulated lengths of YYARG. */
+ YYPTRDIFF_T yysize = 0;
+
+ /* Actual size of YYARG. */
+ int yycount = yy_syntax_error_arguments (yyctx, yyarg, YYARGS_MAX);
+ if (yycount == YYENOMEM)
+ return YYENOMEM;
+
+ switch (yycount)
+ {
+#define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ default: /* Avoid compiler warnings. */
+ YYCASE_(0, YY_("syntax error"));
+ YYCASE_(1, YY_("syntax error, unexpected %s"));
+ YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+#undef YYCASE_
+ }
+
+ /* Compute error message size. Don't count the "%s"s, but reserve
+ room for the terminator. */
+ yysize = yystrlen (yyformat) - 2 * yycount + 1;
+ {
+ int yyi;
+ for (yyi = 0; yyi < yycount; ++yyi)
+ {
+ YYPTRDIFF_T yysize1
+ = yysize + yytnamerr (YY_NULLPTR, yytname[yyarg[yyi]]);
+ if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)
+ yysize = yysize1;
+ else
+ return YYENOMEM;
+ }
+ }
+
+ if (*yymsg_alloc < yysize)
+ {
+ *yymsg_alloc = 2 * yysize;
+ if (! (yysize <= *yymsg_alloc
+ && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
+ *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
+ return -1;
+ }
+
+ /* Avoid sprintf, as that infringes on the user's name space.
+ Don't have undefined behavior even if the translation
+ produced a string with the wrong number of "%s"s. */
+ {
+ char *yyp = *yymsg;
+ int yyi = 0;
+ while ((*yyp = *yyformat) != '\0')
+ if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
+ {
+ yyp += yytnamerr (yyp, yytname[yyarg[yyi++]]);
+ yyformat += 2;
+ }
+ else
+ {
+ ++yyp;
+ ++yyformat;
+ }
+ }
+ return 0;
+}
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep)
+{
+ YY_USE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/* Lookahead token kind. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ int yyn;
+ /* The return value of yyparse. */
+ int yyresult;
+ /* Lookahead symbol kind. */
+ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+ /* Buffer for error messages, and its allocated size. */
+ char yymsgbuf[128];
+ char *yymsg = yymsgbuf;
+ YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf;
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yysetstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ YY_IGNORE_USELESS_CAST_BEGIN
+ *yyssp = YY_CAST (yy_state_t, yystate);
+ YY_IGNORE_USELESS_CAST_END
+ YY_STACK_PRINT (yyss, yyssp);
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ YYNOMEM;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYPTRDIFF_T yysize = yyssp - yyss + 1;
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ yy_state_t *yyss1 = yyss;
+ YYSTYPE *yyvs1 = yyvs;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * YYSIZEOF (*yyssp),
+ &yyvs1, yysize * YYSIZEOF (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ YYNOMEM;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yy_state_t *yyss1 = yyss;
+ union yyalloc *yyptr =
+ YY_CAST (union yyalloc *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize))));
+ if (! yyptr)
+ YYNOMEM;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YY_IGNORE_USELESS_CAST_BEGIN
+ YYDPRINTF ((stderr, "Stack size increased to %ld\n",
+ YY_CAST (long, yystacksize)));
+ YY_IGNORE_USELESS_CAST_END
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token\n"));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = YYEOF;
+ yytoken = YYSYMBOL_YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else if (yychar == YYerror)
+ {
+ /* The scanner already issued an error message, process directly
+ to error recovery. But do not keep the error token as
+ lookahead, it is too special and may lead us to an endless
+ loop in error recovery. */
+ yychar = YYUNDEF;
+ yytoken = YYSYMBOL_YYerror;
+ goto yyerrlab1;
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 2: /* rc: %empty */
+#line 38 "sys/cmd/rc/syntax.y"
+ { return 0; }
+#line 1549 "sys/cmd/rc/parse.c"
+ break;
+
+ case 3: /* rc: line '\n' */
+#line 39 "sys/cmd/rc/syntax.y"
+ { return compile((yyvsp[-1].tree)); }
+#line 1555 "sys/cmd/rc/parse.c"
+ break;
+
+ case 5: /* line: cmds line */
+#line 43 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(';', (yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1561 "sys/cmd/rc/parse.c"
+ break;
+
+ case 7: /* body: cmdsln body */
+#line 47 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(';', (yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1567 "sys/cmd/rc/parse.c"
+ break;
+
+ case 8: /* paren: '(' body ')' */
+#line 50 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tparen, (yyvsp[-1].tree)); }
+#line 1573 "sys/cmd/rc/parse.c"
+ break;
+
+ case 9: /* block: '{' body '}' */
+#line 53 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tblock, (yyvsp[-1].tree)); }
+#line 1579 "sys/cmd/rc/parse.c"
+ break;
+
+ case 11: /* cmds: cmd '&' */
+#line 57 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1('&', (yyvsp[-1].tree)); }
+#line 1585 "sys/cmd/rc/parse.c"
+ break;
+
+ case 14: /* ifbody: cmd */
+#line 64 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Tif, nil, (yyvsp[0].tree)); }
+#line 1591 "sys/cmd/rc/parse.c"
+ break;
+
+ case 15: /* ifbody: block Telse nl cmd */
+#line 65 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree3(Tif, nil, (yyvsp[-3].tree), (yyvsp[-2].tree)); }
+#line 1597 "sys/cmd/rc/parse.c"
+ break;
+
+ case 16: /* case: Tcase words ';' */
+#line 68 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild1((yyvsp[-2].tree), (yyvsp[-1].tree), 0); }
+#line 1603 "sys/cmd/rc/parse.c"
+ break;
+
+ case 17: /* case: Tcase words '\n' */
+#line 69 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild1((yyvsp[-2].tree), (yyvsp[-1].tree), 0); }
+#line 1609 "sys/cmd/rc/parse.c"
+ break;
+
+ case 18: /* casebody: cmd */
+#line 72 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Tcasebody, (yyvsp[0].tree), nil); }
+#line 1615 "sys/cmd/rc/parse.c"
+ break;
+
+ case 19: /* casebody: case casebody */
+#line 73 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Tcasebody, (yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1621 "sys/cmd/rc/parse.c"
+ break;
+
+ case 20: /* casebody: cmdsln casebody */
+#line 74 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Tcasebody, (yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1627 "sys/cmd/rc/parse.c"
+ break;
+
+ case 21: /* assign: executable '=' word */
+#line 77 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2('=', (yyvsp[-2].tree), (yyvsp[0].tree)); }
+#line 1633 "sys/cmd/rc/parse.c"
+ break;
+
+ case 23: /* redir: Tredir word */
+#line 81 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 0); }
+#line 1639 "sys/cmd/rc/parse.c"
+ break;
+
+ case 24: /* epilog: %empty */
+#line 84 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = nil; }
+#line 1645 "sys/cmd/rc/parse.c"
+ break;
+
+ case 25: /* epilog: redir epilog */
+#line 85 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 1); }
+#line 1651 "sys/cmd/rc/parse.c"
+ break;
+
+ case 26: /* cmd: %empty */
+#line 88 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = nil; }
+#line 1657 "sys/cmd/rc/parse.c"
+ break;
+
+ case 27: /* cmd: basic */
+#line 89 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tbasic, (yyvsp[0].tree)); }
+#line 1663 "sys/cmd/rc/parse.c"
+ break;
+
+ case 28: /* cmd: block epilog */
+#line 90 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangepilog((yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1669 "sys/cmd/rc/parse.c"
+ break;
+
+ case 29: /* cmd: cmd Tpipe nl cmd */
+#line 91 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild2((yyvsp[-2].tree), (yyvsp[-3].tree), 0, (yyvsp[0].tree), 1); }
+#line 1675 "sys/cmd/rc/parse.c"
+ break;
+
+ case 30: /* cmd: cmd Tandand nl cmd */
+#line 92 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Tandand, (yyvsp[-3].tree), (yyvsp[0].tree)); }
+#line 1681 "sys/cmd/rc/parse.c"
+ break;
+
+ case 31: /* cmd: cmd Toror nl cmd */
+#line 93 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Toror, (yyvsp[-3].tree), (yyvsp[0].tree)); }
+#line 1687 "sys/cmd/rc/parse.c"
+ break;
+
+ case 32: /* cmd: redir cmd */
+#line 94 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 1); }
+#line 1693 "sys/cmd/rc/parse.c"
+ break;
+
+ case 33: /* cmd: assign cmd */
+#line 95 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 2); }
+#line 1699 "sys/cmd/rc/parse.c"
+ break;
+
+ case 34: /* cmd: Tbang cmd */
+#line 96 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tbang, (yyvsp[0].tree)); }
+#line 1705 "sys/cmd/rc/parse.c"
+ break;
+
+ case 35: /* cmd: Tsubshell cmd */
+#line 97 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tsubshell, (yyvsp[0].tree)); }
+#line 1711 "sys/cmd/rc/parse.c"
+ break;
+
+ case 36: /* cmd: Tfor '(' word ')' nl cmd */
+#line 98 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild3((yyvsp[-5].tree), (yyvsp[-3].tree), nil, (yyvsp[0].tree)); }
+#line 1717 "sys/cmd/rc/parse.c"
+ break;
+
+ case 37: /* cmd: Tfor '(' word Tin words ')' nl cmd */
+#line 99 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild3((yyvsp[-7].tree), (yyvsp[-5].tree), (yyvsp[-3].tree), (yyvsp[0].tree)); }
+#line 1723 "sys/cmd/rc/parse.c"
+ break;
+
+ case 38: /* cmd: Twhile paren nl cmd */
+#line 100 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild2((yyvsp[-3].tree), (yyvsp[-2].tree), 0, (yyvsp[0].tree), 1); }
+#line 1729 "sys/cmd/rc/parse.c"
+ break;
+
+ case 39: /* cmd: Tif paren nl ifbody */
+#line 101 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild1((yyvsp[-2].tree), (yyvsp[-3].tree), 0); }
+#line 1735 "sys/cmd/rc/parse.c"
+ break;
+
+ case 40: /* cmd: Tswitch '(' word ')' nl '{' casebody '}' */
+#line 102 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = hangchild2((yyvsp[-7].tree), (yyvsp[-5].tree), 0, (yyvsp[-1].tree), 1); }
+#line 1741 "sys/cmd/rc/parse.c"
+ break;
+
+ case 42: /* basic: basic word */
+#line 106 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Targs, (yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1747 "sys/cmd/rc/parse.c"
+ break;
+
+ case 43: /* basic: basic redir */
+#line 107 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Targs, (yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1753 "sys/cmd/rc/parse.c"
+ break;
+
+ case 45: /* atom: keyword */
+#line 111 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tword, (yyvsp[0].tree)); }
+#line 1759 "sys/cmd/rc/parse.c"
+ break;
+
+ case 47: /* word: word '^' atom */
+#line 115 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2('^', (yyvsp[-2].tree), (yyvsp[0].tree)); }
+#line 1765 "sys/cmd/rc/parse.c"
+ break;
+
+ case 49: /* executable: executable '^' atom */
+#line 119 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2('^', (yyvsp[-2].tree), (yyvsp[0].tree)); }
+#line 1771 "sys/cmd/rc/parse.c"
+ break;
+
+ case 51: /* nonkeyword: '$' atom */
+#line 123 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1('$', (yyvsp[0].tree)); }
+#line 1777 "sys/cmd/rc/parse.c"
+ break;
+
+ case 52: /* nonkeyword: '$' atom Tindex words ')' */
+#line 124 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Tindex, (yyvsp[-3].tree), (yyvsp[-1].tree)); }
+#line 1783 "sys/cmd/rc/parse.c"
+ break;
+
+ case 53: /* nonkeyword: '(' wordsnl ')' */
+#line 125 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = (yyvsp[-1].tree); }
+#line 1789 "sys/cmd/rc/parse.c"
+ break;
+
+ case 54: /* nonkeyword: Tcount atom */
+#line 126 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tcount, (yyvsp[0].tree)); }
+#line 1795 "sys/cmd/rc/parse.c"
+ break;
+
+ case 55: /* nonkeyword: Tjoin atom */
+#line 127 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1(Tjoin, (yyvsp[0].tree)); }
+#line 1801 "sys/cmd/rc/parse.c"
+ break;
+
+ case 56: /* nonkeyword: '`' block */
+#line 128 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree1('`', (yyvsp[0].tree)); }
+#line 1807 "sys/cmd/rc/parse.c"
+ break;
+
+ case 67: /* words: %empty */
+#line 135 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = nil; }
+#line 1813 "sys/cmd/rc/parse.c"
+ break;
+
+ case 68: /* words: words word */
+#line 136 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = maketree2(Twords, (yyvsp[-1].tree), (yyvsp[0].tree)); }
+#line 1819 "sys/cmd/rc/parse.c"
+ break;
+
+ case 69: /* wordsnl: %empty */
+#line 139 "sys/cmd/rc/syntax.y"
+ { (yyval.tree) = nil; }
+#line 1825 "sys/cmd/rc/parse.c"
+ break;
+
+ case 71: /* wordsnl: wordsnl word */
+#line 141 "sys/cmd/rc/syntax.y"
+ {(yyval.tree) = (!(yyvsp[-1].tree)) ? ((!(yyvsp[0].tree)) ? nil : (yyvsp[0].tree)) : ((!(yyvsp[0].tree)) ? (yyvsp[-1].tree) : maketree2(Twords, (yyvsp[-1].tree), (yyvsp[0].tree))); }
+#line 1831 "sys/cmd/rc/parse.c"
+ break;
+
+
+#line 1835 "sys/cmd/rc/parse.c"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ {
+ yypcontext_t yyctx
+ = {yyssp, yytoken};
+ char const *yymsgp = YY_("syntax error");
+ int yysyntax_error_status;
+ yysyntax_error_status = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx);
+ if (yysyntax_error_status == 0)
+ yymsgp = yymsg;
+ else if (yysyntax_error_status == -1)
+ {
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ yymsg = YY_CAST (char *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc)));
+ if (yymsg)
+ {
+ yysyntax_error_status
+ = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx);
+ yymsgp = yymsg;
+ }
+ else
+ {
+ yymsg = yymsgbuf;
+ yymsg_alloc = sizeof yymsgbuf;
+ yysyntax_error_status = YYENOMEM;
+ }
+ }
+ yyerror (yymsgp);
+ if (yysyntax_error_status == YYENOMEM)
+ YYNOMEM;
+ }
+ }
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+ ++yynerrs;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ /* Pop stack until we find a state that shifts the error token. */
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYSYMBOL_YYerror;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturnlab;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturnlab;
+
+
+/*-----------------------------------------------------------.
+| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. |
+`-----------------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturnlab;
+
+
+/*----------------------------------------------------------.
+| yyreturnlab -- parsing is finished, clean up and return. |
+`----------------------------------------------------------*/
+yyreturnlab:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ YY_ACCESSING_SYMBOL (+*yyssp), yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ return yyresult;
+}
+
+#line 147 "sys/cmd/rc/syntax.y"
+
diff --git a/src/cmd/rc/parse.h b/src/cmd/rc/parse.h
new file mode 100644
index 0000000..64ee07b
--- /dev/null
+++ b/src/cmd/rc/parse.h
@@ -0,0 +1,141 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison interface for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+#ifndef YY_YY_SYS_CMD_RC_PARSE_H_INCLUDED
+# define YY_YY_SYS_CMD_RC_PARSE_H_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ Tfor = 258, /* Tfor */
+ Tin = 259, /* Tin */
+ Twhile = 260, /* Twhile */
+ Tif = 261, /* Tif */
+ Telse = 262, /* Telse */
+ Tswitch = 263, /* Tswitch */
+ Tcase = 264, /* Tcase */
+ Tcasebody = 265, /* Tcasebody */
+ Ttwiddle = 266, /* Ttwiddle */
+ Tbang = 267, /* Tbang */
+ Tsubshell = 268, /* Tsubshell */
+ Tfunc = 269, /* Tfunc */
+ Tredir = 270, /* Tredir */
+ Tdup = 271, /* Tdup */
+ Tpipe = 272, /* Tpipe */
+ Tindex = 273, /* Tindex */
+ Tbasic = 274, /* Tbasic */
+ Targs = 275, /* Targs */
+ Tword = 276, /* Tword */
+ Twords = 277, /* Twords */
+ Tparen = 278, /* Tparen */
+ Tblock = 279, /* Tblock */
+ Tandand = 280, /* Tandand */
+ Toror = 281, /* Toror */
+ Tcount = 282, /* Tcount */
+ Tjoin = 283 /* Tjoin */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+/* Token kinds. */
+#define YYEMPTY -2
+#define YYEOF 0
+#define YYerror 256
+#define YYUNDEF 257
+#define Tfor 258
+#define Tin 259
+#define Twhile 260
+#define Tif 261
+#define Telse 262
+#define Tswitch 263
+#define Tcase 264
+#define Tcasebody 265
+#define Ttwiddle 266
+#define Tbang 267
+#define Tsubshell 268
+#define Tfunc 269
+#define Tredir 270
+#define Tdup 271
+#define Tpipe 272
+#define Tindex 273
+#define Tbasic 274
+#define Targs 275
+#define Tword 276
+#define Twords 277
+#define Tparen 278
+#define Tblock 279
+#define Tandand 280
+#define Toror 281
+#define Tcount 282
+#define Tjoin 283
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 24 "sys/cmd/rc/syntax.y"
+
+ struct Tree *tree;
+
+#line 127 "sys/cmd/rc/parse.h"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+#endif /* !YY_YY_SYS_CMD_RC_PARSE_H_INCLUDED */
diff --git a/src/cmd/rc/prompt.c b/src/cmd/rc/prompt.c
new file mode 100644
index 0000000..1122d54
--- /dev/null
+++ b/src/cmd/rc/prompt.c
@@ -0,0 +1,36 @@
+#include "rc.h"
+
+/* static char promptbuf[7] = {'>', ' ', 0, ' ' , ' ', ' ', 0}; */
+static char *base= "\x1b[1;31m" ">" "\x1b[0;0m" " ", *promptstr;
+
+void
+resetprompt(void)
+{
+ promptstr = base;
+}
+
+int
+prompt(ushort *flag)
+{
+ int f = *flag;
+
+ if(f){
+ if(!readline(promptstr)){
+ runner->flag.eof = 1;
+ return 0;
+ }
+ if(runner->cmd.io->e[-1] == '\n'){
+ runner->cmd.io->e[-1] = 0;
+ addhistory(runner->cmd.io->b);
+ runner->cmd.io->e[-1] = '\n';
+ }
+
+ write(mapfd(0), "\n\r", 2);
+ promptstr = " ";
+
+ runner->line++;
+ *flag = 0;
+ }
+
+ return 1;
+}
diff --git a/src/cmd/rc/rc.h b/src/cmd/rc/rc.h
new file mode 100644
index 0000000..9b415fc
--- /dev/null
+++ b/src/cmd/rc/rc.h
@@ -0,0 +1,263 @@
+
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+
+// -----------------------------------------------------------------------
+// types
+
+typedef struct Io Io;
+typedef struct Var Var;
+typedef struct Word Word;
+typedef struct List List;
+typedef struct Tree Tree;
+typedef struct Redir Redir;
+typedef union Code Code;
+typedef struct Thread Thread;
+typedef struct Shell Shell;
+
+struct Io
+{
+ int fd, cap;
+ char *s;
+ char *b, *e, buf[];
+};
+
+enum
+{
+ Rappend,
+ Rwrite,
+ Rread,
+ Rhere,
+ Rdupfd,
+ Ropen,
+ Rclose,
+ Rrdwr
+};
+
+struct Tree
+{
+ int type;
+ union{
+ struct {
+ ushort quoted;
+ char *str; // Tword
+ };
+ struct {
+ ushort type; // Tpipe, Tredir, Tdup
+ int fd[2];
+ } redir;
+ };
+ Tree *child[3];
+ Tree *link;
+};
+
+struct Word
+{
+ char *str;
+ Word *link;
+};
+
+struct List
+{
+ Word *word;
+ List *link;
+};
+
+/*
+ * the first word of any code vector is a reference count
+ * always create a new reference to a code vector by calling copycode()
+ * always call freecode() when deleting a reference
+ */
+union Code
+{
+ int i;
+ void (*f)(void);
+ char *s;
+};
+
+struct Var
+{
+ char *name;
+ Word *val;
+ short new : 1;
+ short newfunc : 1;
+ Code *func;
+ void (*update)(Var *);
+ Var *link;
+};
+
+struct Redir
+{
+ char type; /* what to do */
+ short from, to; /* what to do it to */
+ struct Redir *link; /* what else to do (reverse order) */
+};
+
+enum
+{
+ Pnil,
+ Prun,
+ Pstop,
+ Psig,
+ Pagain,
+ Pdone,
+};
+
+struct WaitItem
+{
+ int pid;
+ ushort status;
+};
+
+struct Thread
+{
+ struct {
+ int i;
+ Code *exe;
+ } code; // execution stack
+ struct {
+ Io *io;
+ char *path;
+ } cmd; // command input
+
+ List *args; // argument stack
+ Var *local; // local variables
+ struct {
+ Redir *start;
+ Redir *end;
+ } redir; // list of redirections
+
+ struct {
+ ushort user : 1;
+ ushort eof : 1;
+ } flag;
+
+ struct {
+ ushort status;
+ int len, cap;
+ struct WaitItem *on;
+ } wait;
+
+ int pid, pgid, status;
+ long line;
+
+ Thread *caller; // process we return to
+ Thread *link; // next job
+};
+
+struct Shell
+{
+ int pid;
+ Io *err;
+ int status;
+ int interactive;
+ Thread *jobs;
+};
+
+// -----------------------------------------------------------------------
+// globals
+
+extern Shell shell;
+extern Thread *runner;
+extern Code *compiled;
+
+// -----------------------------------------------------------------------
+// functions
+
+/* util.c */
+void itoa(char*, long i);
+void fatal(char *, ...);
+void *emalloc(uintptr);
+void *erealloc(void*, uintptr);
+void efree(void*);
+
+/* input.c */
+int readline(char *);
+void enablevi(void);
+
+void inithistory(void);
+int addhistory(char *);
+
+/* prompt.c */
+void resetprompt(void);
+int prompt(ushort *);
+
+/* io.c */
+Io *openfd(int fd);
+Io *openstr(void);
+void terminate(Io *io);
+
+int get(Io *);
+int put(Io **, char);
+
+void flush(Io *io);
+void print(Io *, char *, ...);
+
+/* lex.c */
+int iswordchar(int c);
+int yylex(void);
+
+/* tree.c */
+Tree *maketree(void);
+Tree *maketree1(int, Tree*);
+Tree *maketree2(int, Tree*, Tree*);
+Tree *maketree3(int, Tree*, Tree*, Tree*);
+
+Tree *token(int, char *);
+Tree *hangchild1(Tree *, Tree *, int);
+Tree *hangchild2(Tree *, Tree *, int, Tree *, int);
+Tree *hangchild3(Tree *, Tree *, Tree *, Tree *);
+Tree *hangepilog(Tree *, Tree*);
+
+void freeparsetree(void);
+
+/* sys.c */
+void initenv(void);
+void redirect(struct Redir *);
+void execute(Word *, Word*);
+int mapfd(int fd);
+
+/* wait.c */
+void addwait(Thread *, int);
+void delwait(Thread *, int);
+void clearwait(Thread*);
+
+int waitall(Thread *);
+int waitfor(Thread *, int);
+
+void killzombies(void);
+
+/* job.c */
+Thread *getjob(int, int*);
+void addjob(Thread *);
+void deljob(Thread *);
+void wakeup(Thread *);
+void report(Thread *, int);
+
+void foreground(Thread *, int);
+void background(Thread *, int);
+
+/* exec.c */
+// XXX: odd place for this
+int count(Word *);
+Word *makeword(char *str, Word *link);
+void freeword(Word *w);
+
+/* var.c */
+Var *var(char*);
+Var *definevar(char*, Var *);
+Var *globalvar(char*);
+Var *makevar(char *name, Var *link);
+void setvar(char *, Word *);
+int iskeyword(char *);
+
+void initpath(void);
+void initkeywords(void);
+
+char **mkenv(void);
+
+/* code.c */
+int compile(Tree *);
+Code *copycode(Code *c);
+void freecode(Code *c);
diff --git a/src/cmd/rc/rules.mk b/src/cmd/rc/rules.mk
new file mode 100644
index 0000000..76837fc
--- /dev/null
+++ b/src/cmd/rc/rules.mk
@@ -0,0 +1,32 @@
+include share/push.mk
+# Iterate through subdirectory tree
+
+# local sources
+SRCS_$(d):=\
+ $(d)/util.c\
+ $(d)/input.c\
+ $(d)/prompt.c\
+ $(d)/io.c\
+ $(d)/lex.c\
+ $(d)/parse.c\
+ $(d)/tree.c\
+ $(d)/code.c\
+ $(d)/var.c\
+ $(d)/sys.c\
+ $(d)/wait.c\
+ $(d)/job.c\
+ $(d)/exec.c\
+ $(d)/main.c
+
+# local targets
+BINS_$(d) := $(d)/rc
+
+include share/paths.mk
+$(d)/parse.h $(d)/parse.c: $(d)/syntax.y
+ yacc --header=$(<D)/parse.h --output=$(<D)/parse.c $(<)
+
+# local rules
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libutf/libutf.a $(OBJ_DIR)/base/base.a $(d)/parse.h
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/rc/syntax.y b/src/cmd/rc/syntax.y
new file mode 100644
index 0000000..0bdc776
--- /dev/null
+++ b/src/cmd/rc/syntax.y
@@ -0,0 +1,147 @@
+%token Tfor Tin Twhile Tif Telse Tswitch Tcase Tcasebody Ttwiddle Tbang Tsubshell Tfunc
+%token Tredir Tdup Tpipe Tindex
+%token Tbasic Targs Tword Twords Tparen Tblock
+
+%define parse.error verbose
+
+%{
+ #include "rc.h"
+
+ int yylex(void);
+ void yyerror(const char *);
+%}
+
+/* operator precendence: lowest first */
+%left Tif Tfor Tswitch Tcase ')' Twhile Telse
+%left Tandand Toror '\n'
+%left Tbang Tsubshell
+%left Tpipe;
+%left '^';
+%right '$' Tcount Tjoin
+%left Tindex
+
+/* semantic types */
+%union{
+ struct Tree *tree;
+}
+%type<tree> line cmds cmdsln body paren block ifbody casebody case assign epilog redir;
+%type<tree> cmd basic executable nonkeyword keyword word words wordsnl atom;
+%type<tree> Tfor Tin Twhile Tif Telse Tswitch Tcase Ttwiddle Tbang Tsubshell Tfunc;
+%type<tree> Tword Tredir Tpipe Tdup;
+
+/* grammar */
+
+%start rc
+
+%%
+rc:
+/*empty*/ { return 0; }
+| line '\n' { return compile($1); }
+
+line:
+ cmd
+| cmds line { $$ = maketree2(';', $1, $2); }
+
+body:
+ cmd
+| cmdsln body { $$ = maketree2(';', $1, $2); }
+
+paren:
+ '(' body ')' { $$ = maketree1(Tparen, $2); }
+
+block:
+ '{' body '}' { $$ = maketree1(Tblock, $2); }
+
+cmds:
+ cmd ';'
+| cmd '&' { $$ = maketree1('&', $1); }
+
+cmdsln:
+ cmds
+| cmd '\n'
+
+ifbody:
+ cmd %prec Telse { $$ = maketree2(Tif, nil, $1); }
+| block Telse nl cmd { $$ = maketree3(Tif, nil, $1, $2); }
+
+case:
+ Tcase words ';' { $$ = hangchild1($1, $2, 0); }
+| Tcase words '\n' { $$ = hangchild1($1, $2, 0); }
+
+casebody:
+ cmd { $$ = maketree2(Tcasebody, $1, nil); }
+| case casebody { $$ = maketree2(Tcasebody, $1, $2); }
+| cmdsln casebody { $$ = maketree2(Tcasebody, $1, $2); }
+
+assign:
+ executable '=' word { $$ = maketree2('=', $1, $3); }
+
+redir:
+ Tdup
+| Tredir word { $$ = hangchild1($1, $2, 0); }
+
+epilog:
+/* empty */ { $$ = nil; }
+| redir epilog { $$ = hangchild1($1, $2, 1); }
+
+cmd:
+/* empty */ %prec Twhile { $$ = nil; }
+| basic { $$ = maketree1(Tbasic, $1); }
+| block epilog { $$ = hangepilog($1, $2); }
+| cmd Tpipe nl cmd { $$ = hangchild2($2, $1, 0, $4, 1); }
+| cmd Tandand nl cmd { $$ = maketree2(Tandand, $1, $4); }
+| cmd Toror nl cmd { $$ = maketree2(Toror, $1, $4); }
+| redir cmd %prec Tbang { $$ = hangchild1($1, $2, 1); }
+| assign cmd %prec Tbang { $$ = hangchild1($1, $2, 2); }
+| Tbang cmd { $$ = maketree1(Tbang, $2); }
+| Tsubshell cmd { $$ = maketree1(Tsubshell, $2); }
+| Tfor '(' word ')' nl cmd { $$ = hangchild3($1, $3, nil, $6); }
+| Tfor '(' word Tin words ')' nl cmd { $$ = hangchild3($1, $3, $5, $8); }
+| Twhile paren nl cmd { $$ = hangchild2($1, $2, 0, $4, 1); }
+| Tif paren nl ifbody { $$ = hangchild1($2, $1, 0); }
+| Tswitch '(' word ')' nl '{' casebody '}' { $$ = hangchild2($1, $3, 0, $7, 1); }
+
+basic:
+ executable
+| basic word { $$ = maketree2(Targs, $1, $2); }
+| basic redir { $$ = maketree2(Targs, $1, $2); }
+
+atom:
+ nonkeyword
+| keyword { $$ = maketree1(Tword, $1); }
+
+word:
+ atom
+| word '^' atom { $$ = maketree2('^', $1, $3); }
+
+executable:
+ nonkeyword
+| executable '^' atom { $$ = maketree2('^', $1, $3); }
+
+nonkeyword:
+ Tword
+| '$' atom { $$ = maketree1('$', $2); }
+| '$' atom Tindex words ')' { $$ = maketree2(Tindex, $2, $4); }
+| '(' wordsnl ')' { $$ = $2; }
+| Tcount atom { $$ = maketree1(Tcount, $2); }
+| Tjoin atom { $$ = maketree1(Tjoin, $2); }
+| '`' block { $$ = maketree1('`', $2); }
+//| Tredir block { $$ = hangchild1($1, $2, 0); $$->type = Tpipefd; }
+
+keyword:
+ Tfor|Tin|Twhile|Tif|Telse|Tswitch|Tcase|Tbang|Tsubshell|Tfunc
+
+words:
+/* empty */ { $$ = nil; }
+| words word { $$ = maketree2(Twords, $1, $2); }
+
+wordsnl:
+/* empty */ { $$ = nil; }
+| wordsnl '\n' /* empty */
+| wordsnl word {$$ = (!$1) ? ((!$2) ? nil : $2) : ((!$2) ? $1 : maketree2(Twords, $1, $2)); }
+
+nl:
+/*empty*/
+| nl '\n'
+
+%%
diff --git a/src/cmd/rc/sys.c b/src/cmd/rc/sys.c
new file mode 100644
index 0000000..807359d
--- /dev/null
+++ b/src/cmd/rc/sys.c
@@ -0,0 +1,137 @@
+#include "rc.h"
+
+// -----------------------------------------------------------------------
+// internal
+
+static
+char**
+mkargv(Word *args)
+{
+ char **argv=emalloc((count(args)+2)*sizeof(char *));
+ char **argp=argv+1; /* leave one at front for runcoms */
+
+ for(;args;args=args->link)
+ *argp++=args->str;
+ *argp=nil;
+
+ return argv;
+}
+
+static
+Word*
+envval(char *s)
+{
+ Word *v;
+ char *t, c;
+
+ for(t=s; *t && *t!='\1'; t++)
+ ;
+
+ c = *t;
+ *t = '\0';
+
+ v = makeword(s, (c=='\0') ? nil : envval(t+1));
+ *t=c;
+
+ return v;
+}
+
+// -----------------------------------------------------------------------
+// exported
+
+void
+initenv(void)
+{
+ extern char **environ;
+
+ char *s;
+ char **env;
+
+ for(env=environ; *env; env++) {
+ for(s=*env; *s && *s != '(' && *s != '='; s++)
+ ;
+ switch(*s){
+ case '\0':
+ break;
+ case '(': /* ignore functions */
+ break;
+ case '=':
+ *s = '\0';
+ setvar(*env, envval(s+1));
+ *s = '=';
+ break;
+ }
+ }
+}
+
+void
+execute(Word *cmd, Word *path)
+{
+ int nc;
+ char **argv = mkargv(cmd);
+ char **env = mkenv();
+ char file[1024];
+
+ for(; path; path=path->link){
+ nc = strlen(path->str);
+ if(nc < arrlen(file)){
+ strcpy(file, path->str);
+ if(file[0]){
+ strcat(file, "/");
+ nc++;
+ }
+ if(nc+strlen(argv[1]) < arrlen(file)){
+ strcat(file, argv[1]);
+ execve(file, argv+1, env);
+ }else
+ fatal("command name too long");
+ }
+ }
+ print(shell.err, "could not execute command: %s\n", argv[1]);
+ efree(argv);
+}
+
+void
+redirect(Redir *r)
+{
+ if(r){
+ redirect(r->link);
+ switch(r->type){
+ case Ropen:
+ if(r->from != r->to){
+ dup2(r->from, r->to);
+ close(r->from);
+ }
+ break;
+ case Rdupfd:
+ dup2(r->from, r->to); // TODO: error checking
+ break;
+ case Rclose:
+ close(r->from);
+ break;
+ default:
+ fatal("unrecognized redirection type %d\n", r->type);
+ }
+ }
+}
+
+int
+mapfd(int fd)
+{
+ Redir *r;
+ for(r = runner->redir.end; r; r = r->link){
+ switch(r->type){
+ case Rclose:
+ if(r->from == fd)
+ fd = -1;
+ break;
+ case Rdupfd:
+ case Ropen:
+ if(r->to == fd)
+ fd = r->from;
+ break;
+ }
+ }
+
+ return fd;
+}
diff --git a/src/cmd/rc/tree.c b/src/cmd/rc/tree.c
new file mode 100644
index 0000000..2c65041
--- /dev/null
+++ b/src/cmd/rc/tree.c
@@ -0,0 +1,111 @@
+#include "rc.h"
+#include "parse.h"
+
+static Tree *nodes;
+
+Tree*
+maketree(void)
+{
+ Tree *node = emalloc(sizeof(*node));
+
+ node->link = nodes;
+ nodes = node;
+ return node;
+}
+
+void
+freeparsetree(void)
+{
+ Tree *t, *nt;
+ for(t = nodes; t; t = nt) {
+ nt = t->link;
+ if(t->type == Tword && t->str)
+ efree(t->str);
+ efree(t);
+ }
+ nodes = nil;
+}
+
+Tree*
+maketree1(int type, Tree *c0)
+{
+ return maketree2(type, c0, nil);
+}
+
+Tree*
+maketree2(int type, Tree *c0, Tree *c1)
+{
+ return maketree3(type, c0, c1, nil);
+}
+
+Tree*
+maketree3(int type, Tree *c0, Tree *c1, Tree *c2)
+{
+ Tree *node = maketree();
+
+ node->type = type;
+ node->child[0] = c0;
+ node->child[1] = c1;
+ node->child[2] = c2;
+
+ return node;
+}
+
+Tree*
+hangchild1(Tree *node, Tree *c, int i)
+{
+ node->child[i] = c;
+
+ return node;
+}
+
+Tree*
+hangchild2(Tree *node, Tree *c1, int i1, Tree *c2, int i2)
+{
+ node->child[i1] = c1;
+ node->child[i2] = c2;
+
+ return node;
+}
+
+Tree*
+hangchild3(Tree *node, Tree *c0, Tree *c1, Tree *c2)
+{
+ node->child[0] = c0;
+ node->child[1] = c1;
+ node->child[2] = c2;
+
+ return node;
+}
+
+Tree*
+hangepilog(Tree *cmd, Tree *epi)
+{
+ Tree *p;
+ if(!epi)
+ return cmd;
+ for(p = epi; p->child[1]; p = p->child[1])
+ ;
+
+ p->child[1] = cmd;
+ return epi;
+}
+
+Tree*
+token(int type, char *s)
+{
+ Tree *node = maketree();
+
+ node->type = type;
+ node->str = strdup(s);
+
+ return node;
+}
+
+/*
+Tree*
+basic(Tree *node)
+{
+ return maketree1(Tbasic, node);
+}
+*/
diff --git a/src/cmd/rc/util.c b/src/cmd/rc/util.c
new file mode 100644
index 0000000..b0be788
--- /dev/null
+++ b/src/cmd/rc/util.c
@@ -0,0 +1,65 @@
+#include "rc.h"
+
+void
+fatal(char *msg, ...)
+{
+ va_list args;
+ vfprintf(stderr, msg, args);
+ va_end(args);
+
+ abort();
+}
+
+void*
+emalloc(uintptr n)
+{
+ void *p;
+ if(!(p = malloc(n)))
+ fatal("out of memory: can't allocate %d bytes", n);
+
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, uintptr n)
+{
+ void *r;
+ if(!(r = realloc(p,n)))
+ fatal("out of memory: can't reallocate %d bytes", n);
+
+ return r;
+}
+
+void
+efree(void *p)
+{
+ if(p)
+ free(p);
+ // TODO: log the double free
+}
+
+
+char *bp;
+
+static
+void
+iacvt(int n)
+{
+ if(n<0){
+ *bp++='-';
+ n=-n; /* doesn't work for n==-inf */
+ }
+ if(n/10)
+ iacvt(n/10);
+
+ *bp++=n%10+'0';
+}
+
+void
+itoa(char *s, long n)
+{
+ bp = s;
+ iacvt(n);
+ *bp='\0';
+}
diff --git a/src/cmd/rc/var.c b/src/cmd/rc/var.c
new file mode 100644
index 0000000..3e9635f
--- /dev/null
+++ b/src/cmd/rc/var.c
@@ -0,0 +1,336 @@
+#include "rc.h"
+#include "parse.h"
+
+// TODO: string interning
+
+// -----------------------------------------------------------------------
+// globals
+
+struct Keyword
+{
+ char *name;
+ int type;
+};
+
+static Var *globals[512];
+static struct Keyword keywords[100]; // sparse map means less hits
+
+// -----------------------------------------------------------------------
+// internals
+
+static
+int
+hash(char *s, int len)
+{
+ int h =0, i = 1;
+ while(*s)
+ h += *s++*i++;
+
+ h %= len;
+ return h < 0 ? h+len : h;
+}
+
+static
+void
+·setvar(char *name, Word *val, int call)
+{
+ Var *v = var(name);
+ freeword(v->val);
+
+ v->val = val;
+ v->new = 1; // this never turns off?
+
+ if(call && v->update)
+ v->update(v);
+}
+
+static
+char*
+list2strcolon(Word *words)
+{
+ char *value, *s, *t;
+ int len = 0;
+ Word *ap;
+ for(ap = words;ap;ap = ap->link)
+ len+=1+strlen(ap->str);
+
+ value = emalloc(len+1);
+
+ s = value;
+ for(ap = words; ap; ap = ap->link){
+ for(t = ap->str;*t;) *s++=*t++;
+ *s++=':';
+ }
+
+ if(s==value)
+ *s='\0';
+ else
+ s[-1]='\0';
+
+ return value;
+}
+
+static
+void
+littlepath(Var *v)
+{
+ /* convert $path to $PATH */
+ char *p;
+ Word *w;
+
+ p = list2strcolon(v->val);
+ w = emalloc(sizeof(*w));
+ w->str = p;
+ w->link = nil;
+
+ ·setvar("PATH", w, 1);
+}
+
+static
+void
+bigpath(Var *v)
+{
+ /* convert $PATH to $path */
+ char *p, *q;
+ Word **l, *w;
+
+ if(v->val == nil){
+ ·setvar("path", nil, 0);
+ return;
+ }
+
+ p = v->val->str;
+ w = nil;
+ l = &w;
+
+ /* Doesn't handle escaped colon nonsense. */
+ if(p[0] == 0)
+ p = nil;
+
+ while(p){
+ q = strchr(p, ':');
+ if(q)
+ *q = 0;
+
+ *l = makeword(p[0] ? p : ".", nil);
+ l = &(*l)->link;
+
+ if(q){
+ *q = ':';
+ p = q+1;
+ }else
+ p = nil;
+ }
+ ·setvar("path", w, 0);
+}
+
+// -----------------------------------------------------------------------
+// exports
+
+Var*
+makevar(char *name, Var *link)
+{
+ Var *v = emalloc(sizeof(*v));
+
+ v->name = name;
+ v->val = 0;
+ v->new = 0;
+ v->newfunc = 0;
+ v->link = link;
+ v->func = nil;
+ v->update = nil;
+
+ return v;
+}
+
+void
+setvar(char *name, Word *val)
+{
+ ·setvar(name, val, 1);
+}
+
+Var*
+definevar(char *name, Var *link)
+{
+ Var *v = emalloc(sizeof(*v));
+
+ v->name = name;
+ v->val = 0;
+ v->link = link;
+
+ return v;
+}
+
+Var*
+globalvar(char *name)
+{
+ int h;
+ Var *v;
+
+ h = hash(name, arrlen(globals));
+
+ if(strcmp(name,"PATH")==0){
+ flush(shell.err);
+ }
+
+ for(v = globals[h]; v; v = v->link){
+ if(strcmp(v->name, name) == 0){
+ return v;
+ }
+ }
+
+ return globals[h] = definevar(strdup(name), globals[h]);
+}
+
+Var*
+var(char *name)
+{
+ Var *v;
+ if(runner){
+ for(v = runner->local; v; v=v->link)
+ if(strcmp(v->name, name) == 0)
+ return v;
+ }
+ return globalvar(name);
+}
+
+static
+int
+cmpenv(const void *a, const void *b)
+{
+ return strcmp(*(char**)a, *(char**)b);
+}
+
+char**
+mkenv(void)
+{
+ char **env, **ep, *p, *q;
+ Var **h, *v;
+ Word *a;
+ int nvar=0, nchr=0, sep;
+
+#define BODY \
+ if((v==var(v->name)) && v->val){ \
+ nvar++; \
+ nchr+=strlen(v->name)+1; \
+ for(a=v->val;a;a=a->link) \
+ nchr+=strlen(a->str)+1; \
+ }
+
+ for(v= runner->local; v; v=v->link){
+ BODY
+ }
+ for(h=globals; h!=arrend(globals); h++){
+ for(v = *h; v; v=v->link){
+ BODY
+ }
+ }
+
+#undef BODY
+
+ env=emalloc((nvar+1)*sizeof(*env)+nchr);
+ ep=env;
+ p=(char *)&env[nvar+1];
+
+#define BODY \
+ if((v==var(v->name)) && v->val){ \
+ *ep++=p; \
+ q=v->name; \
+ while(*q) \
+ *p++=*q++; \
+ sep='='; \
+ for(a=v->val;a;a=a->link){ \
+ *p++=sep; \
+ sep='\1'; \
+ q=a->str; \
+ while(*q) \
+ *p++=*q++; \
+ } \
+ *p++='\0'; \
+ }
+
+ for(v=runner->local; v; v=v->link){
+ BODY
+ }
+ for(h=globals; h!=arrend(globals); h++){
+ for(v = *h; v; v=v->link){
+ BODY
+ }
+ }
+#undef BODY
+
+ *ep=0;
+
+ qsort((char *)env, nvar, sizeof ep[0], cmpenv);
+ return env;
+}
+
+void
+initpath(void)
+{
+ Var *v;
+
+ v = globalvar("path");
+ v->update = littlepath;
+
+ v = globalvar("PATH");
+ v->update = bigpath;
+
+ flush(shell.err);
+ bigpath(v);
+}
+
+#define KEYWORDS \
+ KEYWORD("for", Tfor) \
+ KEYWORD("in", Tin) \
+ KEYWORD("while", Twhile) \
+ KEYWORD("if", Tif) \
+ KEYWORD("else", Telse) \
+ KEYWORD("switch", Tswitch) \
+ KEYWORD("case", Tcase) \
+ KEYWORD("!", Tbang) \
+ KEYWORD("@", Tsubshell) \
+ KEYWORD("func", Tfunc)
+
+void
+initkeywords(void)
+{
+ int i, s, j, h;
+#define KEYWORD(a, b) a,
+ static char *name[] = { KEYWORDS };
+#undef KEYWORD
+#define KEYWORD(a, b) b,
+ static int type[] = { KEYWORDS };
+#undef KEYWORD
+
+ for(i = 0; i < arrlen(type); i++){
+ h = hash(name[i], arrlen(keywords));
+ for(s=0; s < arrlen(keywords); s++){
+ j = (h + s) % arrlen(keywords);
+ if(!keywords[j].type || strcmp(keywords[j].name, name[i]) == 0){
+ keywords[j].name = name[i];
+ keywords[j].type = type[i];
+ goto nextkeyword;
+ }
+ }
+ nextkeyword:;
+ }
+}
+
+int
+iskeyword(char *word)
+{
+ int i, s, h;
+
+ h = hash(word, arrlen(keywords));
+ for(s = 0; s < arrlen(keywords); s++){
+ i = (h + s) % arrlen(keywords);
+ if(!keywords[i].type)
+ return 0;
+ if(strcmp(keywords[i].name, word) == 0)
+ return keywords[i].type;
+ }
+ return 0;
+}
+
+#undef KEYWORDS
diff --git a/src/cmd/rc/wait.c b/src/cmd/rc/wait.c
new file mode 100644
index 0000000..911601c
--- /dev/null
+++ b/src/cmd/rc/wait.c
@@ -0,0 +1,247 @@
+#include "rc.h"
+
+#include <sys/wait.h>
+
+// -----------------------------------------------------------------------
+// globals
+
+struct WaitMsg
+{
+ int pid;
+ int type;
+ ulong time[3];
+ int status;
+};
+
+// -----------------------------------------------------------------------
+// internal
+
+static
+int
+await(int pid4, int opt, struct WaitMsg *msg)
+{
+ int pid, status, core;
+ struct rusage ru;
+ ulong u, s;
+
+ /* event loop */
+ for(;;){
+ if((pid = wait4(pid4, &status, opt, &ru)) <= 0){
+ if(errno == ECHILD){
+ msg->pid = -1;
+ return 1;
+ }
+ msg->pid = 0;
+ perror("failed wait4");
+ return 0;
+ }
+
+ u = ru.ru_utime.tv_sec*1000+((ru.ru_utime.tv_usec+500)/1000);
+ s = ru.ru_stime.tv_sec*1000+((ru.ru_stime.tv_usec+500)/1000);
+
+ if(WIFEXITED(status)){
+ msg->pid = pid;
+ msg->time[0] = u;
+ msg->time[1] = s;
+ msg->time[2] = u+s;
+ msg->status = WEXITSTATUS(status);
+ msg->type = Pdone;
+
+ return 1;
+ }
+
+ if(WIFSIGNALED(status)){
+ msg->pid = pid;
+ msg->time[0] = u;
+ msg->time[1] = s;
+ msg->time[2] = u+s;
+ msg->status = WTERMSIG(status);
+ msg->type = Psig;
+
+ return 1;
+ }
+
+ if(WIFSTOPPED(status)){
+ msg->pid = pid;
+ msg->time[0] = u;
+ msg->time[1] = s;
+ msg->time[2] = u+s;
+ msg->status = WSTOPSIG(status);
+ msg->type = Pstop;
+
+ return 1;
+ }
+ }
+}
+
+static
+int
+shouldwait(Thread *job)
+{
+ int i;
+
+ for(i=0; i<job->wait.len; i++){
+ if(job->wait.on[i].status == Prun)
+ return 1;
+ }
+
+ return 0;
+}
+
+static inline
+void
+notify(Thread *job, struct WaitMsg msg)
+{
+ int i;
+ for(i=0; i < job->wait.len; i++){
+ if(job->wait.on[i].pid == msg.pid){
+ job->status = msg.status;
+ switch(msg.type){
+ case Pstop:
+ print(shell.err, "%d: suspended\n", msg.pid);
+ job->wait.status = Pstop;
+ job->wait.on[i].status = Pstop;
+ break;
+
+ case Psig:
+ print(shell.err, "%d: terminated by signal %d\n", msg.pid, msg.status);
+ /* fallthrough */
+ case Pdone:
+ job->wait.on[i].status = Pdone;
+ delwait(job, msg.pid);
+ if(!job->wait.len)
+ job->wait.status = Pdone;
+ break;
+
+ default:
+ fatal("%d: unrecognized message type %d\n", msg.pid, msg.type);
+ }
+ break;
+ }
+ }
+}
+
+// -----------------------------------------------------------------------
+// exported
+
+void
+clearwait(Thread *job)
+{
+ job->wait.len = 0;
+}
+
+int
+havewait(Thread *job, int pid)
+{
+ int i;
+
+ for(i=0; i<job->wait.len; i++)
+ if(job->wait.on[i].pid == pid)
+ return 1;
+ return 0;
+}
+
+void
+addwait(Thread *job, int pid)
+{
+ if(job->wait.len == job->wait.cap){
+ job->wait.cap = job->wait.cap + 2;
+ job->wait.on = erealloc(job->wait.on, job->wait.cap*sizeof(*job->wait.on));
+ }
+
+ job->wait.on[job->wait.len++] = (struct WaitItem){.pid=pid, .status=Prun};
+}
+
+void
+delwait(Thread *job, int pid)
+{
+ int r, w;
+
+ for(r=w=0; r < job->wait.len; r++){
+ if(job->wait.on[r].pid != pid)
+ job->wait.on[w++].pid = job->wait.on[r].pid;
+ }
+ job->wait.len = w;
+}
+
+int
+waitall(Thread *job)
+{
+ int i;
+ Thread *t;
+ struct WaitMsg msg;
+
+ while(shouldwait(job) && await(-job->pgid, WUNTRACED, &msg)){
+ switch(msg.pid){
+ case 0: // error
+ perror("wait job");
+ return 0;
+ case -1: // no children: assume they have exited
+ job->wait.status = Pdone;
+ clearwait(job);
+ return 1;
+ default:
+ ;
+ }
+
+ notify(job, msg);
+ }
+ return 1;
+}
+
+int
+waitfor(Thread *job, int pid)
+{
+ int i;
+ Thread *t;
+ struct WaitMsg msg;
+
+ while(shouldwait(job) && await(-job->pgid, WUNTRACED, &msg)){
+ switch(msg.pid){
+ case 0: // error
+ perror("wait for");
+ return 0;
+ case -1: // no children: assume they have exited
+ job->wait.status = Pdone;
+ clearwait(job);
+ return 1;
+ default:
+ ;
+ }
+
+ notify(job, msg);
+ /* allow for an early exit */
+ if(msg.pid == pid)
+ return 1;
+ }
+ return 1;
+
+}
+
+void
+killzombies(void)
+{
+ Thread *job;
+ int index, status, pid;
+
+ while((pid=waitpid(-1, &status, WNOHANG))>0){
+ print(shell.err, "found zombie pid %d\n", pid);
+ flush(shell.err);
+
+ job = getjob(pid, &index);
+ if(!job)
+ perror("invalid pid");
+
+ if(WIFEXITED(status))
+ job->wait.status = Pdone;
+ if(WIFSTOPPED(status))
+ job->wait.status = Pstop;
+ if(WIFCONTINUED(status))
+ job->wait.status = Pagain;
+
+ if(job->wait.status == Pdone){
+ report(job,index);
+ deljob(job);
+ }
+ }
+}
diff --git a/src/cmd/rules.mk b/src/cmd/rules.mk
new file mode 100644
index 0000000..72cd0ce
--- /dev/null
+++ b/src/cmd/rules.mk
@@ -0,0 +1,32 @@
+include share/push.mk
+
+# Iterate through subdirectory tree
+
+# DIR := $(d)/cc
+# include $(DIR)/rules.mk
+
+DIR := $(d)/rc
+include $(DIR)/rules.mk
+
+DIR := $(d)/walk
+include $(DIR)/rules.mk
+
+DIR := $(d)/filter
+include $(DIR)/rules.mk
+
+DIR := $(d)/ic
+include $(DIR)/rules.mk
+
+DIR := $(d)/dwm
+include $(DIR)/rules.mk
+
+DIR := $(d)/menu
+include $(DIR)/rules.mk
+
+DIR := $(d)/term
+include $(DIR)/rules.mk
+
+# DIR := $(d)/wm
+# include $(DIR)/rules.mk
+
+include share/pop.mk
diff --git a/src/cmd/term/LICENSE b/src/cmd/term/LICENSE
new file mode 100644
index 0000000..c356c39
--- /dev/null
+++ b/src/cmd/term/LICENSE
@@ -0,0 +1,34 @@
+MIT/X Consortium License
+
+© 2014-2018 Hiltjo Posthuma <hiltjo at codemadness dot org>
+© 2018 Devin J. Pohly <djpohly at gmail dot com>
+© 2014-2017 Quentin Rameau <quinq at fifth dot space>
+© 2009-2012 Aurélien APTEL <aurelien dot aptel at gmail dot com>
+© 2008-2017 Anselm R Garbe <garbeam at gmail dot com>
+© 2012-2017 Roberto E. Vargas Caballero <k0ga at shike2 dot com>
+© 2012-2016 Christoph Lohmann <20h at r-36 dot net>
+© 2013 Eon S. Jeon <esjeon at hyunmu dot am>
+© 2013 Alexander Sedov <alex0player at gmail dot com>
+© 2013 Mark Edgar <medgar123 at gmail dot com>
+© 2013-2014 Eric Pruitt <eric.pruitt at gmail dot com>
+© 2013 Michael Forney <mforney at mforney dot org>
+© 2013-2014 Markus Teich <markus dot teich at stusta dot mhn dot de>
+© 2014-2015 Laslo Hunhold <dev at frign dot de>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/src/cmd/term/config.h b/src/cmd/term/config.h
new file mode 100644
index 0000000..a740ecf
--- /dev/null
+++ b/src/cmd/term/config.h
@@ -0,0 +1,474 @@
+/* See LICENSE file for copyright and license details. */
+#define VERSION "1"
+
+/*
+ * appearance
+ *
+ * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
+ */
+static char *font = "consolas:size=16";
+static int borderpx = 2;
+
+/*
+ * What program is execed by st depends of these precedence rules:
+ * 1: program passed with -e
+ * 2: scroll and/or utmp
+ * 3: SHELL environment variable
+ * 4: value of shell in /etc/passwd
+ * 5: value of shell in config.h
+ */
+static char *shell = "/bin/mksh";
+char *utmp = nil;
+/* scroll program: to enable use a string like "scroll" */
+char *scroll = nil;
+char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
+
+/* identification sequence returned in DA and DECID */
+char *vtiden = "\033[?6c";
+
+/* Kerning / character bounding-box multipliers */
+static float cwscale = 1.0;
+static float chscale = 1.0;
+
+/*
+ * word delimiter string
+ *
+ * More advanced example: L" `'\"()[]{}"
+ */
+wchar_t *worddelimiters = L" ";
+
+/* selection timeouts (in milliseconds) */
+static uint doubleclicktimeout = 300;
+static uint tripleclicktimeout = 600;
+
+/* alt screens */
+int allowaltscreen = 1;
+
+/* allow certain non-interactive (insecure) window operations such as:
+ setting the clipboard text */
+int allowwindowops = 0;
+
+/*
+ * draw latency range in ms - from new content/keypress/etc until drawing.
+ * within this range, st draws when content stops arriving (idle). mostly it's
+ * near minlatency, but it waits longer for slow updates to avoid partial draw.
+ * low minlatency will tear/flicker more, as it can "detect" idle too early.
+ */
+static double minlatency = 8;
+static double maxlatency = 33;
+
+/*
+ * blinking timeout (set to 0 to disable blinking) for the terminal blinking
+ * attribute.
+ */
+static uint blinktimeout = 800;
+
+/*
+ * thickness of underline and bar cursors
+ */
+static uint cursorthickness = 2;
+
+/*
+ * bell volume. It must be a value between -100 and 100. Use 0 for disabling
+ * it
+ */
+static int bellvolume = 0;
+
+/* default TERM value */
+char *termname = "term-256color";
+
+/*
+ * spaces per tab
+ *
+ * When you are changing this value, don't forget to adapt the »it« value in
+ * the st.info and appropriately install the st.info in the environment where
+ * you use this st version.
+ *
+ * it#$tabspaces,
+ *
+ * Secondly make sure your kernel is not expanding tabs. When running `stty
+ * -a` »tab0« should appear. You can tell the terminal to not expand tabs by
+ * running following command:
+ *
+ * stty tabs
+ */
+uint tabspaces = 4;
+
+/* bg opacity */
+float alpha = 0.98;
+
+/* Terminal colors (16 first used in escape sequence) */
+static char *colorname[] = {
+ "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */
+ "#ea6962", /* red */
+ "#a9b665", /* green */
+ "#d8a657", /* yellow */
+ "#7daea3", /* blue */
+ "#d3869b", /* magenta */
+ "#89b482", /* cyan */
+ "#d4be98", /* white */
+
+ "#928374", /* black */
+ "#ef938e", /* red */
+ "#bbc585", /* green */
+ "#e1bb7e", /* yellow */
+ "#9dc2ba", /* blue */
+ "#e1acbb", /* magenta */
+ "#a7c7a2", /* cyan */
+ "#e2d3ba", /* white */
+
+ [255] = 0,
+
+ /* more colors can be added after 255 to use with DefaultXX */
+ "#fbf1c7",
+ "#3c3836",
+ "#555555",
+};
+
+/*
+ * Default colors (colorname index)
+ * foreground, background, cursor, reverse cursor
+ */
+uint defaultfg = 256;
+uint defaultbg = 257;
+static uint defaultcs = 15;
+static uint defaultrcs = 258;
+
+/*
+ * Default shape of cursor
+ * 2: Block ("█")
+ * 4: Underline ("_")
+ * 6: Bar ("|")
+ * 7: Snowman ("☃")
+ */
+static uint cursorshape = 2;
+
+/*
+ * Default columns and rows numbers
+ */
+
+static uint cols = 80;
+static uint rows = 24;
+
+/*
+ * Default colour and shape of the mouse cursor
+ */
+static uint mouseshape = XC_left_ptr;
+static uint mousefg = 0;
+static uint mousebg = 7;
+
+/*
+ * Color used to display font attributes when fontconfig selected a font which
+ * doesn't match the ones requested.
+ */
+static uint defaultattr = 11;
+
+/*
+ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
+ * Note that if you want to use ShiftMask with selmasks, set this to an other
+ * modifier, set to 0 to not use it.
+ */
+static uint forcemousemod = ShiftMask;
+
+/*
+ * Internal mouse shortcuts.
+ * Beware that overloading Button1 will disable the selection.
+ */
+static MouseShortcut mshortcuts[] = {
+ /* mask button function argument release */
+ { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
+ { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
+ { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
+ { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} },
+ { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} },
+};
+
+/* Internal keyboard shortcuts. */
+#define MODKEY Mod1Mask
+#define TERMMOD (ControlMask|ShiftMask)
+
+static Shortcut shortcuts[] = {
+ /* mask keysym function argument */
+ { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
+ { ControlMask, XK_Print, toggleprinter, {.i = 0} },
+ { ShiftMask, XK_Print, printscreen, {.i = 0} },
+ { XK_ANY_MOD, XK_Print, printsel, {.i = 0} },
+ { TERMMOD, XK_plus, zoom, {.f = +1} },
+ { ControlMask, XK_minus, zoom, {.f = -1} },
+ { TERMMOD, XK_Home, zoomreset, {.f = 0} },
+ { TERMMOD, XK_C, clipcopy, {.i = 0} },
+ { TERMMOD, XK_V, clippaste, {.i = 0} },
+ { TERMMOD, XK_Y, selpaste, {.i = 0} },
+ { ShiftMask, XK_Insert, selpaste, {.i = 0} },
+ { TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
+};
+
+/*
+ * Special keys (change & recompile st.info accordingly)
+ *
+ * Mask value:
+ * * Use XK_ANY_MOD to match the key no matter modifiers state
+ * * Use XK_NO_MOD to match the key alone (no modifiers)
+ * appkey value:
+ * * 0: no value
+ * * > 0: keypad application mode enabled
+ * * = 2: term.numlock = 1
+ * * < 0: keypad application mode disabled
+ * appcursor value:
+ * * 0: no value
+ * * > 0: cursor application mode enabled
+ * * < 0: cursor application mode disabled
+ *
+ * Be careful with the order of the definitions because st searches in
+ * this table sequentially, so any XK_ANY_MOD must be in the last
+ * position for a key.
+ */
+
+/*
+ * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF)
+ * to be mapped below, add them to this array.
+ */
+static KeySym mappedkeys[] = { -1 };
+
+/*
+ * State bits to ignore when matching key or button events. By default,
+ * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored.
+ */
+static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;
+
+/*
+ * This is the huge key array which defines all compatibility to the Linux
+ * world. Please decide about changes wisely.
+ */
+static Key key[] = {
+ /* keysym mask string appkey appcursor */
+ { XK_KP_Home, ShiftMask, "\033[2J", 0, -1},
+ { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1},
+ { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1},
+ { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1},
+ { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0},
+ { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1},
+ { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1},
+ { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0},
+ { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1},
+ { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1},
+ { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0},
+ { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1},
+ { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1},
+ { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0},
+ { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1},
+ { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1},
+ { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0},
+ { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
+ { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0},
+ { XK_KP_End, ControlMask, "\033[J", -1, 0},
+ { XK_KP_End, ControlMask, "\033[1;5F", +1, 0},
+ { XK_KP_End, ShiftMask, "\033[K", -1, 0},
+ { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0},
+ { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0},
+ { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0},
+ { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0},
+ { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0},
+ { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0},
+ { XK_KP_Insert, ControlMask, "\033[L", -1, 0},
+ { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0},
+ { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
+ { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
+ { XK_KP_Delete, ControlMask, "\033[M", -1, 0},
+ { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0},
+ { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0},
+ { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0},
+ { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0},
+ { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
+ { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0},
+ { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0},
+ { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0},
+ { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0},
+ { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0},
+ { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0},
+ { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0},
+ { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0},
+ { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0},
+ { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0},
+ { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0},
+ { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0},
+ { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0},
+ { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0},
+ { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0},
+ { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0},
+ { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0},
+ { XK_Up, ShiftMask, "\033[1;2A", 0, 0},
+ { XK_Up, Mod1Mask, "\033[1;3A", 0, 0},
+ { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0},
+ { XK_Up, ControlMask, "\033[1;5A", 0, 0},
+ { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0},
+ { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0},
+ { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0},
+ { XK_Up, XK_ANY_MOD, "\033[A", 0, -1},
+ { XK_Up, XK_ANY_MOD, "\033OA", 0, +1},
+ { XK_Down, ShiftMask, "\033[1;2B", 0, 0},
+ { XK_Down, Mod1Mask, "\033[1;3B", 0, 0},
+ { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0},
+ { XK_Down, ControlMask, "\033[1;5B", 0, 0},
+ { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0},
+ { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0},
+ { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0},
+ { XK_Down, XK_ANY_MOD, "\033[B", 0, -1},
+ { XK_Down, XK_ANY_MOD, "\033OB", 0, +1},
+ { XK_Left, ShiftMask, "\033[1;2D", 0, 0},
+ { XK_Left, Mod1Mask, "\033[1;3D", 0, 0},
+ { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0},
+ { XK_Left, ControlMask, "\033[1;5D", 0, 0},
+ { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0},
+ { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0},
+ { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0},
+ { XK_Left, XK_ANY_MOD, "\033[D", 0, -1},
+ { XK_Left, XK_ANY_MOD, "\033OD", 0, +1},
+ { XK_Right, ShiftMask, "\033[1;2C", 0, 0},
+ { XK_Right, Mod1Mask, "\033[1;3C", 0, 0},
+ { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0},
+ { XK_Right, ControlMask, "\033[1;5C", 0, 0},
+ { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0},
+ { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0},
+ { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0},
+ { XK_Right, XK_ANY_MOD, "\033[C", 0, -1},
+ { XK_Right, XK_ANY_MOD, "\033OC", 0, +1},
+ { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0},
+ { XK_Return, Mod1Mask, "\033\r", 0, 0},
+ { XK_Return, XK_ANY_MOD, "\r", 0, 0},
+ { XK_Insert, ShiftMask, "\033[4l", -1, 0},
+ { XK_Insert, ShiftMask, "\033[2;2~", +1, 0},
+ { XK_Insert, ControlMask, "\033[L", -1, 0},
+ { XK_Insert, ControlMask, "\033[2;5~", +1, 0},
+ { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
+ { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
+ { XK_Delete, ControlMask, "\033[M", -1, 0},
+ { XK_Delete, ControlMask, "\033[3;5~", +1, 0},
+ { XK_Delete, ShiftMask, "\033[2K", -1, 0},
+ { XK_Delete, ShiftMask, "\033[3;2~", +1, 0},
+ { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0},
+ { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
+ { XK_BackSpace, XK_NO_MOD, "\177", 0, 0},
+ { XK_BackSpace, Mod1Mask, "\033\177", 0, 0},
+ { XK_Home, ShiftMask, "\033[2J", 0, -1},
+ { XK_Home, ShiftMask, "\033[1;2H", 0, +1},
+ { XK_Home, XK_ANY_MOD, "\033[H", 0, -1},
+ { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1},
+ { XK_End, ControlMask, "\033[J", -1, 0},
+ { XK_End, ControlMask, "\033[1;5F", +1, 0},
+ { XK_End, ShiftMask, "\033[K", -1, 0},
+ { XK_End, ShiftMask, "\033[1;2F", +1, 0},
+ { XK_End, XK_ANY_MOD, "\033[4~", 0, 0},
+ { XK_Prior, ControlMask, "\033[5;5~", 0, 0},
+ { XK_Prior, ShiftMask, "\033[5;2~", 0, 0},
+ { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
+ { XK_Next, ControlMask, "\033[6;5~", 0, 0},
+ { XK_Next, ShiftMask, "\033[6;2~", 0, 0},
+ { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0},
+ { XK_F1, XK_NO_MOD, "\033OP" , 0, 0},
+ { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0},
+ { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0},
+ { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0},
+ { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0},
+ { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0},
+ { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0},
+ { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0},
+ { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0},
+ { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0},
+ { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0},
+ { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0},
+ { XK_F3, XK_NO_MOD, "\033OR" , 0, 0},
+ { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0},
+ { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0},
+ { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0},
+ { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0},
+ { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0},
+ { XK_F4, XK_NO_MOD, "\033OS" , 0, 0},
+ { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0},
+ { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0},
+ { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0},
+ { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0},
+ { XK_F5, XK_NO_MOD, "\033[15~", 0, 0},
+ { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0},
+ { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0},
+ { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0},
+ { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0},
+ { XK_F6, XK_NO_MOD, "\033[17~", 0, 0},
+ { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0},
+ { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0},
+ { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0},
+ { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0},
+ { XK_F7, XK_NO_MOD, "\033[18~", 0, 0},
+ { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0},
+ { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0},
+ { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0},
+ { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0},
+ { XK_F8, XK_NO_MOD, "\033[19~", 0, 0},
+ { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0},
+ { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0},
+ { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0},
+ { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0},
+ { XK_F9, XK_NO_MOD, "\033[20~", 0, 0},
+ { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0},
+ { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0},
+ { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0},
+ { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0},
+ { XK_F10, XK_NO_MOD, "\033[21~", 0, 0},
+ { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0},
+ { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0},
+ { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0},
+ { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0},
+ { XK_F11, XK_NO_MOD, "\033[23~", 0, 0},
+ { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0},
+ { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0},
+ { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0},
+ { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0},
+ { XK_F12, XK_NO_MOD, "\033[24~", 0, 0},
+ { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0},
+ { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0},
+ { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0},
+ { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0},
+ { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0},
+ { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0},
+ { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0},
+ { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0},
+ { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0},
+ { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0},
+ { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0},
+ { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0},
+ { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0},
+ { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0},
+ { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0},
+ { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0},
+ { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0},
+ { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0},
+ { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0},
+ { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0},
+ { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0},
+ { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0},
+ { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0},
+ { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0},
+ { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0},
+ { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0},
+ { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0},
+};
+
+/*
+ * Selection types' masks.
+ * Use the same masks as usual.
+ * Button1Mask is always unset, to make masks match between ButtonPress.
+ * ButtonRelease and MotionNotify.
+ * If no match is found, regular selection is used.
+ */
+static uint selmasks[] = {
+ [SelRectangular] = Mod1Mask,
+};
+
+/*
+ * Printable characters in ASCII, used to estimate the advance width
+ * of single wide characters.
+ */
+static char ascii_printable[] =
+ " !\"#$%&'()*+,-./0123456789:;<=>?"
+ "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ "`abcdefghijklmnopqrstuvwxyz{|}~";
diff --git a/src/cmd/term/hb.c b/src/cmd/term/hb.c
new file mode 100644
index 0000000..4b6b42d
--- /dev/null
+++ b/src/cmd/term/hb.c
@@ -0,0 +1,147 @@
+#include "term.h"
+
+#include <X11/Xft/Xft.h>
+#include <harfbuzz/hb-ft.h>
+
+#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
+
+hb_font_t *hbfindfont(XftFont *match);
+void hbtransformsegment(XftFont *xfont, const Letter *glyph, rune *codepoints, int start, int end);
+
+typedef struct
+{
+ XftFont *match;
+ hb_font_t *font;
+} HbFontMatch;
+
+static int hbfontslen = 0;
+static HbFontMatch *hbfontcache = nil;
+
+/*
+ * Replace 0 with a list of font features, wrapped in FEATURE macro, e.g.
+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
+ */
+hb_feature_t features[] = { 0 };
+
+void
+hbunloadfonts()
+{
+ int i;
+ for(i = 0; i < hbfontslen; i++) {
+ hb_font_destroy(hbfontcache[i].font);
+ XftUnlockFace(hbfontcache[i].match);
+ }
+
+ if(hbfontcache != nil) {
+ free(hbfontcache);
+ hbfontcache = nil;
+ }
+
+ hbfontslen = 0;
+}
+
+hb_font_t *
+hbfindfont(XftFont *match)
+{
+ int i;
+ for (i = 0; i < hbfontslen; i++) {
+ if (hbfontcache[i].match == match)
+ return hbfontcache[i].font;
+ }
+
+ /* Font not found in cache, caching it now. */
+ hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1));
+ FT_Face face = XftLockFace(match);
+ hb_font_t *font = hb_ft_font_create(face, NULL);
+ if(!font)
+ fatal("failed to load Harfbuzz font.");
+
+ hbfontcache[hbfontslen].match = match;
+ hbfontcache[hbfontslen].font = font;
+ hbfontslen += 1;
+
+ return font;
+}
+
+void
+hbtransform(XftGlyphFontSpec *specs, const Letter *glyphs, size_t len, int x, int y)
+{
+ int idx, specidx, start = 0, length = 1, gstart = 0;
+ rune *runes = calloc((unsigned int)len, sizeof(hb_codepoint_t));
+
+ for(idx = 1, specidx = 1; idx < len; idx++) {
+ if(glyphs[idx].mode & Gwdummy) {
+ length += 1;
+ continue;
+ }
+
+ if(specs[specidx].font != specs[start].font
+ || GLYPHCMP(glyphs[gstart], glyphs[idx])
+ || selected(x + idx, y) != selected(x + gstart, y)
+ ) {
+ hbtransformsegment(specs[start].font, glyphs, runes, gstart, length);
+ /* reset the sequence. */
+ length = 1;
+ start = specidx;
+ gstart = idx;
+ } else {
+ length += 1;
+ }
+
+ specidx++;
+ }
+
+ /* eol */
+ hbtransformsegment(specs[start].font, glyphs, runes, gstart, length);
+
+ /* apply the transformation to glyph specs. */
+ for(idx = 0, specidx = 0; idx < len; idx++) {
+ if(glyphs[idx].mode & Gwdummy)
+ continue;
+
+ if(runes[idx] != specs[specidx].glyph)
+ ((Letter *)glyphs)[idx].mode |= Gliga;
+
+ specs[specidx++].glyph = runes[idx];
+ }
+
+ free(runes);
+}
+
+void
+hbtransformsegment(XftFont *xfont, const Letter *glyph, rune *codepoints, int start, int len)
+{
+ hb_font_t *font = hbfindfont(xfont);
+ if(!font)
+ return;
+
+ int i;
+ rune r;
+ ushort mode = USHRT_MAX;
+ hb_buffer_t *buffer = hb_buffer_create();
+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
+
+ /* Fill buffer with codepoints. */
+ for(i=start; i < (start+len); i++) {
+ r = glyph[i].u;
+ mode = glyph[i].mode;
+ if(mode & Gwdummy)
+ r = 0x0020;
+ hb_buffer_add_codepoints(buffer, &r, 1, 0, 1);
+ }
+
+ /* Shape the segment. */
+ hb_shape(font, buffer, features, sizeof(features));
+
+ /* Get new glyph info. */
+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL);
+
+ /* Write new codepoints. */
+ for(i = 0; i < len; i++) {
+ r = info[i].codepoint;
+ codepoints[start+i] = r;
+ }
+
+ /* Cleanup. */
+ hb_buffer_destroy(buffer);
+}
diff --git a/src/cmd/term/nonspacing.h b/src/cmd/term/nonspacing.h
new file mode 100644
index 0000000..5d05a3d
--- /dev/null
+++ b/src/cmd/term/nonspacing.h
@@ -0,0 +1,89 @@
+16,16,16,18,19,20,21,22,23,24,25,26,27,28,29,30,31,16,16,32,16,16,16,33,34,35,
+36,37,38,39,16,16,40,16,16,16,16,16,16,16,16,16,16,16,41,42,16,16,43,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,44,16,45,46,47,48,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,49,16,16,50,
+51,16,52,53,54,16,16,16,16,16,16,55,16,16,56,16,57,58,59,60,61,62,63,64,65,66,
+67,68,16,69,70,71,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,72,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,73,74,16,16,16,75,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,76,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,77,78,16,16,16,16,16,16,16,79,16,16,16,16,16,80,81,82,16,16,16,16,16,83,
+84,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,248,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,254,255,255,255,255,191,182,0,0,0,0,0,0,0,63,0,255,23,0,0,0,0,0,248,255,
+255,0,0,1,0,0,0,0,0,0,0,0,0,0,0,192,191,159,61,0,0,0,128,2,0,0,0,255,255,255,
+7,0,0,0,0,0,0,0,0,0,0,192,255,1,0,0,0,0,0,0,248,15,32,0,0,192,251,239,62,0,0,
+0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,255,255,255,255,
+255,7,0,0,0,0,0,0,20,254,33,254,0,12,0,0,0,2,0,0,0,0,0,0,16,30,32,0,0,12,0,0,
+64,6,0,0,0,0,0,0,16,134,57,2,0,0,0,35,0,6,0,0,0,0,0,0,16,190,33,0,0,12,0,0,
+252,2,0,0,0,0,0,0,144,30,32,64,0,12,0,0,0,4,0,0,0,0,0,0,0,1,32,0,0,0,0,0,0,17,
+0,0,0,0,0,0,192,193,61,96,0,12,0,0,0,2,0,0,0,0,0,0,144,64,48,0,0,12,0,0,0,3,0,
+0,0,0,0,0,24,30,32,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,4,92,0,0,0,0,0,0,0,0,0,0,0,
+242,7,128,127,0,0,0,0,0,0,0,0,0,0,0,0,242,31,0,63,0,0,0,0,0,0,0,0,0,3,0,0,160,
+2,0,0,0,0,0,0,254,127,223,224,255,254,255,255,255,31,64,0,0,0,0,0,0,0,0,0,0,0,
+0,224,253,102,0,0,0,195,1,0,30,0,100,32,0,32,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,0,
+0,0,28,0,0,0,12,0,0,0,12,0,0,0,0,0,0,0,176,63,64,254,15,32,0,0,0,0,0,120,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,135,1,4,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,9,0,0,0,0,0,0,64,127,
+229,31,248,159,0,0,0,0,0,0,255,127,0,0,0,0,0,0,0,0,15,0,0,0,0,0,208,23,4,0,0,
+0,0,248,15,0,3,0,0,0,60,59,0,0,0,0,0,0,64,163,3,0,0,0,0,0,0,240,207,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,247,255,253,33,16,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,
+251,0,248,0,0,0,124,0,0,0,0,0,0,223,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,
+255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,
+0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,128,247,63,0,0,0,192,0,0,0,0,0,0,0,0,0,0,3,0,68,8,0,0,96,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,255,255,3,128,0,0,0,0,192,63,0,0,128,255,3,0,
+0,0,0,0,7,0,0,0,0,0,200,51,0,0,0,0,32,0,0,0,0,0,0,0,0,126,102,0,8,16,0,0,0,0,
+0,16,0,0,0,0,0,0,157,193,2,0,0,0,0,48,64,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,0,0,0,0,0,64,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,0,0,255,255,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,110,240,0,0,0,0,0,135,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,
+0,0,0,0,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,192,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,255,
+127,0,0,0,0,0,0,128,3,0,0,0,0,0,120,38,0,32,0,0,0,0,0,0,7,0,0,0,128,239,31,0,
+0,0,0,0,0,0,8,0,3,0,0,0,0,0,192,127,0,30,0,0,0,0,0,0,0,0,0,0,0,128,211,64,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,248,7,0,0,3,0,0,0,0,0,0,24,1,0,0,0,192,
+31,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,92,0,0,64,0,0,0,0,0,
+0,0,0,0,0,248,133,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,60,176,1,0,0,48,0,0,0,
+0,0,0,0,0,0,0,248,167,1,0,0,0,0,0,0,0,0,0,0,0,0,40,191,0,0,0,0,0,0,0,0,0,0,0,
+0,224,188,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+128,255,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,12,1,0,0,0,254,7,0,0,0,0,248,121,128,0,
+126,14,0,0,0,0,0,252,127,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,191,0,0,0,
+0,0,0,0,0,0,0,252,255,255,252,109,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,180,191,0,
+0,0,0,0,0,0,0,0,163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,
+0,0,0,0,0,0,0,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,127,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,
+0,128,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,15,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,248,255,231,15,0,0,0,60,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,255,255,255,255,255,255,127,248,255,255,255,255,255,31,32,0,16,0,0,248,
+254,255,0,0,0,0,0,0,0,0,0,
+0,127,255,255,249,219,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,240,7,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,
diff --git a/src/cmd/term/rules.mk b/src/cmd/term/rules.mk
new file mode 100644
index 0000000..76701ec
--- /dev/null
+++ b/src/cmd/term/rules.mk
@@ -0,0 +1,26 @@
+include share/push.mk
+
+# local sources
+SRCS_$(d) := $(d)/term.c $(d)/x.c #$(d)/hb.c
+
+# local outputs
+BINS_$(d) := $(d)/term
+
+include share/paths.mk
+
+# Local rules
+include share/dynamic.mk
+
+$(BINS_$(d)): TCFLAGS=\
+ `$(PKG) --cflags fontconfig`\
+ `$(PKG) --cflags freetype2`
+
+$(BINS_$(d)): TCLIBS=\
+ `$(PKG) --libs fontconfig`\
+ `$(PKG) --libs freetype2`\
+ -lm -lrt -lX11 -lutil -lXft -lXrender #-lharfbuzz
+
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libutf/libutf.a $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/term/term.c b/src/cmd/term/term.c
new file mode 100644
index 0000000..50ab29c
--- /dev/null
+++ b/src/cmd/term/term.c
@@ -0,0 +1,2417 @@
+/* See LICENSE for license details. */
+#include "term.h"
+
+#include <pwd.h>
+#include <termios.h>
+#if defined(__linux)
+ #include <pty.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+ #include <util.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+ #include <libutil.h>
+#endif
+
+/* macros */
+#define IS_SET(flag) ((term.mode & (flag)) != 0)
+#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
+#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
+#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
+#define ISDELIM(u) (u && wcschr(worddelimiters, u))
+
+/* forward declare functions */
+static void execsh(char *, char **);
+static void stty(char **);
+static void sigchld(int);
+static void ttywriteraw(char *, size_t);
+
+static void csidump(void);
+static void csihandle(void);
+static void csiparse(void);
+static void csireset(void);
+static int eschandle(uchar);
+static void strdump(void);
+static void strhandle(void);
+static void strparse(void);
+static void strreset(void);
+
+static void tprinter(char *, size_t);
+static void tdumpsel(void);
+static void tdumpline(int);
+static void tdump(void);
+static void tclearregion(int, int, int, int);
+static void tcursor(int);
+static void tdeletechar(int);
+static void tdeleteline(int);
+static void tinsertblank(int);
+static void tinsertblankline(int);
+static int tlinelen(int);
+static void tmoveto(int, int);
+static void tmoveato(int, int);
+static void tnewline(int);
+static void tputtab(int);
+static void tputc(rune);
+static void treset(void);
+static void tscrollup(int, int);
+static void tscrolldown(int, int);
+static void tsetattr(int *, int);
+static void tsetchar(rune, Letter *, int, int);
+static void tsetdirt(int, int);
+static void tsetscroll(int, int);
+static void tswapscreen(void);
+static void tsetmode(int, int, int *, int);
+static int twrite(char *, int, int);
+static void tfulldirt(void);
+static void tcontrolcode(uchar );
+static void tdectest(char );
+static void tdefutf8(char);
+static int32 tdefcolor(int *, int *, int);
+static void tdeftran(char);
+static void tstrsequence(uchar);
+
+static void drawregion(int, int, int, int);
+
+static void selnormalize(void);
+static void selscroll(int, int);
+static void selsnap(int *, int *, int);
+
+static char *base64dec(char *);
+static char base64dec_getc(char **);
+
+static uintptr xwrite(int, char *, size_t);
+extern int wcwidth(wchar_t wc);
+
+/* globals */
+static Terminal term;
+static Selection sel;
+static CSIEscape csiescseq;
+static STREscape strescseq;
+static int iofd = 1;
+static int cmdfd;
+static pid_t pid;
+
+/* functions */
+uintptr
+xwrite(int fd, char *s, size_t len)
+{
+ size_t aux = len;
+ ssize_t r;
+
+ while (len > 0) {
+ r = write(fd, s, len);
+ if (r < 0)
+ return r;
+ len -= r;
+ s += r;
+ }
+
+ return aux;
+}
+
+void *
+xmalloc(size_t len)
+{
+ void *p;
+
+ if (!(p = malloc(len)))
+ fatal("malloc: %s\n", strerror(errno));
+
+ return p;
+}
+
+void *
+xrealloc(void *p, size_t len)
+{
+ if ((p = realloc(p, len)) == nil)
+ fatal("realloc: %s\n", strerror(errno));
+
+ return p;
+}
+
+char *
+xstrdup(char *s)
+{
+ if ((s = strdup(s)) == nil)
+ fatal("strdup: %s\n", strerror(errno));
+
+ return s;
+}
+
+static char base64_digits[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
+ 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+char
+base64dec_getc(char **src)
+{
+ while (**src && !isprint(**src))
+ (*src)++;
+ return **src ? *((*src)++) : '='; /* emulate padding if string ends */
+}
+
+char *
+base64dec(char *src)
+{
+ size_t in_len = strlen(src);
+ char *result, *dst;
+
+ if (in_len % 4)
+ in_len += 4 - (in_len % 4);
+ result = dst = xmalloc(in_len / 4 * 3 + 1);
+ while (*src) {
+ int a = base64_digits[(unsigned char) base64dec_getc(&src)];
+ int b = base64_digits[(unsigned char) base64dec_getc(&src)];
+ int c = base64_digits[(unsigned char) base64dec_getc(&src)];
+ int d = base64_digits[(unsigned char) base64dec_getc(&src)];
+
+ /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
+ if (a == -1 || b == -1)
+ break;
+
+ *dst++ = (a << 2) | ((b & 0x30) >> 4);
+ if (c == -1)
+ break;
+ *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
+ if (d == -1)
+ break;
+ *dst++ = ((c & 0x03) << 6) | d;
+ }
+ *dst = '\0';
+ return result;
+}
+
+void
+selinit(void)
+{
+ sel.mode = SelIdle;
+ sel.snap = 0;
+ sel.ob.x = -1;
+}
+
+int
+tlinelen(int y)
+{
+ int i = term.col;
+
+ if (term.line[y][i - 1].mode & Gwrap)
+ return i;
+
+ while (i > 0 && term.line[y][i - 1].u == ' ')
+ --i;
+
+ return i;
+}
+
+void
+selstart(int col, int row, int snap)
+{
+ selclear();
+ sel.mode = SelEmpty;
+ sel.type = SelRegular;
+ sel.alt = IS_SET(Taltscreen);
+ sel.snap = snap;
+ sel.oe.x = sel.ob.x = col;
+ sel.oe.y = sel.ob.y = row;
+ selnormalize();
+
+ if (sel.snap != 0)
+ sel.mode = SelReady;
+ tsetdirt(sel.nb.y, sel.ne.y);
+}
+
+void
+selextend(int col, int row, int type, int done)
+{
+ int oldey, oldex, oldsby, oldsey, oldtype;
+
+ if (sel.mode == SelIdle)
+ return;
+ if (done && sel.mode == SelEmpty) {
+ selclear();
+ return;
+ }
+
+ oldey = sel.oe.y;
+ oldex = sel.oe.x;
+ oldsby = sel.nb.y;
+ oldsey = sel.ne.y;
+ oldtype = sel.type;
+
+ sel.oe.x = col;
+ sel.oe.y = row;
+ selnormalize();
+ sel.type = type;
+
+ if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SelEmpty)
+ tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
+
+ sel.mode = done ? SelIdle : SelReady;
+}
+
+void
+selnormalize(void)
+{
+ int i;
+
+ if (sel.type == SelRegular && sel.ob.y != sel.oe.y) {
+ sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
+ sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
+ } else {
+ sel.nb.x = MIN(sel.ob.x, sel.oe.x);
+ sel.ne.x = MAX(sel.ob.x, sel.oe.x);
+ }
+ sel.nb.y = MIN(sel.ob.y, sel.oe.y);
+ sel.ne.y = MAX(sel.ob.y, sel.oe.y);
+
+ selsnap(&sel.nb.x, &sel.nb.y, -1);
+ selsnap(&sel.ne.x, &sel.ne.y, +1);
+
+ /* expand selection over line breaks */
+ if (sel.type == SelRectangular)
+ return;
+ i = tlinelen(sel.nb.y);
+ if (i < sel.nb.x)
+ sel.nb.x = i;
+ if (tlinelen(sel.ne.y) <= sel.ne.x)
+ sel.ne.x = term.col - 1;
+}
+
+int
+selected(int x, int y)
+{
+ if(sel.mode == SelEmpty || sel.ob.x == -1 ||
+ sel.alt != IS_SET(Taltscreen))
+ return 0;
+
+ if(sel.type == SelRectangular)
+ return BETWEEN(y, sel.nb.y, sel.ne.y)
+ && BETWEEN(x, sel.nb.x, sel.ne.x);
+
+ return BETWEEN(y, sel.nb.y, sel.ne.y)
+ && (y != sel.nb.y || x >= sel.nb.x)
+ && (y != sel.ne.y || x <= sel.ne.x);
+}
+
+void
+selsnap(int *x, int *y, int direction)
+{
+ int newx, newy, xt, yt;
+ int delim, prevdelim;
+ Letter *gp, *prevgp;
+
+ switch (sel.snap) {
+ case SnapWord:
+ /*
+ * Snap around if the word wraps around at the end or
+ * beginning of a line.
+ */
+ prevgp = &term.line[*y][*x];
+ prevdelim = ISDELIM(prevgp->u);
+ for (;;) {
+ newx = *x + direction;
+ newy = *y;
+ if (!BETWEEN(newx, 0, term.col - 1)) {
+ newy += direction;
+ newx = (newx + term.col) % term.col;
+ if (!BETWEEN(newy, 0, term.row - 1))
+ break;
+
+ if (direction > 0)
+ yt = *y, xt = *x;
+ else
+ yt = newy, xt = newx;
+ if (!(term.line[yt][xt].mode & Gwrap))
+ break;
+ }
+
+ if (newx >= tlinelen(newy))
+ break;
+
+ gp = &term.line[newy][newx];
+ delim = ISDELIM(gp->u);
+ if (!(gp->mode & Gwdummy) && (delim != prevdelim
+ || (delim && gp->u != prevgp->u)))
+ break;
+
+ *x = newx;
+ *y = newy;
+ prevgp = gp;
+ prevdelim = delim;
+ }
+ break;
+ case SnapLine:
+ /*
+ * Snap around if the the previous line or the current one
+ * has set ATTR_WRAP at its end. Then the whole next or
+ * previous line will be selected.
+ */
+ *x = (direction < 0) ? 0 : term.col - 1;
+ if (direction < 0) {
+ for (; *y > 0; *y += direction) {
+ if (!(term.line[*y-1][term.col-1].mode
+ & Gwrap)) {
+ break;
+ }
+ }
+ } else if (direction > 0) {
+ for (; *y < term.row-1; *y += direction) {
+ if (!(term.line[*y][term.col-1].mode
+ & Gwrap)) {
+ break;
+ }
+ }
+ }
+ break;
+ }
+}
+
+char *
+getsel(void)
+{
+ char *str, *ptr;
+ int y, bufsize, lastx, linelen;
+ Letter *gp, *last;
+
+ if (sel.ob.x == -1)
+ return nil;
+
+ bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTFmax;
+ ptr = str = xmalloc(bufsize);
+
+ /* append every set & selected glyph to the selection */
+ for(y = sel.nb.y; y <= sel.ne.y; y++) {
+ if((linelen = tlinelen(y)) == 0) {
+ *ptr++ = '\n';
+ continue;
+ }
+
+ if(sel.type == SelRectangular) {
+ gp = &term.line[y][sel.nb.x];
+ lastx = sel.ne.x;
+ }else{
+ gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
+ lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
+ }
+ last = &term.line[y][MIN(lastx, linelen-1)];
+ while (last >= gp && last->u == ' ')
+ --last;
+
+ for ( ; gp <= last; ++gp) {
+ if (gp->mode & Gwdummy)
+ continue;
+
+ ptr += utf8·encode(&gp->u, ptr);
+ }
+
+ /*
+ * Copy and pasting of line endings is inconsistent
+ * in the inconsistent terminal and GUI world.
+ * The best solution seems like to produce '\n' when
+ * something is copied from st and convert '\n' to
+ * '\r', when something to be pasted is received by
+ * st.
+ * FIXME: Fix the computer world.
+ */
+ if ((y < sel.ne.y || lastx >= linelen) &&
+ (!(last->mode & Gwrap) || sel.type == SelRectangular))
+ *ptr++ = '\n';
+ }
+ *ptr = 0;
+ return str;
+}
+
+void
+selclear(void)
+{
+ if (sel.ob.x == -1)
+ return;
+ sel.mode = SelIdle;
+ sel.ob.x = -1;
+ tsetdirt(sel.nb.y, sel.ne.y);
+}
+
+void
+fatal(char *errstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+execsh(char *cmd, char **args)
+{
+ char *sh, *prog, *arg;
+ struct passwd *pw;
+
+ errno = 0;
+ if ((pw = getpwuid(getuid())) == nil) {
+ if (errno)
+ fatal("getpwuid: %s\n", strerror(errno));
+ else
+ fatal("who are you?\n");
+ }
+
+ if ((sh = getenv("SHELL")) == nil)
+ sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
+
+ if (args) {
+ prog = args[0];
+ arg = nil;
+ } else if (scroll) {
+ prog = scroll;
+ arg = utmp ? utmp : sh;
+ } else if (utmp) {
+ prog = utmp;
+ arg = nil;
+ } else {
+ prog = sh;
+ arg = nil;
+ }
+ DEFAULT(args, ((char *[]) {prog, arg, nil}));
+
+ unsetenv("COLUMNS");
+ unsetenv("LINES");
+ unsetenv("TERMCAP");
+ setenv("LOGNAME", pw->pw_name, 1);
+ setenv("USER", pw->pw_name, 1);
+ setenv("SHELL", sh, 1);
+ setenv("HOME", pw->pw_dir, 1);
+ setenv("TERM", termname, 1);
+
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+
+ execvp(prog, args);
+ _exit(1);
+}
+
+void
+sigchld(int a)
+{
+ int stat;
+ pid_t p;
+
+ if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
+ fatal("waiting for pid %hd failed: %s\n", pid, strerror(errno));
+
+ if (pid != p)
+ return;
+
+ if (WIFEXITED(stat) && WEXITSTATUS(stat))
+ fatal("child exited with status %d\n", WEXITSTATUS(stat));
+ else if (WIFSIGNALED(stat))
+ fatal("child terminated due to signal %d\n", WTERMSIG(stat));
+ _exit(0);
+}
+
+void
+stty(char **args)
+{
+ char cmd[_POSIX_ARG_MAX], **p, *q, *s;
+ size_t n, siz;
+
+ if ((n = strlen(stty_args)) > sizeof(cmd)-1)
+ fatal("incorrect stty parameters\n");
+ memcpy(cmd, stty_args, n);
+ q = cmd + n;
+ siz = sizeof(cmd) - n;
+ for (p = args; p && (s = *p); ++p) {
+ if ((n = strlen(s)) > siz-1)
+ fatal("stty parameter length too long\n");
+ *q++ = ' ';
+ memcpy(q, s, n);
+ q += n;
+ siz -= n + 1;
+ }
+ *q = '\0';
+ if (system(cmd) != 0)
+ perror("Couldn't call stty");
+}
+
+int
+ttynew(char *line, char *cmd, char *out, char **args)
+{
+ int m, s;
+
+ if (out) {
+ term.mode |= Tprint;
+ iofd = (!strcmp(out, "-")) ?
+ 1 : open(out, O_WRONLY | O_CREAT, 0666);
+ if (iofd < 0) {
+ fprintf(stderr, "Error opening %s:%s\n",
+ out, strerror(errno));
+ }
+ }
+
+ if (line) {
+ if ((cmdfd = open(line, O_RDWR)) < 0)
+ fatal("open line '%s' failed: %s\n",
+ line, strerror(errno));
+ dup2(cmdfd, 0);
+ stty(args);
+ return cmdfd;
+ }
+
+ /* seems to work fine on linux, openbsd and freebsd */
+ if (openpty(&m, &s, nil, nil, nil) < 0)
+ fatal("openpty failed: %s\n", strerror(errno));
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("fork failed: %s\n", strerror(errno));
+ break;
+ case 0:
+ close(iofd);
+ setsid(); /* create a new process group */
+ dup2(s, 0);
+ dup2(s, 1);
+ dup2(s, 2);
+ if (ioctl(s, TIOCSCTTY, nil) < 0)
+ fatal("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
+ close(s);
+ close(m);
+#ifdef __OpenBSD__
+ if (pledge("stdio getpw proc exec", nil) == -1)
+ fatal("pledge\n");
+#endif
+ execsh(cmd, args);
+ break;
+ default:
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath tty proc", nil) == -1)
+ fatal("pledge\n");
+#endif
+ close(s);
+ cmdfd = m;
+ signal(SIGCHLD, sigchld);
+ break;
+ }
+ return cmdfd;
+}
+
+size_t
+ttyread(void)
+{
+ static char buf[BUFSIZ];
+ static int buflen = 0;
+ int ret, written;
+
+ /* append read bytes to unprocessed bytes */
+ ret = read(cmdfd, buf+buflen, arrlen(buf)-buflen);
+
+ switch (ret) {
+ case 0:
+ exit(0);
+ case -1:
+ fatal("couldn't read from shell: %s\n", strerror(errno));
+ default:
+ buflen += ret;
+ written = twrite(buf, buflen, 0);
+ buflen -= written;
+ /* keep any incomplete UTF-8 byte sequence for the next call */
+ if(buflen > 0)
+ memmove(buf, buf + written, buflen);
+ return ret;
+ }
+}
+
+void
+ttywrite(char *s, size_t n, int may_echo)
+{
+ char *next;
+
+ if (may_echo && IS_SET(Techo))
+ twrite(s, n, 1);
+
+ if (!IS_SET(Tcrlf)) {
+ ttywriteraw(s, n);
+ return;
+ }
+
+ /* This is similar to how the kernel handles ONLCR for ttys */
+ while (n > 0) {
+ if (*s == '\r') {
+ next = s + 1;
+ ttywriteraw("\r\n", 2);
+ } else {
+ next = memchr(s, '\r', n);
+ DEFAULT(next, s + n);
+ ttywriteraw(s, next - s);
+ }
+ n -= next - s;
+ s = next;
+ }
+}
+
+void
+ttywriteraw(char *s, size_t n)
+{
+ fd_set wfd, rfd;
+ ssize_t r;
+ size_t lim = 256;
+
+ /*
+ * Remember that we are using a pty, which might be a modem line.
+ * Writing too much will clog the line. That's why we are doing this
+ * dance.
+ * FIXME: Migrate the world to Plan 9.
+ */
+ while (n > 0) {
+ FD_ZERO(&wfd);
+ FD_ZERO(&rfd);
+ FD_SET(cmdfd, &wfd);
+ FD_SET(cmdfd, &rfd);
+
+ /* Check if we can write. */
+ if (pselect(cmdfd+1, &rfd, &wfd, nil, nil, nil) < 0) {
+ if (errno == EINTR)
+ continue;
+ fatal("select failed: %s\n", strerror(errno));
+ }
+ if (FD_ISSET(cmdfd, &wfd)) {
+ /*
+ * Only write the bytes written by ttywrite() or the
+ * default of 256. This seems to be a reasonable value
+ * for a serial line. Bigger values might clog the I/O.
+ */
+ if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
+ goto write_error;
+ if (r < n) {
+ /*
+ * We weren't able to write out everything.
+ * This means the buffer is getting full
+ * again. Empty it.
+ */
+ if (n < lim)
+ lim = ttyread();
+ n -= r;
+ s += r;
+ } else {
+ /* All bytes have been written. */
+ break;
+ }
+ }
+ if (FD_ISSET(cmdfd, &rfd))
+ lim = ttyread();
+ }
+ return;
+
+write_error:
+ fatal("write error on tty: %s\n", strerror(errno));
+}
+
+void
+ttyresize(int tw, int th)
+{
+ struct winsize w;
+
+ w.ws_row = term.row;
+ w.ws_col = term.col;
+ w.ws_xpixel = tw;
+ w.ws_ypixel = th;
+ if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
+ fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
+}
+
+void
+ttyhangup()
+{
+ /* Send SIGHUP to shell */
+ kill(pid, SIGHUP);
+}
+
+int
+tattrset(int attr)
+{
+ int i, j;
+
+ for (i = 0; i < term.row-1; i++) {
+ for (j = 0; j < term.col-1; j++) {
+ if (term.line[i][j].mode & attr)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+tsetdirt(int top, int bot)
+{
+ int i;
+
+ LIMIT(top, 0, term.row-1);
+ LIMIT(bot, 0, term.row-1);
+
+ for (i = top; i <= bot; i++)
+ term.dirty[i] = 1;
+}
+
+void
+tsetdirtattr(int attr)
+{
+ int i, j;
+
+ for (i = 0; i < term.row-1; i++) {
+ for (j = 0; j < term.col-1; j++) {
+ if (term.line[i][j].mode & attr) {
+ tsetdirt(i, i);
+ break;
+ }
+ }
+ }
+}
+
+void
+tfulldirt(void)
+{
+ tsetdirt(0, term.row-1);
+}
+
+void
+tcursor(int mode)
+{
+ static Dot c[2];
+ int alt = IS_SET(Taltscreen);
+
+ if (mode == CursorSave) {
+ c[alt] = term.c;
+ } else if (mode == CursorLoad) {
+ term.c = c[alt];
+ tmoveto(c[alt].x, c[alt].y);
+ }
+}
+
+void
+treset(void)
+{
+ uint i;
+
+ term.c = (Dot){{
+ .mode = Gnil,
+ .fg = defaultfg,
+ .bg = defaultbg
+ }, .x = 0, .y = 0, .state = CursorDefault};
+
+ memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+ for (i = tabspaces; i < term.col; i += tabspaces)
+ term.tabs[i] = 1;
+ term.top = 0;
+ term.bot = term.row - 1;
+ term.mode = Twrap|Tutf8;
+ memset(term.trantbl, CSusa, sizeof(term.trantbl));
+ term.charset = 0;
+
+ for (i = 0; i < 2; i++) {
+ tmoveto(0, 0);
+ tcursor(CursorSave);
+ tclearregion(0, 0, term.col-1, term.row-1);
+ tswapscreen();
+ }
+}
+
+void
+tnew(int col, int row)
+{
+ term = (Terminal){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
+ tresize(col, row);
+ treset();
+}
+
+void
+tswapscreen(void)
+{
+ Letter **tmp = term.line;
+
+ term.line = term.alt;
+ term.alt = tmp;
+ term.mode ^= Taltscreen;
+ tfulldirt();
+}
+
+void
+tscrolldown(int orig, int n)
+{
+ int i;
+ Letter *temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
+
+ tsetdirt(orig, term.bot-n);
+ tclearregion(0, term.bot-n+1, term.col-1, term.bot);
+
+ for (i = term.bot; i >= orig+n; i--) {
+ temp = term.line[i];
+ term.line[i] = term.line[i-n];
+ term.line[i-n] = temp;
+ }
+
+ selscroll(orig, n);
+}
+
+void
+tscrollup(int orig, int n)
+{
+ int i;
+ Letter *temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
+
+ tclearregion(0, orig, term.col-1, orig+n-1);
+ tsetdirt(orig+n, term.bot);
+
+ for (i = orig; i <= term.bot-n; i++) {
+ temp = term.line[i];
+ term.line[i] = term.line[i+n];
+ term.line[i+n] = temp;
+ }
+
+ selscroll(orig, -n);
+}
+
+void
+selscroll(int orig, int n)
+{
+ if (sel.ob.x == -1)
+ return;
+
+ if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
+ selclear();
+ } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
+ sel.ob.y += n;
+ sel.oe.y += n;
+ if (sel.ob.y < term.top || sel.ob.y > term.bot ||
+ sel.oe.y < term.top || sel.oe.y > term.bot) {
+ selclear();
+ } else {
+ selnormalize();
+ }
+ }
+}
+
+void
+tnewline(int first_col)
+{
+ int y = term.c.y;
+
+ if (y == term.bot) {
+ tscrollup(term.top, 1);
+ } else {
+ y++;
+ }
+ tmoveto(first_col ? 0 : term.c.x, y);
+}
+
+void
+csiparse(void)
+{
+ char *p = csiescseq.buf, *np;
+ long int v;
+
+ csiescseq.narg = 0;
+ if (*p == '?') {
+ csiescseq.priv = 1;
+ p++;
+ }
+
+ csiescseq.buf[csiescseq.len] = '\0';
+ while (p < csiescseq.buf+csiescseq.len) {
+ np = nil;
+ v = strtol(p, &np, 10);
+ if (np == p)
+ v = 0;
+ if (v == LONG_MAX || v == LONG_MIN)
+ v = -1;
+ csiescseq.arg[csiescseq.narg++] = v;
+ p = np;
+ if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
+ break;
+ p++;
+ }
+ csiescseq.mode[0] = *p++;
+ csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
+}
+
+/* for absolute user moves, when decom is set */
+void
+tmoveato(int x, int y)
+{
+ tmoveto(x, y + ((term.c.state & CursorOrigin) ? term.top: 0));
+}
+
+void
+tmoveto(int x, int y)
+{
+ int miny, maxy;
+
+ if (term.c.state & CursorOrigin) {
+ miny = term.top;
+ maxy = term.bot;
+ } else {
+ miny = 0;
+ maxy = term.row - 1;
+ }
+ term.c.state &= ~CursorWrap;
+ term.c.x = LIMIT(x, 0, term.col-1);
+ term.c.y = LIMIT(y, miny, maxy);
+}
+
+void
+tsetchar(rune u, Letter *attr, int x, int y)
+{
+ static char *vt100_0[62] = { /* 0x41 - 0x7e */
+ "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
+ 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
+ "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
+ "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
+ "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
+ "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
+ };
+
+ /*
+ * table is proudly stolen from rxvt.
+ */
+ if (term.trantbl[term.charset] == CSgfx0 &&
+ BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
+ utf8·decode(vt100_0[u - 0x41], &u);
+
+ if (term.line[y][x].mode & Gwide) {
+ if (x+1 < term.col) {
+ term.line[y][x+1].u = ' ';
+ term.line[y][x+1].mode &= ~Gwdummy;
+ }
+ } else if (term.line[y][x].mode & Gwdummy) {
+ term.line[y][x-1].u = ' ';
+ term.line[y][x-1].mode &= ~Gwide;
+ }
+
+ term.dirty[y] = 1;
+ term.line[y][x] = *attr;
+ term.line[y][x].u = u;
+}
+
+void
+tclearregion(int x1, int y1, int x2, int y2)
+{
+ int x, y, temp;
+ Letter *gp;
+
+ if(x1 > x2)
+ temp = x1, x1 = x2, x2 = temp;
+ if(y1 > y2)
+ temp = y1, y1 = y2, y2 = temp;
+
+ LIMIT(x1, 0, term.col-1);
+ LIMIT(x2, 0, term.col-1);
+ LIMIT(y1, 0, term.row-1);
+ LIMIT(y2, 0, term.row-1);
+
+ for(y = y1; y <= y2; y++) {
+ term.dirty[y] = 1;
+ for(x = x1; x <= x2; x++) {
+ gp = &term.line[y][x];
+ if(selected(x, y))
+ selclear();
+ gp->fg = term.c.attr.fg;
+ gp->bg = term.c.attr.bg;
+ gp->mode = 0;
+ gp->u = ' ';
+ }
+ }
+}
+
+void
+tdeletechar(int n)
+{
+ int dst, src, size;
+ Letter *line;
+
+ LIMIT(n, 0, term.col - term.c.x);
+
+ dst = term.c.x;
+ src = term.c.x + n;
+ size = term.col - src;
+ line = term.line[term.c.y];
+
+ memmove(&line[dst], &line[src], size * sizeof(Letter));
+ tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
+}
+
+void
+tinsertblank(int n)
+{
+ int dst, src, size;
+ Letter *line;
+
+ LIMIT(n, 0, term.col - term.c.x);
+
+ dst = term.c.x + n;
+ src = term.c.x;
+ size = term.col - dst;
+ line = term.line[term.c.y];
+
+ memmove(&line[dst], &line[src], size * sizeof(Letter));
+ tclearregion(src, term.c.y, dst - 1, term.c.y);
+}
+
+void
+tinsertblankline(int n)
+{
+ if (BETWEEN(term.c.y, term.top, term.bot))
+ tscrolldown(term.c.y, n);
+}
+
+void
+tdeleteline(int n)
+{
+ if (BETWEEN(term.c.y, term.top, term.bot))
+ tscrollup(term.c.y, n);
+}
+
+int32_t
+tdefcolor(int *attr, int *npar, int l)
+{
+ int32_t idx = -1;
+ uint r, g, b;
+
+ switch (attr[*npar + 1]) {
+ case 2: /* direct color in RGB space */
+ if (*npar + 4 >= l) {
+ fprintf(stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar);
+ break;
+ }
+ r = attr[*npar + 2];
+ g = attr[*npar + 3];
+ b = attr[*npar + 4];
+ *npar += 4;
+ if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
+ fprintf(stderr, "erresc(38): bad rgb color (%u,%u,%u)\n", r, g, b);
+ else
+ idx = TRUECOLOR(r, g, b);
+ break;
+ case 5: /* indexed color */
+ if (*npar + 2 >= l) {
+ fprintf(stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar);
+ break;
+ }
+ *npar += 2;
+ if (!BETWEEN(attr[*npar], 0, 255))
+ fprintf(stderr, "erresc: bad color %d\n", attr[*npar]);
+ else
+ idx = attr[*npar];
+ break;
+ case 0: /* implemented defined (only foreground) */
+ case 1: /* transparent */
+ case 3: /* direct color in CMY space */
+ case 4: /* direct color in CMYK space */
+ default:
+ fprintf(stderr, "erresc(38): gfx attr %d unknown\n", attr[*npar]);
+ break;
+ }
+
+ return idx;
+}
+
+void
+tsetattr(int *attr, int l)
+{
+ int i;
+ int32_t idx;
+
+ for (i = 0; i < l; i++) {
+ switch (attr[i]) {
+ case 0:
+ term.c.attr.mode &= ~(
+ Gbold |
+ Gfaint |
+ Gitalic |
+ Gunline |
+ Gblink |
+ Greverse |
+ Ginvisible |
+ Gstruck );
+ term.c.attr.fg = defaultfg;
+ term.c.attr.bg = defaultbg;
+ break;
+ case 1:
+ term.c.attr.mode |= Gbold;
+ break;
+ case 2:
+ term.c.attr.mode |= Gfaint;
+ break;
+ case 3:
+ term.c.attr.mode |= Gitalic;
+ break;
+ case 4:
+ term.c.attr.mode |= Gunline;
+ break;
+ case 5: /* slow blink */
+ /* FALLTHROUGH */
+ case 6: /* rapid blink */
+ term.c.attr.mode |= Gblink;
+ break;
+ case 7:
+ term.c.attr.mode |= Greverse;
+ break;
+ case 8:
+ term.c.attr.mode |= Ginvisible;
+ break;
+ case 9:
+ term.c.attr.mode |= Gstruck;
+ break;
+ case 22:
+ term.c.attr.mode &= ~(Gbold | Gfaint);
+ break;
+ case 23:
+ term.c.attr.mode &= ~Gitalic;
+ break;
+ case 24:
+ term.c.attr.mode &= ~Gunline;
+ break;
+ case 25:
+ term.c.attr.mode &= ~Gblink;
+ break;
+ case 27:
+ term.c.attr.mode &= ~Greverse;
+ break;
+ case 28:
+ term.c.attr.mode &= ~Ginvisible;
+ break;
+ case 29:
+ term.c.attr.mode &= ~Gstruck;
+ break;
+ case 38:
+ if ((idx = tdefcolor(attr, &i, l)) >= 0)
+ term.c.attr.fg = idx;
+ break;
+ case 39:
+ term.c.attr.fg = defaultfg;
+ break;
+ case 48:
+ if ((idx = tdefcolor(attr, &i, l)) >= 0)
+ term.c.attr.bg = idx;
+ break;
+ case 49:
+ term.c.attr.bg = defaultbg;
+ break;
+ default:
+ if (BETWEEN(attr[i], 30, 37)) {
+ term.c.attr.fg = attr[i] - 30;
+ } else if (BETWEEN(attr[i], 40, 47)) {
+ term.c.attr.bg = attr[i] - 40;
+ } else if (BETWEEN(attr[i], 90, 97)) {
+ term.c.attr.fg = attr[i] - 90 + 8;
+ } else if (BETWEEN(attr[i], 100, 107)) {
+ term.c.attr.bg = attr[i] - 100 + 8;
+ } else {
+ fprintf(stderr,
+ "erresc(default): gfx attr %d unknown\n",
+ attr[i]);
+ csidump();
+ }
+ break;
+ }
+ }
+}
+
+void
+tsetscroll(int t, int b)
+{
+ int temp;
+
+ LIMIT(t, 0, term.row-1);
+ LIMIT(b, 0, term.row-1);
+ if (t > b) {
+ temp = t;
+ t = b;
+ b = temp;
+ }
+ term.top = t;
+ term.bot = b;
+}
+
+void
+tsetmode(int priv, int set, int *args, int narg)
+{
+ int alt, *lim;
+
+ for (lim = args + narg; args < lim; ++args) {
+ if (priv) {
+ switch (*args) {
+ case 1: /* DECCKM -- Cursor key */
+ xsetmode(set, Wappcursor);
+ break;
+ case 5: /* DECSCNM -- Reverse video */
+ xsetmode(set, Wreverse);
+ break;
+ case 6: /* DECOM -- Origin */
+ MODBIT(term.c.state, set, CursorOrigin);
+ tmoveato(0, 0);
+ break;
+ case 7: /* DECAWM -- Auto wrap */
+ MODBIT(term.mode, set, Twrap);
+ break;
+ case 0: /* Error (IGNORED) */
+ case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
+ case 3: /* DECCOLM -- Column (IGNORED) */
+ case 4: /* DECSCLM -- Scroll (IGNORED) */
+ case 8: /* DECARM -- Auto repeat (IGNORED) */
+ case 18: /* DECPFF -- Printer feed (IGNORED) */
+ case 19: /* DECPEX -- Printer extent (IGNORED) */
+ case 42: /* DECNRCM -- National characters (IGNORED) */
+ case 12: /* att610 -- Start blinking cursor (IGNORED) */
+ break;
+ case 25: /* DECTCEM -- Text Cursor Enable Mode */
+ xsetmode(!set, Whide);
+ break;
+ case 9: /* X10 mouse compatibility mode */
+ xsetpointermotion(0);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousex10);
+ break;
+ case 1000: /* 1000: report button press */
+ xsetpointermotion(0);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousebtn);
+ break;
+ case 1002: /* 1002: report motion on button press */
+ xsetpointermotion(0);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousemotion);
+ break;
+ case 1003: /* 1003: enable all mouse motions */
+ xsetpointermotion(set);
+ xsetmode(0, Wmouse);
+ xsetmode(set, Wmousemany);
+ break;
+ case 1004: /* 1004: send focus events to tty */
+ xsetmode(set, Wfocus);
+ break;
+ case 1006: /* 1006: extended reporting mode */
+ xsetmode(set, Wmousesgr);
+ break;
+ case 1034:
+ xsetmode(set, W8bit);
+ break;
+ case 1049: /* swap screen & set/restore cursor as xterm */
+ if (!allowaltscreen)
+ break;
+ tcursor((set) ? CursorSave : CursorLoad);
+ /* FALLTHROUGH */
+ case 47: /* swap screen */
+ case 1047:
+ if (!allowaltscreen)
+ break;
+ alt = IS_SET(Taltscreen);
+ if (alt) {
+ tclearregion(0, 0, term.col-1,
+ term.row-1);
+ }
+ if (set ^ alt) /* set is always 1 or 0 */
+ tswapscreen();
+ if (*args != 1049)
+ break;
+ /* FALLTHROUGH */
+ case 1048:
+ tcursor((set) ? CursorSave : CursorLoad);
+ break;
+ case 2004: /* 2004: bracketed paste mode */
+ xsetmode(set, Wbrcktpaste);
+ break;
+ /* Not implemented mouse modes. See comments there. */
+ case 1001: /* mouse highlight mode; can hang the
+ terminal by design when implemented. */
+ case 1005: /* UTF-8 mouse mode; will confuse
+ applications not supporting UTF-8
+ and luit. */
+ case 1015: /* urxvt mangled mouse mode; incompatible
+ and can be mistaken for other control
+ codes. */
+ break;
+ default:
+ fprintf(stderr,
+ "erresc: unknown private set/reset mode %d\n",
+ *args);
+ break;
+ }
+ } else {
+ switch (*args) {
+ case 0: /* Error (IGNORED) */
+ break;
+ case 2:
+ xsetmode(set, Wkbdblock);
+ break;
+ case 4: /* IRM -- Insertion-replacement */
+ MODBIT(term.mode, set, Tinsert);
+ break;
+ case 12: /* SRM -- Send/Receive */
+ MODBIT(term.mode, !set, Techo);
+ break;
+ case 20: /* LNM -- Linefeed/new line */
+ MODBIT(term.mode, set, Tcrlf);
+ break;
+ default:
+ fprintf(stderr,
+ "erresc: unknown set/reset mode %d\n",
+ *args);
+ break;
+ }
+ }
+ }
+}
+
+void
+csihandle(void)
+{
+ char buf[40];
+ int len;
+
+ switch (csiescseq.mode[0]) {
+ default:
+ unknown:
+ fprintf(stderr, "erresc: unknown csi ");
+ csidump();
+ /* fatal(""); */
+ break;
+ case '@': /* ICH -- Insert <n> blank char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblank(csiescseq.arg[0]);
+ break;
+ case 'A': /* CUU -- Cursor <n> Up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'B': /* CUD -- Cursor <n> Down */
+ case 'e': /* VPR --Cursor <n> Down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'i': /* MC -- Media Copy */
+ switch (csiescseq.arg[0]) {
+ case 0:
+ tdump();
+ break;
+ case 1:
+ tdumpline(term.c.y);
+ break;
+ case 2:
+ tdumpsel();
+ break;
+ case 4:
+ term.mode &= ~Tprint;
+ break;
+ case 5:
+ term.mode |= Tprint;
+ break;
+ }
+ break;
+ case 'c': /* DA -- Device Attributes */
+ if (csiescseq.arg[0] == 0)
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 'b': /* REP -- if last char is printable print it <n> more times */
+ DEFAULT(csiescseq.arg[0], 1);
+ if (term.lastc)
+ while (csiescseq.arg[0]-- > 0)
+ tputc(term.lastc);
+ break;
+ case 'C': /* CUF -- Cursor <n> Forward */
+ case 'a': /* HPR -- Cursor <n> Forward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
+ break;
+ case 'D': /* CUB -- Cursor <n> Backward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
+ break;
+ case 'E': /* CNL -- Cursor <n> Down and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'F': /* CPL -- Cursor <n> Up and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'g': /* TBC -- Tabulation clear */
+ switch (csiescseq.arg[0]) {
+ case 0: /* clear current tab stop */
+ term.tabs[term.c.x] = 0;
+ break;
+ case 3: /* clear all the tabs */
+ memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'G': /* CHA -- Move to <col> */
+ case '`': /* HPA */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(csiescseq.arg[0]-1, term.c.y);
+ break;
+ case 'H': /* CUP -- Move to <row> <col> */
+ case 'f': /* HVP */
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], 1);
+ tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
+ break;
+ case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(csiescseq.arg[0]);
+ break;
+ case 'J': /* ED -- Clear screen */
+ switch (csiescseq.arg[0]) {
+ case 0: /* below */
+ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
+ if (term.c.y < term.row-1) {
+ tclearregion(0, term.c.y+1, term.col-1,
+ term.row-1);
+ }
+ break;
+ case 1: /* above */
+ if (term.c.y > 1)
+ tclearregion(0, 0, term.col-1, term.c.y-1);
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, 0, term.col-1, term.row-1);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'K': /* EL -- Clear line */
+ switch (csiescseq.arg[0]) {
+ case 0: /* right */
+ tclearregion(term.c.x, term.c.y, term.col-1,
+ term.c.y);
+ break;
+ case 1: /* left */
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, term.c.y, term.col-1, term.c.y);
+ break;
+ }
+ break;
+ case 'S': /* SU -- Scroll <n> line up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrollup(term.top, csiescseq.arg[0]);
+ break;
+ case 'T': /* SD -- Scroll <n> line down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrolldown(term.top, csiescseq.arg[0]);
+ break;
+ case 'L': /* IL -- Insert <n> blank lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblankline(csiescseq.arg[0]);
+ break;
+ case 'l': /* RM -- Reset Mode */
+ tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'M': /* DL -- Delete <n> lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeleteline(csiescseq.arg[0]);
+ break;
+ case 'X': /* ECH -- Erase <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tclearregion(term.c.x, term.c.y,
+ term.c.x + csiescseq.arg[0] - 1, term.c.y);
+ break;
+ case 'P': /* DCH -- Delete <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeletechar(csiescseq.arg[0]);
+ break;
+ case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(-csiescseq.arg[0]);
+ break;
+ case 'd': /* VPA -- Move to <row> */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveato(term.c.x, csiescseq.arg[0]-1);
+ break;
+ case 'h': /* SM -- Set terminal mode */
+ tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'm': /* SGR -- Terminal attribute (color) */
+ tsetattr(csiescseq.arg, csiescseq.narg);
+ break;
+ case 'n': /* DSR – Device Status Report (cursor position) */
+ if (csiescseq.arg[0] == 6) {
+ len = snprintf(buf, sizeof(buf), "\033[%i;%iR", term.c.y+1, term.c.x+1);
+ ttywrite(buf, len, 0);
+ }
+ break;
+ case 'r': /* DECSTBM -- Set Scrolling Region */
+ if (csiescseq.priv) {
+ goto unknown;
+ } else {
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], term.row);
+ tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
+ tmoveato(0, 0);
+ }
+ break;
+ case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
+ tcursor(CursorSave);
+ break;
+ case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
+ tcursor(CursorLoad);
+ break;
+ case ' ':
+ switch (csiescseq.mode[1]) {
+ case 'q': /* DECSCUSR -- Set Cursor Style */
+ if (xsetcursor(csiescseq.arg[0]))
+ goto unknown;
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ }
+}
+
+void
+csidump(void)
+{
+ size_t i;
+ uint c;
+
+ fprintf(stderr, "ESC[");
+ for (i = 0; i < csiescseq.len; i++) {
+ c = csiescseq.buf[i] & 0xff;
+ if (isprint(c)) {
+ putc(c, stderr);
+ } else if (c == '\n') {
+ fprintf(stderr, "(\\n)");
+ } else if (c == '\r') {
+ fprintf(stderr, "(\\r)");
+ } else if (c == 0x1b) {
+ fprintf(stderr, "(\\e)");
+ } else {
+ fprintf(stderr, "(%02x)", c);
+ }
+ }
+ putc('\n', stderr);
+}
+
+void
+csireset(void)
+{
+ memset(&csiescseq, 0, sizeof(csiescseq));
+}
+
+void
+strhandle(void)
+{
+ char *p = nil, *dec;
+ int j, narg, par;
+
+ term.esc &= ~(Xstrend|Xstr);
+ strparse();
+ par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
+
+ switch (strescseq.type) {
+ case ']': /* OSC -- Operating System Command */
+ switch (par) {
+ case 0:
+ case 1:
+ case 2:
+ if (narg > 1)
+ xsettitle(strescseq.args[1]);
+ return;
+ case 52:
+ if (narg > 2 && allowwindowops) {
+ dec = base64dec(strescseq.args[2]);
+ if (dec) {
+ xsetsel(dec);
+ xclipcopy();
+ } else {
+ fprintf(stderr, "erresc: invalid base64\n");
+ }
+ }
+ return;
+ case 4: /* color set */
+ if (narg < 3)
+ break;
+ p = strescseq.args[2];
+ /* FALLTHROUGH */
+ case 104: /* color reset, here p = nil */
+ j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
+ if (xsetcolorname(j, p)) {
+ if (par == 104 && narg <= 1)
+ return; /* color reset without parameter */
+ fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
+ j, p ? p : "(null)");
+ } else {
+ /*
+ * TODO if defaultbg color is changed, borders
+ * are dirty
+ */
+ redraw();
+ }
+ return;
+ }
+ break;
+ case 'k': /* old title set compatibility */
+ xsettitle(strescseq.args[0]);
+ return;
+ case 'P': /* DCS -- Device Control String */
+ term.mode |= Xdcs;
+ case '_': /* APC -- Application Program Command */
+ case '^': /* PM -- Privacy Message */
+ return;
+ }
+
+ fprintf(stderr, "erresc: unknown str ");
+ strdump();
+}
+
+void
+strparse(void)
+{
+ int c;
+ char *p = strescseq.buf;
+
+ strescseq.narg = 0;
+ strescseq.buf[strescseq.len] = '\0';
+
+ if (*p == '\0')
+ return;
+
+ while (strescseq.narg < STR_ARG_SIZ) {
+ strescseq.args[strescseq.narg++] = p;
+ while ((c = *p) != ';' && c != '\0')
+ ++p;
+ if (c == '\0')
+ return;
+ *p++ = '\0';
+ }
+}
+
+void
+strdump(void)
+{
+ size_t i;
+ uint c;
+
+ fprintf(stderr, "ESC%c", strescseq.type);
+ for (i = 0; i < strescseq.len; i++) {
+ c = strescseq.buf[i] & 0xff;
+ if (c == '\0') {
+ putc('\n', stderr);
+ return;
+ } else if (isprint(c)) {
+ putc(c, stderr);
+ } else if (c == '\n') {
+ fprintf(stderr, "(\\n)");
+ } else if (c == '\r') {
+ fprintf(stderr, "(\\r)");
+ } else if (c == 0x1b) {
+ fprintf(stderr, "(\\e)");
+ } else {
+ fprintf(stderr, "(%02x)", c);
+ }
+ }
+ fprintf(stderr, "ESC\\\n");
+}
+
+void
+strreset(void)
+{
+ strescseq = (STREscape){
+ .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
+ .siz = STR_BUF_SIZ,
+ };
+}
+
+void
+sendbreak(Arg *arg)
+{
+ if (tcsendbreak(cmdfd, 0))
+ perror("Error sending break");
+}
+
+void
+tprinter(char *s, size_t len)
+{
+ if (iofd != -1 && xwrite(iofd, s, len) < 0) {
+ perror("Error writing to output file");
+ close(iofd);
+ iofd = -1;
+ }
+}
+
+void
+toggleprinter(Arg *arg)
+{
+ term.mode ^= Tprint;
+}
+
+void
+printscreen(Arg *arg)
+{
+ tdump();
+}
+
+void
+printsel(Arg *arg)
+{
+ tdumpsel();
+}
+
+void
+tdumpsel(void)
+{
+ char *ptr;
+
+ if ((ptr = getsel())) {
+ tprinter(ptr, strlen(ptr));
+ free(ptr);
+ }
+}
+
+void
+tdumpline(int n)
+{
+ char buf[UTFmax];
+ Letter *bp, *end;
+
+ bp = &term.line[n][0];
+ end = &bp[MIN(tlinelen(n), term.col) - 1];
+ if (bp != end || bp->u != ' ') {
+ for ( ; bp <= end; ++bp)
+ tprinter(buf, utf8·encode(&bp->u, buf));
+ }
+ tprinter("\n", 1);
+}
+
+void
+tdump(void)
+{
+ int i;
+
+ for (i = 0; i < term.row; ++i)
+ tdumpline(i);
+}
+
+void
+tputtab(int n)
+{
+ uint x = term.c.x;
+
+ if (n > 0) {
+ while (x < term.col && n--)
+ for (++x; x < term.col && !term.tabs[x]; ++x)
+ /* nothing */ ;
+ } else if (n < 0) {
+ while (x > 0 && n++)
+ for (--x; x > 0 && !term.tabs[x]; --x)
+ /* nothing */ ;
+ }
+ term.c.x = LIMIT(x, 0, term.col-1);
+}
+
+void
+tdefutf8(char ascii)
+{
+ if (ascii == 'G')
+ term.mode |= Tutf8;
+ else if (ascii == '@')
+ term.mode &= ~Tutf8;
+}
+
+void
+tdeftran(char ascii)
+{
+ static char cs[] = "0B";
+ static int vcs[] = {CSgfx0, CSusa};
+ char *p;
+
+ if ((p = strchr(cs, ascii)) == nil) {
+ fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
+ } else {
+ term.trantbl[term.icharset] = vcs[p - cs];
+ }
+}
+
+void
+tdectest(char c)
+{
+ int x, y;
+
+ if (c == '8') { /* DEC screen alignment test. */
+ for (x = 0; x < term.col; ++x) {
+ for (y = 0; y < term.row; ++y)
+ tsetchar('E', &term.c.attr, x, y);
+ }
+ }
+}
+
+void
+tstrsequence(uchar c)
+{
+ strreset();
+
+ switch (c) {
+ case 0x90: /* DCS -- Device Control String */
+ c = 'P';
+ term.esc |= Xdcs;
+ break;
+ case 0x9f: /* APC -- Application Program Command */
+ c = '_';
+ break;
+ case 0x9e: /* PM -- Privacy Message */
+ c = '^';
+ break;
+ case 0x9d: /* OSC -- Operating System Command */
+ c = ']';
+ break;
+ }
+ strescseq.type = c;
+ term.esc |= Xstr;
+}
+
+void
+tcontrolcode(uchar ascii)
+{
+ switch (ascii) {
+ case '\t': /* HT */
+ tputtab(1);
+ return;
+ case '\b': /* BS */
+ tmoveto(term.c.x-1, term.c.y);
+ return;
+ case '\r': /* CR */
+ tmoveto(0, term.c.y);
+ return;
+ case '\f': /* LF */
+ case '\v': /* VT */
+ case '\n': /* LF */
+ /* go to first col if the mode is set */
+ tnewline(IS_SET(Tcrlf));
+ return;
+ case '\a': /* BEL */
+ if (term.esc & Xstrend) {
+ /* backwards compatibility to xterm */
+ strhandle();
+ } else {
+ xbell();
+ }
+ break;
+ case '\033': /* ESC */
+ csireset();
+ term.esc &= ~(Xcsi|Xaltcs|Xtest);
+ term.esc |= Xstart;
+ return;
+ case '\016': /* SO (LS1 -- Locking shift 1) */
+ case '\017': /* SI (LS0 -- Locking shift 0) */
+ term.charset = 1 - (ascii - '\016');
+ return;
+ case '\032': /* SUB */
+ tsetchar('?', &term.c.attr, term.c.x, term.c.y);
+ /* FALLTHROUGH */
+ case '\030': /* CAN */
+ csireset();
+ break;
+ case '\005': /* ENQ (IGNORED) */
+ case '\000': /* NUL (IGNORED) */
+ case '\021': /* XON (IGNORED) */
+ case '\023': /* XOFF (IGNORED) */
+ case 0177: /* DEL (IGNORED) */
+ return;
+ case 0x80: /* TODO: PAD */
+ case 0x81: /* TODO: HOP */
+ case 0x82: /* TODO: BPH */
+ case 0x83: /* TODO: NBH */
+ case 0x84: /* TODO: IND */
+ break;
+ case 0x85: /* NEL -- Next line */
+ tnewline(1); /* always go to first col */
+ break;
+ case 0x86: /* TODO: SSA */
+ case 0x87: /* TODO: ESA */
+ break;
+ case 0x88: /* HTS -- Horizontal tab stop */
+ term.tabs[term.c.x] = 1;
+ break;
+ case 0x89: /* TODO: HTJ */
+ case 0x8a: /* TODO: VTS */
+ case 0x8b: /* TODO: PLD */
+ case 0x8c: /* TODO: PLU */
+ case 0x8d: /* TODO: RI */
+ case 0x8e: /* TODO: SS2 */
+ case 0x8f: /* TODO: SS3 */
+ case 0x91: /* TODO: PU1 */
+ case 0x92: /* TODO: PU2 */
+ case 0x93: /* TODO: STS */
+ case 0x94: /* TODO: CCH */
+ case 0x95: /* TODO: MW */
+ case 0x96: /* TODO: SPA */
+ case 0x97: /* TODO: EPA */
+ case 0x98: /* TODO: SOS */
+ case 0x99: /* TODO: SGCI */
+ break;
+ case 0x9a: /* DECID -- Identify Terminal */
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 0x9b: /* TODO: CSI */
+ case 0x9c: /* TODO: ST */
+ break;
+ case 0x90: /* DCS -- Device Control String */
+ case 0x9d: /* OSC -- Operating System Command */
+ case 0x9e: /* PM -- Privacy Message */
+ case 0x9f: /* APC -- Application Program Command */
+ tstrsequence(ascii);
+ return;
+ }
+ /* only CAN, SUB, \a and C1 chars interrupt a sequence */
+ term.esc &= ~(Xstrend|Xstr);
+}
+
+/*
+ * returns 1 when the sequence is finished and it hasn't to read
+ * more characters for this sequence, otherwise 0
+ */
+int
+eschandle(uchar ascii)
+{
+ switch (ascii) {
+ case '[':
+ term.esc |= Xcsi;
+ return 0;
+ case '#':
+ term.esc |= Xtest;
+ return 0;
+ case '%':
+ term.esc |= Xutf8;
+ return 0;
+ case 'P': /* DCS -- Device Control String */
+ case '_': /* APC -- Application Program Command */
+ case '^': /* PM -- Privacy Message */
+ case ']': /* OSC -- Operating System Command */
+ case 'k': /* old title set compatibility */
+ tstrsequence(ascii);
+ return 0;
+ case 'n': /* LS2 -- Locking shift 2 */
+ case 'o': /* LS3 -- Locking shift 3 */
+ term.charset = 2 + (ascii - 'n');
+ break;
+ case '(': /* GZD4 -- set primary charset G0 */
+ case ')': /* G1D4 -- set secondary charset G1 */
+ case '*': /* G2D4 -- set tertiary charset G2 */
+ case '+': /* G3D4 -- set quaternary charset G3 */
+ term.icharset = ascii - '(';
+ term.esc |= Xaltcs;
+ return 0;
+ case 'D': /* IND -- Linefeed */
+ if (term.c.y == term.bot) {
+ tscrollup(term.top, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y+1);
+ }
+ break;
+ case 'E': /* NEL -- Next line */
+ tnewline(1); /* always go to first col */
+ break;
+ case 'H': /* HTS -- Horizontal tab stop */
+ term.tabs[term.c.x] = 1;
+ break;
+ case 'M': /* RI -- Reverse index */
+ if (term.c.y == term.top) {
+ tscrolldown(term.top, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y-1);
+ }
+ break;
+ case 'Z': /* DECID -- Identify Terminal */
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 'c': /* RIS -- Reset to initial state */
+ treset();
+ resettitle();
+ xloadcols();
+ break;
+ case '=': /* DECPAM -- Application keypad */
+ xsetmode(1, Wappkeypad);
+ break;
+ case '>': /* DECPNM -- Normal keypad */
+ xsetmode(0, Wappkeypad);
+ break;
+ case '7': /* DECSC -- Save Cursor */
+ tcursor(CursorSave);
+ break;
+ case '8': /* DECRC -- Restore Cursor */
+ tcursor(CursorLoad);
+ break;
+ case '\\': /* ST -- String Terminator */
+ if (term.esc & Xstrend)
+ strhandle();
+ break;
+ default:
+ fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
+ (uchar) ascii, isprint(ascii)? ascii:'.');
+ break;
+ }
+ return 1;
+}
+
+void
+tputc(rune u)
+{
+ char c[UTFmax];
+ int control;
+ int width, len;
+ rune nu;
+ Letter *gp;
+
+ control = ISCONTROL(u);
+ if (u < 127 || !IS_SET(Tutf8 | Tsixel)) {
+ c[0] = u;
+ width = len = 1;
+ } else {
+ len = utf8·encode(&u, c);
+ if(!control && (width = wcwidth(u)) == -1)
+ width = 1;
+ }
+
+ /* combining characters */
+ if(!width){
+ if(term.c.x > 0)
+ gp = &term.line[term.c.y][term.c.x-1];
+ else if(term.c.y > 0)
+ gp = &term.line[term.c.y-1][term.col-1];
+ else
+ return;
+
+#if 0
+ if(!hb_unicode_compose(hb_unicode_funcs_get_default(),gp->u, u, &nu)) {
+ return;
+ }
+#endif
+
+ gp->u = nu;
+ return;
+ }
+
+ if (IS_SET(Tprint))
+ tprinter(c, len);
+
+ /*
+ * STR sequence must be checked before anything else
+ * because it uses all following characters until it
+ * receives a ESC, a SUB, a ST or any other C1 control
+ * character.
+ */
+ if(term.esc & Xstr) {
+ if (u == '\a' || u == 030 || u == 032 || u == 033 ||
+ ISCONTROLC1(u)) {
+ term.esc &= ~(Xstart|Xstr|Xdcs);
+ if (IS_SET(Tsixel)) {
+ /* TODO: render sixel */;
+ term.mode &= ~Tsixel;
+ return;
+ }
+ term.esc |= Xstrend;
+ goto check_control_code;
+ }
+
+ if(IS_SET(Tsixel)) {
+ /* TODO: implement sixel mode */
+ return;
+ }
+ if (term.esc&Xdcs && strescseq.len == 0 && u == 'q')
+ term.mode |= Tsixel;
+
+ if (strescseq.len+len >= strescseq.siz) {
+ /*
+ * Here is a bug in terminals. If the user never sends
+ * some code to stop the str or esc command, then st
+ * will stop responding. But this is better than
+ * silently failing with unknown characters. At least
+ * then users will report back.
+ *
+ * In the case users ever get fixed, here is the code:
+ */
+ /*
+ * term.esc = 0;
+ * strhandle();
+ */
+ if(strescseq.siz > (SIZE_MAX - UTFmax) / 2)
+ return;
+ strescseq.siz *= 2;
+ strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
+ }
+
+ memmove(&strescseq.buf[strescseq.len], c, len);
+ strescseq.len += len;
+ return;
+ }
+
+check_control_code:
+ /*
+ * Actions of control codes must be performed as soon they arrive
+ * because they can be embedded inside a control sequence, and
+ * they must not cause conflicts with sequences.
+ */
+ if(control) {
+ tcontrolcode(u);
+ /*
+ * control codes are not shown ever
+ */
+ if (!term.esc)
+ term.lastc = 0;
+ return;
+ } else if(term.esc & Xstart) {
+ if (term.esc & Xcsi) {
+ csiescseq.buf[csiescseq.len++] = u;
+ if (BETWEEN(u, 0x40, 0x7E)
+ || csiescseq.len >= \
+ sizeof(csiescseq.buf)-1) {
+ term.esc = 0;
+ csiparse();
+ csihandle();
+ }
+ return;
+ } else if (term.esc & Xutf8) {
+ tdefutf8(u);
+ } else if (term.esc & Xaltcs) {
+ tdeftran(u);
+ } else if (term.esc & Xtest) {
+ tdectest(u);
+ } else {
+ if (!eschandle(u))
+ return;
+ /* sequence already finished */
+ }
+ term.esc = 0;
+ /*
+ * All characters which form part of a sequence are not
+ * printed
+ */
+ return;
+ }
+
+ if(selected(term.c.x, term.c.y))
+ selclear();
+
+ gp = &term.line[term.c.y][term.c.x];
+ if(IS_SET(Twrap) && (term.c.state & CursorWrap)) {
+ gp->mode |= Gwrap;
+ tnewline(1);
+ gp = &term.line[term.c.y][term.c.x];
+ }
+
+ if(IS_SET(Tinsert) && term.c.x+width < term.col)
+ memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Letter));
+
+ if(term.c.x+width > term.col) {
+ tnewline(1);
+ gp = &term.line[term.c.y][term.c.x];
+ }
+
+ tsetchar(u, &term.c.attr, term.c.x, term.c.y);
+ term.lastc = u;
+
+ if(width == 2) {
+ gp->mode |= Gwrap;
+ if (term.c.x+1 < term.col) {
+ gp[1].u = '\0';
+ gp[1].mode = Gwdummy;
+ }
+ }
+ if(term.c.x+width < term.col) {
+ tmoveto(term.c.x+width, term.c.y);
+ }else{
+ term.c.state |= CursorWrap;
+ }
+}
+
+int
+twrite(char *buf, int buflen, int show_ctrl)
+{
+ int charsize;
+ rune u;
+ int n;
+
+ for (n = 0; n < buflen; n += charsize) {
+ if(IS_SET(Tutf8) && !IS_SET(Tsixel)) {
+ /* process a complete utf8 char */
+ charsize = utf8·decode(buf + n, &u);
+ if(charsize == 0)
+ break;
+ } else {
+ u = buf[n] & 0xFF;
+ charsize = 1;
+ }
+ if(show_ctrl && ISCONTROL(u)) {
+ if (u & 0x80) {
+ u &= 0x7f;
+ tputc('^');
+ tputc('[');
+ } else if (u != '\n' && u != '\r' && u != '\t') {
+ u ^= 0x40;
+ tputc('^');
+ }
+ }
+ tputc(u);
+ }
+ return n;
+}
+
+void
+tresize(int col, int row)
+{
+ int i;
+ int minrow = MIN(row, term.row);
+ int mincol = MIN(col, term.col);
+ int *bp;
+ Dot c;
+
+ if (col < 1 || row < 1) {
+ fprintf(stderr,
+ "tresize: error resizing to %dx%d\n", col, row);
+ return;
+ }
+
+ /*
+ * slide screen to keep cursor where we expect it -
+ * tscrollup would work here, but we can optimize to
+ * memmove because we're freeing the earlier lines
+ */
+ for (i = 0; i <= term.c.y - row; i++) {
+ free(term.line[i]);
+ free(term.alt[i]);
+ }
+ /* ensure that both src and dst are not nil */
+ if (i > 0) {
+ memmove(term.line, term.line + i, row * sizeof(Letter*));
+ memmove(term.alt, term.alt + i, row * sizeof(Letter*));
+ }
+ for (i += row; i < term.row; i++) {
+ free(term.line[i]);
+ free(term.alt[i]);
+ }
+
+ /* resize to new height */
+ term.line = xrealloc(term.line, row * sizeof(Letter*));
+ term.alt = xrealloc(term.alt, row * sizeof(Letter*));
+ term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
+ term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
+
+ /* resize each row to new width, zero-pad if needed */
+ for (i = 0; i < minrow; i++) {
+ term.line[i] = xrealloc(term.line[i], col * sizeof(Letter));
+ term.alt[i] = xrealloc(term.alt[i], col * sizeof(Letter));
+ }
+
+ /* allocate any new rows */
+ for (/* i = minrow */; i < row; i++) {
+ term.line[i] = xmalloc(col * sizeof(Letter));
+ term.alt[i] = xmalloc(col * sizeof(Letter));
+ }
+ if (col > term.col) {
+ bp = term.tabs + term.col;
+
+ memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
+ while (--bp > term.tabs && !*bp)
+ /* nothing */ ;
+ for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
+ *bp = 1;
+ }
+ /* update terminal size */
+ term.col = col;
+ term.row = row;
+ /* reset scrolling region */
+ tsetscroll(0, row-1);
+ /* make use of the LIMIT in tmoveto */
+ tmoveto(term.c.x, term.c.y);
+ /* Clearing both screens (it makes dirty all lines) */
+ c = term.c;
+ for (i = 0; i < 2; i++) {
+ if (mincol < col && 0 < minrow) {
+ tclearregion(mincol, 0, col - 1, minrow - 1);
+ }
+ if (0 < col && minrow < row) {
+ tclearregion(0, minrow, col - 1, row - 1);
+ }
+ tswapscreen();
+ tcursor(CursorLoad);
+ }
+ term.c = c;
+}
+
+void
+resettitle(void)
+{
+ xsettitle(nil);
+}
+
+void
+drawregion(int x1, int y1, int x2, int y2)
+{
+ int y;
+
+ for (y = y1; y < y2; y++) {
+ if (!term.dirty[y])
+ continue;
+
+ term.dirty[y] = 0;
+ xdrawline(term.line[y], x1, y, x2);
+ }
+}
+
+void
+draw(void)
+{
+ int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
+
+ if (!xstartdraw())
+ return;
+
+ /* adjust cursor position */
+ LIMIT(term.ocx, 0, term.col-1);
+ LIMIT(term.ocy, 0, term.row-1);
+ if (term.line[term.ocy][term.ocx].mode & Gwdummy)
+ term.ocx--;
+ if (term.line[term.c.y][cx].mode & Gwdummy)
+ cx--;
+
+ drawregion(0, 0, term.col, term.row);
+ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
+ term.line[term.ocy], term.col
+ );
+ term.ocx = cx;
+ term.ocy = term.c.y;
+ xfinishdraw();
+ if (ocx != term.ocx || ocy != term.ocy)
+ xximspot(term.ocx, term.ocy);
+}
+
+void
+redraw(void)
+{
+ tfulldirt();
+ draw();
+}
diff --git a/src/cmd/term/term.h b/src/cmd/term/term.h
new file mode 100644
index 0000000..6784974
--- /dev/null
+++ b/src/cmd/term/term.h
@@ -0,0 +1,316 @@
+/* See LICENSE for license details. */
+#pragma once
+
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <harfbuzz/hb.h>
+
+// -----------------------------------------------------------------------
+// macros
+
+#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
+#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
+#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
+#define GLYPHCMP(a, b) (((a).mode & (~Gwrap) & (~Gliga)) != ((b).mode & (~Gwrap) & (~Gliga)) || \
+ (a).fg != (b).fg || (a).bg != (b).bg)
+#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_nsec-t2.tv_nsec)/1E6)
+#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
+#define IS_TRUECOL(x) (1 << 24 & (x))
+
+#define iota(x) 1 << (x)
+
+/* arbitrary sizes */
+#define ESC_BUF_SIZ (128*UTFmax)
+#define ESC_ARG_SIZ 16
+#define STR_BUF_SIZ ESC_BUF_SIZ
+#define STR_ARG_SIZ ESC_ARG_SIZ
+
+// -----------------------------------------------------------------------
+// constants
+
+enum {
+ Gnil,
+ Gbold = iota(0),
+ Gfaint = iota(1),
+ Gitalic = iota(2),
+ Gunline = iota(3),
+ Gblink = iota(4),
+ Greverse = iota(5),
+ Ginvisible = iota(6),
+ Gstruck = iota(7),
+ Gwrap = iota(8),
+ Gwide = iota(9),
+ Gwdummy = iota(10),
+ Gliga = iota(11),
+ Gboldfaint = Gbold | Gfaint,
+};
+
+enum {
+ SelIdle = 0,
+ SelEmpty = 1,
+ SelReady = 2
+};
+
+enum {
+ SelRegular = 1,
+ SelRectangular = 2
+};
+
+enum {
+ SnapWord = 1,
+ SnapLine = 2
+};
+
+/* cursor state */
+enum {
+ CursorSave,
+ CursorLoad
+};
+
+/* cursor mode */
+enum {
+ CursorDefault = 0,
+ CursorWrap = 1,
+ CursorOrigin = 2
+};
+
+/* character set */
+enum {
+ CSgfx0,
+ CSgfx1,
+ CSuk,
+ CSusa,
+ CSmulti,
+ CSger,
+ CSfin,
+};
+
+/* escape sequences */
+enum {
+ Xstart = 1,
+ Xcsi = 2,
+ Xstr = 4, /* OSC, PM, APC */
+ Xaltcs = 8,
+ Xstrend = 16, /* a final string was encountered */
+ Xtest = 32, /* Enter in test mode */
+ Xutf8 = 64,
+ Xdcs =128,
+};
+
+/* terminal mode */
+enum {
+ Twrap = iota(0),
+ Tinsert = iota(1),
+ Taltscreen = iota(2),
+ Tcrlf = iota(3),
+ Techo = iota(4),
+ Tprint = iota(5),
+ Tutf8 = iota(6),
+ Tsixel = iota(7),
+};
+
+/* window mode */
+enum {
+ Wvisible = iota(0),
+ Wfocused = iota(1),
+ Wappkeypad = iota(2),
+ Wmousebtn = iota(3),
+ Wmousemotion = iota(4),
+ Wreverse = iota(5),
+ Wkbdblock = iota(6),
+ Whide = iota(7),
+ Wappcursor = iota(8),
+ Wmousesgr = iota(9),
+ W8bit = iota(10),
+ Wblink = iota(11),
+ Wbflink = iota(12),
+ Wfocus = iota(13),
+ Wmousex10 = iota(14),
+ Wmousemany = iota(15),
+ Wbrcktpaste = iota(16),
+ Wnumlock = iota(17),
+ Wmouse = Wmousebtn|Wmousemotion|Wmousex10|Wmousemany,
+};
+
+
+// -----------------------------------------------------------------------
+// types
+
+/* term.c */
+typedef struct Letter Letter;
+typedef struct Dot Dot;
+typedef struct Selection Selection;
+typedef struct Terminal Terminal;
+
+typedef union Arg Arg;
+
+struct Letter {
+ rune u; /* character code */
+ ushort mode; /* attribute flags */
+ uint32 fg; /* foreground */
+ uint32 bg; /* background */
+};
+
+struct Dot {
+ Letter attr; /* current char attributes */
+ int x;
+ int y;
+ char state;
+};
+
+struct Selection {
+ int mode;
+ int type;
+ int snap;
+ /*
+ * Selection variables:
+ * nb – normalized coordinates of the beginning of the selection
+ * ne – normalized coordinates of the end of the selection
+ * ob – original coordinates of the beginning of the selection
+ * oe – original coordinates of the end of the selection
+ */
+ struct {
+ int x, y;
+ } nb, ne, ob, oe;
+
+ int alt;
+};
+
+/* Internal representation of the screen */
+struct Terminal {
+ int row; /* nb row */
+ int col; /* nb col */
+ Letter **line; /* screen */
+ Letter **alt; /* alternate screen */
+ int *dirty; /* dirtyness of lines */
+ Dot c; /* cursor */
+ int ocx; /* old cursor col */
+ int ocy; /* old cursor row */
+ int top; /* top scroll limit */
+ int bot; /* bottom scroll limit */
+ int mode; /* terminal mode flags */
+ int esc; /* escape state flags */
+ char trantbl[4];/* charset table translation */
+ int charset; /* current charset */
+ int icharset; /* selected charset for sequence */
+ int *tabs;
+ rune lastc; /* last printed char outside of sequence, 0 if control */
+};
+
+/* CSI Escape sequence structs */
+/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
+typedef struct {
+ char buf[ESC_BUF_SIZ]; /* raw string */
+ ulong len; /* raw string length */
+ char priv;
+ int arg[ESC_ARG_SIZ];
+ int narg; /* nb of args */
+ char mode[2];
+} CSIEscape;
+
+/* STR Escape sequence structs */
+/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
+typedef struct {
+ char type; /* ESC type ... */
+ char *buf; /* allocated raw string */
+ size_t siz; /* allocation size */
+ size_t len; /* raw string length */
+ char *args[STR_ARG_SIZ];
+ int narg; /* nb of args */
+} STREscape;
+
+/* x.c */
+typedef struct TermWindow TermWindow;
+
+struct TermWindow {
+ int tw, th; /* tty width and height */
+ int w, h; /* window width and height */
+ int hb, vb; /* horizontal and vertical border (in pix) */
+ int ch; /* char height */
+ int cw; /* char width */
+ int mode; /* window state/mode flags */
+ int cursor; /* cursor style */
+};
+
+/* used for user hooks */
+union Arg {
+ int i;
+ uint ui;
+ float f;
+ void *v;
+ char *s;
+};
+
+// -----------------------------------------------------------------------
+// x.c (backend functions)
+
+void xbell(void);
+void xclipcopy(void);
+void xdrawcursor(int, int, Letter, int, int, Letter, Letter*, int);
+void xdrawline(Letter*, int, int, int);
+void xfinishdraw(void);
+void xloadcols(void);
+int xsetcolorname(int, char *);
+void xsettitle(char *);
+int xsetcursor(int);
+void xsetmode(int, uint);
+void xsetpointermotion(int);
+void xsetsel(char *);
+int xstartdraw(void);
+void xximspot(int, int);
+
+void fatal( char *, ...);
+void redraw(void);
+void draw(void);
+
+void printscreen(Arg *);
+void printsel(Arg *);
+void sendbreak(Arg *);
+void toggleprinter(Arg *);
+
+int tattrset(int);
+void tnew(int, int);
+void tresize(int, int);
+void tsetdirtattr(int);
+void ttyhangup(void);
+int ttynew(char *, char *, char *, char **);
+ulong ttyread(void);
+void ttyresize(int, int);
+void ttywrite( char *, size_t, int);
+
+void resettitle(void);
+
+void selclear(void);
+void selinit(void);
+void selstart(int, int, int);
+void selextend(int, int, int, int);
+int selected(int, int);
+char *getsel(void);
+
+void *xmalloc(size_t);
+void *xrealloc(void *, size_t);
+char *xstrdup(char *);
+
+/* config.h globals */
+extern char *utmp;
+extern char *scroll;
+extern char *stty_args;
+extern char *vtiden;
+extern wchar *worddelimiters;
+extern int allowaltscreen;
+extern int allowwindowops;
+extern char *termname;
+extern uint tabspaces;
+extern uint defaultfg;
+extern uint defaultbg;
+extern float alpha;
diff --git a/src/cmd/term/term.info b/src/cmd/term/term.info
new file mode 100644
index 0000000..7b90344
--- /dev/null
+++ b/src/cmd/term/term.info
@@ -0,0 +1,250 @@
+term+mono| simpleterm monocolor,
+ acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
+ am,
+ bce,
+ bel=^G,
+ blink=\E[5m,
+ bold=\E[1m,
+ cbt=\E[Z,
+ cvvis=\E[?25h,
+ civis=\E[?25l,
+ clear=\E[H\E[2J,
+ cnorm=\E[?12l\E[?25h,
+ colors#2,
+ cols#80,
+ cr=^M,
+ csr=\E[%i%p1%d;%p2%dr,
+ cub=\E[%p1%dD,
+ cub1=^H,
+ cud1=^J,
+ cud=\E[%p1%dB,
+ cuf1=\E[C,
+ cuf=\E[%p1%dC,
+ cup=\E[%i%p1%d;%p2%dH,
+ cuu1=\E[A,
+ cuu=\E[%p1%dA,
+ dch=\E[%p1%dP,
+ dch1=\E[P,
+ dim=\E[2m,
+ dl=\E[%p1%dM,
+ dl1=\E[M,
+ ech=\E[%p1%dX,
+ ed=\E[J,
+ el=\E[K,
+ el1=\E[1K,
+ enacs=\E)0,
+ flash=\E[?5h$<80/>\E[?5l,
+ fsl=^G,
+ home=\E[H,
+ hpa=\E[%i%p1%dG,
+ hs,
+ ht=^I,
+ hts=\EH,
+ ich=\E[%p1%d@,
+ il1=\E[L,
+ il=\E[%p1%dL,
+ ind=^J,
+ indn=\E[%p1%dS,
+ invis=\E[8m,
+ is2=\E[4l\E>\E[?1034l,
+ it#4,
+ kel=\E[1;2F,
+ ked=\E[1;5F,
+ ka1=\E[1~,
+ ka3=\E[5~,
+ kc1=\E[4~,
+ kc3=\E[6~,
+ kbs=\177,
+ kcbt=\E[Z,
+ kb2=\EOu,
+ kcub1=\EOD,
+ kcud1=\EOB,
+ kcuf1=\EOC,
+ kcuu1=\EOA,
+ kDC=\E[3;2~,
+ kent=\EOM,
+ kEND=\E[1;2F,
+ kIC=\E[2;2~,
+ kNXT=\E[6;2~,
+ kPRV=\E[5;2~,
+ kHOM=\E[1;2H,
+ kLFT=\E[1;2D,
+ kRIT=\E[1;2C,
+ kind=\E[1;2B,
+ kri=\E[1;2A,
+ kclr=\E[3;5~,
+ kdl1=\E[3;2~,
+ kdch1=\E[3~,
+ kich1=\E[2~,
+ kend=\E[4~,
+ kf1=\EOP,
+ kf2=\EOQ,
+ kf3=\EOR,
+ kf4=\EOS,
+ kf5=\E[15~,
+ kf6=\E[17~,
+ kf7=\E[18~,
+ kf8=\E[19~,
+ kf9=\E[20~,
+ kf10=\E[21~,
+ kf11=\E[23~,
+ kf12=\E[24~,
+ kf13=\E[1;2P,
+ kf14=\E[1;2Q,
+ kf15=\E[1;2R,
+ kf16=\E[1;2S,
+ kf17=\E[15;2~,
+ kf18=\E[17;2~,
+ kf19=\E[18;2~,
+ kf20=\E[19;2~,
+ kf21=\E[20;2~,
+ kf22=\E[21;2~,
+ kf23=\E[23;2~,
+ kf24=\E[24;2~,
+ kf25=\E[1;5P,
+ kf26=\E[1;5Q,
+ kf27=\E[1;5R,
+ kf28=\E[1;5S,
+ kf29=\E[15;5~,
+ kf30=\E[17;5~,
+ kf31=\E[18;5~,
+ kf32=\E[19;5~,
+ kf33=\E[20;5~,
+ kf34=\E[21;5~,
+ kf35=\E[23;5~,
+ kf36=\E[24;5~,
+ kf37=\E[1;6P,
+ kf38=\E[1;6Q,
+ kf39=\E[1;6R,
+ kf40=\E[1;6S,
+ kf41=\E[15;6~,
+ kf42=\E[17;6~,
+ kf43=\E[18;6~,
+ kf44=\E[19;6~,
+ kf45=\E[20;6~,
+ kf46=\E[21;6~,
+ kf47=\E[23;6~,
+ kf48=\E[24;6~,
+ kf49=\E[1;3P,
+ kf50=\E[1;3Q,
+ kf51=\E[1;3R,
+ kf52=\E[1;3S,
+ kf53=\E[15;3~,
+ kf54=\E[17;3~,
+ kf55=\E[18;3~,
+ kf56=\E[19;3~,
+ kf57=\E[20;3~,
+ kf58=\E[21;3~,
+ kf59=\E[23;3~,
+ kf60=\E[24;3~,
+ kf61=\E[1;4P,
+ kf62=\E[1;4Q,
+ kf63=\E[1;4R,
+ khome=\E[1~,
+ kil1=\E[2;5~,
+ krmir=\E[2;2~,
+ knp=\E[6~,
+ kmous=\E[M,
+ kpp=\E[5~,
+ lines#24,
+ mir,
+ msgr,
+ npc,
+ op=\E[39;49m,
+ pairs#64,
+ mc0=\E[i,
+ mc4=\E[4i,
+ mc5=\E[5i,
+ rc=\E8,
+ rev=\E[7m,
+ ri=\EM,
+ rin=\E[%p1%dT,
+ ritm=\E[23m,
+ rmacs=\E(B,
+ rmcup=\E[?1049l,
+ rmir=\E[4l,
+ rmkx=\E[?1l\E>,
+ rmso=\E[27m,
+ rmul=\E[24m,
+ rs1=\Ec,
+ rs2=\E[4l\E>\E[?1034l,
+ sc=\E7,
+ sitm=\E[3m,
+ sgr0=\E[0m,
+ smacs=\E(0,
+ smcup=\E[?1049h,
+ smir=\E[4h,
+ smkx=\E[?1h\E=,
+ smso=\E[7m,
+ smul=\E[4m,
+ tbc=\E[3g,
+ tsl=\E]0;,
+ xenl,
+ vpa=\E[%i%p1%dd,
+# XTerm extensions
+ rmxx=\E[29m,
+ smxx=\E[9m,
+# disabled rep for now: causes some issues with older ncurses versions.
+# rep=%p1%c\E[%p2%{1}%-%db,
+# tmux extensions, see TERMINFO EXTENSIONS in tmux(1)
+ Tc,
+ Ms=\E]52;%p1%s;%p2%s\007,
+ Se=\E[2 q,
+ Ss=\E[%p1%d q,
+
+term| simpleterm,
+ use=term+mono,
+ colors#8, pairs#64,
+ setab=\E[4%p1%dm,
+ setaf=\E[3%p1%dm,
+ setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
+ setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
+ sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
+
+term-256color| simpleterm with 256 colors,
+ use=term,
+ ccc,
+ colors#256, pairs#32767,
+ oc=\E]104\007,
+# Nicked from xterm-256color
+ initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\,
+ setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
+ setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
+
+term-direct| simpleterm with true color,
+ use=term,
+ RGB,
+# Nicked from xterm-direct
+ colors#0x1000000, pairs#0x7FFFF,
+ initc@, op=\E[39;49m,
+ setab=\E[%?%p1%{8}%<%t4%p1%d%e48;2;%p1%{65536}%/%d;%p1%{256}
+ %/%{255}%&%d;%p1%{255}%&%d%;m,
+ setaf=\E[%?%p1%{8}%<%t3%p1%d%e38;2;%p1%{65536}%/%d;%p1%{256}
+ %/%{255}%&%d;%p1%{255}%&%d%;m,
+ setb@, setf@,
+
+term-meta| simpleterm with meta key,
+ use=term,
+ km,
+ rmm=\E[?1034l,
+ smm=\E[?1034h,
+ rs2=\E[4l\E>\E[?1034h,
+ is2=\E[4l\E>\E[?1034h,
+
+term-meta-256color| simpleterm with meta key and 256 colors,
+ use=term-256color,
+ km,
+ rmm=\E[?1034l,
+ smm=\E[?1034h,
+ rs2=\E[4l\E>\E[?1034h,
+ is2=\E[4l\E>\E[?1034h,
+
+term-bs| simpleterm with backspace as backspace,
+ use=term,
+ kbs=\010,
+ kdch1=\177,
+
+term-bs-256color| simpleterm with backspace as backspace and 256colors,
+ use=term-256color,
+ kbs=\010,
+ kdch1=\177,
diff --git a/src/cmd/term/util.c b/src/cmd/term/util.c
new file mode 100644
index 0000000..3e7d81b
--- /dev/null
+++ b/src/cmd/term/util.c
@@ -0,0 +1,30 @@
+#include <u.h>
+
+static const uchar table[] = {
+#include "nonspacing.h"
+};
+
+static const uchar wtable[] = {
+#include "wide.h"
+};
+
+int
+wcwidth(wchar_t wc)
+{
+ if (wc < 0xffU)
+ return (wc+1 & 0x7f) >= 0x21 ? 1 : wc ? -1 : 0;
+ if ((wc & 0xfffeffffU) < 0xfffe) {
+ if ((table[table[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1)
+ return 0;
+ if ((wtable[wtable[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1)
+ return 2;
+ return 1;
+ }
+ if ((wc & 0xfffe) == 0xfffe)
+ return -1;
+ if (wc-0x20000U < 0x20000)
+ return 2;
+ if (wc == 0xe0001 || wc-0xe0020U < 0x5f || wc-0xe0100U < 0xef)
+ return 0;
+ return 1;
+}
diff --git a/src/cmd/term/wide.h b/src/cmd/term/wide.h
new file mode 100644
index 0000000..e403c9a
--- /dev/null
+++ b/src/cmd/term/wide.h
@@ -0,0 +1,65 @@
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,18,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,19,16,20,21,22,16,16,16,23,16,16,24,25,26,27,28,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,29,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,30,16,16,16,16,31,16,16,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,16,16,16,33,
+34,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,35,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,36,17,17,37,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,38,39,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,40,41,42,43,44,45,46,47,16,48,49,16,16,16,16,
+16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,6,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,48,0,0,0,0,0,0,255,15,0,0,0,0,128,0,0,8,
+0,2,12,0,96,48,64,16,0,0,4,44,36,32,12,0,0,0,1,0,0,0,80,184,0,0,0,0,0,0,0,224,
+0,0,0,1,128,0,0,0,0,0,0,0,0,0,0,0,24,0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,251,255,255,255,255,255,255,255,
+255,255,255,15,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,63,0,0,0,255,15,255,255,255,255,
+255,255,255,127,254,255,255,255,255,255,255,255,255,255,127,254,255,255,255,
+255,255,255,255,255,255,255,255,255,224,255,255,255,255,255,254,255,255,255,
+255,255,255,255,255,255,255,127,255,255,255,255,255,7,255,255,255,255,15,0,
+255,255,255,255,255,127,255,255,255,255,255,0,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,
+0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,31,255,255,255,255,255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,
+255,255,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,15,0,0,0,0,0,0,0,0,0,0,0,0,0,255,3,0,0,255,255,255,255,247,255,127,15,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,255,255,255,255,255,255,255,255,255,255,
+255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,7,0,255,255,255,127,0,0,0,0,0,
+0,7,0,240,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+15,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,64,254,7,0,0,0,0,0,0,0,0,0,0,0,0,7,0,255,255,255,
+255,255,15,255,1,3,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,
+1,224,191,255,255,255,255,255,255,255,255,223,255,255,15,0,255,255,255,255,
+255,135,15,0,255,255,17,255,255,255,255,255,255,255,255,127,253,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+159,255,255,255,255,255,255,255,63,0,120,255,255,255,0,0,4,0,0,96,0,16,0,0,0,
+0,0,0,0,0,0,0,248,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,
+255,255,255,255,255,255,63,16,39,0,0,24,240,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,255,15,0,
+0,0,224,255,255,255,255,255,255,255,255,255,255,255,255,123,252,255,255,255,
+255,231,199,255,255,255,231,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,15,7,7,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,
diff --git a/src/cmd/term/x.c b/src/cmd/term/x.c
new file mode 100644
index 0000000..27a2e7a
--- /dev/null
+++ b/src/cmd/term/x.c
@@ -0,0 +1,2070 @@
+/* See LICENSE for license details. */
+#include "term.h"
+
+#include <locale.h>
+#include <time.h>
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Xft/Xft.h>
+#include <X11/XKBlib.h>
+
+/* harfbuzz additions */
+#if 0
+void hbunloadfonts();
+void hbtransform(XftGlyphFontSpec *, const Letter*, size_t, int, int);
+#endif
+
+/* types used in config.h */
+typedef struct Shortcut Shortcut;
+typedef struct MouseShortcut MouseShortcut;
+typedef struct Key Key;
+
+struct Shortcut{
+ uint mod;
+ KeySym keysym;
+ void (*func)(Arg *);
+ Arg arg;
+};
+
+struct MouseShortcut {
+ uint mod;
+ uint button;
+ void (*func)(Arg *);
+ Arg arg;
+ uint release;
+};
+
+struct Key {
+ KeySym k;
+ uint mask;
+ char *s;
+ /* three-valued logic variables: 0 indifferent, 1 on, -1 off */
+ schar appkey; /* application keypad */
+ schar appcursor; /* application cursor */
+};
+
+/* X modifiers */
+#define XK_ANY_MOD UINT_MAX
+#define XK_NO_MOD 0
+#define XK_SWITCH_MOD (1<<13)
+
+/* function definitions used in config.h */
+static void clipcopy(Arg *);
+static void clippaste(Arg *);
+static void numlock(Arg *);
+static void selpaste(Arg *);
+static void zoom(Arg *);
+static void zoomabs(Arg *);
+static void zoomreset(Arg *);
+static void ttysend(Arg *);
+
+/* config.h for applying patches and the configuration. */
+#include "config.h"
+
+/* XEMBED messages */
+#define XEMBED_FOCUS_IN 4
+#define XEMBED_FOCUS_OUT 5
+
+/* macros */
+#define IS_SET(flag) ((win.mode & (flag)) != 0)
+#define TRUERED(x) (((x) & 0xff0000) >> 8)
+#define TRUEGREEN(x) (((x) & 0xff00))
+#define TRUEBLUE(x) (((x) & 0xff) << 8)
+
+typedef XftDraw *Draw;
+typedef XftColor Color;
+typedef XftGlyphFontSpec GlyphFontSpec;
+
+/* Purely graphic info */
+typedef struct {
+ Display *dpy;
+ Colormap cmap;
+ Window win;
+ Drawable buf;
+ GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
+ Atom xembed, wmdeletewin, netwmname, netwmpid;
+ struct {
+ XIM xim;
+ XIC xic;
+ XPoint spot;
+ XVaNestedList spotlist;
+ } ime;
+ Draw draw;
+ Visual *vis;
+ XSetWindowAttributes attrs;
+ int scr;
+ int isfixed; /* is fixed geometry? */
+ int depth; /* color depth */
+ int l, t; /* left and top offset */
+ int gm; /* geometry mask */
+} XWindow;
+
+typedef struct {
+ Atom xtarget;
+ char *primary, *clipboard;
+ struct timespec tclick1;
+ struct timespec tclick2;
+} XSelection;
+
+/* Font structure */
+#define Font Font_
+typedef struct {
+ int height;
+ int width;
+ int ascent;
+ int descent;
+ int badslant;
+ int badweight;
+ short lbearing;
+ short rbearing;
+ XftFont *match;
+ FcFontSet *set;
+ FcPattern *pattern;
+} Font;
+
+/* Drawing Context */
+typedef struct {
+ Color *col;
+ size_t collen;
+ Font font, bfont, ifont, ibfont;
+ GC gc;
+} DC;
+
+static inline ushort sixd_to_16bit(int);
+static int xmakeglyphfontspecs(XftGlyphFontSpec *, Letter *, int, int, int);
+static void xdrawglyphfontspecs(XftGlyphFontSpec *, Letter, int, int, int);
+static void xdrawglyph(Letter, int, int);
+static void xclear(int, int, int, int);
+static int xgeommasktogravity(int);
+static int ximopen(Display *);
+static void ximinstantiate(Display *, XPointer, XPointer);
+static void ximdestroy(XIM, XPointer, XPointer);
+static int xicdestroy(XIC, XPointer, XPointer);
+static void xinit(int, int);
+static void cresize(int, int);
+static void xresize(int, int);
+static void xhints(void);
+static int xloadcolor(int, char *, Color *);
+static int xloadfont(Font *, FcPattern *);
+static void xloadfonts(char *, double);
+static void xunloadfont(Font *);
+static void xunloadfonts(void);
+static void xsetenv(void);
+static void xseturgency(int);
+static int evcol(XEvent *);
+static int evrow(XEvent *);
+
+static void expose(XEvent *);
+static void visibility(XEvent *);
+static void unmap(XEvent *);
+static void kpress(XEvent *);
+static void cmessage(XEvent *);
+static void resize(XEvent *);
+static void focus(XEvent *);
+static uint buttonmask(uint);
+static int mouseaction(XEvent *, uint);
+static void brelease(XEvent *);
+static void bpress(XEvent *);
+static void bmotion(XEvent *);
+static void propnotify(XEvent *);
+static void selnotify(XEvent *);
+static void selclear_(XEvent *);
+static void selrequest(XEvent *);
+static void setsel(char *, Time);
+static void mousesel(XEvent *, int);
+static void mousereport(XEvent *);
+static char *kmap(KeySym, uint);
+static int match(uint, uint);
+
+static void run(void);
+static void usage(void);
+
+static void (*handler[LASTEvent])(XEvent *) = {
+ [KeyPress] = kpress,
+ [ClientMessage] = cmessage,
+ [ConfigureNotify] = resize,
+ [VisibilityNotify] = visibility,
+ [UnmapNotify] = unmap,
+ [Expose] = expose,
+ [FocusIn] = focus,
+ [FocusOut] = focus,
+ [MotionNotify] = bmotion,
+ [ButtonPress] = bpress,
+ [ButtonRelease] = brelease,
+ [SelectionClear] = selclear_,
+ [SelectionNotify] = selnotify,
+/*
+ * PropertyNotify is only turned on when there is some INCR transfer happening
+ * for the selection retrieval.
+ */
+ [PropertyNotify] = propnotify,
+ [SelectionRequest] = selrequest,
+};
+
+/* Globals */
+static DC dc;
+static XWindow xw;
+static XSelection xsel;
+static TermWindow win;
+
+/* Font Ring Cache */
+enum {
+ FRC_NORMAL,
+ FRC_ITALIC,
+ FRC_BOLD,
+ FRC_ITALICBOLD
+};
+
+typedef struct {
+ XftFont *font;
+ int flags;
+ rune unicodep;
+} Fontcache;
+
+/* Fontcache is an array now. A new font will be appended to the array. */
+static Fontcache *frc = nil;
+static int frclen = 0;
+static int frccap = 0;
+static char *usedfont = nil;
+static double usedfontsize = 0;
+static double defaultfontsize = 0;
+
+static char *opt_alpha = nil;
+static char *opt_class = nil;
+static char **opt_cmd = nil;
+static char *opt_embed = nil;
+static char *opt_font = nil;
+static char *opt_io = nil;
+static char *opt_line = nil;
+static char *opt_name = nil;
+static char *opt_title = nil;
+
+static int oldbutton = 3; /* button event on startup: 3 = release */
+
+void
+clipcopy(Arg *_)
+{
+ Atom clipboard;
+
+ free(xsel.clipboard);
+ xsel.clipboard = nil;
+
+ if (xsel.primary != nil) {
+ xsel.clipboard = xstrdup(xsel.primary);
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
+ }
+}
+
+void
+clippaste(Arg *_)
+{
+ Atom clipboard;
+
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,
+ xw.win, CurrentTime);
+}
+
+void
+selpaste(Arg *_)
+{
+ XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
+ xw.win, CurrentTime);
+}
+
+void
+numlock(Arg *_)
+{
+ win.mode ^= Wnumlock;
+}
+
+void
+zoom(Arg *arg)
+{
+ Arg larg;
+
+ larg.f = usedfontsize + arg->f;
+ zoomabs(&larg);
+}
+
+void
+zoomabs(Arg *arg)
+{
+ xunloadfonts();
+ xloadfonts(usedfont, arg->f);
+ cresize(0, 0);
+ redraw();
+ xhints();
+}
+
+void
+zoomreset(Arg *arg)
+{
+ Arg larg;
+
+ if (defaultfontsize > 0) {
+ larg.f = defaultfontsize;
+ zoomabs(&larg);
+ }
+}
+
+void
+ttysend(Arg *arg)
+{
+ ttywrite(arg->s, strlen(arg->s), 1);
+}
+
+int
+evcol(XEvent *e)
+{
+ int x = e->xbutton.x - win.hb;
+ LIMIT(x, 0, win.tw - 1);
+ return x / win.cw;
+}
+
+int
+evrow(XEvent *e)
+{
+ int y = e->xbutton.y - win.vb;
+ LIMIT(y, 0, win.th - 1);
+ return y / win.ch;
+}
+
+void
+mousesel(XEvent *e, int done)
+{
+ int type, seltype = SelRegular;
+ uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
+
+ for (type = 1; type < arrlen(selmasks); ++type) {
+ if (match(selmasks[type], state)) {
+ seltype = type;
+ break;
+ }
+ }
+ selextend(evcol(e), evrow(e), seltype, done);
+ if (done)
+ setsel(getsel(), e->xbutton.time);
+}
+
+void
+mousereport(XEvent *e)
+{
+ int len, x = evcol(e), y = evrow(e),
+ button = e->xbutton.button, state = e->xbutton.state;
+ char buf[40];
+ static int ox, oy;
+
+ /* from urxvt */
+ if (e->xbutton.type == MotionNotify) {
+ if (x == ox && y == oy)
+ return;
+ if (!IS_SET(Wmousemotion) && !IS_SET(Wmousemany))
+ return;
+ /* MOUSE_MOTION: no reporting if no button is pressed */
+ if (IS_SET(Wmousemotion) && oldbutton == 3)
+ return;
+
+ button = oldbutton + 32;
+ ox = x;
+ oy = y;
+ } else {
+ if (!IS_SET(Wmousesgr) && e->xbutton.type == ButtonRelease) {
+ button = 3;
+ } else {
+ button -= Button1;
+ if (button >= 3)
+ button += 64 - 3;
+ }
+ if (e->xbutton.type == ButtonPress) {
+ oldbutton = button;
+ ox = x;
+ oy = y;
+ } else if (e->xbutton.type == ButtonRelease) {
+ oldbutton = 3;
+ /* Wmousex10: no button release reporting */
+ if (IS_SET(Wmousex10))
+ return;
+ if (button == 64 || button == 65)
+ return;
+ }
+ }
+
+ if (!IS_SET(Wmousex10)) {
+ button += ((state & ShiftMask ) ? 4 : 0)
+ + ((state & Mod4Mask ) ? 8 : 0)
+ + ((state & ControlMask) ? 16 : 0);
+ }
+
+ if (IS_SET(Wmousesgr)) {
+ len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
+ button, x+1, y+1,
+ e->xbutton.type == ButtonRelease ? 'm' : 'M');
+ } else if (x < 223 && y < 223) {
+ len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
+ 32+button, 32+x+1, 32+y+1);
+ } else {
+ return;
+ }
+
+ ttywrite(buf, len, 0);
+}
+
+uint
+buttonmask(uint button)
+{
+ return button == Button1 ? Button1Mask
+ : button == Button2 ? Button2Mask
+ : button == Button3 ? Button3Mask
+ : button == Button4 ? Button4Mask
+ : button == Button5 ? Button5Mask
+ : 0;
+}
+
+int
+mouseaction(XEvent *e, uint release)
+{
+ MouseShortcut *ms;
+
+ /* ignore Button<N>mask for Button<N> - it's set on release */
+ uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
+
+ for (ms = mshortcuts; ms < mshortcuts + arrlen(mshortcuts); ms++) {
+ if (ms->release == release &&
+ ms->button == e->xbutton.button &&
+ (match(ms->mod, state) || /* exact or forced */
+ match(ms->mod, state & ~forcemousemod))) {
+ ms->func(&(ms->arg));
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+bpress(XEvent *e)
+{
+ struct timespec now;
+ int snap;
+
+ if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) {
+ mousereport(e);
+ return;
+ }
+
+ if (mouseaction(e, 0))
+ return;
+
+ if (e->xbutton.button == Button1) {
+ /*
+ * If the user clicks below predefined timeouts specific
+ * snapping behaviour is exposed.
+ */
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) {
+ snap = SnapLine;
+ } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) {
+ snap = SnapWord;
+ } else {
+ snap = 0;
+ }
+ xsel.tclick2 = xsel.tclick1;
+ xsel.tclick1 = now;
+
+ selstart(evcol(e), evrow(e), snap);
+ }
+}
+
+void
+propnotify(XEvent *e)
+{
+ XPropertyEvent *xpev;
+ Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+
+ xpev = &e->xproperty;
+ if (xpev->state == PropertyNewValue &&
+ (xpev->atom == XA_PRIMARY ||
+ xpev->atom == clipboard)) {
+ selnotify(e);
+ }
+}
+
+void
+selnotify(XEvent *e)
+{
+ ulong nitems, ofs, rem;
+ int format;
+ uchar *data, *last, *repl;
+ Atom type, incratom, property = None;
+
+ incratom = XInternAtom(xw.dpy, "INCR", 0);
+
+ ofs = 0;
+ if (e->type == SelectionNotify)
+ property = e->xselection.property;
+ else if (e->type == PropertyNotify)
+ property = e->xproperty.atom;
+
+ if (property == None)
+ return;
+
+ do {
+ if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
+ BUFSIZ/4, False, AnyPropertyType,
+ &type, &format, &nitems, &rem,
+ &data)) {
+ fprintf(stderr, "Clipboard allocation failed\n");
+ return;
+ }
+
+ if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
+ /*
+ * If there is some PropertyNotify with no data, then
+ * this is the signal of the selection owner that all
+ * data has been transferred. We won't need to receive
+ * PropertyNotify events anymore.
+ */
+ MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
+ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
+ &xw.attrs);
+ }
+
+ if (type == incratom) {
+ /*
+ * Activate the PropertyNotify events so we receive
+ * when the selection owner does send us the next
+ * chunk of data.
+ */
+ MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
+ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
+ &xw.attrs);
+
+ /*
+ * Deleting the property is the transfer start signal.
+ */
+ XDeleteProperty(xw.dpy, xw.win, (int)property);
+ continue;
+ }
+
+ /*
+ * As seen in getsel:
+ * Line endings are inconsistent in the terminal and GUI world
+ * copy and pasting. When receiving some selection data,
+ * replace all '\n' with '\r'.
+ * FIXME: Fix the computer world.
+ */
+ repl = data;
+ last = data + nitems * format / 8;
+ while ((repl = memchr(repl, '\n', last - repl))) {
+ *repl++ = '\r';
+ }
+
+ if (IS_SET(Wbrcktpaste) && ofs == 0)
+ ttywrite("\033[200~", 6, 0);
+ ttywrite((char *)data, nitems * format / 8, 1);
+ if (IS_SET(Wbrcktpaste) && rem == 0)
+ ttywrite("\033[201~", 6, 0);
+ XFree(data);
+ /* number of 32-bit chunks returned */
+ ofs += nitems * format / 32;
+ } while (rem > 0);
+
+ /*
+ * Deleting the property again tells the selection owner to send the
+ * next data chunk in the property.
+ */
+ XDeleteProperty(xw.dpy, xw.win, (int)property);
+}
+
+void
+xclipcopy(void)
+{
+ clipcopy(nil);
+}
+
+void
+selclear_(XEvent *e)
+{
+ selclear();
+}
+
+void
+selrequest(XEvent *e)
+{
+ XSelectionRequestEvent *xsre;
+ XSelectionEvent xev;
+ Atom xa_targets, string, clipboard;
+ char *seltext;
+
+ xsre = (XSelectionRequestEvent *) e;
+ xev.type = SelectionNotify;
+ xev.requestor = xsre->requestor;
+ xev.selection = xsre->selection;
+ xev.target = xsre->target;
+ xev.time = xsre->time;
+ if (xsre->property == None)
+ xsre->property = xsre->target;
+
+ /* reject */
+ xev.property = None;
+
+ xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
+ if (xsre->target == xa_targets) {
+ /* respond with the supported type */
+ string = xsel.xtarget;
+ XChangeProperty(xsre->display, xsre->requestor, xsre->property,
+ XA_ATOM, 32, PropModeReplace,
+ (uchar *) &string, 1);
+ xev.property = xsre->property;
+ } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) {
+ /*
+ * xith XA_STRING non ascii characters may be incorrect in the
+ * requestor. It is not our problem, use utf8.
+ */
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ if (xsre->selection == XA_PRIMARY) {
+ seltext = xsel.primary;
+ } else if (xsre->selection == clipboard) {
+ seltext = xsel.clipboard;
+ } else {
+ fprintf(stderr,
+ "Unhandled clipboard selection 0x%lx\n",
+ xsre->selection);
+ return;
+ }
+ if (seltext != nil) {
+ XChangeProperty(xsre->display, xsre->requestor,
+ xsre->property, xsre->target,
+ 8, PropModeReplace,
+ (uchar *)seltext, strlen(seltext));
+ xev.property = xsre->property;
+ }
+ }
+
+ /* all done, send a notification to the listener */
+ if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
+ fprintf(stderr, "Error sending SelectionNotify event\n");
+}
+
+void
+setsel(char *str, Time t)
+{
+ if (!str)
+ return;
+
+ free(xsel.primary);
+ xsel.primary = str;
+
+ XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
+ if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
+ selclear();
+}
+
+void
+xsetsel(char *str)
+{
+ setsel(str, CurrentTime);
+}
+
+void
+brelease(XEvent *e)
+{
+ if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) {
+ mousereport(e);
+ return;
+ }
+
+ if (mouseaction(e, 1))
+ return;
+ if (e->xbutton.button == Button1)
+ mousesel(e, 1);
+}
+
+void
+bmotion(XEvent *e)
+{
+ if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) {
+ mousereport(e);
+ return;
+ }
+
+ mousesel(e, 0);
+}
+
+void
+cresize(int width, int height)
+{
+ int col, row;
+
+ if (width != 0)
+ win.w = width;
+ if (height != 0)
+ win.h = height;
+
+ col = (win.w - 2 * borderpx) / win.cw;
+ row = (win.h - 2 * borderpx) / win.ch;
+ col = MAX(1, col);
+ row = MAX(1, row);
+
+ win.hb = (win.w - col*win.cw)/2;
+ win.vb = (win.h - row*win.ch)/2;
+
+ tresize(col, row);
+ xresize(col, row);
+ ttyresize(win.tw, win.th);
+}
+
+void
+xresize(int col, int row)
+{
+ win.tw = col * win.cw;
+ win.th = row * win.ch;
+
+ XFreePixmap(xw.dpy, xw.buf);
+ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth);
+
+ XftDrawChange(xw.draw, xw.buf);
+ xclear(0, 0, win.w, win.h);
+
+ /* resize to new width */
+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
+}
+
+ushort
+sixd_to_16bit(int x)
+{
+ return x == 0 ? 0 : 0x3737 + 0x2828 * x;
+}
+
+int
+xloadcolor(int i, char *name, Color *ncolor)
+{
+ XRenderColor color = { .alpha = 0xffff };
+
+ if (!name) {
+ if (BETWEEN(i, 16, 255)) { /* 256 color */
+ if (i < 6*6*6+16) { /* same colors as xterm */
+ color.red = sixd_to_16bit( ((i-16)/36)%6 );
+ color.green = sixd_to_16bit( ((i-16)/6) %6 );
+ color.blue = sixd_to_16bit( ((i-16)/1) %6 );
+ } else { /* greyscale */
+ color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
+ color.green = color.blue = color.red;
+ }
+ return XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, ncolor);
+ } else
+ name = colorname[i];
+ }
+
+ return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
+}
+
+void
+xloadcols(void)
+{
+ int i;
+ static int loaded;
+ Color *cp;
+
+ if (loaded) {
+ for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
+ XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
+ } else {
+ dc.collen = MAX(arrlen(colorname), 256);
+ dc.col = xmalloc(dc.collen * sizeof(Color));
+ }
+
+ for (i = 0; i < dc.collen; i++)
+ if (!xloadcolor(i, nil, &dc.col[i])) {
+ if (colorname[i])
+ fatal("could not allocate color '%s'\n", colorname[i]);
+ else
+ fatal("could not allocate color %d\n", i);
+ }
+
+ if (opt_alpha)
+ alpha = strtof(opt_alpha, nil);
+
+ dc.col[defaultbg].color.alpha = (ushort)(0xffff * alpha);
+ dc.col[defaultbg].pixel &= 0x00ffffff;
+ dc.col[defaultbg].pixel |= (uchar)(0xff*alpha) << 24;
+ loaded = 1;
+}
+
+int
+xsetcolorname(int x, char *name)
+{
+ Color ncolor;
+
+ if (!BETWEEN(x, 0, dc.collen))
+ return 1;
+
+ if (!xloadcolor(x, name, &ncolor))
+ return 1;
+
+ XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
+ dc.col[x] = ncolor;
+
+ return 0;
+}
+
+/*
+ * Absolute coordinates.
+ */
+void
+xclear(int x1, int y1, int x2, int y2)
+{
+ XftDrawRect(xw.draw, &dc.col[IS_SET(Wreverse)? defaultfg : defaultbg], x1, y1, x2-x1, y2-y1);
+}
+
+void
+xhints(void)
+{
+ XClassHint class = {opt_name ? opt_name : termname,
+ opt_class ? opt_class : termname};
+ XWMHints wm = {.flags = InputHint, .input = 1};
+ XSizeHints *sizeh;
+
+ sizeh = XAllocSizeHints();
+
+ sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
+ sizeh->height = win.h, sizeh->width = win.w;
+ sizeh->height_inc = 1;
+ sizeh->width_inc = 1;
+ sizeh->base_height = 2 * borderpx;
+ sizeh->base_width = 2 * borderpx;
+ sizeh->min_height = win.ch + 2 * borderpx;
+ sizeh->min_width = win.cw + 2 * borderpx;
+ if (xw.isfixed) {
+ sizeh->flags |= PMaxSize;
+ sizeh->min_width = sizeh->max_width = win.w;
+ sizeh->min_height = sizeh->max_height = win.h;
+ }
+ if (xw.gm & (XValue|YValue)) {
+ sizeh->flags |= USPosition | PWinGravity;
+ sizeh->x = xw.l;
+ sizeh->y = xw.t;
+ sizeh->win_gravity = xgeommasktogravity(xw.gm);
+ }
+
+ XSetWMProperties(xw.dpy, xw.win, nil, nil, nil, 0, sizeh, &wm,
+ &class);
+ XFree(sizeh);
+}
+
+int
+xgeommasktogravity(int mask)
+{
+ switch (mask & (XNegative|YNegative)) {
+ case 0:
+ return NorthWestGravity;
+ case XNegative:
+ return NorthEastGravity;
+ case YNegative:
+ return SouthWestGravity;
+ }
+
+ return SouthEastGravity;
+}
+
+int
+xloadfont(Font *f, FcPattern *pattern)
+{
+ FcPattern *configured;
+ FcPattern *match;
+ FcResult result;
+ XGlyphInfo extents;
+ int wantattr, haveattr;
+
+ /*
+ * Manually configure instead of calling XftMatchFont
+ * so that we can use the configured pattern for
+ * "missing glyph" lookups.
+ */
+ configured = FcPatternDuplicate(pattern);
+ if (!configured)
+ return 1;
+
+ FcConfigSubstitute(nil, configured, FcMatchPattern);
+ XftDefaultSubstitute(xw.dpy, xw.scr, configured);
+
+ match = FcFontMatch(nil, configured, &result);
+ if (!match) {
+ FcPatternDestroy(configured);
+ return 1;
+ }
+
+ if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
+ FcPatternDestroy(configured);
+ FcPatternDestroy(match);
+ return 1;
+ }
+
+ if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==
+ XftResultMatch)) {
+ /*
+ * Check if xft was unable to find a font with the appropriate
+ * slant but gave us one anyway. Try to mitigate.
+ */
+ if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
+ &haveattr) != XftResultMatch) || haveattr < wantattr) {
+ f->badslant = 1;
+ fputs("font slant does not match\n", stderr);
+ }
+ }
+
+ if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==
+ XftResultMatch)) {
+ if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
+ &haveattr) != XftResultMatch) || haveattr != wantattr) {
+ f->badweight = 1;
+ fputs("font weight does not match\n", stderr);
+ }
+ }
+
+ XftTextExtentsUtf8(xw.dpy, f->match,
+ (const FcChar8 *) ascii_printable,
+ strlen(ascii_printable), &extents);
+
+ f->set = nil;
+ f->pattern = configured;
+
+ f->ascent = f->match->ascent;
+ f->descent = f->match->descent;
+ f->lbearing = 0;
+ f->rbearing = f->match->max_advance_width;
+
+ f->height = f->ascent + f->descent;
+ f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
+
+ return 0;
+}
+
+void
+xloadfonts(char *fontstr, double fontsize)
+{
+ FcPattern *pattern;
+ double fontval;
+
+ if (fontstr[0] == '-')
+ pattern = XftXlfdParse(fontstr, False, False);
+ else
+ pattern = FcNameParse((FcChar8 *)fontstr);
+
+ if (!pattern)
+ fatal("can't open font %s\n", fontstr);
+
+ if (fontsize > 1) {
+ FcPatternDel(pattern, FC_PIXEL_SIZE);
+ FcPatternDel(pattern, FC_SIZE);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
+ usedfontsize = fontsize;
+ } else {
+ if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
+ FcResultMatch) {
+ usedfontsize = fontval;
+ } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
+ FcResultMatch) {
+ usedfontsize = -1;
+ } else {
+ /*
+ * Default font size is 12, if none given. This is to
+ * have a known usedfontsize value.
+ */
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
+ usedfontsize = 12;
+ }
+ defaultfontsize = usedfontsize;
+ }
+
+ if (xloadfont(&dc.font, pattern))
+ fatal("can't open font %s\n", fontstr);
+
+ if (usedfontsize < 0) {
+ FcPatternGetDouble(dc.font.match->pattern,
+ FC_PIXEL_SIZE, 0, &fontval);
+ usedfontsize = fontval;
+ if (fontsize == 0)
+ defaultfontsize = fontval;
+ }
+
+ /* Setting character width and height. */
+ win.cw = ceilf(dc.font.width * cwscale);
+ win.ch = ceilf(dc.font.height * chscale);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
+ if (xloadfont(&dc.ifont, pattern))
+ fatal("can't open font %s\n", fontstr);
+
+ FcPatternDel(pattern, FC_WEIGHT);
+ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
+ if (xloadfont(&dc.ibfont, pattern))
+ fatal("can't open font %s\n", fontstr);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
+ if (xloadfont(&dc.bfont, pattern))
+ fatal("can't open font %s\n", fontstr);
+
+ FcPatternDestroy(pattern);
+}
+
+void
+xunloadfont(Font *f)
+{
+ XftFontClose(xw.dpy, f->match);
+ FcPatternDestroy(f->pattern);
+ if (f->set)
+ FcFontSetDestroy(f->set);
+}
+
+void
+xunloadfonts(void)
+{
+#if 0
+ hbunloadfonts();
+#endif
+
+ /* Free the loaded fonts in the font cache. */
+ while (frclen > 0)
+ XftFontClose(xw.dpy, frc[--frclen].font);
+
+ xunloadfont(&dc.font);
+ xunloadfont(&dc.bfont);
+ xunloadfont(&dc.ifont);
+ xunloadfont(&dc.ibfont);
+}
+
+int
+ximopen(Display *dpy)
+{
+ XIMCallback imdestroy = { .client_data = nil, .callback = ximdestroy };
+ XICCallback icdestroy = { .client_data = nil, .callback = xicdestroy };
+
+ xw.ime.xim = XOpenIM(xw.dpy, nil, nil, nil);
+ if (xw.ime.xim == nil)
+ return 0;
+
+ if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, nil))
+ fprintf(stderr, "XSetIMValues: "
+ "Could not set XNDestroyCallback.\n");
+
+ xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
+ nil);
+
+ if (xw.ime.xic == nil) {
+ xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
+ XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, xw.win,
+ XNDestroyCallback, &icdestroy,
+ nil);
+ }
+ if (xw.ime.xic == nil)
+ fprintf(stderr, "XCreateIC: Could not create input context.\n");
+
+ return 1;
+}
+
+void
+ximinstantiate(Display *dpy, XPointer client, XPointer call)
+{
+ if (ximopen(dpy))
+ XUnregisterIMInstantiateCallback(xw.dpy, nil, nil, nil,
+ ximinstantiate, nil);
+}
+
+void
+ximdestroy(XIM xim, XPointer client, XPointer call)
+{
+ xw.ime.xim = nil;
+ XRegisterIMInstantiateCallback(xw.dpy, nil, nil, nil,
+ ximinstantiate, nil);
+ XFree(xw.ime.spotlist);
+}
+
+int
+xicdestroy(XIC xim, XPointer client, XPointer call)
+{
+ xw.ime.xic = nil;
+ return 1;
+}
+
+void
+xinit(int cols, int rows)
+{
+ XGCValues gcvalues;
+ Cursor cursor;
+ Window parent;
+ XColor xmousefg, xmousebg;
+ XWindowAttributes attr;
+ XVisualInfo vis;
+ pid_t thispid = getpid();
+
+ if (!(xw.dpy = XOpenDisplay(nil)))
+ fatal("can't open display\n");
+
+ if (!(opt_embed && (parent == strtol(opt_embed, nil, 0)))) {
+ parent = XRootWindow(xw.dpy, xw.scr);
+ xw.depth = 32;
+ } else {
+ XGetWindowAttributes(xw.dpy, parent, &attr);
+ xw.depth = attr.depth;
+ }
+
+ XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis);
+ xw.vis = vis.visual;
+ xw.scr = XDefaultScreen(xw.dpy);
+
+ /* font */
+ if (!FcInit())
+ fatal("could not init fontconfig.\n");
+
+ usedfont = (opt_font == nil)? font : opt_font;
+ xloadfonts(usedfont, 0);
+
+ /* colors */
+ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None);
+ xloadcols();
+
+ /* adjust fixed window geometry */
+ win.w = 2 * win.hb + cols*win.cw;
+ win.h = 2 * win.vb + rows*win.ch;
+
+ if (xw.gm & XNegative)
+ xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
+ if (xw.gm & YNegative)
+ xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
+
+ /* Events */
+ xw.attrs.background_pixel = dc.col[defaultbg].pixel;
+ xw.attrs.border_pixel = dc.col[defaultbg].pixel;
+ xw.attrs.bit_gravity = NorthWestGravity;
+ xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
+ | ExposureMask | VisibilityChangeMask | StructureNotifyMask
+ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
+ xw.attrs.colormap = xw.cmap;
+
+ xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
+ win.w, win.h, 0, xw.depth, InputOutput,
+ xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
+ | CWEventMask | CWColormap, &xw.attrs);
+
+ memset(&gcvalues, 0, sizeof(gcvalues));
+ gcvalues.graphics_exposures = False;
+
+ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth);
+ dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues);
+
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
+
+ /* font spec buffer */
+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
+
+ /* Xft rendering context */
+ xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
+
+ /* input methods */
+ if (!ximopen(xw.dpy))
+ XRegisterIMInstantiateCallback(xw.dpy, nil, nil, nil, ximinstantiate, nil);
+
+ /* white cursor, black outline */
+ cursor = XCreateFontCursor(xw.dpy, mouseshape);
+ XDefineCursor(xw.dpy, xw.win, cursor);
+
+ if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {
+ xmousefg.red = 0xffff;
+ xmousefg.green = 0xffff;
+ xmousefg.blue = 0xffff;
+ }
+
+ if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {
+ xmousebg.red = 0x0000;
+ xmousebg.green = 0x0000;
+ xmousebg.blue = 0x0000;
+ }
+
+ XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
+
+ xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
+ xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
+ xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
+ XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
+
+ xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
+ XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
+ PropModeReplace, (uchar *)&thispid, 1);
+
+ win.mode = Wnumlock;
+ resettitle();
+ xhints();
+
+ XMapWindow(xw.dpy, xw.win);
+ XSync(xw.dpy, False);
+
+ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
+ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);
+ xsel.primary = nil;
+ xsel.clipboard = nil;
+ xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
+ if (xsel.xtarget == None)
+ xsel.xtarget = XA_STRING;
+}
+
+int
+xmakeglyphfontspecs(XftGlyphFontSpec *specs, Letter *glyphs, int len, int x, int y)
+{
+ float winx = win.hb + x * win.cw, winy = win.vb + y * win.ch, xp, yp;
+ ushort mode, prevmode = USHRT_MAX;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+ float runewidth = win.cw;
+ rune r;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { nil };
+ FcCharSet *fccharset;
+ int i, f, numspecs = 0;
+
+ for(i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
+ /* Fetch rune and mode for current glyph. */
+ r = glyphs[i].u;
+ mode = glyphs[i].mode;
+
+ /* Skip dummy wide-character spacing. */
+#if 0
+ if(mode & Gwdummy)
+#endif
+ if(mode == Gwdummy)
+ continue;
+
+ /* Determine font for glyph if different from previous glyph. */
+ if(prevmode != mode){
+ prevmode = mode;
+ font = &dc.font;
+ frcflags = FRC_NORMAL;
+ runewidth = win.cw * ((mode & Gwide) ? 2.0f : 1.0f);
+ if ((mode & Gitalic) && (mode & Gbold)) {
+ font = &dc.ibfont;
+ frcflags = FRC_ITALICBOLD;
+ } else if (mode & Gitalic) {
+ font = &dc.ifont;
+ frcflags = FRC_ITALIC;
+ } else if (mode & Gbold) {
+ font = &dc.bfont;
+ frcflags = FRC_BOLD;
+ }
+ yp = winy + font->ascent;
+ }
+
+ /* lookup character index with default font. */
+ glyphidx = XftCharIndex(xw.dpy, font->match, r);
+ if(glyphidx){
+ specs[numspecs].font = font->match;
+ specs[numspecs].glyph = glyphidx;
+ specs[numspecs].x = (short)xp;
+ specs[numspecs].y = (short)yp;
+ xp += runewidth;
+ numspecs++;
+ continue;
+ }
+
+ /* Fallback on font cache, search the font cache for match. */
+ for(f = 0; f < frclen; f++) {
+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, r);
+ /* Everything correct. */
+ if (glyphidx && frc[f].flags == frcflags)
+ break;
+ /* We got a default font for a not found glyph. */
+ if (!glyphidx && frc[f].flags == frcflags
+ && frc[f].unicodep == r) {
+ break;
+ }
+ }
+
+ /* Nothing was found. Use fontconfig to find matching font. */
+ if(f >= frclen) {
+ if (!font->set)
+ font->set = FcFontSort(0, font->pattern,
+ 1, 0, &fcres);
+ fcsets[0] = font->set;
+
+ /*
+ * Nothing was found in the cache. Now use
+ * some dozen of Fontconfig calls to get the
+ * font for one single character.
+ *
+ * Xft and fontconfig are design failures.
+ */
+ fcpattern = FcPatternDuplicate(font->pattern);
+ fccharset = FcCharSetCreate();
+
+ FcCharSetAddChar(fccharset, r);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
+ fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
+
+ FcConfigSubstitute(0, fcpattern,
+ FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+
+ fontpattern = FcFontSetMatch(0, fcsets, 1,
+ fcpattern, &fcres);
+
+ /* Allocate memory for the new cache entry. */
+ if (frclen >= frccap) {
+ frccap += 16;
+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ }
+
+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
+ fontpattern);
+ if (!frc[frclen].font)
+ fatal("XftFontOpenPattern failed seeking fallback font: %s\n",
+ strerror(errno));
+ frc[frclen].flags = frcflags;
+ frc[frclen].unicodep = r;
+
+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, r);
+
+ f = frclen;
+ frclen++;
+
+ FcPatternDestroy(fcpattern);
+ FcCharSetDestroy(fccharset);
+ }
+
+ specs[numspecs].font = frc[f].font;
+ specs[numspecs].glyph = glyphidx;
+ specs[numspecs].x = (short)xp;
+ specs[numspecs].y = (short)yp;
+ xp += runewidth;
+ numspecs++;
+ }
+
+#if 0
+ hbtransform(specs, glyphs, len, x, y);
+#endif
+
+ return numspecs;
+}
+
+void
+xdrawglyphfontspecs(XftGlyphFontSpec *specs, Letter base, int len, int x, int y)
+{
+ int charlen = len * ((base.mode & Gwide) ? 2 : 1);
+ int winx = win.hb + x * win.cw, winy = win.vb + y * win.ch, width = charlen * win.cw;
+ Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
+ XRenderColor colfg, colbg;
+ XRectangle r;
+
+ /* Fallback on color display for attributes not supported by the font */
+ if (base.mode & Gitalic && base.mode & Gbold) {
+ if (dc.ibfont.badslant || dc.ibfont.badweight)
+ base.fg = defaultattr;
+ } else if ((base.mode & Gitalic && dc.ifont.badslant) ||
+ (base.mode & Gbold && dc.bfont.badweight)) {
+ base.fg = defaultattr;
+ }
+
+ if (IS_TRUECOL(base.fg)) {
+ colfg.alpha = 0xffff;
+ colfg.red = TRUERED(base.fg);
+ colfg.green = TRUEGREEN(base.fg);
+ colfg.blue = TRUEBLUE(base.fg);
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
+ fg = &truefg;
+ } else {
+ fg = &dc.col[base.fg];
+ }
+
+ if (IS_TRUECOL(base.bg)) {
+ colbg.alpha = 0xffff;
+ colbg.green = TRUEGREEN(base.bg);
+ colbg.red = TRUERED(base.bg);
+ colbg.blue = TRUEBLUE(base.bg);
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
+ bg = &truebg;
+ } else {
+ bg = &dc.col[base.bg];
+ }
+
+ /* Change basic system colors [0-7] to bright system colors [8-15] */
+ if ((base.mode & Gfaint) == Gbold && BETWEEN(base.fg, 0, 7))
+ fg = &dc.col[base.fg + 8];
+
+ if (IS_SET(Wreverse)) {
+ if (fg == &dc.col[defaultfg]) {
+ fg = &dc.col[defaultbg];
+ } else {
+ colfg.red = ~fg->color.red;
+ colfg.green = ~fg->color.green;
+ colfg.blue = ~fg->color.blue;
+ colfg.alpha = fg->color.alpha;
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
+ fg = &revfg;
+ }
+
+ if (bg == &dc.col[defaultbg]) {
+ bg = &dc.col[defaultfg];
+ } else {
+ colbg.red = ~bg->color.red;
+ colbg.green = ~bg->color.green;
+ colbg.blue = ~bg->color.blue;
+ colbg.alpha = bg->color.alpha;
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &revbg);
+ bg = &revbg;
+ }
+ }
+
+ if ((base.mode & Gboldfaint) == Gfaint) {
+ colfg.red = fg->color.red / 2;
+ colfg.green = fg->color.green / 2;
+ colfg.blue = fg->color.blue / 2;
+ colfg.alpha = fg->color.alpha;
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
+ fg = &revfg;
+ }
+
+ if (base.mode & Greverse)
+ temp = fg, fg = bg, bg = temp;
+
+ if (base.mode & Gblink && win.mode & Wblink)
+ fg = bg;
+
+ if (base.mode & Ginvisible)
+ fg = bg;
+
+ /* Intelligent cleaning up of the borders. */
+ if (x == 0) {
+ xclear(0, (y == 0)? 0 : winy, win.vb,
+ winy + win.ch +
+ ((winy + win.ch >= win.vb + win.th)? win.h : 0));
+ }
+ if (winx + width >= win.hb + win.tw) {
+ xclear(winx + width, (y == 0)? 0 : winy, win.w,
+ ((winy + win.ch >= win.vb + win.th)? win.h : (winy + win.ch)));
+ }
+ if (y == 0)
+ xclear(winx, 0, winx + width, win.hb);
+ if (winy + win.ch >= win.hb + win.th)
+ xclear(winx, winy + win.ch, winx + width, win.h);
+
+ /* Clean up the region we want to draw to. */
+ XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
+
+ /* Set the clip region because Xft is sometimes dirty. */
+ r.x = 0, r.y = 0;
+ r.height = win.ch;
+ r.width = width;
+ XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
+
+ /* Render the glyphs. */
+ XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
+
+ /* Render underline and strikethrough. */
+ if (base.mode & Gunline)
+ XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1);
+
+ if (base.mode & Gstruck)
+ XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, width, 1);
+
+ /* Reset clip to none. */
+ XftDrawSetClip(xw.draw, 0);
+}
+
+void
+xdrawglyph(Letter g, int x, int y)
+{
+ int numspecs;
+ XftGlyphFontSpec spec;
+
+ numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
+ xdrawglyphfontspecs(&spec, g, numspecs, x, y);
+}
+
+void
+xdrawcursor(int cx, int cy, Letter g, int ox, int oy, Letter og, Letter *line, int len)
+{
+ Color drawcol;
+
+ /* remove the old cursor */
+ if(selected(ox, oy))
+ og.mode ^= Greverse;
+ xdrawglyph(og, ox, oy);
+#if 0
+ xdrawline(line, 0, oy, len);
+#endif
+
+ if(IS_SET(Whide))
+ return;
+
+ /*
+ * Select the right color for the right mode.
+ */
+ g.mode &= Gbold|Gitalic|Gunline|Gstruck|Gwide;
+
+ if(IS_SET(Wreverse)) {
+ g.mode |= Greverse;
+ g.bg = defaultfg;
+ if (selected(cx, cy)) {
+ drawcol = dc.col[defaultcs];
+ g.fg = defaultrcs;
+ } else {
+ drawcol = dc.col[defaultrcs];
+ g.fg = defaultcs;
+ }
+ } else {
+ if(selected(cx, cy)) {
+ g.fg = defaultfg;
+ g.bg = defaultrcs;
+ } else {
+ g.fg = og.bg; //defaultbg;
+ g.bg = og.fg; //defaultcs;
+ if (IS_TRUECOL(og.fg)) {
+ drawcol.color.alpha = 0xffff;
+ drawcol.color.red = TRUERED(og.fg);
+ drawcol.color.green = TRUEGREEN(og.fg);
+ drawcol.color.blue = TRUEBLUE(og.fg);
+ goto drawnew;
+ }
+ }
+ drawcol = dc.col[g.bg];
+ }
+
+drawnew:
+ if(IS_SET(Wfocused)) {
+ switch (win.cursor) {
+ case 7: /* st extension */
+ g.u = 0x2603; /* snowman (U+2603) */
+ /* fallthrough */
+ case 0: /* Blinking Block */
+ case 1: /* Blinking Block (Default) */
+ case 2: /* Steady Block */
+ xdrawglyph(g, cx, cy);
+ break;
+ case 3: /* Blinking Underline */
+ case 4: /* Steady Underline */
+ XftDrawRect(xw.draw, &drawcol,
+ win.hb + cx * win.cw,
+ win.vb + (cy + 1) * win.ch - cursorthickness,
+ win.cw, cursorthickness);
+ break;
+ case 5: /* Blinking bar */
+ case 6: /* Steady bar */
+ XftDrawRect(xw.draw, &drawcol,
+ win.hb + cx * win.cw,
+ win.vb + cy * win.ch,
+ cursorthickness, win.ch);
+ break;
+ }
+ } else {
+ XftDrawRect(xw.draw, &drawcol,
+ win.hb + cx * win.cw,
+ win.vb + cy * win.ch,
+ win.cw - 1, 1);
+ XftDrawRect(xw.draw, &drawcol,
+ win.hb + cx * win.cw,
+ win.vb + cy * win.ch,
+ 1, win.ch - 1);
+ XftDrawRect(xw.draw, &drawcol,
+ win.hb + (cx + 1) * win.cw - 1,
+ win.vb + cy * win.ch,
+ 1, win.ch - 1);
+ XftDrawRect(xw.draw, &drawcol,
+ win.hb + cx * win.cw,
+ win.vb + (cy + 1) * win.ch - 1,
+ win.cw, 1);
+ }
+}
+
+void
+xsetenv(void)
+{
+ char buf[sizeof(long) * 8 + 1];
+
+ snprintf(buf, sizeof(buf), "%lu", xw.win);
+ setenv("WINDOWID", buf, 1);
+}
+
+void
+xsettitle(char *p)
+{
+ XTextProperty prop;
+ DEFAULT(p, opt_title);
+
+ Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
+ &prop);
+ XSetWMName(xw.dpy, xw.win, &prop);
+ XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
+ XFree(prop.value);
+}
+
+int
+xstartdraw(void)
+{
+ return IS_SET(Wvisible);
+}
+
+void
+xdrawline(Letter *line, int x1, int y1, int x2)
+{
+ int i, x, ox, numspecs;
+ Letter base, new;
+ XftGlyphFontSpec *specs = xw.specbuf;
+
+ numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
+ i = ox = 0;
+ for (x = x1; x < x2 && i < numspecs; x++) {
+ new = line[x];
+ if (new.mode == Gwdummy)
+ continue;
+ if (selected(x, y1))
+ new.mode ^= Greverse;
+ if (i > 0 && GLYPHCMP(base, new)) {
+ xdrawglyphfontspecs(specs, base, i, ox, y1);
+ specs += i;
+ numspecs -= i;
+ i = 0;
+ }
+ if (i == 0) {
+ ox = x;
+ base = new;
+ }
+ i++;
+ }
+ if (i > 0)
+ xdrawglyphfontspecs(specs, base, i, ox, y1);
+}
+
+void
+xfinishdraw(void)
+{
+ XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w,
+ win.h, 0, 0);
+ XSetForeground(xw.dpy, dc.gc,
+ dc.col[IS_SET(Wreverse)?
+ defaultfg : defaultbg].pixel);
+}
+
+void
+xximspot(int x, int y)
+{
+ if (xw.ime.xic == nil)
+ return;
+
+ xw.ime.spot.x = borderpx + x * win.cw;
+ xw.ime.spot.y = borderpx + (y + 1) * win.ch;
+
+ XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, nil);
+}
+
+void
+expose(XEvent *ev)
+{
+ redraw();
+}
+
+void
+visibility(XEvent *ev)
+{
+ XVisibilityEvent *e = &ev->xvisibility;
+
+ MODBIT(win.mode, e->state != VisibilityFullyObscured, Wvisible);
+}
+
+void
+unmap(XEvent *ev)
+{
+ win.mode &= ~Wvisible;
+}
+
+void
+xsetpointermotion(int set)
+{
+ MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
+ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
+}
+
+void
+xsetmode(int set, uint flags)
+{
+ int mode = win.mode;
+ MODBIT(win.mode, set, flags);
+ if ((win.mode & Wreverse) != (mode & Wreverse))
+ redraw();
+}
+
+int
+xsetcursor(int cursor)
+{
+ if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
+ return 1;
+ win.cursor = cursor;
+ return 0;
+}
+
+void
+xseturgency(int add)
+{
+ XWMHints *h = XGetWMHints(xw.dpy, xw.win);
+
+ MODBIT(h->flags, add, XUrgencyHint);
+ XSetWMHints(xw.dpy, xw.win, h);
+ XFree(h);
+}
+
+void
+xbell(void)
+{
+ if (!(IS_SET(Wfocused)))
+ xseturgency(1);
+ if (bellvolume)
+ XkbBell(xw.dpy, xw.win, bellvolume, (Atom)nil);
+}
+
+void
+focus(XEvent *ev)
+{
+ XFocusChangeEvent *e = &ev->xfocus;
+
+ if (e->mode == NotifyGrab)
+ return;
+
+ if (ev->type == FocusIn) {
+ if (xw.ime.xic)
+ XSetICFocus(xw.ime.xic);
+ win.mode |= Wfocused;
+ xseturgency(0);
+ if (IS_SET(Wfocus))
+ ttywrite("\033[I", 3, 0);
+ } else {
+ if (xw.ime.xic)
+ XUnsetICFocus(xw.ime.xic);
+ win.mode &= ~Wfocused;
+ if (IS_SET(Wfocus))
+ ttywrite("\033[O", 3, 0);
+ }
+}
+
+int
+match(uint mask, uint state)
+{
+ return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
+}
+
+char*
+kmap(KeySym k, uint state)
+{
+ Key *kp;
+ int i;
+
+ /* Check for mapped keys out of X11 function keys. */
+ for (i = 0; i < arrlen(mappedkeys); i++) {
+ if (mappedkeys[i] == k)
+ break;
+ }
+ if (i == arrlen(mappedkeys)) {
+ if ((k & 0xFFFF) < 0xFD00)
+ return nil;
+ }
+
+ for (kp = key; kp < key + arrlen(key); kp++) {
+ if (kp->k != k)
+ continue;
+
+ if (!match(kp->mask, state))
+ continue;
+
+ if (IS_SET(Wappkeypad) ? kp->appkey < 0 : kp->appkey > 0)
+ continue;
+ if (IS_SET(Wnumlock) && kp->appkey == 2)
+ continue;
+
+ if (IS_SET(Wappcursor) ? kp->appcursor < 0 : kp->appcursor > 0)
+ continue;
+
+ return kp->s;
+ }
+
+ return nil;
+}
+
+void
+kpress(XEvent *ev)
+{
+ XKeyEvent *e = &ev->xkey;
+ KeySym ksym;
+ char buf[64], *customkey;
+ int len;
+ rune c;
+ Status status;
+ Shortcut *bp;
+
+ if (IS_SET(Wkbdblock))
+ return;
+
+ if (xw.ime.xic)
+ len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
+ else
+ len = XLookupString(e, buf, sizeof buf, &ksym, nil);
+ /* 1. shortcuts */
+ for (bp = shortcuts; bp < shortcuts + arrlen(shortcuts); bp++) {
+ if (ksym == bp->keysym && match(bp->mod, e->state)) {
+ bp->func(&(bp->arg));
+ return;
+ }
+ }
+
+ /* 2. custom keys from config.h */
+ if ((customkey = kmap(ksym, e->state))) {
+ ttywrite(customkey, strlen(customkey), 1);
+ return;
+ }
+
+ /* 3. composed string from input method */
+ if (len == 0)
+ return;
+ if (len == 1 && e->state & Mod1Mask) {
+ if (IS_SET(W8bit)) {
+ if (*buf < 0177) {
+ c = *buf | 0x80;
+ len = utf8·encode(&c, buf);
+ }
+ } else {
+ buf[1] = buf[0];
+ buf[0] = '\033';
+ len = 2;
+ }
+ }
+ ttywrite(buf, len, 1);
+}
+
+void
+cmessage(XEvent *e)
+{
+ /*
+ * See xembed specs
+ * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
+ */
+ if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
+ if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
+ win.mode |= Wfocused;
+ xseturgency(0);
+ } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
+ win.mode &= ~Wfocused;
+ }
+ } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
+ ttyhangup();
+ exit(0);
+ }
+}
+
+void
+resize(XEvent *e)
+{
+ if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
+ return;
+
+ cresize(e->xconfigure.width, e->xconfigure.height);
+}
+
+void
+run(void)
+{
+ XEvent ev;
+ int w = win.w, h = win.h;
+ fd_set rfd;
+ int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
+ struct timespec seltv, *tv, now, lastblink, trigger;
+ double timeout;
+
+ /* Waiting for window mapping */
+ do {
+ XNextEvent(xw.dpy, &ev);
+ /*
+ * This XFilterEvent call is required because of XOpenIM. It
+ * does filter out the key event and some client message for
+ * the input method too.
+ */
+ if (XFilterEvent(&ev, None))
+ continue;
+ if (ev.type == ConfigureNotify) {
+ w = ev.xconfigure.width;
+ h = ev.xconfigure.height;
+ }
+ } while (ev.type != MapNotify);
+
+ ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
+ cresize(w, h);
+
+ for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
+ FD_ZERO(&rfd);
+ FD_SET(ttyfd, &rfd);
+ FD_SET(xfd, &rfd);
+
+ if (XPending(xw.dpy))
+ timeout = 0; /* existing events might not set xfd */
+
+ seltv.tv_sec = timeout / 1E3;
+ seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
+ tv = timeout >= 0 ? &seltv : nil;
+
+ if (pselect(MAX(xfd, ttyfd)+1, &rfd, nil, nil, tv, nil) < 0) {
+ if (errno == EINTR)
+ continue;
+ fatal("select failed: %s\n", strerror(errno));
+ }
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ if (FD_ISSET(ttyfd, &rfd))
+ ttyread();
+
+ xev = 0;
+ while (XPending(xw.dpy)) {
+ xev = 1;
+ XNextEvent(xw.dpy, &ev);
+ if (XFilterEvent(&ev, None))
+ continue;
+ if (handler[ev.type])
+ (handler[ev.type])(&ev);
+ }
+
+ /*
+ * To reduce flicker and tearing, when new content or event
+ * triggers drawing, we first wait a bit to ensure we got
+ * everything, and if nothing new arrives - we draw.
+ * We start with trying to wait minlatency ms. If more content
+ * arrives sooner, we retry with shorter and shorter periods,
+ * and eventually draw even without idle after maxlatency ms.
+ * Typically this results in low latency while interacting,
+ * maximum latency intervals during `cat huge.txt`, and perfect
+ * sync with periodic updates from animations/key-repeats/etc.
+ */
+ if (FD_ISSET(ttyfd, &rfd) || xev) {
+ if (!drawing) {
+ trigger = now;
+ drawing = 1;
+ }
+ timeout = (maxlatency - TIMEDIFF(now, trigger)) \
+ / maxlatency * minlatency;
+ if (timeout > 0)
+ continue; /* we have time, try to find idle */
+ }
+
+ /* idle detected or maxlatency exhausted -> draw */
+ timeout = -1;
+ if (blinktimeout && tattrset(Gblink)) {
+ timeout = blinktimeout - TIMEDIFF(now, lastblink);
+ if (timeout <= 0) {
+ if (-timeout > blinktimeout) /* start visible */
+ win.mode |= Wblink;
+ win.mode ^= Wblink;
+ tsetdirtattr(Gblink);
+ lastblink = now;
+ timeout = blinktimeout;
+ }
+ }
+
+ draw();
+ XFlush(xw.dpy);
+ drawing = 0;
+ }
+}
+
+void
+usage(void)
+{
+ fatal("usage: %s [-aiv] [-c class] [-f font] [-A alpha] [-g geometry]"
+ " [-n name] [-o file]\n"
+ " [-T title] [-t title] [-w windowid]"
+ " [[-e] command [args ...]]\n"
+ " %s [-aiv] [-c class] [-f font] [-g geometry]"
+ " [-n name] [-o file]\n"
+ " [-T title] [-t title] [-w windowid] -l line"
+ " [stty_args ...]\n", argv0, argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ xw.l = xw.t = 0;
+ xw.isfixed = False;
+ xsetcursor(cursorshape);
+
+ ARGBEGIN {
+ case 'a':
+ allowaltscreen = 0;
+ break;
+ case 'A':
+ opt_alpha = EARGF(usage());
+ break;
+ case 'c':
+ opt_class = EARGF(usage());
+ break;
+ case 'e':
+ if (argc > 0)
+ --argc, ++argv;
+ goto run;
+ case 'f':
+ opt_font = EARGF(usage());
+ break;
+ case 'g':
+ xw.gm = XParseGeometry(EARGF(usage()),
+ &xw.l, &xw.t, &cols, &rows);
+ break;
+ case 'i':
+ xw.isfixed = 1;
+ break;
+ case 'o':
+ opt_io = EARGF(usage());
+ break;
+ case 'l':
+ opt_line = EARGF(usage());
+ break;
+ case 'n':
+ opt_name = EARGF(usage());
+ break;
+ case 't':
+ case 'T':
+ opt_title = EARGF(usage());
+ break;
+ case 'w':
+ opt_embed = EARGF(usage());
+ break;
+ case 'v':
+ fatal("%s " VERSION "\n", argv0);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+run:
+ if (argc > 0) /* eat all remaining arguments */
+ opt_cmd = argv;
+
+ if (!opt_title)
+ opt_title = (opt_line || !opt_cmd) ? "term" : opt_cmd[0];
+
+ setlocale(LC_CTYPE, "");
+ XSetLocaleModifiers("");
+
+ cols = MAX(cols, 1);
+ rows = MAX(rows, 1);
+
+ tnew(cols, rows);
+ xinit(cols, rows);
+ xsetenv();
+ selinit();
+
+ run();
+
+ return 0;
+}
diff --git a/src/cmd/walk/rules.mk b/src/cmd/walk/rules.mk
new file mode 100644
index 0000000..cb9768c
--- /dev/null
+++ b/src/cmd/walk/rules.mk
@@ -0,0 +1,15 @@
+include share/push.mk
+
+# local sources
+SRCS_$(d):=$(d)/walk.c
+
+# local outputs
+BINS_$(d):=$(d)/walk
+
+include share/paths.mk
+
+# Local rules
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/walk/walk.c b/src/cmd/walk/walk.c
new file mode 100644
index 0000000..c68d6e0
--- /dev/null
+++ b/src/cmd/walk/walk.c
@@ -0,0 +1,84 @@
+#include <u.h>
+#include <base.h>
+
+static char buf[4*1024], *c = buf; /* should be greater or equal to PATH_MAX */
+
+static
+void
+flush(void)
+{
+ *c = 0;
+ puts(buf);
+ c = buf;
+}
+
+static
+int
+print(void *data, char *rel, char *abs, io·Stat *info)
+{
+copy:
+ while (*abs && c < (arrend(buf)-2))
+ *c++ = *abs++;
+
+ if (*abs) {
+ flush();
+ goto copy;
+ }
+ *c++ = '\n';
+
+ return 0;
+}
+
+static
+void
+usage(void)
+{
+ fprintf(stderr, "usage: walk [-dlpv] file ...\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i, f = fs·nolinks, err, max = 0;
+ char *p;
+ static fs·Walker walker;
+
+ ARGBEGIN{
+ case 'd':
+ max = atoi(ARGF());
+ break;
+ case 'l':
+ f ^= fs·nolinks;
+ break;
+ case 'p':
+ f |= fs·preorder;
+ break;
+ case 'v':
+ f |= fs·verbose;
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ walker.flags = f;
+ walker.func = print;
+ walker.data = nil;
+ walker.max = max;
+
+ if (argc == 0) {
+ fs·init(&walker, "");
+ fs·walk(&walker);
+ return(err = walker.err);
+ } else {
+ err = 0;
+ for (i=0; i<argc; i++) {
+ fs·init(&walker, argv[i]);
+ fs·walk(&walker);
+ err += walker.err;
+ }
+ }
+ fs·fini(&walker);
+ flush();
+ exit(err);
+}
diff --git a/src/cmd/wm/arg.c b/src/cmd/wm/arg.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cmd/wm/arg.c
diff --git a/src/cmd/wm/client.c b/src/cmd/wm/client.c
new file mode 100644
index 0000000..5e0927a
--- /dev/null
+++ b/src/cmd/wm/client.c
@@ -0,0 +1,274 @@
+#include "wm.h"
+
+static char broken[] = "broken";
+
+// -----------------------------------------------------------------------
+// scripts
+
+static inline
+void
+grab_client(void)
+{
+ if(server.cursor.mode != CursorNormal)
+ return;
+ if(!(server.grab.client = client_at(server.cursor.dot->x, server.cursor.dot->y)))
+ return;
+
+ floating(server.grab.client, 1);
+}
+
+void
+move_client(Arg *arg)
+{
+ grab_client();
+ server.cursor.mode = CursorMove;
+
+ server.grab.x = server.cursor.dot->x - server.grab.client->geometry.x;
+ server.grab.y = server.cursor.dot->y - server.grab.client->geometry.y;
+ wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "fleur", server.cursor.dot);
+}
+
+void
+float_client(Arg *arg)
+{
+ Client *client = selected_client();
+ wlr_log(WLR_DEBUG, "client selected = %lx", (uintptr)client);
+ if(!client)
+ return;
+
+ floating(client, client->isfloating ? 0 : 1);
+}
+
+void
+resize_client(Arg *arg)
+{
+ double x, y;
+ struct wlr_box geometry;
+
+ grab_client();
+ server.cursor.mode = CursorResize;
+
+ wlr_xdg_surface_get_geometry(server.grab.client->xdg, &geometry);
+
+ x = server.grab.client->geometry.x + geometry.x + geometry.width;
+ y = server.grab.client->geometry.y + geometry.y + geometry.height;
+
+ server.grab.x = server.cursor.dot->x - x;
+ server.grab.y = server.cursor.dot->y - y;
+
+ server.grab.box = geometry;
+ server.grab.box.x += server.grab.client->geometry.x;
+ server.grab.box.y += server.grab.client->geometry.y;
+}
+
+// -----------------------------------------------------------------------
+// core
+
+static inline
+void
+activate(struct wlr_surface *surface, int state)
+{
+}
+
+void
+focus(Client *client, int lift)
+{
+ struct wlr_xdg_surface *xdg;
+ struct wlr_surface *old, *new;
+ struct wlr_keyboard *keyboard;
+
+ if(!client) {
+ wlr_seat_keyboard_notify_clear_focus(server.input.seat);
+ return;
+ }
+
+ old = server.input.seat->keyboard_state.focused_surface;
+
+ if(lift) {
+ wl_list_remove(&client->stack);
+ wl_list_insert(&server.client.stack, &client->stack);
+ }
+
+ new = client->xdg->surface;
+ if(old==new)
+ return;
+
+ wl_list_remove(&client->focus);
+ wl_list_insert(&server.client.focus, &client->focus);
+ server.monitor.selected = client->monitor;
+ client->isurgent = 0;
+
+ if(old) {
+ if(wlr_surface_is_xdg_surface(old)) {
+ xdg = wlr_xdg_surface_from_wlr_surface(old);
+ wlr_xdg_toplevel_set_activated(xdg, false);
+ }
+ }
+
+ keyboard = wlr_seat_get_keyboard(server.input.seat);
+
+ wlr_seat_keyboard_notify_enter(server.input.seat, new,
+ keyboard->keycodes,
+ keyboard->num_keycodes,
+ &keyboard->modifiers
+ );
+
+ wlr_xdg_toplevel_set_activated(client->xdg, true);
+}
+
+Client*
+client_at(double x, double y)
+{
+ Client *client;
+ wl_list_for_each(client, &server.client.stack, stack)
+ if(VISIBLE_ON(client, client->monitor) && wlr_box_contains_point(&client->geometry, x, y))
+ return client;
+ return nil;
+}
+
+static
+int
+has(Client *client, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy)
+{
+ double x, y, vsx = lx - client->geometry.x, vsy = ly - client->geometry.y;
+ struct wlr_surface *find = nil;
+
+ find = wlr_xdg_surface_surface_at(client->xdg, vsx, vsy, &x, &y);
+ if(find) {
+ *sx = x;
+ *sy = y;
+ *surface = find;
+ return true;
+ }
+
+ return false;
+}
+
+struct wlr_surface *
+client_surface_at(Client *client, double cx, double cy, double *sx, double *sy)
+{
+ return wlr_xdg_surface_surface_at(client->xdg, cx, cy, sx, sy);
+}
+
+
+static
+void
+constrain(Client *client, struct wlr_box *box)
+{
+ client->geometry.width = MAX(1, client->geometry.width);
+ client->geometry.height = MAX(1, client->geometry.height);
+
+ if(client->geometry.x >= box->x + box->width)
+ client->geometry.x = box->x + box->width - client->geometry.width;
+ if(client->geometry.y >= box->y + box->height)
+ client->geometry.y = box->y + box->height - client->geometry.height;
+ if(client->geometry.x + client->geometry.width + 2*client->border <= box->x)
+ client->geometry.x = box->x;
+ if(client->geometry.y + client->geometry.height + 2*client->border <= box->y)
+ client->geometry.y = box->y;
+}
+
+void
+resize(Client *client, int x, int y, int w, int h, int interact)
+{
+ struct wlr_box *box = interact ? &server.monitor.geometry : &client->monitor->window;
+
+ client->geometry.x = x;
+ client->geometry.y = y;
+ client->geometry.width = w;
+ client->geometry.height = h;
+
+ constrain(client, box);
+
+ client->resize = wlr_xdg_toplevel_set_size(client->xdg,
+ client->geometry.width - 2*client->border,
+ client->geometry.height - 2*client->border
+ );
+}
+
+void
+attach(Client *client, Monitor *monitor, uint tags)
+{
+ Monitor *old = client->monitor;
+ if(old == monitor)
+ return;
+
+ client->monitor = monitor;
+
+ if(old) {
+ wlr_surface_send_leave(client->xdg->surface, old->output);
+ arrange(old);
+ }
+
+ if(monitor) {
+ /* make sure window actually overlaps with the monitor */
+ constrain(client, &monitor->geometry);
+ wlr_surface_send_enter(client->xdg->surface, monitor->output);
+ client->tags = tags ? tags : monitor->tag.set[monitor->tag.selected];
+ arrange(monitor);
+ }
+
+ focus(focused_client(server.monitor.selected), 1);
+}
+
+void
+rules(Client *client)
+{
+ /* rule matching */
+ Rule *rule;
+ uint i, tags;
+ char *id, *title;
+ Monitor *monitor, *it;
+
+ monitor = server.monitor.selected;
+
+ if (!(id=client->xdg->toplevel->app_id))
+ id = broken;
+ if (!(title=client->xdg->toplevel->title))
+ title = broken;
+
+ for(tags=0, rule=cfg·rule; rule != cfg·endrule; ++rule) {
+ if ((!rule->title || strstr(title, rule->title))
+ && (!rule->id || strstr(id, rule->id))) {
+ client->isfloating = rule->isfloating;
+ tags |= rule->tags;
+ i = 0;
+ wl_list_for_each(it, &server.monitor.list, link)
+ if(rule->monitor == i++)
+ monitor = it;
+ }
+ }
+
+ attach(client, monitor, tags);
+}
+
+void
+floating(Client *client, int state)
+{
+ wlr_log(WLR_DEBUG, "client %lx, floating = %d", (uintptr)client, state);
+ client->isfloating = state;
+ arrange(client->monitor);
+}
+
+Client *
+selected_client(void)
+{
+ Client *client = wl_container_of(server.client.focus.next, client, focus);
+ if(wl_list_empty(&server.client.focus) || !VISIBLE_ON(client, server.monitor.selected))
+ return nil;
+ return client;
+}
+
+void
+request_activate(struct wl_listener *l, void *data)
+{
+ struct wlr_xdg_activation_v1_request_activate_event *event = data;
+ Client *client;
+
+ if (!wlr_surface_is_xdg_surface(event->surface))
+ return;
+
+ client = wlr_xdg_surface_from_wlr_surface(event->surface)->data;
+ if(client != selected_client())
+ client->isurgent = 1;
+}
diff --git a/src/cmd/wm/config.h b/src/cmd/wm/config.h
new file mode 100644
index 0000000..1f5ba85
--- /dev/null
+++ b/src/cmd/wm/config.h
@@ -0,0 +1,70 @@
+/* appearance */
+CONFIG(int, sloppyfocus, 1);
+CONFIG(int, borderpixel, 1);
+CONFIG(float, rootcolor[], {0.3, 0.3, 0.3, 1.0});
+CONFIG(float, bordercolor[], {0.5, 0.5, 0.5, 1.0});
+CONFIG(float, focuscolor[], {1.0, 0.0, 0.0, 1.0});
+
+/* sampling */
+CONFIG(int, repeat_rate, 25);
+CONFIG(int, repeat_delay, 600);
+
+/* tags */
+CONFIG(char*, tags[], { "1", "2", "3", "4", "5", "6", "7", "8", "9" });
+
+/* application specific rules */
+CONFIG(Rule, rule[], {
+ /* app_id title tags mask isfloating monitor */
+ /* examples:
+ { "Gimp", nil, 0, 1, -1 },
+ { "firefox", nil, 1 << 8, 0, -1 },
+ */
+});
+CONFIG(Rule*, endrule, arrend(cfg·rule));
+
+/* commands */
+CONFIG(char*, termcommand[], { "alacritty", nil });
+CONFIG(char*, menucommand[], { "dmenu-wl_run", nil });
+
+/* layouts */
+CONFIG(Layout, layouts[], {
+ /* symbol arrange */
+ { "[]=", tile },
+ { "><>", nil }, /* no layout function means floating behavior */
+});
+CONFIG(Layout*, endlayout, arrend(cfg·layouts));
+
+/* monitors
+ * The order in which monitors are defined determines their position.
+ * non-configured monitors are always added to the left. */
+CONFIG(MonitorRule, monitorrule[], {
+ /* name layout, x, y, scale, transform master */
+ { nil, &cfg·layouts[0], 0, 0, 1, WL_OUTPUT_TRANSFORM_NORMAL, {0.55, 1} },
+});
+CONFIG(MonitorRule*, endmonitorrule, arrend(cfg·monitorrule));
+
+/* keybindings */
+#define MODKEY WLR_MODIFIER_ALT
+#define MOD(a) WLR_MODIFIER_##a
+#define KEY(a) XKB_KEY_##a
+
+CONFIG(Key, binding[], {
+ /* modifier key function argument */
+ { MODKEY, KEY(Return), spawn, {.v = cfg·termcommand} },
+ { MODKEY, KEY(d), spawn, {.v = cfg·menucommand} },
+ { MODKEY|MOD(SHIFT), KEY(Q), quit, {.v = nil} },
+});
+CONFIG(Key*, endbinding, arrend(cfg·binding));
+
+#undef MOD
+#undef KEY
+
+/* mouse buttons */
+CONFIG(Button, button[], {
+ { MODKEY, BTN_LEFT, move_client, {0} },
+ { MODKEY, BTN_MIDDLE, float_client, {0} },
+ { MODKEY, BTN_RIGHT, resize_client, {0} },
+});
+CONFIG(Button*, endbutton, arrend(cfg·button));
+
+#undef MODKEY
diff --git a/src/cmd/wm/input.c b/src/cmd/wm/input.c
new file mode 100644
index 0000000..4c6bfd4
--- /dev/null
+++ b/src/cmd/wm/input.c
@@ -0,0 +1,316 @@
+#include "wm.h"
+
+// -----------------------------------------------------------------------
+// keyboard
+
+static
+void
+keymodifier(struct wl_listener *l, void *data)
+{
+ Keyboard *keyboard = wl_container_of(l, keyboard, event.modify);
+
+ wlr_seat_set_keyboard(server.input.seat, keyboard->device);
+ wlr_seat_keyboard_notify_modifiers(server.input.seat, &keyboard->device->keyboard->modifiers);
+}
+
+static
+int
+keybinding(uint32 modifier, xkb_keysym_t sym)
+{
+ Key *key;
+
+ for(key=cfg·binding; key!=cfg·endbinding; ++key) {
+ if(modifier == key->modifier && sym == key->sym && key->action){
+ key->action(&key->arg);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static
+void
+keypress(struct wl_listener *l, void *data)
+{
+ int i,h,n;
+ uint32 keycode, modifier;
+ const xkb_keysym_t *syms;
+ struct Keyboard *keyboard = wl_container_of(l, keyboard, event.press);
+ struct wlr_event_keyboard_key *event = data;
+
+ keycode = event->keycode + 8;
+
+ h = 0;
+ n = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, keycode, &syms);
+
+ modifier = wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+ if(event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ for(i=0; i<n; i++)
+ h=keybinding(modifier, syms[i]);
+ }
+
+ if(!h) {
+ wlr_seat_set_keyboard(server.input.seat, keyboard->device);
+ wlr_seat_keyboard_notify_key(server.input.seat, event->time_msec, event->keycode, event->state);
+ }
+}
+
+static
+void
+free_keyboard(struct wl_listener *l, void *data)
+{
+ struct wlr_input_device *device = data;
+ Keyboard *keyboard = device->data;
+
+ /* XXX: debug
+ wl_list_remove(&keyboard->link);
+ wl_list_remove(&keyboard->event.modify.link);
+ wl_list_remove(&keyboard->event.press.link);
+ wl_list_remove(&keyboard->event.destroy.link);
+
+ free(keyboard);
+ */
+}
+
+static
+void
+make_keyboard(struct wlr_input_device *device)
+{
+ Keyboard *keyboard;
+ struct xkb_context *context;
+ struct xkb_keymap *keymap;
+
+ keyboard = device->data = calloc(1, sizeof(*keyboard));
+ keyboard->device = device;
+
+ context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ keymap = xkb_keymap_new_from_names(context, nil, XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+
+ wlr_keyboard_set_repeat_info(device->keyboard, cfg·repeat_rate, cfg·repeat_delay);
+
+ keyboard->event.modify.notify = keymodifier;
+ wl_signal_add(&device->keyboard->events.modifiers, &keyboard->event.modify);
+
+ keyboard->event.press.notify = keypress;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->event.press);
+
+ keyboard->event.destroy.notify = free_keyboard;
+ wl_signal_add(&device->keyboard->events.destroy, &keyboard->event.destroy);
+
+ wlr_seat_set_keyboard(server.input.seat, device);
+
+ wl_list_insert(&server.input.keyboards, &keyboard->link);
+}
+
+// -----------------------------------------------------------------------
+// cursor
+
+static
+void
+focus_surface(Client *client, struct wlr_surface *surface, double sx, double sy, uint32 time)
+{
+ struct timespec now;
+ int lift = time;
+
+ if(client && !surface)
+ surface = client->xdg->surface;
+
+ if(!surface){
+ wlr_seat_pointer_notify_clear_focus(server.input.seat);
+ return;
+ }
+
+ if(!time) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ time = now.tv_sec * 1000 + now.tv_nsec / 1000000;
+ }
+
+ if(surface == server.input.seat->pointer_state.focused_surface) {
+ wlr_seat_pointer_notify_motion(server.input.seat, time, sx, sy);
+ return;
+ }
+
+ wlr_seat_pointer_notify_enter(server.input.seat, surface, sx, sy);
+
+ if(cfg·sloppyfocus && lift)
+ focus(client, 0);
+}
+
+void
+notify_move(uint32 time)
+{
+ double sx, sy;
+ Client *client;
+ struct wlr_box box;
+ struct wlr_surface *surface;
+
+ if(time) {
+ wlr_idle_notify_activity(server.input.idle, server.input.seat);
+ if(cfg·sloppyfocus)
+ server.monitor.selected = monitor_at(server.cursor.dot->x, server.cursor.dot->y);
+ }
+
+ if(server.cursor.mode == CursorMove) {
+ resize(server.grab.client,
+ server.cursor.dot->x - server.grab.x,
+ server.cursor.dot->y - server.grab.y,
+ server.grab.client->geometry.width,
+ server.grab.client->geometry.height,
+ 1
+ );
+ return;
+ }
+
+ if(server.cursor.mode == CursorResize) {
+ wlr_xdg_surface_get_geometry(server.grab.client->xdg, &box);
+ resize(server.grab.client,
+ server.grab.box.x - box.x,
+ server.grab.box.y - box.y,
+ server.cursor.dot->x - server.grab.x - server.grab.box.x,
+ server.cursor.dot->y - server.grab.y - server.grab.box.y,
+ 1
+ );
+ return;
+ }
+
+ /* otherwise, find the client under the pointer and send the event along. */
+ client = client_at(server.cursor.dot->x, server.cursor.dot->y);
+ if(!client) {
+ wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "left_ptr", server.cursor.dot);
+ return;
+ }
+
+ surface = client_surface_at(
+ client,
+ server.cursor.dot->x - client->geometry.x - client->border,
+ server.cursor.dot->y - client->geometry.y - client->border,
+ &sx, &sy
+ );
+
+ focus_surface(client, surface, sx, sy, time);
+}
+
+void
+cursor_move(struct wl_listener *l, void *data)
+{
+ struct wlr_event_pointer_motion *event = data;
+ wlr_cursor_move(server.cursor.dot, event->device, event->delta_x, event->delta_y);
+ notify_move(event->time_msec);
+}
+
+void
+cursor_move_abs(struct wl_listener *l, void *data)
+{
+ struct wlr_event_pointer_motion_absolute *event = data;
+ wlr_cursor_warp_absolute(server.cursor.dot, event->device, event->x, event->y);
+ notify_move(event->time_msec);
+}
+
+void
+cursor_button(struct wl_listener *l, void *data)
+{
+ Client *client;
+ uint32 modifier;
+ Button *button;
+ struct wlr_keyboard *keyboard;
+ struct wlr_event_pointer_button *event = data;
+
+ wlr_idle_notify_activity(server.input.idle, server.input.seat);
+
+ switch(event->state) {
+ case WLR_BUTTON_PRESSED:
+ if((client=client_at(server.cursor.dot->x, server.cursor.dot->y)))
+ focus(client,1);
+
+ keyboard = wlr_seat_get_keyboard(server.input.seat);
+ modifier = wlr_keyboard_get_modifiers(keyboard);
+ for(button=cfg·button; button != cfg·endbutton; ++button) {
+ if(modifier == button->modifier && event->button == button->code && button->function) {
+ button->function(&button->arg);
+ return;
+ }
+ }
+ break;
+ case WLR_BUTTON_RELEASED:
+ if(server.cursor.mode != CursorNormal) {
+ wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "left_ptr", server.cursor.dot);
+ server.cursor.mode = CursorNormal;
+ /* Drop the window off on its new monitor */
+ server.monitor.selected = monitor_at(server.cursor.dot->x, server.cursor.dot->y);
+ attach(server.grab.client, server.monitor.selected, 0);
+ return;
+ }
+ }
+
+ wlr_seat_pointer_notify_button(server.input.seat, event->time_msec, event->button, event->state);
+}
+
+void
+cursor_axis(struct wl_listener *l, void *data)
+{
+ struct wlr_event_pointer_axis *event = data;
+ /* Notify the client with pointer focus of the axis event. */
+ wlr_seat_pointer_notify_axis(server.input.seat,
+ event->time_msec, event->orientation, event->delta,
+ event->delta_discrete, event->source);
+}
+
+void
+cursor_frame(struct wl_listener *l, void *data)
+{
+ wlr_seat_pointer_notify_frame(server.input.seat);
+}
+
+void
+request_cursor(struct wl_listener *l, void *data)
+{
+ struct wlr_seat_pointer_request_set_cursor_event *event = data;
+ struct wlr_seat_client *focused = server.input.seat->pointer_state.focused_client;
+ if(focused == event->seat_client)
+ wlr_cursor_set_surface(server.cursor.dot, event->surface, event->hotspot_x, event->hotspot_y);
+}
+
+void
+request_set_selection(struct wl_listener *l, void *data)
+{
+ struct wlr_seat_request_set_selection_event *event = data;
+ wlr_seat_set_selection(server.input.seat, event->source, event->serial);
+}
+
+static
+void
+make_pointer(struct wlr_input_device *device)
+{
+ wlr_cursor_attach_input_device(server.cursor.dot, device);
+}
+
+// -----------------------------------------------------------------------
+// generic input
+
+void
+make_input(struct wl_listener *l, void *data)
+{
+ uint32 capability;
+ struct wlr_input_device *device = data;
+
+ switch(device->type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ make_keyboard(device);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:
+ make_pointer(device);
+ /* fallthrough */
+ default:
+ break;
+ }
+
+ capability = WL_SEAT_CAPABILITY_POINTER;
+ if(!wl_list_empty(&server.input.keyboards))
+ capability |= WL_SEAT_CAPABILITY_KEYBOARD;
+ wlr_seat_set_capabilities(server.input.seat, capability);
+}
diff --git a/src/cmd/wm/layer.c b/src/cmd/wm/layer.c
new file mode 100644
index 0000000..bfac744
--- /dev/null
+++ b/src/cmd/wm/layer.c
@@ -0,0 +1,107 @@
+#include "wm.h"
+
+static
+void
+map(struct wl_listener *l, void *data)
+{
+ Layer *layer = wl_container_of(l, layer, event.map);
+ wlr_surface_send_enter(layer->surface->surface, layer->surface->output);
+ notify_move(0);
+}
+
+static
+void
+finalize(Layer *layer)
+{
+ layer->surface->mapped = 0;
+ if (layer->surface->surface == server.input.seat->keyboard_state.focused_surface)
+ focus(selected_client(), 1);
+ notify_move(0);
+}
+
+static
+void
+unmap(struct wl_listener *l, void *data)
+{
+ Layer *layer = wl_container_of(l, layer, event.unmap);
+ finalize(layer);
+}
+
+static
+void
+destroy(struct wl_listener *l, void *data)
+{
+ Monitor *monitor;
+ Layer *layer = wl_container_of(l, layer, event.destroy);
+
+ if (layer->surface->mapped)
+ finalize(layer);
+
+ wl_list_remove(&layer->link);
+ wl_list_remove(&layer->event.destroy.link);
+ wl_list_remove(&layer->event.map.link);
+ wl_list_remove(&layer->event.unmap.link);
+ wl_list_remove(&layer->event.commit.link);
+
+ if(layer->surface->output) {
+ monitor = layer->surface->output->data;
+ if(monitor)
+ stratify(monitor);
+ layer->surface->output = nil;
+ }
+ free(layer);
+}
+
+static
+void
+commit(struct wl_listener *l, void *data)
+{
+ Monitor *monitor;
+ Layer *layer = wl_container_of(l, layer, event.commit);
+ struct wlr_layer_surface_v1 *surface = layer->surface;
+ struct wlr_output *output = surface->output;
+
+ if(!output)
+ return;
+
+ monitor = output->data;
+ stratify(monitor);
+
+ if (layer->type != surface->current.layer) {
+ wl_list_remove(&layer->link);
+ wl_list_insert(&monitor->layer[surface->current.layer], &layer->link);
+ layer->type = surface->current.layer;
+ }
+}
+
+void
+make_layer_surface(struct wl_listener *l, void *data)
+{
+ Layer *layer;
+ Monitor *monitor;
+ struct wlr_layer_surface_v1_state state;
+ struct wlr_layer_surface_v1 *surface = data;
+
+ if(!surface->output)
+ surface->output = server.monitor.selected->output;
+
+ layer = surface->data = calloc(1, sizeof(*layer));
+ layer->surface = surface;
+
+ layer->event.map.notify = map;
+ wl_signal_add(&surface->events.map, &layer->event.map);
+ layer->event.unmap.notify = unmap;
+ wl_signal_add(&surface->events.unmap, &layer->event.unmap);
+ layer->event.destroy.notify = destroy;
+ wl_signal_add(&surface->events.destroy, &layer->event.destroy);
+ layer->event.commit.notify = commit;
+ wl_signal_add(&surface->surface->events.commit, &layer->event.commit);
+
+ monitor = surface->output->data;
+ wl_list_insert(&monitor->layer[surface->client_pending.layer], &layer->link);
+
+ state = surface->current;
+ surface->current = surface->client_pending;
+ stratify(monitor);
+ surface->current = state;
+}
diff --git a/src/cmd/wm/main.c b/src/cmd/wm/main.c
new file mode 100644
index 0000000..2607801
--- /dev/null
+++ b/src/cmd/wm/main.c
@@ -0,0 +1,177 @@
+#include "wm.h"
+
+Server server = {
+ .event = {
+ .make_input = { .notify = make_input },
+ .make_monitor = { .notify = make_monitor },
+ .make_xdg_surface = { .notify = make_xdg_surface },
+ .make_layer_surface = { .notify = make_layer_surface },
+
+ .monitor_change = { .notify = monitor_change },
+ .monitor_test = { .notify = monitor_test },
+ .monitor_apply = { .notify = monitor_apply },
+
+ .cursor_move = { .notify = cursor_move },
+ .cursor_move_abs = { .notify = cursor_move_abs },
+ .cursor_button = { .notify = cursor_button },
+ .cursor_axis = { .notify = cursor_axis },
+ .cursor_frame = { .notify = cursor_frame },
+
+ .request_activate = { .notify = request_activate },
+ .request_cursor = { .notify = request_cursor },
+ .request_set_selection = { .notify = request_set_selection },
+ },
+};
+
+// -----------------------------------------------------------------------
+// helper functions
+
+static inline
+void
+init(void)
+{
+ /* compositor initialization */
+ server.display = wl_display_create();
+ server.backend = wlr_backend_autocreate(server.display);
+ server.renderer = wlr_backend_get_renderer(server.backend);
+ server.present = wlr_presentation_create(server.display, server.backend);
+
+ wlr_renderer_init_wl_display(server.renderer, server.display);
+
+ wlr_compositor_create(server.display, server.renderer);
+ wlr_export_dmabuf_manager_v1_create(server.display);
+ wlr_screencopy_manager_v1_create(server.display);
+ wlr_data_control_manager_v1_create(server.display);
+ wlr_data_device_manager_create(server.display);
+ wlr_gamma_control_manager_v1_create(server.display);
+ wlr_primary_selection_v1_device_manager_create(server.display);
+ wlr_viewporter_create(server.display);
+
+ server.activate = wlr_xdg_activation_v1_create(server.display);
+ wl_signal_add(&server.activate->events.request_activate, &server.event.request_activate);
+
+ wlr_data_device_manager_create(server.display);
+
+ server.monitor.layout = wlr_output_layout_create();
+ wl_signal_add(&server.monitor.layout->events.change, &server.event.monitor_change);
+ wlr_xdg_output_manager_v1_create(server.display, server.monitor.layout);
+
+ wl_list_init(&server.monitor.list);
+ wl_signal_add(&server.backend->events.new_output, &server.event.make_monitor);
+
+ server.monitor.manager = wlr_output_manager_v1_create(server.display);
+ wl_signal_add(&server.monitor.manager->events.test, &server.event.monitor_test);
+ wl_signal_add(&server.monitor.manager->events.apply, &server.event.monitor_apply);
+
+ /* shell initialization */
+ wl_list_init(&server.client.list);
+ wl_list_init(&server.client.stack);
+ wl_list_init(&server.client.focus);
+
+ server.shell.xdg = wlr_xdg_shell_create(server.display);
+ wl_signal_add(&server.shell.xdg->events.new_surface, &server.event.make_xdg_surface);
+
+ server.shell.layer = wlr_layer_shell_v1_create(server.display);
+ wl_signal_add(&server.shell.layer->events.new_surface, &server.event.make_layer_surface);
+
+ wlr_server_decoration_manager_set_default_mode(
+ wlr_server_decoration_manager_create(server.display),
+ WLR_SERVER_DECORATION_MANAGER_MODE_SERVER
+ );
+ wlr_xdg_decoration_manager_v1_create(server.display);
+
+ /* input initialization */
+ server.cursor.dot = wlr_cursor_create();
+ wlr_cursor_attach_output_layout(server.cursor.dot, server.monitor.layout);
+
+ server.cursor.manager = wlr_xcursor_manager_create(nil, 24);
+ wlr_xcursor_manager_load(server.cursor.manager, 1);
+
+ wl_signal_add(&server.cursor.dot->events.motion, &server.event.cursor_move);
+ wl_signal_add(&server.cursor.dot->events.motion_absolute, &server.event.cursor_move_abs);
+ wl_signal_add(&server.cursor.dot->events.button, &server.event.cursor_button);
+ wl_signal_add(&server.cursor.dot->events.axis, &server.event.cursor_axis);
+ wl_signal_add(&server.cursor.dot->events.frame, &server.event.cursor_frame);
+
+ wl_list_init(&server.input.keyboards);
+ wl_signal_add(&server.backend->events.new_input, &server.event.make_input);
+
+ server.input.idle = wlr_idle_create(server.display);
+ server.input.seat = wlr_seat_create(server.display, "seat0");
+
+ wl_signal_add(&server.input.seat->events.request_set_cursor, &server.event.request_cursor);
+ wl_signal_add(&server.input.seat->events.request_set_selection, &server.event.request_set_selection);
+}
+
+static inline
+void
+fini(void)
+{
+ wl_display_destroy_clients(server.display);
+
+ wlr_backend_destroy(server.backend);
+ wlr_xcursor_manager_destroy(server.cursor.manager);
+ wlr_output_layout_destroy(server.monitor.layout);
+ wlr_seat_destroy(server.input.seat);
+
+ wl_display_destroy(server.display);
+}
+
+// -----------------------------------------------------------------------
+// main point of entry
+
+int
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-s startup command]\n", argv0);
+ return 1;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ char *socket, *cmd=nil;
+
+ ARGBEGIN{
+ case 's':
+ cmd = ARGF();
+ break;
+ default:
+ return usage();
+ } ARGEND
+
+ if(argc != 0)
+ return usage();
+
+ wlr_log_init(WLR_DEBUG, nil);
+
+ init();
+
+ if(!(socket=(char*)wl_display_add_socket_auto(server.display))) {
+ wlr_backend_destroy(server.backend);
+ return 1;
+ }
+
+ if(!(wlr_backend_start(server.backend))) {
+ wlr_backend_destroy(server.backend);
+ wl_display_destroy(server.display);
+ return 1;
+ }
+
+ setenv("WAYLAND_DISPLAY", socket, true);
+ if(cmd) {
+ if(fork()==0)
+ execl("/bin/sh", "/bin/sh", "-c", cmd, nil);
+ }
+ wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket);
+
+ server.monitor.selected = monitor_at(server.cursor.dot->x, server.cursor.dot->y);
+ wlr_cursor_warp_closest(server.cursor.dot, nil, server.cursor.dot->x, server.cursor.dot->y);
+ wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "left_ptr", server.cursor.dot);
+
+ wl_display_run(server.display); /* event loop */
+
+ fini();
+ return 0;
+}
diff --git a/src/cmd/wm/monitor.c b/src/cmd/wm/monitor.c
new file mode 100644
index 0000000..93073f3
--- /dev/null
+++ b/src/cmd/wm/monitor.c
@@ -0,0 +1,386 @@
+#include "wm.h"
+
+/* callbacks */
+void
+monitor_change(struct wl_listener *l, void *data)
+{
+ Monitor *monitor;
+ struct wlr_output_configuration_v1 *config;
+
+ config = wlr_output_configuration_v1_create();
+ server.monitor.geometry = *wlr_output_layout_get_box(server.monitor.layout, nil);
+
+ wl_list_for_each(monitor, &server.monitor.list, link) {
+ struct wlr_output_configuration_head_v1 *head =
+ wlr_output_configuration_head_v1_create(config, monitor->output);
+
+ monitor->geometry = monitor->window = *wlr_output_layout_get_box(server.monitor.layout, monitor->output);
+
+ stratify(monitor);
+ arrange(monitor);
+
+ head->state.enabled = monitor->output->enabled;
+ head->state.mode = monitor->output->current_mode;
+ head->state.x = monitor->geometry.x;
+ head->state.y = monitor->geometry.y;
+ }
+
+ wlr_output_manager_v1_set_configuration(server.monitor.manager, config);
+}
+
+static
+void
+trylayout(struct wlr_output_configuration_v1 *config, int force)
+{
+ int ok;
+ struct wlr_output_configuration_head_v1 *head;
+
+ ok = 1;
+ wl_list_for_each(head, &config->heads, link) {
+ struct wlr_output *output= head->state.output;
+ wlr_output_enable(output, head->state.enabled);
+ if (head->state.enabled) {
+ if (head->state.mode)
+ wlr_output_set_mode(output, head->state.mode);
+ else
+ wlr_output_set_custom_mode(
+ output,
+ head->state.custom_mode.width,
+ head->state.custom_mode.height,
+ head->state.custom_mode.refresh
+ );
+
+ wlr_output_layout_move(server.monitor.layout, output,
+ head->state.x, head->state.y);
+ wlr_output_set_transform(output, head->state.transform);
+ }
+
+ if(!(ok=wlr_output_test(output)))
+ break;
+ }
+
+ wl_list_for_each(head, &config->heads, link) {
+ if(ok && force)
+ wlr_output_commit(head->state.output);
+ else
+ wlr_output_rollback(head->state.output);
+ }
+
+ if(ok)
+ wlr_output_configuration_v1_send_succeeded(config);
+ else
+ wlr_output_configuration_v1_send_failed(config);
+
+ wlr_output_configuration_v1_destroy(config);
+}
+
+void
+monitor_apply(struct wl_listener *l, void *data)
+{
+ struct wlr_output_configuration_v1 *config = data;
+ trylayout(config, 1);
+}
+
+void
+monitor_test(struct wl_listener *l, void *data)
+{
+ struct wlr_output_configuration_v1 *config = data;
+ trylayout(config, 0);
+}
+
+void
+make_monitor(struct wl_listener *l, void *data)
+{
+ int i;
+ Client *client;
+ Monitor *monitor;
+ MonitorRule *rule;
+ struct wlr_output_mode *mode;
+ struct wlr_output *output = data;
+
+ /*
+ * XXX: needed?
+ if (wl_list_empty(&output->modes))
+ return;
+ */
+
+ monitor = output->data = calloc(1, sizeof(*monitor));
+ monitor->output = output;
+
+ for(i=0; i < arrlen(monitor->layer); i++)
+ wl_list_init(&monitor->layer[i]);
+ monitor->tag.set[0] = monitor->tag.set[1] = 1;
+
+ for(rule=cfg·monitorrule; rule != cfg·endmonitorrule; ++rule) {
+ if(!rule->name || strstr(output->name, rule->name)) {
+ monitor->master.len = rule->master.len;
+ monitor->master.frac = rule->master.frac;
+
+ wlr_output_set_scale(output, rule->scale);
+ wlr_xcursor_manager_load(server.cursor.manager, rule->scale);
+ monitor->layouts[0] = monitor->layouts[1] = monitor->layout = rule->layout;
+
+ wlr_output_set_transform(output, rule->transform);
+ break;
+ }
+ }
+
+ mode = wlr_output_preferred_mode(output);
+ wlr_output_set_mode(output, mode);
+ wlr_output_enable_adaptive_sync(output, true);
+
+ monitor->event.render.notify = render_monitor;
+ wl_signal_add(&output->events.frame, &monitor->event.render);
+ monitor->event.destroy.notify = free_monitor;
+ wl_signal_add(&output->events.destroy, &monitor->event.destroy);
+
+ wlr_output_enable(output, true);
+ if(!wlr_output_commit(output))
+ return;
+
+ wl_list_insert(&server.monitor.list, &monitor->link);
+
+ wlr_output_layout_add(server.monitor.layout, output, rule->x, rule->y);
+ server.monitor.geometry = *wlr_output_layout_get_box(server.monitor.layout, nil);
+
+ /* update the geometries of all monitors */
+ wl_list_for_each(monitor, &server.monitor.list, link) {
+ /* first monitor in the list = most recently added */
+ wl_list_for_each(client, &server.client.list, link) {
+ if(client->isfloating)
+ resize(client, client->geometry.x+monitor->window.width, client->geometry.y,
+ client->geometry.width, client->geometry.height, 0);
+ }
+ return;
+ }
+}
+
+void
+free_monitor(struct wl_listener *l, void *data)
+{
+ int i, len;
+ Client *client;
+ struct wlr_output *output = data;
+ Monitor *monitor = output->data;
+
+ wl_list_remove(&monitor->event.destroy.link);
+ wl_list_remove(&monitor->event.render.link);
+ wl_list_remove(&monitor->link);
+
+ wlr_output_layout_remove(server.monitor.layout, monitor->output);
+
+ for(i=0, len=wl_list_length(&server.monitor.list); i < len; i++) {
+ server.monitor.selected = wl_container_of(server.monitor.list.prev, server.monitor.selected, link);
+ if(server.monitor.selected->output->enabled)
+ break;
+ }
+
+ focus(focused_client(server.monitor.selected), 1);
+
+ /* move closed monitor's clients to newly selected one */
+ wl_list_for_each(client, &server.client.list, link) {
+ if(client->isfloating && client->geometry.x > monitor->geometry.width)
+ resize(client,
+ client->geometry.x - monitor->window.width,
+ client->geometry.y,
+ client->geometry.width,
+ client->geometry.height,
+ 0
+ );
+ if(client->monitor == monitor)
+ attach(client, monitor, client->tags);
+ }
+
+ free(monitor);
+}
+
+/* methods */
+void
+arrange(Monitor *monitor)
+{
+ if(monitor->layout->arrange)
+ monitor->layout->arrange(monitor);
+}
+
+void
+stratum(Monitor *monitor, struct wl_list *list, struct wlr_box *area, int exclusive)
+{
+ Layer *layer;
+ struct wlr_box full = monitor->geometry;
+
+ wl_list_for_each(layer, list, link) {
+ struct wlr_layer_surface_v1 *surface = layer->surface;
+ struct wlr_layer_surface_v1_state *state = &surface->current;
+ struct wlr_box bounds;
+ struct wlr_box box = {
+ .width = state->desired_width,
+ .height = state->desired_height
+ };
+ const uint32 horizontal = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
+ | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
+ const uint32 vertical = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
+ | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
+
+ if (exclusive != (state->exclusive_zone > 0))
+ continue;
+
+ bounds = state->exclusive_zone == -1 ? full : *area;
+
+ // horizontal axis
+ if((state->anchor & horizontal) && box.width == 0) {
+ box.x = bounds.x;
+ box.width = bounds.width;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
+ box.x = bounds.x;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
+ box.x = bounds.x + (bounds.width - box.width);
+ } else {
+ box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));
+ }
+
+ // vertical axis
+ if((state->anchor & vertical) && box.height == 0) {
+ box.y = bounds.y;
+ box.height = bounds.height;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
+ box.y = bounds.y;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
+ box.y = bounds.y + (bounds.height - box.height);
+ } else {
+ box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));
+ }
+
+ // margin
+ if((state->anchor & horizontal) == horizontal) {
+ box.x += state->margin.left;
+ box.width -= state->margin.left + state->margin.right;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
+ box.x += state->margin.left;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
+ box.x -= state->margin.right;
+ }
+
+ if((state->anchor & vertical) == vertical) {
+ box.y += state->margin.top;
+ box.height -= state->margin.top + state->margin.bottom;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
+ box.y += state->margin.top;
+ } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
+ box.y -= state->margin.bottom;
+ }
+ if(box.width < 0 || box.height < 0) {
+ wlr_layer_surface_v1_close(surface);
+ continue;
+ }
+ layer->geometry = box;
+
+ if (state->exclusive_zone > 0)
+ exclude(area,
+ state->anchor, state->exclusive_zone,
+ state->margin.top, state->margin.right,
+ state->margin.bottom, state->margin.left);
+ wlr_layer_surface_v1_configure(surface, box.width, box.height);
+ }
+}
+
+void
+stratify(Monitor *monitor)
+{
+ int i;
+ Layer *layer;
+ struct wlr_box area = monitor->geometry;
+ uint32_t overlays[] = {
+ ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
+ ZWLR_LAYER_SHELL_V1_LAYER_TOP,
+ };
+ struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(server.input.seat);
+
+ // arrange exclusive surfaces from top->bottom
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &area, 1);
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &area, 1);
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &area, 1);
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &area, 1);
+
+ if(memcmp(&area, &monitor->window, sizeof(area))) {
+ monitor->window = area;
+ arrange(monitor);
+ }
+
+ // arrange non-exlusive surfaces from top->bottom
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &area, 0);
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &area, 0);
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &area, 0);
+ stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &area, 0);
+
+ // find topmost keyboard interactive layer, if such a layer exists
+ for(i = 0; i < arrlen(overlays); i++) {
+ wl_list_for_each_reverse(layer, &monitor->layer[overlays[i]], link) {
+ if (layer->surface->current.keyboard_interactive && layer->surface->mapped) {
+ // Deactivate the focused client.
+ focus(nil, 0);
+ wlr_seat_keyboard_notify_enter(
+ server.input.seat,
+ layer->surface->surface,
+ keyboard->keycodes,
+ keyboard->num_keycodes,
+ &keyboard->modifiers
+ );
+ return;
+ }
+ }
+ }
+}
+
+Client *
+focused_client(Monitor *monitor)
+{
+ Client *client;
+ wl_list_for_each(client, &server.client.focus, focus) {
+ if(VISIBLE_ON(client, monitor))
+ return client;
+ }
+
+ return nil;
+}
+
+void
+tile(Monitor *monitor)
+{
+ Client *client;
+ uint i, n, h, mw, my, ty;
+
+ n = 0;
+ wl_list_for_each(client, &server.client.list, link) {
+ if(VISIBLE_ON(client, monitor) && !client->isfloating)
+ n++;
+ }
+ if(!n) return;
+
+ if(n > monitor->master.len)
+ mw = monitor->master.len ? monitor->window.width * monitor->master.frac : 0;
+ else
+ mw = monitor->window.width;
+
+ i = my = ty = 0;
+ wl_list_for_each(client, &server.client.list, link) {
+ if(!VISIBLE_ON(client,monitor) || client->isfloating || client->isfullscreen)
+ continue;
+ if(i < monitor->master.len) {
+ h = (monitor->window.height - my) / (MIN(n, monitor->master.len) - i);
+ resize(client, monitor->window.x, monitor->window.y + my, mw, h, 0);
+ my += client->geometry.height;
+ } else {
+ h = (monitor->window.height - ty) / (n - i);
+ resize(client, monitor->window.x + mw, monitor->window.y + ty, monitor->window.width - mw, h, 0);
+ ty += client->geometry.height;
+ }
+ i++;
+ }
+}
+
+Monitor *
+monitor_at(double x, double y)
+{
+ struct wlr_output *output = wlr_output_layout_output_at(server.monitor.layout, x, y);
+ return output ? output->data : nil;
+}
diff --git a/src/cmd/wm/protocol/sync b/src/cmd/wm/protocol/sync
new file mode 100755
index 0000000..19a728a
--- /dev/null
+++ b/src/cmd/wm/protocol/sync
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+for base in wlr-layer-shell-unstable-v1.xml
+do
+ curl https://raw.githubusercontent.com/swaywm/wlroots/master/protocol/$base --output $base
+done
diff --git a/src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml b/src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml
new file mode 100644
index 0000000..d62fd51
--- /dev/null
+++ b/src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_unstable_v1">
+ <copyright>
+ Copyright © 2017 Drew DeVault
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="zwlr_layer_shell_v1" version="4">
+ <description summary="create surfaces that are layers of the desktop">
+ Clients can use this interface to assign the surface_layer role to
+ wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ rendered with a defined z-depth respective to each other. They may also be
+ anchored to the edges and corners of a screen and specify input handling
+ semantics. This interface should be suitable for the implementation of
+ many desktop shell components, and a broad number of other applications
+ that interact with the desktop.
+ </description>
+
+ <request name="get_layer_surface">
+ <description summary="create a layer_surface from a surface">
+ Create a layer surface for an existing surface. This assigns the role of
+ layer_surface, or raises a protocol error if another role is already
+ assigned.
+
+ Creating a layer surface from a wl_surface which has a buffer attached
+ or committed is a client error, and any attempts by a client to attach
+ or manipulate a buffer prior to the first layer_surface.configure call
+ must also be treated as errors.
+
+ After creating a layer_surface object and setting it up, the client
+ must perform an initial commit without any buffer attached.
+ The compositor will reply with a layer_surface.configure event.
+ The client must acknowledge it and is then allowed to attach a buffer
+ to map the surface.
+
+ You may pass NULL for output to allow the compositor to decide which
+ output to use. Generally this will be the one that the user most
+ recently interacted with.
+
+ Clients can specify a namespace that defines the purpose of the layer
+ surface.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+ <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+ </request>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="wl_surface has another role"/>
+ <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+ <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+ </enum>
+
+ <enum name="layer">
+ <description summary="available layers for surfaces">
+ These values indicate which layers a surface can be rendered in. They
+ are ordered by z depth, bottom-most first. Traditional shell surfaces
+ will typically be rendered between the bottom and top layers.
+ Fullscreen shell surfaces are typically rendered at the top layer.
+ Multiple surfaces can share a single layer, and ordering within a
+ single layer is undefined.
+ </description>
+
+ <entry name="background" value="0"/>
+ <entry name="bottom" value="1"/>
+ <entry name="top" value="2"/>
+ <entry name="overlay" value="3"/>
+ </enum>
+
+ <!-- Version 3 additions -->
+
+ <request name="destroy" type="destructor" since="3">
+ <description summary="destroy the layer_shell object">
+ This request indicates that the client will not use the layer_shell
+ object any more. Objects that have been created through this instance
+ are not affected.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_layer_surface_v1" version="4">
+ <description summary="layer metadata interface">
+ An interface that may be implemented by a wl_surface, for surfaces that
+ are designed to be rendered as a layer of a stacked desktop-like
+ environment.
+
+ Layer surface state (layer, size, anchor, exclusive zone,
+ margin, interactivity) is double-buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+
+ Attaching a null buffer to a layer surface unmaps it.
+
+ Unmapping a layer_surface means that the surface cannot be shown by the
+ compositor until it is explicitly mapped again. The layer_surface
+ returns to the state it had right after layer_shell.get_layer_surface.
+ The client can re-map the surface by performing a commit without any
+ buffer attached, waiting for a configure event and handling it as usual.
+ </description>
+
+ <request name="set_size">
+ <description summary="sets the size of the surface">
+ Sets the size of the surface in surface-local coordinates. The
+ compositor will display the surface centered with respect to its
+ anchors.
+
+ If you pass 0 for either value, the compositor will assign it and
+ inform you of the assignment in the configure event. You must set your
+ anchor to opposite edges in the dimensions you omit; not doing so is a
+ protocol error. Both values are 0 by default.
+
+ Size is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </request>
+
+ <request name="set_anchor">
+ <description summary="configures the anchor point of the surface">
+ Requests that the compositor anchor the surface to the specified edges
+ and corners. If two orthogonal edges are specified (e.g. 'top' and
+ 'left'), then the anchor point will be the intersection of the edges
+ (e.g. the top left corner of the output); otherwise the anchor point
+ will be centered on that edge, or in the center if none is specified.
+
+ Anchor is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"/>
+ </request>
+
+ <request name="set_exclusive_zone">
+ <description summary="configures the exclusive geometry of this surface">
+ Requests that the compositor avoids occluding an area with other
+ surfaces. The compositor's use of this information is
+ implementation-dependent - do not assume that this region will not
+ actually be occluded.
+
+ A positive value is only meaningful if the surface is anchored to one
+ edge or an edge and both perpendicular edges. If the surface is not
+ anchored, anchored to only two perpendicular edges (a corner), anchored
+ to only two parallel edges or anchored to all edges, a positive value
+ will be treated the same as zero.
+
+ A positive zone is the distance from the edge in surface-local
+ coordinates to consider exclusive.
+
+ Surfaces that do not wish to have an exclusive zone may instead specify
+ how they should interact with surfaces that do. If set to zero, the
+ surface indicates that it would like to be moved to avoid occluding
+ surfaces with a positive exclusive zone. If set to -1, the surface
+ indicates that it would not like to be moved to accommodate for other
+ surfaces, and the compositor should extend it all the way to the edges
+ it is anchored to.
+
+ For example, a panel might set its exclusive zone to 10, so that
+ maximized shell surfaces are not shown on top of it. A notification
+ might set its exclusive zone to 0, so that it is moved to avoid
+ occluding the panel, but shell surfaces are shown underneath it. A
+ wallpaper or lock screen might set their exclusive zone to -1, so that
+ they stretch below or over the panel.
+
+ The default value is 0.
+
+ Exclusive zone is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="zone" type="int"/>
+ </request>
+
+ <request name="set_margin">
+ <description summary="sets a margin from the anchor point">
+ Requests that the surface be placed some distance away from the anchor
+ point on the output, in surface-local coordinates. Setting this value
+ for edges you are not anchored to has no effect.
+
+ The exclusive zone includes the margin.
+
+ Margin is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="top" type="int"/>
+ <arg name="right" type="int"/>
+ <arg name="bottom" type="int"/>
+ <arg name="left" type="int"/>
+ </request>
+
+ <enum name="keyboard_interactivity">
+ <description summary="types of keyboard interaction possible for a layer shell surface">
+ Types of keyboard interaction possible for layer shell surfaces. The
+ rationale for this is twofold: (1) some applications are not interested
+ in keyboard events and not allowing them to be focused can improve the
+ desktop experience; (2) some applications will want to take exclusive
+ keyboard focus.
+ </description>
+
+ <entry name="none" value="0">
+ <description summary="no keyboard focus is possible">
+ This value indicates that this surface is not interested in keyboard
+ events and the compositor should never assign it the keyboard focus.
+
+ This is the default value, set for newly created layer shell surfaces.
+
+ This is useful for e.g. desktop widgets that display information or
+ only have interaction with non-keyboard input devices.
+ </description>
+ </entry>
+ <entry name="exclusive" value="1">
+ <description summary="request exclusive keyboard focus">
+ Request exclusive keyboard focus if this surface is above the shell surface layer.
+
+ For the top and overlay layers, the seat will always give
+ exclusive keyboard focus to the top-most layer which has keyboard
+ interactivity set to exclusive. If this layer contains multiple
+ surfaces with keyboard interactivity set to exclusive, the compositor
+ determines the one receiving keyboard events in an implementation-
+ defined manner. In this case, no guarantee is made when this surface
+ will receive keyboard focus (if ever).
+
+ For the bottom and background layers, the compositor is allowed to use
+ normal focus semantics.
+
+ This setting is mainly intended for applications that need to ensure
+ they receive all keyboard events, such as a lock screen or a password
+ prompt.
+ </description>
+ </entry>
+ <entry name="on_demand" value="2" since="4">
+ <description summary="request regular keyboard focus semantics">
+ This requests the compositor to allow this surface to be focused and
+ unfocused by the user in an implementation-defined manner. The user
+ should be able to unfocus this surface even regardless of the layer
+ it is on.
+
+ Typically, the compositor will want to use its normal mechanism to
+ manage keyboard focus between layer shell surfaces with this setting
+ and regular toplevels on the desktop layer (e.g. click to focus).
+ Nevertheless, it is possible for a compositor to require a special
+ interaction to focus or unfocus layer shell surfaces (e.g. requiring
+ a click even if focus follows the mouse normally, or providing a
+ keybinding to switch focus between layers).
+
+ This setting is mainly intended for desktop shell components (e.g.
+ panels) that allow keyboard interaction. Using this option can allow
+ implementing a desktop shell that can be fully usable without the
+ mouse.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_keyboard_interactivity">
+ <description summary="requests keyboard events">
+ Set how keyboard events are delivered to this surface. By default,
+ layer shell surfaces do not receive keyboard events; this request can
+ be used to change this.
+
+ This setting is inherited by child surfaces set by the get_popup
+ request.
+
+ Layer surfaces receive pointer, touch, and tablet events normally. If
+ you do not want to receive them, set the input region on your surface
+ to an empty region.
+
+ Keyboard interactivity is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign this layer_surface as an xdg_popup parent">
+ This assigns an xdg_popup's parent to this layer_surface. This popup
+ should have been created via xdg_surface::get_popup with the parent set
+ to NULL, and this request must be invoked before committing the popup's
+ initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="popup" type="object" interface="xdg_popup"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the layer_surface">
+ This request destroys the layer surface.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event asks the client to resize its surface.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ The client is free to dismiss all but the last configure event it
+ received.
+
+ The width and height arguments specify the size of the window in
+ surface-local coordinates.
+
+ The size is a hint, in the sense that the client is free to ignore it if
+ it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+ resize in steps of NxM pixels). If the client picks a smaller size and
+ is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+ surface will be centered on this axis.
+
+ If the width or height arguments are zero, it means the client should
+ decide its own window dimension.
+ </description>
+ <arg name="serial" type="uint"/>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </event>
+
+ <event name="closed">
+ <description summary="surface should be closed">
+ The closed event is sent by the compositor when the surface will no
+ longer be shown. The output may have been destroyed or the user may
+ have asked for it to be removed. Further changes to the surface will be
+ ignored. The client should destroy the resource after receiving this
+ event, and create a new surface if they so choose.
+ </description>
+ </event>
+
+ <enum name="error">
+ <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+ <entry name="invalid_size" value="1" summary="size is invalid"/>
+ <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+ <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
+ </enum>
+
+ <enum name="anchor" bitfield="true">
+ <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+ <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+ <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+ <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+ </enum>
+
+ <!-- Version 2 additions -->
+
+ <request name="set_layer" since="2">
+ <description summary="change the layer of the surface">
+ Change the layer that the surface is rendered on.
+
+ Layer is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/src/cmd/wm/render.c b/src/cmd/wm/render.c
new file mode 100644
index 0000000..1f51804
--- /dev/null
+++ b/src/cmd/wm/render.c
@@ -0,0 +1,160 @@
+#include "wm.h"
+
+struct Payload
+{
+ Client *client;
+ struct wlr_output *output;
+ struct timespec *when;
+ int x, y;
+};
+
+static
+void
+render(struct wlr_surface *surface, int sx, int sy, void *data)
+{
+ float matrix[9];
+ double x, y;
+ struct Payload *payload;
+
+ struct wlr_box box;
+ struct wlr_output *output;
+ struct wlr_texture *texture;
+
+ enum wl_output_transform transform;
+
+ payload = data;
+ output = payload->output;
+
+ texture = wlr_surface_get_texture(surface);
+ if(!texture)
+ return;
+
+ x = 0, y = 0;
+ wlr_output_layout_output_coords(server.monitor.layout, output, &x, &y);
+
+ box = (struct wlr_box) {
+ .x = x + payload->x + sx,
+ .y = y + payload->y + sy,
+ .width = surface->current.width,
+ .height = surface->current.height,
+ };
+ scale_box(&box, output->scale);
+
+ transform = wlr_output_transform_invert(surface->current.transform);
+ wlr_matrix_project_box(matrix, &box, transform, 0, output->transform_matrix);
+
+ wlr_render_texture_with_matrix(server.renderer, texture, matrix, 1);
+ wlr_surface_send_frame_done(surface, payload->when);
+ wlr_presentation_surface_sampled_on_output(server.present, surface, output);
+}
+
+static
+void
+render_layer(struct wl_list *list, struct timespec *now)
+{
+ Layer *layer;
+ wl_list_for_each(layer, list, link) {
+ struct Payload payload= {
+ .output = layer->surface->output,
+ .x = layer->geometry.x,
+ .y = layer->geometry.y,
+ .when = now,
+ };
+
+ wlr_surface_for_each_surface(layer->surface->surface, render, &payload);
+ }
+}
+
+static
+void
+render_clients(Monitor *monitor, struct timespec *now)
+{
+ double x, y;
+ int i, w, h, bw;
+ float *color;
+
+ Client *client;
+ struct wlr_output *output;
+ struct wlr_box *borders;
+ struct wlr_surface *surface;
+
+ output = monitor->output;
+ wl_list_for_each_reverse(client, &server.client.stack, stack) {
+ if(!VISIBLE_ON(client, client->monitor))
+ continue;
+ if(!wlr_output_layout_intersects(server.monitor.layout, monitor->output, &client->geometry))
+ continue;
+
+ surface = client->xdg->surface;
+
+ x = client->geometry.x, y = client->geometry.y;
+ wlr_output_layout_output_coords(server.monitor.layout, output, &x, &y);
+
+ if((bw=client->border)) {
+ w = surface->current.width;
+ h = surface->current.height;
+ borders = (struct wlr_box[4]) {
+ {x, y, w+2*bw, bw}, /* top */
+ {x, y+bw, bw, h}, /* left */
+ {x+bw+w, y+bw, bw, h}, /* right */
+ {x, y+bw+h, w+2*bw, bw}, /* bottom */
+ };
+
+ color = (client == server.selected) ? cfg·focuscolor : cfg·bordercolor;
+ for(i=0; i<4; i++) {
+ scale_box(&borders[i], output->scale);
+ wlr_render_rect(server.renderer, &borders[i], color, output->transform_matrix);
+ }
+ }
+
+ struct Payload payload = {
+ .output = output,
+ .when = now,
+
+ .x = client->geometry.x + client->border,
+ .y = client->geometry.y + client->border,
+ };
+
+ wlr_xdg_surface_for_each_surface(client->xdg, render, &payload);
+ }
+}
+
+void
+render_monitor(struct wl_listener *l, void *data)
+{
+ int w, h;
+ Client *client;
+ Monitor *monitor;
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ monitor = wl_container_of(l, monitor, event.render);
+
+ wl_list_for_each(client, &server.client.list, link) {
+ if(client->resize) {
+ wlr_surface_send_frame_done(client->xdg->surface, &now);
+ }
+ }
+
+ if(!wlr_output_attach_render(monitor->output, nil))
+ return;
+
+ wlr_output_effective_resolution(monitor->output, &w, &h);
+
+ /* start of rendering kernel */
+ wlr_renderer_begin(server.renderer, w, h);
+ wlr_renderer_clear(server.renderer, cfg·rootcolor);
+
+ render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now);
+ render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now);
+
+ render_clients(monitor, &now);
+
+ render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now);
+ render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now);
+
+ wlr_output_render_software_cursors(monitor->output, nil);
+
+ wlr_renderer_end(server.renderer);
+ wlr_output_commit(monitor->output);
+}
diff --git a/src/cmd/wm/rules.mk b/src/cmd/wm/rules.mk
new file mode 100644
index 0000000..30d786d
--- /dev/null
+++ b/src/cmd/wm/rules.mk
@@ -0,0 +1,62 @@
+include share/push.mk
+# Iterate through subdirectory tree
+
+# local sources
+SRCS_$(d):=\
+ $(d)/xdg-shell-protocol.c\
+ $(d)/wlr-layer-shell-unstable-v1-protocol.c\
+ $(d)/util.c\
+ $(d)/input.c\
+ $(d)/render.c\
+ $(d)/layer.c\
+ $(d)/xdg.c\
+ $(d)/client.c\
+ $(d)/monitor.c\
+ $(d)/main.c
+
+# local outputs
+BINS_$(d) := $(d)/wm
+
+include share/paths.mk
+
+# Local rules
+include share/dynamic.mk
+
+$(d)/xdg-shell-protocol.h:
+ @echo "MK $(notdir $@)";\
+ $(WL_SCAN) server-header $(WL_PROTO)/stable/xdg-shell/xdg-shell.xml $@
+
+$(d)/xdg-shell-protocol.c: $(d)/xdg-shell-protocol.h
+ @echo "MK $(notdir $@)";\
+ $(WL_SCAN) private-code $(WL_PROTO)/stable/xdg-shell/xdg-shell.xml $@
+
+$(d)/wlr-layer-shell-unstable-v1-protocol.h:
+ @echo "MK $(notdir $@)";\
+ $(WL_SCAN) server-header $(dir $@)protocol/wlr-layer-shell-unstable-v1.xml $@
+
+$(d)/wlr-layer-shell-unstable-v1-protocol.c: $(d)/wlr-layer-shell-unstable-v1-protocol.h
+ @echo "MK $(notdir $@)";\
+ $(WL_SCAN) private-code $(dir $@)protocol/wlr-layer-shell-unstable-v1.xml $@
+
+GENS+=\
+ $(d)/xdg-shell-protocol.h\
+ $(d)/xdg-shell-protocol.c\
+ $(d)/wlr-layer-shell-unstable-v1-protocol.h\
+ $(d)/wlr-layer-shell-unstable-v1-protocol.c
+
+$(BINS_$(d)): TCINCS=-I cmd/wm
+
+$(BINS_$(d)): TCFLAGS=\
+ `$(PKG) --cflags wlroots`\
+ `$(PKG) --cflags wayland-server`\
+ `$(PKG) --cflags xkbcommon`
+
+$(BINS_$(d)): TCLIBS=\
+ `$(PKG) --libs wlroots`\
+ `$(PKG) --libs wayland-server`\
+ `$(PKG) --libs xkbcommon`\
+
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/cmd/wm/util.c b/src/cmd/wm/util.c
new file mode 100644
index 0000000..7871d15
--- /dev/null
+++ b/src/cmd/wm/util.c
@@ -0,0 +1,99 @@
+#include "wm.h"
+
+typedef struct {
+ uint32 singular_anchor;
+ uint32 anchor_triplet;
+ int *positive_axis;
+ int *negative_axis;
+ int margin;
+} Edge;
+
+// -----------------------------------------------------------------------
+// general purpose function on rectangles
+
+void
+scale_box(struct wlr_box *box, float scale)
+{
+ box->width = ROUND((box->x + box->width) * scale) - ROUND(box->x * scale);
+ box->height = ROUND((box->y + box->height) * scale) - ROUND(box->y * scale);
+ box->x = ROUND(box->x * scale);
+ box->y = ROUND(box->y * scale);
+}
+
+void
+exclude(struct wlr_box *usable_area, uint32 anchor, int32 exclusive,
+ int32 margin_top, int32 margin_right, int32 margin_bottom, int32 margin_left)
+{
+ Edge edges[] = {
+ { // Top
+ .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
+ .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
+ .positive_axis = &usable_area->y,
+ .negative_axis = &usable_area->height,
+ .margin = margin_top,
+ },
+ { // Bottom
+ .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = NULL,
+ .negative_axis = &usable_area->height,
+ .margin = margin_bottom,
+ },
+ { // Left
+ .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT,
+ .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = &usable_area->x,
+ .negative_axis = &usable_area->width,
+ .margin = margin_left,
+ },
+ { // Right
+ .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT,
+ .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = NULL,
+ .negative_axis = &usable_area->width,
+ .margin = margin_right,
+ }
+ };
+ for(size_t i = 0; i < arrlen(edges); i++) {
+ if((anchor == edges[i].singular_anchor || anchor == edges[i].anchor_triplet)
+ && exclusive + edges[i].margin > 0) {
+ if(edges[i].positive_axis)
+ *edges[i].positive_axis += exclusive + edges[i].margin;
+ if(edges[i].negative_axis)
+ *edges[i].negative_axis -= exclusive + edges[i].margin;
+ break;
+ }
+ }
+}
+
+// -----------------------------------------------------------------------
+// user facing functions
+
+void
+spawn(Arg *arg)
+{
+ wlr_log(WLR_DEBUG, "spawning %s", ((char **)arg->v)[0]);
+ if(!fork()) {
+ dup2(2, 1);
+ setsid();
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ }
+}
+
+void
+quit(Arg *arg)
+{
+ wl_display_terminate(server.display);
+}
+
+#define CONFIG(a,b,...) a cfg·##b = __VA_ARGS__
+#include "config.h"
+#undef CONFIG
diff --git a/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c
new file mode 100644
index 0000000..95ff317
--- /dev/null
+++ b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c
@@ -0,0 +1,93 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+/*
+ * Copyright © 2017 Drew DeVault
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface wl_output_interface;
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface xdg_popup_interface;
+extern const struct wl_interface zwlr_layer_surface_v1_interface;
+
+static const struct wl_interface *wlr_layer_shell_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &zwlr_layer_surface_v1_interface,
+ &wl_surface_interface,
+ &wl_output_interface,
+ NULL,
+ NULL,
+ &xdg_popup_interface,
+};
+
+static const struct wl_message zwlr_layer_shell_v1_requests[] = {
+ { "get_layer_surface", "no?ous", wlr_layer_shell_unstable_v1_types + 4 },
+ { "destroy", "3", wlr_layer_shell_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwlr_layer_shell_v1_interface = {
+ "zwlr_layer_shell_v1", 4,
+ 2, zwlr_layer_shell_v1_requests,
+ 0, NULL,
+};
+
+static const struct wl_message zwlr_layer_surface_v1_requests[] = {
+ { "set_size", "uu", wlr_layer_shell_unstable_v1_types + 0 },
+ { "set_anchor", "u", wlr_layer_shell_unstable_v1_types + 0 },
+ { "set_exclusive_zone", "i", wlr_layer_shell_unstable_v1_types + 0 },
+ { "set_margin", "iiii", wlr_layer_shell_unstable_v1_types + 0 },
+ { "set_keyboard_interactivity", "u", wlr_layer_shell_unstable_v1_types + 0 },
+ { "get_popup", "o", wlr_layer_shell_unstable_v1_types + 9 },
+ { "ack_configure", "u", wlr_layer_shell_unstable_v1_types + 0 },
+ { "destroy", "", wlr_layer_shell_unstable_v1_types + 0 },
+ { "set_layer", "2u", wlr_layer_shell_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwlr_layer_surface_v1_events[] = {
+ { "configure", "uuu", wlr_layer_shell_unstable_v1_types + 0 },
+ { "closed", "", wlr_layer_shell_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwlr_layer_surface_v1_interface = {
+ "zwlr_layer_surface_v1", 4,
+ 9, zwlr_layer_surface_v1_requests,
+ 2, zwlr_layer_surface_v1_events,
+};
+
diff --git a/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h
new file mode 100644
index 0000000..ea2fa9b
--- /dev/null
+++ b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h
@@ -0,0 +1,564 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+#ifndef WLR_LAYER_SHELL_UNSTABLE_V1_SERVER_PROTOCOL_H
+#define WLR_LAYER_SHELL_UNSTABLE_V1_SERVER_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-server.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct wl_client;
+struct wl_resource;
+
+/**
+ * @page page_wlr_layer_shell_unstable_v1 The wlr_layer_shell_unstable_v1 protocol
+ * @section page_ifaces_wlr_layer_shell_unstable_v1 Interfaces
+ * - @subpage page_iface_zwlr_layer_shell_v1 - create surfaces that are layers of the desktop
+ * - @subpage page_iface_zwlr_layer_surface_v1 - layer metadata interface
+ * @section page_copyright_wlr_layer_shell_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2017 Drew DeVault
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * </pre>
+ */
+struct wl_output;
+struct wl_surface;
+struct xdg_popup;
+struct zwlr_layer_shell_v1;
+struct zwlr_layer_surface_v1;
+
+#ifndef ZWLR_LAYER_SHELL_V1_INTERFACE
+#define ZWLR_LAYER_SHELL_V1_INTERFACE
+/**
+ * @page page_iface_zwlr_layer_shell_v1 zwlr_layer_shell_v1
+ * @section page_iface_zwlr_layer_shell_v1_desc Description
+ *
+ * Clients can use this interface to assign the surface_layer role to
+ * wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ * rendered with a defined z-depth respective to each other. They may also be
+ * anchored to the edges and corners of a screen and specify input handling
+ * semantics. This interface should be suitable for the implementation of
+ * many desktop shell components, and a broad number of other applications
+ * that interact with the desktop.
+ * @section page_iface_zwlr_layer_shell_v1_api API
+ * See @ref iface_zwlr_layer_shell_v1.
+ */
+/**
+ * @defgroup iface_zwlr_layer_shell_v1 The zwlr_layer_shell_v1 interface
+ *
+ * Clients can use this interface to assign the surface_layer role to
+ * wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ * rendered with a defined z-depth respective to each other. They may also be
+ * anchored to the edges and corners of a screen and specify input handling
+ * semantics. This interface should be suitable for the implementation of
+ * many desktop shell components, and a broad number of other applications
+ * that interact with the desktop.
+ */
+extern const struct wl_interface zwlr_layer_shell_v1_interface;
+#endif
+#ifndef ZWLR_LAYER_SURFACE_V1_INTERFACE
+#define ZWLR_LAYER_SURFACE_V1_INTERFACE
+/**
+ * @page page_iface_zwlr_layer_surface_v1 zwlr_layer_surface_v1
+ * @section page_iface_zwlr_layer_surface_v1_desc Description
+ *
+ * An interface that may be implemented by a wl_surface, for surfaces that
+ * are designed to be rendered as a layer of a stacked desktop-like
+ * environment.
+ *
+ * Layer surface state (layer, size, anchor, exclusive zone,
+ * margin, interactivity) is double-buffered, and will be applied at the
+ * time wl_surface.commit of the corresponding wl_surface is called.
+ *
+ * Attaching a null buffer to a layer surface unmaps it.
+ *
+ * Unmapping a layer_surface means that the surface cannot be shown by the
+ * compositor until it is explicitly mapped again. The layer_surface
+ * returns to the state it had right after layer_shell.get_layer_surface.
+ * The client can re-map the surface by performing a commit without any
+ * buffer attached, waiting for a configure event and handling it as usual.
+ * @section page_iface_zwlr_layer_surface_v1_api API
+ * See @ref iface_zwlr_layer_surface_v1.
+ */
+/**
+ * @defgroup iface_zwlr_layer_surface_v1 The zwlr_layer_surface_v1 interface
+ *
+ * An interface that may be implemented by a wl_surface, for surfaces that
+ * are designed to be rendered as a layer of a stacked desktop-like
+ * environment.
+ *
+ * Layer surface state (layer, size, anchor, exclusive zone,
+ * margin, interactivity) is double-buffered, and will be applied at the
+ * time wl_surface.commit of the corresponding wl_surface is called.
+ *
+ * Attaching a null buffer to a layer surface unmaps it.
+ *
+ * Unmapping a layer_surface means that the surface cannot be shown by the
+ * compositor until it is explicitly mapped again. The layer_surface
+ * returns to the state it had right after layer_shell.get_layer_surface.
+ * The client can re-map the surface by performing a commit without any
+ * buffer attached, waiting for a configure event and handling it as usual.
+ */
+extern const struct wl_interface zwlr_layer_surface_v1_interface;
+#endif
+
+#ifndef ZWLR_LAYER_SHELL_V1_ERROR_ENUM
+#define ZWLR_LAYER_SHELL_V1_ERROR_ENUM
+enum zwlr_layer_shell_v1_error {
+ /**
+ * wl_surface has another role
+ */
+ ZWLR_LAYER_SHELL_V1_ERROR_ROLE = 0,
+ /**
+ * layer value is invalid
+ */
+ ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER = 1,
+ /**
+ * wl_surface has a buffer attached or committed
+ */
+ ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED = 2,
+};
+#endif /* ZWLR_LAYER_SHELL_V1_ERROR_ENUM */
+
+#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM
+#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM
+/**
+ * @ingroup iface_zwlr_layer_shell_v1
+ * available layers for surfaces
+ *
+ * These values indicate which layers a surface can be rendered in. They
+ * are ordered by z depth, bottom-most first. Traditional shell surfaces
+ * will typically be rendered between the bottom and top layers.
+ * Fullscreen shell surfaces are typically rendered at the top layer.
+ * Multiple surfaces can share a single layer, and ordering within a
+ * single layer is undefined.
+ */
+enum zwlr_layer_shell_v1_layer {
+ ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0,
+ ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1,
+ ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2,
+ ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3,
+};
+#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */
+
+/**
+ * @ingroup iface_zwlr_layer_shell_v1
+ * @struct zwlr_layer_shell_v1_interface
+ */
+struct zwlr_layer_shell_v1_interface {
+ /**
+ * create a layer_surface from a surface
+ *
+ * Create a layer surface for an existing surface. This assigns
+ * the role of layer_surface, or raises a protocol error if another
+ * role is already assigned.
+ *
+ * Creating a layer surface from a wl_surface which has a buffer
+ * attached or committed is a client error, and any attempts by a
+ * client to attach or manipulate a buffer prior to the first
+ * layer_surface.configure call must also be treated as errors.
+ *
+ * After creating a layer_surface object and setting it up, the
+ * client must perform an initial commit without any buffer
+ * attached. The compositor will reply with a
+ * layer_surface.configure event. The client must acknowledge it
+ * and is then allowed to attach a buffer to map the surface.
+ *
+ * You may pass NULL for output to allow the compositor to decide
+ * which output to use. Generally this will be the one that the
+ * user most recently interacted with.
+ *
+ * Clients can specify a namespace that defines the purpose of the
+ * layer surface.
+ * @param layer layer to add this surface to
+ * @param namespace namespace for the layer surface
+ */
+ void (*get_layer_surface)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ struct wl_resource *surface,
+ struct wl_resource *output,
+ uint32_t layer,
+ const char *namespace);
+ /**
+ * destroy the layer_shell object
+ *
+ * This request indicates that the client will not use the
+ * layer_shell object any more. Objects that have been created
+ * through this instance are not affected.
+ * @since 3
+ */
+ void (*destroy)(struct wl_client *client,
+ struct wl_resource *resource);
+};
+
+
+/**
+ * @ingroup iface_zwlr_layer_shell_v1
+ */
+#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_shell_v1
+ */
+#define ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION 3
+
+#ifndef ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ENUM
+#define ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ENUM
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ * request regular keyboard focus semantics
+ *
+ * This requests the compositor to allow this surface to be focused and
+ * unfocused by the user in an implementation-defined manner. The user
+ * should be able to unfocus this surface even regardless of the layer
+ * it is on.
+ *
+ * Typically, the compositor will want to use its normal mechanism to
+ * manage keyboard focus between layer shell surfaces with this setting
+ * and regular toplevels on the desktop layer (e.g. click to focus).
+ * Nevertheless, it is possible for a compositor to require a special
+ * interaction to focus or unfocus layer shell surfaces (e.g. requiring
+ * a click even if focus follows the mouse normally, or providing a
+ * keybinding to switch focus between layers).
+ *
+ * This setting is mainly intended for desktop shell components (e.g.
+ * panels) that allow keyboard interaction. Using this option can allow
+ * implementing a desktop shell that can be fully usable without the
+ * mouse.
+ */
+enum zwlr_layer_surface_v1_keyboard_interactivity {
+ ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE = 0,
+ ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE = 1,
+ /**
+ * @since 4
+ */
+ ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND = 2,
+};
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION 4
+#endif /* ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ENUM */
+
+#ifndef ZWLR_LAYER_SURFACE_V1_ERROR_ENUM
+#define ZWLR_LAYER_SURFACE_V1_ERROR_ENUM
+enum zwlr_layer_surface_v1_error {
+ /**
+ * provided surface state is invalid
+ */
+ ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE = 0,
+ /**
+ * size is invalid
+ */
+ ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE = 1,
+ /**
+ * anchor bitfield is invalid
+ */
+ ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR = 2,
+ /**
+ * keyboard interactivity is invalid
+ */
+ ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_KEYBOARD_INTERACTIVITY = 3,
+};
+#endif /* ZWLR_LAYER_SURFACE_V1_ERROR_ENUM */
+
+#ifndef ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM
+#define ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM
+enum zwlr_layer_surface_v1_anchor {
+ /**
+ * the top edge of the anchor rectangle
+ */
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP = 1,
+ /**
+ * the bottom edge of the anchor rectangle
+ */
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM = 2,
+ /**
+ * the left edge of the anchor rectangle
+ */
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT = 4,
+ /**
+ * the right edge of the anchor rectangle
+ */
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT = 8,
+};
+#endif /* ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM */
+
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ * @struct zwlr_layer_surface_v1_interface
+ */
+struct zwlr_layer_surface_v1_interface {
+ /**
+ * sets the size of the surface
+ *
+ * Sets the size of the surface in surface-local coordinates. The
+ * compositor will display the surface centered with respect to its
+ * anchors.
+ *
+ * If you pass 0 for either value, the compositor will assign it
+ * and inform you of the assignment in the configure event. You
+ * must set your anchor to opposite edges in the dimensions you
+ * omit; not doing so is a protocol error. Both values are 0 by
+ * default.
+ *
+ * Size is double-buffered, see wl_surface.commit.
+ */
+ void (*set_size)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t width,
+ uint32_t height);
+ /**
+ * configures the anchor point of the surface
+ *
+ * Requests that the compositor anchor the surface to the
+ * specified edges and corners. If two orthogonal edges are
+ * specified (e.g. 'top' and 'left'), then the anchor point will be
+ * the intersection of the edges (e.g. the top left corner of the
+ * output); otherwise the anchor point will be centered on that
+ * edge, or in the center if none is specified.
+ *
+ * Anchor is double-buffered, see wl_surface.commit.
+ */
+ void (*set_anchor)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t anchor);
+ /**
+ * configures the exclusive geometry of this surface
+ *
+ * Requests that the compositor avoids occluding an area with
+ * other surfaces. The compositor's use of this information is
+ * implementation-dependent - do not assume that this region will
+ * not actually be occluded.
+ *
+ * A positive value is only meaningful if the surface is anchored
+ * to one edge or an edge and both perpendicular edges. If the
+ * surface is not anchored, anchored to only two perpendicular
+ * edges (a corner), anchored to only two parallel edges or
+ * anchored to all edges, a positive value will be treated the same
+ * as zero.
+ *
+ * A positive zone is the distance from the edge in surface-local
+ * coordinates to consider exclusive.
+ *
+ * Surfaces that do not wish to have an exclusive zone may instead
+ * specify how they should interact with surfaces that do. If set
+ * to zero, the surface indicates that it would like to be moved to
+ * avoid occluding surfaces with a positive exclusive zone. If set
+ * to -1, the surface indicates that it would not like to be moved
+ * to accommodate for other surfaces, and the compositor should
+ * extend it all the way to the edges it is anchored to.
+ *
+ * For example, a panel might set its exclusive zone to 10, so that
+ * maximized shell surfaces are not shown on top of it. A
+ * notification might set its exclusive zone to 0, so that it is
+ * moved to avoid occluding the panel, but shell surfaces are shown
+ * underneath it. A wallpaper or lock screen might set their
+ * exclusive zone to -1, so that they stretch below or over the
+ * panel.
+ *
+ * The default value is 0.
+ *
+ * Exclusive zone is double-buffered, see wl_surface.commit.
+ */
+ void (*set_exclusive_zone)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t zone);
+ /**
+ * sets a margin from the anchor point
+ *
+ * Requests that the surface be placed some distance away from
+ * the anchor point on the output, in surface-local coordinates.
+ * Setting this value for edges you are not anchored to has no
+ * effect.
+ *
+ * The exclusive zone includes the margin.
+ *
+ * Margin is double-buffered, see wl_surface.commit.
+ */
+ void (*set_margin)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t top,
+ int32_t right,
+ int32_t bottom,
+ int32_t left);
+ /**
+ * requests keyboard events
+ *
+ * Set how keyboard events are delivered to this surface. By
+ * default, layer shell surfaces do not receive keyboard events;
+ * this request can be used to change this.
+ *
+ * This setting is inherited by child surfaces set by the get_popup
+ * request.
+ *
+ * Layer surfaces receive pointer, touch, and tablet events
+ * normally. If you do not want to receive them, set the input
+ * region on your surface to an empty region.
+ *
+ * Keyboard interactivity is double-buffered, see
+ * wl_surface.commit.
+ */
+ void (*set_keyboard_interactivity)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t keyboard_interactivity);
+ /**
+ * assign this layer_surface as an xdg_popup parent
+ *
+ * This assigns an xdg_popup's parent to this layer_surface. This
+ * popup should have been created via xdg_surface::get_popup with
+ * the parent set to NULL, and this request must be invoked before
+ * committing the popup's initial state.
+ *
+ * See the documentation of xdg_popup for more details about what
+ * an xdg_popup is and how it is used.
+ */
+ void (*get_popup)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *popup);
+ /**
+ * ack a configure event
+ *
+ * When a configure event is received, if a client commits the
+ * surface in response to the configure event, then the client must
+ * make an ack_configure request sometime before the commit
+ * request, passing along the serial of the configure event.
+ *
+ * If the client receives multiple configure events before it can
+ * respond to one, it only has to ack the last configure event.
+ *
+ * A client is not required to commit immediately after sending an
+ * ack_configure request - it may even ack_configure several times
+ * before its next surface commit.
+ *
+ * A client may send multiple ack_configure requests before
+ * committing, but only the last request sent before a commit
+ * indicates which configure event the client really is responding
+ * to.
+ * @param serial the serial from the configure event
+ */
+ void (*ack_configure)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t serial);
+ /**
+ * destroy the layer_surface
+ *
+ * This request destroys the layer surface.
+ */
+ void (*destroy)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * change the layer of the surface
+ *
+ * Change the layer that the surface is rendered on.
+ *
+ * Layer is double-buffered, see wl_surface.commit.
+ * @param layer layer to move this surface to
+ * @since 2
+ */
+ void (*set_layer)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t layer);
+};
+
+#define ZWLR_LAYER_SURFACE_V1_CONFIGURE 0
+#define ZWLR_LAYER_SURFACE_V1_CLOSED 1
+
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_CONFIGURE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_CLOSED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_SET_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_GET_POPUP_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ */
+#define ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION 2
+
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ * Sends an configure event to the client owning the resource.
+ * @param resource_ The client's resource
+ */
+static inline void
+zwlr_layer_surface_v1_send_configure(struct wl_resource *resource_, uint32_t serial, uint32_t width, uint32_t height)
+{
+ wl_resource_post_event(resource_, ZWLR_LAYER_SURFACE_V1_CONFIGURE, serial, width, height);
+}
+
+/**
+ * @ingroup iface_zwlr_layer_surface_v1
+ * Sends an closed event to the client owning the resource.
+ * @param resource_ The client's resource
+ */
+static inline void
+zwlr_layer_surface_v1_send_closed(struct wl_resource *resource_)
+{
+ wl_resource_post_event(resource_, ZWLR_LAYER_SURFACE_V1_CLOSED);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cmd/wm/wm.h b/src/cmd/wm/wm.h
new file mode 100644
index 0000000..a263804
--- /dev/null
+++ b/src/cmd/wm/wm.h
@@ -0,0 +1,350 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+#include <wayland-server-core.h>
+#include <linux/input-event-codes.h>
+
+#define WLR_USE_UNSTABLE
+#include <wlr/backend.h>
+#include <wlr/render/wlr_renderer.h>
+
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_data_control_v1.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_export_dmabuf_v1.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output_damage.h>
+#include <wlr/types/wlr_output_management_v1.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/types/wlr_primary_selection_v1.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_screencopy_v1.h>
+#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_viewporter.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_activation_v1.h>
+#include <wlr/types/wlr_xdg_decoration_v1.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/types/wlr_xdg_shell.h>
+
+#include <wlr/util/log.h>
+
+#include <xkbcommon/xkbcommon.h>
+
+// -----------------------------------------------------------------------
+// macros
+
+#define ROUND(x) ((int)((x)+0.5))
+#define VISIBLE_ON(C,M) ((C)->monitor == (M) && ((C)->tags & (M)->tag.set[(M)->tag.selected]))
+
+// -----------------------------------------------------------------------
+// types
+
+enum
+{
+ CursorNormal,
+ CursorMove,
+ CursorResize,
+};
+
+typedef union Arg Arg;
+typedef struct Button Button;
+typedef struct Key Key;
+typedef struct Keyboard Keyboard;
+typedef struct Layer Layer;
+typedef struct Client Client;
+typedef struct Layout Layout;
+typedef struct Monitor Monitor;
+typedef struct Server Server;
+
+typedef struct Rule Rule;
+typedef struct MonitorRule MonitorRule;
+
+struct Rectangle
+{
+ int x, y;
+ int w, h;
+};
+
+union Arg
+{
+ int i;
+ uint ui;
+ float f;
+ void *v;
+};
+
+struct Key
+{
+ uint modifier;
+ xkb_keysym_t sym;
+ void (*action)(Arg *);
+ Arg arg;
+};
+
+struct Button
+{
+ uint modifier;
+ uint code;
+ void (*function)(Arg *);
+ Arg arg;
+};
+
+struct Keyboard
+{
+ struct wl_list link;
+ struct wlr_input_device *device;
+ struct {
+ struct wl_listener press;
+ struct wl_listener modify;
+ struct wl_listener destroy;
+ } event;
+};
+
+struct Layer
+{
+ struct wl_list link;
+ struct wlr_layer_surface_v1 *surface;
+ enum zwlr_layer_shell_v1_layer type;
+
+ struct wlr_box geometry;
+
+ struct {
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener commit;
+ struct wl_listener destroy;
+ } event;
+};
+
+struct Client
+{
+ struct wl_list link;
+ struct wl_list stack;
+ struct wl_list focus;
+
+ struct wlr_xdg_surface *xdg;
+
+ struct {
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener commit;
+ struct wl_listener destroy;
+ struct wl_listener request_move;
+ struct wl_listener request_title;
+ struct wl_listener request_resize;
+ struct wl_listener request_fullscreen;
+ } event;
+
+ struct wlr_box geometry, oldgeometry;
+
+ Monitor *monitor;
+
+ uint tags;
+ int border : 4;
+ int ismapped : 1;
+ int isfloating : 1;
+ int isurgent : 1;
+ int isfullscreen : 1;
+
+ uint32 resize;
+};
+
+struct Layout
+{
+ char *symbol;
+ void (*arrange)(Monitor *);
+};
+
+struct Monitor
+{
+ struct wl_list link;
+ struct wlr_output *output;
+ struct {
+ struct wl_listener render;
+ struct wl_listener destroy;
+ } event;
+
+ struct wlr_box geometry;
+ struct wlr_box window;
+ struct wl_list layer[4];
+
+ Layout *layout, *layouts[2];
+ struct {
+ uint set[2];
+ uint selected;
+ } tag;
+ struct {
+ double frac;
+ int len;
+ } master;
+};
+
+struct MonitorRule
+{
+ char *name;
+ Layout *layout;
+ int x, y;
+ float scale;
+ enum wl_output_transform transform;
+ struct {
+ double frac;
+ int len;
+ } master;
+};
+
+struct Rule
+{
+ char *id;
+ char *title;
+ uint tags;
+ int isfloating;
+ int monitor;
+};
+
+struct Server
+{
+ struct wl_display *display;
+ struct wlr_backend *backend;
+ struct wlr_renderer *renderer;
+ struct wlr_presentation *present;
+ struct wlr_xdg_activation_v1 *activate;
+
+ struct {
+ struct wlr_xdg_shell *xdg;
+ struct wlr_layer_shell_v1 *layer;
+ } shell;
+
+ struct {
+ struct wl_list list;
+ struct wl_list stack;
+ struct wl_list focus;
+ } client;
+ Client *selected;
+
+ struct {
+ Client *client;
+ double x, y;
+ struct wlr_box box;
+ } grab;
+ uint32 resize;
+
+ struct {
+ struct wlr_output_layout *layout;
+ struct wl_list list;
+ struct wlr_box geometry;
+ struct wlr_output_manager_v1 *manager;
+ Monitor *selected;
+ } monitor;
+
+ struct {
+ struct wlr_cursor *dot;
+ struct wlr_xcursor_manager *manager;
+ int mode;
+ } cursor;
+
+ struct {
+ struct wlr_seat *seat;
+ struct wl_list keyboards;
+ struct wlr_idle *idle;
+ } input;
+
+ struct {
+ struct wl_listener make_input;
+ struct wl_listener make_monitor;
+ struct wl_listener make_xdg_surface;
+ struct wl_listener make_layer_surface;
+
+ struct wl_listener monitor_test;
+ struct wl_listener monitor_apply;
+ struct wl_listener monitor_change;
+
+ struct wl_listener cursor_move;
+ struct wl_listener cursor_move_abs;
+ struct wl_listener cursor_button;
+ struct wl_listener cursor_axis;
+ struct wl_listener cursor_frame;
+
+ struct wl_listener request_cursor;
+ struct wl_listener request_activate;
+ struct wl_listener request_set_selection;
+ } event;
+};
+
+extern struct Server server;
+
+// -----------------------------------------------------------------------
+// functions
+
+/* util.c */
+void scale_box(struct wlr_box *, float);
+void exclude(struct wlr_box *, uint32, int32, int32, int32, int32, int32 );
+
+/* render.c */
+void render_monitor(struct wl_listener *, void *);
+
+/* xdg.c */
+void make_xdg_surface(struct wl_listener *, void *);
+
+/* layer.c */
+void make_layer_surface(struct wl_listener *, void *);
+
+/* input.c */
+void make_input(struct wl_listener *, void *);
+void notify_move(uint32 time);
+
+void cursor_axis(struct wl_listener *, void *);
+void cursor_frame(struct wl_listener *, void *);
+void cursor_button(struct wl_listener *, void *);
+void cursor_move(struct wl_listener *, void *);
+void cursor_move_abs(struct wl_listener *, void *);
+
+void request_cursor(struct wl_listener *, void *);
+void request_set_selection(struct wl_listener *, void *);
+
+/* client.c */
+void rules(Client *);
+void focus(Client *, int lift);
+void resize(Client *, int x, int y, int w, int h, int interact);
+void attach(Client *, Monitor *, uint tags);
+void floating(Client *, int);
+
+void move_client(Arg *arg);
+void float_client(Arg *arg);
+void resize_client(Arg *arg);
+
+void request_activate(struct wl_listener *, void *);
+
+Client *selected_client(void);
+Client *client_at(double x, double y);
+struct wlr_surface *client_surface_at(Client *, double cx, double cy, double *sx, double *sy);
+struct wlr_surface *top_surface(Client *);
+
+/* monitor.c */
+void tile(Monitor *);
+void arrange(Monitor *);
+void stratify(Monitor *);
+Client *focused_client(Monitor *);
+Monitor *monitor_at(double x, double y);
+
+void monitor_test(struct wl_listener *, void *);
+void monitor_apply(struct wl_listener *, void *);
+void monitor_change(struct wl_listener *, void *);
+
+void free_monitor(struct wl_listener *, void *);
+void make_monitor(struct wl_listener *, void *);
+
+#define CONFIG(a,b,...) extern a cfg·##b
+#include "config.h"
+#undef CONFIG
diff --git a/src/cmd/wm/xdg-shell-protocol.c b/src/cmd/wm/xdg-shell-protocol.c
new file mode 100644
index 0000000..62a2612
--- /dev/null
+++ b/src/cmd/wm/xdg-shell-protocol.c
@@ -0,0 +1,181 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+/*
+ * Copyright © 2008-2013 Kristian Høgsberg
+ * Copyright © 2013 Rafael Antognolli
+ * Copyright © 2013 Jasper St. Pierre
+ * Copyright © 2010-2013 Intel Corporation
+ * Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ * Copyright © 2015-2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+#define WL_PRIVATE __attribute__ ((visibility("hidden")))
+#else
+#define WL_PRIVATE
+#endif
+
+extern const struct wl_interface wl_output_interface;
+extern const struct wl_interface wl_seat_interface;
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface xdg_popup_interface;
+extern const struct wl_interface xdg_positioner_interface;
+extern const struct wl_interface xdg_surface_interface;
+extern const struct wl_interface xdg_toplevel_interface;
+
+static const struct wl_interface *xdg_shell_types[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &xdg_positioner_interface,
+ &xdg_surface_interface,
+ &wl_surface_interface,
+ &xdg_toplevel_interface,
+ &xdg_popup_interface,
+ &xdg_surface_interface,
+ &xdg_positioner_interface,
+ &xdg_toplevel_interface,
+ &wl_seat_interface,
+ NULL,
+ NULL,
+ NULL,
+ &wl_seat_interface,
+ NULL,
+ &wl_seat_interface,
+ NULL,
+ NULL,
+ &wl_output_interface,
+ &wl_seat_interface,
+ NULL,
+ &xdg_positioner_interface,
+ NULL,
+};
+
+static const struct wl_message xdg_wm_base_requests[] = {
+ { "destroy", "", xdg_shell_types + 0 },
+ { "create_positioner", "n", xdg_shell_types + 4 },
+ { "get_xdg_surface", "no", xdg_shell_types + 5 },
+ { "pong", "u", xdg_shell_types + 0 },
+};
+
+static const struct wl_message xdg_wm_base_events[] = {
+ { "ping", "u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
+ "xdg_wm_base", 3,
+ 4, xdg_wm_base_requests,
+ 1, xdg_wm_base_events,
+};
+
+static const struct wl_message xdg_positioner_requests[] = {
+ { "destroy", "", xdg_shell_types + 0 },
+ { "set_size", "ii", xdg_shell_types + 0 },
+ { "set_anchor_rect", "iiii", xdg_shell_types + 0 },
+ { "set_anchor", "u", xdg_shell_types + 0 },
+ { "set_gravity", "u", xdg_shell_types + 0 },
+ { "set_constraint_adjustment", "u", xdg_shell_types + 0 },
+ { "set_offset", "ii", xdg_shell_types + 0 },
+ { "set_reactive", "3", xdg_shell_types + 0 },
+ { "set_parent_size", "3ii", xdg_shell_types + 0 },
+ { "set_parent_configure", "3u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
+ "xdg_positioner", 3,
+ 10, xdg_positioner_requests,
+ 0, NULL,
+};
+
+static const struct wl_message xdg_surface_requests[] = {
+ { "destroy", "", xdg_shell_types + 0 },
+ { "get_toplevel", "n", xdg_shell_types + 7 },
+ { "get_popup", "n?oo", xdg_shell_types + 8 },
+ { "set_window_geometry", "iiii", xdg_shell_types + 0 },
+ { "ack_configure", "u", xdg_shell_types + 0 },
+};
+
+static const struct wl_message xdg_surface_events[] = {
+ { "configure", "u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_surface_interface = {
+ "xdg_surface", 3,
+ 5, xdg_surface_requests,
+ 1, xdg_surface_events,
+};
+
+static const struct wl_message xdg_toplevel_requests[] = {
+ { "destroy", "", xdg_shell_types + 0 },
+ { "set_parent", "?o", xdg_shell_types + 11 },
+ { "set_title", "s", xdg_shell_types + 0 },
+ { "set_app_id", "s", xdg_shell_types + 0 },
+ { "show_window_menu", "ouii", xdg_shell_types + 12 },
+ { "move", "ou", xdg_shell_types + 16 },
+ { "resize", "ouu", xdg_shell_types + 18 },
+ { "set_max_size", "ii", xdg_shell_types + 0 },
+ { "set_min_size", "ii", xdg_shell_types + 0 },
+ { "set_maximized", "", xdg_shell_types + 0 },
+ { "unset_maximized", "", xdg_shell_types + 0 },
+ { "set_fullscreen", "?o", xdg_shell_types + 21 },
+ { "unset_fullscreen", "", xdg_shell_types + 0 },
+ { "set_minimized", "", xdg_shell_types + 0 },
+};
+
+static const struct wl_message xdg_toplevel_events[] = {
+ { "configure", "iia", xdg_shell_types + 0 },
+ { "close", "", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
+ "xdg_toplevel", 3,
+ 14, xdg_toplevel_requests,
+ 2, xdg_toplevel_events,
+};
+
+static const struct wl_message xdg_popup_requests[] = {
+ { "destroy", "", xdg_shell_types + 0 },
+ { "grab", "ou", xdg_shell_types + 22 },
+ { "reposition", "3ou", xdg_shell_types + 24 },
+};
+
+static const struct wl_message xdg_popup_events[] = {
+ { "configure", "iiii", xdg_shell_types + 0 },
+ { "popup_done", "", xdg_shell_types + 0 },
+ { "repositioned", "3u", xdg_shell_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface xdg_popup_interface = {
+ "xdg_popup", 3,
+ 3, xdg_popup_requests,
+ 3, xdg_popup_events,
+};
+
diff --git a/src/cmd/wm/xdg-shell-protocol.h b/src/cmd/wm/xdg-shell-protocol.h
new file mode 100644
index 0000000..312e5b9
--- /dev/null
+++ b/src/cmd/wm/xdg-shell-protocol.h
@@ -0,0 +1,1676 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+#ifndef XDG_SHELL_SERVER_PROTOCOL_H
+#define XDG_SHELL_SERVER_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-server.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct wl_client;
+struct wl_resource;
+
+/**
+ * @page page_xdg_shell The xdg_shell protocol
+ * @section page_ifaces_xdg_shell Interfaces
+ * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces
+ * - @subpage page_iface_xdg_positioner - child surface positioner
+ * - @subpage page_iface_xdg_surface - desktop user interface surface base interface
+ * - @subpage page_iface_xdg_toplevel - toplevel surface
+ * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus
+ * @section page_copyright_xdg_shell Copyright
+ * <pre>
+ *
+ * Copyright © 2008-2013 Kristian Høgsberg
+ * Copyright © 2013 Rafael Antognolli
+ * Copyright © 2013 Jasper St. Pierre
+ * Copyright © 2010-2013 Intel Corporation
+ * Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ * Copyright © 2015-2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_output;
+struct wl_seat;
+struct wl_surface;
+struct xdg_popup;
+struct xdg_positioner;
+struct xdg_surface;
+struct xdg_toplevel;
+struct xdg_wm_base;
+
+#ifndef XDG_WM_BASE_INTERFACE
+#define XDG_WM_BASE_INTERFACE
+/**
+ * @page page_iface_xdg_wm_base xdg_wm_base
+ * @section page_iface_xdg_wm_base_desc Description
+ *
+ * The xdg_wm_base interface is exposed as a global object enabling clients
+ * to turn their wl_surfaces into windows in a desktop environment. It
+ * defines the basic functionality needed for clients and the compositor to
+ * create windows that can be dragged, resized, maximized, etc, as well as
+ * creating transient windows such as popup menus.
+ * @section page_iface_xdg_wm_base_api API
+ * See @ref iface_xdg_wm_base.
+ */
+/**
+ * @defgroup iface_xdg_wm_base The xdg_wm_base interface
+ *
+ * The xdg_wm_base interface is exposed as a global object enabling clients
+ * to turn their wl_surfaces into windows in a desktop environment. It
+ * defines the basic functionality needed for clients and the compositor to
+ * create windows that can be dragged, resized, maximized, etc, as well as
+ * creating transient windows such as popup menus.
+ */
+extern const struct wl_interface xdg_wm_base_interface;
+#endif
+#ifndef XDG_POSITIONER_INTERFACE
+#define XDG_POSITIONER_INTERFACE
+/**
+ * @page page_iface_xdg_positioner xdg_positioner
+ * @section page_iface_xdg_positioner_desc Description
+ *
+ * The xdg_positioner provides a collection of rules for the placement of a
+ * child surface relative to a parent surface. Rules can be defined to ensure
+ * the child surface remains within the visible area's borders, and to
+ * specify how the child surface changes its position, such as sliding along
+ * an axis, or flipping around a rectangle. These positioner-created rules are
+ * constrained by the requirement that a child surface must intersect with or
+ * be at least partially adjacent to its parent surface.
+ *
+ * See the various requests for details about possible rules.
+ *
+ * At the time of the request, the compositor makes a copy of the rules
+ * specified by the xdg_positioner. Thus, after the request is complete the
+ * xdg_positioner object can be destroyed or reused; further changes to the
+ * object will have no effect on previous usages.
+ *
+ * For an xdg_positioner object to be considered complete, it must have a
+ * non-zero size set by set_size, and a non-zero anchor rectangle set by
+ * set_anchor_rect. Passing an incomplete xdg_positioner object when
+ * positioning a surface raises an error.
+ * @section page_iface_xdg_positioner_api API
+ * See @ref iface_xdg_positioner.
+ */
+/**
+ * @defgroup iface_xdg_positioner The xdg_positioner interface
+ *
+ * The xdg_positioner provides a collection of rules for the placement of a
+ * child surface relative to a parent surface. Rules can be defined to ensure
+ * the child surface remains within the visible area's borders, and to
+ * specify how the child surface changes its position, such as sliding along
+ * an axis, or flipping around a rectangle. These positioner-created rules are
+ * constrained by the requirement that a child surface must intersect with or
+ * be at least partially adjacent to its parent surface.
+ *
+ * See the various requests for details about possible rules.
+ *
+ * At the time of the request, the compositor makes a copy of the rules
+ * specified by the xdg_positioner. Thus, after the request is complete the
+ * xdg_positioner object can be destroyed or reused; further changes to the
+ * object will have no effect on previous usages.
+ *
+ * For an xdg_positioner object to be considered complete, it must have a
+ * non-zero size set by set_size, and a non-zero anchor rectangle set by
+ * set_anchor_rect. Passing an incomplete xdg_positioner object when
+ * positioning a surface raises an error.
+ */
+extern const struct wl_interface xdg_positioner_interface;
+#endif
+#ifndef XDG_SURFACE_INTERFACE
+#define XDG_SURFACE_INTERFACE
+/**
+ * @page page_iface_xdg_surface xdg_surface
+ * @section page_iface_xdg_surface_desc Description
+ *
+ * An interface that may be implemented by a wl_surface, for
+ * implementations that provide a desktop-style user interface.
+ *
+ * It provides a base set of functionality required to construct user
+ * interface elements requiring management by the compositor, such as
+ * toplevel windows, menus, etc. The types of functionality are split into
+ * xdg_surface roles.
+ *
+ * Creating an xdg_surface does not set the role for a wl_surface. In order
+ * to map an xdg_surface, the client must create a role-specific object
+ * using, e.g., get_toplevel, get_popup. The wl_surface for any given
+ * xdg_surface can have at most one role, and may not be assigned any role
+ * not based on xdg_surface.
+ *
+ * A role must be assigned before any other requests are made to the
+ * xdg_surface object.
+ *
+ * The client must call wl_surface.commit on the corresponding wl_surface
+ * for the xdg_surface state to take effect.
+ *
+ * Creating an xdg_surface from a wl_surface which has a buffer attached or
+ * committed is a client error, and any attempts by a client to attach or
+ * manipulate a buffer prior to the first xdg_surface.configure call must
+ * also be treated as errors.
+ *
+ * After creating a role-specific object and setting it up, the client must
+ * perform an initial commit without any buffer attached. The compositor
+ * will reply with an xdg_surface.configure event. The client must
+ * acknowledge it and is then allowed to attach a buffer to map the surface.
+ *
+ * Mapping an xdg_surface-based role surface is defined as making it
+ * possible for the surface to be shown by the compositor. Note that
+ * a mapped surface is not guaranteed to be visible once it is mapped.
+ *
+ * For an xdg_surface to be mapped by the compositor, the following
+ * conditions must be met:
+ * (1) the client has assigned an xdg_surface-based role to the surface
+ * (2) the client has set and committed the xdg_surface state and the
+ * role-dependent state to the surface
+ * (3) the client has committed a buffer to the surface
+ *
+ * A newly-unmapped surface is considered to have met condition (1) out
+ * of the 3 required conditions for mapping a surface if its role surface
+ * has not been destroyed.
+ * @section page_iface_xdg_surface_api API
+ * See @ref iface_xdg_surface.
+ */
+/**
+ * @defgroup iface_xdg_surface The xdg_surface interface
+ *
+ * An interface that may be implemented by a wl_surface, for
+ * implementations that provide a desktop-style user interface.
+ *
+ * It provides a base set of functionality required to construct user
+ * interface elements requiring management by the compositor, such as
+ * toplevel windows, menus, etc. The types of functionality are split into
+ * xdg_surface roles.
+ *
+ * Creating an xdg_surface does not set the role for a wl_surface. In order
+ * to map an xdg_surface, the client must create a role-specific object
+ * using, e.g., get_toplevel, get_popup. The wl_surface for any given
+ * xdg_surface can have at most one role, and may not be assigned any role
+ * not based on xdg_surface.
+ *
+ * A role must be assigned before any other requests are made to the
+ * xdg_surface object.
+ *
+ * The client must call wl_surface.commit on the corresponding wl_surface
+ * for the xdg_surface state to take effect.
+ *
+ * Creating an xdg_surface from a wl_surface which has a buffer attached or
+ * committed is a client error, and any attempts by a client to attach or
+ * manipulate a buffer prior to the first xdg_surface.configure call must
+ * also be treated as errors.
+ *
+ * After creating a role-specific object and setting it up, the client must
+ * perform an initial commit without any buffer attached. The compositor
+ * will reply with an xdg_surface.configure event. The client must
+ * acknowledge it and is then allowed to attach a buffer to map the surface.
+ *
+ * Mapping an xdg_surface-based role surface is defined as making it
+ * possible for the surface to be shown by the compositor. Note that
+ * a mapped surface is not guaranteed to be visible once it is mapped.
+ *
+ * For an xdg_surface to be mapped by the compositor, the following
+ * conditions must be met:
+ * (1) the client has assigned an xdg_surface-based role to the surface
+ * (2) the client has set and committed the xdg_surface state and the
+ * role-dependent state to the surface
+ * (3) the client has committed a buffer to the surface
+ *
+ * A newly-unmapped surface is considered to have met condition (1) out
+ * of the 3 required conditions for mapping a surface if its role surface
+ * has not been destroyed.
+ */
+extern const struct wl_interface xdg_surface_interface;
+#endif
+#ifndef XDG_TOPLEVEL_INTERFACE
+#define XDG_TOPLEVEL_INTERFACE
+/**
+ * @page page_iface_xdg_toplevel xdg_toplevel
+ * @section page_iface_xdg_toplevel_desc Description
+ *
+ * This interface defines an xdg_surface role which allows a surface to,
+ * among other things, set window-like properties such as maximize,
+ * fullscreen, and minimize, set application-specific metadata like title and
+ * id, and well as trigger user interactive operations such as interactive
+ * resize and move.
+ *
+ * Unmapping an xdg_toplevel means that the surface cannot be shown
+ * by the compositor until it is explicitly mapped again.
+ * All active operations (e.g., move, resize) are canceled and all
+ * attributes (e.g. title, state, stacking, ...) are discarded for
+ * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
+ * the state it had right after xdg_surface.get_toplevel. The client
+ * can re-map the toplevel by perfoming a commit without any buffer
+ * attached, waiting for a configure event and handling it as usual (see
+ * xdg_surface description).
+ *
+ * Attaching a null buffer to a toplevel unmaps the surface.
+ * @section page_iface_xdg_toplevel_api API
+ * See @ref iface_xdg_toplevel.
+ */
+/**
+ * @defgroup iface_xdg_toplevel The xdg_toplevel interface
+ *
+ * This interface defines an xdg_surface role which allows a surface to,
+ * among other things, set window-like properties such as maximize,
+ * fullscreen, and minimize, set application-specific metadata like title and
+ * id, and well as trigger user interactive operations such as interactive
+ * resize and move.
+ *
+ * Unmapping an xdg_toplevel means that the surface cannot be shown
+ * by the compositor until it is explicitly mapped again.
+ * All active operations (e.g., move, resize) are canceled and all
+ * attributes (e.g. title, state, stacking, ...) are discarded for
+ * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
+ * the state it had right after xdg_surface.get_toplevel. The client
+ * can re-map the toplevel by perfoming a commit without any buffer
+ * attached, waiting for a configure event and handling it as usual (see
+ * xdg_surface description).
+ *
+ * Attaching a null buffer to a toplevel unmaps the surface.
+ */
+extern const struct wl_interface xdg_toplevel_interface;
+#endif
+#ifndef XDG_POPUP_INTERFACE
+#define XDG_POPUP_INTERFACE
+/**
+ * @page page_iface_xdg_popup xdg_popup
+ * @section page_iface_xdg_popup_desc Description
+ *
+ * A popup surface is a short-lived, temporary surface. It can be used to
+ * implement for example menus, popovers, tooltips and other similar user
+ * interface concepts.
+ *
+ * A popup can be made to take an explicit grab. See xdg_popup.grab for
+ * details.
+ *
+ * When the popup is dismissed, a popup_done event will be sent out, and at
+ * the same time the surface will be unmapped. See the xdg_popup.popup_done
+ * event for details.
+ *
+ * Explicitly destroying the xdg_popup object will also dismiss the popup and
+ * unmap the surface. Clients that want to dismiss the popup when another
+ * surface of their own is clicked should dismiss the popup using the destroy
+ * request.
+ *
+ * A newly created xdg_popup will be stacked on top of all previously created
+ * xdg_popup surfaces associated with the same xdg_toplevel.
+ *
+ * The parent of an xdg_popup must be mapped (see the xdg_surface
+ * description) before the xdg_popup itself.
+ *
+ * The client must call wl_surface.commit on the corresponding wl_surface
+ * for the xdg_popup state to take effect.
+ * @section page_iface_xdg_popup_api API
+ * See @ref iface_xdg_popup.
+ */
+/**
+ * @defgroup iface_xdg_popup The xdg_popup interface
+ *
+ * A popup surface is a short-lived, temporary surface. It can be used to
+ * implement for example menus, popovers, tooltips and other similar user
+ * interface concepts.
+ *
+ * A popup can be made to take an explicit grab. See xdg_popup.grab for
+ * details.
+ *
+ * When the popup is dismissed, a popup_done event will be sent out, and at
+ * the same time the surface will be unmapped. See the xdg_popup.popup_done
+ * event for details.
+ *
+ * Explicitly destroying the xdg_popup object will also dismiss the popup and
+ * unmap the surface. Clients that want to dismiss the popup when another
+ * surface of their own is clicked should dismiss the popup using the destroy
+ * request.
+ *
+ * A newly created xdg_popup will be stacked on top of all previously created
+ * xdg_popup surfaces associated with the same xdg_toplevel.
+ *
+ * The parent of an xdg_popup must be mapped (see the xdg_surface
+ * description) before the xdg_popup itself.
+ *
+ * The client must call wl_surface.commit on the corresponding wl_surface
+ * for the xdg_popup state to take effect.
+ */
+extern const struct wl_interface xdg_popup_interface;
+#endif
+
+#ifndef XDG_WM_BASE_ERROR_ENUM
+#define XDG_WM_BASE_ERROR_ENUM
+enum xdg_wm_base_error {
+ /**
+ * given wl_surface has another role
+ */
+ XDG_WM_BASE_ERROR_ROLE = 0,
+ /**
+ * xdg_wm_base was destroyed before children
+ */
+ XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1,
+ /**
+ * the client tried to map or destroy a non-topmost popup
+ */
+ XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2,
+ /**
+ * the client specified an invalid popup parent surface
+ */
+ XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3,
+ /**
+ * the client provided an invalid surface state
+ */
+ XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4,
+ /**
+ * the client provided an invalid positioner
+ */
+ XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5,
+};
+#endif /* XDG_WM_BASE_ERROR_ENUM */
+
+/**
+ * @ingroup iface_xdg_wm_base
+ * @struct xdg_wm_base_interface
+ */
+struct xdg_wm_base_interface {
+ /**
+ * destroy xdg_wm_base
+ *
+ * Destroy this xdg_wm_base object.
+ *
+ * Destroying a bound xdg_wm_base object while there are surfaces
+ * still alive created by this xdg_wm_base object instance is
+ * illegal and will result in a protocol error.
+ */
+ void (*destroy)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * create a positioner object
+ *
+ * Create a positioner object. A positioner object is used to
+ * position surfaces relative to some parent surface. See the
+ * interface description and xdg_surface.get_popup for details.
+ */
+ void (*create_positioner)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id);
+ /**
+ * create a shell surface from a surface
+ *
+ * This creates an xdg_surface for the given surface. While
+ * xdg_surface itself is not a role, the corresponding surface may
+ * only be assigned a role extending xdg_surface, such as
+ * xdg_toplevel or xdg_popup. It is illegal to create an
+ * xdg_surface for a wl_surface which already has an assigned role
+ * and this will result in a protocol error.
+ *
+ * This creates an xdg_surface for the given surface. An
+ * xdg_surface is used as basis to define a role to a given
+ * surface, such as xdg_toplevel or xdg_popup. It also manages
+ * functionality shared between xdg_surface based surface roles.
+ *
+ * See the documentation of xdg_surface for more details about what
+ * an xdg_surface is and how it is used.
+ */
+ void (*get_xdg_surface)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ struct wl_resource *surface);
+ /**
+ * respond to a ping event
+ *
+ * A client must respond to a ping event with a pong request or
+ * the client may be deemed unresponsive. See xdg_wm_base.ping.
+ * @param serial serial of the ping event
+ */
+ void (*pong)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t serial);
+};
+
+#define XDG_WM_BASE_PING 0
+
+/**
+ * @ingroup iface_xdg_wm_base
+ */
+#define XDG_WM_BASE_PING_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_xdg_wm_base
+ */
+#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_wm_base
+ */
+#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_wm_base
+ */
+#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_wm_base
+ */
+#define XDG_WM_BASE_PONG_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_xdg_wm_base
+ * Sends an ping event to the client owning the resource.
+ * @param resource_ The client's resource
+ * @param serial pass this to the pong request
+ */
+static inline void
+xdg_wm_base_send_ping(struct wl_resource *resource_, uint32_t serial)
+{
+ wl_resource_post_event(resource_, XDG_WM_BASE_PING, serial);
+}
+
+#ifndef XDG_POSITIONER_ERROR_ENUM
+#define XDG_POSITIONER_ERROR_ENUM
+enum xdg_positioner_error {
+ /**
+ * invalid input provided
+ */
+ XDG_POSITIONER_ERROR_INVALID_INPUT = 0,
+};
+#endif /* XDG_POSITIONER_ERROR_ENUM */
+
+#ifndef XDG_POSITIONER_ANCHOR_ENUM
+#define XDG_POSITIONER_ANCHOR_ENUM
+enum xdg_positioner_anchor {
+ XDG_POSITIONER_ANCHOR_NONE = 0,
+ XDG_POSITIONER_ANCHOR_TOP = 1,
+ XDG_POSITIONER_ANCHOR_BOTTOM = 2,
+ XDG_POSITIONER_ANCHOR_LEFT = 3,
+ XDG_POSITIONER_ANCHOR_RIGHT = 4,
+ XDG_POSITIONER_ANCHOR_TOP_LEFT = 5,
+ XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6,
+ XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7,
+ XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8,
+};
+#endif /* XDG_POSITIONER_ANCHOR_ENUM */
+
+#ifndef XDG_POSITIONER_GRAVITY_ENUM
+#define XDG_POSITIONER_GRAVITY_ENUM
+enum xdg_positioner_gravity {
+ XDG_POSITIONER_GRAVITY_NONE = 0,
+ XDG_POSITIONER_GRAVITY_TOP = 1,
+ XDG_POSITIONER_GRAVITY_BOTTOM = 2,
+ XDG_POSITIONER_GRAVITY_LEFT = 3,
+ XDG_POSITIONER_GRAVITY_RIGHT = 4,
+ XDG_POSITIONER_GRAVITY_TOP_LEFT = 5,
+ XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6,
+ XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7,
+ XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8,
+};
+#endif /* XDG_POSITIONER_GRAVITY_ENUM */
+
+#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM
+#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM
+/**
+ * @ingroup iface_xdg_positioner
+ * vertically resize the surface
+ *
+ * Resize the surface vertically so that it is completely unconstrained.
+ */
+enum xdg_positioner_constraint_adjustment {
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0,
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1,
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2,
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4,
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8,
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16,
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32,
+};
+#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */
+
+/**
+ * @ingroup iface_xdg_positioner
+ * @struct xdg_positioner_interface
+ */
+struct xdg_positioner_interface {
+ /**
+ * destroy the xdg_positioner object
+ *
+ * Notify the compositor that the xdg_positioner will no longer
+ * be used.
+ */
+ void (*destroy)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * set the size of the to-be positioned rectangle
+ *
+ * Set the size of the surface that is to be positioned with the
+ * positioner object. The size is in surface-local coordinates and
+ * corresponds to the window geometry. See
+ * xdg_surface.set_window_geometry.
+ *
+ * If a zero or negative size is set the invalid_input error is
+ * raised.
+ * @param width width of positioned rectangle
+ * @param height height of positioned rectangle
+ */
+ void (*set_size)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t width,
+ int32_t height);
+ /**
+ * set the anchor rectangle within the parent surface
+ *
+ * Specify the anchor rectangle within the parent surface that
+ * the child surface will be placed relative to. The rectangle is
+ * relative to the window geometry as defined by
+ * xdg_surface.set_window_geometry of the parent surface.
+ *
+ * When the xdg_positioner object is used to position a child
+ * surface, the anchor rectangle may not extend outside the window
+ * geometry of the positioned child's parent surface.
+ *
+ * If a negative size is set the invalid_input error is raised.
+ * @param x x position of anchor rectangle
+ * @param y y position of anchor rectangle
+ * @param width width of anchor rectangle
+ * @param height height of anchor rectangle
+ */
+ void (*set_anchor_rect)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t x,
+ int32_t y,
+ int32_t width,
+ int32_t height);
+ /**
+ * set anchor rectangle anchor
+ *
+ * Defines the anchor point for the anchor rectangle. The
+ * specified anchor is used derive an anchor point that the child
+ * surface will be positioned relative to. If a corner anchor is
+ * set (e.g. 'top_left' or 'bottom_right'), the anchor point will
+ * be at the specified corner; otherwise, the derived anchor point
+ * will be centered on the specified edge, or in the center of the
+ * anchor rectangle if no edge is specified.
+ * @param anchor anchor
+ */
+ void (*set_anchor)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t anchor);
+ /**
+ * set child surface gravity
+ *
+ * Defines in what direction a surface should be positioned,
+ * relative to the anchor point of the parent surface. If a corner
+ * gravity is specified (e.g. 'bottom_right' or 'top_left'), then
+ * the child surface will be placed towards the specified gravity;
+ * otherwise, the child surface will be centered over the anchor
+ * point on any axis that had no gravity specified.
+ * @param gravity gravity direction
+ */
+ void (*set_gravity)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t gravity);
+ /**
+ * set the adjustment to be done when constrained
+ *
+ * Specify how the window should be positioned if the originally
+ * intended position caused the surface to be constrained, meaning
+ * at least partially outside positioning boundaries set by the
+ * compositor. The adjustment is set by constructing a bitmask
+ * describing the adjustment to be made when the surface is
+ * constrained on that axis.
+ *
+ * If no bit for one axis is set, the compositor will assume that
+ * the child surface should not change its position on that axis
+ * when constrained.
+ *
+ * If more than one bit for one axis is set, the order of how
+ * adjustments are applied is specified in the corresponding
+ * adjustment descriptions.
+ *
+ * The default adjustment is none.
+ * @param constraint_adjustment bit mask of constraint adjustments
+ */
+ void (*set_constraint_adjustment)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t constraint_adjustment);
+ /**
+ * set surface position offset
+ *
+ * Specify the surface position offset relative to the position
+ * of the anchor on the anchor rectangle and the anchor on the
+ * surface. For example if the anchor of the anchor rectangle is at
+ * (x, y), the surface has the gravity bottom|right, and the offset
+ * is (ox, oy), the calculated surface position will be (x + ox, y
+ * + oy). The offset position of the surface is the one used for
+ * constraint testing. See set_constraint_adjustment.
+ *
+ * An example use case is placing a popup menu on top of a user
+ * interface element, while aligning the user interface element of
+ * the parent surface with some user interface element placed
+ * somewhere in the popup surface.
+ * @param x surface position x offset
+ * @param y surface position y offset
+ */
+ void (*set_offset)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t x,
+ int32_t y);
+ /**
+ * continuously reconstrain the surface
+ *
+ * When set reactive, the surface is reconstrained if the
+ * conditions used for constraining changed, e.g. the parent window
+ * moved.
+ *
+ * If the conditions changed and the popup was reconstrained, an
+ * xdg_popup.configure event is sent with updated geometry,
+ * followed by an xdg_surface.configure event.
+ * @since 3
+ */
+ void (*set_reactive)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ *
+ *
+ * Set the parent window geometry the compositor should use when
+ * positioning the popup. The compositor may use this information
+ * to determine the future state the popup should be constrained
+ * using. If this doesn't match the dimension of the parent the
+ * popup is eventually positioned against, the behavior is
+ * undefined.
+ *
+ * The arguments are given in the surface-local coordinate space.
+ * @param parent_width future window geometry width of parent
+ * @param parent_height future window geometry height of parent
+ * @since 3
+ */
+ void (*set_parent_size)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t parent_width,
+ int32_t parent_height);
+ /**
+ * set parent configure this is a response to
+ *
+ * Set the serial of an xdg_surface.configure event this
+ * positioner will be used in response to. The compositor may use
+ * this information together with set_parent_size to determine what
+ * future state the popup should be constrained using.
+ * @param serial serial of parent configure event
+ * @since 3
+ */
+ void (*set_parent_configure)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t serial);
+};
+
+
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3
+/**
+ * @ingroup iface_xdg_positioner
+ */
+#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3
+
+#ifndef XDG_SURFACE_ERROR_ENUM
+#define XDG_SURFACE_ERROR_ENUM
+enum xdg_surface_error {
+ XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1,
+ XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2,
+ XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3,
+};
+#endif /* XDG_SURFACE_ERROR_ENUM */
+
+/**
+ * @ingroup iface_xdg_surface
+ * @struct xdg_surface_interface
+ */
+struct xdg_surface_interface {
+ /**
+ * destroy the xdg_surface
+ *
+ * Destroy the xdg_surface object. An xdg_surface must only be
+ * destroyed after its role object has been destroyed.
+ */
+ void (*destroy)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * assign the xdg_toplevel surface role
+ *
+ * This creates an xdg_toplevel object for the given xdg_surface
+ * and gives the associated wl_surface the xdg_toplevel role.
+ *
+ * See the documentation of xdg_toplevel for more details about
+ * what an xdg_toplevel is and how it is used.
+ */
+ void (*get_toplevel)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id);
+ /**
+ * assign the xdg_popup surface role
+ *
+ * This creates an xdg_popup object for the given xdg_surface and
+ * gives the associated wl_surface the xdg_popup role.
+ *
+ * If null is passed as a parent, a parent surface must be
+ * specified using some other protocol, before committing the
+ * initial state.
+ *
+ * See the documentation of xdg_popup for more details about what
+ * an xdg_popup is and how it is used.
+ */
+ void (*get_popup)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ struct wl_resource *parent,
+ struct wl_resource *positioner);
+ /**
+ * set the new window geometry
+ *
+ * The window geometry of a surface is its "visible bounds" from
+ * the user's perspective. Client-side decorations often have
+ * invisible portions like drop-shadows which should be ignored for
+ * the purposes of aligning, placing and constraining windows.
+ *
+ * The window geometry is double buffered, and will be applied at
+ * the time wl_surface.commit of the corresponding wl_surface is
+ * called.
+ *
+ * When maintaining a position, the compositor should treat the (x,
+ * y) coordinate of the window geometry as the top left corner of
+ * the window. A client changing the (x, y) window geometry
+ * coordinate should in general not alter the position of the
+ * window.
+ *
+ * Once the window geometry of the surface is set, it is not
+ * possible to unset it, and it will remain the same until
+ * set_window_geometry is called again, even if a new subsurface or
+ * buffer is attached.
+ *
+ * If never set, the value is the full bounds of the surface,
+ * including any subsurfaces. This updates dynamically on every
+ * commit. This unset is meant for extremely simple clients.
+ *
+ * The arguments are given in the surface-local coordinate space of
+ * the wl_surface associated with this xdg_surface.
+ *
+ * The width and height must be greater than zero. Setting an
+ * invalid size will raise an error. When applied, the effective
+ * window geometry will be the set window geometry clamped to the
+ * bounding rectangle of the combined geometry of the surface of
+ * the xdg_surface and the associated subsurfaces.
+ */
+ void (*set_window_geometry)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t x,
+ int32_t y,
+ int32_t width,
+ int32_t height);
+ /**
+ * ack a configure event
+ *
+ * When a configure event is received, if a client commits the
+ * surface in response to the configure event, then the client must
+ * make an ack_configure request sometime before the commit
+ * request, passing along the serial of the configure event.
+ *
+ * For instance, for toplevel surfaces the compositor might use
+ * this information to move a surface to the top left only when the
+ * client has drawn itself for the maximized or fullscreen state.
+ *
+ * If the client receives multiple configure events before it can
+ * respond to one, it only has to ack the last configure event.
+ *
+ * A client is not required to commit immediately after sending an
+ * ack_configure request - it may even ack_configure several times
+ * before its next surface commit.
+ *
+ * A client may send multiple ack_configure requests before
+ * committing, but only the last request sent before a commit
+ * indicates which configure event the client really is responding
+ * to.
+ * @param serial the serial from the configure event
+ */
+ void (*ack_configure)(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t serial);
+};
+
+#define XDG_SURFACE_CONFIGURE 0
+
+/**
+ * @ingroup iface_xdg_surface
+ */
+#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_xdg_surface
+ */
+#define XDG_SURFACE_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_surface
+ */
+#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_surface
+ */
+#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_surface
+ */
+#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_surface
+ */
+#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_xdg_surface
+ * Sends an configure event to the client owning the resource.
+ * @param resource_ The client's resource
+ * @param serial serial of the configure event
+ */
+static inline void
+xdg_surface_send_configure(struct wl_resource *resource_, uint32_t serial)
+{
+ wl_resource_post_event(resource_, XDG_SURFACE_CONFIGURE, serial);
+}
+
+#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM
+#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM
+/**
+ * @ingroup iface_xdg_toplevel
+ * edge values for resizing
+ *
+ * These values are used to indicate which edge of a surface
+ * is being dragged in a resize operation.
+ */
+enum xdg_toplevel_resize_edge {
+ XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0,
+ XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1,
+ XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2,
+ XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4,
+ XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5,
+ XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6,
+ XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8,
+ XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9,
+ XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10,
+};
+#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */
+
+#ifndef XDG_TOPLEVEL_STATE_ENUM
+#define XDG_TOPLEVEL_STATE_ENUM
+/**
+ * @ingroup iface_xdg_toplevel
+ * the surface is tiled
+ *
+ * The window is currently in a tiled layout and the bottom edge is
+ * considered to be adjacent to another part of the tiling grid.
+ */
+enum xdg_toplevel_state {
+ /**
+ * the surface is maximized
+ */
+ XDG_TOPLEVEL_STATE_MAXIMIZED = 1,
+ /**
+ * the surface is fullscreen
+ */
+ XDG_TOPLEVEL_STATE_FULLSCREEN = 2,
+ /**
+ * the surface is being resized
+ */
+ XDG_TOPLEVEL_STATE_RESIZING = 3,
+ /**
+ * the surface is now activated
+ */
+ XDG_TOPLEVEL_STATE_ACTIVATED = 4,
+ /**
+ * @since 2
+ */
+ XDG_TOPLEVEL_STATE_TILED_LEFT = 5,
+ /**
+ * @since 2
+ */
+ XDG_TOPLEVEL_STATE_TILED_RIGHT = 6,
+ /**
+ * @since 2
+ */
+ XDG_TOPLEVEL_STATE_TILED_TOP = 7,
+ /**
+ * @since 2
+ */
+ XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8,
+};
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2
+#endif /* XDG_TOPLEVEL_STATE_ENUM */
+
+/**
+ * @ingroup iface_xdg_toplevel
+ * @struct xdg_toplevel_interface
+ */
+struct xdg_toplevel_interface {
+ /**
+ * destroy the xdg_toplevel
+ *
+ * This request destroys the role surface and unmaps the surface;
+ * see "Unmapping" behavior in interface section for details.
+ */
+ void (*destroy)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * set the parent of this surface
+ *
+ * Set the "parent" of this surface. This surface should be
+ * stacked above the parent surface and all other ancestor
+ * surfaces.
+ *
+ * Parent windows should be set on dialogs, toolboxes, or other
+ * "auxiliary" surfaces, so that the parent is raised when the
+ * dialog is raised.
+ *
+ * Setting a null parent for a child window removes any
+ * parent-child relationship for the child. Setting a null parent
+ * for a window which currently has no parent is a no-op.
+ *
+ * If the parent is unmapped then its children are managed as
+ * though the parent of the now-unmapped parent has become the
+ * parent of this surface. If no parent exists for the now-unmapped
+ * parent then the children are managed as though they have no
+ * parent surface.
+ */
+ void (*set_parent)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *parent);
+ /**
+ * set surface title
+ *
+ * Set a short title for the surface.
+ *
+ * This string may be used to identify the surface in a task bar,
+ * window list, or other user interface elements provided by the
+ * compositor.
+ *
+ * The string must be encoded in UTF-8.
+ */
+ void (*set_title)(struct wl_client *client,
+ struct wl_resource *resource,
+ const char *title);
+ /**
+ * set application ID
+ *
+ * Set an application identifier for the surface.
+ *
+ * The app ID identifies the general class of applications to which
+ * the surface belongs. The compositor can use this to group
+ * multiple surfaces together, or to determine how to launch a new
+ * application.
+ *
+ * For D-Bus activatable applications, the app ID is used as the
+ * D-Bus service name.
+ *
+ * The compositor shell will try to group application surfaces
+ * together by their app ID. As a best practice, it is suggested to
+ * select app ID's that match the basename of the application's
+ * .desktop file. For example, "org.freedesktop.FooViewer" where
+ * the .desktop file is "org.freedesktop.FooViewer.desktop".
+ *
+ * Like other properties, a set_app_id request can be sent after
+ * the xdg_toplevel has been mapped to update the property.
+ *
+ * See the desktop-entry specification [0] for more details on
+ * application identifiers and how they relate to well-known D-Bus
+ * names and .desktop files.
+ *
+ * [0] http://standards.freedesktop.org/desktop-entry-spec/
+ */
+ void (*set_app_id)(struct wl_client *client,
+ struct wl_resource *resource,
+ const char *app_id);
+ /**
+ * show the window menu
+ *
+ * Clients implementing client-side decorations might want to
+ * show a context menu when right-clicking on the decorations,
+ * giving the user a menu that they can use to maximize or minimize
+ * the window.
+ *
+ * This request asks the compositor to pop up such a window menu at
+ * the given position, relative to the local surface coordinates of
+ * the parent surface. There are no guarantees as to what menu
+ * items the window menu contains.
+ *
+ * This request must be used in response to some sort of user
+ * action like a button press, key press, or touch down event.
+ * @param seat the wl_seat of the user event
+ * @param serial the serial of the user event
+ * @param x the x position to pop up the window menu at
+ * @param y the y position to pop up the window menu at
+ */
+ void (*show_window_menu)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *seat,
+ uint32_t serial,
+ int32_t x,
+ int32_t y);
+ /**
+ * start an interactive move
+ *
+ * Start an interactive, user-driven move of the surface.
+ *
+ * This request must be used in response to some sort of user
+ * action like a button press, key press, or touch down event. The
+ * passed serial is used to determine the type of interactive move
+ * (touch, pointer, etc).
+ *
+ * The server may ignore move requests depending on the state of
+ * the surface (e.g. fullscreen or maximized), or if the passed
+ * serial is no longer valid.
+ *
+ * If triggered, the surface will lose the focus of the device
+ * (wl_pointer, wl_touch, etc) used for the move. It is up to the
+ * compositor to visually indicate that the move is taking place,
+ * such as updating a pointer cursor, during the move. There is no
+ * guarantee that the device focus will return when the move is
+ * completed.
+ * @param seat the wl_seat of the user event
+ * @param serial the serial of the user event
+ */
+ void (*move)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *seat,
+ uint32_t serial);
+ /**
+ * start an interactive resize
+ *
+ * Start a user-driven, interactive resize of the surface.
+ *
+ * This request must be used in response to some sort of user
+ * action like a button press, key press, or touch down event. The
+ * passed serial is used to determine the type of interactive
+ * resize (touch, pointer, etc).
+ *
+ * The server may ignore resize requests depending on the state of
+ * the surface (e.g. fullscreen or maximized).
+ *
+ * If triggered, the client will receive configure events with the
+ * "resize" state enum value and the expected sizes. See the
+ * "resize" enum value for more details about what is required. The
+ * client must also acknowledge configure events using
+ * "ack_configure". After the resize is completed, the client will
+ * receive another "configure" event without the resize state.
+ *
+ * If triggered, the surface also will lose the focus of the device
+ * (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+ * compositor to visually indicate that the resize is taking place,
+ * such as updating a pointer cursor, during the resize. There is
+ * no guarantee that the device focus will return when the resize
+ * is completed.
+ *
+ * The edges parameter specifies how the surface should be resized,
+ * and is one of the values of the resize_edge enum. The compositor
+ * may use this information to update the surface position for
+ * example when dragging the top left corner. The compositor may
+ * also use this information to adapt its behavior, e.g. choose an
+ * appropriate cursor image.
+ * @param seat the wl_seat of the user event
+ * @param serial the serial of the user event
+ * @param edges which edge or corner is being dragged
+ */
+ void (*resize)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *seat,
+ uint32_t serial,
+ uint32_t edges);
+ /**
+ * set the maximum size
+ *
+ * Set a maximum size for the window.
+ *
+ * The client can specify a maximum size so that the compositor
+ * does not try to configure the window beyond this size.
+ *
+ * The width and height arguments are in window geometry
+ * coordinates. See xdg_surface.set_window_geometry.
+ *
+ * Values set in this way are double-buffered. They will get
+ * applied on the next commit.
+ *
+ * The compositor can use this information to allow or disallow
+ * different states like maximize or fullscreen and draw accurate
+ * animations.
+ *
+ * Similarly, a tiling window manager may use this information to
+ * place and resize client windows in a more effective way.
+ *
+ * The client should not rely on the compositor to obey the maximum
+ * size. The compositor may decide to ignore the values set by the
+ * client and request a larger size.
+ *
+ * If never set, or a value of zero in the request, means that the
+ * client has no expected maximum size in the given dimension. As a
+ * result, a client wishing to reset the maximum size to an
+ * unspecified state can use zero for width and height in the
+ * request.
+ *
+ * Requesting a maximum size to be smaller than the minimum size of
+ * a surface is illegal and will result in a protocol error.
+ *
+ * The width and height must be greater than or equal to zero.
+ * Using strictly negative values for width and height will result
+ * in a protocol error.
+ */
+ void (*set_max_size)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t width,
+ int32_t height);
+ /**
+ * set the minimum size
+ *
+ * Set a minimum size for the window.
+ *
+ * The client can specify a minimum size so that the compositor
+ * does not try to configure the window below this size.
+ *
+ * The width and height arguments are in window geometry
+ * coordinates. See xdg_surface.set_window_geometry.
+ *
+ * Values set in this way are double-buffered. They will get
+ * applied on the next commit.
+ *
+ * The compositor can use this information to allow or disallow
+ * different states like maximize or fullscreen and draw accurate
+ * animations.
+ *
+ * Similarly, a tiling window manager may use this information to
+ * place and resize client windows in a more effective way.
+ *
+ * The client should not rely on the compositor to obey the minimum
+ * size. The compositor may decide to ignore the values set by the
+ * client and request a smaller size.
+ *
+ * If never set, or a value of zero in the request, means that the
+ * client has no expected minimum size in the given dimension. As a
+ * result, a client wishing to reset the minimum size to an
+ * unspecified state can use zero for width and height in the
+ * request.
+ *
+ * Requesting a minimum size to be larger than the maximum size of
+ * a surface is illegal and will result in a protocol error.
+ *
+ * The width and height must be greater than or equal to zero.
+ * Using strictly negative values for width and height will result
+ * in a protocol error.
+ */
+ void (*set_min_size)(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t width,
+ int32_t height);
+ /**
+ * maximize the window
+ *
+ * Maximize the surface.
+ *
+ * After requesting that the surface should be maximized, the
+ * compositor will respond by emitting a configure event. Whether
+ * this configure actually sets the window maximized is subject to
+ * compositor policies. The client must then update its content,
+ * drawing in the configured state. The client must also
+ * acknowledge the configure when committing the new content (see
+ * ack_configure).
+ *
+ * It is up to the compositor to decide how and where to maximize
+ * the surface, for example which output and what region of the
+ * screen should be used.
+ *
+ * If the surface was already maximized, the compositor will still
+ * emit a configure event with the "maximized" state.
+ *
+ * If the surface is in a fullscreen state, this request has no
+ * direct effect. It may alter the state the surface is returned to
+ * when unmaximized unless overridden by the compositor.
+ */
+ void (*set_maximized)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * unmaximize the window
+ *
+ * Unmaximize the surface.
+ *
+ * After requesting that the surface should be unmaximized, the
+ * compositor will respond by emitting a configure event. Whether
+ * this actually un-maximizes the window is subject to compositor
+ * policies. If available and applicable, the compositor will
+ * include the window geometry dimensions the window had prior to
+ * being maximized in the configure event. The client must then
+ * update its content, drawing it in the configured state. The
+ * client must also acknowledge the configure when committing the
+ * new content (see ack_configure).
+ *
+ * It is up to the compositor to position the surface after it was
+ * unmaximized; usually the position the surface had before
+ * maximizing, if applicable.
+ *
+ * If the surface was already not maximized, the compositor will
+ * still emit a configure event without the "maximized" state.
+ *
+ * If the surface is in a fullscreen state, this request has no
+ * direct effect. It may alter the state the surface is returned to
+ * when unmaximized unless overridden by the compositor.
+ */
+ void (*unset_maximized)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * set the window as fullscreen on an output
+ *
+ * Make the surface fullscreen.
+ *
+ * After requesting that the surface should be fullscreened, the
+ * compositor will respond by emitting a configure event. Whether
+ * the client is actually put into a fullscreen state is subject to
+ * compositor policies. The client must also acknowledge the
+ * configure when committing the new content (see ack_configure).
+ *
+ * The output passed by the request indicates the client's
+ * preference as to which display it should be set fullscreen on.
+ * If this value is NULL, it's up to the compositor to choose which
+ * display will be used to map this surface.
+ *
+ * If the surface doesn't cover the whole output, the compositor
+ * will position the surface in the center of the output and
+ * compensate with with border fill covering the rest of the
+ * output. The content of the border fill is undefined, but should
+ * be assumed to be in some way that attempts to blend into the
+ * surrounding area (e.g. solid black).
+ *
+ * If the fullscreened surface is not opaque, the compositor must
+ * make sure that other screen content not part of the same surface
+ * tree (made up of subsurfaces, popups or similarly coupled
+ * surfaces) are not visible below the fullscreened surface.
+ */
+ void (*set_fullscreen)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *output);
+ /**
+ * unset the window as fullscreen
+ *
+ * Make the surface no longer fullscreen.
+ *
+ * After requesting that the surface should be unfullscreened, the
+ * compositor will respond by emitting a configure event. Whether
+ * this actually removes the fullscreen state of the client is
+ * subject to compositor policies.
+ *
+ * Making a surface unfullscreen sets states for the surface based
+ * on the following: * the state(s) it may have had before becoming
+ * fullscreen * any state(s) decided by the compositor * any
+ * state(s) requested by the client while the surface was
+ * fullscreen
+ *
+ * The compositor may include the previous window geometry
+ * dimensions in the configure event, if applicable.
+ *
+ * The client must also acknowledge the configure when committing
+ * the new content (see ack_configure).
+ */
+ void (*unset_fullscreen)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * set the window as minimized
+ *
+ * Request that the compositor minimize your surface. There is no
+ * way to know if the surface is currently minimized, nor is there
+ * any way to unset minimization on this surface.
+ *
+ * If you are looking to throttle redrawing when minimized, please
+ * instead use the wl_surface.frame event for this, as this will
+ * also work with live previews on windows in Alt-Tab, Expose or
+ * similar compositor features.
+ */
+ void (*set_minimized)(struct wl_client *client,
+ struct wl_resource *resource);
+};
+
+#define XDG_TOPLEVEL_CONFIGURE 0
+#define XDG_TOPLEVEL_CLOSE 1
+
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_toplevel
+ */
+#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_xdg_toplevel
+ * Sends an configure event to the client owning the resource.
+ * @param resource_ The client's resource
+ */
+static inline void
+xdg_toplevel_send_configure(struct wl_resource *resource_, int32_t width, int32_t height, struct wl_array *states)
+{
+ wl_resource_post_event(resource_, XDG_TOPLEVEL_CONFIGURE, width, height, states);
+}
+
+/**
+ * @ingroup iface_xdg_toplevel
+ * Sends an close event to the client owning the resource.
+ * @param resource_ The client's resource
+ */
+static inline void
+xdg_toplevel_send_close(struct wl_resource *resource_)
+{
+ wl_resource_post_event(resource_, XDG_TOPLEVEL_CLOSE);
+}
+
+#ifndef XDG_POPUP_ERROR_ENUM
+#define XDG_POPUP_ERROR_ENUM
+enum xdg_popup_error {
+ /**
+ * tried to grab after being mapped
+ */
+ XDG_POPUP_ERROR_INVALID_GRAB = 0,
+};
+#endif /* XDG_POPUP_ERROR_ENUM */
+
+/**
+ * @ingroup iface_xdg_popup
+ * @struct xdg_popup_interface
+ */
+struct xdg_popup_interface {
+ /**
+ * remove xdg_popup interface
+ *
+ * This destroys the popup. Explicitly destroying the xdg_popup
+ * object will also dismiss the popup, and unmap the surface.
+ *
+ * If this xdg_popup is not the "topmost" popup, a protocol error
+ * will be sent.
+ */
+ void (*destroy)(struct wl_client *client,
+ struct wl_resource *resource);
+ /**
+ * make the popup take an explicit grab
+ *
+ * This request makes the created popup take an explicit grab. An
+ * explicit grab will be dismissed when the user dismisses the
+ * popup, or when the client destroys the xdg_popup. This can be
+ * done by the user clicking outside the surface, using the
+ * keyboard, or even locking the screen through closing the lid or
+ * a timeout.
+ *
+ * If the compositor denies the grab, the popup will be immediately
+ * dismissed.
+ *
+ * This request must be used in response to some sort of user
+ * action like a button press, key press, or touch down event. The
+ * serial number of the event should be passed as 'serial'.
+ *
+ * The parent of a grabbing popup must either be an xdg_toplevel
+ * surface or another xdg_popup with an explicit grab. If the
+ * parent is another xdg_popup it means that the popups are nested,
+ * with this popup now being the topmost popup.
+ *
+ * Nested popups must be destroyed in the reverse order they were
+ * created in, e.g. the only popup you are allowed to destroy at
+ * all times is the topmost one.
+ *
+ * When compositors choose to dismiss a popup, they may dismiss
+ * every nested grabbing popup as well. When a compositor dismisses
+ * popups, it will follow the same dismissing order as required
+ * from the client.
+ *
+ * The parent of a grabbing popup must either be another xdg_popup
+ * with an active explicit grab, or an xdg_popup or xdg_toplevel,
+ * if there are no explicit grabs already taken.
+ *
+ * If the topmost grabbing popup is destroyed, the grab will be
+ * returned to the parent of the popup, if that parent previously
+ * had an explicit grab.
+ *
+ * If the parent is a grabbing popup which has already been
+ * dismissed, this popup will be immediately dismissed. If the
+ * parent is a popup that did not take an explicit grab, an error
+ * will be raised.
+ *
+ * During a popup grab, the client owning the grab will receive
+ * pointer and touch events for all their surfaces as normal
+ * (similar to an "owner-events" grab in X11 parlance), while the
+ * top most grabbing popup will always have keyboard focus.
+ * @param seat the wl_seat of the user event
+ * @param serial the serial of the user event
+ */
+ void (*grab)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *seat,
+ uint32_t serial);
+ /**
+ * recalculate the popup's location
+ *
+ * Reposition an already-mapped popup. The popup will be placed
+ * given the details in the passed xdg_positioner object, and a
+ * xdg_popup.repositioned followed by xdg_popup.configure and
+ * xdg_surface.configure will be emitted in response. Any
+ * parameters set by the previous positioner will be discarded.
+ *
+ * The passed token will be sent in the corresponding
+ * xdg_popup.repositioned event. The new popup position will not
+ * take effect until the corresponding configure event is
+ * acknowledged by the client. See xdg_popup.repositioned for
+ * details. The token itself is opaque, and has no other special
+ * meaning.
+ *
+ * If multiple reposition requests are sent, the compositor may
+ * skip all but the last one.
+ *
+ * If the popup is repositioned in response to a configure event
+ * for its parent, the client should send an
+ * xdg_positioner.set_parent_configure and possibly an
+ * xdg_positioner.set_parent_size request to allow the compositor
+ * to properly constrain the popup.
+ *
+ * If the popup is repositioned together with a parent that is
+ * being resized, but not in response to a configure event, the
+ * client should send an xdg_positioner.set_parent_size request.
+ * @param token reposition request token
+ * @since 3
+ */
+ void (*reposition)(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *positioner,
+ uint32_t token);
+};
+
+#define XDG_POPUP_CONFIGURE 0
+#define XDG_POPUP_POPUP_DONE 1
+#define XDG_POPUP_REPOSITIONED 2
+
+/**
+ * @ingroup iface_xdg_popup
+ */
+#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_popup
+ */
+#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_popup
+ */
+#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3
+
+/**
+ * @ingroup iface_xdg_popup
+ */
+#define XDG_POPUP_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_popup
+ */
+#define XDG_POPUP_GRAB_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_popup
+ */
+#define XDG_POPUP_REPOSITION_SINCE_VERSION 3
+
+/**
+ * @ingroup iface_xdg_popup
+ * Sends an configure event to the client owning the resource.
+ * @param resource_ The client's resource
+ * @param x x position relative to parent surface window geometry
+ * @param y y position relative to parent surface window geometry
+ * @param width window geometry width
+ * @param height window geometry height
+ */
+static inline void
+xdg_popup_send_configure(struct wl_resource *resource_, int32_t x, int32_t y, int32_t width, int32_t height)
+{
+ wl_resource_post_event(resource_, XDG_POPUP_CONFIGURE, x, y, width, height);
+}
+
+/**
+ * @ingroup iface_xdg_popup
+ * Sends an popup_done event to the client owning the resource.
+ * @param resource_ The client's resource
+ */
+static inline void
+xdg_popup_send_popup_done(struct wl_resource *resource_)
+{
+ wl_resource_post_event(resource_, XDG_POPUP_POPUP_DONE);
+}
+
+/**
+ * @ingroup iface_xdg_popup
+ * Sends an repositioned event to the client owning the resource.
+ * @param resource_ The client's resource
+ * @param token reposition request token
+ */
+static inline void
+xdg_popup_send_repositioned(struct wl_resource *resource_, uint32_t token)
+{
+ wl_resource_post_event(resource_, XDG_POPUP_REPOSITIONED, token);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cmd/wm/xdg.c b/src/cmd/wm/xdg.c
new file mode 100644
index 0000000..6a0c2c8
--- /dev/null
+++ b/src/cmd/wm/xdg.c
@@ -0,0 +1,118 @@
+#include "wm.h"
+
+static
+void
+map(struct wl_listener *l, void *data)
+{
+ Client *client = wl_container_of(l, client, event.map);
+
+ wl_list_insert(&server.client.list, &client->link);
+ wl_list_insert(&server.client.stack, &client->stack);
+ wl_list_insert(&server.client.focus, &client->focus);
+
+ wlr_xdg_surface_get_geometry(client->xdg, &client->geometry);
+ client->geometry.width += 2 * client->border;
+ client->geometry.height += 2 * client->border;
+
+ wlr_xdg_toplevel_set_tiled(client->xdg,
+ WLR_EDGE_TOP|WLR_EDGE_BOTTOM|WLR_EDGE_LEFT|WLR_EDGE_RIGHT
+ );
+
+ rules(client);
+}
+
+static
+void
+unmap(struct wl_listener *l, void *data)
+{
+ Client *client = wl_container_of(l, client, event.unmap);
+
+ wl_list_remove(&client->link);
+ attach(client, nil, 0);
+
+ wl_list_remove(&client->stack);
+ wl_list_remove(&client->focus);
+}
+
+static
+void
+commit(struct wl_listener *l, void *data)
+{
+ Client *client = wl_container_of(l, client, event.commit);
+ if(client->resize && client->resize <= client->xdg->configure_serial)
+ client->resize = 0;
+}
+
+static
+void
+destroy(struct wl_listener *l, void *data)
+{
+ Client *client = wl_container_of(l, client, event.destroy);
+ free(client);
+}
+
+static
+void
+request_move(struct wl_listener *l, void *data)
+{
+ Client *client = wl_container_of(l, client, event.request_move);
+}
+
+static
+void
+request_resize(struct wl_listener *l, void *data)
+{
+ struct wlr_xdg_toplevel_resize_event *event = data;
+ Client *client = wl_container_of(l, client, event.request_resize);
+}
+
+
+static
+void
+request_title(struct wl_listener *l, void *data)
+{
+ Client *client = wl_container_of(l, client, event.request_title);
+}
+
+static
+void
+request_fullscreen(struct wl_listener *l, void *data)
+{
+ Client *client = wl_container_of(l, client, event.request_fullscreen);
+ client->isfullscreen = 1;
+}
+
+void
+make_xdg_surface(struct wl_listener *l, void *data)
+{
+ Client *client;
+ struct wlr_xdg_toplevel *toplevel;
+ struct wlr_xdg_surface *xdg = data;
+
+ if(xdg->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL)
+ return;
+
+ client = xdg->surface->data = calloc(1, sizeof(*client));
+ client->xdg = xdg;
+ client->border = cfg·borderpixel;
+
+ client->event.map.notify = map;
+ wl_signal_add(&xdg->events.map, &client->event.map);
+ client->event.unmap.notify = unmap;
+ wl_signal_add(&xdg->events.unmap, &client->event.unmap);
+ client->event.destroy.notify = destroy;
+ wl_signal_add(&xdg->events.destroy, &client->event.destroy);
+
+ client->event.commit.notify = commit;
+ wl_signal_add(&xdg->surface->events.commit, &client->event.commit);
+
+ toplevel = xdg->toplevel;
+ client->event.request_move.notify = request_move;
+ wl_signal_add(&toplevel->events.request_move, &client->event.request_move);
+ client->event.request_title.notify = request_title;
+ wl_signal_add(&toplevel->events.set_title, &client->event.request_title);
+ client->event.request_resize.notify = request_resize;
+ wl_signal_add(&toplevel->events.request_resize, &client->event.request_resize);
+ client->event.request_fullscreen.notify = request_fullscreen;
+ wl_signal_add(&toplevel->events.request_fullscreen, &client->event.request_fullscreen);
+}
diff --git a/src/libbio/align.c b/src/libbio/align.c
new file mode 100644
index 0000000..20a57df
--- /dev/null
+++ b/src/libbio/align.c
@@ -0,0 +1,178 @@
+#include <u.h>
+#include <libn.h>
+#include <libn/macro/qsort.h>
+#include <libbio.h>
+
+// -----------------------------------------------------------------------
+// globals
+
+uint64 aln·shft = (2ULL * (aln·K - 1ULL));
+uint64 aln·mask = (1ULL << (2*aln·K)) - 1ULL;
+
+// -----------------------------------------------------------------------
+// static data
+
+static uint64 nuctab[256] = {
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+};
+
+// -----------------------------------------------------------------------
+// hash functions
+
+enum
+{
+ MURM641 = 0xff51afd7ed558ccd,
+ MURM642 = 0xc4ceb9fe1a85ec53
+};
+
+static
+uint64
+minihash(uint64 x, uint64 mask)
+{
+ x = (~x + (x << 21)) & mask;
+ x = x ^ (x >> 24);
+ x = (x + (x << 3) + (x << 8)) & mask;
+ x = x ^ (x >> 14);
+ x = (x + (x << 2) + (x << 4)) & mask;
+ x = x ^ (x >> 28);
+ x = (x + (x << 31));
+
+ return x;
+}
+
+static
+uint64
+murmurhash(uint64 x, uint64 mask)
+{
+ x = x ^ (x >> 33);
+ x = (x * MURM641);
+ x = x ^ (x >> 33);
+ x = (x * MURM642);
+ x = x ^ (x >> 33);
+
+ return x;
+}
+
+// -----------------------------------------------------------------------
+// locality sensitive hashing (with spatial extent)
+
+static
+void
+sortpos(uintptr len, uint64 vals[], int locs[])
+{
+ int tmpi;
+ uint64 tmpu64;
+
+#define LESS(i, j) (locs[i] < locs[j])
+#define SWAP(i, j) (tmpu64 = vals[i], tmpi = locs[i], \
+ vals[i] = vals[j], locs[i] = locs[j], \
+ vals[j] = tmpu64 , locs[j] = tmpi)
+ QSORT(len, LESS, SWAP);
+#undef LESS
+#undef SWAP
+}
+
+/*
+ * sketch
+ * @param seq: '0' terminated string
+ * @param len: number of sequential sketches to keep
+ * @param vals: buffer to store hashes of sketch.
+ * @param locs: buffer to store location of sketch hashes
+ */
+error
+aln·sketch(byte *seq, int len, uint64 *vals[aln·N], int *locs[aln·N])
+{
+ int i, n, l, *loc;
+ uint64 kmer, h[3], *val;
+ int tmpi[2];
+ uint64 tmpu[2];
+
+ for(n = 0; n < aln·N; n++) {
+ for(l = 0; l < len; l++) {
+ vals[n][l] = UINT64_MAX;
+ }
+ }
+
+ kmer = UINT64_MAX;
+ for(l = 0; *seq != '\0'; seq++, l++) {
+ kmer = ((kmer << 2) | nuctab[*seq]) & aln·mask;
+
+ h[0] = minihash(kmer, aln·mask);
+ h[1] = murmurhash(kmer, aln·mask);
+
+ for(n = 0; n < aln·N; n++) {
+ val = vals[n];
+ loc = locs[n];
+
+ h[2] = (h[0] + n * h[1]) & aln·mask;
+ for (i = 0; i < len && h[2] < val[i]; i++) {
+ ;
+ }
+
+ tmpu[1] = h[2];
+ tmpi[1] = l;
+ for(i -= 1; i >= 0; i--) {
+ tmpu[0] = tmpu[1], tmpu[1] = val[i], val[i] = tmpu[0];
+ tmpi[0] = tmpi[1], tmpi[1] = loc[i], loc[i] = tmpi[0];
+ }
+ }
+ }
+
+ for(n = 0; n < aln·N; n++) {
+ sortpos(len, vals[n], locs[n]);
+ }
+
+ return 0;
+}
+
+static
+int
+lessarrs(int len, uint64 a[], uint64 b[])
+{
+ int l;
+
+ for(l = 0; l < len; l++) {
+ if (a[l] < b[l]) return 1;
+ if (a[l] > b[l]) return 0;
+ }
+
+ return 0;
+}
+
+static
+void
+swaparrs(int len, uint64 a[], uint64 b[])
+{
+ int l;
+ uint64 tmp;
+
+ for(l = 0; l < len; l++) {
+ tmp = a[l], a[l] = b[l], b[l] = tmp;
+ }
+}
+
+error
+aln·sort(uintptr n, int len, uint64 *vals)
+{
+#define LESS(i, j) (lessarrs(len, vals+((i)*len), vals+((j)*len)))
+#define SWAP(i, j) (swaparrs(len, vals+((i)*len), vals+((j)*len)))
+ QSORT(n, LESS, SWAP);
+#undef LESS
+#undef SWAP
+ return 0;
+}
diff --git a/src/libbio/fasta.c b/src/libbio/fasta.c
new file mode 100644
index 0000000..3788544
--- /dev/null
+++ b/src/libbio/fasta.c
@@ -0,0 +1,393 @@
+#include <u.h>
+#include <base.h>
+#include <libbio.h>
+
+#define INIT_NM_SIZE 128
+#define INIT_SQ_SIZE 4096
+
+struct SeqBuf
+{
+ mem·Allocator mem;
+ void *heap;
+
+ int cap, off;
+ byte *it, b[];
+};
+
+static
+void
+reset(struct SeqBuf *sb)
+{
+ sb->off = 0;
+ sb->it = sb->b;
+}
+
+static
+error
+grow(struct SeqBuf **sb, int min)
+{
+ void* heap;
+ mem·Allocator mem;
+
+ vlong newcap;
+ struct SeqBuf *old, *new;
+
+ old = *sb;
+ mem = old->mem;
+ heap = old->heap;
+
+ assert((*sb)->cap <= (SIZE_MAX - 1) / 2);
+ newcap = MAX(16, MAX(1 + 2*(*sb)->cap, (*sb)->cap+min));
+ assert(newcap >= (*sb)->cap+min);
+
+ if(new = mem.alloc(heap, 1, sizeof(*new)+newcap), !new) {
+ errorf("memory: could not allocate new buffer\n");
+ return 1;
+ }
+
+ memcpy(new, old, sizeof(*new) + (*sb)->cap);
+
+ new->cap = newcap;
+ new->it = new->b + (old->it - old->b);
+ mem.free(heap, old);
+
+ *sb = new;
+ return 0;
+}
+
+static
+error
+put(struct SeqBuf **sb, byte c)
+{
+ int err;
+ struct SeqBuf *sq;
+
+ sq = *sb;
+ if(sq->it < (sq->b + sq->cap)) {
+ *sq->it++ = c;
+ return 0;
+ }
+
+ if(err = grow(sb, 1), err) {
+ errorf("memory fail: could not allocate more buffer\n");
+ sq->mem.free(sq->heap, sq);
+ return 1;
+ }
+
+ *((*sb)->it++) = c;
+ return 0;
+}
+
+static
+error
+push(struct SeqBuf **sb, int n, void *buf)
+{
+ int d, err;
+ struct SeqBuf *seq;
+
+ seq = *sb;
+ if(d = (seq->cap - (seq->it - seq->b)), d < n) {
+ assert(d >= 0);
+ if (err = grow(sb, n-d), err) {
+ errorf("memory fail: could not allocate more buffer\n");
+ seq->mem.free(seq->heap, seq);
+ return 1;
+ }
+ }
+ seq = *sb;
+
+ memcpy(seq->it, buf, n);
+ seq->it += n;
+
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// sequence data
+
+struct bio·SeqReader {
+ byte eof;
+ io·Reader rdr;
+ void *io;
+
+ struct SeqBuf *seq;
+
+ /* read buffer */
+ byte *b, *bend, buf[4*4098];
+};
+
+static
+error
+fill(bio·SeqReader *rdr)
+{
+ int n;
+ // NOTE: This could lead to an infinite loop.
+ if(rdr->eof)
+ return 0;
+
+ n = rdr->rdr.read(rdr->io, 1, arrlen(rdr->buf), rdr->buf);
+ if(n < 0) {
+ errorf("read: no data obtained from reader\n");
+ return 1;
+ }
+ rdr->b = rdr->buf;
+ rdr->bend = rdr->b + n;
+ if(rdr->eof = (n < arrlen(rdr->buf)), rdr->eof)
+ *rdr->bend++ = '\0';
+
+ return 0;
+}
+
+bio·SeqReader*
+bio·openseq(io·Reader rdr, void *io, mem·Allocator mem, void *heap)
+{
+ error err;
+ bio·SeqReader *r;
+
+ r = mem.alloc(heap, 1, sizeof(bio·SeqReader));
+ r->rdr = rdr;
+ r->io = io;
+ r->eof = 0;
+
+ r->seq = mem.alloc(heap, 1, sizeof(*r->seq) + INIT_NM_SIZE + INIT_SQ_SIZE);
+ r->seq->mem = mem;
+ r->seq->heap = heap;
+ r->seq->it = r->seq->b;
+ r->seq->cap = INIT_NM_SIZE + INIT_SQ_SIZE;
+
+ if (err=fill(r), err) {
+ errorf("fill: could not populate buffer\n");
+ goto ERROR;
+ }
+
+ return r;
+
+ERROR:
+ mem.free(heap, r->seq);
+ mem.free(heap, r);
+ return nil;
+}
+
+error
+bio·closeseq(bio·SeqReader *rdr)
+{
+ mem·Allocator mem;
+ void *heap;
+
+ mem = rdr->seq->mem;
+ heap = rdr->seq->heap;
+
+ mem.free(heap, rdr->seq);
+ mem.free(heap, rdr);
+
+ return 0;
+}
+
+
+static
+error
+readfasta(bio·SeqReader *rdr, bio·Seq *seq, byte hdr, byte stop)
+{
+ error err;
+ byte *beg;
+
+ if(rdr->eof && rdr->b == rdr->bend-1)
+ return EOF;
+
+ reset(rdr->seq);
+
+ // NOTE: Can this case happen?
+ assert(rdr->b != rdr->bend);
+ if(*rdr->b++ != hdr) {
+ errorf("fasta/q format: expected '%c', found '%c'\n", hdr, *rdr->b--);
+ return 1;
+ }
+
+NAME:
+ beg = rdr->b;
+ while(rdr->b != rdr->bend) {
+ if(*rdr->b++ == '\n') {
+ push(&rdr->seq, (rdr->b - 1) - beg, beg);
+ goto SEQ;
+ }
+ }
+ push(&rdr->seq, rdr->b - beg, beg);
+
+ if(err=fill(rdr), err) {
+ errorf("read: could not populate buffer\n");
+ return 1;
+ }
+ goto NAME;
+
+SEQ:
+ put(&rdr->seq, '\0');
+ rdr->seq->off = rdr->seq->it - rdr->seq->b;
+
+SEQLOOP:
+ beg = rdr->b;
+ while(rdr->b != rdr->bend) {
+ if(*rdr->b == '\n') {
+ push(&rdr->seq, rdr->b - beg, beg);
+ beg = rdr->b + 1;
+ }
+
+ if(*rdr->b == stop || *rdr->b == '\0')
+ goto SUCCESS;
+
+ rdr->b++;
+ }
+
+ push(&rdr->seq, rdr->b - beg, beg);
+
+ if(err=fill(rdr), err) {
+ errorf("read: could not populate buffer\n");
+ return 1;
+ }
+ goto SEQLOOP;
+
+SUCCESS:
+ push(&rdr->seq, rdr->b - beg, beg);
+ put(&rdr->seq, '\0');
+
+ return 0;
+}
+
+/*
+ * fasta files
+ */
+
+error
+bio·readfasta(bio·SeqReader *rdr, bio·Seq *seq)
+{
+ error err;
+
+ err = readfasta(rdr, seq, '>', '>');
+ if(err && err != EOF) {
+ errorf("parse fail: could not read sequence of record\n");
+ return err;
+ }
+
+ seq->name = rdr->seq->b;
+ seq->s = rdr->seq->b + rdr->seq->off;
+ seq->len = rdr->seq->it - seq->s - 1; // shift by 1 as we pushed a '0' to end
+ seq->q = nil;
+
+ return err;
+}
+
+/*
+ * fastq files
+ */
+
+error
+bio·readfastq(bio·SeqReader *rdr, bio·Seq *seq)
+{
+ int n;
+ byte *beg;
+ error err;
+
+ err = readfasta(rdr, seq, '@', '+');
+ if(err) {
+ errorf("parse fail: could not read sequence of record\n");
+ return err;
+ }
+
+ seq->len = rdr->seq->it - (rdr->seq->b + rdr->seq->off);
+
+ if(*rdr->b++ != '+') {
+ errorf("format error: no '+' character seperator found\n");
+ return -1;
+ }
+
+EATLN:
+ while(rdr->b != rdr->bend) {
+ if (*rdr->b++ == '\n') {
+ n = 0;
+ goto QUAL;
+ }
+ }
+
+ if(err = fill((bio·SeqReader*)rdr), err) {
+ errorf("read: could not populate buffer\n");
+ return 1;
+ }
+ goto EATLN;
+
+QUAL:
+ beg = rdr->b;
+ while(rdr->b != rdr->bend) {
+ if(*rdr->b == '\n') {
+ push(&rdr->seq, rdr->b - beg, beg);
+ beg = rdr->b + 1;
+ }
+
+ if(n++ == seq->len || *rdr->b == '\0') {
+ err = *rdr->b == '\0' ? EOF : 0;
+ goto SUCCESS;
+ }
+
+ rdr->b++;
+ }
+
+ push(&rdr->seq, rdr->b - beg, beg);
+
+ if(err = fill((bio·SeqReader*)rdr), err) {
+ errorf("read: could not populate buffer\n");
+ return 1;
+ }
+ goto QUAL;
+
+
+SUCCESS:
+ push(&rdr->seq, rdr->b - beg, beg);
+ put(&rdr->seq, '\0');
+
+ seq->name = rdr->seq->b;
+ seq->s = rdr->seq->b + rdr->seq->off - 1;
+ seq->q = seq->s + seq->len + 1;
+
+ return err;
+}
+
+// -----------------------------------------------------------------------
+// sequence writing
+
+error
+bio·writefasta(io·Writer io, void *wtr, bio·Seq seq)
+{
+ int i, j, d;
+ char buf[2048], *b = buf, *e = arrend(buf);
+
+ *b++ = '>';
+ while(*seq.name) {
+ *b++ = *seq.name++;
+ if(b == e) {
+ io.write(wtr, 1, arrlen(buf), buf);
+ b = buf;
+ }
+ }
+
+ for(i=0; i<seq.len; i = j) {
+ j = MIN(i+70, seq.len);
+ d = j - i;
+ if((e-b) <= d+1) {
+ io.write(wtr, 1, b-buf, buf);
+ b = buf;
+ }
+ *b++ = '\n';
+ memcpy(b, seq.s+i, d);
+ b += d;
+ }
+
+ *b++ = '\n';
+ io.write(wtr, 1, b-buf, buf);
+
+ return 0;
+}
+
+error
+bio·writefastq(io·Writer io, void *wtr, bio·Seq seq)
+{
+ panicf("need to implement");
+ return 1;
+}
diff --git a/src/libbio/newick.c b/src/libbio/newick.c
new file mode 100644
index 0000000..5e6d30a
--- /dev/null
+++ b/src/libbio/newick.c
@@ -0,0 +1,414 @@
+#include <u.h>
+#include <base.h>
+#include <libbio.h>
+
+// -----------------------------------------------------------------------
+// Tokens
+
+enum TokenKind
+{
+ tok·nil,
+ tok·eof,
+ tok·space,
+ tok·ident,
+ tok·number,
+ tok·lparen,
+ tok·rparen,
+ tok·lbrak,
+ tok·rbrak,
+ tok·comma,
+ tok·semi,
+ tok·colon,
+
+ NUM_TOKENS,
+};
+
+
+struct Token {
+ enum TokenKind kind;
+ union
+ {
+ byte *s;
+ double x;
+ } lit;
+};
+
+static
+byte*
+tokstr(struct Token tok)
+{
+ static byte b[50];
+ switch (tok.kind) {
+ case tok·nil: return "";
+ case tok·eof: return nil;
+ case tok·space: return " ";
+ case tok·ident: return tok.lit.s;
+ case tok·lparen: return "(";
+ case tok·rparen: return ")";
+ case tok·lbrak: return "[";
+ case tok·rbrak: return "]";
+ case tok·comma: return ",";
+ case tok·semi: return ";";
+ case tok·colon: return ":";
+ case tok·number:
+ snprintf(b, arrlen(b), "%f", tok.lit.x);
+ return b;
+ default:
+ return nil;
+ }
+}
+
+
+// -----------------------------------------------------------------------
+// Read
+
+// TODO: Bounds checking on buffer
+static
+struct Token
+lex(io·Peeker s, void* fp)
+{
+#define isvalidchar(C) ((C) == '!') || \
+ ('\"' < (C) && (C) < '\'') || \
+ (')' < (C) && (C) < '+') || \
+ (',' < (C) && (C) < ':') || \
+ (':' < (C) && (C) < '[') || \
+ ((C) == '\\') || \
+ (']' < (C) && (C) <= '~')
+ byte *c;
+ struct Token tok;
+ static byte b[1024];
+ c = b;
+ *c = s.get(fp);
+
+ if (isspace(*c)) {
+ while (isspace(*c)) {
+ *(++c) = s.get(fp);
+ }
+
+ s.unget(fp, *c);
+ assert(c - b < 1024);
+
+ *c = 0;
+ tok.kind = tok·space;
+ tok.lit.s = b;
+ return tok;
+ }
+
+ switch (*c) {
+ case EOF: tok.kind = tok·eof; return tok;
+ case '(': tok.kind = tok·lparen; return tok;
+ case ')': tok.kind = tok·rparen; return tok;
+ case '[': tok.kind = tok·lbrak; return tok;
+ case ']': tok.kind = tok·rbrak; return tok;
+ case ',': tok.kind = tok·comma; return tok;
+ case ';': tok.kind = tok·semi; return tok;
+ case ':': tok.kind = tok·colon; return tok;
+
+ case '.':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ while (isdigit(*c)) {
+ NUM: *(++c) = s.get(fp);
+ }
+ if (*c == '.') goto NUM;
+ if (isvalidchar(*c)) goto IDENT;
+
+ s.unget(fp, *c);
+ assert(c - b < 1024);
+
+ *c = 0;
+ tok.kind = tok·number;
+ tok.lit.x = atof(b);
+ return tok;
+
+ case '\"':
+ while ((*c) != '\"') {
+ *(++c) = s.get(fp);
+ }
+ assert(c - b < 1024);
+
+ *c = '\0';
+ tok.kind = tok·ident;
+ tok.lit.s = b + 1;
+ return tok;
+
+ default:
+ IDENT:
+ while (isvalidchar(*c)) {
+ *(++c) = s.get(fp);
+ }
+ s.unget(fp, *c);
+ assert(c - b < 1024);
+
+ *c = '\0';
+ tok.kind = tok·ident;
+ tok.lit.s = b;
+ return tok;
+ }
+#undef isvalidchar
+}
+
+static
+struct Token
+lex_nospace(io·Peeker s, void *impl)
+{
+ struct Token tok;
+ tok = lex(s, impl);
+ if (tok.kind == tok·space) {
+ tok = lex_nospace(s, impl);
+ }
+
+ return tok;
+}
+
+struct Parser
+{
+ int lev;
+ bio·Node *root;
+ struct Token tok;
+
+ void *io;
+ io·Peeker file;
+ void *heap;
+ mem·Allocator mem;
+};
+
+static
+error
+parse(struct Parser *p)
+{
+ error err;
+ bio·Node *node;
+ bio·Node *root;
+ struct Token tok;
+
+ node = p->root;
+ for (;;) {
+ tok = lex_nospace(p->file, p->io);
+
+ switch (tok.kind) {
+ case tok·lparen:
+ if (!p->root && p->lev > 0) {
+ errorf("parse format: attempted to make root at non-zero level");
+ goto ERROR;
+ }
+
+ node = p->mem.alloc(p->heap, 1, sizeof(*node));
+ memset(node, 0, sizeof(*node));
+
+ if (p->root) {
+ phylo·addchild(p->root, node);
+ root = p->root;
+ } else {
+ root = node;
+ }
+
+ p->lev++;
+ p->root = node;
+ p->tok = tok;
+ err = parse(p);
+ if (err) {
+ goto ERROR;
+ }
+ if (p->tok.kind != tok·rparen) {
+ errorf("incorrect format: closing parentheses expected to proceed opening");
+ goto ERROR;
+ }
+ p->root = root;
+ // NOTE(nnoll): We don't want to override the state of p->tok here!
+ // Jump straight to grabbing next token.
+ continue;
+
+ case tok·rparen:
+ p->lev--;
+ goto DONE;
+
+ /* Comments */
+ case tok·lbrak:
+ if (!node) {
+ errorf("incorrect format: comment found in disallowed region");
+ goto ERROR;
+ }
+ node->comment = str·make("");
+ while (tok.kind != tok·rbrak) {
+ tok = lex_nospace(p->file, p->io);
+ if (tok.kind == tok·eof || tok.kind == tok·nil) {
+ errorf("incorrect format: unmatched comment bracket '['");
+ goto ERROR;
+ }
+ str·append(&node->comment, tokstr(tok));
+ }
+ break;
+
+ case tok·rbrak:
+ errorf("incorrect format: end comment token found in disallowed region");
+ goto ERROR;
+ break;
+
+ case tok·colon:
+ tok = lex_nospace(p->file, p->io);
+ if (tok.kind != tok·number) {
+ errorf("incorrect format: expected number after colon");
+ goto ERROR;
+ }
+ if (node == nil) {
+ errorf("parse error: attempting to set distance of nil node");
+ goto ERROR;
+ }
+ node->dist = tok.lit.x;
+ break;
+
+ case tok·comma:
+ node = nil;
+ break;
+
+ case tok·ident:
+ if (p->tok.kind == tok·rparen) {
+ if (!node) {
+ errorf("parse error: attempting to set name of nil node");
+ goto ERROR;
+ }
+ node->name = str·make(tok.lit.s);
+ } else {
+ if (p->tok.kind != tok·lparen && p->tok.kind != tok·comma) {
+ errorf("format error: misplaced identifier for leaf found");
+ goto ERROR;
+ }
+
+ if (!p->root) {
+ errorf("parse error: attempting to create child for no parent");
+ goto ERROR;
+ }
+
+ node = p->mem.alloc(p->heap, 1, sizeof(*node));
+ memset(node, 0, sizeof(*node));
+
+ node->name = str·make(tok.lit.s);
+
+ phylo·addchild(p->root, node);
+ }
+ break;
+
+ case tok·number:
+ if (p->tok.kind == tok·rparen) {
+ if (p->lev == 0) {
+ errorf("format error: support value on root not supported");
+ goto ERROR;
+ }
+ node->support = tok.lit.x;
+ } else {
+ errorf("format error: found number in unexpected location");
+ goto ERROR;
+ }
+ break;
+
+ case tok·semi:
+ p->file.unget(p->io, ';');
+ if (p->lev) {
+ errorf("format error: uneven number of parentheses found at ';'");
+ goto ERROR;
+ }
+ goto DONE;
+
+ case tok·eof:
+ goto DONE;
+
+ default:
+ errorf("parse error: unrecognized token");
+ goto ERROR;
+ }
+
+ p->tok = tok;
+ }
+
+DONE:
+ p->tok = tok;
+ return 0;
+ERROR:
+ // TODO(nnoll): cleanup
+ return 1;
+}
+
+int
+bio·readnewick(io·Peeker stream, void *s, bio·Tree *tree)
+{
+ error err;
+ struct Parser p;
+
+ if (!tree) {
+ errorf("tree pointer nil");
+ return 0;
+ }
+
+ p = (struct Parser){
+ .lev = 0,
+ .root = nil,
+ .tok = (struct Token){ 0 },
+ .io = s,
+ .file = stream,
+ .mem = tree->mem,
+ .heap = tree->heap,
+ };
+ err = parse(&p);
+ if (err) {
+ errorf("parsing failed\n");
+ return 0;
+ }
+
+ tree->root = p.root;
+ tree->nleaf = 0;
+ tree->root->nnode = 0;
+
+ phylo·countleafs(tree->root, &tree->nleaf);
+ phylo·countnodes(tree->root, &tree->root->nnode);
+
+ return 1;
+}
+
+// -----------------------------------------------------------------------
+// Write
+
+static
+error
+dump(bio·Node *node, void *impl, io·Putter out)
+{
+ byte b[24];
+
+ if (!node) {
+ return 1;
+ }
+
+ bio·Node *child;
+ if (node->nchild) {
+ out.put(impl, '(');
+
+ dump(node->child, impl, out);
+ for (child = node->child->sibling; child != nil; child = child->sibling) {
+ out.put(impl, ',');
+ dump(child, impl, out);
+ }
+
+ out.put(impl, ')');
+ }
+ if (node->name) {
+ out.puts(impl, node->name);
+ }
+
+ if (node->parent) {
+ out.put(impl, ':');
+ snprintf(b, arrlen(b), "%f", node->dist);
+ out.puts(impl, b);
+ }
+
+ return 0;
+}
+
+error
+bio·writenewick(bio·Tree tree, io·Putter out, void* impl)
+{
+ dump(tree.root, impl, out);
+ out.put(impl, ';');
+ out.put(impl, '\n');
+
+ return 0;
+}
diff --git a/src/libbio/phylo.c b/src/libbio/phylo.c
new file mode 100644
index 0000000..d50934f
--- /dev/null
+++ b/src/libbio/phylo.c
@@ -0,0 +1,427 @@
+#include <u.h>
+#include <base.h>
+#include <base/macro/qsort.h>
+#include <libbio.h>
+
+// -----------------------------------------------------------------------
+// subtree manipulation methods
+// NOTE: As of now these don't update nnode & nleaf stats.
+// It is the caller's responsibility to refresh counts.
+
+error
+phylo·addchild(bio·Node* parent, bio·Node* child)
+{
+ bio·Node *it, *sibling;
+ if (!parent->nchild) {
+ parent->child = child;
+ goto SUCCESS;
+ }
+
+ for (it = parent->child, sibling = it; it != nil; it = it->sibling) {
+ sibling = it;
+ }
+ sibling->sibling = child;
+
+SUCCESS:
+ child->parent = parent;
+ parent->nchild++;
+ return 0;
+}
+
+error
+phylo·rmchild(bio·Node *parent, bio·Node *child)
+{
+ bio·Node *it, *prev;
+ enum {
+ error·nil,
+ error·notfound,
+ error·nochildren,
+ };
+
+ prev = nil;
+ for (it = parent->child; it != nil; it = it->sibling) {
+ if (it == child) goto FOUND;
+ prev = it;
+ }
+ return error·notfound;
+
+FOUND:
+ if (prev == nil) {
+ parent->child = child->sibling;
+ } else {
+ prev->sibling = child->sibling;
+ }
+
+ parent->nchild--;
+ return error·nil;
+}
+
+// -----------------------------------------------------------------------
+// subtree statistics
+
+error
+phylo·countnodes(bio·Node *node, int *n)
+{
+ int m;
+ error err;
+ bio·Node *child;
+
+ m = *n;
+ for (child = node->child; child != nil; child = child->sibling) {
+ if (err = phylo·countnodes(child, n), err) {
+ errorf("node count: failure at '%s'", child->name);
+ return 1;
+ }
+ }
+ node->nnode = *n - m;
+ *n += 1;
+
+ return 0;
+}
+
+error
+phylo·countleafs(bio·Node *node, int *n)
+{
+ error err;
+ bio·Node *child;
+
+ if (!node->nchild) {
+ *n += 1;
+ }
+
+ for (child = node->child; child != nil; child = child->sibling) {
+ if (err = phylo·countleafs(child, n), err) {
+ errorf("leaf count: failure at '%s'", child->name);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// generic operations on tree
+
+void*
+phylo·postorder(bio·Node *clade, void *(*op)(bio·Node*, void*), void *ctx)
+{
+ bio·Node *it;
+
+ for(it = clade->child; it != nil; it = it->sibling) {
+ ctx = phylo·postorder(it, op, ctx);
+ }
+
+ return op(clade, ctx);
+}
+
+void*
+phylo·preorder(bio·Node *clade, void *(*op)(bio·Node*, void*), void *ctx)
+{
+ bio·Node *it;
+
+ ctx = op(clade, ctx);
+ for(it = clade->child; it != nil; it = it->sibling) {
+ ctx = phylo·preorder(it, op, ctx);
+ }
+
+ return ctx;
+}
+
+int
+phylo·collectpostorder(bio·Node *clade, bio·Node **list)
+{
+ bio·Node *it;
+ int n;
+
+ for(n = 0, it = clade->child; it != nil; it = it->sibling) {
+ n += phylo·collectpostorder(it, list+n);
+ }
+
+ return n;
+}
+
+static
+inline
+void*
+appendleaf(bio·Node *node, void* list)
+{
+ bio·Node **leafs;
+
+ leafs = list;
+ if (!node->nchild) {
+ *leafs++ = node;
+ }
+
+ return leafs;
+}
+
+void
+phylo·getleafs(bio·Tree tree, bio·Node **leafs)
+{
+ phylo·postorder(tree.root, &appendleaf, leafs);
+}
+
+// -----------------------------------------------------------------------
+// tree editing
+
+static
+void
+sortnodelist(bio·Node **head, bio·Node *next)
+{
+ bio·Node tmp, *it;
+
+ it = &tmp;
+ tmp.sibling = *head;
+
+ while (it->sibling != nil && it->sibling->nnode < next->nnode) {
+ it = it->sibling;
+ }
+
+ next->sibling = it->sibling;
+ it->sibling = next;
+ *head = tmp.sibling;
+}
+
+error
+phylo·ladderize(bio·Node *root)
+{
+ int i;
+ error err;
+ bio·Node *child, *sorted, *sibling;
+
+ if (!root->nchild) return 0;
+
+ // ladderize below
+ for (child = root->child; child != nil; child = child->sibling) {
+ if (err = phylo·ladderize(child), err) {
+ errorf("ladderize: failure at '%s'", child->name);
+ return 1;
+ }
+ }
+
+ // ladderize yourself
+ sorted = nil;
+ child = root->child;
+ while (child != nil) {
+ sibling = child->sibling;
+ sortnodelist(&sorted, child);
+ child = sibling;
+ }
+ root->child = sorted;
+
+ return 0;
+}
+
+/*
+ * compute all distances from a given node
+ * must provide a working buffer
+ */
+
+struct Tuple
+{
+ double *d;
+ bio·Node **n;
+};
+
+static
+struct Tuple
+getdistsfrom(bio·Node *node, bio·Node *prev, double curr, double *dist, bio·Node **list)
+{
+ bio·Node *it;
+ struct Tuple ret;
+
+ *dist++ = curr;
+ *list++ = node;
+
+ ret.d = dist;
+ ret.n = list;
+
+ if (node->parent && node->parent != prev) {
+ ret = getdistsfrom(node->parent, node, curr + node->dist, dist, list);
+
+ dist = ret.d;
+ list = ret.n;
+ }
+
+ for (it = node->child; it != nil; it = it->sibling) {
+ if (it != prev) {
+ ret = getdistsfrom(it, node, curr + it->dist, dist, list);
+
+ dist = ret.d;
+ list = ret.n;
+ }
+ }
+
+ return ret;
+}
+
+int
+phylo·getdistsfrom(bio·Node *node, int len, double *dist, bio·Node **list)
+{
+ struct Tuple ret;
+ // TODO: Better bounds checking.
+
+ ret = getdistsfrom(node, nil, 0.0, dist, list);
+
+ assert(ret.n - list == len);
+ assert(ret.d - dist == len);
+
+ return len;
+}
+
+/*
+static
+void
+disttoroot(bio·Node *clade, double anc, double *dists)
+{
+ double d;
+ bio·Node *it;
+
+ *dists++ = anc + clade->dist;
+ d = dists[-1];
+ for (it = clade->child; it != nil; it = it->sibling) {
+ disttoroot(it, d, ++dists);
+ }
+}
+
+void
+phylo·disttoroot(bio·Tree tree, double *dists)
+{
+ disttoroot(tree.root, 0.0, dists);
+}
+*/
+
+/*
+ * compute the path constituting the tree diameter
+ * returns the number of edges in the path
+ */
+
+static
+void
+sort·nodedists(uintptr len, double fs[], bio·Node* ns[])
+{
+ double f;
+ bio·Node *n;
+#define LESS(i, j) (fs[i] < fs[j])
+#define SWAP(i, j) (n = ns[i], f = fs[i], \
+ fs[i] = fs[j], ns[i] = ns[j], \
+ fs[j] = f, ns[j] = n)
+ QSORT(len, LESS, SWAP);
+#undef LESS
+#undef SWAP
+}
+
+#define BUFLEN 4096
+double
+phylo·diameter(bio·Tree tree, int *len, bio·Node **path)
+{
+ // TODO: deal with large tree > BUFLEN gracefully
+ int n;
+ double fbuf[BUFLEN];
+ bio·Node *nbuf[BUFLEN];
+
+ n = tree.root->nnode;
+
+ assert(n < BUFLEN);
+
+ n = phylo·getdistsfrom(tree.root, tree.root->nnode, fbuf, nbuf);
+ sort·nodedists(n, fbuf, nbuf);
+
+ path[0] = nbuf[n-1];
+ printf("first end '%s'\n", path[0]->name);
+
+ n = phylo·getdistsfrom(path[0], n, fbuf, nbuf);
+ sort·nodedists(n, fbuf, nbuf);
+ printf("second end '%s'\n", nbuf[n-1]->name);
+
+ *len = 0;
+
+ // TODO: Traverse up the tree from each node
+ // Find MRCA by intersection of nodes hit
+
+ return 0.0;
+}
+#undef BUFLEN
+
+/*
+ * reroot a tree on a new node
+ */
+static
+error
+rotateparent(bio·Node *node, bio·Node *to)
+{
+ error err;
+
+ // NOTE: will this ever be taken?
+ if (node->parent == to) {
+ return 0;
+ }
+
+ if (!node->parent) {
+ goto RMCHILD;
+ }
+
+ err = rotateparent(node->parent, node);
+ if (err) {
+ errorf("failure: broken tree");
+ return err;
+ }
+
+ err = phylo·addchild(node, node->parent);
+ if (err) {
+ errorf("inconsistent topology: could not add parent '%s' as child of '%s'", node->parent->name, node->name);
+ return err;
+ }
+
+RMCHILD:
+ err = phylo·rmchild(node, to);
+ if (err) {
+ errorf("inconsistent topology: could not remove child '%s' from '%s'", to->name, node->name);
+ return err;
+ }
+
+ node->parent = to;
+ return 0;
+}
+
+#define PREC .00000001
+error
+phylo·reroot(bio·Tree *tree, bio·Node *node, double d)
+{
+ bio·Node *new;
+
+ // TODO: should check that node is part of this tree?
+ // TODO: should we check if node->parent != nil?
+
+ if (fabs(d) < PREC) {
+ new = node;
+ rotateparent(node->parent, node);
+ } else if (fabs(d-node->dist) < PREC) {
+ new = node->parent;
+ if (new->parent->parent) {
+ rotateparent(new->parent->parent, new->parent);
+ }
+ } else {
+ new = tree->mem.alloc(tree->heap, 1, sizeof(*new));
+ memset(new, 0, sizeof(*new));
+
+ phylo·addchild(new, node);
+ node->parent = new;
+
+ phylo·addchild(new, node->parent);
+ if (node->parent->parent) {
+ rotateparent(node->parent->parent, node->parent);
+ }
+ node->parent->parent = new;
+ }
+
+ printf("number of children on old root: %d\n", tree->root->nchild);
+ tree->root = new;
+ tree->nleaf = 0;
+
+ phylo·countleafs(new, &tree->nleaf);
+ phylo·countnodes(new, &new->nnode);
+
+ return 0;
+}
+#undef PREC
diff --git a/src/libbio/rules.mk b/src/libbio/rules.mk
new file mode 100644
index 0000000..07ce97e
--- /dev/null
+++ b/src/libbio/rules.mk
@@ -0,0 +1,24 @@
+include share/push.mk
+
+# Local sources
+SRCS_$(d) := \
+ $(d)/fasta.c \
+ $(d)/newick.c \
+ $(d)/phylo.c
+LIBS_$(d) := $(d)/libbio.a
+BINS_$(d) :=
+# CHECK_$(d) := \
+# $(d)/test.c \
+# $(d)/simulate.c
+
+include share/paths.mk
+
+# Local rules
+$(LIBS_$(d)): $(OBJS_$(d)) $(OBJS_$(d)/io)
+ $(ARCHIVE)
+
+$(TEST_$(d)): TCLIBS = $(LIBS_$(d)) $(OBJ_DIR)/libn/libn.a
+$(TEST_$(d)): $(UNIT_$(d)) $(LIBS_$(d)) $(OBJ_DIR)/libn/libn.a
+ $(LINK)
+
+include share/pop.mk
diff --git a/src/libbio/simulate.c b/src/libbio/simulate.c
new file mode 100644
index 0000000..0f5a97e
--- /dev/null
+++ b/src/libbio/simulate.c
@@ -0,0 +1,120 @@
+#include <u.h>
+#include <libn.h>
+#include <libbio.h>
+
+#define SEQLEN 2560
+static byte *SEQ =
+"GGCGGCTTCGGTGCGCTGTGTGCATTGCCGCAAAAATATCGTGAACCCGTGCTGGTTTCCGGCACTGACGGCGTAGGTAC"
+"CAAGCTGCGTCTGGCAATGGACTTAAAACGTCACGACACCATTGGTATTGATCTGGTCGCCATGTGCGTTAATGACCTGG"
+"TGGTGCAAGGTGCGGAACCGCTGTTTTTCCTCGACTATTACGCAACCGGAAAACTGGATGTTGATACCGCTTCAGCGGTG"
+"ATCAGCGGCATTGCGGAAGGTTGTCTGCAATCGGGCTGTTCTCTGGTGGGTGGCGAAACGGCAGAAATGCCGGGGATGTA"
+"TCACGGTGAAGATTACGATGTCGCGGGTTTCTGCGTGGGCGTGGTAGAAAAATCAGAAATCATCGACGGCTCTAAAGTCA"
+"GCGACGGCGATGTGCTGATTGCACTCGGTTCCAGCGGTCCGCACTCGAACGGTTATTCGCTGGTGCGCAAAATTCTTGAA"
+"GTCAGCGGTTGTGATCCGCAAACCACCGAACTTGATGGTAAGCCATTAGCCGATCATCTGCTGGCACCGACCCGCATTTA"
+"CGTGAAGTCAGTGCTGGAGTTGATTGAAAAGGTCGATGTGCATGCCATTGCGCACCTGACCGGCGGCGGCTTCTGGGAAA"
+"ACATTCCGCGCGTATTGCCAGATAATACCCAGGCAGTGATTGATGAATCTTCCTGGCAGTGGCCGGAAGTGTTCAACTGG"
+"CTGCAAACGGCAGGTAACGTTGAGCGCCATGAAATGTATCGCACCTTCAACTGCGGCGTCGGGATGATTATCGCCCTGCC"
+"TGCTCCGGAAGTGGACAAAGCCCTCGCCCTGCTCAATGCCAACGGTGAAAACGCGTGGAAAATCGGTATCATCAAAGCCT"
+"CTGATTCCGAACAACGCGTGGTTATCGAATAATGAATATTGTGGTGCTTATTTCCGGCAACGGAAGTAATTTACAGGCAA"
+"TTATTGACGCCTGTAAAACCAACAAAATTAAAGGCACCGTACGGGCAGTTTTCAGCAATAAGGCCGACGCGTTCGGCCTT"
+"GAACGCGCCCGCCAGGCGGGTATTGCAACGCATACGCTCATCGCCAGCGCGTTTGACAGTCGTGAAGCCTATGACCGGGA"
+"GTTGATTCATGAAATCGACATGTACGCACCCGATGTGGTCGTGCTGGCTGGTTTTATGCGCATTCTCAGCCCGGCGTTTG"
+"TCTCCCACTATGCCGGGCGTTTGCTGAACATTCACCCTTCTCTGCTGCCGAAATATCCCGGATTACACACCCATCGTCAA"
+"GCGCTGGAAAATGGCGATGAAGAGCACGGTACATCGGTGCATTTCGTCACCGATGAACTGGACGGTGGCCCGGTTATTTT"
+"ACAGGCGAAAGTCCCGGTATTTGCTGGTGATACGGAAGATGACGTCACCGCCCGCGTGCAAACCCAGGAACACGCCATTT"
+"ATCCACTGGTGATTAGCTGGTTTGCCGATGGTCGTCTGAAAATGCACGAAAACGCCGCGTGGCTGGATGGTCAACGTCTG"
+"CCGCCGCAGGGCTACGCTGCCGACGAGTAATGCCCCCGTAGTTAAAGCGCCAGCTCTGCCGCTGGCGTTTTTCAATTCAC"
+"CTGTAAATCGCAAGCTCCAGCAGTTTTTTTCCCCCTTTTCTGGCATAGTTGGACATCTGCCAATATTGCTCGCCATAATA"
+"TCCAGGCAGTGTCCCGTGAATAAAACGGAGTAAAAGTGGTAATGGGTCAGGAAAAGCTATACATCGAAAAAGAGCTCAGT"
+"TGGTTATCGTTCAATGAACGCGTGCTTCAGGAAGCGGCGGACAAATCTAACCCGCTGATTGAAAGGATGCGTTTCCTGGG"
+"GATCTATTCCAATAACCTTGATGAGTTCTATAAAGTCCGCTTCGCTGAACTGAAGCGACGCATCATTATTAGCGAAGAAC"
+"AAGGCTCCAACTCTCATTCCCGCCATTTACTGGGCAAAATTCAGTCCCGGGTGCTGAAAGCCGATCAGGAATTCGACGGC"
+"CTCTACAACGAGCTATTGCTGGAGATGGCGCGCAACCAGATCTTCCTGATTAATGAACGCCAGCTCTCCGTCAATCAACA"
+"AAACTGGCTGCGTCATTATTTTAAGCAGTATCTGCGTCAGCACATTACGCCGATTTTAATCAATCCTGACACTGACTTAG"
+"TGCAGTTCCTGAAAGATGATTACACCTATCTGGCGGTGGAAATTATCCGTGGCGATACCATCCGTTACGCGCTTCTGGAG"
+"ATCCCATCAGATAAAGTGCCGCGCTTTGTGAATTTACCGCCAGAAGCGCCGCGTCGACGCAAGCCGATGATTCTTCTGGA"
+"TAACATTCTGCGTTACTGCCTTGATGATATTTTCAAAGGCTTCTTTGATTATGACGCGCTGAATGCCTATTCAATGAAGA"
+"TGACCCGCGATGCCGAATACGATTTAGTGCATGAGATGGAAGCCAGCCTGATGGAGTTGATGTCTTCCAGTCTCAAGCAG"
+"CGTTTAACTGCTGAGCCGGTGCGTTTTGTTTATCAGCGCGATATGCCCAATGCGCTGGTTGAAGTTTTACGCGAAAAACT";
+
+byte*
+modify(byte *seq, int *len, double p)
+{
+ byte *head, *new;
+
+ head = calloc(SEQLEN+1, sizeof(byte));
+ new = head;
+ for (; *seq != '\0'; seq++) {
+ if (rng·bernoulli(p)) {
+ switch (rng·randi(5)) {
+ case 0: *new++ = 'A'; break;
+ case 1: *new++ = 'C'; break;
+ case 2: *new++ = 'G'; break;
+ case 3: *new++ = 'T'; break;
+ case 4: continue;
+ }
+ } else {
+ *new++ = *seq;
+ }
+ }
+ *new = '\0';
+ *len = new - head;
+ return head;
+}
+
+#define NSEQS 20
+int
+main()
+{
+ int n, i, l, lens[NSEQS];
+ byte *seqs[NSEQS];
+
+ int locs[aln·N][NSEQS][aln·L];
+ int *loc[aln·N];
+ uint64 vals[aln·N][NSEQS][aln·L];
+ uint64 *val[aln·N];
+
+ rng·init(0);
+
+ seqs[0] = SEQ;
+ lens[0] = SEQLEN;
+
+ for (n = 0; n < aln·N; n++) {
+ for (i = 0; i < NSEQS; i++) {
+ for (l = 0; l < aln·L; l++) {
+ vals[n][i][l] = 0;
+ }
+ }
+ }
+
+ for (i = 1; i < NSEQS; i++) {
+ seqs[i] = modify(SEQ, lens + i, .01*i);
+ }
+
+ for (i = 0; i < NSEQS; i++) {
+ for (n = 0; n < aln·N; n++) {
+ val[n] = vals[n][i];
+ loc[n] = locs[n][i];
+ }
+ aln·sketch(seqs[i], aln·L, val, loc);
+ }
+
+ // for (n = 0; n < aln·N; n++) {
+ // printf("iteration %d\n", n);
+ // printf("[\n");
+ // for (i = 0; i < NSEQS; i++) {
+ // printf(" [");
+ // for (l = 0; l < aln·L; l++) {
+ // printf("%lu,", vals[n][i][l]);
+ // }
+ // printf("],\n");
+ // }
+ // printf("]\n");
+ // }
+
+ for (n = 0; n < aln·N; n++) {
+ aln·sort(NSEQS, aln·L, (uint64*)vals[n]);
+ }
+
+ return 0;
+}
diff --git a/src/libbio/test.c b/src/libbio/test.c
new file mode 100644
index 0000000..9926764
--- /dev/null
+++ b/src/libbio/test.c
@@ -0,0 +1,283 @@
+#include <u.h>
+#include <libn.h>
+#include <libbio.h>
+
+#include <time.h>
+
+// -----------------------------------------------------------------------
+// Global data
+
+static byte *SEQ[] = {
+"GGCGGCTTCGGTGCGCTGTGTGCATTGCCGCAAAAATATCGTGAACCCGTGCTGGTTTCCGGCACTGACGGCGTAGGTAC"
+"CAAGCTGCGTCTGGCAATGGACTTAAAACGTCACGACACCATTGGTATTGATCTGGTCGCCATGTGCGTTAATGACCTGG"
+"TGGTGCAAGGTGCGGAACCGCTGTTTTTCCTCGACTATTACGCAACCGGAAAACTGGATGTTGATACCGCTTCAGCGGTG"
+"ATCAGCGGCATTGCGGAAGGTTGTCTGCAATCGGGCTGTTCTCTGGTGGGTGGCGAAACGGCAGAAATGCCGGGGATGTA"
+"TCACGGTGAAGATTACGATGTCGCGGGTTTCTGCGTGGGCGTGGTAGAAAAATCAGAAATCATCGACGGCTCTAAAGTCA"
+"GCGACGGCGATGTGCTGATTGCACTCGGTTCCAGCGGTCCGCACTCGAACGGTTATTCGCTGGTGCGCAAAATTCTTGAA"
+"GTCAGCGGTTGTGATCCGCAAACCACCGAACTTGATGGTAAGCCATTAGCCGATCATCTGCTGGCACCGACCCGCATTTA"
+"CGTGAAGTCAGTGCTGGAGTTGATTGAAAAGGTCGATGTGCATGCCATTGCGCACCTGACCGGCGGCGGCTTCTGGGAAA"
+"ACATTCCGCGCGTATTGCCAGATAATACCCAGGCAGTGATTGATGAATCTTCCTGGCAGTGGCCGGAAGTGTTCAACTGG"
+"CTGCAAACGGCAGGTAACGTTGAGCGCCATGAAATGTATCGCACCTTCAACTGCGGCGTCGGGATGATTATCGCCCTGCC"
+"TGCTCCGGAAGTGGACAAAGCCCTCGCCCTGCTCAATGCCAACGGTGAAAACGCGTGGAAAATCGGTATCATCAAAGCCT"
+"CTGATTCCGAACAACGCGTGGTTATCGAATAATGAATATTGTGGTGCTTATTTCCGGCAACGGAAGTAATTTACAGGCAA"
+"TTATTGACGCCTGTAAAACCAACAAAATTAAAGGCACCGTACGGGCAGTTTTCAGCAATAAGGCCGACGCGTTCGGCCTT"
+"GAACGCGCCCGCCAGGCGGGTATTGCAACGCATACGCTCATCGCCAGCGCGTTTGACAGTCGTGAAGCCTATGACCGGGA"
+"GTTGATTCATGAAATCGACATGTACGCACCCGATGTGGTCGTGCTGGCTGGTTTTATGCGCATTCTCAGCCCGGCGTTTG"
+"TCTCCCACTATGCCGGGCGTTTGCTGAACATTCACCCTTCTCTGCTGCCGAAATATCCCGGATTACACACCCATCGTCAA"
+"GCGCTGGAAAATGGCGATGAAGAGCACGGTACATCGGTGCATTTCGTCACCGATGAACTGGACGGTGGCCCGGTTATTTT"
+"ACAGGCGAAAGTCCCGGTATTTGCTGGTGATACGGAAGATGACGTCACCGCCCGCGTGCAAACCCAGGAACACGCCATTT"
+"ATCCACTGGTGATTAGCTGGTTTGCCGATGGTCGTCTGAAAATGCACGAAAACGCCGCGTGGCTGGATGGTCAACGTCTG"
+"CCGCCGCAGGGCTACGCTGCCGACGAGTAATGCCCCCGTAGTTAAAGCGCCAGCTCTGCCGCTGGCGTTTTTCAATTCAC"
+"CTGTAAATCGCAAGCTCCAGCAGTTTTTTTCCCCCTTTTCTGGCATAGTTGGACATCTGCCAATATTGCTCGCCATAATA"
+"TCCAGGCAGTGTCCCGTGAATAAAACGGAGTAAAAGTGGTAATGGGTCAGGAAAAGCTATACATCGAAAAAGAGCTCAGT"
+"TGGTTATCGTTCAATGAACGCGTGCTTCAGGAAGCGGCGGACAAATCTAACCCGCTGATTGAAAGGATGCGTTTCCTGGG"
+"GATCTATTCCAATAACCTTGATGAGTTCTATAAAGTCCGCTTCGCTGAACTGAAGCGACGCATCATTATTAGCGAAGAAC"
+"AAGGCTCCAACTCTCATTCCCGCCATTTACTGGGCAAAATTCAGTCCCGGGTGCTGAAAGCCGATCAGGAATTCGACGGC"
+"CTCTACAACGAGCTATTGCTGGAGATGGCGCGCAACCAGATCTTCCTGATTAATGAACGCCAGCTCTCCGTCAATCAACA"
+"AAACTGGCTGCGTCATTATTTTAAGCAGTATCTGCGTCAGCACATTACGCCGATTTTAATCAATCCTGACACTGACTTAG"
+"TGCAGTTCCTGAAAGATGATTACACCTATCTGGCGGTGGAAATTATCCGTGGCGATACCATCCGTTACGCGCTTCTGGAG"
+"ATCCCATCAGATAAAGTGCCGCGCTTTGTGAATTTACCGCCAGAAGCGCCGCGTCGACGCAAGCCGATGATTCTTCTGGA"
+"TAACATTCTGCGTTACTGCCTTGATGATATTTTCAAAGGCTTCTTTGATTATGACGCGCTGAATGCCTATTCAATGAAGA"
+"TGACCCGCGATGCCGAATACGATTTAGTGCATGAGATGGAAGCCAGCCTGATGGAGTTGATGTCTTCCAGTCTCAAGCAG"
+"CGTTTAACTGCTGAGCCGGTGCGTTTTGTTTATCAGCGCGATATGCCCAATGCGCTGGTTGAAGTTTTACGCGAAAAACT",
+
+"GGCGGCTTCGGTGCGCTGTGTGCATTGCCGCAAAAATATCGTGAACCCGTGCTGGTTTCCGGCACTGACGGCGTAAATAC"
+"CAAGCTGCGTCTGGCAATGGACTTAAAACGTCACGACACCATTGGTATTGATCTGGTCGCCATGTGCGTTAATGACCTGG"
+"TGGTGCAAGGTGCGGAACCGCTGTTTTTCCTCGACTATTACGCACCGGAAAACTGGATGTTGATACCGCTTCAGCGGTG"
+"ATCAGCGGCATTGCGGAAGGTTGTCTGCAATCGGGCTGTTCTCTGGTGGGTGGCGAAACGGCAGAAATGCCGGGGATGTA"
+"TCACGGTGAAGATTACGATGTCGCGGGTTTCTGCGTGGGCGTGGTAGAAAAATCAGAAATCATCGACGGCAAAGTCA"
+"GCGACGGCGATGTGCTGATTGCACTCGGTTCCAGCGGTCCGCACTCGAACGGTTATTCGCTGGTGCGCAAAATTCTTGAA"
+"GTCAGCGGTTGTGATCCGCAAACCACCGAACTTGATGGTAAGCCATTAGCCGATCATCTGCTGGCACCGACCCGCATTTA"
+"ACATTCCGCGCGTATTGCCAGATAATACCCAGGCAGTGATTGATGAATCTTCCTGGCAGTGGCCGGAAGTGTTCAACTGG"
+"CTGCAAACGGCAGGTAACGTTGAGCGCCATGAAATGTATCGCACCTTCAACTGCGGCGTCGGGATGATTATCCCCTGCC"
+"TGCTCCGGAAGTGGACAAAGCCCTCGCCCTGCTCAATGCCAACGGTGAAAACGCGTGGAAAATCGGTATCATCAAAGCCT"
+"CTGATTCCGAACAACGCGTGGTTATCGAATAATGAATATTGTGTGCTTATTTCCGGCAACGGAAGTAATTTACAGGCAA"
+"TTATTGACGCCTGTAAAACCAACAAAATTAAAGGCACCGTACGGGCAGTTTTCAGCAATAAGGCCGACGCGCGGCCTT"
+"GAACGCGCCCGCCAGGCGGGTATTGCAACGCATACGCTCATCGCCAGCGCGTTTGACAGTCGTGAAGCCTATGACCGGGA"
+"GTTGATTCATGAAATCGACATGTACGCACCCGATGTGGTCGTGCTGGCTGGTTTTATGCGCATTCTCAGCCCGGCGTTTG"
+"TCTCCCACTATGCCGGGCGTTTGCTGAACATTCACCCTTCTCTGCTGCCGAAATATCCCGGATTACACACCCATCGTCAA"
+"GCGCTGGAAAATGGCGATGAAGAGCACGGTACATCGGGCATTTCGTCACCGATGAACTGGACGGTGGCCCGGTTATTTT"
+"ACAGTCGAAAGTCCCGGTATTTGCTGGTGATACGGAAGATGACGTCACCGCCCGCGTGCAAACCCAGGAACACGCCATTT"
+"ATCCTCTGGTGATTAGCTGGTTTGCCGATGGTCGTCTGAAAATGCACGAAAACGCCGCGTGGCTGGATGGTCAACGTCTG"
+"CCGCTGCAGGGCTACGCTGCCGACGAGTAATGCCCCCGTAGTTAAAGCGCCAGCTCTGCCGCTGGCGTTTTTCAATTCAC"
+"CTGTTAATCGCAAGCTCCAGCAGCCCCCCCCCCCCTTTTCTGCATAGTTGGACATCTGCCAATATTGCTCGCCATAATA"
+"TCCATGCAGTGTCCCGTGAATAAAACGGAGTAAAAGTGGTAATGGGTCAGGAAAAGCTATACATAAAAAGAGCTCAGT"
+"TGGTTATCGTTCAATGAACGCGTGCTTCAGGAAGCGGCGGACAAATCTAACCCGCTGATTGAAAGGATGCGTTTCCTGGG"
+"GATCTATTCCAATAACCTTGATGAGTTCTATAAAGTCCGCTTCGCTGAACTGAAGCGACGCATTATTAGCGAAGAAC"
+"AAGGTTCCAACTCTCATTCCCGCCATTTACTGGGAAAATTCAGTCCCGGGTGCTGAAAGCCGATCAGGAATTCGACGGC"
+"CTCTTCAACGAGCTATTGCTGGAGATGGCGCGCAACCAGATCTTCCTGATTAATGAACGCCAGCTCTCCGTCAATCAACA"
+"AAACTGGCTGCGTCATTATTTTAAGCAGTATCTGCGTCAGCACATTACGCCGATTTTAATCAATCCTGACACTGACTTAG"
+"TGCATTTCCTGAAAGATGATTACACCTATCTGGCGGTGGAAATTATCCGTGGCGATACCATCCGTTACGCGCTTCTGGAG"
+"ATCCCATCAGATAAAGTGCCGCGCTTTGTGAATTTACCGCAGAAGCGCCGCGTCGACGCAAGCCGATGATTCTTCTGGA"
+"TAACATTCTGCGTTACTGCCTTGATGATATTTTCAAAGGCTTCTTTGATTATGACGCGCTGAATGCCTATTCAATGAAGA"
+"TGACCCGCGATGCCGAATACGATTTAGTGCATGAGATGGAAGCCAGCCTGATGGAGTTGATGTCTTCCAGTCTCAAGCAG"
+"CGTTTAACTGCTGAGCCGGTGCGTTTTGTTTATCGCGCGATATGCCCAATGCGCTGGTTGAAGTTTTACGCGAAAAACT",
+};
+
+
+static
+int
+my_read(Stream *s, void *buf, int n)
+{
+ return io·read(s, 1, n, buf);
+}
+
+// -----------------------------------------------------------------------
+// Point of entry for testing
+
+error
+test·newick()
+{
+ error err;
+ bio·Tree t;
+ mem·Arena *heap;
+ Stream *fd[2];
+
+ io·Peeker rdr;
+ io·Putter wtr;
+
+ bio·Node **end, **it, **list;
+
+ heap = mem·makearena(mem·sys, nil);
+ rdr = (io·Peeker){.get = (byte (*)(void *))io·getbyte, .unget = (error (*)(void *, byte))io·ungetbyte};
+ wtr = (io·Putter){.put = (error (*)(void *, byte))io·putbyte, .putstr = (int (*)(void *, string))io·putstring};
+
+ fd[0] = io·open("/home/nolln/root/data/test/zika.nwk", "r");
+ fd[1] = io·open("/home/nolln/root/data/test/zika.proc.nwk", "w");
+
+ t.h = heap;
+ t.heap = (mem·Allocator){ .alloc = (void *(*)(void *, uint, ulong))mem·arenaalloc, .free = nil, };
+
+ if (err = bio·readnewick(rdr, fd[0], &t), err) {
+ errorf("failed to read newick");
+ return 1;
+ }
+ printf("number of children: %d\n", t.root->nchild);
+
+ phylo·ladderize(t.root);
+
+ list = mem·arenaalloc(heap, t.nleaf, sizeof(**list));
+ phylo·getleafs(t, list);
+ for (it = list, end = list + t.nleaf; it != end; ++it) {
+ printf("Leaf '%s'\n", (*it)->name);
+ }
+
+ bio·Node *path[100];
+ // phylo·diameter(t, path);
+
+ printf("Loaded tree with %d leafs and %d nodes\n", t.nleaf, t.root->nnode);
+ err = bio·writenewick(t, wtr, fd[1]);
+
+ io·flush(fd[1]);
+
+ io·close(fd[0]);
+ io·close(fd[1]);
+
+ mem·freearena(heap);
+ return 0;
+}
+
+error
+test·fasta()
+{
+ error err;
+ Stream *fd;
+
+ bio·Seq seq;
+ bio·FastaReader *rdr;
+
+ clock_t t;
+
+ fd = io·open("/home/nolln/root/data/test/zika.fa", "r");
+
+ /* Benchmark against Heng */
+#if 0
+ int n, slen;
+ kseq_t *kseq;
+
+ t = clock();
+ kseq = kseq_init(fd);
+ while (kseq_read(kseq) >= 0) {
+ ++n, slen += kseq->seq.l;
+ }
+ t = clock() - t;
+ printf("heng's code took %f ms to execute\n", 1000.*t/CLOCKS_PER_SEC);
+
+ kseq_destroy(kseq);
+
+ io·seek(fd, 0, seek·set);
+#endif
+
+ rdr = bio·openfasta((io·Reader){.read = (int (*)(void *, int, int, void *))io·read}, fd, mem·sys, nil);
+
+ t = clock();
+ err = 0;
+ while (!err) {
+ err = bio·readfasta(rdr, &seq);
+ }
+ t = clock() - t;
+ printf("nick's code took %f ms to execute\n", 1000.*t/CLOCKS_PER_SEC);
+ bio·closefasta(rdr);
+
+
+ io·close(fd);
+ return err <= 0 ? 0 : 1;
+}
+
+#define asrdr(x) (int (*)(void *, int, int, void *))(x)
+error
+test·fastq()
+{
+ error err;
+ Stream *fd;
+
+ bio·Seq seq;
+ bio·FastqReader *rdr;
+
+ clock_t t;
+
+ fd = io·open("/home/nolln/root/data/test/eg.fq", "r");
+
+ rdr = bio·openfastq((io·Reader){.read = asrdr(io·read)}, fd, mem·sys, nil);
+
+ t = clock();
+ err = 0;
+ while (!err) {
+ err = bio·readfastq(rdr, &seq);
+ }
+ t = clock() - t;
+ printf("nick's fastq code took %f ms to execute\n", 1000.*t/CLOCKS_PER_SEC);
+ bio·closefastq(rdr);
+
+
+ io·close(fd);
+ return err <= 0 ? 0 : 1;
+}
+
+error
+test·align()
+{
+ double f;
+ error err;
+ int i, l, n;
+
+ uint64 mem[aln·N][arrlen(SEQ)][aln·L];
+ uint64 *phi[aln·N];
+ int loc[aln·N][arrlen(SEQ)][aln·L];
+ int *pos[aln·N];
+
+ for (i = 0; i < arrlen(SEQ); i++) {
+ for (n = 0; n < aln·N; n++) {
+ phi[n] = mem[n][i];
+ pos[n] = loc[n][i];
+ }
+
+ err = aln·sketch(SEQ[i], aln·L, phi, pos);
+ }
+
+ f = 0;
+ for (n = 0; n < aln·N; n++) {
+ aln·sort(arrlen(SEQ), aln·L, (uint64*)mem[n]);
+
+ if (!memcmp(mem[n][0], mem[n][1], sizeof(uint64)*aln·L)) {
+ f += 1.;
+ printf("True : ");
+ } else {
+ printf("False: ");
+ }
+ for (i = 0; i < arrlen(SEQ); i++) {
+ printf("[");
+ for (l = 0; l < aln·L; l++) {
+ printf("%lu,", mem[n][i][l]);
+ }
+ printf("]");
+ if (i == 0) printf(" ~ ");
+ }
+ printf("\n");
+ }
+
+ printf("Fraction hits %f\n", f/aln·N);
+ return err;
+
+}
+
+error
+main()
+{
+ error err;
+
+ if (err = test·newick(), err) {
+ errorf("test fail: newick");
+ }
+
+#if 0
+ if (err = test·fasta(), err) {
+ errorf("test fail: fasta");
+ }
+
+ if (err = test·fastq(), err) {
+ errorf("test fail: fastq");
+ }
+#endif
+}
+
diff --git a/src/libc/rules.mk b/src/libc/rules.mk
new file mode 100644
index 0000000..34e0912
--- /dev/null
+++ b/src/libc/rules.mk
@@ -0,0 +1,20 @@
+include share/push.mk
+
+# Iterate through subdirectory tree
+
+# Local sources
+SRCS_$(d) := $(wildcard $(d)/*.c)
+LIBS_$(d) := $(d)/libc_n.a
+BINS_$(d) :=
+
+include share/paths.mk
+
+# Local rules
+$(LIBS_$(d)): TCFLAGS = -ffreestanding -fno-builtin -nostdlib
+$(LIBS_$(d)): $(OBJS_$(d))
+ $(ARCHIVE)
+
+$(BINS_$(d)): $(OBJ_DIR)/libn/test.o
+ $(LINK)
+
+include share/pop.mk
diff --git a/src/libc/stdio.c b/src/libc/stdio.c
new file mode 100644
index 0000000..8bbbe9a
--- /dev/null
+++ b/src/libc/stdio.c
@@ -0,0 +1,59 @@
+#include <u.h>
+#include <libc.h>
+
+int
+printf(byte* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ int nw, rem, peek, len;
+ byte *str, c;
+
+ while (*fmt) {
+ rem = INT_MAX - nw;
+
+ if (fmt[0] != '%' || fmt[1] == '%') {
+ if (fmt[0] == '%') fmt++;
+
+ for (peek = 1; fmt[peek] && fmt[peek] != '%'; peek++) {
+ ;
+ }
+ if (rem < peek) return -1;
+ // TODO: Print here.
+ fmt += peek;
+ nw += peek;
+ continue;
+ }
+
+ str = fmt++;
+
+ switch (*fmt++) {
+ case 'c':
+ c = va_arg(args, int);
+ if (rem < 0) return -1;
+ // TODO: Print here
+ nw++;
+ break;
+
+ case 's':
+ str = va_arg(args, byte*);
+ len = strlen(str);
+ if (rem < len) return -1;
+ // TODO: Print here
+ nw += len;
+ break;
+ default:
+ fmt = str;
+ len = strlen(fmt);
+ if (rem < len) return -1;
+ // TODO: Print here
+ nw += len;
+ fmt += len;
+ break;
+ }
+ }
+
+ va_end(args);
+ return nw;
+}
diff --git a/src/libc/string.c b/src/libc/string.c
new file mode 100644
index 0000000..0e41efa
--- /dev/null
+++ b/src/libc/string.c
@@ -0,0 +1,80 @@
+#include <u.h>
+#include <libc.h>
+
+void*
+memcopy(void *dst, void *src, intptr n)
+{
+ byte *e, *s, *d;
+
+ d = dst;
+ e = d + n;
+ for (s = src ; d != e; ++s, ++d) {
+ *d = *s;
+ }
+
+ return dst;
+}
+
+void*
+memmove(void *dst, void *src, intptr n)
+{
+ byte *e, *s, *d;
+ s = src;
+ d = dst;
+
+ if (d < s) {
+ e = d + n;
+ for (; d != e; ++s, ++d)
+ *d = *s;
+
+ } else {
+ e = d;
+ d += n;
+ s += n;
+ for (; d != e; --s, --d)
+ d[-1] = s[-1];
+ }
+
+ return dst;
+}
+
+void*
+memset(void *buf, int val, intptr n)
+{
+ byte *b, *e;
+ b = buf;
+ e = b + n;
+ for (; b != e; b++) {
+ *b = (byte)val;
+ }
+
+ return buf;
+}
+
+int
+memcmp(void *lhs, void *rhs, intptr n)
+{
+ byte *bl, *br, *e;
+
+ br = rhs;
+ e = br + n;
+ for (bl = lhs; br != e; ++bl, ++br) {
+ if (*bl < *br)
+ return -1;
+ else if (*bl > *br)
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+strlen(byte* s)
+{
+ byte* b;
+ for (b = s; *b; b++) {
+ ;
+ }
+
+ return b - s;
+}
diff --git a/src/libfmt/buffer.c b/src/libfmt/buffer.c
new file mode 100644
index 0000000..0099e72
--- /dev/null
+++ b/src/libfmt/buffer.c
@@ -0,0 +1,60 @@
+#include "internal.h"
+
+static int
+flush(fmt·State *io)
+{
+ int n;
+ char *s;
+
+ void *heap = io->heap;
+ mem·Reallocator mem = io->mem;
+
+ if(!io->buffer.beg)
+ return 0;
+
+ n = 2*(uintptr)io->file;
+ s = io->buffer.beg;
+
+ io->buffer.beg = mem.realloc(heap, io->buffer.beg, n, 1);
+ if(!io->buffer.beg){
+ io->file = io->buffer.cur = io->buffer.end = nil;
+ mem.free(heap, s);
+ return 0;
+ }
+ io->file = (void*)(uintptr)n;
+ io->buffer.cur = io->buffer.beg + (io->buffer.cur - s);
+ io->buffer.end = io->buffer.beg + n - 1;
+
+ return 1;
+}
+
+int
+fmt·make(mem·Reallocator mem, void *heap, fmt·State *io)
+{
+ int n;
+
+ memset(io, 0, sizeof(*io));
+
+ n = 32;
+ io->buffer.beg = io->buffer.cur = mem.alloc(heap, n, 1);
+ if(!io->buffer.beg)
+ return -1;
+ io->buffer.end = io->buffer.beg + n - 1;
+
+ io->flush = flush;
+ io->file = (void*)(uintptr)n;
+ io->n = 0;
+
+ fmt·setlocale(io, nil, nil, nil);
+ return 0;
+}
+
+void
+fmt·free(fmt·State *io)
+{
+ void *heap = io->heap;
+ mem·Reallocator mem = io->mem;
+
+ mem.free(heap, io->buffer.beg);
+ io->buffer.beg = io->buffer.cur = io->buffer.end = nil;
+}
diff --git a/src/libfmt/do.c b/src/libfmt/do.c
new file mode 100644
index 0000000..eaac0a3
--- /dev/null
+++ b/src/libfmt/do.c
@@ -0,0 +1,730 @@
+#include "internal.h"
+#include <stdatomic.h>
+
+#define atomic _Atomic
+#define MaxFmt 128
+#define atomic·load atomic_load
+#define atomic·store atomic_store
+
+// -----------------------------------------------------------------------
+// globals
+
+/* built in verbs */
+static int fmtflag(fmt·State *);
+static int fmtpercent(fmt·State *);
+static int fmtrune(fmt·State *);
+static int fmtfloat(fmt·State *);
+static int fmtutf8(fmt·State *);
+static int fmtint(fmt·State *);
+static int fmtchar(fmt·State *);
+static int fmtcount(fmt·State *);
+static int fmtstring(fmt·State *);
+static int fmterror(fmt·State *);
+
+static int badfmt(fmt·State *);
+
+static struct
+{
+ atomic int len;
+ Verb verb[MaxFmt];
+} formatter =
+{
+ ATOMIC_VAR_INIT(30),
+ {
+ {' ', fmtflag},
+ {'#', fmtflag},
+ {'%', fmtpercent},
+ {'\'',fmtflag},
+ {'+', fmtflag},
+ {',', fmtflag},
+ {'-', fmtflag},
+ {'C', fmtrune},
+ {'E', fmtfloat},
+ {'F', fmtfloat},
+ {'G', fmtfloat},
+ {'L', fmtflag},
+ {'S', fmtutf8},
+ {'X', fmtint},
+ {'b', fmtint},
+ {'c', fmtchar},
+ {'d', fmtint},
+ {'e', fmtfloat},
+ {'f', fmtfloat},
+ {'g', fmtfloat},
+ {'h', fmtflag},
+ {'i', fmtint},
+ {'l', fmtflag},
+ {'n', fmtcount},
+ {'o', fmtint},
+ {'p', fmtint},
+ {'r', fmterror},
+ {'s', fmtstring},
+ {'U', fmtflag},
+ {'u', fmtint},
+ {'x', fmtint},
+ }
+};
+
+// -----------------------------------------------------------------------
+// internal functions
+
+static Formatter
+format(int c)
+{
+ Verb *v, *e;
+ e = &formatter.verb[atomic·load(&formatter.len)];
+ for(v=e; v > formatter.verb; --v){
+ if(v->c == c)
+ return v->fmt;
+ }
+
+ return badfmt;
+}
+
+static char *
+dispatch(fmt·State *io, char *fmt)
+{
+ rune r;
+ int i, n;
+
+ io->flag = 0;
+ io->width = io->prec = 0;
+
+ /*
+ * the form of each print verb:
+ * % [flags] verb
+ * + the verb is a single character
+ * + each flag is either
+ * - a single character
+ * - a decimal numeric string
+ * - up to 2 decimal strings can be used
+ * - [width|*].[prec|*]
+ * - if missing, set to 0
+ * - if *, grab from varargs
+ */
+ for(;;){
+ fmt += utf8·decode(fmt, &r);
+ io->verb = r;
+ switch(r){
+ case 0:
+ return nil;
+ case '.':
+ io->flag |= fmt·Width|fmt·Prec;
+ continue;
+ case '0':
+ if(!(io->flag & fmt·Width)){
+ io->flag |= fmt·Zero;
+ continue;
+ }
+ /* fallthrough */
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ i = 0;
+ while('0' <= r && r <= '9'){
+ i = 10*i + (r-'0');
+ r = *fmt++;
+ }
+ fmt--;
+ number:
+ if(io->flag & fmt·Width){
+ io->flag |= fmt·Prec;
+ io->prec = i;
+ }else{
+ io->flag |= fmt·Width;
+ io->width = i;
+ }
+ continue;
+ case '*':
+ i = va_arg(io->args, int);
+ if(i < 0){
+ if(io->flag&fmt·Prec){
+ io->flag &= ~fmt·Prec;
+ io->prec = 0;
+ continue;
+ }
+ i = -i;
+ io->flag |= fmt·Left;
+ }
+ goto number;
+ }
+ n = format(r)(io);
+ if(n < 0)
+ return nil;
+ if(!n)
+ return fmt;
+ }
+}
+
+static char *
+flush(fmt·State *io, char *b, int len)
+{
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(!io->flush || !(*io->flush)(io) || io->buffer.cur + len >= io->buffer.end) {
+ io->buffer.end = io->buffer.cur;
+ return nil;
+ }
+ return io->buffer.cur;
+}
+
+static int
+pad(fmt·State *io, int n)
+{
+ int i;
+ char *b=io->buffer.cur, *e=io->buffer.end;
+
+ for(i=0; i<n; i++){
+ if(b>=e){
+ if(!(b=flush(io, b, 1)))
+ return -1;
+ e = io->buffer.end;
+ }
+ *b++ = ' ';
+ }
+
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ return 0;
+}
+
+static int
+copy(fmt·State *io, char *m, int sz, int n)
+{
+ ulong f;
+ rune r;
+ int nc, w, nb;
+ char *b, *e, *me;
+
+ w = 0;
+ f = io->flag;
+ me = m + sz;
+
+ if(f&fmt·Width)
+ w = io->width;
+ if(f&fmt·Prec && n > io->prec)
+ n = io->prec;
+ if(!(f&fmt·Left) && pad(io, w-n)<0)
+ return -1;
+
+ b = io->buffer.cur;
+ e = io->buffer.end;
+
+ for(nc=n; nc>0; nc--){
+ r = *(uchar *)m;
+ if(utf8·onebyte(r)){
+ nb=1;
+ m++;
+ }else if((me-m) >= UTFmax || utf8·canfit(m, me-m)){
+ nb=utf8·decode(m, &r);
+ m+=n;
+ }else
+ break;
+
+ if(b+n>e){
+ if(!(b=flush(io, b, nb)))
+ return -1;
+ e = io->buffer.end;
+ }
+ b += utf8·encode(&r, b);
+ }
+
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(f&fmt·Left && pad(io, w-n)<0)
+ return -1;
+
+ return 0;
+}
+
+static int
+copyrune(fmt·State *io, rune *m, int n)
+{
+ ulong f;
+ rune r, *me;
+ int w, nb;
+ char *b, *e;
+
+ w = 0;
+ f = io->flag;
+
+ if(f&fmt·Width)
+ w = io->width;
+ if(f&fmt·Prec && n > io->prec)
+ n = io->prec;
+
+ if(!(f&fmt·Left) && pad(io, w-n)<0)
+ return -1;
+
+ b = io->buffer.cur;
+ e = io->buffer.end;
+
+ for(me=m+n; m < me; m++){
+ r = *m;
+ nb = utf8·runelen(r);
+ if(b + nb > e){
+ if(!(b=flush(io, b, nb)))
+ return -1;
+ e = io->buffer.end;
+ }
+ b += utf8·encode(&r, b);
+ }
+
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(f&fmt·Left && pad(io, w-n)<0)
+ return -1;
+
+ return 0;
+}
+
+static int
+copystring(fmt·State *io, char *s)
+{
+ rune r;
+ int i,j;
+
+ if(!s)
+ return copy(io, "<nil>", 5, 5);
+
+ if(io->flag&fmt·Prec){
+ i = 0;
+ for(j=0; j < io->prec && s[i]; j++)
+ i += utf8·decode(s+i, &r);
+
+ return copy(io, s, i, j);
+ }
+ return copy(io, s, strlen(s), utf8·len(s));
+}
+
+static int
+copyutf8(fmt·State *io, rune *s)
+{
+ rune *e;
+ int n,p;
+
+ if(!s)
+ return copy(io, "<nil>", 5, 5);
+
+ if(io->flag & fmt·Prec){
+ p = io->prec;
+ for(n=0; n<p; n++)
+ if(!s[n])
+ break;
+ }else{
+ for(e=s; *e; e++)
+ ;
+ n = e - s;
+ }
+
+ return copyrune(io, s, n);
+}
+
+// -----------------------------------------------------------------------
+// format helpers
+
+static int
+needseperate(int *digits, char **groups)
+{
+ int group;
+
+ (*digits)++;
+ group = *(uchar *)*groups;
+
+ if(group == 0xFF || group == 0x7f || group == 0x00)
+ return 0;
+ if(*digits > group){
+ if((*groups)[1] != 0)
+ (*groups)++;
+ *digits = 1;
+ return 1;
+ }
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// formatters
+
+static int
+fmtchar(fmt·State *io)
+{
+ char x[1];
+ x[0] = va_arg(io->args, int);
+ io->prec = 1;
+
+ return copy(io, x, 1, 1);
+}
+
+static int
+fmtstring(fmt·State *io)
+{
+ char *s;
+ s = va_arg(io->args, char *);
+ return copystring(io, s);
+}
+
+static int
+fmterror(fmt·State *io)
+{
+ char *s;
+ s = strerror(errno);
+ return copystring(io, s);
+}
+
+static int
+fmtrune(fmt·State *io)
+{
+ rune x[1];
+
+ x[0] = va_arg(io->args, int);
+ return copyrune(io, x, 1);
+}
+
+static int
+fmtutf8(fmt·State *io)
+{
+ rune *s;
+
+ s = va_arg(io->args, rune *);
+ return copyutf8(io, s);
+}
+
+static int
+fmtpercent(fmt·State *io)
+{
+ rune x[1];
+
+ x[0] = io->verb;
+ io->prec = 1;
+ return copyrune(io, x, 1);
+}
+
+static int
+fmtint(fmt·State *io)
+{
+ union{
+ ulong u;
+ uvlong v;
+ } val;
+ int neg, base, i, n, f, w, isv;
+ int digits, bytes, runes, excess;
+ char *groups, *thousands;
+ char *p, *conv, buf[140];
+
+ f = io->flag;
+ neg = 0;
+ isv = 0;
+ val.u = 0;
+
+ switch(io->verb){
+ case 'o': case 'p': case 'u': case 'x': case 'X':
+ f |= fmt·Unsigned;
+ f &= ~(fmt·Sign|fmt·Space);
+ }
+
+ /* set flags */
+ if(io->verb=='p'){
+ val.u = (ulong)va_arg(io->args, void*);
+ io->verb = 'x';
+ f |= fmt·Unsigned;
+ }else if(f&fmt·Vlong){
+ isv=1;
+ if(f&fmt·Unsigned)
+ val.v = va_arg(io->args, uvlong);
+ else
+ val.v = va_arg(io->args, vlong);
+ }else if(f&fmt·Long){
+ if(f&fmt·Unsigned)
+ val.u = va_arg(io->args, ulong);
+ else
+ val.u = va_arg(io->args, long);
+ }else if(f&fmt·Byte){
+ if(f&fmt·Unsigned)
+ val.u = (uchar)va_arg(io->args, int);
+ else
+ val.u = (char)va_arg(io->args, int);
+ }else if(f&fmt·Short){
+ if(f&fmt·Unsigned)
+ val.u = (ushort)va_arg(io->args, int);
+ else
+ val.u = (short)va_arg(io->args, int);
+ }else{
+ if(f&fmt·Unsigned)
+ val.u = va_arg(io->args, uint);
+ else
+ val.u = va_arg(io->args, int);
+ }
+
+ conv = "0123456789abcdef";
+ groups = "\4";
+ thousands = io->thousands;
+ /* get base */
+ switch(io->verb){
+ case 'd': case 'i': case 'u':
+ base = 10;
+ groups = io->groups;
+ break;
+ case 'X':
+ conv = "0123456789ABCDEF";
+ /*fallthrough*/
+ case 'x':
+ base = 16;
+ thousands = ":";
+ break;
+ case 'b':
+ base = 2;
+ thousands = ":";
+ break;
+ case 'o':
+ base = 8;
+ break;
+ default:
+ return -1;
+ }
+
+ /* check for negativity */
+ if(!(f&fmt·Unsigned)){
+ if(isv && (vlong)val.v < 0){
+ val.v = -(vlong)val.v;
+ neg = 1;
+ }else if(!isv && (long)val.u < 0){
+ val.u = -(long)val.u;
+ neg = 1;
+ }
+ }
+
+ p = buf + sizeof(buf) - 1;
+ n = 0;
+ digits = 0;
+ excess = 0;
+ runes = utf8·len(thousands);
+ bytes = strlen(thousands);
+
+#define PARSE(VALUE) \
+ while((VALUE)){ \
+ i = (VALUE) % base; \
+ (VALUE) /= base; \
+ if((f&fmt·Comma) && n%4 == 3){ \
+ *p-- = ','; \
+ n++; \
+ } \
+ if((f&fmt·Apost) && needseperate(&digits, &groups)){ \
+ n += runes; \
+ excess += bytes - runes; \
+ p -= bytes; \
+ memmove(p+1, thousands, bytes); \
+ } \
+ *p-- = conv[i]; \
+ n++; \
+ }
+ if(isv)
+ PARSE(val.v)
+ else
+ PARSE(val.u)
+#undef PARSE
+
+ if(!n){
+ if(!(f&fmt·Prec) || io->prec != 0 || (io->verb == 'o' && (f&fmt·Sharp))){
+ *p-- = '0';
+ n = 1;
+ if(f&fmt·Apost)
+ needseperate(&digits,&groups);
+ }
+
+ if(io->verb == 'x' || io->verb == 'X')
+ f &= ~fmt·Sharp;
+ }
+
+ for(w = io->prec; n < w && p > buf+3; n++){
+ if((f&fmt·Apost) && needseperate(&digits, &groups)){
+ n += runes;
+ excess += bytes - runes;
+ p -= bytes;
+ memmove(p+1, thousands, bytes);
+ }
+ *p-- = '0';
+ }
+
+ if(neg || (f&(fmt·Sign|fmt·Space)))
+ n++;
+
+ if(f&fmt·Sharp){
+ if(base==16)
+ n += 2;
+ else if(base == 8){
+ if(p[1] == '0')
+ f &= ~fmt·Sharp;
+ else
+ n++;
+ }
+ }
+
+ if(f&fmt·Zero && !(f & (fmt·Left|fmt·Prec))){
+ w = 0;
+ if(f & fmt·Width)
+ w = io->width;
+ for(; n < w && p > buf+3; n++){
+ if((f & fmt·Apost) && needseperate(&digits, &groups)){
+ n += runes;
+ excess += bytes - runes;
+ p -= bytes;
+ memmove(p+1, thousands, bytes);
+ }
+ *p-- = '0';
+ }
+ io->flag &= ~fmt·Width;
+ }
+
+ if(f&fmt·Sharp){
+ if(base==16)
+ *p-- = io->verb;
+ if(base==16 || base == 8)
+ *p-- = '0';
+ }
+
+ if(neg)
+ *p-- = '-';
+ else if(f & fmt·Sign)
+ *p-- = '+';
+ else if (f & fmt·Space)
+ *p-- = ' ';
+
+ io->flag &= ~fmt·Prec;
+ return copy(io, p+1, n+excess, n);
+}
+
+static int
+fmtcount(fmt·State *io)
+{
+ void *p;
+ ulong f;
+
+ f = io->flag;
+ p = va_arg(io->args, void*);
+
+ if(f&fmt·Vlong)
+ *(vlong*)p = io->n;
+ else if(f&fmt·Long)
+ *(long*)p = io->n;
+ else if(f&fmt·Byte)
+ *(char*)p = io->n;
+ else if(f&fmt·Short)
+ *(short*)p = io->n;
+ else
+ *(int*)p = io->n;
+
+ return 0;
+}
+
+static int
+fmtflag(fmt·State *io)
+{
+ switch(io->verb){
+ case ',': io->flag |= fmt·Comma; break;
+ case '-': io->flag |= fmt·Left; break;
+ case '+': io->flag |= fmt·Sign; break;
+ case '#': io->flag |= fmt·Sharp; break;
+ case '\'': io->flag |= fmt·Apost; break;
+ case ' ': io->flag |= fmt·Space; break;
+ case 'u': io->flag |= fmt·Unsigned; break;
+ case 'L': io->flag |= fmt·Ldouble; break;
+ case 'h':
+ if(io->flag&fmt·Short)
+ io->flag |= fmt·Byte;
+ io->flag |= fmt·Short;
+ break;
+ case 'l':
+ if(io->flag&fmt·Long)
+ io->flag |= fmt·Vlong;
+ io->flag |= fmt·Long;
+ break;
+ }
+ return 1;
+}
+
+static int
+badfmt(fmt·State *io)
+{
+ int n;
+ char x[UTFmax+2];
+
+ x[0] = '%';
+ n = 1 + utf8·encode(&io->verb, x+1);
+ x[n++] = '%';
+ io->prec = n;
+ copy(io, x, n, n);
+
+ return 0;
+}
+
+#include "float.c"
+
+// -----------------------------------------------------------------------
+// exports
+
+int
+fmt·do(fmt·State *io, char *fmt)
+{
+ rune r;
+ int c, n;
+ char *b, *e;
+
+ for(;;){
+ b = io->buffer.cur;
+ e = io->buffer.end;
+ while((c = *(uchar *)fmt) && c != '%'){
+ if(utf8·onebyte(c)){
+ if(b >= e){
+ if(!(b=flush(io, b, 1)))
+ return -1;
+ e = io->buffer.end;
+ }
+ *b++ = *fmt++;
+ }else{
+ n = utf8·decode(fmt, &r);
+ if(b + n > e){
+ if(!(b=flush(io, b, n)))
+ return -1;
+ e = io->buffer.end;
+ }
+ while(n--)
+ *b++ = *fmt++;
+ }
+ }
+ fmt++;
+ io->n += b - io->buffer.cur;
+ io->buffer.cur = b;
+ if(!c) /* we hit our nul terminator */
+ return io->n - n;
+ io->buffer.end = e;
+
+ if(!(fmt=dispatch(io, fmt)))
+ return -1;
+ }
+}
+
+int
+fmt·install(int verb, Formatter func)
+{
+ Verb *v;
+ int i, ret;
+
+lock:
+ if(verb <= 0 || verb >= 65536){
+ ret = -1;
+ goto unlock;
+ }
+ if(!func)
+ func = badfmt;
+
+ if((i = atomic·load(&formatter.len))==MaxFmt)
+ return -1;
+
+ v = &formatter.verb[i];
+ v->c = verb;
+ v->fmt = func;
+
+ atomic·store(&formatter.len, i+1);
+ ret = 0;
+unlock:
+ return ret;
+}
diff --git a/src/libfmt/esprint.c b/src/libfmt/esprint.c
new file mode 100644
index 0000000..6d97340
--- /dev/null
+++ b/src/libfmt/esprint.c
@@ -0,0 +1,14 @@
+#include "internal.h"
+
+char *
+fmt·esprint(char *buf, char *end, char *fmt, ...)
+{
+ char *p;
+ va_list args;
+
+ va_start(args, fmt);
+ p = fmt·vesprint(buf, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
diff --git a/src/libfmt/float.c b/src/libfmt/float.c
new file mode 100644
index 0000000..63ea80f
--- /dev/null
+++ b/src/libfmt/float.c
@@ -0,0 +1,1077 @@
+#define FDIGIT 30
+#define FDEFLT 6
+#define NSIGNIF 17
+
+static uvlong uvnan = ((uvlong)0x7FF00000<<32)|0x00000001;
+static uvlong uvinf = ((uvlong)0x7FF00000<<32)|0x00000000;
+static uvlong uvneginf = ((uvlong)0xFFF00000<<32)|0x00000000;
+
+static char *special[] = { "NaN", "NaN", "+Inf", "+Inf", "-Inf", "-Inf" };
+
+static int
+isNaN(double val)
+{
+ union{
+ uvlong i;
+ double f;
+ }x;
+
+ x.f = val;
+ return (x.i&uvinf) == uvinf && (x.i&~uvneginf) != 0;
+}
+
+static double
+NaN(void)
+{
+ union{
+ uvlong i;
+ double f;
+ }x;
+ x.i = uvnan;
+ return x.f;
+}
+
+static int
+isInf(double val, int sign)
+{
+ union{
+ uvlong i;
+ double f;
+ }x;
+
+ x.f = val;
+ if(sign == 0)
+ return x.i == uvinf || x.i == uvneginf;
+ else if(sign == 1)
+ return x.i == uvinf;
+ else
+ return x.i == uvneginf;
+}
+
+static double pows10[] =
+{
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
+ 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
+ 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
+ 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
+ 1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49,
+ 1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59,
+ 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69,
+ 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79,
+ 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, 1e89,
+ 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, 1e99,
+ 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
+ 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119,
+ 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129,
+ 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139,
+ 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149,
+ 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
+};
+
+static double
+fpow10(int n)
+{
+ double d;
+ int neg;
+
+ neg = 0;
+ if(n < 0){
+ neg = 1;
+ n = -n;
+ }
+
+ if(n<arrlen(pows10))
+ d = pows10[n];
+ else{
+ d = pows10[arrlen(pows10)-1];
+ for(;;){
+ n -= arrlen(pows10)- 1;
+ if(n < arrlen(pows10)){
+ d *= pows10[n];
+ break;
+ }
+ d *= pows10[arrlen(pows10)- 1];
+ }
+ }
+ if(neg)
+ return 1./d;
+ return d;
+}
+
+static int
+add1(char *a, int n)
+{
+ int c;
+ char *b;
+
+ if(n < 0 || n > NSIGNIF)
+ return 0;
+
+ for(b = a+n-1; b >= a; b--){
+ c = *b + 1;
+ if(c <= '9'){
+ *b = c;
+ return 0;
+ }
+ *b = '0';
+ }
+ /*
+ * need to overflow adding digit.
+ * shift number down and insert 1 at beginning.
+ * decimal is known to be 0s or we wouldn't
+ * have gotten this far. (e.g., 99999+1 => 00000)
+ */
+ a[0] = '1';
+ return 1;
+}
+
+static int
+sub1(char *a, int n)
+{
+ int c;
+ char *b;
+
+ if(n < 0 || n > NSIGNIF)
+ return 0;
+ for(b = a+n-1; b >= a; b--){
+ c = *b - 1;
+ if(c >= '0'){
+ if(c == '0' && b == a){
+ /*
+ * just zeroed the top digit; shift everyone up.
+ * decimal is known to be 9s or we wouldn't
+ * have gotten this far. (e.g., 10000-1 => 09999)
+ */
+ *b = '9';
+ return 1;
+ }
+ *b = c;
+ return 0;
+ }
+ *b = '9';
+ }
+ /*
+ * can't get here. the number a is always normalized
+ * so that it has a nonzero first digit.
+ */
+ abort();
+}
+
+// -----------------------------------------------------------------------
+// strtod
+
+#define Nbits 28
+#define Nmant 53
+#define Prec ((Nmant+Nbits+1)/Nbits)
+
+#define Sigbit (1<<(Prec*Nbits-Nmant)) /* first significant bit of Prec-th word */
+#define Ndig 1500
+#define One (ulong)(1<<Nbits)
+#define Half (ulong)(One>>1)
+#define Maxe 310
+
+#define Fsign (1<<0) /* found - */
+#define Fesign (1<<1) /* found e- */
+#define Fdpoint (1<<2) /* found . */
+
+#define S0 0 /* _ _S0 +S1 #S2 .S3 */
+#define S1 1 /* _+ #S2 .S3 */
+#define S2 2 /* _+# #S2 .S4 eS5 */
+#define S3 3 /* _+. #S4 */
+#define S4 4 /* _+#.# #S4 eS5 */
+#define S5 5 /* _+#.#e +S6 #S7 */
+#define S6 6 /* _+#.#e+ #S7 */
+#define S7 7 /* _+#.#e+# #S7 */
+
+typedef struct Tab Tab;
+struct Tab
+{
+ int bp;
+ int siz;
+ char *cmp;
+};
+
+static ulong
+umuldiv(ulong a, ulong b, ulong c)
+{
+ double d;
+
+ d = ((double)a * (double)b) / (double)c;
+ if(d >= 4294967295.)
+ d = 4294967295.;
+ return (ulong)d;
+}
+
+static void
+frnorm(ulong *f)
+{
+ int i, c;
+
+ c = 0;
+ for(i=Prec-1; i>0; i--) {
+ f[i] += c;
+ c = f[i] >> Nbits;
+ f[i] &= One-1;
+ }
+ f[0] += c;
+}
+
+static int
+fpcmp(char *a, ulong* f)
+{
+ ulong tf[Prec];
+ int i, d, c;
+
+ for(i=0; i<Prec; i++)
+ tf[i] = f[i];
+
+ for(;;) {
+ /* tf *= 10 */
+ for(i=0; i<Prec; i++)
+ tf[i] = tf[i]*10;
+ frnorm(tf);
+ d = (tf[0] >> Nbits) + '0';
+ tf[0] &= One-1;
+
+ /* compare next digit */
+ c = *a;
+ if(c == 0) {
+ if('0' < d)
+ return -1;
+ if(tf[0] != 0)
+ goto cont;
+ for(i=1; i<Prec; i++)
+ if(tf[i] != 0)
+ goto cont;
+ return 0;
+ }
+ if(c > d)
+ return +1;
+ if(c < d)
+ return -1;
+ a++;
+ cont:;
+}
+}
+
+static void
+divby(char *a, int *na, int b)
+{
+ int n, c;
+ char *p;
+
+ p = a;
+ n = 0;
+ while(n>>b == 0){
+ c = *a++;
+ if(c == 0) {
+ while(n) {
+ c = n*10;
+ if(c>>b)
+ break;
+ n = c;
+ }
+ goto xx;
+ }
+ n = n*10 + c-'0';
+ (*na)--;
+ }
+ for(;;){
+ c = n>>b;
+ n -= c<<b;
+ *p++ = c + '0';
+ c = *a++;
+ if(c == 0)
+ break;
+ n = n*10 + c-'0';
+ }
+ (*na)++;
+ xx:
+ while(n){
+ n = n*10;
+ c = n>>b;
+ n -= c<<b;
+ *p++ = c + '0';
+ (*na)++;
+ }
+ *p = 0;
+}
+
+static Tab tab1[] =
+{
+ 1, 0, "",
+ 3, 1, "7",
+ 6, 2, "63",
+ 9, 3, "511",
+ 13, 4, "8191",
+ 16, 5, "65535",
+ 19, 6, "524287",
+ 23, 7, "8388607",
+ 26, 8, "67108863",
+ 27, 9, "134217727",
+};
+
+static void
+divascii(char *a, int *na, int *dp, int *bp)
+{
+ int b, d;
+ Tab *t;
+
+ d = *dp;
+ if(d >= (int)(arrlen(tab1)))
+ d = (int)(arrlen(tab1))-1;
+ t = tab1 + d;
+ b = t->bp;
+ if(memcmp(a, t->cmp, t->siz) > 0)
+ d--;
+ *dp -= d;
+ *bp += b;
+ divby(a, na, b);
+}
+
+static void
+mulby(char *a, char *p, char *q, int b)
+{
+ int n, c;
+
+ n = 0;
+ *p = 0;
+ for(;;) {
+ q--;
+ if(q < a)
+ break;
+ c = *q - '0';
+ c = (c<<b) + n;
+ n = c/10;
+ c -= n*10;
+ p--;
+ *p = c + '0';
+ }
+ while(n) {
+ c = n;
+ n = c/10;
+ c -= n*10;
+ p--;
+ *p = c + '0';
+ }
+}
+
+static Tab tab2[] =
+{
+ 1, 1, "", /* dp = 0-0 */
+ 3, 3, "125",
+ 6, 5, "15625",
+ 9, 7, "1953125",
+ 13, 10, "1220703125",
+ 16, 12, "152587890625",
+ 19, 14, "19073486328125",
+ 23, 17, "11920928955078125",
+ 26, 19, "1490116119384765625",
+ 27, 19, "7450580596923828125", /* dp 8-9 */
+};
+
+static void
+mulascii(char *a, int *na, int *dp, int *bp)
+{
+ char *p;
+ int d, b;
+ Tab *t;
+
+ d = -*dp;
+ if(d >= (int)(arrlen(tab2)))
+ d = (int)(arrlen(tab2))-1;
+ t = tab2 + d;
+ b = t->bp;
+ if(memcmp(a, t->cmp, t->siz) < 0)
+ d--;
+ p = a + *na;
+ *bp -= b;
+ *dp += d;
+ *na += d;
+ mulby(a, p+d, p, b);
+}
+
+static int
+cmp(char *a, char *b)
+{
+ int c1, c2;
+
+ while((c1 = *b++) != '\0') {
+ c2 = *a++;
+ if(isupper(c2))
+ c2 = tolower(c2);
+ if(c1 != c2)
+ return 1;
+ }
+ return 0;
+}
+
+double
+fmtstrtod(char *as, char **aas)
+{
+ int na, ex, dp, bp, c, i, flag, state;
+ ulong low[Prec], hig[Prec], mid[Prec];
+ double d;
+ char *s, a[Ndig];
+
+ flag = 0; /* Fsign, Fesign, Fdpoint */
+ na = 0; /* number of digits of a[] */
+ dp = 0; /* na of decimal point */
+ ex = 0; /* exonent */
+
+ state = S0;
+ for(s=as;;s++){
+ c = *s;
+ if('0' <= c && c <= '9'){
+ switch(state){
+ case S0: case S1: case S2:
+ state = S2;
+ break;
+ case S3: case S4:
+ state = S4;
+ break;
+ case S5: case S6: case S7:
+ state = S7;
+ ex = ex*10 + (c-'0');
+ continue;
+ }
+
+ if(na == 0 && c == '0'){
+ dp--;
+ continue;
+ }
+ if(na < Ndig-50)
+ a[na++] = c;
+ continue;
+ }
+ switch(c){
+ case '\t': case '\n': case '\v': case '\f': case '\r': case ' ':
+ if(state == S0)
+ continue;
+ break;
+ case '-':
+ if(state == S0)
+ flag |= Fsign;
+ else
+ flag |= Fesign;
+ case '+':
+ if(state == S0)
+ state = S1;
+ else
+ if(state == S5)
+ state = S6;
+ else
+ break; /* syntax */
+ continue;
+ case '.':
+ flag |= Fdpoint;
+ dp = na;
+ if(state == S0 || state == S1){
+ state = S3;
+ continue;
+ }
+ if(state == S2){
+ state = S4;
+ continue;
+ }
+ break;
+ case 'e': case 'E':
+ if(state == S2 || state == S4){
+ state = S5;
+ continue;
+ }
+ break;
+ }
+ break;
+ }
+
+ /* clean up return char-pointer */
+ switch(state) {
+ case S0:
+ if(cmp(s, "nan") == 0){
+ if(aas != nil)
+ *aas = s+3;
+ goto retnan;
+ }
+ case S1:
+ if(cmp(s, "infinity") == 0){
+ if(aas != nil)
+ *aas = s+8;
+ goto retinf;
+ }
+ if(cmp(s, "inf") == 0){
+ if(aas != nil)
+ *aas = s+3;
+ goto retinf;
+ }
+ case S3:
+ if(aas != nil)
+ *aas = as;
+ goto ret0; /* no digits found */
+ case S6:
+ s--; /* back over +- */
+ case S5:
+ s--; /* back over e */
+ break;
+ }
+ if(aas != nil)
+ *aas = s;
+
+ if(flag & Fdpoint)
+ while(na > 0 && a[na-1] == '0')
+ na--;
+ if(na == 0)
+ goto ret0; /* zero */
+ a[na] = 0;
+ if(!(flag & Fdpoint))
+ dp = na;
+ if(flag & Fesign)
+ ex = -ex;
+ dp += ex;
+ if(dp < -Maxe){
+ errno = ERANGE;
+ goto ret0; /* underflow by exp */
+ } else
+ if(dp > +Maxe)
+ goto retinf; /* overflow by exp */
+
+ /*
+ * normalize the decimal ascii number
+ * to range .[5-9][0-9]* e0
+ */
+ bp = 0; /* binary exponent */
+ while(dp > 0)
+ divascii(a, &na, &dp, &bp);
+ while(dp < 0 || a[0] < '5')
+ mulascii(a, &na, &dp, &bp);
+
+ /* close approx by naive conversion */
+ mid[0] = 0;
+ mid[1] = 1;
+ for(i=0; (c=a[i]) != '\0'; i++) {
+ mid[0] = mid[0]*10 + (c-'0');
+ mid[1] = mid[1]*10;
+ if(i >= 8)
+ break;
+ }
+ low[0] = umuldiv(mid[0], One, mid[1]);
+ hig[0] = umuldiv(mid[0]+1, One, mid[1]);
+ for(i=1; i<Prec; i++) {
+ low[i] = 0;
+ hig[i] = One-1;
+ }
+
+ /* binary search for closest mantissa */
+ for(;;) {
+ /* mid = (hig + low) / 2 */
+ c = 0;
+ for(i=0; i<Prec; i++) {
+ mid[i] = hig[i] + low[i];
+ if(c)
+ mid[i] += One;
+ c = mid[i] & 1;
+ mid[i] >>= 1;
+ }
+ frnorm(mid);
+
+ /* compare */
+ c = fpcmp(a, mid);
+ if(c > 0) {
+ c = 1;
+ for(i=0; i<Prec; i++)
+ if(low[i] != mid[i]) {
+ c = 0;
+ low[i] = mid[i];
+ }
+ if(c)
+ break; /* between mid and hig */
+ continue;
+ }
+ if(c < 0) {
+ for(i=0; i<Prec; i++)
+ hig[i] = mid[i];
+ continue;
+ }
+
+ /* only hard part is if even/odd roundings wants to go up */
+ c = mid[Prec-1] & (Sigbit-1);
+ if(c == Sigbit/2 && (mid[Prec-1]&Sigbit) == 0)
+ mid[Prec-1] -= c;
+ break; /* exactly mid */
+ }
+
+ /* normal rounding applies */
+ c = mid[Prec-1] & (Sigbit-1);
+ mid[Prec-1] -= c;
+ if(c >= Sigbit/2) {
+ mid[Prec-1] += Sigbit;
+ frnorm(mid);
+ }
+ goto out;
+
+ret0:
+ return 0;
+
+retnan:
+ return NaN();
+
+retinf:
+ /* Unix strtod requires these. Plan 9 would return Inf(0) or Inf(-1). */
+ errno = ERANGE;
+ if(flag & Fsign)
+ return -HUGE_VAL;
+ return HUGE_VAL;
+
+out:
+ d = 0;
+ for(i=0; i<Prec; i++)
+ d = d*One + mid[i];
+ if(flag & Fsign)
+ d = -d;
+ d = ldexp(d, bp - Prec*Nbits);
+ if(d == 0) /* underflow */
+ errno = ERANGE;
+
+ return d;
+}
+
+#undef Nbits
+#undef Nmant
+#undef Prec
+
+#undef Sigbit
+#undef Ndig
+#undef One
+#undef Half
+#undef Maxe
+
+#undef Fsign
+#undef Fesign
+#undef Fdpoint
+
+#undef S0
+#undef S1
+#undef S2
+#undef S3
+#undef S4
+#undef S5
+#undef S6
+#undef S7
+
+static void
+fmtexp(char *p, int e, int ucase)
+{
+ int i;
+ char se[9];
+
+ *p++ = ucase ? 'E' : 'e';
+ if(e < 0){
+ *p++ = '-';
+ e = -e;
+ }else
+ *p++ = '+';
+
+ i = 0;
+ while(e){
+ se[i++] = e % 10 + '0';
+ e /= 10;
+ }
+
+ while(i < 2)
+ se[i++] = '0';
+ while(i > 0)
+ *p++ = se[--i];
+
+ *p++ = '\0';
+}
+
+/*
+ * compute decimal integer m, exp such that:
+ * f = m*10^exp
+ * m is as short as possible with losing exactness
+ * assumes special cases (NaN, +Inf, -Inf) have been handled.
+ */
+static void
+dtoa(double f, char *s, int *exp, int *neg, int *len)
+{
+ int c, d, e2, e, ee, i, ndigit, oerrno;
+ char buf[NSIGNIF+10];
+ double g;
+
+ oerrno = errno;
+
+ *neg = 0;
+ if(f < 0){
+ f = -f;
+ *neg = 1;
+ }
+
+ if(f == 0){
+ *exp = 0;
+ s[0] = '0';
+ s[1] = 0;
+ *len = 1;
+ return;
+ }
+
+ frexp(f, &e2);
+ e = (int)(e2 * .301029995664);
+ g = f * fpow10(-e);
+ while(g < 1) {
+ e--;
+ g = f * fpow10(-e);
+ }
+ while(g >= 10){
+ e++;
+ g = f * fpow10(-e);
+ }
+
+ /* convert nsignif digits as a first approximation */
+ for(i=0; i<NSIGNIF; i++){
+ d = (int)g;
+ s[i] = d+'0';
+ g = (g-d)*10;
+ }
+ s[i] = 0;
+
+ e -= NSIGNIF-1;
+ fmtexp(s+NSIGNIF, e, 0);
+
+ for(i=0; i<10; i++) {
+ g=fmtstrtod(s, nil);
+ if(f > g) {
+ if(add1(s, NSIGNIF)){
+ /* gained a digit */
+ e--;
+ fmtexp(s+NSIGNIF, e, 0);
+ }
+ continue;
+ }
+ if(f < g){
+ if(sub1(s, NSIGNIF)){
+ /* lost a digit */
+ e++;
+ fmtexp(s+NSIGNIF, e, 0);
+ }
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * bump last few digits down to 0 as we can.
+ */
+ for(i=NSIGNIF-1; i>=NSIGNIF-3; i--){
+ c = s[i];
+ if(c != '0'){
+ s[i] = '0';
+ g=fmtstrtod(s, nil);
+ if(g != f){
+ s[i] = c;
+ break;
+ }
+ }
+ }
+
+ /*
+ * remove trailing zeros.
+ */
+ ndigit = NSIGNIF;
+ while(ndigit > 1 && s[ndigit-1] == '0'){
+ e++;
+ --ndigit;
+ }
+ s[ndigit] = 0;
+ *exp = e;
+ *len = ndigit;
+
+ errno = oerrno;
+}
+
+
+static int
+fmtfloat(fmt·State *io)
+{
+ char buf[NSIGNIF+10], *dot, *digits, *p, *end, suf[10], *cur;
+ double val;
+ int c, verb, ndot, e, exp, f, ndigits, neg, newndigits;
+ int npad, pt, prec, realverb, sign, nsuf, ucase, n, z1, z2;
+
+ if(io->flag&fmt·Long)
+ val = va_arg(io->args, long double);
+ else
+ val = va_arg(io->args, double);
+
+ /* extract formatting flags */
+ f = io->flag;
+ io->flag = 0;
+ prec = FDEFLT;
+ if(f & fmt·Prec)
+ prec = io->prec;
+
+ verb = io->verb;
+ ucase = 0;
+ switch(verb) {
+ case 'A':
+ case 'E':
+ case 'F':
+ case 'G':
+ verb += 'a'-'A';
+ ucase = 1;
+ break;
+ }
+
+ /* pick off special numbers. */
+ if(isNaN(val)) {
+ end = special[0+ucase];
+ special:
+ io->flag = f & (fmt·Width|fmt·Left);
+ return copy(io, end, strlen(end), strlen(end));
+ }
+ if(isInf(val, 1)) {
+ end = special[2+ucase];
+ goto special;
+ }
+ if(isInf(val, -1)) {
+ end = special[4+ucase];
+ goto special;
+ }
+
+ /* get exact representation. */
+ digits = buf;
+ dtoa(val, digits, &exp, &neg, &ndigits);
+
+ /* get locale's decimal point. */
+ dot = io->decimal;
+ if(dot == nil)
+ dot = ".";
+ ndot = utf8·len(dot);
+
+ /*
+ * now the formatting fun begins.
+ * compute parameters for actual fmt:
+ *
+ * pad: number of spaces to insert before/after field.
+ * z1: number of zeros to insert before digits
+ * z2: number of zeros to insert after digits
+ * point: number of digits to print before decimal point
+ * ndigits: number of digits to use from digits[]
+ * suf: trailing suffix, like "e-5"
+ */
+ realverb = verb;
+ switch(verb){
+ case 'g':
+ /* convert to at most prec significant digits. (prec=0 means 1) */
+ if(prec == 0)
+ prec = 1;
+ if(ndigits > prec) {
+ if(digits[prec] >= '5' && add1(digits, prec))
+ exp++;
+ exp += ndigits-prec;
+ ndigits = prec;
+ }
+
+ /*
+ * extra rules for %g (implemented below):
+ * trailing zeros removed after decimal unless FmtSharp.
+ * decimal point only if digit follows.
+ */
+
+ /* fall through to %e */
+ default:
+ case 'e':
+ /* one significant digit before decimal, no leading zeros. */
+ pt = 1;
+ z1 = 0;
+
+ /*
+ * decimal point is after ndigits digits right now.
+ * slide to be after first.
+ */
+ e = exp + (ndigits-1);
+
+ /* if this is %g, check exponent and convert prec */
+ if(realverb == 'g') {
+ if(-4 <= e && e < prec)
+ goto casef;
+ prec--; /* one digit before decimal; rest after */
+ }
+
+ /* compute trailing zero padding or truncate digits. */
+ if(1+prec >= ndigits)
+ z2 = 1+prec - ndigits;
+ else {
+ /* truncate digits */
+ assert(realverb != 'g');
+ newndigits = 1+prec;
+ if(digits[newndigits] >= '5' && add1(digits, newndigits)) {
+ /* had 999e4, now have 100e5 */
+ e++;
+ }
+ ndigits = newndigits;
+ z2 = 0;
+ }
+ fmtexp(suf, e, ucase);
+ nsuf = strlen(suf);
+ break;
+
+ casef:
+ case 'f':
+ /* determine where digits go with respect to decimal point */
+ if(ndigits+exp > 0) {
+ pt = ndigits+exp;
+ z1 = 0;
+ } else {
+ pt = 1;
+ z1 = 1 + -(ndigits+exp);
+ }
+
+ /*
+ * %g specifies prec = number of significant digits
+ * convert to number of digits after decimal point
+ */
+ if(realverb == 'g')
+ prec += z1 - pt;
+
+ /* compute trailing zero padding or truncate digits. */
+ if(pt+prec >= z1+ndigits)
+ z2 = pt+prec - (z1+ndigits);
+ else{
+ /* truncate digits */
+ assert(realverb != 'g');
+ newndigits = pt+prec - z1;
+ if(newndigits < 0){
+ z1 += newndigits;
+ newndigits = 0;
+ }else if(newndigits == 0){
+ /* perhaps round up */
+ if(digits[0] >= '5'){
+ digits[0] = '1';
+ newndigits = 1;
+ goto newdigit;
+ }
+ }else if(digits[newndigits] >= '5' && add1(digits, newndigits)){
+ /* digits was 999, is now 100; make it 1000 */
+ digits[newndigits++] = '0';
+ newdigit:
+ /* account for new digit */
+ if(z1) /* 0.099 => 0.100 or 0.99 => 1.00*/
+ z1--;
+ else /* 9.99 => 10.00 */
+ pt++;
+ }
+ z2 = 0;
+ ndigits = newndigits;
+ }
+ nsuf = 0;
+ break;
+ }
+
+ /*
+ * if %g is given without FmtSharp, remove trailing zeros.
+ * must do after truncation, so that e.g. print %.3g 1.001
+ * produces 1, not 1.00. sorry, but them's the rules.
+ */
+ if(realverb == 'g' && !(f & fmt·Sharp)) {
+ if(z1+ndigits+z2 >= pt) {
+ if(z1+ndigits < pt)
+ z2 = pt - (z1+ndigits);
+ else{
+ z2 = 0;
+ while(z1+ndigits > pt && digits[ndigits-1] == '0')
+ ndigits--;
+ }
+ }
+ }
+
+ /*
+ * compute width of all digits and decimal point and suffix if any
+ */
+ n = z1+ndigits+z2;
+ if(n > pt)
+ n += ndot;
+ else if(n == pt){
+ if(f & fmt·Sharp)
+ n += ndot;
+ else
+ pt++; /* do not print any decimal point */
+ }
+ n += nsuf;
+
+ /*
+ * determine sign
+ */
+ sign = 0;
+ if(neg)
+ sign = '-';
+ else if(f & fmt·Sign)
+ sign = '+';
+ else if(f & fmt·Space)
+ sign = ' ';
+ if(sign)
+ n++;
+
+ /* compute padding */
+ npad = 0;
+ if((f & fmt·Width) && io->width > n)
+ npad = io->width - n;
+ if(npad && !(f & fmt·Left) && (f & fmt·Zero)){
+ z1 += npad;
+ pt += npad;
+ npad = 0;
+ }
+
+ /* format the actual field. too bad about doing this twice. */
+ if(npad && !(f & fmt·Left) && pad(io, npad < 0))
+ return -1;
+
+ cur = io->buffer.cur;
+ end = io->buffer.end;
+
+ if(sign){
+ if(cur+1 > end){
+ if(!(cur=flush(io,cur,1)))
+ return -1;
+ end = io->buffer.end;
+ }
+ *cur++ = sign;
+ }
+
+ while(z1>0 || ndigits>0 || z2>0){
+ if(z1 > 0){
+ z1--;
+ c = '0';
+ }else if(ndigits > 0){
+ ndigits--;
+ c = *digits++;
+ }else{
+ z2--;
+ c = '0';
+ }
+
+ if(cur+1 > end){
+ if(!(cur=flush(io,cur,1)))
+ return -1;
+ end = io->buffer.end;
+ }
+ *cur++ = c;
+
+ if(--pt == 0)
+ for(p=dot; *p; p++){
+ if(cur+1 > end){
+ if(!(cur=flush(io,cur,1)))
+ return -1;
+ end = io->buffer.end;
+ }
+ *cur++ = *p;
+ }
+ }
+ io->n += cur - (char*)io->buffer.cur;
+ io->buffer.cur = cur;
+ if(nsuf && copy(io, suf, nsuf, nsuf) < 0)
+ return -1;
+ if(npad && (f & fmt·Left) && pad(io, npad < 0))
+ return -1;
+
+ return 0;
+}
diff --git a/src/libfmt/fprint.c b/src/libfmt/fprint.c
new file mode 100644
index 0000000..26343f7
--- /dev/null
+++ b/src/libfmt/fprint.c
@@ -0,0 +1,14 @@
+#include "internal.h"
+
+int
+fprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = fmt·vfprint(fd, fmt, args);
+ va_end(args);
+
+ return n;
+}
diff --git a/src/libfmt/internal.h b/src/libfmt/internal.h
new file mode 100644
index 0000000..725cfff
--- /dev/null
+++ b/src/libfmt/internal.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+#include <libfmt.h>
+
+typedef int (*Formatter)(fmt·State *io);
+typedef struct Verb Verb;
+
+struct Verb
+{
+ int c;
+ Formatter fmt;
+};
+
+void fmt·setlocale(fmt·State *io, char *decimal, char *thousands, char *groups);
diff --git a/src/libfmt/locale.c b/src/libfmt/locale.c
new file mode 100644
index 0000000..437c61e
--- /dev/null
+++ b/src/libfmt/locale.c
@@ -0,0 +1,16 @@
+#include "internal.h"
+
+void
+fmt·setlocale(fmt·State *io, char *decimal, char *thousands, char *groups)
+{
+ if(decimal == nil || decimal[0] == '\0')
+ decimal = ".";
+ if(thousands == nil)
+ thousands = ",";
+ if(groups == nil)
+ groups = "\3";
+
+ io->groups = groups;
+ io->decimal = decimal;
+ io->thousands = thousands;
+}
diff --git a/src/libfmt/nsprint.c b/src/libfmt/nsprint.c
new file mode 100644
index 0000000..90489e0
--- /dev/null
+++ b/src/libfmt/nsprint.c
@@ -0,0 +1,14 @@
+#include "internal.h"
+
+int
+fmt·nsprint(int len, char *buf, char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = fmt·vnsprint(len, buf, fmt, args);
+ va_end(args);
+
+ return n;
+}
diff --git a/src/libfmt/open.c b/src/libfmt/open.c
new file mode 100644
index 0000000..8aadef5
--- /dev/null
+++ b/src/libfmt/open.c
@@ -0,0 +1,34 @@
+#include "internal.h"
+
+static int
+flush(fmt·State *io)
+{
+ int n, fd;
+
+ fd = (uintptr)io->file;
+ n = io->buffer.cur - io->buffer.beg;
+ if(n && write(fd, io->buffer.beg, n) != n)
+ return -1;
+
+ io->buffer.cur = io->buffer.beg;
+ return io->n;
+}
+
+int
+fmt·open(int fd, int len, char *buf, fmt·State *io)
+{
+ io->buffer.beg = buf;
+ io->buffer.cur = buf;
+ io->buffer.end = buf+len;
+ io->flush = flush;
+ io->file = (void*)(uintptr)fd;
+ io->flag = 0;
+ io->n = 0;
+ /* no heap needed */
+ io->heap = nil;
+ io->mem = (mem·Reallocator){ 0 };
+
+ fmt·setlocale(io, nil, nil, nil);
+
+ return 0;
+}
diff --git a/src/libfmt/print.c b/src/libfmt/print.c
new file mode 100644
index 0000000..20b8e00
--- /dev/null
+++ b/src/libfmt/print.c
@@ -0,0 +1,13 @@
+#include "internal.h"
+
+int
+fmt·print(char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = fmt·vfprint(1, fmt, args);
+ va_end(args);
+ return n;
+}
diff --git a/src/libfmt/rules.mk b/src/libfmt/rules.mk
new file mode 100644
index 0000000..9080bba
--- /dev/null
+++ b/src/libfmt/rules.mk
@@ -0,0 +1,35 @@
+include share/push.mk
+
+# Local sources
+SRCS_$(d):=\
+ $(d)/buffer.c\
+ $(d)/do.c\
+ $(d)/esprint.c\
+ $(d)/fprint.c\
+ $(d)/locale.c\
+ $(d)/nsprint.c\
+ $(d)/open.c\
+ $(d)/print.c\
+ $(d)/sprint.c\
+ $(d)/vesprint.c\
+ $(d)/vfprint.c\
+ $(d)/vnsprint.c\
+ $(d)/vprint.c\
+ $(d)/vwrite.c\
+ $(d)/write.c
+
+LIBS_$(d):=\
+ $(d)/libfmt.a
+
+CHECK_$(d):=\
+ $(d)/test.c
+
+include share/paths.mk
+
+$(LIBS_$(d)): $(OBJS_$(d))
+ $(ARCHIVE)
+
+$(TEST_$(d)): $(UNIT_$(d)) $(LIBS_$(d)) $(OBJ_DIR)/libutf/libutf.a $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/src/libfmt/sprint.c b/src/libfmt/sprint.c
new file mode 100644
index 0000000..f1be6dd
--- /dev/null
+++ b/src/libfmt/sprint.c
@@ -0,0 +1,19 @@
+#include "internal.h"
+
+int
+fmt·sprint(char *buf, char *fmt, ...)
+{
+ int n;
+ uint len;
+ va_list args;
+
+ len = 1 << 30;
+ if(buf+len < buf)
+ len = -(uintptr)buf-1;
+
+ va_start(args, fmt);
+ n = fmt·vnsprint(len, buf, fmt, args);
+ va_end(args);
+
+ return n;
+}
diff --git a/src/libfmt/test.c b/src/libfmt/test.c
new file mode 100644
index 0000000..d81a62e
--- /dev/null
+++ b/src/libfmt/test.c
@@ -0,0 +1,72 @@
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+#include <libfmt.h>
+
+typedef struct Complex
+{
+ double r, i;
+} Complex;
+
+int
+Xfmt(fmt·State *io)
+{
+ Complex c;
+ c = va_arg(io->args, Complex);
+
+ return fmt·write(io, "(real=%g,imag=%g)", c.r, c.i);
+}
+
+int
+main(int argc, char *argv[])
+{
+ fmt·print("basic tests\n");
+ fmt·print("\tx: %x\n", 0x87654321);
+ fmt·print("\tu: %u\n", 0x87654321);
+ fmt·print("\td: %d\n", 0x87654321);
+ fmt·print("\ts: %s\n", "hi there");
+ fmt·print("\tc: %c\n", '!');
+ fmt·print("\tg: %g %g %g\n", 3.14159, 3.14159e10, 3.14159e-10);
+ fmt·print("\te: %e %e %e\n", 3.14159, 3.14159e10, 3.14159e-10);
+ fmt·print("\tf: %f %f %f\n", 3.14159, 3.14159e10, 3.14159e-10);
+ fmt·print("\tsmiley: %C\n", (rune)0x263a);
+ fmt·print("\t%g %.18g\n", 2e25, 2e25);
+ fmt·print("\t%2.18g\n", 1.0);
+ fmt·print("\t%2.18f\n", 1.0);
+ fmt·print("\t%f\n", 3.1415927/4);
+ fmt·print("\t%d\n", 23);
+ fmt·print("\t%i\n", 23);
+ fmt·print("\t%0.10d\n", 12345);
+
+ fmt·print("%%4%%d tests\n");
+ fmt·print("\t%3$d %4$06d %2$d %1$d\n", 444, 333, 111, 222);
+ fmt·print("\t%3$d %4$06d %2$d %1$d\n", 444, 333, 111, 222);
+ fmt·print("\t%3$d %4$*5$06d %2$d %1$d\n", 444, 333, 111, 222, 20);
+ fmt·print("\t%3$hd %4$*5$06d %2$d %1$d\n", 444, 333, (short)111, 222, 20);
+ fmt·print("\t%3$lld %4$*5$06d %2$d %1$d\n", 444, 333, 111LL, 222, 20);
+
+ /* test %'d formats */
+ fmt·print("%%'%%d tests\n");
+ fmt·print("\t%'d %'d %'d\n", 1, 2222, 33333333);
+ fmt·print("\t%'019d\n", 0);
+ fmt·print("\t%08d %08d %08d\n", 1, 2222, 33333333);
+ fmt·print("\t%'08d %'08d %'08d\n", 1, 2222, 33333333);
+ fmt·print("\t%'x %'X %'b\n", 0x11111111, 0xabcd1234, 12345);
+ fmt·print("\t%'lld %'lld %'lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%019lld %019lld %019lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%'019lld %'019lld %'019lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%'020lld %'020lld %'020lld\n", 1LL, 222222222LL, 3333333333333LL);
+ fmt·print("\t%'llx %'llX %'llb\n", 0x111111111111LL, 0xabcd12345678LL, 112342345LL);
+
+ /* test precision */
+ fmt·print("precision tests\n");
+ fmt·print("%020.10d\n", 100);
+
+ /* test install */
+ fmt·install('X', Xfmt);
+ Complex c = { 1.5, -2.3 };
+ fmt·print("x = %X\n", c);
+
+ return 0;
+
+}
diff --git a/src/libfmt/vesprint.c b/src/libfmt/vesprint.c
new file mode 100644
index 0000000..18f4dd2
--- /dev/null
+++ b/src/libfmt/vesprint.c
@@ -0,0 +1,26 @@
+#include "internal.h"
+
+char*
+fmt·vesprint(char *buf, char *end, char *fmt, va_list args)
+{
+ fmt·State io;
+
+ if(end <= buf)
+ return nil;
+
+ io.n = 0;
+ io.buffer.beg = io.buffer.cur = buf;
+ io.buffer.end = end-1;
+ io.flush = nil;
+ io.file = nil;
+
+ va_copy(io.args, args);
+
+ fmt·setlocale(&io, nil, nil, nil);
+ fmt·do(&io, fmt);
+
+ va_end(io.args);
+
+ *(io.buffer.cur) = 0;
+ return io.buffer.cur;
+}
diff --git a/src/libfmt/vfprint.c b/src/libfmt/vfprint.c
new file mode 100644
index 0000000..4306ea7
--- /dev/null
+++ b/src/libfmt/vfprint.c
@@ -0,0 +1,19 @@
+#include "internal.h"
+
+int
+fmt·vfprint(int fd, char *fmt, va_list args)
+{
+ int n;
+ fmt·State io;
+ char buf[256];
+
+ fmt·open(fd, sizeof(buf), buf, &io);
+
+ va_copy(io.args, args);
+ n = fmt·do(&io, fmt);
+ va_end(io.args);
+
+ if(n > 0 && io.flush(&io) < 0)
+ return -1;
+ return n;
+}
diff --git a/src/libfmt/vnsprint.c b/src/libfmt/vnsprint.c
new file mode 100644
index 0000000..7ded908
--- /dev/null
+++ b/src/libfmt/vnsprint.c
@@ -0,0 +1,26 @@
+#include "internal.h"
+
+int
+fmt·vnsprint(int len, char *buf, char *fmt, va_list args)
+{
+ fmt·State io;
+
+ if(len <= 0)
+ return -1;
+
+ io.n = 0;
+ io.buffer.beg = io.buffer.cur = buf;
+ io.buffer.end = buf+len-1;
+ io.flush = nil;
+ io.file = nil;
+
+ va_copy(io.args, args);
+
+ fmt·setlocale(&io, nil, nil, nil);
+ fmt·do(&io, fmt);
+
+ va_end(io.args);
+
+ *(io.buffer.cur) = 0;
+ return io.buffer.cur - io.buffer.beg;
+}
diff --git a/src/libfmt/vprint.c b/src/libfmt/vprint.c
new file mode 100644
index 0000000..bb3076b
--- /dev/null
+++ b/src/libfmt/vprint.c
@@ -0,0 +1,19 @@
+#include "internal.h"
+
+int
+fmt·vprint(char *fmt, va_list args)
+{
+ fmt·State io;
+ int n;
+ char buf[256];
+
+ fmt·open(1, sizeof(buf), buf, &io);
+
+ va_copy(io.args, args);
+ n = fmt·do(&io, fmt);
+ va_end(io.args);
+
+ if(n > 0 && io.flush(&io) < 0)
+ return -1;
+ return n;
+}
diff --git a/src/libfmt/vwrite.c b/src/libfmt/vwrite.c
new file mode 100644
index 0000000..cacdef2
--- /dev/null
+++ b/src/libfmt/vwrite.c
@@ -0,0 +1,26 @@
+#include "internal.h"
+
+int
+fmt·vwrite(fmt·State *io, char *fmt, va_list args)
+{
+ int n;
+ va_list tmp;
+
+ io->flag = io->width = io->prec = 0;
+
+ va_copy(tmp, io->args);
+ va_end(io->args);
+
+ va_copy(io->args,args);
+ n = fmt·do(io, fmt);
+ va_end(io->args);
+
+ va_copy(io->args, tmp);
+ va_end(tmp);
+
+ io->flag = io->width = io->prec = 0;
+
+ if(n >= 0)
+ return 0;
+ return n;
+}
diff --git a/src/libfmt/write.c b/src/libfmt/write.c
new file mode 100644
index 0000000..9a77223
--- /dev/null
+++ b/src/libfmt/write.c
@@ -0,0 +1,22 @@
+#include "internal.h"
+
+int
+fmt·write(fmt·State *io, char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ io->flag = io->width = io->prec = 0;
+
+ va_copy(args, io->args);
+ va_end(io->args);
+
+ va_start(io->args, fmt);
+ n = fmt·do(io, fmt);
+ va_end(io->args);
+
+ io->flag = io->width = io->prec = 0;
+ if(n >= 0)
+ return 0;
+ return n;
+}
diff --git a/src/libmath/basic.c b/src/libmath/basic.c
new file mode 100644
index 0000000..1341f7b
--- /dev/null
+++ b/src/libmath/basic.c
@@ -0,0 +1,531 @@
+#include <u.h>
+#include <base.h>
+#include <libmath.h>
+
+#include <math.h>
+
+// TODO(nnoll): Replace implementations with your own.
+
+double
+math·acos(double x)
+{
+ return acos(x);
+}
+
+float
+math·acosf(float x)
+{
+ return acosf(x);
+}
+
+
+double
+math·acosh(double x)
+{
+ return acosh(x);
+}
+
+float
+math·acoshf(float x)
+{
+ return acoshf(x);
+}
+
+
+double
+math·asin(double x)
+{
+ return asin(x);
+}
+
+float
+math·asinf(float x)
+{
+ return asinf(x);
+}
+
+
+double
+math·asinh(double x)
+{
+ return asinh(x);
+}
+
+float
+math·asinhf(float x)
+{
+ return asinhf(x);
+}
+
+
+double
+math·atan(double x)
+{
+ return atan(x);
+}
+
+float
+math·atanf(float x)
+{
+ return atanf(x);
+}
+
+
+double
+math·atan2(double x, double y)
+{
+ return atan2(x, y);
+}
+
+float
+math·atan2f(float x, float y)
+{
+ return atan2f(x, y);
+}
+
+double
+math·atanh(double x)
+{
+ return atanh(x);
+}
+
+float
+math·atanhf(float x)
+{
+ return atanhf(x);
+}
+
+
+double
+math·cbrt(double x)
+{
+ return cbrt(x);
+}
+
+float
+math·cbrtf(float x)
+{
+ return cbrtf(x);
+}
+
+
+double
+math·ceil(double x)
+{
+ return ceil(x);
+}
+
+float
+math·ceilf(float x)
+{
+ return ceilf(x);
+}
+
+double
+math·cos(double x)
+{
+ return cos(x);
+}
+
+float
+math·cosf(float x)
+{
+ return cosf(x);
+}
+
+
+double
+math·cosh(double x)
+{
+ return cosh(x);
+}
+
+float
+math·coshf(float x)
+{
+ return coshf(x);
+}
+
+
+double
+math·erf(double x)
+{
+ return erf(x);
+}
+
+float
+math·erff(float x)
+{
+ return erff(x);
+}
+
+
+double
+math·erfc(double x)
+{
+ return erfc(x);
+}
+
+float
+math·erfcf(float x)
+{
+ return erfcf(x);
+}
+
+
+double
+math·exp(double x)
+{
+ return exp(x);
+}
+
+float
+math·expf(float x)
+{
+ return expf(x);
+}
+
+
+double
+math·exp2(double x)
+{
+ return exp2(x);
+}
+
+float
+math·exp2f(float x)
+{
+ return exp2f(x);
+}
+
+
+double
+math·expm1(double x)
+{
+ return expm1(x);
+}
+
+float
+math·expm1f(float x)
+{
+ return expm1f(x);
+}
+
+
+double
+math·floor(double x)
+{
+ return floor(x);
+}
+
+float
+math·floorf(float x)
+{
+ return floorf(x);
+}
+
+
+int
+math·ilogb(double x)
+{
+ return ilogb(x);
+}
+
+int
+math·ilogbf(float x)
+{
+ return ilogbf(x);
+}
+
+double
+math·lgamma(double x)
+{
+ return lgamma(x);
+}
+
+float
+math·lgammaf(float x)
+{
+ return lgammaf(x);
+}
+
+
+vlong
+math·llrint(double x)
+{
+ return math·llrint(x);
+}
+
+vlong
+math·llrintf(float x)
+{
+ return math·llrintf(x);
+}
+
+
+vlong
+math·llround(double x)
+{
+ return llround(x);
+}
+
+vlong
+math·llroundf(float x)
+{
+ return llroundf(x);
+}
+
+
+double
+math·log(double x)
+{
+ return log(x);
+}
+
+float
+math·logf(float x)
+{
+ return logf(x);
+}
+
+
+double
+math·log10(double x)
+{
+ return log10(x);
+}
+
+float
+math·log10f(float x)
+{
+ return log10f(x);
+}
+
+
+double
+math·log1p(double x)
+{
+ return log1p(x);
+}
+
+float
+math·log1pf(float x)
+{
+ return log1pf(x);
+}
+
+
+double
+math·log2(double x)
+{
+ return log2(x);
+}
+
+float
+math·log2f(float x)
+{
+ return log2f(x);
+}
+
+
+double
+math·logb(double x)
+{
+ return logb(x);
+}
+
+float
+math·logbf(float x)
+{
+ return logbf(x);
+}
+
+
+long
+math·lrint(double x)
+{
+ return lrint(x);
+}
+
+long
+math·lrintf(float x)
+{
+ return lrintf(x);
+}
+
+
+long
+math·lround(double x)
+{
+ return lround(x);
+}
+
+long
+math·lroundf(float x)
+{
+ return lroundf(x);
+}
+
+
+double math·modf(double, double *);
+float math·modff(float, float *);
+
+double
+math·nan(const char * x)
+{
+ return nan(x);
+}
+
+float
+math·nanf(const char * x)
+{
+ return nanf(x);
+}
+
+
+double
+math·nearbyint(double x)
+{
+ return nearbyint(x);
+}
+
+float
+math·nearbyintf(float x)
+{
+ return nearbyintf(x);
+}
+
+
+double
+math·pow(double x, double exp)
+{
+ return pow(x, exp);
+}
+
+float
+math·powf(float x, float exp)
+{
+ return powf(x, exp);
+}
+
+double
+math·rint(double x)
+{
+ return rint(x);
+}
+
+float
+math·rintf(float x)
+{
+ return rintf(x);
+}
+
+
+double
+math·round(double x)
+{
+ return round(x);
+}
+
+float
+math·roundf(float x)
+{
+ return roundf(x);
+}
+
+
+double math·scalbln(double, long);
+float math·scalblnf(float, long);
+
+double math·scalbn(double, int);
+float math·scalbnf(float, int);
+
+double
+math·sin(double x)
+{
+ return sin(x);
+}
+
+float
+math·sinf(float x)
+{
+ return sinf(x);
+}
+
+
+double
+math·sinh(double x)
+{
+ return sinh(x);
+}
+
+float
+math·sinhf(float x)
+{
+ return sinhf(x);
+}
+
+
+double
+math·sqrt(double x)
+{
+ return sqrt(x);
+}
+
+float
+math·sqrtf(float x)
+{
+ return sqrtf(x);
+}
+
+
+double
+math·tan(double x)
+{
+ return tan(x);
+}
+
+float
+math·tanf(float x)
+{
+ return tanf(x);
+}
+
+
+double
+math·tanh(double x)
+{
+ return tanh(x);
+}
+
+float
+math·tanhf(float x)
+{
+ return tanhf(x);
+}
+
+
+double
+math·tgamma(double x)
+{
+ return tgamma(x);
+}
+
+float
+math·tgammaf(float x)
+{
+ return tgammaf(x);
+}
+
+
+double
+math·trunc(double x)
+{
+ return trunc(x);
+}
+
+float
+math·truncf(float x)
+{
+ return truncf(x);
+}
diff --git a/src/libmath/blas.c b/src/libmath/blas.c
new file mode 100644
index 0000000..18f9760
--- /dev/null
+++ b/src/libmath/blas.c
@@ -0,0 +1,63 @@
+#include <u.h>
+#include <base.h>
+#include <libmath.h>
+#include <libmath/blas.h>
+#include <time.h>
+
+/* #include <vendor/blas/cblas.h> */
+
+#define NCOL 2*512
+#define NROW 2*512
+#define NSUM 2*512
+#define NIT 10
+#define INC 1
+error
+main()
+{
+ int i, j, nit;
+ double *x, *y, *z, *w, res[2];
+
+ clock_t t;
+ double tprof[2] = { 0 };
+
+ rng·init(0);
+
+ x = malloc(sizeof(*x)*NROW*NCOL);
+ y = malloc(sizeof(*x)*NROW*NCOL);
+ z = malloc(sizeof(*x)*NROW*NCOL);
+ w = malloc(sizeof(*x)*NROW*NCOL);
+
+#define DO_0 t = clock(); \
+ blas·dgemm(0,0,NROW,NCOL,NSUM,10.1,x,NROW,y,NROW,1.2,z,NROW);\
+ t = clock() - t; \
+ res[0] += blas·dasum(NROW*NCOL,z,INC); \
+ tprof[0] += 1000.*t/CLOCKS_PER_SEC; \
+
+#define DO_1 t = clock(); \
+ cblas_dgemm(CblasRowMajor,CblasNoTrans,CblasNoTrans,NROW,NCOL,NSUM,10.1,x,NROW,y,NROW,1.2,w,NROW);\
+ t = clock() - t; \
+ res[1] += cblas_dasum(NROW*NCOL,w,INC); \
+ tprof[1] += 1000.*t/CLOCKS_PER_SEC;
+
+ for (nit = 0; nit < NIT; nit++) {
+ for (i = 0; i < NROW; i++) {
+ for (j = 0; j < NCOL; j++) {
+ x[j + NROW*i] = rng·random();
+ y[j + NROW*i] = rng·random();
+ z[j + NROW*i] = rng·random();
+ w[j + NROW*i] = z[j + NROW*i];
+ }
+ }
+
+ switch (nit % 2) {
+ case 0: DO_0; DO_1; break;
+ case 1: DO_1; DO_0; break;
+ }
+ }
+ printf("mean time/iteration (mine): %fms\n", tprof[0]/NIT);
+ printf("--> result (mine): %f\n", res[0]);
+ printf("mean time/iteration (openblas): %fms\n", tprof[1]/NIT);
+ printf("--> result (openblas): %f\n", res[1]);
+
+ return 0;
+}
diff --git a/src/libmath/blas1.c b/src/libmath/blas1.c
new file mode 100644
index 0000000..a8ca085
--- /dev/null
+++ b/src/libmath/blas1.c
@@ -0,0 +1,58 @@
+#include <u.h>
+#include <libmath.h>
+
+// -----------------------------------------------------------------------
+// Templates
+
+#include "loop.h"
+#define BODY_XY() \
+ LOOP(UNROLL, 0, INIT); \
+ n = ROUNDBY(len, UNROLL); \
+ if (incx == 1 && incy == 1) { \
+ for (i = 0; i < n; i+=UNROLL) { \
+ LOOP(UNROLL,0,KERNEL,1,1); \
+ } \
+ } else { \
+ for (i = 0; i < n; i+=UNROLL) { \
+ LOOP(UNROLL,0,KERNEL,incx,incy);\
+ } \
+ } \
+ \
+ for (; i < len; i++) { \
+ LOOP(1,0,KERNEL,incx,incy); \
+ }
+
+#define BODY_X() \
+ LOOP(UNROLL, 0, INIT); \
+ n = ROUNDBY(len, UNROLL); \
+ if (incx == 1) { \
+ for (i = 0; i < n; i+=UNROLL) { \
+ LOOP(UNROLL,0,KERNEL,1); \
+ } \
+ } else { \
+ for (i = 0; i < n; i+=UNROLL) { \
+ LOOP(UNROLL,0,KERNEL,incx); \
+ } \
+ } \
+ \
+ for (; i < len; i++) { \
+ LOOP(1,0,KERNEL,incx); \
+ }
+
+// -----------------------------------------------------------------------
+// Implementation
+
+#define UNROLL 8
+#define INT int
+
+#define FLOAT double
+#define func(name) blas·d##name
+#include "blas1body"
+
+#undef FLOAT
+#undef func
+
+#define FLOAT float
+#define func(name) blas·f##name
+#include "blas1body"
+#undef FLOAT
diff --git a/src/libmath/blas1body b/src/libmath/blas1body
new file mode 100644
index 0000000..de4b637
--- /dev/null
+++ b/src/libmath/blas1body
@@ -0,0 +1,215 @@
+/* vim: set ft=c */
+// -----------------------------------------------------------------------
+// Function implementations
+
+FLOAT
+func(dot)(INT len, FLOAT *x, INT incx, FLOAT *y, INT incy)
+{
+#define INIT(I,...) reg[I] = 0;
+#define KERNEL(I, INCX, INCY) reg[I] += x[(INCX)*(i + I)] * y[(INCY)*(i + I)];
+ INT i, n;
+ FLOAT reg[UNROLL];
+
+ BODY_XY()
+
+ for (i = 1; i < UNROLL; i++)
+ reg[0] += reg[i];
+
+ return reg[0];
+#undef INIT
+#undef KERNEL
+}
+
+void
+func(copy)(INT len, FLOAT *x, INT incx, FLOAT *y, INT incy)
+{
+#define INIT(I,...)
+#define KERNEL(I, INCX, INCY) y[(INCY)*(i + I)] = x[(INCX)*(i + I)];
+ INT i, n;
+
+ BODY_XY();
+
+#undef INIT
+#undef KERNEL
+}
+
+void
+func(swap)(INT len, FLOAT *x, INT incx, FLOAT *y, INT incy)
+{
+#define INIT(I,...)
+#define KERNEL(I, INCX, INCY) tmp[I] = x[(INCX)*(i + I)], x[(INCX)*(i + I)] = y[(INCY)*(i + I)], y[(INCY)*(i + I)] = tmp[I];
+ INT i, n;
+ FLOAT tmp[UNROLL];
+
+ BODY_XY();
+
+#undef INIT
+#undef KERNEL
+}
+
+void
+func(axpy)(INT len, FLOAT a, FLOAT *x, INT incx, FLOAT *y, INT incy)
+{
+#define INIT(I,...)
+#define KERNEL(I, INCX, INCY) y[(INCY)*(i + I)] += a*x[(INCX)*(i + I)];
+ INT i, n;
+
+ BODY_XY();
+
+#undef INIT
+#undef KERNEL
+}
+
+void
+func(axpby)(INT len, FLOAT a, FLOAT *x, INT incx, FLOAT b, FLOAT *y, INT incy)
+{
+#define INIT(I,...)
+#define KERNEL(I, INCX, INCY) y[(INCY)*(i + I)] = a*x[(INCX)*(i + I)] + b*y[(INCY)*(i + I)];
+ INT i, n;
+
+ BODY_XY();
+
+#undef INIT
+#undef KERNEL
+}
+
+INT
+func(argmax)(INT len, FLOAT *x, INT incx)
+{
+#define INIT(I,...) max[I] = x[I], idx[I] = I;
+#define KERNEL(I, INCX) if (x[(INCX)*(i+I)] > max[I]) {max[i] = x[INCX*(i+I)]; idx[I] = i+I;}
+ INT i, n;
+ FLOAT max[UNROLL];
+ INT idx[UNROLL];
+
+ BODY_X();
+
+ for (i = 1; i < UNROLL; i++)
+ if (max[i] > max[0])
+ idx[0] = idx[i];
+
+ return idx[0];
+#undef INIT
+#undef KERNEL
+}
+
+INT
+func(argmin)(INT len, FLOAT *x, INT incx)
+{
+#define INIT(I,...) min[I] = x[I], idx[I] = I;
+#define KERNEL(I, INCX) if (x[INCX*(i+I)] < min[I]) {min[i] = x[INCX*(i+I)]; idx[I] = i+I;}
+ INT i, n;
+ FLOAT min[UNROLL];
+ INT idx[UNROLL];
+
+ BODY_X();
+
+ for (i = 1; i < UNROLL; i++)
+ if (min[i] < min[0])
+ idx[0] = idx[i];
+
+ return idx[0];
+#undef INIT
+#undef KERNEL
+}
+
+FLOAT
+func(asum)(INT len, FLOAT *x, INT incx)
+{
+#define INIT(I,...) sum[I] = 0;
+#define KERNEL(I, INCX) sum[I] += x[INCX*(i+I)] > 0 ? x[INCX*(i+I)] : -x[INCX*(i+I)];
+ INT i, n;
+ FLOAT sum[UNROLL];
+
+ BODY_X();
+
+ for (i = 1; i < UNROLL; i++)
+ sum[0] += sum[i];
+
+ return sum[0];
+
+#undef INIT
+#undef KERNEL
+}
+
+void
+func(scale)(INT len, FLOAT a, FLOAT *x, INT incx)
+{
+#define INIT(I, ...)
+#define KERNEL(I, INCX) x[INCX*(i+I)] *= a;
+ INT i, n;
+
+ BODY_X();
+
+#undef INIT
+#undef KERNEL
+}
+
+FLOAT
+func(norm)(INT len, FLOAT *x, INT incx)
+{
+#define INIT(I, ...)
+#define KERNEL(I, INCX) norm[I] += x[INCX*(i+I)] * x[INCX*(i+I)];
+ INT i, n;
+ FLOAT norm[UNROLL];
+
+ BODY_X();
+
+ for (i = 1; i < UNROLL; i++)
+ norm[0] += norm[i];
+
+ return math·sqrt(norm[0]);
+
+#undef INIT
+#undef KERNEL
+}
+
+void
+func(drot)(INT len, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT cos, FLOAT sin)
+{
+#define INIT(I, ...)
+#define KERNEL(I, INCX, INCY) tmp[I] = x[INCX*(i+I)], x[INCX*(i+I)] = cos*x[INCX*(i+I)] + sin*y[INCY*(i+I)], y[INCY*(i+I)] = cos*y[INCY*(i+I)] - sin*tmp[I];
+ INT i, n;
+ FLOAT tmp[UNROLL];
+
+ BODY_XY();
+
+#undef INIT
+#undef KERNEL
+}
+
+void
+func(rotm)(INT len, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT H[5])
+{
+#define INIT(I, ...)
+#define KERNEL(I, INCX, INCY) tmp[I] = x[INCX*(i+I)], x[INCX*(i+I)] = H[1]*x[INCX*(i+I)] + H[2]*y[INCY*(i+I)], y[INCY*(i+I)] = H[3]*tmp[I] + H[4]*y[INCY*(i+I)];
+ INT i, n, f;
+ FLOAT tmp[UNROLL];
+
+ f = (INT)H[0];
+ switch (f) {
+ case -2:
+ H[1] = +1;
+ H[2] = +0;
+ H[3] = +0;
+ H[4] = +1;
+ break;
+ case -1:
+ break;
+ case +0:
+ H[1] = +1;
+ H[4] = +1;
+ break;
+ case +1:
+ H[2] = +1;
+ H[3] = -1;
+ break;
+ default:
+ return;
+ }
+
+ BODY_XY();
+
+#undef INIT
+#undef KERNEL
+}
diff --git a/src/libmath/blas2.c b/src/libmath/blas2.c
new file mode 100644
index 0000000..7e4b08e
--- /dev/null
+++ b/src/libmath/blas2.c
@@ -0,0 +1,222 @@
+#include <u.h>
+#include <libmath/blas.h>
+#include "loop.h"
+
+// -----------------------------------------------------------------------
+// Templates
+
+#define BODY_RECT() \
+ nr = ROUNDBY(nrow, UNROW); \
+ nc = ROUNDBY(ncol, UNCOL); \
+ if (incx == 1 && incy == 1) { \
+ for (r = 0; r < nr; r += UNROW) { \
+ LOOP(UNROW,0,INIT,1,1); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(UNROW,0,KERN,1,1,UNCOL); \
+ } \
+ for (; c < ncol; c++) { \
+ LOOP(UNROW,0,KERN,1,1,1); \
+ } \
+ LOOP(UNROW,0,FINI,1,1); \
+ } \
+ } else { \
+ for (r = 0; r < nr; r += UNROW) { \
+ LOOP(UNROW,0,INIT,incx,incy); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(UNROW,0,KERN,incx,incy,UNCOL); \
+ } \
+ for (; c < ncol; c++) { \
+ LOOP(UNROW,0,KERN,incx,incy,1); \
+ } \
+ LOOP(UNROW,0,FINI,incx,incy); \
+ } \
+ } \
+ \
+ for (; r < nrow; r++) { \
+ LOOP(1,0,INIT,incx,incy); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(1,0,KERN,incx,incy,UNCOL); \
+ } \
+ for (; c < ncol; c++) { \
+ LOOP(1,0,KERN,incx,incy,1); \
+ } \
+ LOOP(1,0,FINI,incx,incy); \
+ }
+
+#define BODY_LOTRI() \
+ nr = ROUNDBY(n, UNROW); \
+ if (incx == 1) { \
+ for (r = 0; r < nr; r += UNROW) { \
+ LOOP(UNROW,0,INIT,1); \
+ nc = ROUNDBY(r, UNCOL); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(UNROW,0,KERN,1,UNCOL); \
+ } \
+ for (; c < r; c++) { \
+ LOOP(UNROW,0,KERN,1,1); \
+ } \
+ LOOP(UNROW,0,FINI,1); \
+ } \
+ } else { \
+ for (r = 0; r < nr; r += UNROW) { \
+ LOOP(UNROW,0,INIT,incx); \
+ nc = ROUNDBY(r, UNCOL); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(UNROW,0,KERN,incx,UNCOL); \
+ } \
+ for (; c < r; c++) { \
+ LOOP(UNROW,0,KERN,incx,1); \
+ } \
+ LOOP(UNROW,0,FINI,incx); \
+ } \
+ } \
+ \
+ for (; r < n; r++) { \
+ LOOP(1,0,INIT,incx); \
+ nc = ROUNDBY(r, UNCOL); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(1,0,KERN,incx,UNCOL); \
+ } \
+ for (; c < r; c++) { \
+ LOOP(1,0,KERN,incx,1); \
+ } \
+ LOOP(1,0,FINI,incx); \
+ }
+
+#define BODY_UPTRI() \
+ nr = n - ROUNDBY(n, UNROW); \
+ if (incx == 1) { \
+ for (r = n-1; r >= nr; r -= UNROW) { \
+ LOOP(UNROW,0,INIT,1); \
+ nc = n - ROUNDBY(r, UNCOL); \
+ for (c = n-1; c >= nc; c -= UNCOL) { \
+ LOOP(UNROW,0,KERN,1,UNCOL); \
+ } \
+ for (; c > r; c--) { \
+ LOOP(UNROW,0,KERN,1,1); \
+ } \
+ LOOP(UNROW,0,FINI,1); \
+ } \
+ } else { \
+ for (r = n-1; r >= nr; r -= UNROW) { \
+ LOOP(UNROW,0,INIT,incx); \
+ nc = n - ROUNDBY(r, UNCOL); \
+ for (c = n-1; c >= nc; c -= UNCOL) { \
+ LOOP(UNROW,0,KERN,incx,UNCOL); \
+ } \
+ for (; c > r; c--) { \
+ LOOP(UNROW,0,KERN,incx,1); \
+ } \
+ LOOP(UNROW,0,FINI,incx); \
+ } \
+ } \
+ \
+ for (; r >= 0; r--) { \
+ LOOP(1,0,INIT,incx); \
+ nc = n - ROUNDBY(r, UNCOL); \
+ for (c = n-1; c >= nc; c -= UNCOL) { \
+ LOOP(1,0,KERN,incx,UNCOL); \
+ } \
+ for (; c > r; c--) { \
+ LOOP(1,0,KERN,incx,1); \
+ } \
+ LOOP(1,0,FINI,incx); \
+ }
+
+#define BODY_LOTRI_XY() \
+ nr = ROUNDBY(n, UNROW); \
+ if (incx == 1 && incy == 1) { \
+ for (r = 0; r < nr; r += UNROW) { \
+ LOOP(UNROW,0,INIT,1,1); \
+ nc = ROUNDBY(r, UNCOL); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(UNROW,0,KERN,1,1,UNCOL); \
+ } \
+ for (; c < r; c++) { \
+ LOOP(UNROW,0,KERN,1,1,1); \
+ } \
+ LOOP(UNROW,0,FINI,1,1); \
+ } \
+ } else { \
+ for (r = 0; r < nr; r += UNROW) { \
+ LOOP(UNROW,0,INIT,incx,incy); \
+ nc = ROUNDBY(r, UNCOL); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(UNROW,0,KERN,incx,incy,UNCOL); \
+ } \
+ for (; c < r; c++) { \
+ LOOP(UNROW,0,KERN,incx,incy,1); \
+ } \
+ LOOP(UNROW,0,FINI,incx, incy); \
+ } \
+ } \
+ \
+ for (; r < n; r++) { \
+ LOOP(1,0,INIT,incx,incy); \
+ nc = ROUNDBY(r, UNCOL); \
+ for (c = 0; c < nc; c += UNCOL) { \
+ LOOP(1,0,KERN,incx,incy,UNCOL); \
+ } \
+ for (; c < r; c++) { \
+ LOOP(1,0,KERN,incx,incy,1); \
+ } \
+ LOOP(1,0,FINI,incx,incy); \
+ }
+
+#define BODY_UPTRI_XY() \
+ nr = n - ROUNDBY(n, UNROW); \
+ if (incx == 1 && incy == 1) { \
+ for (r = n-1; r >= nr; r -= UNROW) { \
+ LOOP(UNROW,0,INIT,1,1); \
+ nc = n - ROUNDBY(r, UNCOL); \
+ for (c = n-1; c >= nc; c -= UNCOL) { \
+ LOOP(UNROW,0,KERN,1,1,UNCOL); \
+ } \
+ for (; c > r; c--) { \
+ LOOP(UNROW,0,KERN,1,1,1); \
+ } \
+ LOOP(UNROW,0,FINI,1,1); \
+ } \
+ } else { \
+ for (r = n-1; r >= nr; r -= UNROW) { \
+ LOOP(UNROW,0,INIT,incx,incy); \
+ nc = n - ROUNDBY(r, UNCOL); \
+ for (c = n-1; c >= nc; c -= UNCOL) { \
+ LOOP(UNROW,0,KERN,incx,incy,UNCOL); \
+ } \
+ for (; c > r; c--) { \
+ LOOP(UNROW,0,KERN,incx,incy,1); \
+ } \
+ LOOP(UNROW,0,FINI,incx,incy); \
+ } \
+ } \
+ \
+ for (; r >= 0; r--) { \
+ LOOP(1,0,INIT,incx,incy); \
+ nc = n - ROUNDBY(r, UNCOL); \
+ for (c = n-1; c >= nc; c -= UNCOL) { \
+ LOOP(1,0,KERN,incx,incy,UNCOL); \
+ } \
+ for (; c > r; c--) { \
+ LOOP(1,0,KERN,incx,incy,1); \
+ } \
+ LOOP(1,0,FINI,incx,incy); \
+ }
+
+// -----------------------------------------------------------------------
+// implementation
+
+#define UNROW 4
+#define UNCOL 4
+
+#define INT int
+#define FLOAT double
+#define func(name) blas·d##name
+#include "blas2body"
+
+#undef FLOAT
+#undef func
+
+#define FLOAT float
+#define func(name) blas·f##name
+#include "blas2body"
diff --git a/src/libmath/blas2body b/src/libmath/blas2body
new file mode 100644
index 0000000..45baf67
--- /dev/null
+++ b/src/libmath/blas2body
@@ -0,0 +1,256 @@
+/* general matrix multiply */
+error
+func(gemv)(uint flag, INT nrow, INT ncol, FLOAT a, FLOAT *m, INT incm, FLOAT *x, INT incx, FLOAT b, FLOAT *y, INT incy)
+{
+ INT r, c, nr, nc;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX, INCY) row[I] = m + (r+I)*incm, reg[I] = 0;
+#define TERM(J, I, INCX, INCY) row[I][c+J] * x[INCX*(c+J)]
+#define KERN(I, INCX, INCY, LENGTH) reg[I] += EXPAND(LENGTH,0,TERM,+,I,INCX,INCY);
+#define FINI(I, INCX, INCY) y[INCY*(r+I)] = b*y[INCY*(r+I)] + a*reg[I];
+
+ if (!flag) {
+ BODY_RECT();
+ } else {
+ func(scale)(ncol, b, y, incy);
+#undef KERN
+#undef FINI
+#undef INIT
+#undef TERM
+#define INIT(I, INCX, INCY) row[I] = m + (r+I)*incm, reg[I] = a*x[INCX*(r+I)];
+#define TERM(J, I, INCX, INCY) row[I][c+J] * reg[I]
+#define KERN(I, INCX, INCY, LENGTH) y[INCY*(c+I)] += EXPAND(LENGTH,0,TERM,+,I,INCX,INCY);
+#define FINI(I, INCX, INCY)
+ BODY_RECT();
+ }
+
+ return 0;
+#undef INIT
+#undef TERM
+#undef KERN
+#undef FINI
+}
+
+/* symmetric matrix vector multiply (different layouts) */
+void
+func(symv)(uint upper, INT n, FLOAT a, FLOAT *m, INT incm, FLOAT *x, INT incx, FLOAT b, FLOAT *y, INT incy)
+{
+ INT r, c, nr, nc, i;
+ FLOAT *row[UNROW], reg1[UNROW], reg2[UNROW];
+
+#define INIT(I, INCX, INCY) row[I] = m + (r XX I)*incm, reg1[I] = 0, reg2[I] = 0;
+#define TERM1(J, I, INCX, INCY) row[I][c XX J]*x[INCX*(c XX J)]
+#define TERM2(J, I, INCX, INCY) row[I][c XX J]*x[INCX*((n-c-1) XX J)]
+#define KERN(I, INCX, INCY, REPEAT) reg1[I] += EXPAND(REPEAT,0,TERM1,+,I,INCX,INCY), \
+ reg2[I] += EXPAND(REPEAT,0,TERM2,+,I,INCX,INCY);
+#define FINI(I, INCX, INCY) y[INCY*(r+I)] += a*(reg1[I] + row[I][r]*x[INCX*r]), \
+ y[INCY*(n-r-1+I)] += a*reg2[I];
+
+ func(scale)(n, b, y, incy);
+#define XX +
+ if (!upper) {
+ BODY_LOTRI_XY();
+ } else {
+#undef XX
+#define XX -
+ BODY_UPTRI_XY();
+ }
+#undef XX
+
+#undef INIT
+}
+
+void
+func(spmv)(uint upper, INT n, FLOAT a, FLOAT *m, FLOAT *x, INT incx, FLOAT b, FLOAT *y, INT incy)
+{
+ INT r, c, nr, nc, i;
+ FLOAT *row[UNROW], reg1[UNROW], reg2[UNROW];
+
+#define INIT(I, INCX, INCY) row[I] = m + ((r+I)*(r+I+1))*(1/2), reg1[I] = 0, reg2[I] = 0;
+
+ func(scale)(n, b, y, incy);
+#define XX +
+ if (!upper) {
+ BODY_LOTRI_XY();
+ } else {
+#undef XX
+#undef INIT
+#define INIT(I, INCX, INCY) row[I] = m + ((2*n-r-I)*(r+I+1))*(1/2), reg1[I] = 0, reg2[I] = 0;
+#define XX -
+ BODY_UPTRI_XY();
+ }
+#undef XX
+
+#undef TERM
+#undef INIT
+#undef KERN
+#undef FINI
+}
+
+/* triangular multiply (different layouts) */
+void
+func(trmv)(uint upper, INT n, FLOAT *m, INT incm, FLOAT *x, INT incx)
+{
+ INT r, c, nr, nc, i;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX) row[I] = m + (r XX I)*incm, reg[I] = 0;
+#define TERM(J, I, INCX) row[I][c XX J]*x[INCX*(c XX J)]
+#define KERN(I, INCX, REPEAT) reg[I] += EXPAND(REPEAT,0,TERM,+,I,INCX);
+#define FINI(I, INCX) x[INCX*(r XX I)] = row[I][r XX I]*x[INCX*(r XX I)] + reg[I];
+
+#define XX +
+ if (!upper) {
+ BODY_LOTRI();
+ } else {
+#undef XX
+#define XX -
+ BODY_UPTRI();
+ }
+#undef XX
+
+#undef INIT
+}
+
+void
+func(tpmv)(uint upper, INT n, FLOAT *m, FLOAT *x, INT incx)
+{
+ INT r, c, nr, nc, i;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX) row[I] = m + ((r+I)*(r+I+1))*(1/2), reg[I] = 0;
+
+#define XX +
+ if (!upper) {
+ BODY_LOTRI();
+ } else {
+#undef XX
+#undef INIT
+#define INIT(I, INCX) row[I] = m + ((2*n-r-I)*(r+I+1))*(1/2), reg[I] = 0;
+#define XX -
+ BODY_UPTRI();
+ }
+#undef XX
+
+#undef INIT
+#undef TERM
+#undef KERN
+#undef FINI
+}
+
+/* triangular solve (different layouts) */
+void
+func(trsv)(uint upper, INT n, FLOAT *m, INT incm, FLOAT *x, INT incx)
+{
+ INT r, c, nr, nc, i;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX) row[I] = m + (r XX I)*incm, reg[I] = 0;
+#define TERM(J, I, INCX) row[I][c XX J]*x[c XX J]
+#define KERN(I, INCX, REPEAT) reg[I] += EXPAND(REPEAT,0,TERM,+,I,INCX);
+#define SOLN(J, I, INCX) reg[J] += row[I][r XX I]*x[INCX*(r XX I)]
+#define FINI(I, INCX) x[INCX*(r XX I)] = reg[I] / row[I][r XX I]; EXPAND_TRI(UNROW,INC(I),SOLN,;,I,INCX);
+
+#define XX +
+ if (!upper) {
+ BODY_LOTRI();
+ } else {
+#undef XX
+#define XX -
+ BODY_UPTRI();
+ }
+#undef XX
+
+#undef INIT
+}
+
+void
+func(tpsv)(uint upper, INT n, FLOAT *m, FLOAT *x, INT incx)
+{
+ INT r, c, nr, nc, i;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX) row[I] = m + ((r+I)*(r+I+1))*(1/2), reg[I] = 0;
+
+#define XX +
+ if (!upper) {
+ BODY_LOTRI();
+ } else {
+#undef XX
+#undef INIT
+#define INIT(I, INCX) row[I] = m + ((2*n-r-I)*(r+I+1))*(1/2), reg[I] = 0;
+#define XX -
+ BODY_UPTRI();
+ }
+#undef XX
+
+#undef INIT
+#undef TERM
+#undef KERN
+#undef SOLN
+#undef FINI
+}
+
+/* rank 1 update */
+void
+func(ger)(INT nrow, INT ncol, FLOAT a, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT *m, INT incm)
+{
+ INT r, c, nr, nc;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX, INCY) row[I] = m + (r+I)*incm, reg[I] = a*x[INCX*(r+I)];
+#define TERM(J, I, INCX, INCY) row[I][c+J] += reg[I] * y[INCY*(c+J)]
+#define KERN(I, INCX, INCY, LENGTH) EXPAND(LENGTH,0,TERM,;,I,INCX, INCY);
+#define FINI(I, ...)
+
+ BODY_RECT();
+
+#undef INIT
+#undef TERM
+#undef KERN
+#undef FINI
+}
+
+/* symmetric rank 1 update (different memory layouts) */
+void
+func(syr)(uint upper, INT n, FLOAT a, FLOAT *x, INT incx, FLOAT *m, INT incm)
+{
+ INT r, c, nr, nc;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX) row[I] = m + (r XX I)*incm, reg[I] = a*x[INCX*(r XX I)];
+#define TERM(J, I, INCX) row[I][c XX J] += reg[I] * x[INCX*(c XX J)]
+#define KERN(I, INCX, LENGTH) EXPAND(LENGTH,0,TERM,;,I,INCX);
+#define FINI(I, ...)
+
+#define XX +
+ if (!upper) {
+ BODY_LOTRI();
+ } else {
+#undef XX
+#define XX -
+ BODY_UPTRI();
+ }
+#undef XX
+
+#undef INIT
+}
+
+void
+func(spr)(uint upper, INT n, FLOAT a, FLOAT *x, INT incx, FLOAT *m)
+{
+ INT r, c, nr, nc;
+ FLOAT *row[UNROW], reg[UNROW];
+#define INIT(I, INCX) row[I] = m + ((r+I)*(r+I+1))*(1/2), reg[I] = 0;
+
+#define XX +
+ if (!upper) {
+ BODY_LOTRI();
+ } else {
+#undef XX
+#undef INIT
+#define INIT(I, INCX) row[I] = m + ((2*n-r-I)*(r+I+1))*(1/2), reg[I] = 0;
+#define XX -
+ BODY_UPTRI();
+ }
+#undef XX
+
+#undef INIT
+#undef TERM
+#undef KERN
+#undef FINI
+}
diff --git a/src/libmath/blas3.c b/src/libmath/blas3.c
new file mode 100644
index 0000000..b048c95
--- /dev/null
+++ b/src/libmath/blas3.c
@@ -0,0 +1,279 @@
+#include <u.h>
+#include <base.h>
+#include <libmath.h>
+
+#define INT int
+#define FLOAT double
+#define func(name) blas·d##name
+
+#define X(i, j) x[j + incx*(i)]
+#define Y(i, j) y[j + incy*(i)]
+#define Z(i, j) z[j + incz*(i)]
+
+void
+func(gemm)(uint trm, uint trn, INT ni, INT nj, INT nk, FLOAT a, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT b, FLOAT *z, INT incz)
+{
+ INT jj, jb, kk, kb, dk, i, j, k, end;
+ FLOAT r0[8], r1[8], r2[8], r3[8], pf;
+
+ for (i = 0; i < ni; i++) {
+ for (j = 0; j < nj; j++) {
+ Z(i,j) *= b;
+ }
+ }
+
+ jb = MIN(256, nj);
+ kb = MIN(48, nk);
+ for (jj = 0; jj < nj; jj += jb) {
+ for (kk = 0; kk < nk; kk += kb) {
+ for (i = 0; i < ni; i += 4) {
+ for (j = jj; j < jj + jb; j += 8) {
+ r0[0] = Z(i+0, j+0); r0[1] = Z(i+0, j+1); r0[2] = Z(i+0, j+2); r0[3] = Z(i+0, j+3);
+ r1[0] = Z(i+1, j+0); r1[1] = Z(i+1, j+1); r1[2] = Z(i+1, j+2); r1[3] = Z(i+1, j+3);
+ r2[0] = Z(i+2, j+0); r2[1] = Z(i+2, j+1); r2[2] = Z(i+2, j+2); r2[3] = Z(i+2, j+3);
+ r3[0] = Z(i+3, j+0); r3[1] = Z(i+3, j+1); r3[2] = Z(i+3, j+2); r3[3] = Z(i+3, j+3);
+ end = MIN(nk, kk+kb);
+ for (k = kk; k < end; k++) {
+ pf = a * X(i, k);
+ r0[0] += pf * Y(k, j+0); r0[1] += pf * Y(k, j+1); r0[2] += pf * Y(k, j+2); r0[3] += pf * Y(k, j+3);
+
+ pf = a * X(i+1, k);
+ r1[0] += pf * Y(k, j+0); r1[1] += pf * Y(k, j+1); r1[2] += pf * Y(k, j+2); r1[3] += pf * Y(k, j+3);
+
+ pf = a * X(i+2, k);
+ r1[0] += pf * Y(k, j+0); r1[1] += pf * Y(k, j+1); r1[2] += pf * Y(k, j+2); r1[3] += pf * Y(k, j+3);
+
+ pf = a * X(i+3, k);
+ r1[0] += pf * Y(k, j+0); r1[1] += pf * Y(k, j+1); r1[2] += pf * Y(k, j+2); r1[3] += pf * Y(k, j+3);
+ }
+ Z(i+0, j+0) = r0[0]; Z(i+0, j+1) = r0[1]; Z(i+0, j+2) = r0[2]; Z(i+0, j+3) = r0[3];
+ Z(i+1, j+0) = r1[0]; Z(i+1, j+1) = r1[1]; Z(i+1, j+2) = r1[2]; Z(i+1, j+3) = r1[3];
+ Z(i+2, j+0) = r2[0]; Z(i+2, j+1) = r2[1]; Z(i+2, j+2) = r2[2]; Z(i+2, j+3) = r2[3];
+ Z(i+3, j+0) = r3[0]; Z(i+3, j+1) = r3[1]; Z(i+3, j+2) = r3[2]; Z(i+3, j+3) = r3[3];
+ }
+ }
+ }
+ }
+}
+
+#if 0
+void
+func(gemm)(uint trm, uint trn, INT ni, INT nj, INT nk, FLOAT a, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT b, FLOAT *z, INT incz)
+{
+ int i, j, k;
+ FLOAT w[nj*nk], acc[4][4];
+
+ for (i = 0; i < ni; i++) {
+ for (j = 0; j < nj; j++) {
+ Z(i,j) *= b;
+ W(i,j) = Y(j,i);
+ }
+ }
+
+ for (i = 0; i < ni; i+=4) {
+ for (j = 0; j < nj; j+=4) {
+ memset(acc, 0, sizeof(acc));
+ for (k = 0; k < nk; k+=4) {
+ acc[0][0] += X(i+0,k)*W(j+0,k) + X(i+0,k+1)*W(j+0,k+1) + X(i+0,k+2)*W(j+0,k+2) + X(i+0,k+3)*W(j+0,k+3);
+ acc[0][1] += X(i+0,k)*W(j+1,k) + X(i+0,k+1)*W(j+1,k+1) + X(i+0,k+2)*W(j+1,k+2) + X(i+0,k+3)*W(j+1,k+3);
+ acc[0][2] += X(i+0,k)*W(j+2,k) + X(i+0,k+1)*W(j+2,k+1) + X(i+0,k+2)*W(j+2,k+2) + X(i+0,k+3)*W(j+2,k+3);
+ acc[0][3] += X(i+0,k)*W(j+3,k) + X(i+0,k+1)*W(j+3,k+1) + X(i+0,k+2)*W(j+3,k+2) + X(i+0,k+3)*W(j+3,k+3);
+
+ acc[1][0] += X(i+1,k)*W(j+0,k) + X(i+1,k+1)*W(j+0,k+1) + X(i+1,k+2)*W(j+0,k+2) + X(i+1,k+3)*W(j+0,k+3);
+ acc[1][1] += X(i+1,k)*W(j+1,k) + X(i+1,k+1)*W(j+1,k+1) + X(i+1,k+2)*W(j+1,k+2) + X(i+1,k+3)*W(j+1,k+3);
+ acc[1][2] += X(i+1,k)*W(j+2,k) + X(i+1,k+1)*W(j+2,k+1) + X(i+1,k+2)*W(j+2,k+2) + X(i+1,k+3)*W(j+2,k+3);
+ acc[1][3] += X(i+1,k)*W(j+3,k) + X(i+1,k+1)*W(j+3,k+1) + X(i+1,k+2)*W(j+3,k+2) + X(i+1,k+3)*W(j+3,k+3);
+
+ acc[2][0] += X(i+2,k)*W(j+0,k) + X(i+2,k+1)*W(j+0,k+1) + X(i+2,k+2)*W(j+0,k+2) + X(i+2,k+3)*W(j+0,k+3);
+ acc[2][1] += X(i+2,k)*W(j+1,k) + X(i+2,k+1)*W(j+1,k+1) + X(i+2,k+2)*W(j+1,k+2) + X(i+2,k+3)*W(j+1,k+3);
+ acc[2][2] += X(i+2,k)*W(j+2,k) + X(i+2,k+1)*W(j+2,k+1) + X(i+2,k+2)*W(j+2,k+2) + X(i+2,k+3)*W(j+2,k+3);
+ acc[2][3] += X(i+2,k)*W(j+3,k) + X(i+2,k+1)*W(j+3,k+1) + X(i+2,k+2)*W(j+3,k+2) + X(i+2,k+3)*W(j+3,k+3);
+
+ acc[2][0] += X(i+3,k)*W(j+0,k) + X(i+3,k+1)*W(j+0,k+1) + X(i+3,k+2)*W(j+0,k+2) + X(i+3,k+3)*W(j+0,k+3);
+ acc[2][1] += X(i+3,k)*W(j+1,k) + X(i+3,k+1)*W(j+1,k+1) + X(i+3,k+2)*W(j+1,k+2) + X(i+3,k+3)*W(j+1,k+3);
+ acc[2][2] += X(i+3,k)*W(j+2,k) + X(i+3,k+1)*W(j+2,k+1) + X(i+3,k+2)*W(j+2,k+2) + X(i+3,k+3)*W(j+2,k+3);
+ acc[2][3] += X(i+3,k)*W(j+3,k) + X(i+3,k+1)*W(j+3,k+1) + X(i+3,k+2)*W(j+3,k+2) + X(i+3,k+3)*W(j+3,k+3);
+ // Z(i,j) += X(i,k)*Y(k,j);
+ }
+ Z(i+0,j+1) = a*acc[0][0];
+ Z(i+0,j+2) = a*acc[0][1];
+ Z(i+0,j+3) = a*acc[0][2];
+ Z(i+0,j+4) = a*acc[0][3];
+
+ Z(i+1,j+1) = a*acc[1][0];
+ Z(i+1,j+2) = a*acc[1][1];
+ Z(i+1,j+3) = a*acc[1][2];
+ Z(i+1,j+4) = a*acc[1][3];
+
+ Z(i+2,j+1) = a*acc[2][0];
+ Z(i+2,j+2) = a*acc[2][1];
+ Z(i+2,j+3) = a*acc[2][2];
+ Z(i+2,j+4) = a*acc[2][3];
+
+ Z(i+3,j+1) = a*acc[3][0];
+ Z(i+3,j+2) = a*acc[3][1];
+ Z(i+3,j+3) = a*acc[3][2];
+ Z(i+3,j+4) = a*acc[3][3];
+ }
+ }
+}
+#endif
+
+#if 0
+void
+func(gemm)(uint trm, uint trn, INT ni, INT nj, INT nk, FLOAT a, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT b, FLOAT *z, INT incz)
+{
+ int i, j, k, ri, rj, rk;
+ FLOAT reg[4][4], *xrow[4], *yrow[4];
+
+ for (i = 0; i < ni; i++) {
+ for (j = 0; j < nj; j++) {
+ z[j + incz*i] *= b;
+ }
+ }
+
+ for (i = 0; i < ni; i += 4) {
+ xrow[0] = x + incx*(i+0);
+ xrow[1] = x + incx*(i+1);
+ xrow[2] = x + incx*(i+2);
+ xrow[3] = x + incx*(i+3);
+ for (k = 0; k < nk; k+=4) {
+ yrow[0] = y + incy*(k+0);
+ yrow[1] = y + incy*(k+1);
+ yrow[2] = y + incy*(k+2);
+ yrow[3] = y + incy*(k+3);
+ reg[0][0] = a * xrow[0][k+0]; reg[0][1] = a * xrow[0][k+1]; reg[0][2] = a * xrow[0][k+2]; reg[0][3] = a * xrow[0][k+3];
+ reg[1][0] = a * xrow[1][k+0]; reg[1][1] = a * xrow[1][k+1]; reg[1][2] = a * xrow[1][k+2]; reg[1][3] = a * xrow[1][k+3];
+ reg[2][0] = a * xrow[2][k+0]; reg[2][1] = a * xrow[2][k+1]; reg[2][2] = a * xrow[2][k+2]; reg[2][3] = a * xrow[2][k+3];
+ reg[3][0] = a * xrow[3][k+0]; reg[3][1] = a * xrow[3][k+1]; reg[3][2] = a * xrow[3][k+2]; reg[3][3] = a * xrow[3][k+3];
+ for (j = 0; j < nj; j += 1) {
+ z[j + incz*(i+0)] += (reg[0][0]*yrow[0][j]+reg[0][1]*yrow[1][j]+reg[0][2]*yrow[2][j]+reg[0][3]*yrow[3][j]);
+ z[j + incz*(i+1)] += (reg[1][0]*yrow[0][j]+reg[1][1]*yrow[1][j]+reg[1][2]*yrow[2][j]+reg[1][3]*yrow[3][j]);
+ z[j + incz*(i+2)] += (reg[2][0]*yrow[0][j]+reg[2][1]*yrow[1][j]+reg[2][2]*yrow[2][j]+reg[2][3]*yrow[3][j]);
+ z[j + incz*(i+3)] += (reg[3][0]*yrow[0][j]+reg[3][1]*yrow[1][j]+reg[3][2]*yrow[2][j]+reg[3][3]*yrow[3][j]);
+ }
+ }
+ }
+}
+#endif
+
+#if 0
+void
+func(gemm)(uint trm, uint trn, INT ni, INT nj, INT nk, FLOAT a, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT b, FLOAT *z, INT incz)
+{
+ int i, j, k, ri, rj, rk;
+ FLOAT r[4][4], *row[4];
+
+ for (i = 0; i < ni; i++) {
+ for (j = 0; j < nj; j++) {
+ Z(i, j) *= b;
+ }
+ }
+
+ for (i = 0; i < ni; i+=4) {
+ for (j = 0; j < nj; j+=4) {
+ r[0][0] = 0; r[0][1] = 0; r[0][2] = 0; r[0][3] = 0;
+ r[1][0] = 0; r[1][1] = 0; r[1][2] = 0; r[1][3] = 0;
+ r[2][0] = 0; r[2][1] = 0; r[2][2] = 0; r[2][3] = 0;
+ r[3][0] = 0; r[3][1] = 0; r[3][2] = 0; r[3][3] = 0;
+ row[0] = &X(i+0, 0);
+ row[1] = &X(i+1, 0);
+ row[2] = &X(i+2, 0);
+ row[3] = &X(i+3, 0);
+ for (k = 0; k < nk; k++) {
+ r[0][0] += row[0][k]*Y(k,0); r[0][1] += row[0][k]*Y(k,1); r[0][2] += row[0][k]*Y(k,2); r[0][3] += row[0][k]*Y(k,3);
+ r[1][0] += row[1][k]*Y(k,0); r[1][1] += row[1][k]*Y(k,1); r[1][2] += row[1][k]*Y(k,2); r[1][3] += row[1][k]*Y(k,3);
+ r[2][0] += row[2][k]*Y(k,0); r[2][1] += row[2][k]*Y(k,1); r[2][2] += row[2][k]*Y(k,2); r[2][3] += row[2][k]*Y(k,3);
+ r[3][0] += row[3][k]*Y(k,0); r[3][1] += row[3][k]*Y(k,1); r[3][2] += row[3][k]*Y(k,2); r[3][3] += row[3][k]*Y(k,3);
+ }
+ Z(i+0, j+0) += r[0][0]; Z(i+0, j+1) += r[0][1]; Z(i+0, j+2) += r[0][2]; Z(i+0, j+3) += r[0][3];
+ Z(i+1, j+0) += r[1][0]; Z(i+1, j+1) += r[1][1]; Z(i+1, j+2) += r[1][2]; Z(i+1, j+3) += r[1][3];
+ Z(i+2, j+0) += r[2][0]; Z(i+2, j+1) += r[2][1]; Z(i+2, j+2) += r[2][2]; Z(i+2, j+3) += r[2][3];
+ Z(i+3, j+0) += r[3][0]; Z(i+3, j+1) += r[3][1]; Z(i+3, j+2) += r[3][2]; Z(i+3, j+3) += r[3][3];
+ }
+ }
+}
+#endif
+
+#if 0
+void
+func(gemm)(uint trm, uint trn, INT ni, INT nj, INT nk, FLOAT a, FLOAT *x, INT incx, FLOAT *y, INT incy, FLOAT b, FLOAT *z, INT incz)
+{
+ int i, j, k, ri, rj, rk;
+ FLOAT *xrow[8], *yrow[8], reg;
+
+ for (i = 0; i < ni; i++) {
+ for (j = 0; j < nj; j++) {
+ z[j + incz*i] *= b;
+ }
+ }
+
+ ri = ni & ~7;
+ rj = nj & ~7;
+ for (i = 0; i < ri; i += 8) {
+ xrow[0] = x + incx*(i+0);
+ xrow[1] = x + incx*(i+1);
+ xrow[2] = x + incx*(i+2);
+ xrow[3] = x + incx*(i+3);
+ xrow[4] = x + incx*(i+4);
+ xrow[5] = x + incx*(i+5);
+ xrow[6] = x + incx*(i+6);
+ xrow[7] = x + incx*(i+7);
+ for (j = 0; j < rj; j += 8) {
+ yrow[0] = y + incy*(j+0);
+ yrow[1] = y + incy*(j+1);
+ yrow[2] = y + incy*(j+2);
+ yrow[3] = y + incy*(j+3);
+ yrow[4] = y + incy*(j+4);
+ yrow[5] = y + incy*(j+5);
+ yrow[6] = y + incy*(j+6);
+ yrow[7] = y + incy*(j+7);
+ for (k = 0; k < nk; k++) {
+ reg = a*(yrow[0][k] + yrow[1][k] + yrow[2][k] + yrow[3][k] + yrow[4][k] + yrow[5][k] + yrow[6][k] + yrow[7][k]);
+ z[k + incz*(i+0)] += xrow[0][k]*reg;
+ z[k + incz*(i+1)] += xrow[1][k]*reg;
+ z[k + incz*(i+2)] += xrow[2][k]*reg;
+ z[k + incz*(i+3)] += xrow[3][k]*reg;
+ z[k + incz*(i+4)] += xrow[4][k]*reg;
+ z[k + incz*(i+5)] += xrow[5][k]*reg;
+ z[k + incz*(i+6)] += xrow[6][k]*reg;
+ z[k + incz*(i+7)] += xrow[7][k]*reg;
+ }
+ }
+ for (; j < nj; j++) {
+ for (k = 0; k < nk; k++) {
+ reg = a*y[k+incy*j];
+ z[k + incz*(i+0)] += xrow[0][k]*reg;
+ z[k + incz*(i+1)] += xrow[1][k]*reg;
+ z[k + incz*(i+2)] += xrow[2][k]*reg;
+ z[k + incz*(i+3)] += xrow[3][k]*reg;
+ z[k + incz*(i+4)] += xrow[4][k]*reg;
+ z[k + incz*(i+5)] += xrow[5][k]*reg;
+ z[k + incz*(i+6)] += xrow[6][k]*reg;
+ z[k + incz*(i+7)] += xrow[7][k]*reg;
+ }
+ }
+ }
+
+ for (; i < ni; i++) {
+ for (j = 0; j < rj; j += 8) {
+ yrow[0] = y + incy*(j+0);
+ yrow[1] = y + incy*(j+1);
+ yrow[2] = y + incy*(j+2);
+ yrow[3] = y + incy*(j+3);
+ yrow[4] = y + incy*(j+4);
+ yrow[5] = y + incy*(j+5);
+ yrow[6] = y + incy*(j+6);
+ yrow[7] = y + incy*(j+7);
+ for (k = 0; k < nk; k++) {
+ z[k + incz*(i)] += a*x[k + incx*i]*(yrow[0][k] + yrow[1][k] + yrow[2][k] + yrow[3][k] + yrow[4][k] + yrow[5][k] + yrow[6][k] + yrow[7][k]);
+ }
+ }
+ for (; j < nj; j++) {
+ for (k = 0; k < nk; k++) {
+ z[k + incz*i] += a*x[k + incx*i]*y[k + incy*j];
+ }
+ }
+ }
+}
+#endif
diff --git a/src/libmath/lapack.c b/src/libmath/lapack.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/libmath/lapack.c
diff --git a/src/libmath/linalg.c b/src/libmath/linalg.c
new file mode 100644
index 0000000..8551ff1
--- /dev/null
+++ b/src/libmath/linalg.c
@@ -0,0 +1,63 @@
+#include <u.h>
+#include <libn.h>
+#include <libmath.h>
+#include <libmath/blas.h>
+
+// -----------------------------------------------------------------------
+// Vector
+
+void
+linalg·normalize(math·Vector vec)
+{
+ double norm;
+
+ norm = blas·normd(vec.len, vec.data, 1);
+ blas·scaled(vec.len, 1/norm, vec.data, 1);
+}
+// TODO: Write blas wrappers that eat vectors for convenience
+
+// -----------------------------------------------------------------------
+// Matrix
+//
+// NOTE: all matrices are row major oriented
+
+/*
+ * linalg·lq
+ * computes the LQ decomposition of matrix M: M = LQ
+ * L is lower triangular
+ * Q is orthogonal -> transp(Q) * Q = I
+ *
+ * m: matrix to factorize. changes in place
+ * + lower triangle -> L
+ * + upper triangle -> all reflection vectors stored in rows
+ * w: working buffer: len = ncols!
+ */
+error
+linalg·lq(math·Matrix m, math·Vector w)
+{
+ int i, j, len;
+ double *row, mag;
+ enum {
+ err·nil,
+ err·baddims,
+ };
+
+ if (m.dim[0] > m.dim[1]) {
+ return err·baddims;
+ }
+
+ for (i = 0; i < m.dim[0]; i++, m.data += m.dim[1]) {
+ row = m.data + i;
+ len = m.dim[0] - i;
+
+ // TODO: Don't want to compute norm twice!!
+ w.data[0] = math·sgn(row[0]) * blas·normd(len, row, 1);
+ blas·axpyd(len, 1.0, row, 1, w.data, 1);
+ mag = blas·normd(len, w.data, 1);
+ blas·scaled(len, 1/mag, w.data, 1);
+
+ blas·copyd(len - m.dim[0], w.data, 1, m.data + i, 1);
+ }
+
+ return err·nil;
+}
diff --git a/src/libmath/loop.h b/src/libmath/loop.h
new file mode 100644
index 0000000..a877d84
--- /dev/null
+++ b/src/libmath/loop.h
@@ -0,0 +1,114 @@
+#pragma once
+
+/* increment operator */
+#define INC2(x) INC_##x
+#define INC1(x) INC2(x)
+#define INC(x) INC1(x)
+
+#define INC_0 1
+#define INC_1 2
+#define INC_2 3
+#define INC_3 4
+#define INC_4 5
+#define INC_5 6
+#define INC_6 7
+#define INC_7 8
+#define INC_8 9
+#define INC_9 10
+#define INC_10 11
+#define INC_11 12
+#define INC_12 13
+#define INC_13 14
+#define INC_14 15
+#define INC_15 16
+
+#define ROUNDBY(x, n) ((x) & ~((n)-1))
+
+/* subtraction tables */
+#define SUB2(x, y) SUB_##x##_##y
+#define SUB1(x, y) SUB2(x, y)
+#define SUB(x, y) SUB1(x, y)
+#define SUB_8_0 8
+#define SUB_8_1 7
+#define SUB_8_2 6
+#define SUB_8_3 5
+#define SUB_8_4 4
+#define SUB_8_5 3
+#define SUB_8_6 2
+#define SUB_8_7 1
+#define SUB_8_8 0
+#define SUB_7_0 7
+#define SUB_7_1 6
+#define SUB_7_2 5
+#define SUB_7_3 4
+#define SUB_7_4 3
+#define SUB_7_5 2
+#define SUB_7_6 1
+#define SUB_7_7 0
+#define SUB_6_0 6
+#define SUB_6_1 5
+#define SUB_6_2 4
+#define SUB_6_3 3
+#define SUB_6_4 2
+#define SUB_6_5 1
+#define SUB_6_6 0
+#define SUB_5_0 5
+#define SUB_5_1 4
+#define SUB_5_2 3
+#define SUB_5_3 2
+#define SUB_5_4 1
+#define SUB_5_5 0
+#define SUB_4_0 4
+#define SUB_4_1 3
+#define SUB_4_2 2
+#define SUB_4_3 1
+#define SUB_4_4 0
+#define SUB_3_0 3
+#define SUB_3_1 2
+#define SUB_3_2 1
+#define SUB_3_3 0
+#define SUB_2_0 2
+#define SUB_2_1 1
+#define SUB_2_2 0
+#define SUB_1_0 1
+#define SUB_1_1 0
+
+/* rounding operator */
+#define ROUNDBY(x, n) ((x) & ~((n)-1))
+
+/* loop unrolling (vertical) */
+#define LOOP1(I,STMT,...) STMT(I,__VA_ARGS__)
+#define LOOP2(I,STMT,...) STMT(I,__VA_ARGS__) LOOP1(INC(I),STMT,__VA_ARGS__)
+#define LOOP3(I,STMT,...) STMT(I,__VA_ARGS__) LOOP2(INC(I),STMT,__VA_ARGS__)
+#define LOOP4(I,STMT,...) STMT(I,__VA_ARGS__) LOOP3(INC(I),STMT,__VA_ARGS__)
+#define LOOP5(I,STMT,...) STMT(I,__VA_ARGS__) LOOP4(INC(I),STMT,__VA_ARGS__)
+#define LOOP6(I,STMT,...) STMT(I,__VA_ARGS__) LOOP5(INC(I),STMT,__VA_ARGS__)
+#define LOOP7(I,STMT,...) STMT(I,__VA_ARGS__) LOOP6(INC(I),STMT,__VA_ARGS__)
+#define LOOP8(I,STMT,...) STMT(I,__VA_ARGS__) LOOP7(INC(I),STMT,__VA_ARGS__)
+#define LOOP9(I,STMT,...) STMT(I,__VA_ARGS__) LOOP8(INC(I),STMT,__VA_ARGS__)
+#define LOOP10(I,STMT,...) STMT(I,__VA_ARGS__) LOOP9(INC(I),STMT,__VA_ARGS__)
+#define LOOP11(I,STMT,...) STMT(I,__VA_ARGS__) LOOP10(INC(I),STMT,__VA_ARGS__)
+#define LOOP12(I,STMT,...) STMT(I,__VA_ARGS__) LOOP11(INC(I),STMT,__VA_ARGS__)
+#define LOOP13(I,STMT,...) STMT(I,__VA_ARGS__) LOOP12(INC(I),STMT,__VA_ARGS__)
+#define LOOP14(I,STMT,...) STMT(I,__VA_ARGS__) LOOP13(INC(I),STMT,__VA_ARGS__)
+#define LOOP15(I,STMT,...) STMT(I,__VA_ARGS__) LOOP14(INC(I),STMT,__VA_ARGS__)
+#define LOOP16(I,STMT,...) STMT(I,__VA_ARGS__) LOOP15(INC(I),STMT,__VA_ARGS__)
+
+#define _LOOP_(n,I,STMT,...) LOOP##n(I,STMT,__VA_ARGS__)
+#define LOOP(n,I,STMT,...) _LOOP_(n,I,STMT,__VA_ARGS__)
+
+/* loop expansion (horizontal) */
+#define EXPAND0(I,TERM,OP,...)
+#define EXPAND1(I,TERM,OP,...) TERM(I,__VA_ARGS__)
+#define EXPAND2(I,TERM,OP,...) TERM(I,__VA_ARGS__) OP EXPAND1(INC(I),TERM,OP,__VA_ARGS__)
+#define EXPAND3(I,TERM,OP,...) TERM(I,__VA_ARGS__) OP EXPAND2(INC(I),TERM,OP,__VA_ARGS__)
+#define EXPAND4(I,TERM,OP,...) TERM(I,__VA_ARGS__) OP EXPAND3(INC(I),TERM,OP,__VA_ARGS__)
+#define EXPAND5(I,TERM,OP,...) TERM(I,__VA_ARGS__) OP EXPAND4(INC(I),TERM,OP,__VA_ARGS__)
+#define EXPAND6(I,TERM,OP,...) TERM(I,__VA_ARGS__) OP EXPAND5(INC(I),TERM,OP,__VA_ARGS__)
+#define EXPAND7(I,TERM,OP,...) TERM(I,__VA_ARGS__) OP EXPAND6(INC(I),TERM,OP,__VA_ARGS__)
+#define EXPAND8(I,TERM,OP,...) TERM(I,__VA_ARGS__) OP EXPAND7(INC(I),TERM,OP,__VA_ARGS__)
+
+#define _EXPAND_(n,I,TERM,OP,...) EXPAND##n(I,TERM,OP,__VA_ARGS__)
+#define EXPAND(n,I,TERM,OP,...) _EXPAND_(n,I,TERM,OP,__VA_ARGS__)
+#define EXPAND_TRI1(n,I,TERM,OP,...) EXPAND(n,I,TERM,OP,__VA_ARGS__)
+#define EXPAND_TRI(n,I,TERM,OP,...) EXPAND_TRI1(SUB(n,I),I,TERM,OP,__VA_ARGS__)
diff --git a/src/libmath/matrix.c b/src/libmath/matrix.c
new file mode 100644
index 0000000..e8bca0b
--- /dev/null
+++ b/src/libmath/matrix.c
@@ -0,0 +1,176 @@
+#include <u.h>
+#include <libn.h>
+#include <libmath.h>
+
+/* TODO: replace (incrementally) with native C version! */
+#include <vendor/blas/cblas.h>
+#include <vendor/blas/lapacke.h>
+
+// -----------------------------------------------------------------------
+// level 1
+
+error
+la·vecslice(math·Vector *x, int min, int max, int inc)
+{
+ if (max > x->len || min < 0) {
+ errorf("out of bounds: attempted to access vector past length");
+ return 1;
+ }
+ x->len = (max - min) / inc;
+ x->d += x->inc * min;
+ x->inc *= inc;
+
+ return 0;
+}
+
+/* simple blas wrappers */
+void
+la·veccopy(math·Vector *dst, math·Vector *src)
+{
+ return cblas_dcopy(src->len, src->d, src->inc, dst->d, dst->inc);
+}
+
+double
+la·vecnorm(math·Vector *x)
+{
+ return cblas_dnrm2(x->len, x->d, x->inc);
+}
+
+void
+la·vecscale(math·Vector *x, double a)
+{
+ return cblas_dscal(x->len, a, x->d, x->inc);
+}
+
+double
+la·vecdot(math·Vector *x, math·Vector *y)
+{
+ return cblas_ddot(x->len, x->d, x->inc, y->d, y->inc);
+}
+
+// -----------------------------------------------------------------------
+// level 2
+
+error
+la·vecmat(math·Vector *x, math·Matrix *M)
+{
+ if (M->dim[1] != x->len) {
+ errorf("incompatible matrix dimensions");
+ return 1;
+ }
+ if (M->state & ~mat·trans)
+ cblas_dgemv(CblasRowMajor,CblasNoTrans,M->dim[0],M->dim[1],1.,M->d,M->inc,x->d,x->inc,0.,x->d,x->inc);
+ else
+ cblas_dgemv(CblasRowMajor,CblasTrans,M->dim[0],M->dim[1],1.,M->d,M->inc,x->d,x->inc,0.,x->d,x->inc);
+
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// level 3
+
+void
+la·transpose(math·Matrix *X)
+{
+ int tmp;
+ X->state ^= mat·trans;
+ tmp = X->dim[0], X->dim[0] = X->dim[1], X->dim[1] = tmp;
+}
+
+error
+la·matrow(math·Matrix *X, int r, math·Vector *row)
+{
+ if (r < 0 || r >= X->dim[0]) {
+ errorf("out of bounds");
+ return 1;
+ }
+
+ row->len = X->dim[1];
+ row->inc = 1;
+ row->d = X->d + X->dim[1] * r;
+
+ return 0;
+}
+
+error
+la·matcol(math·Matrix *X, int c, math·Vector *col)
+{
+ if (c < 0 || c >= X->dim[1]) {
+ errorf("out of bounds");
+ return 1;
+ }
+
+ col->len = X->dim[0];
+ col->inc = X->dim[1];
+ col->d = X->d + c;
+
+ return 0;
+}
+
+error
+la·matslice(math·Matrix *X, int r[3], int c[3])
+{
+ /* TODO */
+ return 0;
+}
+
+error
+la·eig(math·Matrix *X)
+{
+
+}
+
+/* X = A*B */
+error
+la·matmul(math·Matrix *X, math·Matrix *A, math·Matrix *B)
+{
+ if (A->dim[1] != B->dim[0]) {
+ errorf("number of interior dimensions of A '%d' not equal to that of B '%d'", A->dim[1], B->dim[0]);
+ return 1;
+ }
+ if (X->dim[0] != A->dim[0]) {
+ errorf("number of exterior dimensions of X '%d' not equal to that of A '%d'", X->dim[0], A->dim[0]);
+ return 1;
+ }
+ if (X->dim[1] != B->dim[1]) {
+ errorf("number of exterior dimensions of X '%d' not equal to that of B '%d'", X->dim[1], B->dim[1]);
+ return 1;
+ }
+
+ if (X->state & ~mat·trans)
+ if (A->state & ~mat·trans)
+ cblas_dgemm(CblasRowMajor,CblasNoTrans,CblasNoTrans,A->dim[0],B->dim[1],A->dim[1],1.,A->d,A->inc,B->d,B->inc,0.,X->d,X->inc);
+ else
+ cblas_dgemm(CblasRowMajor,CblasNoTrans,CblasTrans,A->dim[0],B->dim[1],A->dim[1],1.,A->d,A->inc,B->d,B->inc,0.,X->d,X->inc);
+ else
+ if (A->state & ~mat·trans)
+ cblas_dgemm(CblasRowMajor,CblasTrans,CblasNoTrans,A->dim[0],B->dim[1],A->dim[1],1.,A->d,A->inc,B->d,B->inc,0.,X->d,X->inc);
+ else
+ cblas_dgemm(CblasRowMajor,CblasTrans,CblasTrans,A->dim[0],B->dim[1],A->dim[1],1.,A->d,A->inc,B->d,B->inc,0.,X->d,X->inc);
+
+ return 0;
+}
+
+/*
+ * solves A*X=B
+ * pass in B via X
+ */
+error
+la·solve(math·Matrix *X, math·Matrix *A)
+{
+ error err;
+ int n, *ipv;
+ static int buf[512];
+ if (n = A->dim[0], n < arrlen(buf)) {
+ ipv = buf;
+ n = 0;
+ } else
+ ipv = malloc(n*sizeof(*ipv));
+
+ /* TODO: utilize more specific regimes if applicable */
+ err = LAPACKE_dgesv(LAPACK_ROW_MAJOR,A->dim[0],X->dim[1],A->d,A->inc,ipv,X->d,X->inc);
+
+ if (n)
+ free(ipv);
+ return err;
+}
diff --git a/src/libmath/rules.mk b/src/libmath/rules.mk
new file mode 100644
index 0000000..577aba4
--- /dev/null
+++ b/src/libmath/rules.mk
@@ -0,0 +1,27 @@
+include share/push.mk
+
+# Iterate through subdirectory tree
+
+# local sources
+SRCS_$(d):=\
+ $(d)/basic.c\
+ $(d)/blas1.c\
+ $(d)/blas2.c\
+ $(d)/blas3.c
+CHECK_$(d):=\
+
+# outputs
+LIBS_$(d):=\
+ $(d)/libmath.a
+
+include share/paths.mk
+
+$(LIBS_$(d)): $(OBJS_$(d))
+ $(ARCHIVE)
+
+$(TEST_$(d)): TCFLAGS = -D_GNU_SOURCE
+$(TEST_$(d)): TCLIBS = -lpthread -lm $(LIB_DIR)/libblas.a
+$(TEST_$(d)): $(UNIT_$(d)) $(LIBS_$(d)) $(OBJ_DIR)/base/base.a
+ $(LINK)
+
+include share/pop.mk
diff --git a/src/libmath/test.c b/src/libmath/test.c
new file mode 100644
index 0000000..66700f8
--- /dev/null
+++ b/src/libmath/test.c
@@ -0,0 +1,471 @@
+#include <u.h>
+#include <base.h>
+/* #include <vendor/blas/cblas.h> */
+
+#include <x86intrin.h>
+
+#include <time.h>
+
+// -----------------------------------------------------------------------
+// Vectors
+
+/*
+ * NOTE: I'm not sure I like stashing the header in _all_ vectors
+ * The only way to fix is to have a library based allocator...
+ */
+typedef struct math·Vec
+{
+ struct {
+ void *h;
+ mem·Allocator heap;
+ };
+ int len;
+ double *d;
+} math·Vec;
+
+math·Vec
+math·makevec(int len, mem·Allocator heap, void *h)
+{
+ math·Vec v;
+ v.len = len;
+ v.heap = heap;
+ v.h = h;
+ v.d = heap.alloc(h, 1, len*sizeof(double));
+
+ // memset(v.d, 0, len*sizeof(double));
+
+ return v;
+}
+
+error
+math·freevec(math·Vec *v)
+{
+ if (v->h == nil && v->heap.alloc == nil && v->heap.free == nil) {
+ errorf("attempting to free a vector that doesn't own its data");
+ return 1;
+ }
+ v->heap.free(v->h, v->d);
+ v->d = nil;
+ v->len = 0;
+
+ return 0;
+}
+
+math·Vec
+math·copyvec(math·Vec v)
+{
+ math·Vec cpy;
+ cpy.heap = v.heap;
+ cpy.h = v.h;
+ cpy.len = v.len;
+ cpy.d = cpy.heap.alloc(cpy.h, 1, v.len);
+
+ memcpy(cpy.d, v.d, sizeof(double)*v.len);
+ return cpy;
+}
+
+/*
+ * Scale vector
+ */
+
+static
+void
+scale_kernel8_avx2(int n, double *x, double a)
+{
+ __m128d a128;
+ __m256d a256;
+ register int i;
+
+ a128 = _mm_load_sd(&a);
+ a256 = _mm256_broadcastsd_pd(a128);
+ for (i = 0; i < n; i += 8) {
+ _mm256_storeu_pd(x+i+0, a256 * _mm256_loadu_pd(x+i+0));
+ _mm256_storeu_pd(x+i+4, a256 * _mm256_loadu_pd(x+i+4));
+ }
+}
+
+static
+void
+scale_kernel8(int n, double *x, double a)
+{
+ register int i;
+ for (i = 0; i < n; i += 8) {
+ x[i+0] *= a;
+ x[i+1] *= a;
+ x[i+2] *= a;
+ x[i+3] *= a;
+ x[i+4] *= a;
+ x[i+5] *= a;
+ x[i+6] *= a;
+ x[i+7] *= a;
+ }
+}
+
+void
+math·scalevec(math·Vec u, double a)
+{
+ int n;
+
+ n = u.len & ~7;
+ scale_kernel8_avx2(n, u.d, a);
+
+ for (; n < u.len; n++) {
+ u.d[n] *= a;
+ }
+}
+
+/*
+ * Add scaled vector
+ */
+
+static
+void
+daxpy_kernel8_avx2(int n, double *x, double *y, double a)
+{
+ __m128d a128;
+ __m256d a256;
+ register int i;
+
+ a128 = _mm_load_sd(&a);
+ a256 = _mm256_broadcastsd_pd(a128);
+ for (i = 0; i < n; i += 8) {
+ _mm256_storeu_pd(x+i+0, _mm256_loadu_pd(x+i+0) + a256 * _mm256_loadu_pd(y+i+0));
+ _mm256_storeu_pd(x+i+4, _mm256_loadu_pd(x+i+4) + a256 * _mm256_loadu_pd(y+i+4));
+ }
+}
+
+static
+void
+daxpy_kernel8(int n, double *x, double *y, double a)
+{
+ register int i;
+ for (i = 0; i < n; i += 8) {
+ x[i+0] += a*y[i+0];
+ x[i+1] += a*y[i+1];
+ x[i+2] += a*y[i+2];
+ x[i+3] += a*y[i+3];
+ x[i+4] += a*y[i+4];
+ x[i+5] += a*y[i+5];
+ x[i+6] += a*y[i+6];
+ x[i+7] += a*y[i+7];
+ }
+}
+
+/* performs u = u + a*v */
+void
+math·addvec(math·Vec u, math·Vec v, double a)
+{
+ int n;
+
+ n = u.len & ~7;
+ daxpy_kernel8_avx2(n, u.d, v.d, a);
+
+ for (; n < u.len; n++) {
+ u.d[n] += a*v.d[n];
+ }
+}
+
+/*
+ * Dot product
+ */
+
+static
+double
+dot_kernel8_avx2(int len, double *x, double *y)
+{
+ register int i;
+ __m256d sum[4];
+ __m128d res;
+
+ for (i = 0; i < arrlen(sum); i++) {
+ sum[i] = _mm256_setzero_pd();
+ }
+
+ for (i = 0; i < len; i += 16) {
+ sum[0] += _mm256_loadu_pd(x+i+0) * _mm256_loadu_pd(y+i+0);
+ sum[1] += _mm256_loadu_pd(x+i+4) * _mm256_loadu_pd(y+i+4);
+ sum[2] += _mm256_loadu_pd(x+i+8) * _mm256_loadu_pd(y+i+8);
+ sum[3] += _mm256_loadu_pd(x+i+12) * _mm256_loadu_pd(y+i+12);
+ }
+
+ sum[0] += sum[1] + sum[2] + sum[3];
+
+ res = _mm_add_pd(_mm256_extractf128_pd(sum[0], 0), _mm256_extractf128_pd(sum[0], 1));
+ res = _mm_hadd_pd(res, res);
+
+ return res[0];
+}
+
+static
+double
+dot_kernel8_fma3(int len, double *x, double *y)
+{
+ register int i;
+ __m256d sum[4];
+ __m128d res;
+
+ for (i = 0; i < arrlen(sum); i++) {
+ sum[i] = _mm256_setzero_pd();
+ }
+
+ for (i = 0; i < len; i += 16) {
+ sum[0] = _mm256_fmadd_pd(_mm256_loadu_pd(x+i+0), _mm256_loadu_pd(y+i+0), sum[0]);
+ sum[1] = _mm256_fmadd_pd(_mm256_loadu_pd(x+i+4), _mm256_loadu_pd(y+i+4), sum[1]);
+ sum[2] = _mm256_fmadd_pd(_mm256_loadu_pd(x+i+8), _mm256_loadu_pd(y+i+8), sum[2]);
+ sum[3] = _mm256_fmadd_pd(_mm256_loadu_pd(x+i+12), _mm256_loadu_pd(y+i+12), sum[3]);
+ }
+
+ sum[0] += sum[1] + sum[2] + sum[3];
+
+ res = _mm_add_pd(_mm256_extractf128_pd(sum[0], 0), _mm256_extractf128_pd(sum[0], 1));
+ res = _mm_hadd_pd(res, res);
+
+ return res[0];
+}
+
+static
+double
+dot_kernel8(int len, double *x, double *y)
+{
+ double res;
+ register int i;
+
+ for (i = 0; i < len; i += 8) {
+ res += x[i] * y[i] +
+ x[i+1] * y[i+1] +
+ x[i+2] * y[i+2] +
+ x[i+3] * y[i+3] +
+ x[i+4] * y[i+4] +
+ x[i+5] * y[i+5] +
+ x[i+6] * y[i+6] +
+ x[i+7] * y[i+7];
+ }
+
+ return res;
+}
+
+double
+math·dot(math·Vec u, math·Vec v)
+{
+ int i, len;
+ double res;
+
+ len = u.len & ~15; // neat trick
+ res = dot_kernel8_fma3(len, u.d, v.d);
+
+ for (i = len; i < u.len; i++) {
+ res += u.d[i] * v.d[i];
+ }
+
+ return res;
+}
+
+// -----------------------------------------------------------------------
+// Matrix
+
+typedef struct math·Mtx
+{
+ struct {
+ void *h;
+ mem·Allocator heap;
+ };
+ int dim[2];
+ double *d;
+} math·Mtx;
+
+math·Mtx
+math·makemtx(int n, int m, mem·Allocator heap, void *h)
+{
+ math·Mtx a;
+ a.dim[0] = n;
+ a.dim[1] = m;
+ a.heap = heap;
+ a.h = h;
+ a.d = heap.alloc(h, 1, n*m*sizeof(double));
+
+ // memset(a.d, 0, n*m*sizeof(double));
+
+ return a;
+}
+
+error
+math·freemtx(math·Vec *m)
+{
+ if (m->h == nil && m->heap.alloc == nil && m->heap.free == nil) {
+ errorf("attempting to free a matrix that doesn't own its data");
+ return 1;
+ }
+ m->heap.free(m->h, m->d);
+ m->d = nil;
+ m->len = 0;
+
+ return 0;
+}
+
+/************************************************
+ * multiply matrix to vector
+ ***********************************************/
+
+/*
+ * Notation: (number of rows) x (number of columns) _ unroll factor
+ * N => variable we sum over
+ */
+static
+void
+mtxvec_kernel4xN_4_avx2(int ncol, double **row, double *x, double *y)
+{
+ int c;
+ __m128d hr;
+ __m256d x256, r256[4];
+
+ for (c = 0; c < 4; c++) {
+ r256[c] = _mm256_setzero_pd();
+ }
+
+ for (c = 0; c < ncol; c += 4) {
+ x256 = _mm256_loadu_pd(x+c);
+ r256[0] += x256 * _mm256_loadu_pd(row[0] + c);
+ r256[1] += x256 * _mm256_loadu_pd(row[1] + c);
+ r256[2] += x256 * _mm256_loadu_pd(row[2] + c);
+ r256[3] += x256 * _mm256_loadu_pd(row[3] + c);
+ }
+
+ for (c = 0; c < 4; c++) {
+ hr = _mm_add_pd(_mm256_extractf128_pd(r256[c], 0), _mm256_extractf128_pd(r256[c], 1));
+ hr = _mm_hadd_pd(hr, hr);
+ y[c] = hr[0];
+ }
+}
+
+static
+void
+mtxvec_kernel4xN_4(int ncol, double **row, double *x, double *y)
+{
+ int c;
+ double res[4];
+
+ res[0] = 0.;
+ res[1] = 0.;
+ res[2] = 0.;
+ res[3] = 0.;
+
+ for (c = 0; c < ncol; c += 4) {
+ res[0] += row[0][c+0]*x[c+0] + row[0][c+1]*x[c+1] + row[0][c+2]*x[c+2] + row[0][c+3]*x[c+3];
+ res[1] += row[1][c+0]*x[c+0] + row[1][c+1]*x[c+1] + row[1][c+2]*x[c+2] + row[1][c+3]*x[c+3];
+ res[2] += row[2][c+0]*x[c+0] + row[2][c+1]*x[c+1] + row[2][c+2]*x[c+2] + row[2][c+3]*x[c+3];
+ res[3] += row[3][c+0]*x[c+0] + row[3][c+1]*x[c+1] + row[3][c+2]*x[c+2] + row[3][c+3]*x[c+3];
+ }
+
+ y[0] = res[0];
+ y[1] = res[1];
+ y[2] = res[2];
+ y[3] = res[3];
+}
+
+static
+void
+mtxvec_kernel1xN_4(int ncol, double *row, double *x, double *y)
+{
+ int c;
+ double res;
+
+ res = 0.;
+ for (c = 0; c < ncol; c += 4) {
+ res += row[c+0]*x[c+0] + row[c+1]*x[c+1] + row[c+2]*x[c+2] + row[c+3]*x[c+3];
+ }
+
+ y[0] = res;
+}
+
+// y = a*mx + b*y
+error
+math·mtxvec(math·Mtx m, double a, math·Vec x, double b, math·Vec y)
+{
+ int c, r, nrow, ncol;
+ double *row[4], res[4];
+
+ nrow = m.dim[0] & ~3;
+ ncol = m.dim[1] & ~3;
+ for (r = 0; r < nrow; r += 4) {
+ row[0] = m.d + (r * (m.dim[1]+0));
+ row[1] = m.d + (r * (m.dim[1]+1));
+ row[2] = m.d + (r * (m.dim[1]+2));
+ row[3] = m.d + (r * (m.dim[1]+3));
+
+ mtxvec_kernel4xN_4_avx2(ncol, row, x.d + r, res);
+
+ for (c = ncol; c < m.dim[1]; c++) {
+ res[0] += row[0][c];
+ res[1] += row[1][c];
+ res[2] += row[2][c];
+ res[3] += row[3][c];
+ }
+
+ y.d[r+0] = res[0] + b*y.d[r+0];
+ y.d[r+1] = res[1] + b*y.d[r+1];
+ y.d[r+2] = res[2] + b*y.d[r+2];
+ y.d[r+3] = res[3] + b*y.d[r+3];
+ }
+
+ for (; r < m.dim[0]; r++) {
+ mtxvec_kernel1xN_4(m.dim[0], m.d + (r * m.dim[1]), x.d + r, res);
+ y.d[r] = res[0] + b*y.d[r];
+ }
+
+ return 0;
+}
+
+/************************************************
+ * add matrix to vector outerproduct
+ ***********************************************/
+
+#define NITER 50
+
+#if 0
+error
+main()
+{
+ int i;
+ clock_t t;
+ double res;
+
+ math·Mtx m;
+ math·Vec x, y;
+
+ openblas_set_num_threads(1);
+
+ x = math·makevec(1000, mem·sys, nil);
+ y = math·makevec(1000, mem·sys, nil);
+ m = math·makemtx(1000, 1000, mem·sys, nil);
+
+ for (i = 0; i < x.len; i++) {
+ y.d[i] = i;
+ }
+
+ t = clock();
+ for (i = 0; i < NITER; i++) {
+ cblas_dgemv(CblasRowMajor, CblasNoTrans, m.dim[0], m.dim[1], 1.5, m.d, m.dim[1], x.d, 1, 2.5, y.d, 1);
+ }
+ t = clock() - t;
+ res = math·dot(y, y);
+ printf("the result is %f\n", res);
+ printf("time elapsed (blas): %fms\n", 1000.*t/CLOCKS_PER_SEC);
+
+ for (i = 0; i < x.len; i++) {
+ y.d[i] = i;
+ }
+
+ t = clock();
+ for (i = 0; i < NITER; i++) {
+ math·mtxvec(m, 1.5, x, 2.5, y);
+ }
+ t = clock() - t;
+ res = math·dot(y, y);
+
+ printf("the dot product is %f\n", res);
+ printf("time elapsed (naive): %fms\n", 1000.*t/CLOCKS_PER_SEC);
+
+
+ return 0;
+}
+#endif
diff --git a/src/libsre/lex.c b/src/libsre/lex.c
new file mode 100644
index 0000000..f4c6ac2
--- /dev/null
+++ b/src/libsre/lex.c
@@ -0,0 +1,246 @@
+#include "sre.h"
+
+static
+State *
+state(Machine *m, int t)
+{
+ if (m->state >= m->statestk + arrlen(m->statestk))
+ panicf("regexp vm: out of state space");
+
+ m->state->type = t;
+ m->state->l = nil;
+ m->state->r = nil;
+
+ return m->state++;
+}
+
+static
+int
+poptor(Parser *p)
+{
+ if (p->optor <= p->optorstk)
+ panicf("regexp parser: opand stack underflow");
+
+ return *--p->optor;
+}
+
+static
+void
+pushtor(Parser *p, int t)
+{
+ if (p->optor >= arrend(p->optorstk))
+ panicf("regexp parser: opand stack overflow");
+
+ *p->optor++ = t;
+}
+
+static
+void
+pushand(Parser *p, State *beg, State *end)
+{
+ if (p->node >= arrend(p->nodestk))
+ panicf("regexp parser: opand stack overflow");
+
+ p->node->beg = beg;
+ p->node->end = end;
+
+ p->node++;
+}
+
+static
+Node *
+popand(Parser *p)
+{
+ if (p->node <= p->nodestk)
+ panicf("regexp parser: opand stack underflow");
+
+ return --p->node;
+}
+
+static
+void
+operateuntil(Parser *p, int prec)
+{
+ Node *o1, *o2, *t;
+ State *s1, *s2;
+
+ while (prec == Trparen || p->optor[-1] >= prec) {
+ switch (poptor(p)) {
+ case Tor:
+ o1 = popand(p);
+ o2 = popand(p);
+
+ s1 = state(p->mach, Tor);
+ s2 = state(p->mach, Tnop);
+ s1->l = o1->beg;
+ s1->r = o2->beg;
+
+ o1->end->out = s2;
+ o2->end->out = s2;
+
+ pushand(p, s1, s2);
+ break;
+
+ case Tcat:
+ o1 = popand(p);
+ o2 = popand(p);
+
+ o1->end->out = o2->beg;
+ pushand(p, o1->beg, o2->end);
+ break;
+
+ case Tstar:
+ o1 = popand(p);
+ s1 = state(p->mach, Tor);
+ o1->end->out = s1;
+ s1->l = o1->beg;
+ pushand(p, s1, s1);
+ break;
+
+ case Tplus:
+ o1 = popand(p);
+ s1 = state(p->mach, Tor);
+ o1->end->out = s1;
+ s1->l = o1->beg;
+ pushand(p, o1->beg, s1);
+ break;
+
+ case Tqmark:
+ o1 = popand(p);
+ s1 = state(p->mach, Tor);
+ s2 = state(p->mach, Tnop);
+ s1->l = o1->beg;
+ s1->r = s2;
+ o1->end->out = s2;
+ pushand(p, s1, s2);
+ break;
+
+ default:
+ panicf("unsupported regexp operator");
+ }
+ }
+}
+
+static
+void
+operator(Parser *p, int t)
+{
+ operateuntil(p, t);
+ pushtor(p, t);
+ p->wasopand = (t != Tstar && t != Tqmark && t != Tplus && t != Trparen);
+}
+
+static
+void
+operand(Parser *p, int t)
+{
+ State *new;
+ if (p->wasopand)
+ operator(p, Tcat);
+
+ new = state(p->mach, t);
+ pushand(p, new, new);
+ p->wasopand = true;
+}
+
+#define cinc 20
+int
+lex(Parser *p)
+{
+ int c, t;
+ byte *class;
+ long n, cap;
+
+ c = *p->re++;
+ switch (c) {
+ case '\\':
+ if (*p->re)
+ if ((c = *p->re++) == '\n')
+ c = '\n';
+ break;
+ case '\0':
+ c = Tend,
+ --p->re;
+ break;
+ case '*':
+ c = Tstar;
+ break;
+ case '?':
+ c = Tqmark;
+ break;
+ case '+':
+ c = Tplus;
+ break;
+ case '|':
+ c = Tor;
+ break;
+ case '.':
+ c = Tany;
+ break;
+ case '(':
+ c = Tlparen;
+ break;
+ case ')':
+ c = Trparen;
+ break;
+ case '^':
+ c = Tbol;
+ break;
+ case '$':
+ c = Teol;
+ break;
+ case '[':
+ goto charclass;
+ default:
+ ;
+ }
+ return c;
+
+charclass:
+ panicf("to implement");
+}
+
+#undef cinc
+
+static
+State*
+optimize(State *entry)
+{
+ State *curr, *next;
+ for (curr=entry; curr->type != Tend; curr++) {
+ next = curr->out;
+ while (next->type == Tnop)
+ next = next->out;
+ curr->out = next;
+ }
+
+ return entry;
+}
+
+void
+sre·compile(Machine *mach, byte *regexp)
+{
+ int tok;
+ Parser p;
+ Node *prog;
+
+ p = (Parser) {
+ .re = regexp,
+ .mach = mach,
+ .node = p.nodestk,
+ };
+
+ pushtor(&p, Tstart - 1);
+ while ((tok = lex(&p)) != Tend) {
+ if ((tok & isoptor) == Toperator)
+ operator(&p, tok);
+ else
+ operand(&p, tok);
+ }
+ operateuntil(&p, Tstart);
+ operand(&p, Tend);
+ operateuntil(&p, Tstart);
+
+ prog = popand(&p);
+ mach->entry = optimize(prog->beg);
+}
diff --git a/src/libsre/sre.h b/src/libsre/sre.h
new file mode 100644
index 0000000..a7ace1a
--- /dev/null
+++ b/src/libsre/sre.h
@@ -0,0 +1,93 @@
+#pragma once
+
+#include <u.h>
+#include <libn.h>
+
+enum
+{
+ Toperator = RuneMask + 1,
+ Tstart = Toperator,
+ Trparen,
+ Tlparen,
+ Tor,
+ Tcat,
+ Tstar,
+ Tplus,
+ Tqmark,
+
+ Tany = Toperator << 1,
+ Tnop,
+ Tbol,
+ Teol,
+ Tcclass,
+ Tnclass,
+ Tend,
+
+ isoptor = Toperator,
+ isopand = Toperator << 1,
+};
+
+typedef struct Class Class;
+typedef struct State State;
+typedef struct Patch Patch;
+typedef struct Node Node;
+
+typedef struct Parser Parser;
+typedef struct Machine Machine;
+
+struct Class
+{
+ rune *end;
+ rune span[64];
+};
+
+struct State
+{
+ int type;
+ union {
+ State *l;
+ };
+ union {
+ State *r;
+ State *out;
+ };
+};
+
+struct Patch
+{
+ State *s;
+ Patch *link;
+};
+
+struct Node
+{
+ State *beg;
+ State *end;
+};
+
+struct Parser
+{
+ Machine *mach;
+ byte *re;
+ int wasopand : 1;
+ int *optor, optorstk[1000];
+ Node *node, nodestk[1000];
+};
+
+struct Machine
+{
+ /* memory buffers */
+ struct {
+ void *heap;
+ mem·Reallocator;
+ };
+ State *state, statestk[1000];
+
+ struct {
+ int cap;
+ int len;
+ Class *c;
+ } class;
+
+ State *entry;
+};
diff --git a/src/libterm/term.c b/src/libterm/term.c
new file mode 100644
index 0000000..11591fc
--- /dev/null
+++ b/src/libterm/term.c
@@ -0,0 +1,489 @@
+#include "term.h"
+
+#include <signal.h>
+#include <sys/ioctl.h>
+
+struct ExtraInfo
+{
+ char *enteralt;
+ char *exitalt;
+
+ char *entermouse;
+ char *exitmouse;
+};
+
+static
+struct ExtraInfo vt200 =
+{
+ .enteralt = "\e[?1049h",
+ .exitalt = "\e[?1049l",
+
+ .entermouse = "\e[?1049h\e[?1006l",
+ .exitmouse = "\e[?1002l\e[?1006l",
+};
+
+static Term *sigwinchhead;
+
+// -----------------------------------------------------------------------
+// database lookup
+
+static
+char*
+tryinfostr(Term *t, enum unibi_string s)
+{
+ char *val = (char*)unibi_get_str(t->info, s);
+ /* TODO: provide fallbacks */
+ return val;
+}
+
+static
+char*
+guessinfostr(Term *t, enum unibi_string s, char *guess)
+{
+ char *val = (char*)unibi_get_str(t->info, s);
+ if (!val)
+ return guess;
+ return val;
+}
+
+static
+char*
+getinfostr(Term *t, enum unibi_string s)
+{
+ char *val = tryinfostr(t, s);
+ if (!val)
+ panicf("required term info string '%s' missing", unibi_name_str(s));
+
+ return val;
+}
+
+static
+char *
+tryextrastr(Term *t, char *name)
+{
+ const char *nm;
+ size_t max = unibi_count_ext_str(t->info);
+ for (size_t i = 0; i < max; i++) {
+ nm = unibi_get_ext_str_name(t->info, i);
+ if (nm && !strcmp(nm, name)) {
+ return (char *)nm;
+ }
+ }
+ return nil;
+}
+
+static
+char *
+guessextrastr(Term *t, char *name, char *guess)
+{
+ char *s;
+ if ((s = tryextrastr(t, name)))
+ return s;
+
+ return guess;
+}
+
+/* formats escape strings and writes to output */
+static void tfmt(Term *t, char *esc, int n, ...);
+static void tclear(Term *t);
+
+// -----------------------------------------------------------------------
+// exported term methods
+
+static
+char *
+ttmpbuf(Term *t, int len)
+{
+ if (t->tmp.len >= len)
+ return t->tmp.b;
+
+ /* TODO: error handling */
+ return (t->tmp.b = realloc(t->tmp.b, len));
+}
+
+void twrite(Term *t, long len, char *s);
+void tlistensigwinch(Term *t);
+
+Term*
+tmake(void)
+{
+ Term *t;
+
+ t = calloc(1, sizeof(*t));
+
+ /* meta data */
+ t->name = getenv("TERM");
+ t->info = unibi_from_term(t->name);
+ if (!t->info)
+ panicf("could not identify terminal");
+
+ t->fd = 1; // stdout
+ tlistensigwinch(t);
+
+ t->mode.mouse = 0;
+ t->mode.cursorvis = 1;
+ t->mode.altscreen = 0;
+
+ t->cap.colors = unibi_get_num(t->info, unibi_max_colors);
+ t->cap.bce = unibi_get_bool(t->info, unibi_back_color_erase);
+
+ /* initialize root window (get current size)*/
+ struct winsize ws = { 0 };
+ if (ioctl(t->fd, TIOCGWINSZ, &ws) == 1)
+ goto bad;
+
+ t->root = wmake(nil, 0, 0, ws.ws_col, ws.ws_row, 0);
+
+ t->root->curvis = 1;
+ t->root->blink = 0;
+
+ t->pen = (Pen){
+ .state = PenNormal,
+ .col = {.fg = -1, .bg = -1},
+ };
+
+ /* fill in output buffers */
+ t->buf.c = t->buf.b;
+ t->tmp.b = nil;
+ t->tmp.len = 0;
+
+ /* get all term info format strings */
+ t->esc.cup = getinfostr(t, unibi_cursor_address);
+ t->esc.vpa = tryinfostr(t, unibi_row_address);
+ t->esc.hpa = tryinfostr(t, unibi_column_address);
+ t->esc.cuu = getinfostr(t, unibi_parm_up_cursor);
+ t->esc.cuu1 = tryinfostr(t, unibi_cursor_up);
+ t->esc.cud = getinfostr(t, unibi_parm_down_cursor);
+ t->esc.cud1 = tryinfostr(t, unibi_cursor_down);
+ t->esc.cuf = getinfostr(t, unibi_parm_right_cursor);
+ t->esc.cuf1 = tryinfostr(t, unibi_cursor_right);
+ t->esc.cub = getinfostr(t, unibi_parm_left_cursor);
+ t->esc.cub1 = tryinfostr(t, unibi_cursor_left);
+ t->esc.ich = getinfostr(t, unibi_parm_ich);
+ t->esc.ich1 = tryinfostr(t, unibi_insert_character);
+ t->esc.dch = getinfostr(t, unibi_parm_dch);
+ t->esc.dch1 = tryinfostr(t, unibi_delete_character);
+ t->esc.il = getinfostr(t, unibi_parm_insert_line);
+ t->esc.il1 = tryinfostr(t, unibi_insert_line);
+ t->esc.dl = getinfostr(t, unibi_parm_delete_line);
+ t->esc.dl1 = tryinfostr(t, unibi_delete_line);
+ t->esc.ech = getinfostr(t, unibi_erase_chars);
+ t->esc.ed2 = getinfostr(t, unibi_clear_screen);
+ t->esc.stbm = getinfostr(t, unibi_change_scroll_region);
+ t->esc.sgr = getinfostr(t, unibi_set_attributes);
+ t->esc.sgr0 = getinfostr(t, unibi_exit_attribute_mode);
+ t->esc.sgr_i0 = tryinfostr(t, unibi_exit_italics_mode);
+ t->esc.sgr_i1 = tryinfostr(t, unibi_enter_italics_mode);
+ t->esc.sgr_fg = getinfostr(t, unibi_set_a_foreground);
+ t->esc.sgr_bg = getinfostr(t, unibi_set_a_background);
+ t->esc.sm_csr = getinfostr(t, unibi_cursor_normal);
+ t->esc.rm_csr = getinfostr(t, unibi_cursor_invisible);
+
+ /* extensions to terminfo */
+ t->esc.ext.rgbf = guessextrastr(t, "setrgbf", "\x1b[38;2;%p1%d;%p2%d;%p3%dm");
+ t->esc.ext.rgbb = guessextrastr(t, "setrgbb", "\x1b[48;2;%p1%d;%p2%d;%p3%dm");
+
+ return t;
+
+bad:
+ panicf("failed to initialize terminal instance");
+ free(t);
+ return nil;
+}
+
+void
+tfree(Term *t)
+{
+ if (t->mode.mouse)
+ twrite(t, 0, vt200.exitmouse);
+ if (!t->mode.cursorvis)
+ tfmt(t, t->esc.rm_csr, 0);
+ if (t->mode.altscreen)
+ twrite(t, 0, vt200.exitalt);
+
+ tfmt(t, t->esc.sgr0, 0);
+ tclear(t);
+ free(t);
+}
+
+/* handle resize events */
+void
+tresize(Term *t)
+{
+ if (t->fd == -1)
+ return;
+
+ struct winsize ws = { 0 };
+ if (ioctl(t->fd, TIOCGWINSZ, &ws) == 1)
+ return;
+
+ printf("[%d,%d]\n", ws.ws_col, ws.ws_row);
+ if (t->root->w != ws.ws_col || t->root->h != ws.ws_row)
+ wresize(t->root, ws.ws_col, ws.ws_row);
+}
+
+static
+void
+sigwinch(int num)
+{
+ Term *it;
+ for (it = sigwinchhead; it; it = it->link)
+ tresize(it);
+}
+
+void
+tlistensigwinch(Term *t)
+{
+ sigset_t new, old;
+ Term *it;
+
+ sigemptyset(&new);
+ sigaddset(&new, SIGWINCH);
+ sigprocmask(SIG_BLOCK, &new, &old);
+
+ if (!sigwinchhead) {
+ sigaction(SIGWINCH, &(struct sigaction){ .sa_handler = sigwinch }, nil);
+ sigwinchhead = t;
+ } else {
+ it = sigwinchhead;
+ while (it->link)
+ it = it->link;
+ it->link = t;
+ }
+
+ sigprocmask(SIG_SETMASK, &old, nil);
+}
+
+void
+tflush(Term *t)
+{
+ if (t->fd != -1)
+ write(t->fd, t->buf.b, t->buf.c - t->buf.b);
+
+ t->buf.c = t->buf.b;
+}
+
+void
+twrite(Term *t, long len, char *s)
+{
+ int n;
+ if (!len)
+ len = strlen(s);
+
+loop:
+ n = MIN(len, arrend(t->buf.b) - t->buf.c);
+ memcpy(t->buf.c, s, n);
+ t->buf.c += n;
+ len -= n;
+ if (len) {
+ tflush(t);
+ goto loop;
+ }
+}
+
+void
+tsetpen(Term *t, Pen new)
+{
+ int c;
+ ushort ic, in;
+ Pen cur = t->pen;
+ if (!memcmp(&new, &cur, sizeof(new)))
+ return;
+
+ /* attributes */
+ tfmt(t, t->esc.sgr, 9,
+ 0, /* standout */
+ new.state & PenUnderline,
+ new.state & PenReverse,
+ new.state & PenBlink,
+ new.state & PenDim,
+ new.state & PenBold,
+ new.state & PenInvis,
+ 0, /* protect */
+ 0); /* alt */
+
+ ic = cur.state & PenItalic;
+ in = new.state & PenItalic;
+ if (ic & ~in)
+ tfmt(t, t->esc.sgr_i0, 0);
+ else if (~ic & in)
+ tfmt(t, t->esc.sgr_i1, 0);
+
+ /* fg/bg color */
+ /* TODO: add a check for if the terminal supports true color */
+ /* TODO: deal w/ negative indices properly */
+ if (new.state & PenRGB) {
+ tfmt(t, t->esc.ext.rgbf, 3, new.rgb.fg.r, new.rgb.fg.g, new.rgb.fg.b);
+ tfmt(t, t->esc.ext.rgbb, 3, new.rgb.bg.r, new.rgb.bg.g, new.rgb.bg.b);
+ } else {
+ tfmt(t, t->esc.sgr_fg, 1, new.col.fg);
+ tfmt(t, t->esc.sgr_bg, 1, new.col.bg);
+ }
+
+ t->pen = new;
+}
+
+static
+void
+tfmt(Term *t, char *esc, int n, ...)
+{
+ int i;
+ long len;
+ va_list args;
+ unibi_var_t param[9];
+ char buf[64], *c = buf;
+
+ if (!esc)
+ panicf("no terminfo escape string given");
+
+ va_start(args, n);
+ for (i = 0; i < arrlen(param) && i < n; i++) {
+ param[i] = unibi_var_from_num(va_arg(args, int));
+ }
+ va_end(args);
+
+ len = unibi_run(esc, param, c, sizeof(buf));
+ if (len >= arrlen(buf)) {
+ c = ttmpbuf(t, len);
+ unibi_run(esc, param, c, len);
+ }
+
+ twrite(t, len, c);
+}
+
+/* absolute move */
+static
+int
+tgoto(Term *t, int row, int col)
+{
+ if (row != -1 && col != -1)
+ tfmt(t, t->esc.cup, 2, row, col);
+ else if (row != -1) {
+ if (!t->esc.vpa)
+ return 0;
+ tfmt(t, t->esc.vpa, 1, row);
+ } else if (col != -1) {
+ if (col == 0) {
+ twrite(t, 1, "\r");
+ return 1;
+ }
+ if (t->esc.hpa)
+ tfmt(t, t->esc.hpa, 1, col);
+ else if (t->esc.cuf) {
+ twrite(t, 1, "\r");
+ tfmt(t, t->esc.cuf, 1, col);
+ } else
+ return 0;
+ } else
+ return 0; /* unreachable */
+
+ return 1;
+}
+
+/* relative move */
+static
+void
+tjump(Term *t, int down, int right)
+{
+ if (down == 1 && t->esc.cud1)
+ tfmt(t, t->esc.cud1, 0);
+ else if (down == -1 && t->esc.cuu1)
+ tfmt(t, t->esc.cuu1, 0);
+ else if (down > 0)
+ tfmt(t, t->esc.cud, 1, down);
+ else if (down < 0)
+ tfmt(t, t->esc.cuu, 1, -down);
+
+ if (right == 1 && t->esc.cuf1)
+ tfmt(t, t->esc.cuf1, 0);
+ else if (right == -1 && t->esc.cub1)
+ tfmt (t, t->esc.cub1, 0);
+ else if (right > 0)
+ tfmt(t, t->esc.cuf, 1, right);
+ else if( right < 0)
+ tfmt(t, t->esc.cub, 1, -right);
+}
+
+static
+void
+tclear(Term *t)
+{
+ tfmt(t, t->esc.ed2, 0);
+}
+
+void
+tblit(Term *t, Window *win)
+{
+ int r, c, n, j;
+ Row *row;
+ char u[UTFmax+1] = {0};
+
+ j = 0;
+ tgoto(t, win->top, win->left);
+ for (r = 0; r < win->h; r++) {
+ row = win->row + r;
+ if (!row->dirty) {
+ j++;
+ continue;
+ }
+
+ if (j) {
+ tjump(t, j, 0);
+ j = 0;
+ }
+
+ for (c = 0; c < win->w; c++) {
+ tsetpen(t, row->cells[c].pen);
+ n = utf8·runetobyte(u, &row->cells[c].txt);
+ twrite(t, n, u);
+ }
+
+ row->dirty = 0;
+ }
+
+ tflush(t);
+}
+
+// -----------------------------------------------------------------------
+// testing
+
+int
+main()
+{
+ int i;
+ Term *t;
+ Window *win;
+
+ t = tmake();
+ win = t->root;
+ tclear(t);
+
+ win->pen = (Pen){
+ .state = PenNormal,
+ .col = {.fg=-1, .bg=-1},
+ };
+ for (i = 0; i < 2000; i++)
+ wputrune(win, 'a');
+
+ tblit(t, win);
+
+ win->cur.row = 10;
+ win->cur.col = 0;
+
+ win->pen = (Pen){
+ .state=PenNormal|PenRGB,
+ .rgb={.fg={200, 100, 100}, .bg={0, 0, 0} },
+ };
+
+ for (i = 0; i < 500; i++)
+ wputrune(win, 'b');
+
+ tblit(t, win);
+
+ sleep(5);
+ wscroll(win, 10);
+ tblit(t, win);
+ sleep(5);
+
+ tfree(t);
+}
diff --git a/src/libterm/term.h b/src/libterm/term.h
new file mode 100644
index 0000000..6bd2f6b
--- /dev/null
+++ b/src/libterm/term.h
@@ -0,0 +1,270 @@
+#pragma once
+
+#include <u.h>
+#include <libn.h>
+
+#include <termios.h>
+#include <unibilium.h>
+
+#define iota(x) 1 << (x)
+
+typedef struct RGB8 RGB8;
+typedef struct Pen Pen;
+
+typedef struct Dot Dot;
+typedef struct Cell Cell;
+typedef struct Row Row;
+typedef struct Buffer Buffer;
+typedef struct Window Window;
+
+typedef struct Node Node;
+typedef struct Key Key;
+typedef struct Input Input;
+
+typedef struct Term Term;
+
+struct RGB8
+{
+ uint8 r, g, b;
+};
+
+enum
+{
+ PenNormal = 0,
+ PenBold = iota(0),
+ PenDim = iota(1),
+ PenInvis = iota(2),
+ PenItalic = iota(3),
+ PenReverse = iota(4),
+ PenStrike = iota(5),
+ PenUnderline = iota(6),
+ PenBlink = iota(7),
+ /* ... */
+ PenRGB = iota(15),
+};
+
+struct Pen
+{
+ ushort state;
+ union {
+ /* 256 color (legacy) */
+ struct {
+ sshort fg : 8, bg : 8; /* 0 - 255 or COLOUR_DEFAULT */
+ } col;
+ /* true color (modern) */
+ struct {
+ RGB8 fg, bg;
+ } rgb;
+ };
+};
+
+/* outputs */
+struct Cell
+{
+ rune txt;
+ Pen pen;
+};
+
+struct Row
+{
+ Cell *cells;
+ uint dirty : 1;
+};
+
+struct Dot
+{
+ int row, col;
+};
+
+/*
+ * scroll.top & scroll.bot are pointers into the viewport.
+ *
+ * scroll back buffer
+ *
+ * scroll.buf->+----------------+-----+
+ * | | | ^ \
+ * | before | | | |
+ * current terminal content | viewport | | | |
+ * | | | |
+ * +----------------+-----+\ | | | s > scroll.above
+ * ^ | | i | \ | | i | c |
+ * | | | n | \ | | n | r |
+ * | | v | \ | | v | o |
+ * | | i | \ | | i | l /
+ * | buffer | s | >|<- scroll.index | s | l \
+ * h | | i | / | | i | |
+ * | | b | / | after | b | s > scroll.below
+ * | | l | / | viewport | l | i |
+ * v | | e | / | | e | z /
+ * +----------------+-----+/ | unused | | e
+ * <- maxw -> | scroll back | |
+ * <- w -> | buffer | | |
+ * | | | |
+ * | | | v
+ * scroll.buf + scroll.size->+----------------+-----+
+ * <- maxw ->
+ * <- w ->
+ */
+
+struct Buffer
+{
+ int w, h; /* dimension of buffer */
+ Pen pen; /* default attributes */
+ int maxw; /* allocated cells (maximal cols over time) */
+ Row *row; /* array of row pointers of size 'h' */
+ struct {
+ Row *buf;
+ Row *top;
+ Row *bot;
+ int size;
+ int index;
+ int above;
+ int below;
+ } scroll;
+ Dot cur, save; /* cursor position within buffer */
+};
+
+struct Window
+{
+ struct Buffer;
+ int top, left;
+ uchar curvis : 1;
+ uchar blink : 2;
+
+ Window *parent, *child, *link;
+};
+
+/* input */
+struct Key
+{
+ int type;
+ int mods;
+ uchar utf8[UTFmax+1];
+ union {
+ rune pt;
+ int num;
+ int sym;
+ char mouse[4];
+ } code;
+};
+
+struct KeyInfo
+{
+ int type;
+ int sym;
+ int modmask;
+ int modset;
+};
+
+struct Input
+{
+ int fd;
+ int flags;
+ int wait; /* in ms */
+
+ /* modifiers */
+ uchar closed : 1;
+ uchar started : 1;
+ uchar hasold : 1;
+
+ struct termios oldterm;
+
+ /* buffer */
+ struct {
+ long off;
+ uchar *b, *c, *e, bytes[256];
+ } rbuf;
+ struct {
+ uchar *s, bytes[256];
+ } ebuf;
+
+ /* key data */
+ Node *keys;
+ struct KeyInfo c0[32];
+};
+
+
+struct Term
+{
+ /* meta data */
+ char *name;
+ unibi_term *info;
+ struct {
+ uchar altscreen : 1;
+ uchar cursorvis : 1;
+ uchar mouse : 1;
+ } mode;
+ struct {
+ uchar bce : 1;
+ int colors;
+ } cap;
+
+ /* input capture */
+ Input input;
+
+ /* output display */
+ Window *root;
+ Pen pen;
+
+ /* raw text to pty */
+ int fd;
+ struct {
+ char *c, b[512];
+ } buf;
+
+ struct {
+ int len;
+ char *b;
+ } tmp;
+
+ /* info */
+ struct {
+ /* Positioning */
+ char *cup; // cursor_address
+ char *vpa; // row_address == vertical position absolute
+ char *hpa; // column_address = horizontal position absolute
+
+ /* Moving */
+ char *cuu; char *cuu1; // Cursor Up
+ char *cud; char *cud1; // Cursor Down
+ char *cuf; char *cuf1; // Cursor Forward == Right
+ char *cub; char *cub1; // Cursor Backward == Left
+
+ /* Editing */
+ char *ich; char *ich1; // Insert Character
+ char *dch; char *dch1; // Delete Character
+ char *il; char *il1; // Insert Line
+ char *dl; char *dl1; // Delete Line
+ char *ech; // Erase Character
+ char *ed2; // Erase Data 2 == Clear screen
+ char *stbm; // Set Top/Bottom Margins
+
+ /* formatting */
+ char *sgr; // Select Graphic Rendition
+ char *sgr0; // Exit Attribute Mode
+ char *sgr_i0, *sgr_i1; // SGR italic off/on
+ char *sgr_fg; // SGR foreground colour
+ char *sgr_bg; // SGR background colour
+
+ /* Mode setting/clearing */
+ char *sm_csr; char *rm_csr; // Set/reset mode: Cursor visible
+
+ /* augmentations to terminfo */
+ struct {
+ char *rgbf; // rgb foreground
+ char *rgbb; // rgb background
+ char *smxx; // strikethrough
+ char *smulx; // curly underline
+ } ext;
+ } esc;
+
+ Term *link;
+};
+
+/* functions */
+void tresize(Term *t);
+
+Window *wmake(Window *root, int top, int left, int w, int h, int scroll);
+void wresize(Window *root, int w, int h);
+void wputrune(Window *win, rune r);
+void wscroll(Window *win, int s);
diff --git a/src/libterm/window.c b/src/libterm/window.c
new file mode 100644
index 0000000..5d36c8b
--- /dev/null
+++ b/src/libterm/window.c
@@ -0,0 +1,408 @@
+#include "term.h"
+
+// -----------------------------------------------------------------------
+// buffers
+
+static
+void
+zero(Row *row, int start, int len)
+{
+ int i;
+ Cell cell = {
+ .txt = L' ',
+ .pen = {
+ .state = PenNormal,
+ .col.fg = -1,
+ .col.bg = -1,
+ },
+ };
+
+ for (i = start; i < len + start; i++)
+ row->cells[i] = cell;
+ row->dirty = 1;
+}
+
+static
+void
+roll(Row *start, Row *end, int count)
+{
+ int n = end - start;
+
+ /* enforce circularity */
+ count %= n;
+ if (count < 0)
+ count += n;
+
+ if (count) {
+ char buf[count * sizeof(Row)]; /* XXX: remove VLA */
+ memcpy(buf, start, count * sizeof(Row));
+ memmove(start, start + count, (n - count) * sizeof(Row));
+ memcpy(end - count, buf, count * sizeof(Row));
+
+ for (Row *row = start; row < end; row++)
+ row->dirty = 1;
+ }
+}
+
+/* buffer operations */
+static
+void
+bclear(Buffer *b)
+{
+ int i;
+ Cell cell = {
+ .txt = L' ',
+ .pen = {
+ .state = PenNormal,
+ .col.fg = -1,
+ .col.bg = -1,
+ },
+ };
+
+ for (i = 0; i < b->h; i++) {
+ Row *row = b->row + i;
+ for (int j = 0; j < b->w; j++) {
+ row->cells[j] = cell;
+ row->dirty = 1;
+ }
+ }
+}
+
+static
+void
+bfini(Buffer *b)
+{
+ int i;
+
+ for (i = 0; i < b->h; i++)
+ free(b->row[i].cells);
+
+ free(b->row);
+
+ if (b->scroll.size) {
+ for (i = 0; i < b->scroll.size; i++)
+ free(b->scroll.buf[i].cells);
+
+ free(b->scroll.buf);
+ }
+}
+
+static
+void
+bscroll(Buffer *b, int s)
+{
+ Row tmp;
+ int i, ssz = b->scroll.bot - b->scroll.top;
+
+ /* work in quanta of screen size */
+ if (s > ssz) {
+ bscroll(b, ssz);
+ bscroll(b, s - ssz);
+ return;
+ }
+ if (s < -ssz) {
+ bscroll(b, -ssz);
+ bscroll(b, s + ssz);
+ return;
+ }
+
+ b->scroll.above += s;
+ b->scroll.above = CLAMP(b->scroll.above, 0, b->scroll.size);
+
+ if (s > 0) {
+ if (b->scroll.size) {
+ for (i = 0; i < s; i++) {
+ tmp = b->scroll.top[i];
+ b->scroll.top[i] = b->scroll.buf[b->scroll.index];
+ b->scroll.buf[b->scroll.index] = tmp;
+
+ b->scroll.index++;
+ if (b->scroll.index == b->scroll.size)
+ b->scroll.index = 0;
+ }
+ } else
+ for (i = 0; i < s; i++)
+ zero(b->scroll.top+i, 0, b->maxw);
+ }
+
+ roll(b->scroll.top, b->scroll.bot, s);
+
+ if (s < 0) {
+ if (b->scroll.size) {
+ for (i = (-s) - 1; i >= 0; i--) {
+ b->scroll.index--;
+ if (b->scroll.index == -1)
+ b->scroll.index = b->scroll.size - 1;
+
+ tmp = b->scroll.top[i];
+
+ b->scroll.top[i] = b->scroll.buf[b->scroll.index];
+ b->scroll.buf[b->scroll.index] = tmp;
+ b->scroll.top[i].dirty = 1;
+ }
+ } else
+ for (i = (-s) - 1; i >= 0; i--)
+ zero(b->scroll.top+i, 0, b->maxw);
+ }
+}
+
+static
+void
+bresize(Buffer *b, int nrow, int ncol)
+{
+ int r, d;
+ Row *row = b->row;
+ Row *cur = row + b->cur.row;
+
+ if (b->h != nrow) {
+ /* scroll if we can */
+ if (cur >= row + nrow)
+ bscroll(b, b->cur.row - nrow + 1);
+ while (b->h > nrow) {
+ free(row[b->h - 1].cells);
+ b->h--;
+ }
+
+ row = realloc(row, sizeof(Row) * nrow);
+ }
+
+ if (b->maxw < ncol) {
+ /* expand each row */
+ for (r = 0; r < b->h; r++) {
+ row[r].cells = realloc(row[r].cells, sizeof(Cell) * ncol);
+ if (b->h < ncol)
+ zero(row + r, b->w, ncol - b->w);
+ row[r].dirty = 1;
+ }
+ /* expand the scroll buffer */
+ Row *sbuf = b->scroll.buf;
+ for (r = 0; r < b->scroll.size; r++) {
+ sbuf[r].cells = realloc(sbuf[r].cells, sizeof(Cell) * ncol);
+ if (b->w < ncol)
+ zero(sbuf + r, b->w, ncol - b->w);
+ }
+ b->maxw = b->w = ncol;
+ } else if (b->w != ncol) {
+ for (r = 0; r < b->h; r++)
+ row[r].dirty = 1;
+ b->w = ncol;
+ }
+
+ d = 0;
+ if (b->h < nrow) {
+ while (b->h < nrow) {
+ row[b->h].cells = calloc(b->maxw, sizeof(Cell));
+ zero(row + b->h, 0, b->maxw);
+ b->h++;
+ }
+
+ /* prepare for backfill */
+ if (cur >= b->scroll.bot - 1) {
+ d = b->row + nrow - cur - 1;
+ if (d > b->scroll.above)
+ d = b->scroll.above;
+ }
+ }
+
+ b->cur.row += row - b->row;
+ b->scroll.top = row;
+ b->scroll.bot = row + nrow;
+ b->row = row;
+
+ /* perform backfill */
+ if (d > 0) {
+ bscroll(b, -d);
+ b->cur.row += d;
+ }
+}
+
+static
+bool
+binit(Buffer *b, int cols, int rows, int scroll)
+{
+ int size;
+
+ b->pen.state = PenNormal;
+ b->pen.col.fg = b->pen.col.bg = -1;
+
+ size = MAX(scroll, 0);
+ if (size && !(b->scroll.buf = calloc(size, sizeof(Row))))
+ return false;
+
+ b->scroll.size = size;
+ bresize(b, rows, cols);
+
+ b->cur = (Dot){0};
+ b->save = b->cur;
+
+ return true;
+}
+
+static
+void
+bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae)
+{
+ if (bs)
+ *bs = nil;
+ if (be)
+ *be = nil;
+ if (as)
+ *as = nil;
+ if (ae)
+ *ae = nil;
+ if (!b->scroll.size)
+ return;
+
+ if (b->scroll.above) {
+ if (bs)
+ *bs = &b->scroll.buf[(b->scroll.index - b->scroll.above + b->scroll.size) % b->scroll.size];
+ if (be)
+ *be = &b->scroll.buf[(b->scroll.index-1 + b->scroll.size) % b->scroll.size];
+ }
+ if (b->scroll.below) {
+ if (as)
+ *as = &b->scroll.buf[b->scroll.index];
+ if (ae)
+ *ae = &b->scroll.buf[(b->scroll.index + b->scroll.below-1) % b->scroll.size];
+ }
+}
+
+static
+Row *
+browfirst(Buffer *b)
+{
+ Row *bstart;
+ if (!b->scroll.size || !b->scroll.above)
+ return b->row;
+ bboundary(b, &bstart, nil, nil, nil);
+ return bstart;
+}
+
+static
+Row *
+browlast(Buffer *b)
+{
+ Row *aend;
+ if (!b->scroll.size || !b->scroll.below)
+ return b->row + b->h - 1;
+ bboundary(b, nil, nil, nil, &aend);
+ return aend;
+}
+
+static
+Row *
+brownext(Buffer *b, Row *row)
+{
+ Row *before_start, *before_end, *after_start, *after_end;
+ Row *first = b->row, *last = b->row + b->h - 1;
+
+ if (!row)
+ return nil;
+
+ bboundary(b, &before_start, &before_end, &after_start, &after_end);
+
+ if (row >= first && row < last)
+ return ++row;
+ if (row == last)
+ return after_start;
+ if (row == before_end)
+ return first;
+ if (row == after_end)
+ return nil;
+ if (row == &b->scroll.buf[b->scroll.size - 1])
+ return b->scroll.buf;
+ return ++row;
+}
+
+static
+Row *
+bprevrow(Buffer *b, Row *row)
+{
+ Row *before_start, *before_end, *after_start, *after_end;
+ Row *first = b->row, *last = b->row + b->h - 1;
+
+ if (!row)
+ return nil;
+
+ bboundary(b, &before_start, &before_end, &after_start, &after_end);
+
+ if (row > first && row <= last)
+ return --row;
+ if (row == first)
+ return before_end;
+ if (row == before_start)
+ return nil;
+ if (row == after_start)
+ return last;
+ if (row == b->scroll.buf)
+ return &b->scroll.buf[b->scroll.size - 1];
+ return --row;
+}
+
+// -----------------------------------------------------------------------
+// windows
+
+Window *
+wmake(Window *root, int top, int left, int w, int h, int scroll)
+{
+ Window *child, *it;
+
+ child = calloc(1, sizeof(*child));
+ child->top = top;
+ child->left = left;
+ child->parent = root;
+ if (root) {
+ if (root->child) {
+ for (it = root->child; it->link != nil; it = it->link)
+ ;
+ it->link = child;
+ } else
+ root->child = child;
+
+ child->curvis = root->curvis;
+ child->blink = root->blink;
+ }
+
+ if (!binit((Buffer*)child, w, h, scroll)) {
+ free(child);
+ return nil;
+ }
+
+ return child;
+}
+
+void
+wfree(Window *win)
+{
+ free(win);
+}
+
+void
+wresize(Window *win, int w, int h)
+{
+ bresize((Buffer*)win, w, h);
+}
+
+/* TODO: more sophisticated damage tracking */
+void
+wputrune(Window *win, rune r)
+{
+ Row *row = win->row + win->cur.row;
+ Cell *cell = row->cells + win->cur.col;
+
+ cell->pen = win->pen;
+ cell->txt = r;
+
+ if (win->cur.col++ >= win->w) {
+ win->cur.col = 0;
+ if (win->cur.row++ >= win->h)
+ win->cur.row = win->h-1;
+ }
+ row->dirty = 1;
+}
+
+void
+wscroll(Window *win, int s)
+{
+ bscroll((Buffer*)win, s);
+}
diff --git a/src/libutf/canfit.c b/src/libutf/canfit.c
new file mode 100644
index 0000000..4579ab3
--- /dev/null
+++ b/src/libutf/canfit.c
@@ -0,0 +1,23 @@
+#include "internal.h"
+
+/* returns 1 if string of length n is long enough to be decoded */
+int
+utf8·canfit(byte* s, int n)
+{
+ int i;
+ rune c;
+
+ if(n <= 0)
+ return 0;
+
+ c = *(ubyte*)s;
+ if(c < TByte1)
+ return 1;
+
+ if(c < TByte3)
+ return n >= 2;
+ if(c < TByte4)
+ return n >= 3;
+
+ return n >= UTFmax;
+}
diff --git a/src/libutf/decode.c b/src/libutf/decode.c
new file mode 100644
index 0000000..01797f1
--- /dev/null
+++ b/src/libutf/decode.c
@@ -0,0 +1,98 @@
+#include "internal.h"
+
+#define ACCEPT 0
+#define REJECT 12
+
+static uint8 decode[] = {
+ /*
+ * the first part of the table maps bytes to character classes that
+ * to reduce the size of the transition table and create bitmasks
+ */
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
+
+ /*
+ * the second part is a transition table that maps a combination
+ * of a state of the automaton and a character class to a state
+ */
+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+ 12,36,12,12,12,12,12,12,12,12,12,12,
+};
+
+int
+utf8·decode(char *s, rune *r)
+{
+ int n;
+ rune v;
+ uint8 b, t, x=ACCEPT;
+
+ b = ((uint8 *)s)[0];
+ t = decode[b];
+ v = (0xFF >> t) & b;
+ x = decode[256+x+t];
+
+ for(n=1; x > REJECT && n < UTFmax; n++){
+ b = ((uint8 *)s)[n];
+ t = decode[b];
+ v = (v << 6) | (b & TMask);
+ x = decode[256+x+t];
+ }
+
+ if(x != ACCEPT){
+ *r = RuneErr;
+ return 1;
+ }
+
+ *r = v;
+ return n;
+}
+
+#if 0
+int
+utf8·decode(byte *s, rune *r)
+{
+ int c[UTFmax], i;
+ rune l;
+
+ c[0] = *(ubyte*)(s);
+ if(c[0] < Tx){
+ *r = c[0];
+ return 1;
+ }
+
+ l = c[0];
+ for(i = 1; i < UTFmax; i++){
+ c[i] = *(ubyte*)(s+i);
+ c[i] ^= Tx;
+ if(c[i] & Testx) goto bad;
+
+ l = (l << Bitx) | c[i];
+ if(c[0] < Tbyte(i + 2)){
+ l &= RuneX(i + 1);
+ if(i == 1){
+ if(c[0] < Tbyte(2) || l <= Rune1)
+ goto bad;
+ }else if(l <= RuneX(i) || l > RuneMax)
+ goto bad;
+
+ if(i == 2 && SurrogateMin <= l && l <= SurrogateMax)
+ goto bad;
+
+ *r = l;
+ return i + 1;
+ }
+ }
+bad:
+ *r = RuneErr;
+ return 1;
+}
+#endif
diff --git a/src/libutf/decodeprev.c b/src/libutf/decodeprev.c
new file mode 100644
index 0000000..27dced6
--- /dev/null
+++ b/src/libutf/decodeprev.c
@@ -0,0 +1,60 @@
+#include "internal.h"
+
+#define ACCEPT 0
+#define REJECT 12
+
+static uint8 decode[] = {
+ /*
+ * the first part of the table maps bytes to character classes that
+ * to reduce the size of the transition table and create bitmasks.
+ */
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
+ /*
+ * The second part is a transition table that maps a combination
+ * of a state of the automaton and a character class to a state.
+ */
+ // 0 1 2 3 4 5 6 7 8 9 10 11
+ 0,24,12,12,12,12,12,24,12,24,12,12,
+ 0,24,12,12,12,12,12,24,12,24,12,12,
+ 12,36, 0,12,12,12,12,48,12,36,12,12,
+ 12,60,12, 0, 0,12,12,72,12,72,12,12,
+ 12,60,12, 0,12,12,12,72,12,72, 0,12,
+ 12,12,12,12,12, 0, 0,12,12,12,12,12,
+ 12,12,12,12,12,12,12,12,12,12,12, 0
+};
+
+int
+utf8·decodeprev(byte *s, rune *r)
+{
+ int n;
+ rune v;
+ uint8 b, t, d, x=ACCEPT;
+
+ v=0, n=0, d=0;
+nextbyte:
+ b = ((uint8 *)s)[-n++];
+ t = decode[b];
+ x = decode[256+x+t];
+
+ if(x > REJECT && n < UTFmax){
+ v = v | ((b & TMask) << d);
+ d += 6;
+ goto nextbyte;
+ }
+
+ if(x != ACCEPT)
+ *r = RuneErr;
+ else{
+ v |= (((0xFFu >> t) & b) << d);
+ *r = v;
+ }
+
+ return n;
+}
diff --git a/src/libutf/encode.c b/src/libutf/encode.c
new file mode 100644
index 0000000..fa7c93e
--- /dev/null
+++ b/src/libutf/encode.c
@@ -0,0 +1,69 @@
+#include "internal.h"
+
+int
+utf8·encode(rune *r, byte *s)
+{
+ rune c;
+
+ c = *r;
+ if(c < Rune1Byte){ // 7 bits
+ s[0] = (uint8)c;
+ return 1;
+ }
+
+ if(c < Rune2Byte){ // 11 bits
+ s[0] = TByte1 | (c >> 6);
+ s[1] = Tx | (c & TMask);
+ return 2;
+ }
+
+ if(c < Rune3Byte){ // 16 bits
+ s[0] = TByte2 | ((c >> 12));
+ s[1] = Tx | ((c >> 6) & TMask);
+ s[2] = Tx | ((c) & TMask);
+ return 3;
+ }
+
+ // 22 bits
+ if(c > RuneMax || (RuneSurrogateMin <= c && c <= RuneSurrogateMax))
+ c = RuneErr;
+
+ s[0] = TByte3 | ((c >> 18));
+ s[1] = Tx | ((c >> 12) & TMask);
+ s[2] = Tx | ((c >> 6) & TMask);
+ s[3] = Tx | ((c) & TMask);
+
+ return 4;
+}
+
+#if 0
+int
+utf8·encode(rune* r, byte* s)
+{
+ int i, j;
+ rune c;
+
+ c = *r;
+ if(c <= Rune1) {
+ s[0] = c;
+ return 1;
+ }
+
+ for(i = 2; i < UTFmax + 1; i++){
+ if(i == 3){
+ if(c > RuneMax)
+ c = RuneErr;
+ if(SurrogateMin <= c && c <= SurrogateMax)
+ c = RuneErr;
+ }
+ if(c <= RuneX(i) || i == UTFmax) {
+ s[0] = Tbyte(i) | (c >> (i - 1)*Bitx);
+ for(j = 1; j < i; j++)
+ s[j] = Tx | ((c >> (i - j - 1)*Bitx) & Maskx);
+ return i;
+ }
+ }
+
+ return UTFmax;
+}
+#endif
diff --git a/src/libutf/find.c b/src/libutf/find.c
new file mode 100644
index 0000000..d75feb8
--- /dev/null
+++ b/src/libutf/find.c
@@ -0,0 +1,31 @@
+#include "internal.h"
+
+byte*
+utf8·find(byte* s, rune c)
+{
+ long c1;
+ rune r;
+ int n;
+
+ if(c < Tx)
+ return strchr(s, c);
+
+ for(;;){
+ c1 = *(ubyte*)s;
+ if(c1 < Tx){
+ if(c1 == 0) return nil;
+ if(c1 == c) return s;
+ s++;
+ continue;
+ }
+
+ n = utf8·decode(s, &r);
+
+ if(r == c)
+ return s;
+
+ s += n;
+ }
+
+ return nil;
+}
diff --git a/src/libutf/findlast.c b/src/libutf/findlast.c
new file mode 100644
index 0000000..ab25ab2
--- /dev/null
+++ b/src/libutf/findlast.c
@@ -0,0 +1,32 @@
+#include "internal.h"
+
+byte*
+utf8·findlast(byte* s, rune c)
+{
+ long c1;
+ rune r;
+ byte *l;
+
+ if(c < Tx)
+ return strrchr(s, c);
+
+ l = nil;
+ for(;;){
+ c1 = *(ubyte*)s;
+ if(c1 < Tx){
+ if(c1 == 0) return l;
+ if(c1 == c) l = s;
+ s++;
+ continue;
+ }
+
+ c1 = utf8·decode(s, &r);
+
+ if(r == c)
+ l = s;
+
+ s += c1;
+ }
+
+ return nil;
+}
diff --git a/src/libutf/internal.h b/src/libutf/internal.h
new file mode 100644
index 0000000..9719977
--- /dev/null
+++ b/src/libutf/internal.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+
+/*
+ * NOTE: we use the preprocessor to ensure we have unsigned constants.
+ * UTF-8 code:
+ * 1 byte:
+ * 0xxxxxxx
+ * 2 byte:
+ * 110xxxxx 10xxxxxx
+ * 3 byte:
+ * 1110xxxx 10xxxxxx 10xxxxxx
+ * 4 byte:
+ * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+
+#define Tx 0x80u // 0b10000000 transfer header
+#define TMask 0x3Fu // 0b00111111 transfer mask
+
+#define TByte1 0xC0u // 0b11000000
+#define TByte2 0xE0u // 0b11100000
+#define TByte3 0xF0u // 0b11110000
+#define TByte4 0xF8u // 0b11111000
+
+#define RuneMask 0x1FFFFFu
+
+#define Rune1Byte 0x000080u // 1 << 8 (1 byte)
+#define Rune2Byte 0x001000u // 1 << 12 (2 bytes)
+#define Rune3Byte 0x020000u // 1 << 17 (3 bytes)
+#define Rune4Byte 0x400000u // 1 << 22 (4 bytes)
+
+
+/* UTF-16 nonsense */
+#define RuneSurrogateMin 0x0D8000
+#define RuneSurrogateMax 0x0D8FFF
diff --git a/src/libutf/len.c b/src/libutf/len.c
new file mode 100644
index 0000000..8fbd679
--- /dev/null
+++ b/src/libutf/len.c
@@ -0,0 +1,21 @@
+#include "internal.h"
+
+int
+utf8·len(char *s)
+{
+ int c;
+ long n;
+ rune r;
+
+ n = 0;
+ for(;;){
+ c = *(uchar*)s;
+ if(c < Tx){
+ if(c == 0)
+ return n;
+ s++;
+ }else
+ s += utf8·decode(s, &r);
+ n++;
+ }
+}
diff --git a/src/libutf/rules.mk b/src/libutf/rules.mk
new file mode 100644
index 0000000..aeb86b2
--- /dev/null
+++ b/src/libutf/rules.mk
@@ -0,0 +1,76 @@
+include share/push.mk
+
+UNICODE=14.0.0
+
+SRCS_$(d):=\
+ $(d)/encode.c\
+ $(d)/decode.c\
+ $(d)/decodeprev.c\
+ $(d)/find.c\
+ $(d)/findlast.c\
+ $(d)/canfit.c\
+ $(d)/runelen.c\
+ $(d)/len.c\
+ $(d)/runetype-$(UNICODE).c\
+ $(d)/runewidth-$(UNICODE).c
+
+LIBS_$(d):=$(d)/libutf.a
+
+include share/paths.mk
+
+# ========================================================================
+# table generation
+
+$(d)/vendor/common.o: $(d)/vendor/common.c
+ $(COMPILE)
+
+# rune categories
+$(d)/vendor/UnicodeData-$(UNICODE).txt:
+ @echo "GET UnicodeData.txt";\
+ curl https://www.unicode.org/Public/$(UNICODE)/ucd/UnicodeData.txt > $@
+
+$(d)/vendor/mkrunetype: $(d)/vendor/mkrunetype.c $(d)/vendor/common.o $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+GENS += $(d)/vendor/mkrunetype
+
+$(d)/runetype-$(UNICODE).c: $(d)/vendor/UnicodeData-$(UNICODE).txt $(d)/vendor/mkrunetype
+ @$(dir $@)vendor/mkrunetype $< > $@
+
+# rune widths
+$(d)/vendor/EastAsianWidth-$(UNICODE).txt:
+ @echo "GET EastAsianWidth.txt";\
+ curl https://www.unicode.org/Public/$(UNICODE)/ucd/EastAsianWidth.txt > $@
+
+$(d)/vendor/EmojiData-$(UNICODE).txt:
+ @echo "GET EmojiData.txt";\
+ curl https://www.unicode.org/Public/$(UNICODE)/ucd/emoji/emoji-data.txt > $@
+
+$(d)/vendor/mkrunewidth: $(d)/vendor/mkrunewidth.c $(d)/vendor/common.o $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+GENS += $(d)/vendor/mkrunewidth
+
+$(d)/runewidth-$(UNICODE).c: $(d)/vendor/mkrunewidth $(d)/vendor/UnicodeData-$(UNICODE).txt $(d)/vendor/EastAsianWidth-$(UNICODE).txt $(d)/vendor/EmojiData-$(UNICODE).txt
+ @$(dir $@)vendor/mkrunewidth $(filter-out $<, $^) > $@
+
+# grapheme boundaries
+$(d)/vendor/GraphemeBreakProperty-$(UNICODE).txt:
+ @echo "GET GraphemeBreakProperty.txt";\
+ curl https://www.unicode.org/Public/$(UNICODE)/ucd/auxiliary/GraphemeBreakProperty.txt > $@
+
+$(d)/vendor/mkgraphemedata: $(d)/vendor/mkgraphemedata.c $(d)/vendor/common.o $(OBJ_DIR)/base/base.a
+ $(COMPLINK)
+
+$(d)/graphemedata-$(UNICODE).c: $(d)/vendor/mkgraphemedata $(d)/vendor/GraphemeBreakProperty-$(UNICODE).txt
+ $^ > $@
+
+GENS += $(d)/vendor/mkgraphemedata
+
+# ========================================================================
+# normal operations
+
+$(LIBS_$(d)): $(OBJS_$(d))
+ $(ARCHIVE)
+
+include share/pop.mk
diff --git a/src/libutf/runelen.c b/src/libutf/runelen.c
new file mode 100644
index 0000000..dac7f15
--- /dev/null
+++ b/src/libutf/runelen.c
@@ -0,0 +1,8 @@
+#include "internal.h"
+
+int
+utf8·runelen(rune r)
+{
+ byte s[10];
+ return utf8·encode(&r, s);
+}
diff --git a/src/libutf/vendor/common.c b/src/libutf/vendor/common.c
new file mode 100644
index 0000000..5a03a50
--- /dev/null
+++ b/src/libutf/vendor/common.c
@@ -0,0 +1,220 @@
+#include "common.h"
+
+// -----------------------------------------------------------------------
+// input functions
+
+int
+parse(io·Stream *io, int nfield, char **field, int len, char *line)
+{
+ int n;
+ if((n=io·readln(io, len, line)) <= 0)
+ return ParseEOF;
+
+ if(n == len)
+ panicf("line too long");
+
+ if(line[n-1] != '\n')
+ panicf("invalid line: expected '\n', found '%c'", line[n]);
+
+ line[n-1] = 0;
+
+ if(line[0] == '#' || line[0] == 0)
+ return ParseSkip;
+
+ /* tokenize line into fields */
+ n = 0;
+ field[n] = line;
+ while(*line){
+ if(*line == ';'){
+ *line = 0;
+ field[++n] = line+1;
+ }
+ line++;
+ }
+
+ if(n != nfield-1)
+ panicf("expected %d number of fields, got %d: %s", nfield, n, line);
+
+ return ParseOK;
+}
+
+int
+codepoint(char *s)
+{
+ int c, b;
+
+ c = 0;
+ while((b=*s++)){
+ c <<= 4;
+ if(b >= '0' && b <= '9')
+ c += b - '0';
+ else if(b >= 'A' && b <= 'F')
+ c += b - 'A' + 10;
+ else
+ panicf("bad codepoint char '%c'", b);
+ }
+
+ return c;
+}
+
+void
+codepointrange(io·Stream *utf8, char *field[NumFields], int *start, int *stop)
+{
+ int e, c;
+ char *other[NumFields], line[1024];
+
+ // XXX: the stop variable passes in the previous stopping character
+ e = *stop;
+ c = codepoint(field[Fcode]);
+
+ if(c >= NumRunes)
+ panicf("unexpected large codepoint %x", c);
+ if(c <= e)
+ panicf("bad code sequence: %x then %x", e, c);
+ e = c;
+
+ if(strstr(field[Fname], ", First>") != nil){
+ if(!parse(utf8, arrlen(other), other, arrlen(line), line))
+ panicf("range start at end of file");
+ if(strstr(other[Fname], ", Last>") == nil)
+ panicf("range start not followed by range end");
+
+ e = codepoint(other[Fcode]);
+
+ if(e <= c)
+ panicf("bad code sequence: %x then %x", c, e);
+ if(strcmp(field[Fcategory], other[Fcategory]) != 0)
+ panicf("range with mismatched category");
+ }
+
+ *start = c;
+ *stop = e;
+}
+
+// -----------------------------------------------------------------------
+// output functions
+
+void
+putsearch(void)
+{
+ puts(
+ "#include <u.h>\n"
+ "#include <libutf.h>\n"
+ "\n"
+ "static\n"
+ "rune*\n"
+ "rangesearch(rune c, rune *t, int n, int ne)\n"
+ "{\n"
+ " rune *p;\n"
+ " int m;\n"
+ " while(n > 1) {\n"
+ " m = n >> 1;\n"
+ " p = t + m*ne;\n"
+ " if(c >= p[0]){\n"
+ " t = p;\n"
+ " n = n-m;\n"
+ " }else\n"
+ " n = m;\n"
+ " }\n"
+ " if(n && c >= t[0])\n"
+ " return t;\n"
+ " return 0;\n"
+ "}\n"
+ );
+
+}
+
+int
+putrange(char *ident, char *prop, int force)
+{
+ int l, r, start;
+
+ start = 0;
+ for(l = 0; l < NumRunes;) {
+ if(!prop[l]){
+ l++;
+ continue;
+ }
+
+ for(r = l+1; r < NumRunes; r++){
+ if(!prop[r])
+ break;
+ prop[r] = 0;
+ }
+
+ if(force || r > l + 1){
+ if(!start){
+ printf("static rune %s[] = {\n", ident);
+ start = 1;
+ }
+ prop[l] = 0;
+ printf("\t0x%.4x, 0x%.4x,\n", l, r-1);
+ }
+
+ l = r;
+ }
+
+ if(start)
+ printf("};\n\n");
+
+ return start;
+}
+
+int
+putpair(char *ident, char *prop)
+{
+ int l, r, start;
+
+ start = 0;
+ for(l=0; l+2 < NumRunes; ){
+ if(!prop[l]){
+ l++;
+ continue;
+ }
+
+ for(r = l + 2; r < NumRunes; r += 2){
+ if(!prop[r])
+ break;
+ prop[r] = 0;
+ }
+
+ if(r != l + 2){
+ if(!start){
+ printf("static rune %s[] = {\n", ident);
+ start = 1;
+ }
+ prop[l] = 0;
+ printf("\t0x%.4x, 0x%.4x,\n", l, r - 2);
+ }
+
+ l = r;
+ }
+
+ if(start)
+ printf("};\n\n");
+ return start;
+}
+
+int
+putsingle(char *ident, char *prop)
+{
+ int i, start;
+
+ start = 0;
+ for(i = 0; i < NumRunes; i++) {
+ if(!prop[i])
+ continue;
+
+ if(!start){
+ printf("static rune %s[] = {\n", ident);
+ start = 1;
+ }
+ prop[i] = 0;
+ printf("\t0x%.4x,\n", i);
+ }
+
+ if(start)
+ printf("};\n\n");
+
+ return start;
+}
diff --git a/src/libutf/vendor/common.h b/src/libutf/vendor/common.h
new file mode 100644
index 0000000..62f6c5b
--- /dev/null
+++ b/src/libutf/vendor/common.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+
+enum
+{
+ // Fields inside UnicodeData.txt
+ Fcode,
+ Fname,
+ Fcategory,
+ Fcombine,
+ Fbidir,
+ Fdecomp,
+ Fdecimal,
+ Fdigit,
+ Fnumeric,
+ Fmirror,
+ Foldname,
+ Fcomment,
+ Fupper,
+ Flower,
+ Ftitle,
+
+ NumFields,
+ NumRunes = 1 << 21,
+};
+
+/* input functions */
+enum
+{
+ ParseEOF,
+ ParseOK,
+ ParseSkip,
+};
+
+int parse(io·Stream *io, int nfield, char **field, int len, char *line);
+int codepoint(char *s);
+void codepointrange(io·Stream *utf8, char *field[NumFields], int *start, int *stop);
+
+/* output functions */
+void putsearch(void);
+int putrange(char *ident, char *prop, int force);
+int putpair(char *ident, char *prop);
+int putsingle(char *ident, char *prop);
diff --git a/src/libutf/vendor/mkgraphemedata.c b/src/libutf/vendor/mkgraphemedata.c
new file mode 100644
index 0000000..ce5a952
--- /dev/null
+++ b/src/libutf/vendor/mkgraphemedata.c
@@ -0,0 +1,24 @@
+#include <u.h>
+#include <base.h>
+#include <libutf.h>
+
+// -----------------------------------------------------------------------
+// main point of entry
+
+static
+void
+usage(void)
+{
+ fprintf(stderr, "usage: mkgraphemedata <GraphemeBreakProperty.txt>\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ io·Stream *utf8;
+ char line[1024];
+
+ ARGBEGIN{
+ }ARGEND;
+}
diff --git a/src/libutf/vendor/mkrunetype.c b/src/libutf/vendor/mkrunetype.c
new file mode 100644
index 0000000..9f939f4
--- /dev/null
+++ b/src/libutf/vendor/mkrunetype.c
@@ -0,0 +1,388 @@
+#include "common.h"
+
+// -----------------------------------------------------------------------
+// globals
+
+#define OFFSET (1 << 20)
+#define DELTA(mapx, x) ((1 << 20) + (mapx) - (x))
+
+// TODO: use bitarrays. will reduce executable size 8x
+struct Table
+{
+ /* properties */
+ char isspace[NumRunes];
+ char isalpha[NumRunes];
+ char ismark[NumRunes];
+ char isdigit[NumRunes];
+ char isupper[NumRunes];
+ char islower[NumRunes];
+ char istitle[NumRunes];
+ char ispunct[NumRunes];
+ char issymbl[NumRunes];
+ char iscntrl[NumRunes];
+
+ char combine[NumRunes];
+
+ /* transformations */
+ int toupper[NumRunes];
+ int tolower[NumRunes];
+ int totitle[NumRunes];
+};
+
+static struct Table table;
+
+// -----------------------------------------------------------------------
+// internal functions
+
+static
+int
+isrange(char *label, char *prop, int force)
+{
+ char ident[128];
+ if(snprintf(ident, arrlen(ident), "is%s_range", label) == arrlen(ident))
+ panicf("out of identifier space\n");
+
+ return putrange(ident, prop, force);
+}
+
+static
+int
+ispair(char *label, char *prop)
+{
+ char ident[128];
+ if(snprintf(ident, arrlen(ident), "is%s_pair", label) == arrlen(ident))
+ panicf("out of identifier space\n");
+
+ return putpair(ident, prop);
+}
+
+static
+int
+issingle(char *label, char *prop)
+{
+ char ident[128];
+ if(snprintf(ident, arrlen(ident), "is%s_single", label) == arrlen(ident))
+ panicf("out of identifier space\n");
+
+ return putsingle(ident, prop);
+}
+
+static
+void
+makeis(char *label, char *table, int pairs, int onlyranges)
+{
+ int hasr, hasp=0, hass=0;
+
+ hasr = isrange(label, table, onlyranges);
+ if(!onlyranges && pairs)
+ hasp = ispair(label, table);
+ if(!onlyranges)
+ hass = issingle(label, table);
+
+ printf(
+ "int\n"
+ "utf8·is%s(rune c)\n"
+ "{\n"
+ " rune *p;\n"
+ "\n",
+ label);
+
+ if(hasr){
+ printf(
+ " p = rangesearch(c, is%s_range, arrlen(is%s_range)/2, 2);\n"
+ " if(p && c >= p[0] && c <= p[1])\n"
+ " return 1;\n",
+ label, label);
+ }
+
+ if(hasp){
+ printf(
+ " p = rangesearch(c, is%s_pair, arrlen(is%s_pair)/2, 2);\n"
+ " if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))\n"
+ " return 1;\n",
+ label, label);
+ }
+
+ if(hass)
+ printf(
+ " p = rangesearch(c, is%s_single, arrlen(is%s_single), 1);\n"
+ " if(p && c == p[0])\n"
+ " return 1;\n",
+ label, label);
+
+ printf(
+ " return 0;\n"
+ "}\n"
+ "\n");
+}
+
+static
+int
+torange(char *label, int *index, int force)
+{
+ int l, r, d, start = 0;
+
+ for(l = 0; l < NumRunes; ){
+ if(index[l] == l){
+ l++;
+ continue;
+ }
+
+ d = DELTA(index[l], l);
+ if(d != (rune)d)
+ panicf("bad map delta %d", d);
+
+ for(r = l+1; r < NumRunes; r++){
+ if(DELTA(index[r], r) != d)
+ break;
+ index[r] = r;
+ }
+
+ if(force || r != l + 1){
+ if(!start){
+ printf("static rune to%s_range[] = {\n", label);
+ start = 1;
+ }
+ index[l] = l;
+ printf("\t0x%.4x, 0x%.4x, %d,\n", l, r-1, d);
+ }
+ l = r;
+ }
+ if(start)
+ printf("};\n\n");
+
+ return start;
+}
+
+static
+int
+topair(char *label, int *index)
+{
+ int l, r, d, start = 0;
+
+ for(l = 0; l + 2 < NumRunes; ){
+ if(index[l] == l){
+ l++;
+ continue;
+ }
+
+ d = DELTA(index[l], l);
+ if(d != (rune)d)
+ panicf("bad delta %d", d);
+
+ for(r = l+2; r < NumRunes; r += 2){
+ if(DELTA(index[r], r) != d)
+ break;
+ index[r] = r;
+ }
+
+ if(r > l+2){
+ if(!start){
+ printf("static rune to%s_pair[] = {\n", label);
+ start = 1;
+ }
+ index[l] = l;
+ printf("\t0x%.4x, 0x%.4x, %d,\n", l, r-2, d);
+ }
+
+ l = r;
+ }
+ if(start)
+ printf("};\n\n");
+
+ return start;
+}
+
+static
+int
+tosingle(char *label, int *index)
+{
+ int i, d, start = 0;
+
+ for(i=0; i < NumRunes; i++) {
+ if(index[i] == i)
+ continue;
+
+ d = DELTA(index[i], i);
+ if(d != (rune)d)
+ panicf("bad map delta %d", d);
+
+ if(!start){
+ printf("static rune to%s_single[] = {\n", label);
+ start = 1;
+ }
+ index[i] = i;
+ printf("\t0x%.4x, %d,\n", i, d);
+ }
+ if(start)
+ printf("};\n\n");
+
+ return start;
+}
+
+static
+void
+mkto(char *label, int *index, int pairs, int onlyrange)
+{
+ int hasr, hasp=0, hass=0;
+
+ hasr = torange(label, index, !onlyrange);
+ if(!onlyrange && pairs)
+ hasp = topair(label, index);
+ if(!onlyrange)
+ hass = tosingle(label, index);
+
+ printf(
+ "rune\n"
+ "utf8·to%s(rune c)\n"
+ "{\n"
+ " rune *p;\n"
+ "\n",
+ label);
+
+ if(hasr)
+ printf(
+ " p = rangesearch(c, to%s_range, arrlen(to%s_range)/3, 3);\n"
+ " if(p && c >= p[0] && c <= p[1])\n"
+ " return c + p[2] - %d;\n",
+ label, label, OFFSET);
+
+ if(hasp)
+ printf(
+ " p = rangesearch(c, to%s_pair, arrlen(to%s_pair)/3, 3);\n"
+ " if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))\n"
+ " return c + p[2] - %d;\n",
+ label, label, OFFSET);
+
+ if(hass)
+ printf(
+ " p = rangesearch(c, to%s_single, arrlen(to%s_single)/2, 2);\n"
+ " if(p && c == p[0])\n"
+ " return c + p[1] - %d;\n",
+ label, label, OFFSET);
+
+
+ printf(
+ " return c;\n"
+ "}\n"
+ "\n"
+ );
+}
+
+// -----------------------------------------------------------------------
+// main point of entry
+
+static
+void
+usage(void)
+{
+ fprintf(stderr, "usage: mkrunetype <UnicodeData.txt>\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i, sc, c, ec;
+ io·Stream *utf8;
+ char *prop, *field[NumFields], line[1024];
+
+ ARGBEGIN{
+ }ARGEND;
+
+ if(argc != 1)
+ usage();
+
+ if(!(utf8 = io·open(argv[0], "r")))
+ panicf("can't open %s\n", argv[0]);
+
+ /* by default each character maps to itself */
+ for(i = 0; i < NumRunes; i++) {
+ table.toupper[i] = i;
+ table.tolower[i] = i;
+ table.totitle[i] = i;
+ }
+
+ /* ensure all C local white space characters pass */
+ table.isspace['\t'] = 1;
+ table.isspace['\n'] = 1;
+ table.isspace['\r'] = 1;
+ table.isspace['\f'] = 1;
+ table.isspace['\v'] = 1;
+ table.isspace[0x85] = 1;
+
+ ec = -1;
+ // NOTE: we don't check for comments here: assume UnicodeData.txt doesn't have any
+ while(parse(utf8, arrlen(field), field, arrlen(line), line)){
+ /* parse unicode range */
+ codepointrange(utf8, field, &sc, &ec);
+ prop = field[Fcategory];
+
+ for(c = sc; c <= ec; c++){
+ /* grab properties */
+ switch(prop[0]){
+ case 'L':
+ table.isalpha[c] = 1;
+ switch(prop[1]){
+ case 'u': table.isupper[c] = 1; break;
+ case 'l': table.islower[c] = 1; break;
+ case 't': table.istitle[c] = 1; break;
+ case 'm': break; // modifier letters
+ case 'o': break; // ideograph letters
+ default:
+ goto badproperty;
+ }
+ break;
+
+ case 'Z':
+ table.isspace[c] = 1;
+ break;
+
+ case 'M':
+ table.ismark[c] = 1;
+ break;
+
+ case 'N':
+ table.isdigit[c] = 1;
+ break;
+
+ case 'P':
+ table.ispunct[c] = 1;
+ break;
+
+ case 'S':
+ table.issymbl[c] = 1;
+ break;
+
+ case 'C':
+ table.iscntrl[c] = 1;
+ break;
+
+ default: badproperty:
+ panicf("unrecognized category '%s'", prop);
+ }
+ /* grab transformations */
+ if(*field[Fupper])
+ table.toupper[c] = codepoint(field[Fupper]);
+ if(*field[Flower])
+ table.tolower[c] = codepoint(field[Flower]);
+ if(*field[Ftitle])
+ table.totitle[c] = codepoint(field[Ftitle]);
+ }
+ }
+ io·close(utf8);
+
+ putsearch();
+
+ makeis("space", table.isspace, 0, 1);
+ makeis("digit", table.isdigit, 0, 1);
+ makeis("alpha", table.isalpha, 0, 0);
+ makeis("upper", table.isupper, 1, 0);
+ makeis("lower", table.islower, 1, 0);
+ makeis("title", table.istitle, 1, 0);
+ makeis("punct", table.ispunct, 1, 0);
+
+ mkto("upper", table.toupper, 1, 0);
+ mkto("lower", table.tolower, 1, 0);
+ mkto("title", table.totitle, 1, 0);
+}
diff --git a/src/libutf/vendor/mkrunewidth.c b/src/libutf/vendor/mkrunewidth.c
new file mode 100644
index 0000000..14e6973
--- /dev/null
+++ b/src/libutf/vendor/mkrunewidth.c
@@ -0,0 +1,325 @@
+#include "common.h"
+
+/*
+ * inspired by design choices in utf8proc/charwidths.jl
+ * all widths default to 1 unless they fall within the categories:
+ * 1. Mn 2. Mc 3. Me 4. Zl
+ * 5. Zp 6. Cc 7. Cf 8. Cs
+ * these default to zero width
+ */
+enum
+{
+ /* width ? */
+ WidthNeutral, /* (N) practially treated like narrow but unclear ... */
+ WidthAmbiguous, /* (A) sometimes wide and sometimes not... */
+ /* width 1 */
+ WidthHalf, /* (H) = to narrow (compatability equivalent) */
+ WidthNarrow, /* (Na) ASCII width */
+ /* width 2 */
+ WidthWide, /* (W) 2x width */
+ WidthFull, /* (F) = to wide (compatability equivalent) */
+};
+
+struct Table
+{
+ char width[3][NumRunes];
+};
+
+static struct Table table;
+
+// -----------------------------------------------------------------------
+// internal functions
+
+static
+void
+parse_category(char *path)
+{
+ int sc, c, ec, w;
+ io·Stream *utf8;
+ char *prop, *field[NumFields], line[1024];
+
+ if(!(utf8 = io·open(path, "r")))
+ panicf("can't open %s\n", path);
+
+ // NOTE: we don't check for comments here
+ ec = -1;
+ while(parse(utf8, arrlen(field), field, arrlen(line), line)){
+ codepointrange(utf8, field, &sc, &ec);
+
+ prop = field[Fcategory];
+
+ switch(prop[0]){
+ case 'M':
+ switch(prop[1]){
+ case 'n': case 'c': case 'e':
+ w = 0;
+ break;
+ default:
+ w = 1;
+ break;
+ }
+ break;
+ case 'Z':
+ switch(prop[1]){
+ case 'l': case 'p':
+ w = 0;
+ break;
+ default:
+ w = 1;
+ break;
+ }
+ break;
+ case 'C':
+ switch(prop[1]){
+ case 'c': case 'f': case 's':
+ w = 0;
+ break;
+ default:
+ w = 1;
+ break;
+ }
+ default:
+ w = 1;
+ }
+
+ for(c = sc; c <= ec; c++)
+ table.width[w][c] = 1;
+ }
+
+ io·close(utf8);
+}
+
+static
+void
+coderange(char *field, int *l, int *r)
+{
+ char *s;
+
+ if(!(s = strstr(field, "..")))
+ *l=*r=codepoint(field);
+ else{
+ *s++ = 0, *s++ = 0;
+ *l=codepoint(field);
+ *r=codepoint(s);
+ }
+}
+
+static
+void
+parse_eawidths(char *path)
+{
+ int at, w;
+ int l, c, r;
+ io·Stream *utf8;
+ char *field[2], line[1024];
+
+ utf8 = io·open(path, "r");
+ while((at=parse(utf8, arrlen(field), field, arrlen(line), line)) != ParseEOF){
+ if(at == ParseSkip)
+ continue;
+
+ switch(field[1][0]){
+ case 'A': continue;
+ case 'N':
+ if(field[1][1] != 'a')
+ continue;
+ /* fallthrough */
+ case 'H': w = 1; break;
+
+ case 'W': /* fallthrough */
+ case 'F': w = 2; break;
+
+ default:
+ panicf("malformed east asian width class: %s\n", field[1]);
+ }
+
+ coderange(field[0], &l, &r);
+
+ for(c=l; c <= r; c++){
+ /* ensure it only exists in one table */
+ table.width[w][c] = 1;
+ table.width[(w+1)%3][c] = 0;
+ table.width[(w+2)%3][c] = 0;
+ }
+ }
+ io·close(utf8);
+}
+
+static
+void
+parse_emoji(char *path)
+{
+ int at, w;
+ int l, c, r;
+ io·Stream *utf8;
+ char *s, *field[2], line[1024];
+
+ utf8 = io·open(path, "r");
+ while((at=parse(utf8, arrlen(field), field, arrlen(line), line)) != ParseEOF){
+ if(at == ParseSkip)
+ continue;
+
+ /* only override emoji presentation */
+ if(!strstr(field[1], "Emoji_Presentation"))
+ continue;
+
+ /* trim trailing space */
+ for(s=field[0]; *s; s++){
+ if(*s == ' ')
+ *s = 0;
+ }
+
+ coderange(field[0], &l, &r);
+
+ for(c=l; c <= r; c++){
+ table.width[0][c] = 0;
+ table.width[1][c] = 0;
+ table.width[2][c] = 1;
+ }
+ }
+
+ io·close(utf8);
+}
+
+/* output functions */
+static
+void
+maketable(char *label, char *table, int pairs, int onlyranges)
+{
+ int r, p=0, s=0;
+ char ident[3][128];
+
+ enum
+ {
+ Irange,
+ Ipair,
+ Isingle,
+ };
+
+ /* ranges */
+ if(snprintf(ident[Irange], arrlen(ident[Irange]), "%s_range", label) == arrlen(ident[Irange]))
+ panicf("out of identifier space\n");
+ r = putrange(ident[Irange], table, onlyranges);
+
+ if(!onlyranges && pairs){
+ if(snprintf(ident[Ipair], arrlen(ident[Ipair]), "%s_pair", label) == arrlen(ident[Ipair]))
+ panicf("out of identifier space\n");
+ p = putpair(ident[Ipair], table);
+ }
+ if(!onlyranges){
+ if(snprintf(ident[Isingle], arrlen(ident[Isingle]), "%s_single", label) == arrlen(ident[Isingle]))
+ panicf("out of identifier space\n");
+
+ s = putsingle(ident[Isingle], table);
+ }
+
+ printf(
+ "static int\n"
+ "is%s(rune c)\n"
+ "{\n"
+ " rune *p;\n"
+ "\n",
+ label);
+
+ if(r){
+ printf(
+ " p = rangesearch(c, %s, arrlen(%s)/2, 2);\n"
+ " if(p && c >= p[0] && c <= p[1])\n"
+ " return 1;\n",
+ ident[Irange], ident[Irange]);
+ }
+
+ if(p){
+ printf(
+ " p = rangesearch(c, %s, arrlen(%s)/2, 2);\n"
+ " if(p && c >= p[0] && c <= p[1] && !((c - p[0]) & 1))\n"
+ " return 1;\n",
+ ident[Ipair], ident[Ipair]);
+ }
+
+ if(s)
+ printf(
+ " p = rangesearch(c, %s, arrlen(%s), 1);\n"
+ " if(p && c == p[0])\n"
+ " return 1;\n",
+ ident[Isingle], ident[Isingle]);
+
+ printf(
+ " return 0;\n"
+ "}\n"
+ "\n");
+}
+
+// -----------------------------------------------------------------------
+// main point of entry
+
+static
+void
+usage(void)
+{
+ fprintf(stderr, "usage: mkrunewidth <UnicodeData.txt> <EastAsianWidth.txt> <EmojiData.txt>\n");
+ exit(1);
+}
+
+#define SETW0(c) \
+ table.width[0][(c)] = 1, \
+ table.width[1][(c)] = 0, \
+ table.width[2][(c)] = 0;
+
+#define SETW1(c) \
+ table.width[0][(c)] = 0, \
+ table.width[1][(c)] = 1, \
+ table.width[2][(c)] = 0;
+
+#define SETW2(c) \
+ table.width[0][(c)] = 0, \
+ table.width[1][(c)] = 0, \
+ table.width[2][(c)] = 1;
+
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+
+ ARGBEGIN{
+ }ARGEND;
+
+ if(argc != 3)
+ usage();
+
+ parse_category(*argv++);
+ parse_eawidths(*argv++);
+ parse_emoji(*argv);
+
+ /* overrides */
+ SETW0(0x2028);
+ SETW0(0x2029);
+
+ SETW1(0x00AD);
+
+ /* simple checking */
+ for(c=0; c<NumRunes; c++){
+ if(table.width[0][c] + table.width[1][c] + table.width[2][c] > 1)
+ panicf("improper table state");
+ }
+
+ putsearch();
+
+ maketable("width0", table.width[0], 1, 0);
+ maketable("width1", table.width[1], 1, 0);
+ maketable("width2", table.width[2], 1, 0);
+
+ puts(
+ "\n"
+ "int\n"
+ "utf8·runewidth(rune c)\n"
+ "{\n"
+ " if(iswidth1(c))\n"
+ " return 1;\n"
+ " if(iswidth2(c))\n"
+ " return 2;\n"
+ " return 0;\n"
+ "}"
+ );
+}
diff --git a/src/nixos/rules.mk b/src/nixos/rules.mk
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/nixos/rules.mk
diff --git a/src/rules.mk b/src/rules.mk
new file mode 100644
index 0000000..9bb61ae
--- /dev/null
+++ b/src/rules.mk
@@ -0,0 +1,23 @@
+include share/push.mk
+
+# Iterate through subdirectory tree
+
+DIR := $(d)/cmd
+include $(DIR)/rules.mk
+
+DIR := $(d)/base
+include $(DIR)/rules.mk
+
+DIR := $(d)/libutf
+include $(DIR)/rules.mk
+
+DIR := $(d)/libfmt
+include $(DIR)/rules.mk
+
+DIR := $(d)/libmath
+include $(DIR)/rules.mk
+
+DIR := $(d)/libbio
+include $(DIR)/rules.mk
+
+include share/pop.mk