mojoshader_assembler.c
changeset 542 a56d3bfd2e36
parent 539 8a26cb9a148f
child 543 98742b1d8a4a
--- a/mojoshader_assembler.c	Tue Feb 03 04:10:50 2009 -0500
+++ b/mojoshader_assembler.c	Tue Feb 03 04:14:00 2009 -0500
@@ -7,8 +7,6 @@
  *  This file written by Ryan C. Gordon.
  */
 
-// !!! FIXME: this should report all errors, not quit on the first fail().
-
 #define __MOJOSHADER_INTERNAL__ 1
 #include "mojoshader_internal.h"
 
@@ -37,10 +35,14 @@
 // Context...this is state that changes as we assemble a shader...
 typedef struct Context
 {
+    int isfail;
+    int out_of_memory;
+    int eof;
     MOJOSHADER_malloc malloc;
     MOJOSHADER_free free;
     void *malloc_data;
-    const char *failstr;
+    int error_count;
+    ErrorList *errors;
     TokenizerContext tctx;
     MOJOSHADER_parsePhase parse_phase;
     MOJOSHADER_shaderType shader_type;
@@ -62,11 +64,9 @@
 
 // Convenience functions for allocators...
 
-static inline int out_of_memory(Context *ctx)
+static inline void out_of_memory(Context *ctx)
 {
-    if (ctx->failstr == NULL)
-        ctx->failstr = out_of_mem_str;  // fail() would call malloc().
-    return FAIL;
+    ctx->isfail = ctx->out_of_memory = 1;
 } // out_of_memory
 
 static inline void *Malloc(Context *ctx, const size_t len)
@@ -77,44 +77,100 @@
     return retval;
 } // Malloc
 
+static inline char *StrDup(Context *ctx, const char *str)
+{
+    char *retval = (char *) Malloc(ctx, strlen(str) + 1);
+    if (retval == NULL)
+        out_of_memory(ctx);
+    else
+        strcpy(retval, str);
+    return retval;
+} // StrDup
+
 static inline void Free(Context *ctx, void *ptr)
 {
     if (ptr != NULL)  // check for NULL in case of dumb free() impl.
         ctx->free(ptr, ctx->malloc_data);
 } // Free
 
-static int failf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3);
-static int failf(Context *ctx, const char *fmt, ...)
+static void failf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3);
+static void failf(Context *ctx, const char *fmt, ...)
 {
-    if (ctx->failstr == NULL)  // don't change existing error.
+    const char *fname = NULL;
+    unsigned int linenum = 0;
+    int error_position = 0;
+
+    switch (ctx->parse_phase)
     {
-        char scratch = 0;
-        va_list ap;
+        case MOJOSHADER_PARSEPHASE_NOTSTARTED:
+            error_position = -2;
+            break;
+        case MOJOSHADER_PARSEPHASE_WORKING:
+            // !!! FIXME: fname == base source file if output_pos == 0.
+            if (ctx->output_len > 0)
+            {
+                const size_t idx = ctx->output_len - 1;
+                linenum = ctx->token_to_source[idx].line;
+                fname = ctx->token_to_source[idx].filename;
+            } // if
+            error_position = linenum;
+            break;
+        case MOJOSHADER_PARSEPHASE_DONE:
+            error_position = -1;
+            break;
+        default:
+            assert(0 && "Unexpected value");
+            return;
+    } // switch
+
+    ErrorList *error = (ErrorList *) Malloc(ctx, sizeof (ErrorList));
+    if (error == NULL)
+        return;
+
+    char scratch = 0;
+    va_list ap;
+    va_start(ap, fmt);
+    const int len = vsnprintf(&scratch, sizeof (scratch), fmt, ap);
+    va_end(ap);
+
+    char *failstr = (char *) Malloc(ctx, len + 1);
+    if (failstr == NULL)
+        Free(ctx, error);
+    else
+    {
         va_start(ap, fmt);
-        const int len = vsnprintf(&scratch, sizeof (scratch), fmt, ap);
+        vsnprintf(failstr, len + 1, fmt, ap);  // rebuild it.
         va_end(ap);
 
-        char *failstr = (char *) Malloc(ctx, len + 1);
-        if (failstr != NULL)
+        error->error.error = failstr;
+        error->error.filename = fname ? StrDup(ctx, fname) : NULL;
+        error->error.error_position = error_position;
+
+        ErrorList *prev = NULL;
+        error->next = ctx->errors;
+        while (error->next != NULL)
         {
-            va_start(ap, fmt);
-            vsnprintf(failstr, len + 1, fmt, ap);  // rebuild it.
-            va_end(ap);
-            ctx->failstr = failstr;
-        } // if
-    } // if
+            prev = error->next;
+            error->next = error->next->next;
+        } // while
 
-    return FAIL;
+        if (prev != NULL)
+            prev->next = error;
+        else
+            ctx->errors = error;
+
+        ctx->error_count++;
+    } // else
 } // failf
 
-static inline int fail(Context *ctx, const char *reason)
+static inline void fail(Context *ctx, const char *reason)
 {
-    return failf(ctx, "%s", reason);
+    failf(ctx, "%s", reason);
 } // fail
 
 static inline int isfail(const Context *ctx)
 {
-    return (ctx->failstr != NULL);
+    return ctx->isfail;
 } // isfail
 
 
@@ -243,13 +299,10 @@
 {
     int idx = 0;
 
-    if (isfail(ctx))
-        return FAIL;
-
     if (tctx->pushedback)
     {
         tctx->pushedback = 0;
-        return NOFAIL;
+        return 1;
     } // if
 
     if (tctx->on_endline)
@@ -262,7 +315,10 @@
     {
         // !!! FIXME: carefully crafted (but legal) comments can trigger this.
         if (idx >= sizeof (tctx->token))
-            return fail(ctx, "buffer overflow");
+        {
+            fail(ctx, "buffer overflow");
+            return 0;
+        } // if
 
         char ch = *tctx->source;
         if (ch == '\t')
@@ -280,7 +336,7 @@
             if ((idx > 0) && ((tctx->prevchar < '0') || (tctx->prevchar > '9')))
             {
                 tctx->token[idx++] = '\0';
-                return NOFAIL;
+                return 1;
             } // if
         } // if
         else
@@ -289,7 +345,7 @@
             if ((idx > 0) && ((tctx->prevchar >= '0') && (tctx->prevchar <= '9')))
             {
                 tctx->token[idx++] = '\0';
-                return NOFAIL;
+                return 1;
             } // if
         } // else
 
@@ -310,7 +366,7 @@
                     } // if
                     tctx->token[idx++] = '\0';
                 } // else
-                return NOFAIL;
+                return 1;
 
             case ' ':
                 if (tctx->prevchar == ' ')
@@ -338,13 +394,14 @@
                     tctx->token[idx++] = ch;
                     tctx->token[idx++] = '\0';
                 } // else
-                return NOFAIL;
+                return 1;
 
             case '\0':
                 tctx->token[idx] = '\0';
                 if (idx != 0)  // had any chars? It's a token.
-                    return NOFAIL;
-                return END_OF_STREAM;
+                    return 1;
+                ctx->eof = 1;
+                return 0;
 
             default:
                 tctx->source++;
@@ -355,7 +412,8 @@
         tctx->prevchar = ch;
     } // while
 
-    return fail(ctx, "???");  // shouldn't hit this.
+    assert(0 && "Shouldn't hit this code");
+    return 0;
 } // tokenize_ctx
 
 
@@ -364,10 +422,7 @@
     const int rc = tokenize_ctx(ctx, &ctx->tctx);
 
     #if DEBUG_TOKENIZER
-    printf("TOKENIZE: %s '%s'\n",
-           (rc == END_OF_STREAM) ? "END_OF_STREAM" :
-           (rc == FAIL) ? "FAIL" :
-           (rc == NOFAIL) ? "NOFAIL" : "???",
+    printf("TOKENIZE: %d '%s'\n", rc,
            (ctx->tctx.token[0] == '\n') ? "\\n" : ctx->tctx.token);
     #endif
 
@@ -375,28 +430,19 @@
 } // tokenize
 
 
-static int pushback_ctx(Context *ctx, TokenizerContext *tctx)
+static void pushback_ctx(Context *ctx, TokenizerContext *tctx)
 {
-    if (tctx->pushedback)
-        return fail(ctx, "BUG: Double pushback in parser");
-    else
-        tctx->pushedback = 1;
+    assert(!tctx->pushedback);
+    tctx->pushedback = 1;
+} // pushback_ctx
 
-    return NOFAIL;
-}
 
-static inline int pushback(Context *ctx)
+static inline void pushback(Context *ctx)
 {
-    const int rc = pushback_ctx(ctx, &ctx->tctx);
-
+    pushback_ctx(ctx, &ctx->tctx);
     #if DEBUG_TOKENIZER
-    printf("PUSHBACK: %s\n",
-           (rc == END_OF_STREAM) ? "END_OF_STREAM" :
-           (rc == FAIL) ? "FAIL" :
-           (rc == NOFAIL) ? "NOFAIL" : "???");
+    printf("PUSHBACK\n");
     #endif
-
-    return rc;
 } // pushback
 
 
@@ -404,16 +450,17 @@
                      const int ignoreeol, const int ignorewhitespace,
                      const int eolok, const int eosok)
 {
-    int rc = NOFAIL;
-
-    while ((rc = tokenize_ctx(ctx, tctx)) == NOFAIL)
+    while (tokenize_ctx(ctx, tctx))
     {
         if (tokeq(tctx, "\n"))
         {
             if (ignoreeol)
                 continue;
             else if (!eolok)
-                return fail(ctx, "Unexpected EOL");
+            {
+                fail(ctx, "Unexpected EOL");
+                return 0;
+            } // else if
         } // if
 
         else if (tokeq(tctx, " "))
@@ -425,7 +472,7 @@
         // skip comments...
         else if (tokeq(tctx, "//") || tokeq(tctx, ";"))
         {
-            while ((rc = tokenize_ctx(ctx, tctx)) == NOFAIL)
+            while (tokenize_ctx(ctx, tctx))
             {
                 if (tokeq(tctx, "\n"))
                 {
@@ -439,10 +486,13 @@
         break;
     } // while
 
-    if ((rc == END_OF_STREAM) && (!eosok))
-        return fail(ctx, "Unexpected EOF");
+    if ((ctx->eof) && (!eosok))
+    {
+        fail(ctx, "Unexpected EOF");
+        return 0;
+    } // if
 
-    return rc;
+    return 1;
 } // nexttoken_ctx
 
 
@@ -454,10 +504,7 @@
                                  ignorewhitespace, eolok, eosok);
 
     #if DEBUG_TOKENIZER
-    printf("NEXTTOKEN: %s '%s'\n",
-           (rc == END_OF_STREAM) ? "END_OF_STREAM" :
-           (rc == FAIL) ? "FAIL" :
-           (rc == NOFAIL) ? "NOFAIL" : "???",
+    printf("NEXTTOKEN: %d '%s'\n", rc,
            (ctx->tctx.token[0] == '\n') ? "\\n" : ctx->tctx.token);
     #endif
 
@@ -465,17 +512,30 @@
 } // nexttoken
 
 
-static int require_endline(Context *ctx)
+static void skip_line(Context *ctx)
+{
+    if (!tokeq(&ctx->tctx, "\n"))
+    {
+        while (nexttoken(ctx, 0, 1, 1, 1))
+        {
+            if (tokeq(&ctx->tctx, "\n"))
+                break;
+        } // while
+    } // if
+} // skip_line
+
+
+static void require_endline(Context *ctx)
 {
     TokenizerContext *tctx = &ctx->tctx;
     const int rc = nexttoken(ctx, 0, 1, 1, 1);
-    if (rc == FAIL)
-        return FAIL;
-    else if (rc == END_OF_STREAM)
-        return NOFAIL;  // we'll call this an EOL.
-    else if (!tokeq(tctx, "\n"))
-        return fail(ctx, "Endline expected");
-    return NOFAIL;
+    if (ctx->eof)
+        return;  // we'll call this an EOL.
+    else if ((rc == 0) || (!tokeq(tctx, "\n")))
+    {
+        fail(ctx, "Endline expected");
+        skip_line(ctx);
+    } // else if
 } // require_endline
 
 
@@ -483,19 +543,20 @@
 {
     TokenizerContext *tctx = &ctx->tctx;
     const int rc = nexttoken(ctx, 0, 1, 0, 0);
-    if (rc == FAIL)
-        return FAIL;
-    else if (!tokeq(tctx, ","))
-        return fail(ctx, "Comma expected");
-    return NOFAIL;
+    if ((rc == 0) || (!tokeq(tctx, ",")))
+    {
+        fail(ctx, "Comma expected");
+        return 0;
+    } // if
+    return 1;
 } // require_comma
 
 
 static int parse_register_name(Context *ctx, RegisterType *rtype, int *rnum)
 {
     TokenizerContext *tctx = &ctx->tctx;
-    if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 0;
 
     int neednum = 1;
     int regnum = 0;
@@ -533,25 +594,25 @@
     else if (tokeq(tctx, "o"))
     {
         if (!shader_is_vertex(ctx) || !shader_version_atleast(ctx, 3, 0))
-            return fail(ctx, "Output register not valid in this shader type");
+            fail(ctx, "Output register not valid in this shader type");
         regtype = REG_TYPE_OUTPUT;
     } // else if
     else if (tokeq(tctx, "oT"))
     {
         if (shader_is_vertex(ctx) && shader_version_atleast(ctx, 3, 0))
-            return fail(ctx, "Output register not valid in this shader type");
+            fail(ctx, "Output register not valid in this shader type");
         regtype = REG_TYPE_OUTPUT;
     } // else if
     else if (tokeq(tctx, "a"))
     {
         if (!shader_is_vertex(ctx))
-            return fail(ctx, "Address register only valid in vertex shaders.");
+            fail(ctx, "Address register only valid in vertex shaders.");
         regtype = REG_TYPE_ADDRESS;
     } // else if
     else if (tokeq(tctx, "t"))
     {
         if (!shader_is_pixel(ctx))
-            return fail(ctx, "Address register only valid in pixel shaders.");
+            fail(ctx, "Address register only valid in pixel shaders.");
         regtype = REG_TYPE_ADDRESS;
     } // else if
     else if (tokeq(tctx, "vPos"))
@@ -589,7 +650,10 @@
 
     else
     {
-        return fail(ctx, "expected register type");
+        fail(ctx, "expected register type");
+        regtype = REG_TYPE_CONST;
+        regnum = 0;
+        neednum = 0;
     } // else
 
     if (neednum)
@@ -599,20 +663,20 @@
         //  that whitespace back.
         TokenizerContext tmptctx;
         memcpy(&tmptctx, tctx, sizeof (TokenizerContext));
-        if (nexttoken_ctx(ctx, &tmptctx, 0, 1, 1, 1) == FAIL)
-            return FAIL;
+        if (!nexttoken_ctx(ctx, &tmptctx, 0, 1, 1, 1))
+            return 0;
         else if (tokeq(&tmptctx, "["))
             neednum = 0;
     } // if
 
     if (neednum)
     {
-        if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 0, 0, 0))
+            return 0;
 
         uint32 ui32 = 0;
         if (!ui32fromstr(tctx->token, &ui32))
-            return fail(ctx, "Invalid register index");
+            fail(ctx, "Invalid register index");
         regnum = (int) ui32;
     } // if
 
@@ -641,23 +705,22 @@
         } // if
         else
         {
-            return fail(ctx, "Invalid const register index");
+            fail(ctx, "Invalid const register index");
         } // else
     } // if
 
     *rtype = regtype;
     *rnum = regnum;
 
-    return NOFAIL;
+    return 1;
 } // parse_register_name
 
 
-static int set_result_shift(Context *ctx, DestArgInfo *info, const int val)
+static void set_result_shift(Context *ctx, DestArgInfo *info, const int val)
 {
     if (info->result_shift != 0)
-        return fail(ctx, "Multiple result shift modifiers");
+        fail(ctx, "Multiple result shift modifiers");
     info->result_shift = val;
-    return NOFAIL;
 } // set_result_shift
 
 
@@ -671,19 +734,19 @@
     // See if there are destination modifiers on the instruction itself...
     while (1)
     {
-        if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 0, 0, 0))
+            return 1;
         else if (tokeq(tctx, " "))
             break;  // done with modifiers.
         else if (!tokeq(tctx, "_"))
-            return fail(ctx, "Expected modifier or whitespace");
-        else if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-            return FAIL;
+            fail(ctx, "Expected modifier or whitespace");
+        else if (!nexttoken(ctx, 0, 0, 0, 0))
+            return 1;
         // !!! FIXME: this can be cleaned up when tokenizer is fixed.
         else if (tokeq(tctx, "x"))
         {
-            if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-                return FAIL;
+            if (!nexttoken(ctx, 0, 0, 0, 0))
+                return 1;
             else if (tokeq(tctx, "2"))
                 set_result_shift(ctx, info, 0x1);
             else if (tokeq(tctx, "4"))
@@ -691,13 +754,13 @@
             else if (tokeq(tctx, "8"))
                 set_result_shift(ctx, info, 0x3);
             else
-                return fail(ctx, "Expected modifier");
+                fail(ctx, "Expected modifier");
         } // else if
         // !!! FIXME: this can be cleaned up when tokenizer is fixed.
         else if (tokeq(tctx, "d"))
         {
-            if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-                return FAIL;
+            if (!nexttoken(ctx, 0, 0, 0, 0))
+                return 1;
             else if (tokeq(tctx, "8"))
                 set_result_shift(ctx, info, 0xD);
             else if (tokeq(tctx, "4"))
@@ -705,7 +768,7 @@
             else if (tokeq(tctx, "2"))
                 set_result_shift(ctx, info, 0xF);
             else
-                return fail(ctx, "Expected modifier");
+                fail(ctx, "Expected modifier");
         } // else if
         else if (tokeq(tctx, "sat"))
             info->result_mod |= MOD_SATURATE;
@@ -714,25 +777,26 @@
         else if (tokeq(tctx, "centroid"))
             info->result_mod |= MOD_CENTROID;
         else
-            return fail(ctx, "Expected modifier");
+            fail(ctx, "Expected modifier");
     } // while
 
-    if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 1;
 
     // !!! FIXME: predicates.
     if (tokeq(tctx, "("))
-        return fail(ctx, "Predicates unsupported at this time");
+        fail(ctx, "Predicates unsupported at this time");  // !!! FIXME: ...
+
     pushback(ctx);  // parse_register_name calls nexttoken().
 
-    if (parse_register_name(ctx, &info->regtype, &info->regnum) == FAIL)
-        return FAIL;
+    parse_register_name(ctx, &info->regtype, &info->regnum);
 
-    if (nexttoken(ctx, 0, 1, 1, 1) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 1, 1))
+        return 1;
 
     // !!! FIXME: can dest registers do relative addressing?
 
+    int invalid_writemask = 0;
     int implicit_writemask = 0;
     if (!tokeq(tctx, "."))
     {
@@ -744,11 +808,11 @@
     // !!! FIXME: Cg generates code with oDepth.z ... this is a bug, I think.
     //else if (scalar_register(ctx->shader_type, info->regtype, info->regnum))
     else if ( (scalar_register(ctx->shader_type, info->regtype, info->regnum)) && (info->regtype != REG_TYPE_DEPTHOUT) )
-        return fail(ctx, "Writemask specified for scalar register");
-    else if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+        fail(ctx, "Writemask specified for scalar register");
+    else if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 1;
     else if (tokeq(tctx, ""))
-        return fail(ctx, "Invalid writemask");
+        invalid_writemask = 1;
     else
     {
         char *ptr = tctx->token;
@@ -766,7 +830,7 @@
         } // if
 
         if (*ptr != '\0')
-            return fail(ctx, "Invalid writemask");
+            invalid_writemask = 1;
 
         info->writemask = ( ((info->writemask0 & 0x1) << 0) |
                             ((info->writemask1 & 0x1) << 1) |
@@ -774,18 +838,24 @@
                             ((info->writemask3 & 0x1) << 3) );
     } // else
 
+    if (invalid_writemask)
+        fail(ctx, "Invalid writemask");
+
     // !!! FIXME: Cg generates code with oDepth.z ... this is a bug, I think.
     if (info->regtype == REG_TYPE_DEPTHOUT)
     {
         if ( (!implicit_writemask) && ((info->writemask0 + info->writemask1 +
                info->writemask2 + info->writemask3) > 1) )
-            return fail(ctx, "Writemask specified for scalar register");
+            fail(ctx, "Writemask specified for scalar register");
     } // if
 
     info->orig_writemask = info->writemask;
 
     if (ctx->tokenbufpos >= STATICARRAYLEN(ctx->tokenbuf))
-        return fail(ctx, "Too many tokens");
+    {
+        fail(ctx, "Too many tokens");
+        return 1;
+    } // if
 
     ctx->tokenbuf[ctx->tokenbufpos++] =
             ( ((((uint32) 1)) << 31) |
@@ -818,21 +888,25 @@
     int retval = 1;
 
     if (ctx->tokenbufpos >= STATICARRAYLEN(ctx->tokenbuf))
-        return fail(ctx, "Too many tokens");
+    {
+        fail(ctx, "Too many tokens");
+        return 0;
+    } // if
 
     // mark this now, so optional relative addressing token is placed second.
     uint32 *token = &ctx->tokenbuf[ctx->tokenbufpos++];
+    *token = 0;
 
     SourceMod srcmod = SRCMOD_NONE;
     int negate = 0;
-    if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 1;
     else if (tokeq(tctx, "1"))
     {
-        if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 1, 0, 0))
+            return 1;
         else if (!tokeq(tctx, "-"))
-            return fail(ctx, "Unexpected value");
+            fail(ctx, "Unexpected value");
         else
             srcmod = SRCMOD_COMPLEMENT;
     } // else
@@ -845,14 +919,13 @@
 
     RegisterType regtype;
     int regnum;
-    if (parse_register_name(ctx, &regtype, &regnum) == FAIL)
-        return FAIL;
-    else if (nexttoken(ctx, 0, 1, 1, 1) == FAIL)
-        return FAIL;
+    parse_register_name(ctx, &regtype, &regnum);
+    if (!nexttoken(ctx, 0, 1, 1, 1))
+        return 1;
     else if (!tokeq(tctx, "_"))
         pushback(ctx);
-    else if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
+    else if (!nexttoken(ctx, 0, 0, 0, 0))
+        return 1;
     else if (tokeq(tctx, "bias"))
         set_source_mod(ctx, negate, SRCMOD_BIAS, SRCMOD_BIASNEGATE, &srcmod);
     else if (tokeq(tctx, "bx2"))
@@ -866,48 +939,49 @@
     else if (tokeq(tctx, "abs"))
         set_source_mod(ctx, negate, SRCMOD_ABS, SRCMOD_ABSNEGATE, &srcmod);
     else
-        return fail(ctx, "Invalid source modifier");
+        fail(ctx, "Invalid source modifier");
 
-    if (nexttoken(ctx, 0, 1, 1, 1) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 1, 1))
+        return 1;
 
     uint32 relative = 0;
     if (!tokeq(tctx, "["))
         pushback(ctx);  // not relative addressing?
-    else if (!relok)
-        return fail(ctx, "Relative addressing not permitted here.");
     else
     {
-        const int rc = parse_source_token_maybe_relative(ctx, 0);
-        if (rc == FAIL)
-            return FAIL;
-        retval += rc;
+        if (!relok)
+            fail(ctx, "Relative addressing not permitted here.");
+        else
+            retval++;
+
+        parse_source_token_maybe_relative(ctx, 0);
         relative = 1;
-        if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 1, 0, 0))
+            return retval;
         else if (!tokeq(tctx, "+"))
             pushback(ctx);
-        else if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-            return FAIL;
+        else if (!nexttoken(ctx, 0, 1, 0, 0))
+            return retval;
         else
         {
             if (regnum != 0)  // !!! FIXME: maybe c3[a0.x + 5] is legal and becomes c[a0.x + 8] ?
                 fail(ctx, "Relative addressing with explicit register number.");
             uint32 ui32 = 0;
             if (!ui32fromstr(tctx->token, &ui32))
-                return fail(ctx, "Invalid relative addressing offset");
+                fail(ctx, "Invalid relative addressing offset");
             regnum += (int) ui32;
         } // else
 
-        if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 1, 0, 0))
+            return retval;
         else if (!tokeq(tctx, "]"))
-            return fail(ctx, "Expected ']'");
+            fail(ctx, "Expected ']'");
     } // else
 
-    if (nexttoken(ctx, 0, 1, 1, 1) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 1, 1))
+        return retval;
 
+    int invalid_swizzle = 0;
     uint32 swizzle = 0;
     if (!tokeq(tctx, "."))
     {
@@ -915,11 +989,11 @@
         pushback(ctx);  // no explicit writemask; do full mask.
     } // if
     else if (scalar_register(ctx->shader_type, regtype, regnum))
-        return fail(ctx, "Swizzle specified for scalar register");
-    else if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+        fail(ctx, "Swizzle specified for scalar register");
+    else if (!nexttoken(ctx, 0, 1, 0, 0))
+        return retval;
     else if (tokeq(tctx, ""))
-        return fail(ctx, "Invalid swizzle");
+        invalid_swizzle = 1;
     else
     {
         // deal with shortened form (.x = .xxxx, etc).
@@ -930,10 +1004,10 @@
         else if (tctx->token[3] == '\0')
             tctx->token[3] = tctx->token[2];
         else if (tctx->token[4] != '\0')
-            return fail(ctx, "Invalid swizzle");
+            invalid_swizzle = 1;
         tctx->token[4] = '\0';
 
-        uint32 val;
+        uint32 val = 0;
         int saw_xyzw = 0;
         int saw_rgba = 0;
         int i;
@@ -950,15 +1024,18 @@
                 case 'g': val = 1; saw_rgba = 1; break;
                 case 'b': val = 2; saw_rgba = 1; break;
                 case 'a': val = 3; saw_rgba = 1; break;
-                default: return fail(ctx, "Invalid swizzle");
+                default: invalid_swizzle = 1; break;
             } // switch
             swizzle |= (val << (i * 2));
         } // for
 
         if (saw_xyzw && saw_rgba)
-            return fail(ctx, "Invalid swizzle");
+            invalid_swizzle = 1;
     } // else
 
+    if (invalid_swizzle)
+        fail(ctx, "Invalid swizzle");
+
     *token = ( ((((uint32) 1)) << 31) |
                ((((uint32) regnum) & 0x7ff) << 0) |
                ((((uint32) relative) & 0x1) << 13) |
@@ -979,7 +1056,7 @@
 
 static int parse_args_NULL(Context *ctx)
 {
-    return (isfail(ctx) ? FAIL : 1);
+    return 1;
 } // parse_args_NULL
 
 
@@ -990,41 +1067,43 @@
     union { float f; int32 si32; uint32 ui32; } cvt;
     cvt.si32 = 0;
 
-    if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+    *token = 0;
+
+    if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 0;
     else if (tokeq(tctx, "-"))
         negative = -1;
     else
         pushback(ctx);
 
     uint32 val = 0;
-    if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 0;
     else if (!ui32fromstr(tctx->token, &val))
-        return fail(ctx, "Expected number");
+        fail(ctx, "Expected number");
 
     uint32 fraction = 0;
-    if (nexttoken(ctx, 0, 1, 1, 1) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 1, 1))
+        return 0;
     else if (!tokeq(tctx, "."))
         pushback(ctx);  // whole number
     else if (!floatok)
-        return fail(ctx, "Expected whole number");
-    else if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+        fail(ctx, "Expected whole number");
+    else if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 0;
     else if (!ui32fromstr(tctx->token, &fraction))
-        return fail(ctx, "Expected number");
+        fail(ctx, "Expected number");
 
     uint32 exponent = 0;
     int negexp = 0;
-    if (nexttoken(ctx, 0, 1, 1, 1) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 1, 1, 1))
+        return 0;
     else if (!tokeq(tctx, "e"))
         pushback(ctx);
     else if (!floatok)
-        return fail(ctx, "Exponent on whole number");  // !!! FIXME: illegal?
-    else if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+        fail(ctx, "Exponent on whole number");  // !!! FIXME: illegal?
+    else if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 0;
     else
     {
         if (!tokeq(tctx, "-"))
@@ -1032,10 +1111,10 @@
         else
             negexp = 1;
 
-        if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 1, 0, 0))
+            return 0;
         else if (!ui32fromstr(tctx->token, &exponent))
-            return fail(ctx, "Expected exponent");
+            fail(ctx, "Expected exponent");
     } // else
 
     if (!floatok)
@@ -1066,30 +1145,21 @@
     } // else
 
     *token = cvt.ui32;
-    return NOFAIL;
+    return 1;
 } // parse_num
 
 
 static int parse_args_DEFx(Context *ctx, const int isflt)
 {
-    if (parse_destination_token(ctx) == FAIL)
-        return FAIL;
-    else if (require_comma(ctx) == FAIL)
-        return FAIL;
-    else if (parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]) == FAIL)
-        return FAIL;
-    else if (require_comma(ctx) == FAIL)
-        return FAIL;
-    else if (parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]) == FAIL)
-        return FAIL;
-    else if (require_comma(ctx) == FAIL)
-        return FAIL;
-    else if (parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]) == FAIL)
-        return FAIL;
-    else if (require_comma(ctx) == FAIL)
-        return FAIL;
-    else if (parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]) == FAIL)
-        return FAIL;
+    parse_destination_token(ctx);
+    require_comma(ctx);
+    parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]);
+    require_comma(ctx);
+    parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]);
+    require_comma(ctx);
+    parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]);
+    require_comma(ctx);
+    parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]);
     return 6;
 } // parse_args_DEFx
 
@@ -1109,20 +1179,16 @@
 static int parse_args_DEFB(Context *ctx)
 {
     TokenizerContext *tctx = &ctx->tctx;
-    if (parse_destination_token(ctx) == FAIL)
-        return FAIL;
-    else if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
-    else if (!tokeq(tctx, ","))
-        return fail(ctx, "Expected ','");
-    else if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-        return FAIL;
+    parse_destination_token(ctx);
+    require_comma(ctx);
+    if (!nexttoken(ctx, 0, 1, 0, 0))
+        return 1;
     else if (tokeq(tctx, "true"))
         ctx->tokenbuf[ctx->tokenbufpos++] = 1;
     else if (tokeq(tctx, "false"))
         ctx->tokenbuf[ctx->tokenbufpos++] = 0;
     else
-        return fail(ctx, "Expected 'true' or 'false'");
+        fail(ctx, "Expected 'true' or 'false'");
     return 3;
 } // parse_args_DEFB
 
@@ -1144,8 +1210,8 @@
     strcpy(token, tctx->token);
     if (tokeq(tctx, "2"))  // "2d" is two tokens.
     {
-        if (nexttoken(ctx, 0, 0, 1, 1) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 0, 1, 1))
+            return 0;
         else if (!tokeq(tctx, "d") != 0)
             pushback(ctx);
         else
@@ -1158,7 +1224,7 @@
         {
             *issampler = 0;
             *val = i;
-            return NOFAIL;
+            return 1;
         } // if
     } // for
 
@@ -1168,7 +1234,7 @@
         {
             *issampler = 1;
             *val = i + 2;
-            return NOFAIL;
+            return 1;
         } // if
     } // for
 
@@ -1182,11 +1248,11 @@
             tctx->source -= strlen(token);  // !!! FIXME: hack to move back
             strcpy(tctx->token, "_");  // !!! FIXME: hack to move back
             pushback(ctx);  // !!! FIXME: hack to move back
-            return NOFAIL;  // if you have "dcl_pp", then "_pp" isn't a usage.
+            return 1;  // if you have "dcl_pp", then "_pp" isn't a usage.
         } // if
     } // for
 
-    return FAIL;
+    return 0;
 } // parse_dcl_usage
 
 
@@ -1198,31 +1264,31 @@
     uint32 index = 0;
 
     ctx->tokenbufpos++;  // save a spot for the usage/index token.
+    ctx->tokenbuf[0] = 0;
 
-    if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 0, 0, 0))
+        return 1;
     else if (tokeq(tctx, " "))
         pushback(ctx);
     else if (!tokeq(tctx, "_"))
-        return fail(ctx, "Expected register or usage");
-    else if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
-    else if (parse_dcl_usage(ctx, &usage, &issampler) == FAIL)
-        return FAIL;
+        fail(ctx, "Expected register or usage");
+    else if (!nexttoken(ctx, 0, 0, 0, 0))
+        return 1;
+    else
+        parse_dcl_usage(ctx, &usage, &issampler);
 
-    if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 0, 0, 0))
+        return 1;
     else if (tokeq(tctx, " ") || tokeq(tctx, "_"))
         pushback(ctx);  // parse_destination_token() wants these.
     else if (!ui32fromstr(tctx->token, &index))
-        return fail(ctx, "Expected usage index or register");
+        fail(ctx, "Expected usage index or register");
 
-    if (parse_destination_token(ctx) == FAIL)
-        return FAIL;
+    parse_destination_token(ctx);
 
     const int samplerreg = (ctx->dest_arg.regtype == REG_TYPE_SAMPLER);
     if (issampler != samplerreg)
-        return fail(ctx, "Invalid usage");
+        fail(ctx, "Invalid usage");
     else if (samplerreg)
         ctx->tokenbuf[0] = (usage << 27) | 0x80000000;
     else
@@ -1236,7 +1302,7 @@
 {
     int retval = 1;
     retval += parse_destination_token(ctx);
-    return isfail(ctx) ? FAIL : retval;
+    return retval;
 } // parse_args_D
 
 
@@ -1244,7 +1310,7 @@
 {
     int retval = 1;
     retval += parse_source_token(ctx);
-    return isfail(ctx) ? FAIL : retval;
+    return retval;
 } // parse_args_S
 
 
@@ -1252,9 +1318,9 @@
 {
     int retval = 1;
     retval += parse_source_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    return isfail(ctx) ? FAIL : retval;
+    return retval;
 } // parse_args_SS
 
 
@@ -1262,9 +1328,9 @@
 {
     int retval = 1;
     retval += parse_destination_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    return isfail(ctx) ? FAIL : retval;
+    return retval;
 } // parse_args_DS
 
 
@@ -1272,11 +1338,11 @@
 {
     int retval = 1;
     retval += parse_destination_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    return isfail(ctx) ? FAIL : retval;
+    return retval;
 } // parse_args_DSS
 
 
@@ -1284,13 +1350,13 @@
 {
     int retval = 1;
     retval += parse_destination_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    return isfail(ctx) ? FAIL : retval;
+    return retval;
 } // parse_args_DSSS
 
 
@@ -1298,15 +1364,15 @@
 {
     int retval = 1;
     retval += parse_destination_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    if (require_comma(ctx) == FAIL) return FAIL;
+    require_comma(ctx);
     retval += parse_source_token(ctx);
-    return isfail(ctx) ? FAIL : retval;
+    return retval;
 } // parse_args_DSSSS
 
 
@@ -1365,7 +1431,7 @@
 static int parse_condition(Context *ctx, uint32 *controls)
 {
     TokenizerContext *tctx = &ctx->tctx;
-    if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
+    if (!nexttoken(ctx, 0, 0, 0, 0))
         return 0;
     else if (!tokeq(tctx, "_"))
     {
@@ -1373,7 +1439,7 @@
         return 0;
     } // else if
 
-    if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
+    if (!nexttoken(ctx, 0, 0, 0, 0))
         return 0;
     else
     {
@@ -1413,8 +1479,8 @@
 
     if (tokeq(tctx, "+"))
     {
-        if (nexttoken(ctx, 0, 1, 0, 0) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 1, 0, 0))
+            return 0;
         coissue = 1;
     } // if
 
@@ -1425,7 +1491,10 @@
     while (1)
     {
         if ( (strlen(opstr) + strlen(tctx->token)) >= (sizeof (opstr)-1) )
-            return fail(ctx, "Expected instruction");
+        {
+            fail(ctx, "Expected instruction");
+            return 0;
+        } // if
 
         char *ptr;
         for (ptr = tctx->token; *ptr != '\0'; ptr++)
@@ -1442,8 +1511,8 @@
 
         strcat(opstr, tctx->token);
 
-        if (nexttoken(ctx, 0, 0, 1, 1) == FAIL)
-            return FAIL;
+        if (!nexttoken(ctx, 0, 0, 1, 1))
+            return 0;
     } // while
 
     uint32 controls = 0;
@@ -1479,7 +1548,10 @@
     uint32 opcode = (uint32) i;
 
     if (!valid_opcode)
-        return failf(ctx, "Unknown instruction '%s'", opstr);
+    {
+        failf(ctx, "Unknown instruction '%s'", opstr);
+        return 0;
+    } // if
 
     // This might need to be IFC instead of IF.
     // !!! FIXME: compare opcode, not string
@@ -1502,7 +1574,7 @@
     else if (strcmp(instruction->opcode_string, "SETP") == 0)
     {
         if (!parse_condition(ctx, &controls))
-            return fail(ctx, "SETP requires a condition");
+            fail(ctx, "SETP requires a condition");
     } // else if
 
     instruction = &instructions[opcode];  // ...in case this changed.
@@ -1512,8 +1584,7 @@
     ctx->tokenbufpos = 0;
 
     const int tokcount = instruction->parse_args(ctx);
-    if (require_endline(ctx) == FAIL)
-        return FAIL;
+    require_endline(ctx);
 
     // insttoks bits are reserved and should be zero if < SM2.
     const uint32 insttoks = shader_version_atleast(ctx, 2, 0) ? tokcount-1 : 0;
@@ -1529,15 +1600,15 @@
     for (i = 0; i < (tokcount-1); i++)
         output_token(ctx, ctx->tokenbuf[i]);
 
-    return NOFAIL;
+    return 1;
 } // parse_instruction_token
 
 
-static int parse_version_token(Context *ctx)
+static void parse_version_token(Context *ctx)
 {
     TokenizerContext *tctx = &ctx->tctx;
-    if (nexttoken(ctx, 1, 1, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 1, 1, 0, 0))
+        return;
 
     uint32 shader_type = 0;
     if (tokeq(tctx, "vs"))
@@ -1553,75 +1624,75 @@
     else
     {
         // !!! FIXME: geometry shaders?
-        return fail(ctx, "Expected version string");
+        fail(ctx, "Expected version string");
     } // else
 
+    int bad_version = 0;
+
     uint32 major = 0;
-    if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 0, 0, 0))
+        return;
     else if ( (!tokeq(tctx, "_")) && (!tokeq(tctx, ".")) )
-        return fail(ctx, "Expected version string");
-    else if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
+        bad_version = 1;
+    else if (!nexttoken(ctx, 0, 0, 0, 0))
+        return;
     else if (!ui32fromstr(tctx->token, &major))
-        return fail(ctx, "Expected version string");
+        bad_version = 1;
 
     uint32 minor = 0;
-    if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
+    if (!nexttoken(ctx, 0, 0, 0, 0))
+        return;
     else if ( (!tokeq(tctx, "_")) && (!tokeq(tctx, ".")) )
-        return fail(ctx, "Expected version string");
-    else if (nexttoken(ctx, 0, 0, 0, 0) == FAIL)
-        return FAIL;
+        bad_version = 1;
+    else if (!nexttoken(ctx, 0, 0, 0, 0))
+        return;
     else if (tokeq(tctx, "x"))
         minor = 1;
     else if (tokeq(tctx, "sw"))
         minor = 255;
     else if (!ui32fromstr(tctx->token, &minor))
-        return fail(ctx, "Expected version string");
+        bad_version = 1;
+
+    if (bad_version)
+        fail(ctx, "Expected version string");
+    else
+        require_endline(ctx);
 
     ctx->major_ver = major;
     ctx->minor_ver = minor;
 
-    if (require_endline(ctx) == FAIL)
-        return FAIL;
-
     ctx->version_token = (shader_type << 16) | (major << 8) | (minor << 0);
     output_token(ctx, ctx->version_token);
-    return NOFAIL;
 } // parse_version_token
 
 
-static int parse_phase_token(Context *ctx)
+static void parse_phase_token(Context *ctx)
 {
-    if (require_endline(ctx) == FAIL)
-        return FAIL;
+    require_endline(ctx);
     output_token(ctx, 0x0000FFFD); // phase token always 0x0000FFFD.
-    return NOFAIL;
 } // parse_phase_token
 
 
-static int parse_end_token(Context *ctx)
+static void parse_end_token(Context *ctx)
 {
-    if (require_endline(ctx) == FAIL)
-        return FAIL;
+    require_endline(ctx);
     // We don't emit the end token bits here, since it's valid for a shader
     //  to not specify an "end" string at all; it's implicit, in that case.
     // Instead, we make sure if we see "end" that it's the last thing we see.
-    if (nexttoken(ctx, 1, 1, 0, 1) != END_OF_STREAM)
-        return fail(ctx, "Content after END");
-    return NOFAIL;
+    nexttoken(ctx, 1, 1, 0, 1);
+    if (!ctx->eof)
+        fail(ctx, "Content after END");
 } // parse_end_token
 
 
-static int parse_token(Context *ctx)
+static void parse_token(Context *ctx)
 {
     TokenizerContext *tctx = &ctx->tctx;
     if (tokeq(tctx, "end"))
-        return parse_end_token(ctx);
+        parse_end_token(ctx);
     else if (tokeq(tctx, "phase"))
-        return parse_phase_token(ctx);
-    return parse_instruction_token(ctx);
+        parse_phase_token(ctx);
+    parse_instruction_token(ctx);
 } // parse_token
 
 
@@ -1647,14 +1718,26 @@
 } // build_context
 
 
+static void free_error_list(MOJOSHADER_free f, void *d, ErrorList *item)
+{
+    while (item != NULL)
+    {
+        ErrorList *next = item->next;
+        f((void *) item->error.error, d);
+        f((void *) item->error.filename, d);
+        f(item, d);
+        item = next;
+    } // while
+} // free_error_list
+
+
 static void destroy_context(Context *ctx)
 {
     if (ctx != NULL)
     {
         MOJOSHADER_free f = ((ctx->free != NULL) ? ctx->free : internal_free);
         void *d = ctx->malloc_data;
-        if ((ctx->failstr != NULL) && (ctx->failstr != out_of_mem_str))
-            f((void *) ctx->failstr, d);
+        free_error_list(f, d, ctx->errors);
         if (ctx->output != NULL)
             f(ctx->output, d);
         if (ctx->token_to_source != NULL)
@@ -1666,6 +1749,31 @@
 } // destroy_context
 
 
+static MOJOSHADER_error *build_errors(Context *ctx)
+{
+    int total = 0;
+    MOJOSHADER_error *retval = (MOJOSHADER_error *)
+            Malloc(ctx, sizeof (MOJOSHADER_error) * ctx->error_count);
+    if (retval == NULL)
+        return NULL;
+
+    ErrorList *item = ctx->errors;
+    while (item != NULL)
+    {
+        ErrorList *next = item->next;
+        // reuse the string allocations
+        memcpy(&retval[total], &item->error, sizeof (MOJOSHADER_error));
+        Free(ctx, item);
+        item = next;
+        total++;
+    } // while
+    ctx->errors = NULL;
+
+    assert(total == ctx->error_count);
+    return retval;
+} // build_errors
+
+
 static const MOJOSHADER_parseData *build_failed_assembly(Context *ctx)
 {
     MOJOSHADER_parseData *retval = NULL;
@@ -1681,32 +1789,13 @@
     retval->free = (ctx->free == internal_free) ? NULL : ctx->free;
     retval->malloc_data = ctx->malloc_data;
 
-    // !!! FIXME: handle multiple errors.
-    retval->error_count = 1;
-    retval->errors = (MOJOSHADER_error *)
-                     Malloc(ctx, sizeof (MOJOSHADER_error));
-    if (retval->errors == NULL)
+    retval->error_count = ctx->error_count;
+    retval->errors = build_errors(ctx);
+    if ((retval->errors == NULL) && (ctx->error_count > 0))
     {
         Free(ctx, retval);
         return &out_of_mem_data;
     } // if
-    retval->errors->error = ctx->failstr;
-    retval->errors->filename = NULL;
-
-    ctx->failstr = NULL;  // don't let this get free()'d too soon.
-
-    switch (ctx->parse_phase)
-    {
-        case MOJOSHADER_PARSEPHASE_NOTSTARTED:
-            retval->errors->error_position = -2;
-            break;
-        case MOJOSHADER_PARSEPHASE_WORKING:
-            retval->errors->error_position = ctx->tctx.linenum;
-            break;
-        case MOJOSHADER_PARSEPHASE_DONE:
-            retval->errors->error_position = -1;
-            break;
-    } // switch
 
     return retval;
 } // build_failed_assembly
@@ -1884,8 +1973,7 @@
     for (i = 0; i < comment_count; i++)
         output_comment_string(ctx, comments[i]);
 
-    if (!isfail(ctx))
-        ctx->parse_phase = MOJOSHADER_PARSEPHASE_WORKING;
+    ctx->parse_phase = MOJOSHADER_PARSEPHASE_WORKING;
 } // output_comments
 
 
@@ -1897,6 +1985,7 @@
                             unsigned int symbol_count,
                             MOJOSHADER_malloc m, MOJOSHADER_free f, void *d)
 {
+    int failed = 0;
     MOJOSHADER_parseData *retval = NULL;
     Context *ctx = NULL;
 
@@ -1913,12 +2002,22 @@
     output_comments(ctx, comments, comment_count, symbols, symbol_count);
 
     // parse out the rest of the tokens after the version token...
-    while (nexttoken(ctx, 1, 1, 0, 1) == NOFAIL)
+    while (nexttoken(ctx, 1, 1, 0, 1))
+    {
+        if (isfail(ctx))
+        {
+            failed = 1;
+            ctx->isfail = 0;
+            skip_line(ctx);  // start fresh on next line.
+        } // if
         parse_token(ctx);
+    } // while
+
+    ctx->isfail = failed;
 
     output_token(ctx, 0x0000FFFF);   // end token always 0x0000FFFF.
 
-    if (isfail(ctx))
+    if (failed)
         retval = (MOJOSHADER_parseData *) build_failed_assembly(ctx);
     else
     {