From 2705ebe2ab66515bf8143342943aaa1b56a7bf38 Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Wed, 18 Dec 2019 16:59:16 -0500 Subject: [PATCH] reworked doxygen layout --- Doxyfile | 6 +- DoxygenLayout.xml | 35 +-- README.md | 34 ++- src/anvil.c | 715 ---------------------------------------------- src/tsed.c | 318 --------------------- 5 files changed, 39 insertions(+), 1069 deletions(-) delete mode 100644 src/anvil.c delete mode 100644 src/tsed.c diff --git a/Doxyfile b/Doxyfile index fcc7c21..bee583f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1510,7 +1510,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. @@ -1624,7 +1624,7 @@ MATHJAX_CODEFILE = # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -SEARCHENGINE = YES +SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using Javascript. There @@ -2465,7 +2465,7 @@ DIAFILE_DIRS = # generate a warning when it encounters a \startuml command in this case and # will not generate output for the diagram. -PLANTUML_JAR_PATH = +PLANTUML_JAR_PATH = /usr/share/plantuml/lib/plantuml.jar # When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a # configuration file for plantuml. diff --git a/DoxygenLayout.xml b/DoxygenLayout.xml index e2da3f2..bec1cbd 100644 --- a/DoxygenLayout.xml +++ b/DoxygenLayout.xml @@ -2,38 +2,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/README.md b/README.md index 40c3505..ca1a4c7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Tide Text Editor +# Readme A minimal text editor inspired by the Acme text editor from Plan 9. @@ -13,7 +13,6 @@ Execute the following command to produce all of the binaries into bin/: make - ## Installation # optionally set the prefix for where it will be installed (*Default:* /usr/local/) @@ -31,6 +30,37 @@ Execute the following command to produce all of the binaries into bin/: * [System Design Document](docs/sys_design.md) * [Software Architecture Document](google.com) +### Use Case: Editing a file that is not already open +@startuml +actor User +User -> Edit : edit foo.txt +Edit -> Registrar : foo.txt +Registrar -> Edit : Not open +Edit -> Tide : tide foo.txt +Edit -> User : Success +@enduml + +### Use Case: Editing a file that is already open +@startuml +actor User +User -> Edit : edit foo.txt +Edit -> Registrar : foo.txt +Registrar -> Tide : Focus window +Registrar -> Edit : Open already +Edit -> User : Success +@enduml + +### Use Case: Editing a file at a specific line number +@startuml +actor User +User -> Edit : edit foo.txt:42 +Edit -> Registrar : foo.txt:42 +Registrar -> Tide : Focus window +Registrar -> Tide : Goto line 42 +Registrar -> Edit : Open already +Edit -> User : Success +@enduml + ## Executable Overview ### Compiled Executables diff --git a/src/anvil.c b/src/anvil.c deleted file mode 100644 index 6a1b2eb..0000000 --- a/src/anvil.c +++ /dev/null @@ -1,715 +0,0 @@ -/** - @file - @ingroup anvil -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define INCLUDE_DEFS -#include "config.h" - -#define BARHEIGHT(x) \ - ((x)->font->height + 3) - -#define TCLIENT(c) \ - container_of(c, Client, tnode) - -#define MCLIENT(c) \ - container_of(c, Client, mnode) - -#define Tiled_Clients \ - (Desktops[Desktop].tiled) - -#define Floated_Clients \ - (Desktops[Desktop].floated) - -enum { - TOO_SMALL = (1 << 0), - FLOATING = (1 << 1), -}; - -typedef struct Node { - struct Node* next; - struct Node* prev; -} Node; - -typedef void* (*Reducer)(Node* node, void* accum); - -typedef struct Client { - Node mnode, tnode; - Window win; - Window frame; - char* name; - XftDraw* xft; - int flags, x, y, w, h; -} Client; - -typedef struct { - Client* c; - size_t nclients, nexty, sh; -} GrowState; - -typedef struct Column { - struct Column* next; - struct Client* clients; - int flags, x, w; -} Column; - -static struct { - Node *floated, *tiled; -} Desktops[10] = {{0},{0},{0},{0},{0},{0},{0},{0},{0},{0}}; -static int Desktop = 1; -XConf X = {0}; -static Node* All_Clients = NULL; -static Cursor Move_Cursor; -static Cursor Main_Cursor; -static int StartY = 0; - -/* configuration */ -static uint32_t BorderWidth = 1; -static uint32_t BorderColor = 0xFF0000; -static uint32_t BackgroundColor = 0x000077; - -static Atom - XA_NET_WM_WINDOW_TYPE, - XA_NET_WM_WINDOW_TYPE_DIALOG, - XA_WM_PROTOCOLS; - -typedef union { - const char** com; - const int i; -} Arg; - -struct key { - unsigned int mod; - KeySym keysym; - void (*function)(const Arg arg); - const Arg arg; -}; - -static void change_desktop(const Arg arg); -static void client_to_desktop(const Arg arg); - -#define MOD Mod1Mask -#define DESKTOPCHANGE(K,N) \ - { MOD, K, change_desktop, {.i = N}}, \ - { MOD|ShiftMask, K, client_to_desktop, {.i = N}}, - -static struct key keys[] = { - // MOD KEY FUNCTION ARGS -// { MOD, XK_c, spawn, {.com = lockcmd}}, -// { MOD, XK_p, spawn, {.com = dmenucmd}}, -// { MOD|ShiftMask, XK_Return, spawn, {.com = urxvtcmd}}, -// { MOD|ShiftMask, XK_q, quit, {NULL}} - DESKTOPCHANGE( XK_0, 0) - DESKTOPCHANGE( XK_1, 1) - DESKTOPCHANGE( XK_2, 2) - DESKTOPCHANGE( XK_3, 3) - DESKTOPCHANGE( XK_4, 4) - DESKTOPCHANGE( XK_5, 5) - DESKTOPCHANGE( XK_6, 6) - DESKTOPCHANGE( XK_7, 7) - DESKTOPCHANGE( XK_8, 8) - DESKTOPCHANGE( XK_9, 9) -}; - -/* Utility Functions - *****************************************************************************/ -static void die(char* errstr) -{ - fprintf(stderr, "error: %s\n", errstr); - exit(1); -} - -static Atom atom(XConf* x, char* s) -{ - return XInternAtom(x->display, s, False); -} - -static void* xfree(void* p) -{ - if (p) - { - XFree(p); - } - return NULL; -} - -static int get_prop(XConf* x, Window w, Atom name, Atom* type, int* format, void** data, size_t* ndata) -{ - unsigned long nleft; - return XGetWindowProperty( - x->display, w, name, 0, -1, False, AnyPropertyType, type, format, ndata, &nleft, (unsigned char**)data); -} - -/* List Handling - *****************************************************************************/ -static void list_add(Node** list, Node* parent, Node* node) -{ - if (!parent) - { - node->next = *list; - node->prev = NULL; - if (*list) (*list)->prev = node; - *list = node; - } - else - { - node->next = parent->next; - node->prev = parent; - parent->next = node; - if (node->next) - { - node->next->prev = node; - } - } -} - -static void list_del(Node** list, Node* node) -{ - if (node->prev) - { - node->prev->next = node->next; - } - if (node->next) - { - node->next->prev = node->prev; - } - node->next = NULL, node->prev = NULL; - if (*list == node) - { - *list = node->next; - } -} - -static void* list_reduce(Node* list, Reducer rfn, void* accum) -{ - return (list ? list_reduce(list->next, rfn, rfn(list, accum)) : accum); -} - -static size_t list_count(Node* curr) -{ - size_t nclients = 0; - for (; curr; curr = curr->next, nclients++); - return nclients; -} - -/* Client Handling - *****************************************************************************/ -static void client_reconfig(XConf* xs, Client* c); -static void client_config(XConf* xs, Client* c, int x, int y, int w, int h); - -static void* biggest(Node* node, void* accum) -{ - if (!accum || TCLIENT(node)->h > ((Client*)accum)->h) - { - accum = TCLIENT(node); - } - return accum; -} - -static void* find_win(Node* n, void* env) -{ - struct { Window w; Client* c; }* state = env; - if ((MCLIENT(n)->frame == state->w) || (MCLIENT(n)->win == state->w)) - { - state->c = MCLIENT(n); - } - return env; -} - -static void* grow_client(Node* node, void* env) -{ - GrowState* state = env; - Client* curr = TCLIENT(node); - state->nclients--; - curr->y = state->nexty; - if (curr == state->c) - { - curr->h = state->sh - state->nexty - (state->nclients * BARHEIGHT(&X)); - } - else - { - curr->h = BARHEIGHT(&X); - } - client_reconfig(&X, curr); - state->nexty += curr->h; - return state; -} - -static int client_flags(Client* c, int mask) -{ - return (c->flags & mask); -} - -static void client_raise(XConf* x, Client* c) -{ - XMapWindow(x->display, c->frame); - XMapWindow(x->display, c->win); - if (!client_flags(c, FLOATING)) - { - XLowerWindow(x->display, c->frame); - XLowerWindow(x->display, c->win); - } - else - { - XRaiseWindow(x->display, c->frame); - XRaiseWindow(x->display, c->win); - } - XSync(x->display, False); -} - -static void client_add(Client* c, int dtop) -{ - list_add(&All_Clients, NULL, &(c->mnode)); - if (!client_flags(c, FLOATING)) - { - Client* parent = list_reduce(Desktops[dtop].tiled, biggest, NULL); - list_add(&Desktops[dtop].tiled, (parent ? &(parent->tnode) : NULL), &(c->tnode)); - if (parent) - { - c->h = (parent->h - (parent->h / 2)); - parent->h /= 2; - c->y = parent->y + parent->h; - client_config(&X, parent, parent->x, parent->y, parent->w, parent->h); - } - } - else - { - list_add(&Desktops[dtop].floated, NULL, &(c->tnode)); - } -} - -static void client_del(Client* c) -{ - list_del(&All_Clients, &(c->mnode)); - if (!client_flags(c, FLOATING)) - { - list_del(&Tiled_Clients, &(c->tnode)); - int y = c->y, h = c->h; - Node* nsucc = (c->tnode.prev ? c->tnode.prev : c->tnode.next); - if (nsucc) - { - Client* succ = TCLIENT(nsucc); - succ->h += h; - if (nsucc == c->tnode.next) - succ->y = y; - client_config(&X, succ, succ->x, succ->y, succ->w, succ->h); - client_raise(&X, succ); - } - } - else - { - list_del(&Floated_Clients, &(c->tnode)); - } -} - -static void client_redraw(XConf* x, Client* c) -{ - XftColor fgclr, bgclr; - if (!c->name) return; - xftcolor(x, &fgclr, -1); - xftcolor(x, &bgclr, BackgroundColor); - XftDrawRect(c->xft, &bgclr, 0, 0, c->w, BARHEIGHT(x)); - XftDrawStringUtf8(c->xft, &fgclr, x->font, 0, x->font->ascent, (const FcChar8*)c->name, strlen(c->name)); - XftColorFree(x->display, x->visual, x->colormap, &fgclr); - XftColorFree(x->display, x->visual, x->colormap, &bgclr); -} - -static void client_reconfig(XConf* xs, Client* c) -{ - int height = BARHEIGHT(xs); - XMoveResizeWindow(xs->display, c->frame, c->x, c->y, c->w - 2, height); - if (c->h <= height) - { - XUnmapWindow(xs->display, c->win); - c->flags |= TOO_SMALL; - } - else - { - XMoveResizeWindow(xs->display, c->win, c->x, c->y + height, c->w - 2, c->h - height - 2); - if (client_flags(c, TOO_SMALL)) - { - c->flags &= ~TOO_SMALL; - XMapWindow(xs->display, c->win); - } - } - client_redraw(xs, c); - XSync(xs->display, False); -} - -static void client_config(XConf* xs, Client* c, int x, int y, int w, int h) -{ - c->x = x, c->y = y, c->w = w, c->h = h; - client_reconfig(xs, c); - client_redraw(xs, c); -} - -static void client_initprops(XConf* x, Client* c) -{ - int nprops = 0; - Atom* props = XListProperties(x->display, c->win, &nprops); - Atom type; - int format; - void* data = 0; - size_t ndata; - for (int i = 0; i < nprops; i++) - { - if (props[i] == XA_NET_WM_WINDOW_TYPE) - { - if (Success == get_prop(x, c->win, props[i], &type, &format, &data, &ndata)) - { - if (((Atom*)data)[0] == XA_NET_WM_WINDOW_TYPE_DIALOG) - { - c->flags |= FLOATING; - } - } - } - else if (props[i] == XA_WM_PROTOCOLS) - { - /* register desire for WM_DELETE message here */ - } - data = xfree(data); - } -} - -static void client_create(XConf* x, Window win) -{ - Client* c = calloc(1, sizeof(Client)); - c->win = win; - XGrabServer(x->display); - XFetchName(x->display, win, &c->name); - client_initprops(x, c); - - /* create the frame window */ - if (client_flags(c, FLOATING)) - { - XWindowAttributes attr; - XGetWindowAttributes(x->display, c->win, &attr); - c->x = attr.x, c->y = attr.y; - c->w = attr.width; - c->h = attr.height; - } - else - { - c->x = 0, c->y = 0; - c->w = WidthOfScreen(DefaultScreenOfDisplay(x->display)); - c->h = HeightOfScreen(DefaultScreenOfDisplay(x->display)); - } - c->frame = XCreateSimpleWindow(x->display, x->root, c->x, c->y, 1, 1, BorderWidth, BorderColor, BackgroundColor); - c->xft = XftDrawCreate(x->display, (Drawable) c->frame, x->visual, x->colormap); - XSetWindowAttributes pattr = { .override_redirect = True }; - XChangeWindowAttributes(x->display, c->frame, CWOverrideRedirect, &pattr); - XSetWindowBorder(x->display, c->win, BorderColor); - XSetWindowBorderWidth(x->display, c->win, BorderWidth); - - /* setup event handling on both windows */ - XSelectInput(x->display, c->frame, - ButtonPressMask|ButtonReleaseMask|FocusChangeMask|StructureNotifyMask); - XSelectInput(x->display, c->win, - EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); - - /* position the window and frame */ - client_add(c, Desktop); - client_config(x, c, c->x, c->y, c->w, c->h); - client_redraw(x, c); - client_raise(x, c); - - XSync(x->display, False); - XUngrabServer(x->display); -} - -static void client_destroy(XConf* x, Client* c) -{ - client_del(c); - XGrabServer(x->display); - XftDrawDestroy(c->xft); - XDestroyWindow(x->display, c->frame); - xfree(c->name); - free(c); - XSync(x->display, False); - XUngrabServer(x->display); -} - -static Client* client_find(Window win) -{ - struct { Window w; Client* c; } state = { .w = win }; - list_reduce(All_Clients, find_win, &state); - return state.c; -} - -static void client_resize(XConf* x, Client* c, int dir) -{ - (void)x, (void)c, (void)dir; - if (!c->tnode.prev) return; - Client* prev = TCLIENT(c->tnode.prev); - dir = (!abs(dir) ? -BARHEIGHT(x) : dir); - int sh = HeightOfScreen(DefaultScreenOfDisplay(x->display)); - int miny = prev->y + BARHEIGHT(x); - int maxy = (c->tnode.next ? TCLIENT(c->tnode.next)->y : sh) - BARHEIGHT(x); - int newy = min(max(miny, (c->y + dir)), maxy); - prev->h = newy - prev->y; - c->h = (c->tnode.next ? (TCLIENT(c->tnode.next)->y - newy) : (sh - newy)); - c->y = newy; - client_reconfig(x, prev); - client_reconfig(x, c); -} - -static void client_grow(XConf* x, Client* c) -{ - GrowState state = { - .c = c, - .nclients = list_count(Tiled_Clients), - .sh = HeightOfScreen(DefaultScreenOfDisplay(x->display)) - }; - list_reduce(Tiled_Clients, grow_client, &state); -} - -static void client_maximize(XConf* xs, Client* c) -{ - int y = c->y, h = c->h; - c->y = 0, c->h = HeightOfScreen(DefaultScreenOfDisplay(xs->display)); - client_reconfig(xs, c); - client_raise(xs, c); - c->y = y, c->h = h; -} - -/* Desktop Management - *****************************************************************************/ -static void* hide_wins(Node* node, void* data) -{ - Client* c = TCLIENT(node); - XUnmapWindow(X.display, c->frame); - XUnmapWindow(X.display, c->win); - return data; -} - -static void* show_wins(Node* node, void* data) -{ - Client* c = TCLIENT(node); - XMapWindow(X.display, c->frame); - XMapWindow(X.display, c->win); - client_redraw(&X, c); - return data; -} - -static void change_desktop(const Arg arg) -{ - XGrabServer(X.display); - list_reduce(Tiled_Clients, hide_wins, NULL); - list_reduce(Floated_Clients, hide_wins, NULL); - Desktop = arg.i; - list_reduce(Tiled_Clients, show_wins, NULL); - list_reduce(Floated_Clients, show_wins, NULL); - XUngrabServer(X.display); - XSync(X.display, False); -} - -static void client_to_desktop(const Arg arg) -{ - int bar; - Window foo, win; - do - { - (void)XQueryPointer(X.display, DefaultRootWindow(X.display), - &foo, &win, &bar, &bar, &bar, &bar, (unsigned int*)&bar); - } - while (win <= 0); - Client* c = client_find(win); - if (!c) return; - client_del(c); - XUnmapWindow(X.display, c->frame); - XUnmapWindow(X.display, c->win); - XSync(X.display, False); - client_add(c, arg.i); -} - -/* X11 Event Handling - *****************************************************************************/ - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" - -/* - ** B1 Grow window a little - ** B1 Drag: Resize vertically or move to column - ** B2: Stack windows with titlebars visible but only one window expanded - ** B3: Maximize in column - * B1+B2: Kill window - * Shift+B1: Move one column to the left - * Shift+B2: Move one column to the right -*/ - -static void xbtnpress(XConf* x, XEvent* e) -{ - Client* c = client_find(e->xbutton.window); - if (!c || c->frame != e->xbutton.window || client_flags(c, FLOATING)) - return; - - if (Button1 == e->xbutton.button) - { - XDefineCursor(X.display, e->xbutton.window, Move_Cursor); - StartY = e->xbutton.y; - } - else if (Button2 == e->xbutton.button) - { - if (e->xbutton.state & (1 << (Button1 + 7))) - XDestroyWindow(x->display, e->xbutton.window); - else - client_grow(x, c); - } - else if (Button3 == e->xbutton.button) - { - client_maximize(x, c); - } - - XSync(X.display, False); -} - -static void xbtnrelease(XConf* x, XEvent* e) -{ - Client* c = client_find(e->xbutton.window); - if (!c || c->frame != e->xbutton.window || client_flags(c, FLOATING)) - return; - - if (Button1 == e->xbutton.button) - { - XUndefineCursor(X.display, e->xbutton.window); - client_resize(x, c, e->xbutton.y - StartY); - } - - XSync(X.display, False); -} - -static void xconfigrequest(XConf* x, XEvent* e) -{ - /* - Check if it's a window we care about. If it is, and it's floating, just - grant the request. Otherwise, deny it as we have it tiled already. All - other windows have their requests granted. - */ - Client* c = client_find(e->xconfigurerequest.window); - XWindowChanges wc; - wc.x = e->xconfigurerequest.x; - wc.y = e->xconfigurerequest.y; - wc.width = e->xconfigurerequest.width; - wc.height = e->xconfigurerequest.height; - wc.border_width = e->xconfigurerequest.border_width; - wc.sibling = e->xconfigurerequest.above; - wc.stack_mode = e->xconfigurerequest.detail; - if (c && !client_flags(c, FLOATING)) - return; - XConfigureWindow(x->display, e->xconfigurerequest.window, e->xconfigurerequest.value_mask, &wc); -} - -static void xmaprequest(XConf* x, XEvent* e) -{ - static XWindowAttributes attr = {0}; - if (XGetWindowAttributes(x->display, e->xmaprequest.window, &attr)) - { - if (attr.override_redirect) return; /* ignore certain windows (like frames) */ - if (!client_find(e->xmaprequest.window)) - client_create(x, e->xmaprequest.window); - } -} - -static void xunmapnotify(XConf* x, XEvent* e) -{ -} - -static void xdestroynotify(XConf* x, XEvent* e) -{ - /* This is where we cleanup windows we care about. destroy them and their frames. */ - Client* c = client_find(e->xdestroywindow.window); - if (c) client_destroy(x, c); -} - -static void xclientmsg(XConf* x, XEvent* e) -{ -} - -static void xpropnotify(XConf* x, XEvent* e) -{ - /* - We only care about updating the window titles here for now - */ - Client* c = client_find(e->xproperty.window); - if (c) { - c->name = xfree(c->name); - XFetchName(x->display, c->win, &c->name); - client_redraw(x, c); - } -} - -static void xenternotify(XConf* x, XEvent* e) -{ - /* - Handle focus follows mouse here. - */ -} - -static void xexpose(XConf* x, XEvent* e) -{ - Client* c = client_find(e->xexpose.window); - if (c && e->xexpose.count == 0) - client_redraw(x, c); -} - -#include -static void xkeypress(XConf* x, XEvent* e) -{ - KeySym keysym = XkbKeycodeToKeysym( - x->display, e->xkey.keycode, 0, 0); - for (size_t i = 0; i < nelem(keys); i++) - if(keys[i].keysym == keysym && keys[i].mod == e->xkey.state) - keys[i].function(keys[i].arg); -} - -#pragma GCC diagnostic pop - -int main(void) -{ - /* initialize basic x11 handling */ - x11_init(&X); - X.font = x11_font_load(&X, Fonts[0]); - XSelectInput(X.display, X.root, SubstructureRedirectMask|SubstructureNotifyMask); - XSync(X.display, False); - if (x11_error_get()) - die("Could not start. Is another WM running?\n"); - - XA_NET_WM_WINDOW_TYPE = atom(&X, "_NET_WM_WINDOW_TYPE"); - XA_NET_WM_WINDOW_TYPE_DIALOG = atom(&X, "_NET_WM_WINDOW_TYPE_DIALOG"); - XA_WM_PROTOCOLS = atom(&X, "WM_PROTOCOLS"); - - /* setup cursors */ - Main_Cursor = XCreateFontCursor(X.display, XC_left_ptr); - Move_Cursor = XCreateFontCursor(X.display, XC_draped_box); - XDefineCursor(X.display, X.root, Main_Cursor); - - KeyCode code; - for (size_t i = 0; i < nelem(keys); i++) - if ((code = XKeysymToKeycode(X.display, keys[i].keysym))) - XGrabKey(X.display, code, keys[i].mod, X.root, True, GrabModeAsync, GrabModeAsync); - - /* register e vent handlers */ - X.eventfns[ButtonPress] = xbtnpress; - X.eventfns[ButtonRelease] = xbtnrelease; - X.eventfns[KeyPress] = xkeypress; - X.eventfns[ConfigureRequest] = xconfigrequest; - X.eventfns[MapRequest] = xmaprequest; - X.eventfns[UnmapNotify] = xunmapnotify; - X.eventfns[DestroyNotify] = xdestroynotify; - X.eventfns[ClientMessage] = xclientmsg; - X.eventfns[PropertyNotify] = xpropnotify; - X.eventfns[EnterNotify] = xenternotify; - X.eventfns[Expose] = xexpose; - x11_event_loop(&X, 0); - return 0; -} diff --git a/src/tsed.c b/src/tsed.c deleted file mode 100644 index 5af4780..0000000 --- a/src/tsed.c +++ /dev/null @@ -1,318 +0,0 @@ -/** - @file - @ingroup tsed -*/ -#include -#include -#include -#include -#include - -/* Line buffer flags */ -enum { - LB_NEWLINE = (1 << 0), - LB_DELETE = (1 << 1) -}; - -typedef struct { - int flags; - ssize_t capacity; - ssize_t length; - char* buffer; -} LineBuf; - -/* Command flags */ -enum { - SUB_GLOBAL = (1 << 0), - SUB_PRINT = (1 << 1), - IN_RANGE = (1 << 2), -}; - -typedef struct { - enum { NONE, LINE, REGEX } type; - union { - regex_t* regex; - size_t line; - } u; -} Addr; - -typedef struct { - int type; - int flags; - Addr addr[2]; - regex_t* regex; - char* text; -} Cmd; - -typedef struct { - size_t ncmds; - size_t line; - Cmd cmds[]; -} Prog; - -typedef void (*CmdFn)(Cmd* cmd, LineBuf* buf); - -static void lbputc(LineBuf* lbuf, int c) -{ - if ((lbuf->length + 2u) >= lbuf->capacity) - { - lbuf->capacity += 2u; - lbuf->buffer = realloc(lbuf->buffer, lbuf->capacity); - } - lbuf->buffer[lbuf->length++] = c; - lbuf->buffer[lbuf->length] = '\0'; -} - -static void lbputsn(LineBuf* lbuf, char* s, ssize_t l) -{ - for (ssize_t i = 0; i < l; i++) - lbputc(lbuf, *(s+i)); -} - -static void cmd_d(Cmd* cmd, LineBuf* lbuf) -{ - (void)cmd; - lbuf->flags |= LB_DELETE; -} - -static void cmd_s(Cmd* cmd, LineBuf* lbuf) -{ - regmatch_t match[10]; - LineBuf out = {0}; - char* pos = lbuf->buffer; - do { - memset(match, 0, sizeof(match)); - if (!regexec(cmd->regex, pos, 10, match, 0)) - { - lbputsn(&out, pos, match[0].rm_so); - for (char* rs = cmd->text; *rs; rs++) - { - if (*rs == '\\') - { - rs++; - if (isdigit(*rs)) - { - int i = *rs - '0'; - lbputsn(&out, (pos + match[i].rm_so), (match[i].rm_eo - match[i].rm_so)); - } - else - { - lbputc(&out, *rs); - } - } - else - { - lbputc(&out, *rs); - } - } - pos += match[0].rm_eo; - if (!*pos || !(cmd->flags & SUB_GLOBAL)) - { - lbputsn(&out, pos, (lbuf->length - (pos - lbuf->buffer) - 1)); - } - } - else - { - lbputsn(&out, pos, (lbuf->length - (pos - lbuf->buffer) - 1)); - break; - } - } while (*pos && (cmd->flags & SUB_GLOBAL)); - lbuf->length = 0; - lbputsn(lbuf, out.buffer, out.length); - free(out.buffer); -} - -static const CmdFn Commands[] = { - ['d'] = cmd_d, - ['s'] = cmd_s -}; - -static int match_addr(Prog* prog, Addr* addr, LineBuf* lbuf) -{ - if (addr->type == NONE) - return 1; - else if (addr->type == LINE) - return (addr->u.line == prog->line); - else if (addr->type == REGEX) - return (regexec(addr->u.regex, lbuf->buffer, 0, 0, 0) == 0); - else - return 0; -} - -static char* rdline(LineBuf* buf, FILE* file) -{ - buf->length = getline(&(buf->buffer), (size_t*)&(buf->capacity), file); - buf->flags = 0; - if (buf->length <= 0) { - free(buf->buffer); - buf->buffer = NULL; - } else if (buf->buffer[buf->length-1] == '\n') { - buf->flags |= LB_NEWLINE; - buf->buffer[buf->length-1] = '\0'; - } - return buf->buffer; -} - -static void parse_fail(char* msg) -{ - printf("parse error: %s\n", msg); - exit(1); -} - -static char* parse_till(char* script, int term, char** str) -{ - if (!*script) return script; - char *sbeg = script, *send = script; - for (; *send && *send != term; send++) - if (*send == '\\') send++; - *str = strndup(sbeg, send - sbeg); - return (*send ? send+1 : send); -} - -static char* parse_addr(char* script, Addr* addr) -{ - if (isdigit(*script)) - { - addr->type = LINE; - for (; *script && isdigit(*script); script++) - addr->u.line = (addr->u.line * 10) + (*script - '0'); - } - else if (*script == '/') - { - addr->type = REGEX; - char* rstr = NULL; - script = parse_till(++script, '/', &rstr); - addr->u.regex = calloc(1, sizeof(regex_t)); - if (regcomp(addr->u.regex, rstr, REG_EXTENDED|REG_NOSUB) != 0) - parse_fail("failed to compile regex"); - } - return script; -} - -static char* parse_sub(char* script, Cmd* cmd) -{ - int sep = *script++; - if (!sep) return (script-1); - - /* parse out the regex and template */ - char *rstr = NULL, *tstr = NULL; - script = parse_till(script, sep, &rstr); - script = parse_till(script, sep, &tstr); - - /* parse the flags */ - for (;*script && !isspace(*script); script++) - { - switch (*script) - { - case 'g': cmd->flags |= SUB_GLOBAL; break; - case 'p': cmd->flags |= SUB_PRINT; break; - default: parse_fail("bad substitute flag"); - } - } - - /* compile the regex and setup the command */ - cmd->text = tstr; - cmd->regex = calloc(1, sizeof(regex_t)); - if (regcomp(cmd->regex, rstr, REG_EXTENDED|REG_NEWLINE) < 0) - parse_fail("failed to compile regex"); - free(rstr); - - return script; -} - -static Prog* prog_grow(Prog* prog, Cmd** lastcmd) -{ - prog = realloc(prog, sizeof(Prog) + ((prog->ncmds + 1) * sizeof(Cmd))); - *lastcmd = &(prog->cmds[prog->ncmds]); - prog->ncmds++; - memset(*lastcmd, 0, sizeof(Cmd)); - return prog; -} - -Prog* prog_parse(char* script) -{ - Cmd* cmd = NULL; - Prog* prog = calloc(1, sizeof(Prog)); - while (*script) { - while (isspace(*script)) script++; - if (!*script) break; - - /* allocate a new command */ - prog = prog_grow(prog, &cmd); - - /* parse the addresses */ - script = parse_addr(script, &(cmd->addr[0])); - if (*script == ',') - script = parse_addr(++script, &(cmd->addr[1])); - - /* parse the command */ - cmd->type = *script; - switch (*script++) - { - case 'd': - /* handled above */ - break; - case 's': - script = parse_sub(script, cmd); - break; - case '\0': - parse_fail("unexpected end of string"); - break; - default: - parse_fail("unexpected char"); - exit(1); - break; - } - } - return prog; -} - -void prog_free(Prog* prog) -{ -#define FREE_REGEX(regex) do { regfree(regex); free(regex); } while(0) - for (size_t i = 0; i < prog->ncmds; i++) { - if (prog->cmds[i].addr[0].type == REGEX) FREE_REGEX(prog->cmds[i].addr[0].u.regex); - if (prog->cmds[i].addr[1].type == REGEX) FREE_REGEX(prog->cmds[i].addr[1].u.regex); - if (prog->cmds[i].regex) FREE_REGEX(prog->cmds[i].regex); - if (prog->cmds[i].text) free(prog->cmds[i].text); - } - free(prog); -} - -void prog_exec(Prog* prog, LineBuf* lbuf) -{ - prog->line++; - for (size_t i = 0; i < prog->ncmds; i++) - { - Cmd* cmd = &(prog->cmds[i]); - if ((cmd->flags & IN_RANGE) || match_addr(prog, &(cmd->addr[0]), lbuf)) - { - if (cmd->addr[1].type != NONE) - { - cmd->flags |= IN_RANGE; - if (match_addr(prog, &(cmd->addr[1]), lbuf)) - cmd->flags &= ~IN_RANGE; - } - Commands[cmd->type](cmd, lbuf); - } - } -} - -int main(int argc, char** argv) -{ - if (argc < 2) return 1; - - LineBuf buf = {0}; - Prog* prog = prog_parse(argv[1]); - while (rdline(&buf, stdin)) - { - prog_exec(prog, &buf); - if (buf.flags & LB_DELETE) continue; - fwrite(buf.buffer, 1u, buf.length, stdout); - if (buf.flags & LB_NEWLINE) - fwrite("\n", 1u, 1u, stdout); - } - prog_free(prog); - - return 0; -} -- 2.52.0