Implemented the basics for new printf() style argument parsing.
authorStephan Soller <stephan.soller@helionweb.de>
Sat, 11 Feb 2017 10:24:37 +0000 (11:24 +0100)
committerStephan Soller <stephan.soller@helionweb.de>
Sat, 11 Feb 2017 10:31:05 +0000 (11:31 +0100)
.gitignore
Makefile
slim_gl.h
tests/slim_gl_test.c [new file with mode: 0644]

index 1b6eb75..eda4ca0 100644 (file)
@@ -1,3 +1,2 @@
 tests/slim_test_crashtest
-tests/math_3d_test
-tests/slim_hash_test
\ No newline at end of file
+tests/*_test
\ No newline at end of file
index f8754c6..446cd8c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@ tests/slim_test_crashtest.c: slim_test.h
 tests/math_3d_test: math_3d.h slim_test.h
 tests/math_3d_test: LDLIBS += -lm
 tests/slim_hash_test: slim_hash.h slim_test.h
+tests/slim_gl_test: LDLIBS += -lGL
 
 
 # Clean all files in the .gitignore list, ensures that the ignore file
index 7cd4476..72d3402 100644 (file)
--- a/slim_gl.h
+++ b/slim_gl.h
@@ -81,6 +81,8 @@ License: MIT License
 // OpenGL program functions
 //
 
+GLuint sgl_program_new(const char* args, ...);
+
 /**
  * Compiles a vertex and fragment shader from two files, links them into an OpenGL program reports compiler errors on failure.
  * 
@@ -655,6 +657,212 @@ static const char* sgl__type_to_string(GLenum type);
 static GLuint sgl__create_and_compile_program(const char* vertex_shader_code, const char* fragment_shader_code, const char* vertex_shader_name, const char* fragment_shader_name, char** compiler_errors);
 static GLuint sgl__create_and_compile_shader(GLenum shader_type, const char* code, const char* filename_for_errors, char** compiler_errors);
 
+
+typedef struct {
+       const char* error_at;
+       const char* error_message;
+       // We need a zero terminated string for glGetAttribLocation() so use a buffer instead of pointer + length into the
+       // argument string.
+       char name[128];
+       char modifiers[16];
+       char type;
+} sgl_arg_t, *sgl_arg_p;
+
+/**
+ * Returns true if a character is a whitespace as defined by the GLSL spec (3.1 Character Set).
+ * That are spaces and the ASCII range containing horizontal tab, new line, vertical tab, form feed and carriage return.
+ * If you look at the ASCII table (man ascii) you'll see that all whitespaces except space are consecutive ASCII codes.
+ * So we cover all these characters with one range check.
+ */
+static inline int sgl__is_whitespace(char c) {
+       return c == ' ' || (c >= '\t' && c <= '\r');
+}
+
+/**
+ * Returns true if a character is valid for an GLSL identifier (spec chapter 3.7 Identifiers) or a minus sign (-).
+ * Names starting with a minus are internal options (e.g. -index or -padding) instead of attribute or uniform names.
+ */
+static inline int sgl__is_name(char c) {
+       return (c >= 'a' && c <= 'z') || c == '_' || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-';
+}
+
+#define SGL__NAMED_ARGS        (1 << 0)
+#define SGL__BUFFER_DIRECTIVES (1 << 1)
+
+static const char* sgl__next_argument(const char* string, int flags, sgl_arg_p arg) {
+       const char* c = string;
+       
+#define EXIT_WITH_ERROR_IF(condition, message) if(condition){ arg->error_at = c; arg->error_message = (message); return NULL; }
+       
+       // A return value of NULL signals the end of arguments or an error. Return NULL when we're called with a NULL string
+       // so we can be called repeatedly at the end. It also makes sure we can dereference the string after this.
+       if (c == NULL)
+               return NULL;
+       
+       // Skip whitespaces
+       while( sgl__is_whitespace(*c) )
+               c++;
+       
+       // Return NULL when we're at the zero terminator to signal the end of arguments
+       if (*c == '\0')
+               return NULL;
+       
+       if (flags & SGL__BUFFER_DIRECTIVES) {
+               if (*c == ';') {
+                       // Got a buffer end directive
+                       arg->type = *c++;
+                       return c;
+               }
+       }
+       
+       if (flags & SGL__NAMED_ARGS) {
+               // Read the name into the name buffer and add the zero-terminator
+               size_t name_len = 0;
+               while ( sgl__is_name(*c) ) {
+                       // We can only fill the name buffer up until one byte it left. We need that byte for the zero terminator.
+                       EXIT_WITH_ERROR_IF(name_len >= sizeof(arg->name) - 1, "Name is to long");
+                       arg->name[name_len++] = *c++;
+               }
+               arg->name[name_len] = '\0';
+               // If the name isn't followed by a whitespace char we either got an invalid char in a name or the zero terminator.
+               // Either way we can't continue.
+               EXIT_WITH_ERROR_IF(!sgl__is_whitespace(*c), "Got invalid character in name");
+               
+               // Skip whitespaces after name
+               while( sgl__is_whitespace(*c) )
+                       c++;
+       }
+       
+       EXIT_WITH_ERROR_IF(*c != '%', "Expected at '%' at the start of a directive");
+       c++;
+       
+       // Read all following chars as modifiers (including the last one for now)
+       size_t mod_len = 0;
+       while ( !(sgl__is_whitespace(*c) || *c == '\0') ) {
+               EXIT_WITH_ERROR_IF(mod_len >= sizeof(arg->modifiers), "To many modifiers for directive");
+               arg->modifiers[mod_len++] = *c++;
+       }
+       EXIT_WITH_ERROR_IF(mod_len < 1, "At least one character for the type is necessary after a '%'");
+       
+       // The last char in the modifiers buffer is our type so put it into the type field. Then overwrite the last char
+       // with the zero terminator.
+       arg->type = arg->modifiers[mod_len-1];
+       arg->modifiers[mod_len-1] = '\0';
+       
+       return c;
+       
+#undef EXIT_WITH_ERROR_IF
+}
+
+/*
+static const char* sgl__parse_name(const char* string, sgl_arg_p option) {
+       const char* c = string;
+       if (c == NULL || *c == '\0')
+               return NULL;
+       
+       // Skip whitespaces
+       while( sgl__is_whitespace(*c) )
+               c++;
+       
+       // Read the name into the name buffer and add the zero-terminator
+       size_t offset = 0;
+       while ( sgl__is_name(*c) ) {
+               if (offset >= sizeof(option->name) - 1) {
+                       option->error_at = c;
+                       option->error_message = "Name is to long";
+                       return NULL;
+               }
+               option->name[offset++] = *c++;
+       }
+       option->name[offset] = '\0';
+       
+       // Only return the end position if we actually succeeded in reading a name. That is:
+       // - We read more than 0 characters and
+       // - the current end pos points to a whitespace or zero terminator
+       // Otherwise we're stuck inside or at the start of a name and got an invalid character.
+       if ( offset > 0 && (sgl__is_whitespace(*c) || *c == '\0') )
+               return c;
+       else {
+               option->error_at = c;
+               option->error_message = "Got invalid character in name";
+               return NULL;
+       }
+}
+
+static const char* sgl__parse_directive(const char* string, sgl_arg_p option) {
+       const char* c = string;
+       if (c == NULL)
+               return NULL;
+       
+       // Skip whitespaces
+       while( sgl__is_whitespace(*c) )
+               c++;
+       
+       if (*c == ';') {
+               option->type = *c++;
+               return c;
+       } else if (*c == '%') {
+               c++;
+               // Read all following chars as modifiers (including the last one for now)
+               size_t offset = 0;
+               while ( !(sgl__is_whitespace(*c) || *c == '\0') ) {
+                       if (offset >= sizeof(option->modifiers)) {
+                               option->error_at = c;
+                               option->error_message = "To many modifiers for directive";
+                               return NULL;
+                       }
+                       option->modifiers[offset++] = *c++;
+               }
+               
+               if (offset < 1) {
+                       option->error_at = c;
+                       option->error_message = "At least one character is necessary after a '%'";
+                       return NULL;
+               }
+               
+               // The last char in the modifiers buffer is our type so put it into the type field. Then overwrite the last char
+               // with the zero terminator.
+               option->type = option->modifiers[offset-1];
+               option->modifiers[offset-1] = '\0';
+               
+               return c;
+       } else {
+               option->error_at = c;
+               option->error_message = "Expected '%' at start of directive or a ';' directive";
+               return NULL;
+       }
+}
+*/
+
+
+/*
+GLuint sgl_program_new(const char* args, ...) {
+       directive_t directive;
+       
+       while ( (args = next_directive(args, &directive)) != NULL ) {
+               
+       }
+       
+       char *c = args, *start = NULL, *end = NULL;
+       while(*c != '\0') {
+               switch(*c) {
+                       case '%':
+                               c++;
+                               start = c;
+                               while()
+                               break;
+                       case ' ': case '\f': case '\n':  case '\r': case '\t': case '\v':
+                               // Ignore whitespaces
+                               break;
+                       default:
+                               // error: unknown character in args parameter
+                               break;
+               }
+               c++;
+       }
+}
+*/
+
 GLuint sgl_program_from_files(const char* vertex_shader_file, const char* fragment_shader_file, char** compiler_errors) {
        char* vertex_shader_code = sgl_fload(vertex_shader_file, NULL);
        if (vertex_shader_code == NULL) {
@@ -736,69 +944,69 @@ void sgl_program_inspect(GLuint program) {
  */
 static const char* sgl__type_to_string(GLenum type) {
        switch(type){
-               case GL_FLOAT: return "float";
-               case GL_FLOAT_VEC2: return "vec2";
-               case GL_FLOAT_VEC3: return "vec3";
-               case GL_FLOAT_VEC4: return "vec4";
-               case GL_INT: return "int";
-               case GL_INT_VEC2: return "ivec2";
-               case GL_INT_VEC3: return "ivec3";
-               case GL_INT_VEC4: return "ivec4";
-               case GL_UNSIGNED_INT: return "unsigned int";
-               case GL_UNSIGNED_INT_VEC2: return "uvec2";
-               case GL_UNSIGNED_INT_VEC3: return "uvec3";
-               case GL_UNSIGNED_INT_VEC4: return "uvec4";
-               case GL_BOOL: return "bool";
-               case GL_BOOL_VEC2: return "bvec2";
-               case GL_BOOL_VEC3: return "bvec3";
-               case GL_BOOL_VEC4: return "bvec4";
-               case GL_FLOAT_MAT2: return "mat2";
-               case GL_FLOAT_MAT3: return "mat3";
-               case GL_FLOAT_MAT4: return "mat4";
-               case GL_FLOAT_MAT2x3: return "mat2x3";
-               case GL_FLOAT_MAT2x4: return "mat2x4";
-               case GL_FLOAT_MAT3x2: return "mat3x2";
-               case GL_FLOAT_MAT3x4: return "mat3x4";
-               case GL_FLOAT_MAT4x2: return "mat4x2";
-               case GL_FLOAT_MAT4x3: return "mat4x3";
-               case GL_SAMPLER_1D: return "sampler1D";
-               case GL_SAMPLER_2D: return "sampler2D";
-               case GL_SAMPLER_3D: return "sampler3D";
-               case GL_SAMPLER_CUBE: return "samplerCube";
-               case GL_SAMPLER_1D_SHADOW: return "sampler1DShadow";
-               case GL_SAMPLER_2D_SHADOW: return "sampler2DShadow";
-               case GL_SAMPLER_1D_ARRAY: return "sampler1DArray";
-               case GL_SAMPLER_2D_ARRAY: return "sampler2DArray";
-               case GL_SAMPLER_1D_ARRAY_SHADOW: return "sampler1DArrayShadow";
-               case GL_SAMPLER_2D_ARRAY_SHADOW: return "sampler2DArrayShadow";
-               case GL_SAMPLER_CUBE_SHADOW: return "samplerCubeShadow";
-               case GL_SAMPLER_BUFFER: return "samplerBuffer";
-               case GL_SAMPLER_2D_RECT: return "sampler2DRect";
-               case GL_SAMPLER_2D_RECT_SHADOW: return "sampler2DRectShadow";
-               case GL_INT_SAMPLER_1D: return "isampler1D";
-               case GL_INT_SAMPLER_2D: return "isampler2D";
-               case GL_INT_SAMPLER_3D: return "isampler3D";
-               case GL_INT_SAMPLER_CUBE: return "isamplerCube";
-               case GL_INT_SAMPLER_1D_ARRAY: return "isampler1DArray";
-               case GL_INT_SAMPLER_2D_ARRAY: return "isampler2DArray";
-               case GL_INT_SAMPLER_BUFFER: return "isamplerBuffer";
-               case GL_INT_SAMPLER_2D_RECT: return "isampler2DRect";
-               case GL_UNSIGNED_INT_SAMPLER_1D: return "usampler1D";
-               case GL_UNSIGNED_INT_SAMPLER_2D: return "usampler2D";
-               case GL_UNSIGNED_INT_SAMPLER_3D: return "usampler3D";
-               case GL_UNSIGNED_INT_SAMPLER_CUBE: return "usamplerCube";
-               case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return "usampler2DArray";
-               case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return "usampler2DArray";
-               case GL_UNSIGNED_INT_SAMPLER_BUFFER: return "usamplerBuffer";
-               case GL_UNSIGNED_INT_SAMPLER_2D_RECT: return "usampler2DRect";
+               case GL_FLOAT: return "float";
+               case GL_FLOAT_VEC2: return "vec2";
+               case GL_FLOAT_VEC3: return "vec3";
+               case GL_FLOAT_VEC4: return "vec4";
+               case GL_INT: return "int";
+               case GL_INT_VEC2: return "ivec2";
+               case GL_INT_VEC3: return "ivec3";
+               case GL_INT_VEC4: return "ivec4";
+               case GL_UNSIGNED_INT: return "unsigned int";
+               case GL_UNSIGNED_INT_VEC2: return "uvec2";
+               case GL_UNSIGNED_INT_VEC3: return "uvec3";
+               case GL_UNSIGNED_INT_VEC4: return "uvec4";
+               case GL_BOOL: return "bool";
+               case GL_BOOL_VEC2: return "bvec2";
+               case GL_BOOL_VEC3: return "bvec3";
+               case GL_BOOL_VEC4: return "bvec4";
+               case GL_FLOAT_MAT2: return "mat2";
+               case GL_FLOAT_MAT3: return "mat3";
+               case GL_FLOAT_MAT4: return "mat4";
+               case GL_FLOAT_MAT2x3: return "mat2x3";
+               case GL_FLOAT_MAT2x4: return "mat2x4";
+               case GL_FLOAT_MAT3x2: return "mat3x2";
+               case GL_FLOAT_MAT3x4: return "mat3x4";
+               case GL_FLOAT_MAT4x2: return "mat4x2";
+               case GL_FLOAT_MAT4x3: return "mat4x3";
+               case GL_SAMPLER_1D: return "sampler1D";
+               case GL_SAMPLER_2D: return "sampler2D";
+               case GL_SAMPLER_3D: return "sampler3D";
+               case GL_SAMPLER_CUBE: return "samplerCube";
+               case GL_SAMPLER_1D_SHADOW: return "sampler1DShadow";
+               case GL_SAMPLER_2D_SHADOW: return "sampler2DShadow";
+               case GL_SAMPLER_1D_ARRAY: return "sampler1DArray";
+               case GL_SAMPLER_2D_ARRAY: return "sampler2DArray";
+               case GL_SAMPLER_1D_ARRAY_SHADOW: return "sampler1DArrayShadow";
+               case GL_SAMPLER_2D_ARRAY_SHADOW: return "sampler2DArrayShadow";
+               case GL_SAMPLER_CUBE_SHADOW: return "samplerCubeShadow";
+               case GL_SAMPLER_BUFFER: return "samplerBuffer";
+               case GL_SAMPLER_2D_RECT: return "sampler2DRect";
+               case GL_SAMPLER_2D_RECT_SHADOW: return "sampler2DRectShadow";
+               case GL_INT_SAMPLER_1D: return "isampler1D";
+               case GL_INT_SAMPLER_2D: return "isampler2D";
+               case GL_INT_SAMPLER_3D: return "isampler3D";
+               case GL_INT_SAMPLER_CUBE: return "isamplerCube";
+               case GL_INT_SAMPLER_1D_ARRAY: return "isampler1DArray";
+               case GL_INT_SAMPLER_2D_ARRAY: return "isampler2DArray";
+               case GL_INT_SAMPLER_BUFFER: return "isamplerBuffer";
+               case GL_INT_SAMPLER_2D_RECT: return "isampler2DRect";
+               case GL_UNSIGNED_INT_SAMPLER_1D: return "usampler1D";
+               case GL_UNSIGNED_INT_SAMPLER_2D: return "usampler2D";
+               case GL_UNSIGNED_INT_SAMPLER_3D: return "usampler3D";
+               case GL_UNSIGNED_INT_SAMPLER_CUBE: return "usamplerCube";
+               case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return "usampler2DArray";
+               case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return "usampler2DArray";
+               case GL_UNSIGNED_INT_SAMPLER_BUFFER: return "usamplerBuffer";
+               case GL_UNSIGNED_INT_SAMPLER_2D_RECT: return "usampler2DRect";
                
 #              ifdef GL_VERSION_3_2
-                       case GL_SAMPLER_2D_MULTISAMPLE: return "sampler2DMS";
-                       case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return "sampler2DMSArray";
-                       case GL_INT_SAMPLER_2D_MULTISAMPLE: return "isampler2DMS";
-                       case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "isampler2DMSArray";
-                       case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return "usampler2DMS";
-                       case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "usampler2DMSArray";
+                       case GL_SAMPLER_2D_MULTISAMPLE: return "sampler2DMS";
+                       case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return "sampler2DMSArray";
+                       case GL_INT_SAMPLER_2D_MULTISAMPLE: return "isampler2DMS";
+                       case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "isampler2DMSArray";
+                       case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return "usampler2DMS";
+                       case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "usampler2DMSArray";
 #              endif
                
                default: return "unknown";
diff --git a/tests/slim_gl_test.c b/tests/slim_gl_test.c
new file mode 100644 (file)
index 0000000..d591506
--- /dev/null
@@ -0,0 +1,342 @@
+#include "../../slim_gl/demos/gl_3_1_core.h"
+
+#define SLIM_GL_IMPLEMENTATION
+#include "../slim_gl.h"
+#define SLIM_TEST_IMPLEMENTATION
+#include "../slim_test.h"
+
+
+void test_next_argument_basics() {
+       const char* sample;
+       const char* next;
+       sgl_arg_t arg = { 0 };
+       
+       sample = "";
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_null(arg.error_message);
+       
+       sample = "  \t  \n  \v  \f  \r  ";
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_null(arg.error_message);
+       
+       sample = NULL;
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_null(arg.error_message);
+       
+       sample = ";";
+       next = sgl__next_argument(sample, SGL__BUFFER_DIRECTIVES, &arg);
+       st_check_not_null(next);
+       st_check_int(arg.type, ';');
+       next = sgl__next_argument(next, SGL__BUFFER_DIRECTIVES, &arg);
+       st_check_null(next);
+       st_check_null(arg.error_message);
+       
+       sample = "foo %4f";
+       next = sgl__next_argument(sample, SGL__NAMED_ARGS, &arg);
+       st_check_not_null(next);
+       st_check_str(arg.name, "foo");
+       st_check_int(arg.type, 'f');
+       st_check_str(arg.modifiers, "4");
+       
+       sample = "waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay_to_long %4f";
+       next = sgl__next_argument(sample, SGL__NAMED_ARGS, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+       
+       sample = "foo+bar %4f";
+       next = sgl__next_argument(sample, SGL__NAMED_ARGS, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+       
+       sample = "foo";
+       next = sgl__next_argument(sample, SGL__NAMED_ARGS, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+       
+       sample = "x";
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+       
+       sample = "%";
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+       
+       sample = "%xxxxxxxxxxxxxxxxf";
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+}
+
+void test_next_argument_for_program_new() {
+       // sgl_program_new() only uses directives, named arguments or buffer directives result in errors
+       
+       const char* sample;
+       const char* next;
+       sgl_arg_t arg = { 0 };
+       
+       sample = "%G %fV %fF";
+       next = sample;
+       
+       next = sgl__next_argument(next, 0, &arg);
+       st_check_not_null(next);
+       st_check_null(arg.error_message);
+       st_check_int(arg.type, 'G');
+       st_check_str(arg.modifiers, "");
+       
+       next = sgl__next_argument(next, 0, &arg);
+       st_check_not_null(next);
+       st_check_null(arg.error_message);
+       st_check_int(arg.type, 'V');
+       st_check_str(arg.modifiers, "f");
+       
+       next = sgl__next_argument(next, 0, &arg);
+       st_check_not_null(next);
+       st_check_null(arg.error_message);
+       st_check_int(arg.type, 'F');
+       st_check_str(arg.modifiers, "f");
+       
+       next = sgl__next_argument(next, 0, &arg);
+       st_check_null(next);
+       
+       // Named arguments should give an error
+       sample = "foo %4f";
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+       
+       // End of buffer directive should give an error
+       sample = ";";
+       next = sgl__next_argument(sample, 0, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+}
+
+void test_next_argument_for_vao_new() {
+       // sgl_vao_new() uses named arguments as well as the end of buffer directive
+       
+       const char* sample;
+       const char* next;
+       sgl_arg_t arg = { 0 };
+       
+       sample = "pos %3f ; color %4unb";
+       next = sgl__next_argument(sample, SGL__NAMED_ARGS | SGL__BUFFER_DIRECTIVES, &arg);
+       st_check_not_null(next);
+       st_check_str(arg.name, "pos");
+       st_check_int(arg.type, 'f');
+       st_check_str(arg.modifiers, "3");
+       
+       next = sgl__next_argument(next, SGL__NAMED_ARGS | SGL__BUFFER_DIRECTIVES, &arg);
+       st_check_not_null(next);
+       st_check_int(arg.type, ';');
+       
+       next = sgl__next_argument(next, SGL__NAMED_ARGS | SGL__BUFFER_DIRECTIVES, &arg);
+       st_check_not_null(next);
+       st_check_str(arg.name, "color");
+       st_check_int(arg.type, 'b');
+       st_check_str(arg.modifiers, "4un");
+       
+       next = sgl__next_argument(next, SGL__NAMED_ARGS | SGL__BUFFER_DIRECTIVES, &arg);
+       st_check_null(next);
+       st_check_null(arg.error_message);
+}
+
+void test_next_argument_for_draw() {
+       // sgl_draw() only uses named directives for the uniforms, end of buffer directives result in errors
+       
+       const char* sample;
+       const char* next;
+       sgl_arg_t arg = { 0 };
+       
+       sample = "proj %4x4tm light_pos %3f";
+       next = sgl__next_argument(sample, SGL__NAMED_ARGS, &arg);
+       st_check_not_null(next);
+       st_check_str(arg.name, "proj");
+       st_check_int(arg.type, 'm');
+       st_check_str(arg.modifiers, "4x4t");
+       
+       next = sgl__next_argument(next, SGL__NAMED_ARGS, &arg);
+       st_check_not_null(next);
+       st_check_str(arg.name, "light_pos");
+       st_check_int(arg.type, 'f');
+       st_check_str(arg.modifiers, "3");
+       
+       next = sgl__next_argument(next, SGL__NAMED_ARGS, &arg);
+       st_check_null(next);
+       st_check_null(arg.error_message);
+       
+       // End of buffer directive should give an error
+       sample = ";";
+       next = sgl__next_argument(sample, SGL__NAMED_ARGS, &arg);
+       st_check_null(next);
+       st_check_not_null(arg.error_message);
+       printf("error: %s\n%s\n%*s^\n", arg.error_message, sample, (int)(arg.error_at - sample), "");
+}
+
+
+/*
+void test_parse_name() {
+       const char* sample = "foo batz bar";
+       sgl_option_t option;
+       
+       const char* next = sgl__parse_name(sample, &option);
+       st_check_str(option.name, "foo");
+       st_check_int(option.name[3], '\0');
+       st_check_str(next, " batz bar");
+       
+       next = sgl__parse_name(next, &option);
+       st_check_str(option.name, "batz");
+       st_check_int(option.name[4], '\0');
+       st_check_str(next, " bar");
+       
+       next = sgl__parse_name(next, &option);
+       st_check_str(option.name, "bar");
+       st_check_int(option.name[3], '\0');
+       st_check_str(next, "");
+       
+       next = sgl__parse_name(next, &option);
+       st_check_null(next);
+}
+
+void test_parse_directive() {
+       const char* sample = "%4f %unb %4x4tm %*rt";
+       sgl_option_t option;
+       
+       const char* next = sgl__parse_directive(sample, &option);
+       st_check_str(option.modifiers, "4");
+       st_check_int(option.modifiers[1], '\0');
+       st_check_int(option.type, 'f');
+       st_check_str(next, " %unb %4x4tm %*rt");
+       
+       next = sgl__parse_directive(next, &option);
+       st_check_str(option.modifiers, "un");
+       st_check_int(option.modifiers[2], '\0');
+       st_check_int(option.type, 'b');
+       st_check_str(next, " %4x4tm %*rt");
+       
+       next = sgl__parse_directive(next, &option);
+       st_check_str(option.modifiers, "4x4t");
+       st_check_int(option.modifiers[4], '\0');
+       st_check_int(option.type, 'm');
+       st_check_str(next, " %*rt");
+       
+       next = sgl__parse_directive(next, &option);
+       st_check_str(option.modifiers, "*r");
+       st_check_int(option.modifiers[2], '\0');
+       st_check_int(option.type, 't');
+       st_check_str(next, "");
+       
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+}
+
+void test_parse_name_and_directive() {
+       const char* sample = "pos %4f color %unb -index %s";
+       const char* next = sample;
+       sgl_option_t option;
+       
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_str(option.name, "pos");
+       st_check_int(option.type, 'f');
+       st_check_str(option.modifiers, "4");
+       st_check_str(next, " color %unb -index %s");
+       
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_str(option.name, "color");
+       st_check_int(option.type, 'b');
+       st_check_str(option.modifiers, "un");
+       st_check_str(next, " -index %s");
+       
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_str(option.name, "-index");
+       st_check_int(option.type, 's');
+       st_check_str(option.modifiers, "");
+       st_check_str(next, "");
+       
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+}
+
+void test_parse_name_and_directive_errors() {
+       sgl_option_t option = { 0 };
+       
+       const char* sample = "";
+       const char* next = sample;
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+       
+       sample = "waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay_to_long %4f";
+       next = sample;
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+       st_check_not_null(option.error_message);
+       printf("error: %s\n%s\n%*s^\n", option.error_message, sample, (int)(option.error_at - sample), "");
+       
+       sample = "foo+bar %4f";
+       next = sample;
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+       st_check_not_null(option.error_message);
+       printf("error: %s\n%s\n%*s^\n", option.error_message, sample, (int)(option.error_at - sample), "");
+       
+       sample = "foo %";
+       next = sample;
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+       st_check_not_null(option.error_message);
+       printf("error: %s\n%s\n%*s^\n", option.error_message, sample, (int)(option.error_at - sample), "");
+       
+       sample = "foo %to_looooooooooooooooongf";
+       next = sample;
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+       st_check_not_null(option.error_message);
+       printf("error: %s\n%s\n%*s^\n", option.error_message, sample, (int)(option.error_at - sample), "");
+       
+       sample = "foo bar";
+       next = sample;
+       next = sgl__parse_name(next, &option);
+       next = sgl__parse_directive(next, &option);
+       st_check_null(next);
+       st_check_not_null(option.error_message);
+       printf("error: %s\n%s\n%*s^\n", option.error_message, sample, (int)(option.error_at - sample), "");
+}
+*/
+
+
+int main() {
+       /*
+       st_run(test_parse_name);
+       st_run(test_parse_directive);
+       st_run(test_parse_name_and_directive);
+       st_run(test_parse_name_and_directive_errors);
+       */
+       
+       st_run(test_next_argument_basics);
+       st_run(test_next_argument_for_program_new);
+       st_run(test_next_argument_for_vao_new);
+       st_run(test_next_argument_for_draw);
+       
+       return st_show_report();
+}
\ No newline at end of file