Commits

Anonymous committed 6e9b34d

bunch of stuff, mostly saving progress on sed attempt 2

  • Participants
  • Parent commits 1246069

Comments (0)

Files changed (7)

File bash/arrange_opts.bash

     echo
 }
 
-example -ad foo -b bar baz
+example -ad foo -cb bar baz -- -a bonzai
 char  *bfm , *p;       /* bf memory, bf pointer */
 size_t prog_size, bfm_size;
 
-int grow(void **ptr, size_t *nmemb, size_t size, void **half, int set)
+int grow(void **ptr, size_t *nmemb, size_t size, void **half, int clear)
 {
-	void *tmp = realloc(*ptr, (*nmemb *= 2) * size);
+    size_t n   = *nmemb * 2;
+	void  *tmp = realloc(*ptr, n * size);
 	if (!tmp) { perror("realloc failed"); return 1; }
-	*ptr = tmp;
-	*half = (char *)*ptr + *nmemb * size / 2;
-	if (set) memset(*half, 0, *nmemb * size / 2);
+    *nmemb = n;
+	*ptr   = tmp;
+	*half  = (char *)*ptr + n * size / 2;
+	if (clear) memset(*half, 0, n * size / 2);
 	return 0;
 }
 int code(Inst f)
 		fprintf(stderr, "extra %c\n", b < 0 ? ']' : '[');
 	else if (!e)
 		for (pc = prog; *pc != STOP; pc++)
-			if ((*(*pc))())
+			if ((*pc)())
 				break;
 	if (fclose(script)) perror("fclose failed");
 	free(bfm);

File draw/draw.bash

File contents unchanged.

File raytrace/raytrace.awk

     make_vector(direction)
 
     make_vector(xdir)
-    make_vector(ydir, "", 0, 1, 0)                 # camera always "right side up"
-    cross(xdir, "", ydir, "", camera, "direction") # perpendicular to Y and gaze
-    cross(ydir, "", camera, "direction", xdir, "") # perpendicular to X and gaze
+    make_vector(ydir, "", 0, -1, 0)                 # camera always "right side up"
+    cross(xdir, "", ydir, "", camera, "direction")  # perpendicular to Y and gaze
+    scalar_multiply(xdir, "", -1, xdir, "")         # negate to get proper direction
+    cross(ydir, "", camera, "direction", xdir, "")  # perpendicular to X and gaze
+    scalar_multiply(xdir, "", -1, xdir, "")         # return to normal
     normalize(xdir, "", xdir, "")
     normalize(ydir, "", ydir, "")
 
     camera["far_plane" ] = $8
     copy_vector(camera, "position" , globals,  $9)
     copy_vector(camera, "direction", globals, $10)
+    if (magnitude(camera, "direction") == 0)
+        make_vector(camera, "direction", 0, 0, -1)
     normalize(camera, "direction", camera, "direction")
+    if (camera["directionx"] == 0 && camera["directionz"] == 0) {
+        camera["directionz"] = -0.0001 # dirty, but I think better than erorring out
+        normalize(camera, "direction", camera, "direction")
+    }
 }
 
 END {

File raytrace/raytrace.c

 typedef struct {
     Shape  shape;
     Color  color;
+    Color  emission;
     int    (*intersect)(Shape, Ray*);
 } Object;
 
 // TODO: what is gamma correction correction / why the pow?
 void print_color(Color c)
 {
-    printf("%d %d %d ", (int)(pow(clamp(c.x),1/2.2) * 255), (int)(pow(clamp(c.y),1/2.2) * 255), (int)(pow(clamp(c.z),1/2.2) * 255));
+    printf("%d %d %d ", (int)(pow(clamp(c.x),1/2.2) * 255 + 0.5),
+                        (int)(pow(clamp(c.y),1/2.2) * 255 + 0.5),
+                        (int)(pow(clamp(c.z),1/2.2) * 255 + 0.5));
 }
 
 int qroots(double a, double b, double c, double *r1, double *r2)
     return 1;
 }
 
+Color trace(Ray r, Object *o, Color bg)
+{
+    Object *p, *hit = NULL;
+    Color  c = BLACK;
+
+    for (p = o; p->intersect; p++)
+        if (p->intersect(p->shape, &r))
+            hit = p;
+
+    if (!hit) return bg;
+}
+
+/*
 Color trace(Ray r, Object *o, Light *l, Color bg)
 {
     Object *p, *hit = NULL;
     }
     return (i - l) ? scalar_multiply(1. / (i - l), c) : c;
 }
+*/
 
+// TODO: tent filter?
 Ray primary_ray(Camera c, double x, double y)
 {
     double aspect_ratio = (double)c.xres / c.yres;
     double t = tan(c.fov / 2);
-    Vector pixel;
+    Vector direction, xdir, ydir = { 0, 1, 0 }; // camera always "right side up"
+
+    xdir = cross(scalar_multiply(-1, ydir), c.direction); // perpendicular to Y and gaze
+    ydir = cross(c.direction, scalar_multiply(-1, xdir)); // perpendicular to X and gaze
+
+    x = (-1 + 2 * (x + drand48() / c.sub) / c.xres) * t * aspect_ratio;
+    y = ( 1 - 2 * (y + drand48() / c.sub) / c.yres) * t;
 
-    pixel.x = (-1 + 2 * (x + drand48() / c.sub) / c.xres) * t * aspect_ratio;
-    pixel.y = ( 1 - 2 * (y + drand48() / c.sub) / c.yres) * t;
-    pixel.z =  -1;
-    pixel = normalize(pixel);
+    direction = normalize(add(c.direction, add(scalar_multiply(x, normalize(xdir)), scalar_multiply(y, normalize(ydir)))));
 
-    //TODO: transform pixel to camera coordinates
-    return (Ray){ c.position, pixel, c.near_plane, c.far_plane };
+    return (Ray){ c.position, direction, c.near_plane, c.far_plane };
 }
 
 void render(Camera c, Object *o, Light *l, Color bg)
 
 int main(void)
 {
-    Camera c = { 640, 480, 2, 10, radians(45), 1, 1000, { 0, 0, 0 }, { 0, 0, 1 } };
+    Camera c = { 640, 480, 2, 10, radians(45), 1, 1000, { 0, 0, 0 }, { 0, 0, -1 } };
     Object objects[] = {
         { { .sphere   = { {  -5.5,  -7, -35 }, 3                                     } }, {  .9 ,  .9 ,  .9  }, intersect_sphere   },
         { { .sphere   = { {   4.5,  -7, -30 }, 3                                     } }, {  .9 ,  .9 ,  .9  }, intersect_sphere   },
     REGEX  = -2,
     LASTRE = -3,
 };
-// use regex unless regex is NULL
+
 // if line is EVERY , every line
 // if line is LAST  , $ line
 // if line is REGEX , regex
     Address end;
 } Range;
 
-typedef struct command_t Command;
-struct command_t {
+typedef struct command_struct Command;
+struct command_struct {
     Command      *next;
-    Command      *other; // }, label, ...?
+    void         *other; // Command* for b, {, etc. regex_t for s///, ...
     Range         range;
     size_t        line_number;
     char         *text;
-    int         (*cmd_func)();
+    int         (*cmd_func)(Command*);
     char          negate;
 };
 
-typedef struct sched_write_t Sched_write;
-struct sched_write_t {
+typedef struct sched_write_struct Sched_write;
+struct sched_write_struct {
     Sched_write *next;
     char        *text;
     char         is_file;
 
 regex_t *last_regex;
 Sched_write *sched_writes;
-Command *program, *pc, *append;
+Command *program, *pc;
 size_t line_number;
 
 struct {
     unsigned char did_sub :1;
 } flags;
 
+char takes_argument[] = "brstwy:";          // function may take an argument
+char following_semi[] = "dDgGhHlnNpPqsxy="; // function may be followed by a semicolon
+int (*cmd_funcs)(Command*)[] = {
+    ['a'] = cmd_a,
+    ['b'] = cmd_b,
+    ['c'] = cmd_c,
+    ['d'] = cmd_d,
+    ['D'] = cmd_D,
+    ['g'] = cmd_g,
+    ['G'] = cmd_G,
+    ['h'] = cmd_h,
+    ['H'] = cmd_H,
+    ['i'] = cmd_i,
+    ['l'] = cmd_l,
+    ['n'] = cmd_n,
+    ['N'] = cmd_N,
+    ['p'] = cmd_p,
+    ['P'] = cmd_P,
+    ['q'] = cmd_q,
+    ['r'] = cmd_r,
+    ['s'] = cmd_s,
+    ['t'] = cmd_t,
+    ['w'] = cmd_w,
+    ['x'] = cmd_x,
+    ['y'] = cmd_y,
+    [':'] = cmd_colon,
+    ['='] = cmd_equal,
+    ['{'] = cmd_lbrace,
+    ['}'] = cmd_rbrace,
+};
+
+
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
-#define truncating(s,l) fprintf(stderr,                            \
-    "%s: script line, %d: input line, %d: truncating %s to %d bytes\n",\
+#define truncating(s,l) fprintf(stderr,                                  \
+    "%s: script line, %zu: input line, %zu: truncating %s to %d bytes\n",\
     prog_name, pc->line_number, line_number, (s), (l))
-
+#define badaddr(s)      fprintf(stderr,                                  \
+    "%s: script line, %zu: bad address, %s\n",                           \
+    prog_name, pc->line_number, (s))
+#define undefined(s)    fprintf(stderr,                                  \
+    "%s: script line, %zu: undefined results, %s\n",                     \
+    prog_name, pc->line_number, (s))
+
+// FIXME: brackets
 char *regex_end(char *str, char delim)
 {
     char *p, escape;
         else if (*p == '\\')
             escape = 1;
         else if (*p == delim)
-            break;
+            return p;
+    return NULL;
 }
+
 char *make_address(Address *addr, char *str)
 {
-    if (*str == '/' || *str == '\\') {
+    if (*str == '/' || *str == '\\') { // regex
         char delim, *end;
         if (*str == '\\') {
             str++;
             if (*str == '\\' || *str == '\n') {
-                fprintf(stderr, "%s: script line, %d: bad address, %s\n", prog_name, pc->line_number, str);
+                badaddr(str);
                 return NULL;
             }
         }
         delim = *str;
         if (*++str == delim) { // empty regex
-            *addr = (Address){ NULL, LASTRE };
+            end = str;
+            *addr = (Address){ .line = LASTRE };
         } else {
+            end = regex_end(str, delim);
+            if (!end) {
+                badaddr(str);
+                return NULL;
+            }
+            *end = '\0';
+            addr->line = REGEX;
             if (regcomp(&addr->regex, str, REG_NOSUB)) {
-                fprintf(stderr, "%s: script line, %d: bad address, %s\n", prog_name, pc->line_number, str);
+                badaddr(str);
                 return NULL;
             }
-            str = p + 1;
         }
+        return end + 1;
+    }
+    if (*str == '$') { // last line
+        *addr = (Address) { .line = LAST };
+        return str + 1;
+    }
+    { // line number
+        char *end;
+        long line = strtol(str, &end, 10);
+        if (line == LONG_MIN || line == LONG_MAX) {
+            badaddr(str);
+            return NULL;
+        }
+        *addr = (Address){ .line = line ? line : EVERY};
+        return end;
     }
 }
 
+char *make_range(Range *range, char *str)
+{
+    str = make_address(&range->begin, str);
+    if (str && *str == ',') {
+        if (range->begin->line == EVERY)
+            undefined("empty start of range");
+        str = make_address(&range->end, str + 1);
+        if (range->end->line == EVERY)
+            undefined("empty end of range");
+    } else
+        range->end = range->begin;
+    return str;
+}
+
 FILE *file_open(char *path, char *mode)
 {
     FILE* file = fopen(path, mode);
     return 0;
 }
 
+// *line == func letter
+char *read_s_argument(Command *cmd, char *line)
+{
+    char     delim = *line, *end;
+    regex_t *re = malloc(sizeof(regex_t));
+
+    if (!re) {
+        perror(prog_name);
+        return NULL;
+    }
+
+    if (!(end = regex_end(++line, delim))) { // get regex for s
+        fprintf(stderr, "bad regex in s///\n"); // FIXME: proper error message
+        return NULL;
+    }
+    *end = '\0';
+    if (regcomp(re, line, 0)) {
+        fprintf(stderr, "bad regex in s///\n");
+        return NULL;
+    }
+    cmd->other = (void*)re;
+    line = end + 1;
+    if (!(end = regex_end(line, delim))) { // get replacement text for s
+        fprintf(stderr, "bad replacement text in s///\n");
+        return NULL;
+    }
+    *end = '\0';
+    cmd->text = strdup(line);
+
+    for (char *p = end + 1; *p; p++)
+}
+
+int read_script(char *line)
+{
+    for (char *p = line; *p; p++) {
+        if (isblank(*p) || *p == ';')
+            continue;
+        pc = new_command();
+        pc->line_number = line_number;
+        p = make_range(&pc->range, p);
+        while (isblank(*p))
+            p++;
+        if (*p == '!')
+            pc->negate = 1;
+        while (*p == '!')
+            p++;
+        if (pc->negate && isblank(*p))
+            undefined("blank following !");
+        if (!cmd_funcs[*p]) {
+            fprintf(stderr, "no such function %c\n", *p);
+            return -1;
+        }
+        pc->cmd_func = cmd_funcs[*p];
+        if (strchr(takes_argument, *p))
+            p = read_argument(pc, p);
+
+        // FIXME: 0,1,2 addr
+    }
+}
+
 int read_char(FILE* file)
 {
     int c = fgetc(file);
     return c;
 }
 
+Command *new_command(void)
+{
+    Command **p, *new = calloc(1, sizeof(Command));
+
+    if (!new) {
+        perror(prog_name);
+        return;
+    }
+    for (p = &Program; *p; p = &(*p)->next)
+        ;
+    return (*p = new);
+}
+
 void schedule_write(char *text, int is_file)
 {
     Sched_write **p, *new = malloc(sizeof(Sched_write));
 
-    if (!p) {
+    if (!new) {
         perror(prog_name);
         return;
     }
 {
     *patt_space = '\0';
     print(stdout, cmd->text, 0);
+    return CONTINUE;
 }
 
 int cmd_d(Command *cmd)
 
 int cmd_w(Command *cmd)
 {
-    char line[LINE_MAX];
     FILE *f = file_open(cmd->text, "a");
 
     if (f) {
 
 int cmd_equal(Command *cmd)
 {
-    printf("%d\n", line_number);
+    printf("%zu\n", line_number);
     return CONTINUE;
 }
 
 
 int main(int argc, char **argv)
 {
+    // -e script, -f file, -n
+    int c;
+
+    while ((c = getopt(argc, argv, ":e:f:n")) != -1) {
+        switch (c) {
+            case 'n': flags.no_print = 1; break;
+            case 'e': break; // FIXME: read script
+            case 'f': break; // FIXME: read script
+            case ':': fprintf(stderr, "Option -%c requires an operand\n", optopt); break;
+            case '?': fprintf(stderr, "Unrecognized option: -%c\n"      , optopt); break;
+        }
+    }
+
+    // FIXME: die/usage if getopt errors
+    for (; optind < argc; optind++) {
+        // FIXME: if no -e and no -f first arg here is program
+        // FIXME: files to work on
+    }
+
     return 0;
 }
+#define _POSIX_C_SOURCE 200809L
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAX_WFILES  10   // minimum required by POSIX
+#define LABEL_BYTES 9    // minimum required by POSIX + '\0'
+#define SPACE_BYTES 8192 // minimum required by POSIX
+
+#define USE(a) ((void)(a)) // to escape unused warnings
+#define serror() fprintf(stderr, "%s: %s,%s,%d: %s\n", prog_name, __FILE__, __func__, __LINE__, strerror(errno))
+#define warn(...) do{ fprintf(stderr, "%s: %zu: ", prog_name, line_number); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }while(0)
+
+typedef enum {
+    CONTINUE, // continue execution
+    NEW_LINE, // read new line, continue current cycle
+    APP_LINE, // append new line, continue current cycle
+    NEW_NEXT, // move to new cycle, read new line
+    OLD_NEXT, // move to new cycle, reuse pattern space
+    QUIT    , // do not start a new cycle
+    UNIMP   , // unimplemented feature
+    ERROR   , // he's dead Jim
+} Action;
+
+typedef struct {
+    enum {
+        IGNORE, // empty address, ignore
+        EVERY , // every line
+        LINE  , // line number
+        LAST  , // last line ($)
+        REGEX , // use included regex
+        LASTRE, // use most recently used regex
+    } type;
+    union {
+        size_t  line_number;
+        regex_t regex;
+    };
+} Address;
+
+// naddr == 0 iff beg.type == EVERY  && end.type == IGNORE
+// naddr == 1 iff beg.type != IGNORE && end.type == IGNORE
+// naddr == 2 iff beg.type != IGNORE && end.type != IGNORE
+typedef struct {
+    unsigned naddr; // not necessary, but helpful
+    Address  beg;
+    Address  end;
+} Range;
+
+typedef Action (*Sedfunc)(void);
+typedef struct {
+    Range   range;
+    Sedfunc func;
+    void   *extra;
+    unsigned char negate;
+} Command;
+
+typedef char  *(*Argfunc)(Command*, char*);
+typedef struct {
+    char          letter;
+    Sedfunc       func;
+    Argfunc       getarg;
+    unsigned char naddr    :2; // max address function takes
+    unsigned char semicolon:1; // can this command be followed by a semicolon?
+} Sedfunc_info;
+
+typedef struct {
+    regex_t       regex;
+    char         *replace;
+    FILE         *file;
+    unsigned      occurrence;
+    unsigned char flag_p:1;
+    unsigned char flag_g:1;
+} Extra_s;
+
+typedef struct {
+    char  *text;
+    size_t size;
+} Extra_aci;
+
+struct {
+    char *name;
+    FILE *file;
+} wfiles[MAX_WFILES];
+
+#define unimp_func(c) Action cmd_##c(void) { return UNIMP; }
+unimp_func(a)
+unimp_func(b)
+unimp_func(c)
+unimp_func(d)
+unimp_func(D)
+unimp_func(g)
+unimp_func(G)
+unimp_func(h)
+unimp_func(H)
+unimp_func(i)
+unimp_func(l)
+unimp_func(n)
+unimp_func(N)
+unimp_func(p)
+unimp_func(P)
+unimp_func(q)
+unimp_func(r)
+unimp_func(s)
+unimp_func(t)
+unimp_func(w)
+unimp_func(x)
+unimp_func(y)
+unimp_func(colon)
+unimp_func(equal)
+unimp_func(lbrace)
+unimp_func(rbrace)
+
+char *get_aci_arg  (Command*, char*);
+char *get_bt_arg   (Command*, char*);
+char *get_r_arg    (Command*, char*);
+char *get_s_arg    (Command*, char*);
+char *get_w_arg    (Command*, char*);
+char *get_y_arg    (Command*, char*);
+char *get_colon_arg(Command*, char*);
+
+Sedfunc_info funcs[] = {
+            //|Command letter
+            //|    |Command function
+            //|    |           |Get argument function
+            //|    |           |              |Max addresses
+            //|    |           |              |  |Can be followed by a semicolon
+            //|    |           |              |  |       |Argument
+            //|    |           |              |  |       |               |Explanation
+    ['a'] = { 'a', cmd_a     , get_aci_arg  , 1, 0, },// char    *text : schedule write of text for later
+    ['b'] = { 'b', cmd_b     , get_bt_arg   , 2, 0, },// Command *label: branch to label (extra holds char *label while building, Command *label while running)
+    ['c'] = { 'c', cmd_c     , get_aci_arg  , 2, 0, },// char    *text : delete pattern space, at 0 or 1 addr or end of 2 addr, write text
+    ['d'] = { 'd', cmd_d     , NULL         , 2, 1, },//               : delete pattern space
+    ['D'] = { 'D', cmd_D     , NULL         , 2, 1, },//               : delete to first newline and start new cycle without reading (if no newline, d)
+    ['g'] = { 'g', cmd_g     , NULL         , 2, 1, },//               : replace pattern space with hold space
+    ['G'] = { 'G', cmd_G     , NULL         , 2, 1, },//               : append newline and hold space to pattern space
+    ['h'] = { 'h', cmd_h     , NULL         , 2, 1, },//               : replace hold space with pattern space
+    ['H'] = { 'H', cmd_H     , NULL         , 2, 1, },//               : append newline and pattern space to hold space
+    ['i'] = { 'i', cmd_i     , get_aci_arg  , 1, 0, },// char    *text : write text
+    ['l'] = { 'l', cmd_l     , NULL         , 2, 1, },//               : write pattern space in 'visually unambiguous form'
+    ['n'] = { 'n', cmd_n     , NULL         , 2, 1, },//               : write pattern space (unless -n) read to replace pattern space (if no input, quit)
+    ['N'] = { 'N', cmd_N     , NULL         , 2, 1, },//               : append to pattern space separated by newline, line number changes (if no input, quit)
+    ['p'] = { 'p', cmd_p     , NULL         , 2, 1, },//               : write pattern space
+    ['P'] = { 'P', cmd_P     , NULL         , 2, 1, },//               : write pattern space up to first newline
+    ['q'] = { 'q', cmd_q     , NULL         , 1, 1, },//               : quit
+    ['r'] = { 'r', cmd_r     , get_r_arg    , 1, 0, },// char    *file : write contents of file (unable to open/read treated as empty file)
+    ['s'] = { 's', cmd_s     , get_s_arg    , 2, 1, },// Extra_s *ext  : find/replace/all that crazy s stuff
+    ['t'] = { 't', cmd_t     , get_bt_arg   , 2, 0, },// Command *label: if s/// succeeded (since input or last t) brance to label (end if no label)
+    ['w'] = { 'w', cmd_w     , get_w_arg    , 2, 0, },// FILE    *file : append pattern space to file
+    ['x'] = { 'x', cmd_x     , NULL         , 2, 1, },//               : exchange pattern and hold spaces
+    ['y'] = { 'y', cmd_y     , get_y_arg    , 2, 1, },// char    *sets : replace characters in set1 with characters in set2 (sets is two adjacent strings)
+    [':'] = { ':', cmd_colon , get_colon_arg, 0, 0, },// char    *label: defines label for later b and t commands
+    ['='] = { '=', cmd_equal , NULL         , 1, 1, },//               : printf("%d\n", line_number);
+    ['{'] = { '{', cmd_lbrace, NULL         , 2, 0, },// Command *close: if we match, run commands, otherwise jump to close
+    ['}'] = { '}', cmd_rbrace, NULL         , 0, 0, },// Command *open : noop, hold onto open for ease of building scripts
+
+    [CHAR_MAX] = { 0, NULL, NULL, 0, 0 }
+};
+
+struct {
+    unsigned char n       :1; // -n (no print)
+    unsigned char s       :1; // s/// happened
+    unsigned char aci_cont:1; // a,c,i text continuation
+} gflags;
+
+char     space1[SPACE_BYTES],  space2[SPACE_BYTES];
+char    *patt_space = space1, *hold_space = space2;
+
+char    *prog_name;      // argv[0]
+Command *prog, *pc, *ni; // program memory, program counter, next instruction (while creating program)
+size_t   prog_size;      // number of Commands in prog
+
+size_t   line_number;    // of script when building, of input when running
+
+// given memory pointed to by *ptr that currently holds *nmemb members of size
+// size, realloc to hold new_nmemb members, return new_nmemb in *memb and one
+// past old end in *next. if clear is nonzero clear new memory. if realloc fails
+// change nothing. (should work to shrink, too...)
+int grow(void **ptr, size_t *nmemb, size_t size, size_t new_nmemb, void **next, int clear)
+{
+	void *n, *tmp = realloc(*ptr, new_nmemb * size);
+	if (!tmp)
+        return -1;
+    n = (char *)*ptr + *nmemb * size;
+	if (clear && new_nmemb > *nmemb)
+        memset(n, 0, (new_nmemb - *nmemb) * size);
+    *nmemb = new_nmemb;
+	*ptr   = tmp;
+    if (next)
+        *next = n;
+	return 0;
+}
+
+char *chomp(char *str) {
+    for (; *str && isblank(*str); str++)
+        ;
+    return str;
+}
+
+// Find first non escaped instance of delim in str
+// TODO: ignore delim in [], \(\), and maybe \{\}
+char *find_delim(char *str, char delim) {
+    unsigned escape;
+
+    for (char *p = str; *p; p++)
+        if (escape)
+            escape = 0;
+        else if (*p == '\\')
+            escape = 1;
+        else if (*p == delim)
+            return p;
+    return NULL;
+}
+
+// Read the first address from str into addr and return pointer to character 1
+// past end of address or NULL on error
+char *make_address(Address *addr, char *str) {
+    char *end = NULL;
+
+    if (*str == '$') {
+        addr->type = LAST;
+        end = str + 1;
+    } else if (isdigit(*str)) { // line number
+        // TODO: strtol func so I don't repeat it
+        long num = strtol(str, &end, 10);
+        if (num == LONG_MAX)
+            serror();
+        if (num == 0) {
+            warn("unsupported address: 0");
+            return NULL;
+        }
+        *addr = (Address){ .type = LINE, .line_number = num };
+    } else if (*str == '/') { // TODO: \c any delimiter. make_regex() ?
+        if (*(str + 1) == '/') {
+            addr->type = LASTRE;
+            end = str + 2;
+        } else if (!(end = find_delim(str + 1, *str))) {
+            warn("unclosed regex: %s", str);
+        } else {
+            int err;
+            *end = '\0';
+            addr->type = REGEX;
+            if ((err = regcomp(&addr->regex, str + 1, REG_NOSUB))) {
+                char msg[128]; // TODO: size?
+                regerror(err, &addr->regex, msg, sizeof(msg));
+                warn("bad regex: %s: %s", str, msg);
+                end = NULL;
+            }
+        }
+    } else {
+        addr->type = EVERY;
+        end = str;
+    }
+    return end;
+}
+
+// Read the first range from str in range and return pointer to character 1
+// past end of range or NULL on error
+char *make_range(Range *range, char *str) {
+    char *p = str;
+
+    if (!(p = make_address(&range->beg, p)))
+        return NULL;
+
+    if (*p != ',')
+        range->end.type = IGNORE;
+    else if (!(p = make_address(&range->end, p + 1)))
+        return NULL;
+
+    if      (range->beg.type == EVERY  && range->end.type == IGNORE)
+        range->naddr = 0;
+    else if (range->beg.type != IGNORE && range->end.type == IGNORE)
+        range->naddr = 1;
+    else if (range->beg.type != IGNORE && range->end.type != IGNORE)
+        range->naddr = 2;
+    else { // This should never happen right?
+        warn("bad range: %s", str);
+        return NULL;
+    }
+
+    return p;
+}
+
+// Read the first argument from str into cmd->extra and return pointer to 1
+// past end of argument or NULL on error
+// On arguments that are whole line, return a pointer to any '\0'
+char *get_w_arg(Command *cmd, char *str){
+    char    *p;
+    unsigned i;
+
+    if (!isblank(*str)) {
+        warn("no space before file name");
+        return NULL;
+    }
+    p = chomp(str);
+    if (!*p) {
+        warn("no file name");
+        return NULL;
+    }
+    for (i = 0; i < MAX_WFILES && wfiles[i].file; i++)
+        if (!strcmp(p, wfiles[i].name))
+            break;
+    if (i == MAX_WFILES) {
+        warn("too many wfiles");
+        return NULL;
+    }
+    if (wfiles[i].file) // match
+        cmd->extra = wfiles[i].file;
+    else { // no match
+        wfiles[i].name = strdup(p);
+        if (!(wfiles[i].file = fopen(p, "w"))) {
+            serror();
+            return NULL;
+        }
+    }
+    *p = '\0';
+    return p;
+}
+
+char *get_r_arg(Command *cmd, char *str) {
+    char *p;
+
+    if (!isblank(*str)) {
+        warn("no space before file name");
+        return NULL;
+    }
+    p = chomp(str);
+    if (!*p) {
+        warn("no file name");
+        return NULL;
+    }
+    if (!(cmd->extra = strdup(p))) {
+        serror();
+        return NULL;
+    }
+    *p = '\0';
+    return p;
+}
+
+char *get_aci_arg(Command *cmd, char *str) {
+    Extra_aci *arg = calloc(1, sizeof(*arg));
+
+    if (!arg) {
+        serror();
+        return NULL;
+    }
+    cmd->extra = arg;
+
+    if (!(arg->text = calloc(1, 1))) { // start with empty string
+        serror();
+        return NULL;
+    }
+    if (*str != '\\' || *(str + 1) != '\0') {
+        warn("trailing characters");
+        return NULL;
+    }
+    gflags.aci_cont = 1;
+    *str = '\0';
+    return str;
+}
+
+char *get_y_arg(Command *cmd, char *str) {
+    char *p;
+
+    if (*str != '/') {
+        warn("argument for y must be delimited with /");
+        return NULL;
+    }
+    if (!(p = find_delim(str + 1, *str))) {
+        warn("bad y argument: %s", str);
+        return NULL;
+    }
+    if (!(p = find_delim(p, *p))) {
+        warn("bad y argument: %s", str);
+        return NULL;
+    }
+    *p = '\0';
+    if (!(cmd->extra = strdup(str + 1))) {
+        serror();
+        return NULL;
+    }
+    *find_delim(cmd->extra, *str) = '\0';
+    return p + 1;
+}
+
+char *get_bt_arg(Command *cmd, char *str) {
+    char *p = chomp(str);
+
+    if (!*p)
+        cmd->extra = NULL;
+    else if (!(cmd->extra = strdup(p))) {
+        serror();
+        return NULL;
+    }
+    *p = '\0';
+    return p;
+}
+
+char *get_colon_arg(Command *cmd, char *str) {
+    char *p = chomp(str);
+
+    if (!*p) {
+        warn("no label name");
+        return NULL;
+    }
+    if (!(cmd->extra = strdup(p))) {
+        serror();
+        return NULL;
+    }
+    *p = '\0';
+    return p;
+}
+
+char *get_s_arg(Command *cmd, char *str) {
+    char    *p, *q;
+    int      err;
+    Extra_s *arg = calloc(1, sizeof(*arg));
+
+    if (!arg) {
+        serror();
+        return NULL;
+    }
+    cmd->extra = arg;
+    if (*str != '/') {
+        warn("s arguments must be delimited by /");
+        return NULL;
+    }
+    if (!(p = find_delim(str + 1, *str))) {
+        warn("bad s argument: %s", str);
+        return NULL;
+    }
+    *p = '\0';
+    if ((err = regcomp(&arg->regex, str + 1, 0))) {
+        char msg[128]; // TODO: size? own regcomp() function so no repeats
+        regerror(err, &arg->regex, msg, sizeof(msg));
+        warn("bad regex: %s: %s", str, msg);
+        return NULL;
+    }
+    *p = *str; // replace delim for error messages
+    if (!(q = find_delim(p + 1, *str))) {
+        warn("bad s argument: %s", str);
+        return NULL;
+    }
+    *q = '\0';
+    if (!(arg->replace = strdup(p + 1))) {
+        serror();
+        return NULL;
+    }
+    *q = *str; // replace delim for error messages
+    for (p = q + 1; *p; p++) {
+        if (isdigit(*p)) {
+            long num = strtol(p, &p, 10);
+            if (num == LONG_MAX)
+                serror();
+            if (num == 0) {
+                warn("invalid match number: 0");
+                return NULL;
+            }
+        } else switch (*p) {
+            default : return p;
+            case 'p': arg->flag_p = 1; break;
+            case 'g': arg->flag_g = 1; break;
+            case 'w':
+            {
+                Command buf;
+                if (!(p = get_w_arg(&buf, p + 1)))
+                    return NULL;
+                arg->file = buf.extra;
+                break;
+            }
+        }
+    }
+    return p;
+}
+
+// lex/parse given line building the program. return 0 on success -1 on error
+int build(char *line) {
+    for (char *p = line; *p; p++) {
+        Sedfunc_info *info;
+
+        if (ni == prog + prog_size)
+            if (grow((void **)&prog, &prog_size, sizeof(*prog), prog_size * 2, (void **)&ni, 0))
+                return -1;
+
+        for (; isblank(*p) || *p == ';'; p++)
+            ;
+        if (*p == '#')
+            continue;
+        if (!(p = make_range(&ni->range, p)))
+            return -1;
+        p = chomp(p);
+        if (*p == '!')
+            ni->negate = 1;
+        for (; *p == '!'; p++)
+            ;
+        if (!(info = &funcs[(int)*p])) { // stupid -Wchar-subcripts
+            warn("bad sed function: %c", *p);
+            return -1;
+        }
+        ni->func = info->func;
+        if (ni->range.naddr > info->naddr) {
+            warn("function %c only takes %d addr", *p, info->naddr);
+            return -1;
+        }
+        if (info->getarg && !(p = info->getarg(ni, p)))
+                return -1;
+    }
+    return 0;
+}
+
+int read_line(char *buf, size_t size, FILE* file)
+{
+    size_t len;
+    if (fgets(buf, size, file) == NULL) {
+        if (ferror(file))
+            serror();
+        return EOF;
+    }
+    len = strlen(buf);
+    if (buf[len] == '\n') {
+        buf[len] =  '\0';
+    } else {
+        warn("truncating line to %zu bytes", size);
+        do {
+            if (fgets(buf, size, file) == NULL) {
+                if (ferror(file))
+                    serror();
+                return EOF;
+            }
+        } while (buf[strlen(buf)] != '\n');
+    }
+    return 0;
+}
+
+int read_script(char *path) {
+    char  line[LINE_MAX];
+    FILE *file = fopen(path, "r");
+
+    if (!file) {
+        serror();
+        return -1;
+    }
+
+    // FIXME: random backslashes?
+    while (read_line(line, sizeof(line), file) != EOF) {
+        if (gflags.aci_cont) {
+            Extra_aci *arg = (ni - 1)->extra;
+            size_t     len = strlen(line);
+
+            if (!len--) // empty line
+                continue;
+            if (line[len  ] == '\\') {
+                line[len--] =  '\0';
+            } else
+                gflags.aci_cont = 0;
+
+            // already have 1 null byte
+            if (grow((void **)&arg->text, &arg->size, 1, arg->size + len, NULL, 0)) {
+                serror();
+                return -1;
+            }
+            strcat(arg->text, line);
+        } else if (build(line))
+            return -1;
+    }
+    return 0;
+}
+
+// TODO: pointers for braces and labels
+
+void cleanup(void) {
+    // FIXME: free everything
+}
+
+int main(int argc, char **argv)
+{
+    int c, err = 0;
+
+    prog_name = argv[0];
+
+    ni = prog = calloc(prog_size = 1, sizeof(*prog));
+    if (!prog) {
+        serror();
+        return EXIT_FAILURE;
+    }
+
+    // -e script, -f file, -n
+    while ((c = getopt(argc, argv, ":e:f:n")) != -1) {
+        switch (c) {
+            case 'n': gflags.n = 1;                                                       break;
+            case 'e': err = build(optarg);                                                break;
+            case 'f': err = read_script(optarg);                                          break;
+            case ':': err++; fprintf(stderr, "Option -%c requires an operand\n", optopt); break;
+            case '?': err++; fprintf(stderr, "Unrecognized option: -%c\n"      , optopt); break;
+        }
+    }
+
+    if (err) {
+        cleanup();
+        return EXIT_FAILURE;
+    }
+
+    for (; optind < argc; optind++) {
+
+    }
+    return 0;
+}