Refactor decoder input stream
[jansson.git] / src / load.c
index f4fb3b7..74e33fc 100644 (file)
 #include "strbuffer.h"
 #include "utf.h"
 
+#define STREAM_STATE_OK        0
+#define STREAM_STATE_EOF      -1
+#define STREAM_STATE_ERROR    -2
+
 #define TOKEN_INVALID         -1
 #define TOKEN_EOF              0
 #define TOKEN_STRING         256
 #define TOKEN_FALSE          260
 #define TOKEN_NULL           261
 
-/* read one byte from stream, return EOF on end of file */
+/* Read one byte from stream, convert to unsigned char, then int, and
+   return. return EOF on end of file. This corresponds to the
+   behaviour of fgetc(). */
 typedef int (*get_func)(void *data);
 
-/* return non-zero if end of file has been reached */
-typedef int (*eof_func)(void *data);
-
 typedef struct {
     get_func get;
-    eof_func eof;
     void *data;
-    int stream_pos;
     char buffer[5];
     int buffer_pos;
+    int state;
+    int line;
+    int column, last_column;
+    size_t position;
 } stream_t;
 
-
 typedef struct {
     stream_t stream;
     strbuffer_t saved_text;
     int token;
-    int line, column;
     union {
         char *string;
         json_int_t integer;
@@ -57,6 +60,8 @@ typedef struct {
     } value;
 } lex_t;
 
+#define stream_to_lex(stream) container_of(stream, lex_t, stream)
+
 
 /*** error reporting ***/
 
@@ -67,6 +72,7 @@ static void error_set(json_error_t *error, const lex_t *lex,
     char msg_text[JSON_ERROR_TEXT_LENGTH];
 
     int line = -1, col = -1;
+    size_t pos = 0;
     const char *result = msg_text;
 
     if(!error)
@@ -81,7 +87,9 @@ static void error_set(json_error_t *error, const lex_t *lex,
         const char *saved_text = strbuffer_value(&lex->saved_text);
         char msg_with_context[JSON_ERROR_TEXT_LENGTH];
 
-        line = lex->line;
+        line = lex->stream.line;
+        col = lex->stream.column;
+        pos = lex->stream.position;
 
         if(saved_text && saved_text[0])
         {
@@ -93,41 +101,57 @@ static void error_set(json_error_t *error, const lex_t *lex,
         }
         else
         {
-            snprintf(msg_with_context, JSON_ERROR_TEXT_LENGTH,
-                     "%s near end of file", msg_text);
-            result = msg_with_context;
+            if(lex->stream.state == STREAM_STATE_ERROR) {
+                /* No context for UTF-8 decoding errors */
+                result = msg_text;
+            }
+            else {
+                snprintf(msg_with_context, JSON_ERROR_TEXT_LENGTH,
+                         "%s near end of file", msg_text);
+                result = msg_with_context;
+            }
         }
     }
 
-    jsonp_error_set(error, line, col, "%s", result);
+    jsonp_error_set(error, line, col, pos, "%s", result);
 }
 
 
 /*** lexical analyzer ***/
 
 static void
-stream_init(stream_t *stream, get_func get, eof_func eof, void *data)
+stream_init(stream_t *stream, get_func get, void *data)
 {
     stream->get = get;
-    stream->eof = eof;
     stream->data = data;
-    stream->stream_pos = 0;
     stream->buffer[0] = '\0';
     stream->buffer_pos = 0;
+
+    stream->state = STREAM_STATE_OK;
+    stream->line = 1;
+    stream->column = 0;
+    stream->position = 0;
 }
 
-static char stream_get(stream_t *stream, json_error_t *error)
+static int stream_get(stream_t *stream, json_error_t *error)
 {
-    char c;
+    int c;
+
+    if(stream->state != STREAM_STATE_OK)
+        return stream->state;
 
     if(!stream->buffer[stream->buffer_pos])
     {
-        stream->buffer[0] = stream->get(stream->data);
-        stream->buffer_pos = 0;
+        c = stream->get(stream->data);
+        if(c == EOF) {
+            stream->state = STREAM_STATE_EOF;
+            return STREAM_STATE_EOF;
+        }
 
-        c = stream->buffer[0];
+        stream->buffer[0] = c;
+        stream->buffer_pos = 0;
 
-        if((unsigned char)c >= 0x80 && c != (char)EOF)
+        if(0x80 <= c && c <= 0xFF)
         {
             /* multi-byte UTF-8 sequence */
             int i, count;
@@ -144,30 +168,47 @@ static char stream_get(stream_t *stream, json_error_t *error)
             if(!utf8_check_full(stream->buffer, count, NULL))
                 goto out;
 
-            stream->stream_pos += count;
             stream->buffer[count] = '\0';
         }
-        else {
+        else
             stream->buffer[1] = '\0';
-            stream->stream_pos++;
-        }
     }
 
-    return stream->buffer[stream->buffer_pos++];
+    c = stream->buffer[stream->buffer_pos++];
 
-out:
-    error_set(error, NULL, "unable to decode byte 0x%x at position %d",
-              (unsigned char)c, stream->stream_pos);
+    stream->position++;
+    if(c == '\n') {
+        stream->line++;
+        stream->last_column = stream->column;
+        stream->column = 0;
+    }
+    else if(utf8_check_first(c)) {
+        /* track the Unicode character column, so increment only if
+           this is the first character of a UTF-8 sequence */
+        stream->column++;
+    }
 
-    stream->buffer[0] = EOF;
-    stream->buffer[1] = '\0';
-    stream->buffer_pos = 1;
+    return c;
 
-    return EOF;
+out:
+    stream->state = STREAM_STATE_ERROR;
+    error_set(error, stream_to_lex(stream), "unable to decode byte 0x%x", c);
+    return STREAM_STATE_ERROR;
 }
 
-static void stream_unget(stream_t *stream, char c)
+static void stream_unget(stream_t *stream, int c)
 {
+    if(c == STREAM_STATE_EOF || c == STREAM_STATE_ERROR)
+        return;
+
+    stream->position--;
+    if(c == '\n') {
+        stream->line--;
+        stream->column = stream->last_column;
+    }
+    else if(utf8_check_first(c))
+        stream->column--;
+
     assert(stream->buffer_pos > 0);
     stream->buffer_pos--;
     assert(stream->buffer[stream->buffer_pos] == c);
@@ -179,29 +220,32 @@ static int lex_get(lex_t *lex, json_error_t *error)
     return stream_get(&lex->stream, error);
 }
 
-static int lex_eof(lex_t *lex)
-{
-    return lex->stream.eof(lex->stream.data);
-}
-
-static void lex_save(lex_t *lex, char c)
+static void lex_save(lex_t *lex, int c)
 {
     strbuffer_append_byte(&lex->saved_text, c);
 }
 
 static int lex_get_save(lex_t *lex, json_error_t *error)
 {
-    char c = stream_get(&lex->stream, error);
-    lex_save(lex, c);
+    int c = stream_get(&lex->stream, error);
+    if(c != STREAM_STATE_EOF && c != STREAM_STATE_ERROR)
+        lex_save(lex, c);
     return c;
 }
 
-static void lex_unget_unsave(lex_t *lex, char c)
+static void lex_unget(lex_t *lex, int c)
 {
-    char d;
     stream_unget(&lex->stream, c);
-    d = strbuffer_pop(&lex->saved_text);
-    assert(c == d);
+}
+
+static void lex_unget_unsave(lex_t *lex, int c)
+{
+    if(c != STREAM_STATE_EOF && c != STREAM_STATE_ERROR) {
+        char d;
+        stream_unget(&lex->stream, c);
+        d = strbuffer_pop(&lex->saved_text);
+        assert(c == d);
+    }
 }
 
 static void lex_save_cached(lex_t *lex)
@@ -210,6 +254,7 @@ static void lex_save_cached(lex_t *lex)
     {
         lex_save(lex, lex->stream.buffer[lex->stream.buffer_pos]);
         lex->stream.buffer_pos++;
+        lex->stream.position++;
     }
 }
 
@@ -239,7 +284,7 @@ static int32_t decode_unicode_escape(const char *str)
 
 static void lex_scan_string(lex_t *lex, json_error_t *error)
 {
-    char c;
+    int c;
     const char *p;
     char *t;
     int i;
@@ -250,14 +295,15 @@ static void lex_scan_string(lex_t *lex, json_error_t *error)
     c = lex_get_save(lex, error);
 
     while(c != '"') {
-        if(c == (char)EOF) {
-            lex_unget_unsave(lex, c);
-            if(lex_eof(lex))
-                error_set(error, lex, "premature end of input");
+        if(c == STREAM_STATE_ERROR)
+            goto out;
+
+        else if(c == STREAM_STATE_EOF) {
+            error_set(error, lex, "premature end of input");
             goto out;
         }
 
-        else if((unsigned char)c <= 0x1F) {
+        else if(0 <= c && c <= 0x1F) {
             /* control character */
             lex_unget_unsave(lex, c);
             if(c == '\n')
@@ -273,7 +319,6 @@ static void lex_scan_string(lex_t *lex, json_error_t *error)
                 c = lex_get_save(lex, error);
                 for(i = 0; i < 4; i++) {
                     if(!isxdigit(c)) {
-                        lex_unget_unsave(lex, c);
                         error_set(error, lex, "invalid escape");
                         goto out;
                     }
@@ -284,7 +329,6 @@ static void lex_scan_string(lex_t *lex, json_error_t *error)
                     c == 'f' || c == 'n' || c == 'r' || c == 't')
                 c = lex_get_save(lex, error);
             else {
-                lex_unget_unsave(lex, c);
                 error_set(error, lex, "invalid escape");
                 goto out;
             }
@@ -399,7 +443,7 @@ out:
 #define json_strtoint     strtol
 #endif
 
-static int lex_scan_number(lex_t *lex, char c, json_error_t *error)
+static int lex_scan_number(lex_t *lex, int c, json_error_t *error)
 {
     const char *saved_text;
     char *end;
@@ -423,8 +467,8 @@ static int lex_scan_number(lex_t *lex, char c, json_error_t *error)
             c = lex_get_save(lex, error);
     }
     else {
-      lex_unget_unsave(lex, c);
-      goto out;
+        lex_unget_unsave(lex, c);
+        goto out;
     }
 
     if(c != '.' && c != 'E' && c != 'e') {
@@ -453,8 +497,10 @@ static int lex_scan_number(lex_t *lex, char c, json_error_t *error)
 
     if(c == '.') {
         c = lex_get(lex, error);
-        if(!isdigit(c))
+        if(!isdigit(c)) {
+            lex_unget(lex, c);
             goto out;
+        }
         lex_save(lex, c);
 
         c = lex_get_save(lex, error);
@@ -498,7 +544,7 @@ out:
 
 static int lex_scan(lex_t *lex, json_error_t *error)
 {
-    char c;
+    int c;
 
     strbuffer_clear(&lex->saved_text);
 
@@ -509,18 +555,15 @@ static int lex_scan(lex_t *lex, json_error_t *error)
 
     c = lex_get(lex, error);
     while(c == ' ' || c == '\t' || c == '\n' || c == '\r')
-    {
-        if(c == '\n')
-            lex->line++;
-
         c = lex_get(lex, error);
+
+    if(c == STREAM_STATE_EOF) {
+        lex->token = TOKEN_EOF;
+        goto out;
     }
 
-    if(c == (char)EOF) {
-        if(lex_eof(lex))
-            lex->token = TOKEN_EOF;
-        else
-            lex->token = TOKEN_INVALID;
+    if(c == STREAM_STATE_ERROR) {
+        lex->token = TOKEN_INVALID;
         goto out;
     }
 
@@ -580,15 +623,13 @@ static char *lex_steal_string(lex_t *lex)
     return result;
 }
 
-static int lex_init(lex_t *lex, get_func get, eof_func eof, void *data)
+static int lex_init(lex_t *lex, get_func get, void *data)
 {
-    stream_init(&lex->stream, get, eof, data);
+    stream_init(&lex->stream, get, data);
     if(strbuffer_init(&lex->saved_text))
         return -1;
 
     lex->token = TOKEN_INVALID;
-    lex->line = 1;
-
     return 0;
 }
 
@@ -791,16 +832,10 @@ static int string_get(void *data)
     else
     {
         stream->pos++;
-        return c;
+        return (unsigned char)c;
     }
 }
 
-static int string_eof(void *data)
-{
-    string_data_t *stream = (string_data_t *)data;
-    return (stream->data[stream->pos] == '\0');
-}
-
 json_t *json_loads(const char *string, size_t flags, json_error_t *error)
 {
     lex_t lex;
@@ -809,7 +844,7 @@ json_t *json_loads(const char *string, size_t flags, json_error_t *error)
 
     (void)flags; /* unused */
 
-    if(lex_init(&lex, string_get, string_eof, (void *)&stream_data))
+    if(lex_init(&lex, string_get, (void *)&stream_data))
         return NULL;
 
     jsonp_error_init(error, "<string>");
@@ -837,7 +872,7 @@ json_t *json_loadf(FILE *input, size_t flags, json_error_t *error)
     json_t *result;
     (void)flags; /* unused */
 
-    if(lex_init(&lex, (get_func)fgetc, (eof_func)feof, input))
+    if(lex_init(&lex, (get_func)fgetc, input))
         return NULL;
 
     if(input == stdin)