From 3f74dbfa39b849ed1ebf3c814f949b96bbeaab65 Mon Sep 17 00:00:00 2001
From: Stephan Soller <stephan.soller@helionweb.de>
Date: Sat, 11 Feb 2017 11:24:37 +0100
Subject: [PATCH] Implemented the basics for new printf() style argument
 parsing.

---
 .gitignore           |   3 +-
 Makefile             |   1 +
 slim_gl.h            | 330 +++++++++++++++++++++++++++++++++--------
 tests/slim_gl_test.c | 342 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 613 insertions(+), 63 deletions(-)
 create mode 100644 tests/slim_gl_test.c

diff --git a/.gitignore b/.gitignore
index 1b6eb75..eda4ca0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/Makefile b/Makefile
index f8754c6..446cd8c 100644
--- 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
diff --git a/slim_gl.h b/slim_gl.h
index 7cd4476..72d3402 100644
--- 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
index 0000000..d591506
--- /dev/null
+++ b/tests/slim_gl_test.c
@@ -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
-- 
2.20.1