richterm

"terminal emulator" with support for text fonts and images for plan9
git clone git://nsmpr.xyz/richterm.git
Log | Files | Refs | README

commit 835be231c45eb36e19a2c23f6f5fb719424ca1c1
parent 16147bfd6a08ac6e19fe4450d778035156c3faa2
Author: Pavel Renev <an2qzavok@gmail.com>
Date:   Sat,  9 Apr 2022 22:21:56 +0000

Markdown: new implementation

Diffstat:
Mextra/Handler | 4++--
Mextra/Markdown.c | 1012++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mextra/mkfile | 5+++++
3 files changed, 598 insertions(+), 423 deletions(-)

diff --git a/extra/Handler b/extra/Handler @@ -30,7 +30,7 @@ fn lmarkdown { history = (`{pwd}^/$fname $history) forward = () clear - cat $fname + Markdown $fname > $rroot/text } fn lgopher { @@ -49,7 +49,7 @@ fn tryfile { type=`{file -m $1} switch ($type) { case 'text/plain'; - lmarkdown $1 + cat $1 case *; plumb $1 } diff --git a/extra/Markdown.c b/extra/Markdown.c @@ -1,535 +1,705 @@ #include <u.h> #include <libc.h> #include <bio.h> -#include <String.h> +#include <thread.h> -char *data; -long count; +#include "config.h" -/* Lexer */ +#define ATTACH(array, size, new) \ + { \ + array = realloc(array, sizeof(*array) * (size + 1)); \ + array[size] = new; \ + size++; \ + } -enum { - TEOF = 0, - TH0, TH1, TH2, TH3, TH4, TH5, TH6, - TWORD, TWBRK, TPBRK, - TLINK, - TCODEBLOCK, - TUNDEF = -1, -}; +#define APPEND(t1, t2) \ + { \ + ATTACH(t1->tokens, t1->count, t2) \ + t2 = t1; \ + t1 = nil; \ + } typedef struct Token Token; + struct Token { int type; - String *s; - String *a; + union { + Rune rune; + struct { + int count; + Token **tokens; + }; + }; }; -void (*lex)(void); -long p, tokn; -Token tok, *tokens; -int oldtype; - -void lnewline(void); +enum { + TNil, + + TRune, + + TSpace, + TNewline, + TTab, + TBraceOpen, + TBraceClose, + TSqrBraceOpen, + TSqrBraceClose, + THash, + TQuote, + + TWhiteSpace, + THMarker, + TWord, + TWords, + TQuoted, + TBraced, + TSqrBraced, + TLink, + TText, + THeader, + TLine, + TEmptyLine, + TParagraph, + + TMax, +}; -void lheader(void); -void lhspace(void); -void lhword(void); -void lhline(void); -void lsubhline(void); +char *names[] = { + [TNil] "nil", + [TRune] "rune", + + [TSpace] "sp", + [TNewline] "nl", + [TTab] "tab", + [TBraceOpen] "(", + [TBraceClose] ")", + [TSqrBraceOpen] "[", + [TSqrBraceClose] "]", + [THash] "#", + [TQuote] "\"", + + [TWhiteSpace] "ws", + [THMarker] "h_marker", + [TWord] "word", + [TWords] "words", + [TQuoted] "quoted", + [TBraced] "()", + [TSqrBraced] "[]", + [TLink] "link", + [TText] "text", + [THeader] "h", + [TLine] "line", + [TEmptyLine] "pb", + [TParagraph] "p", +}; -void lword(void); -void lspace(void); +Biobuf *bfdin; + +Token* twrap(int, int, Token **); +Token** token1(Token *); + +void input(void *); +void header(void *); +void pass1(void *); +void quote(void *); +void pass2(void *); +void words(void *); +void pass3(void *); +void link(void *); +void line(void *); +void debug(void *); +void output(void *); +void clear(void *); + +void freetoken(Token *t); +void dbgprinttoken(Biobuf *, Token *, int); +void printtoken(Biobuf *, Token *); +void printlink(Biobuf *, Token *); +char * tokentotext(Token *, int); + +Rune trune(Token *); +int ttype(Token *); +Token ** findtype(Token **, int, int); -void llink(void); -void lladdr(void); -void lltitle(void); +void +usage(void) +{ + fprint(2, "usage: %s [file]", argv0); + threadexitsall("usage"); +} -void lcodeblock(void); +void +threadmain(int argc, char **argv) +{ + ARGBEGIN { + default: + usage(); + }ARGEND + + if (argc > 0) { + bfdin = Bopen(argv[0], OREAD); + if (bfdin == nil) sysfatal("%r"); + } else bfdin = Bfdopen(0, OREAD); + + int n; + Channel **c; + void (*pipeline[])(void *) = { + input, + pass1, + quote, + pass2, + words, + pass3, + link, + header, + line, +// debug, + output, + clear, + }; -char consume(void); -char peek(int); -void emit(void); + n = sizeof(pipeline) / sizeof(*pipeline); -void emitwbrk(void); -void emitpbrk(void); + c = mallocz(sizeof(Channel *) * (n + 1), 1); -/* Rich */ + int i; + for (i = 1; i < n; i ++) c[i] = chancreate(sizeof(Token *), 64); + for (i = 0; i < n; i++) threadcreate(pipeline[i], (void *)(c + i), 64 * 1024); +} -char * newobj(void); -int setfield(char *, char *, char *); +Token * +twrap(int type, int count, Token **tokens) +{ + Token *nt = mallocz(sizeof(Token), 1); + nt->type = type; + nt->count = count; + nt->tokens = tokens; + return nt; +} -void printtoken(Token); +Token ** +token1(Token *t) +{ + Token **tt; + tt = malloc(sizeof(Token *)); + tt[0] = t; + return tt; +} -char *root = "/mnt/richterm"; +void +input(void *v) +{ + Channel **c = v; + + Rune r; + while ((r = Bgetrune(bfdin)) != Beof) { + Token *t = mallocz(sizeof(Token), 1); + t->type = TRune; + t->rune = r; + send(c[1], &t); + } + chanclose(c[1]); +} void -main(int argc, char **argv) +header(void *v) { - int fd; - char buf[1024]; - long i, n; - - if (argc > 1) { - if ((fd = open(argv[1], OREAD)) < 0) - sysfatal("can't open %s, %r", argv[1]); - } else fd = 0; - count = 0; - data = nil; - while ((n = read(fd, buf, sizeof(buf))) > 0) { - data = realloc(data, count + n); - memcpy(data + count, buf, n); - count += n; + Channel **c = v; + Token *t, *nt, **tt; + tt = nil; + int h = 0; + int count = 0; + while (recv(c[0], &t) > 0) { + if (h == 0) { + if (t->type == THMarker) h = 1; + else send(c[1], &t); + } + if (h != 0) { + + if ((t->type == TNewline) || (t->type == TEmptyLine)) { + h = 0; + nt = twrap(THeader, count, tt); + send(c[1], &nt); + send(c[1], &t); + tt = nil; + count = 0; + } else ATTACH(tt, count, t) + } } - if (n < 0) sysfatal("%r"); - - tokens = nil; - tokn = 0; - tok.s = s_new(); - tok.a = s_new(); - tok.type = TUNDEF; - oldtype = TUNDEF; - lex = lnewline; - while(lex != nil) { - lex(); + if (tt != nil) { + nt = twrap(THeader, count, tt); + send(c[1], &nt); } - emitpbrk(); - tok.type = TEOF; - emit(); + chanclose(c[1]); +} - for (i = 0; i < tokn; i++) { - if (tokens[i].type == TEOF) break; - printtoken(tokens[i]); +void +pass1(void *v) +{ + Channel **c = v; + Token *t; + while (recv(c[0], &t) > 0) { + if (ttype(t) == TRune) { + switch (trune(t)) { + case L'[': + t = twrap(TSqrBraceOpen, 1, token1(t)); + break; + case L']': + t = twrap(TSqrBraceClose, 1, token1(t)); + break; + case L'(': + t = twrap(TBraceOpen, 1, token1(t)); + break; + case L')': + t = twrap(TBraceClose, 1, token1(t)); + break; + case L'\n': + t = twrap(TNewline, 1, token1(t)); + break; + case L' ': + t = twrap(TSpace, 1, token1(t)); + break; + case L'\t': + t = twrap(TTab, 1, token1(t)); + break; + case L'\#': + t = twrap(THash, 1, token1(t)); + break; + case L'\"': + t = twrap(TQuote, 1, token1(t)); + break; + } + send(c[1], &t); + } } + chanclose(c[1]); } void -lnewline(void) +quote(void *v) { - char c; - c = peek(0); - switch (c){ - case 0: - lex = nil; - break; - // case '\n': - // consume(); - // emitpbrk(); - // tok.type = TUNDEF; - // break; - case '#': - lex = lheader; - consume(); - tok.type = TH0; - break; - case '=': - lex = lhline; - consume(); - break; - case '-': - lex =lsubhline; - consume(); - break; - case '[': - lex = llink; - consume(); - // emitwbrk(); - break; - case '\t': - lex = lcodeblock; - consume(); - break; - default: - lex = lword; - // emitwbrk(); + Channel **c = v; + Token *t, *q = nil; + while (recv(c[0], &t) > 0) { + if (q == nil) { + if (ttype(t) == TQuote) { + q = twrap(TQuoted, 1, token1(t)); + } else send(c[1], &t); + } else { + if (ttype(t) == TQuote) { + ATTACH(q->tokens, q->count, t) + send(c[1], &q); + q = nil; + } else ATTACH(q->tokens, q->count, t) + } } + if (q != nil) { + fprint(2, "missing end quote\n"); + send(c[1], &q); + } + chanclose(c[1]); } void -lword(void) +pass2(void *v) { - char c; - c = peek(0); - switch (c) { - case 0: - lex = nil; - emit(); - break; - case '\n': - lex = lnewline; - s_putc(tok.s, c); - consume(); - tok.type = TWORD; - emit(); - tok.type = TUNDEF; - break; - // case ' ': - // lex = lspace; - // s_putc(tok.s, c); - // consume(); - // break; - case '[': - lex = llink; - tok.type = TWORD; - emit(); - consume(); - break; - default: - s_putc(tok.s, c); - consume(); + Channel **c = v; + Token *t[2] = {nil, nil}; + while (recv(c[0], &t[0]) > 0) { + switch(ttype(t[1])) { + case TTab: + case TSpace: + t[1] = twrap(TWhiteSpace, 1, token1(t[1])); + break; + case THash: + t[1] = twrap(THMarker, 1, token1(t[1])); + break; + case TRune: + t[1] = twrap(TWord, 1, token1(t[1])); + break; + } + + switch(ttype(t[1])) { + case TNewline: + if (ttype(t[0]) == TNewline) { + t[1] = twrap(TEmptyLine, 1, token1(t[1])); + APPEND(t[1], t[0]) + } + break; + case TWhiteSpace: + if ((ttype(t[0]) == TSpace) || (ttype(t[0]) == TTab)) { + APPEND(t[1], t[0]) + } + break; + case THMarker: + if (ttype(t[0]) == THash) { + APPEND(t[1], t[0]) + } + break; + case TWord: + if (ttype(t[0]) == TRune) { + APPEND(t[1], t[0]) + } + break; + } + + if (t[1] != nil) send(c[1], &t[1]); + t[1] = t[0]; + t[0] = nil; } + if (t[1] != nil) send(c[1], &t[1]); + chanclose(c[1]); } void -lspace(void) +words(void *v) { - char c; - c = peek(0); - switch (c) { - case ' ': - consume(); - break; - case '\n': - tok.type = TWORD; - lex = lnewline; - consume(); - emit(); - tok.type = TUNDEF; - break; - default: - lex = lword; + Channel **c = v; + Token *t, **buf; + char bf = 0xff; + buf = mallocz(sizeof(Token *) * 8, 1); + int r = 1; + while (bf != 0) { + t = nil; + if (r > 0) { + recv(c[0], &t); + } + memcpy(buf, buf + 1, 7 * sizeof(Token *)); + buf[7] = t; + bf = (bf << 1) | (1 & (t != nil)); + + if (ttype(buf[4]) == TWord) { + buf[4] = twrap(TWords, 1, token1(buf[4])); + } + + if ((ttype(buf[4]) == TWords) && + (ttype(buf[5]) == TWhiteSpace) && + (ttype(buf[6]) == TWord)) { + APPEND(buf[4], buf[5]) + APPEND(buf[5], buf[6]) + } + + if (buf[0] != nil) { + send(c[1], &buf[0]); + } } + chanclose(c[1]); } void -lheader(void) +pass3(void *v) { - char c; - if ((tok.type >= TH0) && (tok.type < TH6)) tok.type++; - else { - /* an error */ - lex = nil; - return; + Channel **c = v; + Token *t, *b = nil; + while(recv(c[0], &t) > 0) { + if (b == nil) { + if (ttype(t) == TBraceOpen) { + b = twrap(TBraced, 1, token1(t)); + } else if (ttype(t) == TSqrBraceOpen) { + b = twrap(TSqrBraced, 1, token1(t)); + } else send(c[1], &t); + } else if (ttype(b) == TBraced) { + if (ttype(t) == TBraceClose) { + ATTACH(b->tokens, b->count, t) + send(c[1], &b); + b = nil; + + } else ATTACH(b->tokens, b->count, t) + } else /* if (ttype(b) == TSqrBtaced) */ { + if (ttype(t) == TSqrBraceClose) { + ATTACH(b->tokens, b->count, t) + send(c[1], &b); + b = nil; + } else ATTACH(b->tokens, b->count, t) + } } - c = peek(0); - switch (c){ - case '#': - consume(); - lex = lheader; - break; - case '\n': - /* an error */ - lex = nil; - break; - case ' ': - consume(); - lex = lhspace; - break; - default: - /* an error */ - lex = nil; + if (b != nil) { + fprint(2, "unclosed (square? ) brace\n"); + send(c[1], &b); } + chanclose(c[1]); } void -lhspace(void) +link(void *v) { - char c; - c = peek(0); - switch(c) { - case 0: - case '\n': - lex = nil; - case ' ': - consume(); - break; - default: - lex = lhword; + Channel **c = v; + Token *t, *l = nil; + while(recv(c[0], &t) > 0) { + if (l == nil) { + if (ttype(t) == TSqrBraced) { + l = t; + } else send(c[1], &t); + } else { + if (ttype(t) == TBraced) { + l = twrap(TLink, 1, token1(l)); + ATTACH(l->tokens, l->count, t) + send(c[1], &l); + l = nil; + } else { + send(c[1], &l); + send(c[1], &t); + l = nil; + } + } } + if (l != nil) send(c[1], &l); + chanclose(c[1]); } void -lhword(void) +line(void *v) { - char c; - c = peek(0); - switch(c) { - case 0: - lex = nil; - break; - case ' ': - s_putc(tok.s, c); - consume(); - lex = lhspace; - break; - case '\n': - consume(); - emit(); - emitpbrk(); - lex = lnewline; - break; - default: - s_putc(tok.s, c); - consume(); + Channel **c = v; + Token *t, *l = nil; + while(recv(c[0], &t) > 0) { + switch(ttype(t)) { + case TWords: + case TLink: + case TWhiteSpace: + if (l == nil) { + l = twrap(TLine, 1, token1(t)); + } else { + ATTACH(l->tokens, l->count, t) + } + break; + default: + if (l != nil) { + send(c[1], &l); + l = nil; + } + send(c[1], &t); + } } + chanclose(c[1]); } void -lhline(void) +debug(void *v) { - char c; - c = peek(0); - switch (c) { - case 0: - lex = nil; - break; - case '=': - consume(); - s_putc(tok.s, c); - break; - case '\n': - lex = lnewline; - consume(); - tokens[tokn - 1].type = TH1; - break; - default: - lex = lword; - consume(); - tok.type = TWORD; - emit(); - }; + Channel **c = v; + Token *t; + Biobuf *b; + b = Bfdopen(2, OWRITE); + if (b == nil) sysfatal("debug: %r"); + while (recv(c[0], &t) > 0) { + dbgprinttoken(b, t, 0); + send(c[1], &t); + } + chanclose(c[1]); + Bflush(b); } void -lsubhline(void) +output(void *v) { - char c; - c = peek(0); - switch (c){ - case 0: - lex = nil; - break; - case '-': - consume(); - s_putc(tok.s, c); - break; - case '\n': - lex = lnewline; - tokens[tokn - 1].type = TH2; - consume(); - break; - default: - lex = lword; - tok.type = TWORD; - consume(); - emit(); + Channel **c = v; + Token *t; + Biobuf *b; + b = Bfdopen(1, OWRITE); + while (recv(c[0], &t) > 0) { + printtoken(b, t); + send(c[1], &t); } + chanclose(c[1]); + Bflush(b); } void -llink(void) +clear(void *v) { - switch (peek(0)){ - case 0: - lex = nil; - break; - case ']': - consume(); - if (peek(0) == '(') { - consume(); - lex = lladdr; - } else lex = lword; - break; - default: - s_putc(tok.s, consume()); + Channel **c = v; + Token *t; + while (recv(c[0], &t) > 0) { + freetoken(t); } } void -lladdr(void) +freetoken(Token *t) { - switch (peek(0)){ - case 0: - lex = nil; - break; - case ')': - lex = lword; - s_terminate(tok.a); - consume(); - tok.type = TLINK; - emit(); - tok.type = TUNDEF; - break; - case ' ': - lex = lltitle; - consume(); - break; - default: - s_putc(tok.a, consume()); - } + if (ttype(t) != TRune) + for (; t->count > 0; t->count--) freetoken(t->tokens[t->count - 1]); + free(t); } void -lltitle(void) +dbgprinttoken(Biobuf *b, Token *t, int ind) { - /* richterm has no support for link titles, - * so we're skipping it - */ - - switch (peek(0)){ - case 0: - lex = nil; - break; - case ')': - lex = lladdr; + int i; + char *s; + static neednl; + + for (i = 0; i < ind; i++) Bprint(b, " "); + + neednl = 1; + switch (t->type) { + case TRune: + s = tokentotext(t, 1); + Bprint(b, "'%s'", s); + free(s); + break; + case TText: + s = tokentotext(t, 1); + Bprint(b, "text \"%s\"", s); + free(s); + break; + case THMarker: + Bprint(b, "h_marker %d", t->count); + break; + case TQuoted: + Bprint(b, "%s ", names[t->type]); + case TWord: + s = tokentotext(t, 1); + Bprint(b, "\"%s\"", s); + free(s); + break; + case TLine: + case TLink: + case TBraced: + case TSqrBraced: + case TWords: + Bprint(b, "%s\n", names[t->type]); + for (i = 0; i < t->count; i++) { + dbgprinttoken(b, t->tokens[i], ind + 1); + } break; - case ' ': default: - consume(); + Bprint(b, "%s", names[t->type]); + } + + if (neednl > 0) { + neednl = 0; + Bprint(b, "\n"); } } void -lcodeblock(void) +printtoken(Biobuf *b, Token *t) { - switch (peek(0)){ - case 0: - lex = nil; + int i; + switch(ttype(t)) { + case TWord: + Bprint(b, "."); + for (i = 0; i < t->count; i++) { + Bprint(b, "%C", trune(t->tokens[i])); + } + Bprint(b, "\n"); + break; + case TLink: + printlink(b, t); + break; + case TLine: + for (i = 0; i < t->count; i++) printtoken(b, t->tokens[i]); + Bprint(b, "n\n"); + break; + case THeader: + i = t->tokens[0]->count; + if (i > 6) i = 6; + i--; + + Bprint(b, "f%s\n", fonts[Fheader1 + i]); + for (i = 2; i < t->count; i++) printtoken(b, t->tokens[i]); + Bprint(b, "n\n" "f\n"); + break; + case TEmptyLine: + Bprint(b, "n\n"); + break; + case TWhiteSpace: + Bprint(b, "s\n"); break; - case '\n': - lex = lnewline; - s_putc(tok.s, consume()); - s_terminate(tok.s); - tok.type = TCODEBLOCK; - emit(); - tok.type = TUNDEF; + case TRune: + Bprint(b, ".%C\n", t->rune); + break; + case TNewline: break; default: - s_putc(tok.s, consume()); + for (i = 0; i < t->count; i++) printtoken(b, t->tokens[i]); } } -char -consume(void) -{ - if (p < count) return data[p++]; - else return 0; -} - -char -peek(int k) -{ - if (p + k < count) return data[p + k]; - else return 0; -} - void -emit(void) +printlink(Biobuf *b, Token *t) { - s_terminate(tok.s); + char *text, *url; + Token *tlink, *ttext, **tt; + ttext = t->tokens[0]; + tlink = t->tokens[1]; + + tt = findtype(ttext->tokens, ttext->count, TWords); + if (tt == nil) { + fprint(2, "malformed link\n"); + return; + } + text = tokentotext(*tt, 0); - tokens = realloc(tokens, (tokn + 1) * sizeof(Token)); - tokens[tokn] = tok; - tokn++; + tt = findtype(tlink->tokens, tlink->count, TWords); + if (tt == nil) { + fprint(2, "malformed link\n"); + free(text); + return; + } + url = tokentotext(*tt, 0); - tok.s = s_new(); - tok.a = s_new(); - oldtype = tok.type; - tok.type = TUNDEF; + Bprint(b, "l%s\n", url); + Bprint(b, ".%s\n", text); + Bprint(b, "l\n"); + free(url); + free(text); } -void -emitwbrk(void) +Token ** +findtype(Token **tt, int count, int type) { - if (oldtype == TWORD) { - tok.type = TWBRK; - emit(); + int i; + for (i = 0; i < count; i++) { + if (ttype(tt[i]) == type) return tt + i; } + return nil; } -void -emitpbrk(void) +char * +tokentotext(Token *t, int escape) { - if (oldtype != TPBRK) { - tok.type = TPBRK; - emit(); + char *r, *s; + int i; + switch (t->type) { + case TRune: + if (escape != 0) { + if (t->rune == L'\n') return smprint("\\n"); + if (t->rune == L'\t') return smprint("\\t"); + if (t->rune == L'"') return smprint("\\%c", '"'); + } + return smprint("%C", t->rune); + case TWhiteSpace: + return smprint(" "); + default: + r = malloc(128); + r[0] = '\0'; + for (i = 0; i < t->count; i++) { + s = tokentotext(t->tokens[i], escape); + strncat(r, s, 128); + free(s); + } + return r; } } -int -setfield(char *id, char *field, char *value) +Rune +trune(Token *t) { - char *path; - int fd; - usize n; - path = smprint("%s/%s/%s", root, id, field); - fd = open(path, OWRITE); - if (fd < 0) sysfatal("%r"); - n = write(fd, value, strlen(value)); - if (n != strlen(value)) sysfatal("write failed: %r"); - close(fd); - free(path); - return 0; -} - -char * -newobj(void) -{ - char *buf; - int fd; - fd = open("/mnt/richterm/new", OREAD); - if (fd < 0) sysfatal("%r"); - buf = mallocz(256, 1); - read(fd, buf, 256); - close(fd); - return buf; + if (t == nil) return Runeerror; + switch (t->type) { + case TRune: + return t->rune; + default: + return Runeerror; + } } -void -printtoken(Token tok) +int +ttype(Token *t) { - char *font, *text, *link, *id; - font = nil; - link = nil; - text = s_to_c(tok.s); - switch(tok.type) { - case TH1: - font = "/lib/font/bit/lucida/unicode.32.font"; - break; - case TH2: - font = "/lib/font/bit/lucida/unicode.28.font"; - break; - case TH3: - font = "/lib/font/bit/lucida/unicode.24.font"; - break; - case TH4: - font = "/lib/font/bit/lucida/unicode.20.font"; - break; - case TH5: - font = "/lib/font/bit/lucida/unicode.18.font"; - break; - case TH6: - font = "/lib/font/bit/lucida/unicode.16.font"; - break; - case TWORD: - break; - case TWBRK: - text = " "; - break; - case TPBRK: - text = "\n\n"; - break; - case TLINK: - link = s_to_c(tok.a); - break; - case TCODEBLOCK: - text = smprint("\t%s", s_to_c(tok.s)); - font = "/lib/font/bit/terminus/unicode.16.font"; - break; - default: - sysfatal("unknown token type %d for text \"%s\"", tok.type, text); - } - id = newobj(); - if (text != nil) setfield(id, "text", text); - if (font != nil) setfield(id, "font", font); - if (link != nil) setfield(id, "link", link); + if (t == nil) return 0; + return t->type; } diff --git a/extra/mkfile b/extra/mkfile @@ -6,6 +6,8 @@ TARG=\ Gopher\ Gemini\ +HFILES=config.h + SYSLIB=/sys/lib/richterm BIN=$SYSLIB/bin/$objtype @@ -19,6 +21,9 @@ install:V: bindir $RC bindir:V: mkdir -p $BIN +config.h: + cp config.def.h config.h + $RC:V: mkdir -p $SYSLIB/bin/rc for (i in $RC) @{