Browse Source

replace simple working forward tokenizer

by broken and not working callback-based thing for no good reason
Ivan Avdeev 6 years ago
parent
commit
2a17e64e1b
7 changed files with 395 additions and 287 deletions
  1. 164 108
      src/bsp.c
  2. 4 4
      src/bsp.h
  3. 3 0
      src/common.h
  4. 133 138
      src/material.c
  5. 1 1
      src/render.c
  6. 61 22
      src/vmfparser.c
  7. 29 14
      src/vmfparser.h

+ 164 - 108
src/bsp.c

@@ -764,70 +764,97 @@ static void bspLoadSkybox(StringView name, ICollection *coll, Stack *tmp, struct
 	}
 }
 
-struct EntityProp {
+typedef struct {
 	const char *name;
-	const char *value;
-	int value_length;
-};
+	StringView value;
+} EntityProp;
+
+typedef struct {
+	EntityProp *props;
+	int props_count;
+	int prop_to_read;
+} EntityPropParser;
+
+static ParserCallbackResult bspReadEntityPropsReadValue(ParserState *state, StringView s);
+
+static ParserCallbackResult bspReadEntityPropsFinalize(ParserState *state, StringView s) {
+	(void)s;
+	EntityPropParser *epp = state->user_data;
 
-enum BSPLoadResult bspReadEntityProps(struct TokenContext *tctx, struct EntityProp* props, int prop_count) {
-	// TODO: what if this entity for some reason has nested entities
-	// should count curlies then
-	for (;;) {
-		const enum TokenType type = getNextToken(tctx);
-		switch (type) {
-		case Token_String:
-			/*if (!prop_count)
-				PRINTF("%.*s", tctx->str_length, tctx->str_start);*/
-			for (int i = 0; i < prop_count; ++i) {
-				if (strncmp(props[i].name, tctx->str_start, tctx->str_length) == 0) {
-					const enum TokenType type = getNextToken(tctx);
-					if (type == Token_Error)
-						return BSPLoadResult_ErrorFileFormat;
-
-					if (type != Token_String) {
-						PRINTF("Warning: expected string, got %d", type);
-						continue;
-					}
-
-					props[i].value = tctx->str_start;
-					props[i].value_length = tctx->str_length;
-					break;
-				} // if prop match found
-			} // for all props
+	for (int i = 0; i < epp->props_count; ++i) {
+		if (epp->props[i].value.length == 0) {
+			return Parser_Error;
+		}
+	}
+
+	return Parser_Exit;
+}
+
+static ParserCallbackResult bspReadEntityPropsReadKey(ParserState *state, StringView s) {
+	EntityPropParser *epp = state->user_data;
+
+	epp->prop_to_read = -1;
+	for (int i = 0; i < epp->props_count; ++i) {
+		if (strncmp(epp->props[i].name, s.str, s.length) == 0) {
+			epp->prop_to_read = i;
 			break;
-		case Token_CurlyClose:
-			/*
-			for (int i = 0; i < prop_count; ++i) {
-				PRINTF("prop[%i] '%s' = '%.*s'", i,
-					props[i].name, props[i].value_length, props[i].value);
-			}
-			*/
-			return BSPLoadResult_Success;
-		default:
-			PRINTF("Illegal token: %d", type);
-			return BSPLoadResult_ErrorFileFormat;
 		}
-	} // forever
+	}
+
+	state->callbacks.curlyClose = parserError;
+	state->callbacks.string = bspReadEntityPropsReadValue;
+
+	return Parser_Continue;
+}
+
+static ParserCallbackResult bspReadEntityPropsReadValue(ParserState *state, StringView s) {
+	EntityPropParser *epp = state->user_data;
+
+	if (epp->prop_to_read >= 0)
+		epp->props[epp->prop_to_read].value = s;
+
+	state->callbacks.curlyClose = bspReadEntityPropsFinalize;
+	state->callbacks.string = bspReadEntityPropsReadKey;
+
+	return Parser_Continue;
+}
+
+static BSPLoadResult bspReadEntityProps(StringView source, EntityProp *props, int count) {
+	EntityPropParser epp = {
+		.props = props,
+		.props_count = count,
+		.prop_to_read = -1
+	};
+
+	ParserState parser = {
+		.user_data = &epp,
+		.callbacks = {
+			.curlyOpen = parserError,
+			.curlyClose = bspReadEntityPropsFinalize,
+			.string = bspReadEntityPropsReadKey
+		}
+	};
+
+	for (int i = 0; i < epp.props_count; ++i) {
+		epp.props[i].value.length = 0;
+		epp.props[i].value.str = NULL;
+	}
+
+	return ParseResult_Success == parserParse(&parser, source)
+		? BSPLoadResult_Success : BSPLoadResult_ErrorFileFormat;
 }
 
-enum BSPLoadResult bspReadEntityInfoLandmark(struct BSPLoadModelContext *ctx, struct TokenContext* tctx) {
-	struct EntityProp props[] = {
-		{"targetname", NULL, 0},
-		{"origin", NULL, 0}
+enum BSPLoadResult bspReadEntityInfoLandmark(struct BSPLoadModelContext *ctx, StringView src) {
+	EntityProp props[] = {
+		{"targetname", {NULL, 0}},
+		{"origin", {NULL, 0}}
 	};
 
-	const enum BSPLoadResult result = bspReadEntityProps(tctx, props, COUNTOF(props));
+	const enum BSPLoadResult result = bspReadEntityProps(src, props, COUNTOF(props));
 
 	if (result != BSPLoadResult_Success)
 		return result;
 
-	for (int i = 0; i < (int)COUNTOF(props); ++i)
-		if (props[i].value == NULL || props[i].value_length == 0) {
-			PRINTF("Property %s is empty, skipping this landmark", props[i].name);
-			return BSPLoadResult_Success;
-		}
-
 	struct BSPModel *model = ctx->model;
 	if (model->landmarks_count == BSP_MAX_LANDMARKS) {
 		PRINT("Too many landmarks");
@@ -835,23 +862,23 @@ enum BSPLoadResult bspReadEntityInfoLandmark(struct BSPLoadModelContext *ctx, st
 	}
 
 	struct BSPLandmark *landmark = model->landmarks + model->landmarks_count;
-	if (props[0].value_length >= (int)sizeof(landmark->name)) {
+	if (props[0].value.length >= (int)sizeof(landmark->name)) {
 		PRINTF("Landmark name \"%.*s\" is too long",
-			props[0].value_length, props[0].value);
+			PRI_SVV(props[0].value));
 		return BSPLoadResult_ErrorMemory;
 	}
 
-	memcpy(landmark->name, props[0].value, props[0].value_length);
-	landmark->name[props[0].value_length] = '\0';
+	memcpy(landmark->name, props[0].value.str, props[0].value.length);
+	landmark->name[props[0].value.length] = '\0';
 
 	// FIXME props[1].value is not null-terminated suman
-	if (3 != sscanf(props[1].value, "%f %f %f",
+	if (3 != sscanf(props[1].value.str, "%f %f %f",
 			&landmark->origin.x,
 			&landmark->origin.y,
 			&landmark->origin.z))
 	{
 		PRINTF("Cannot read x, y, z from origin=\"%.*s\"",
-			props[1].value_length, props[1].value);
+			PRI_SVV(props[1].value));
 		return BSPLoadResult_ErrorFileFormat;
 	}
 
@@ -860,89 +887,118 @@ enum BSPLoadResult bspReadEntityInfoLandmark(struct BSPLoadModelContext *ctx, st
 	return BSPLoadResult_Success;
 }
 
-enum BSPLoadResult bspReadEntityTriggerChangelevel(struct BSPLoadModelContext *ctx, struct TokenContext* tctx) {
+enum BSPLoadResult bspReadEntityTriggerChangelevel(struct BSPLoadModelContext *ctx, StringView src) {
 	(void)ctx;
-	struct EntityProp props[] = {
-		{"landmark", NULL, 0},
-		{"map", NULL, 0}
+	EntityProp props[] = {
+		{"landmark", {NULL, 0}},
+		{"map", {NULL, 0}}
 	};
 
-	const enum BSPLoadResult result = bspReadEntityProps(tctx, props, COUNTOF(props));
+	const enum BSPLoadResult result = bspReadEntityProps(src, props, COUNTOF(props));
 
 	if (result != BSPLoadResult_Success)
 		return result;
 
-	openSourceAddMap(props[1].value, props[1].value_length);
+	openSourceAddMap(props[1].value.str, props[1].value.length);
 
 	return BSPLoadResult_Success;
 }
 
-enum BSPLoadResult bspReadEntityWorldspawn(struct BSPLoadModelContext *ctx, struct TokenContext* tctx) {
+enum BSPLoadResult bspReadEntityWorldspawn(struct BSPLoadModelContext *ctx, StringView src) {
 	(void)ctx;
-	struct EntityProp props[] = {
-		{"skyname", NULL, 0},
+	EntityProp props[] = {
+		{"skyname", {NULL, 0}},
 	};
 
-	const enum BSPLoadResult result = bspReadEntityProps(tctx, props, COUNTOF(props));
+	const enum BSPLoadResult result = bspReadEntityProps(src, props, COUNTOF(props));
 
 	if (result != BSPLoadResult_Success)
 		return result;
 
-	if (props[0].value_length > 0) {
-		const StringView sky = { props[0].value, props[0].value_length };
+	if (props[0].value.length > 0) {
+		const StringView sky = { props[0].value.str, props[0].value.length };
 		bspLoadSkybox(sky, ctx->collection, ctx->persistent, ctx->model);
 	}
 
 	return BSPLoadResult_Success;
 }
 
-enum BSPLoadResult bspReadEntityAndDumpProps(struct BSPLoadModelContext *ctx, struct TokenContext* tctx) {
+/*
+enum BSPLoadResult bspReadEntityAndDumpProps(struct BSPLoadModelContext *ctx, StringView src) {
 	(void)ctx;
-	return bspReadEntityProps(tctx, NULL, 0);
+	return bspReadEntityProps(src, NULL, 0);
+}
+*/
+
+typedef struct {
+	BSPLoadModelContext *ctx;
+	const char *entity_begin;
+	const char *end;
+} EntityReadContext;
+
+static ParserCallbackResult entitySearchBegin(ParserState *state, StringView s);
+static ParserCallbackResult entitySearchSkip(ParserState *state, StringView s) {
+	(void)s;
+
+	state->callbacks.curlyOpen = entitySearchBegin;
+	state->callbacks.curlyClose = parserError;
+	state->callbacks.string = parserError;
+
+	return Parser_Continue;
+}
+
+static ParserCallbackResult entitySearchReadString(ParserState *state, StringView s) {
+	EntityReadContext *ctx = state->user_data;
+
+#define LOAD_ENTITY(name, func) \
+	if (strncmp(name, s.str, s.length) == 0) { \
+		const StringView src = { .str = ctx->entity_begin, .length = ctx->end - ctx->entity_begin }; \
+		if (func(ctx->ctx, src) != BSPLoadResult_Success) \
+			PRINTF("Cannot parse %s", name); \
+	}
+
+	LOAD_ENTITY("info_landmark", bspReadEntityInfoLandmark);
+	LOAD_ENTITY("trigger_changelevel", bspReadEntityTriggerChangelevel);
+	LOAD_ENTITY("worldspawn", bspReadEntityWorldspawn);
+	//LOAD_ENTITY("info_player_start", bspReadEntityAndDumpProps);
+
+	return Parser_Continue;
+}
+
+static ParserCallbackResult entitySearchBegin(ParserState *state, StringView s) {
+	(void)s;
+	EntityReadContext *ctx = state->user_data;
+	ctx->entity_begin = s.str;
+
+	state->callbacks.curlyOpen = parserError;
+	state->callbacks.curlyClose = entitySearchSkip;
+	state->callbacks.string = entitySearchReadString;
+
+	return Parser_Continue;
 }
 
 enum BSPLoadResult bspReadEntities(struct BSPLoadModelContext *ctx, const char *str, int length) {
 	ctx->model->landmarks_count = 0;
 
-	struct TokenContext tctx;
-	tctx.cursor = str;
-	tctx.end = str + length;
+	EntityReadContext ents_context = {
+		.ctx = ctx,
+		.entity_begin = str,
+		.end = str + length
+	};
 
-	int loop = 1;
-	const char* curlyOpen = NULL;
-	while(loop) {
-		switch(getNextToken(&tctx)) {
-		case Token_CurlyOpen:
-			curlyOpen = tctx.cursor;
-			break;
-		case Token_String:
-			//PRINTF("%.*s", (int)tctx.str_length, tctx.str_start);
-#define LOAD_ENTITY(name, func) \
-			if (strncmp(name, tctx.str_start, tctx.str_length) == 0) { \
-				tctx.cursor = curlyOpen; \
-				const enum BSPLoadResult result = func(ctx, &tctx); \
-				if (result != BSPLoadResult_Success) { \
-					PRINTF("Failed at loading " name "@%d", (int)(tctx.cursor - str)); \
-					return result; \
-				} \
-				continue; \
-			}
-			LOAD_ENTITY("info_landmark", bspReadEntityInfoLandmark);
-			LOAD_ENTITY("trigger_changelevel", bspReadEntityTriggerChangelevel);
-			LOAD_ENTITY("worldspawn", bspReadEntityWorldspawn);
-			LOAD_ENTITY("info_player_start", bspReadEntityAndDumpProps);
-			break;
-		case Token_Error:
-			return BSPLoadResult_ErrorFileFormat;
-		case Token_End:
-			loop = 0;
-			break;
-		default:
-			break;
+	ParserState parser = {
+		.user_data = &ents_context,
+		.callbacks = {
+			.curlyOpen = entitySearchBegin,
+			.curlyClose = parserError,
+			.string = parserError
 		}
-	}
+	};
 
-	return BSPLoadResult_Success;
+	const StringView ent_sv = { .str = str, .length = length };
+
+return ParseResult_Success == parserParse(&parser, ent_sv)
+	? BSPLoadResult_Success : BSPLoadResult_ErrorFileFormat;
 }
 
 static int lumpRead(const char *name, const struct VBSPLumpHeader *header,

+ 4 - 4
src/bsp.h

@@ -62,23 +62,23 @@ struct ICollection;
 struct MemoryPool;
 struct Stack;
 
-struct BSPLoadModelContext {
+typedef struct BSPLoadModelContext {
 	struct ICollection *collection;
 	struct Stack *persistent;
 	struct Stack *tmp;
 
 	/* allocated by caller, populated by callee */
 	struct BSPModel *model;
-};
+} BSPLoadModelContext;
 
-enum BSPLoadResult {
+typedef enum BSPLoadResult {
 	BSPLoadResult_Success,
 	BSPLoadResult_ErrorFileOpen,
 	BSPLoadResult_ErrorFileFormat,
 	BSPLoadResult_ErrorMemory,
 	BSPLoadResult_ErrorTempMemory,
 	BSPLoadResult_ErrorCapabilities
-};
+} BSPLoadResult;
 
 /* should be called AFTER renderInit() */
 void bspInit();

+ 3 - 0
src/common.h

@@ -14,3 +14,6 @@ typedef struct {
 	const char *str;
 	int length;
 } StringView;
+
+#define PRI_SV "%.*s"
+#define PRI_SVV(s) ((s).length), ((s).str)

+ 133 - 138
src/material.c

@@ -5,55 +5,15 @@
 #include "vmfparser.h"
 #include "common.h"
 
-struct MaterialContext {
+typedef struct {
+	ICollection *collection;
+	Stack *temp;
+	StringView shader;
 	struct Material *mat;
-	struct TokenContext tok;
-	const char *key;
-	int key_length;
-	char value[128];
-};
-
-enum KeyValueResult {KeyValue_Read, KeyValue_Error, KeyValue_End};
-static enum KeyValueResult getNextKeyValue(struct MaterialContext *ctx) {
-	for (;;) {
-		enum TokenType type = getNextToken(&ctx->tok);
-		if (type == Token_End || type == Token_CurlyClose) return KeyValue_End;
-		if (type != Token_String) return KeyValue_Error;
-
-		ctx->key = ctx->tok.str_start;
-		ctx->key_length = ctx->tok.str_length;
-
-		type = getNextToken(&ctx->tok);
-		if (type == Token_CurlyOpen) {
-			/* skip unsupported proxies and DX-level specifics */
-			if (strncmp(ctx->key, "replace", ctx->key_length) != 0)
-				PRINTF("Skipping section %.*s", ctx->key_length, ctx->key);
-
-			int depth = 1;
-			for (;;) {
-				type = getNextToken(&ctx->tok);
-				if (type == Token_CurlyClose) {
-					if (--depth == 0)
-						break;
-				} else if (type == Token_CurlyOpen) {
-					++depth;
-				} else if (type != Token_String)
-					return KeyValue_Error;
-			}
-		} else if (type != Token_String) {
-			return KeyValue_Error;
-		} else {
-			if (ctx->tok.str_length > (int)sizeof(ctx->value) - 1) {
-				PRINTF("Value is too long: %d", ctx->tok.str_length);
-				return KeyValue_Error;
-			}
-			memcpy(ctx->value, ctx->tok.str_start, ctx->tok.str_length);
-			ctx->value[ctx->tok.str_length] = '\0';
-			return KeyValue_Read;
-		}
-	} /* loop until key value pair found */
-} /* getNextKeyValue */
+	StringView key;
+} MaterialContext;
 
+#if 0
 static const char * const ignore_params[] = {
 	"$surfaceprop", "$surfaceprop2", "$tooltexture",
 	"%tooltexture", "%keywords", "%compilewater", "%detailtype",
@@ -61,114 +21,149 @@ static const char * const ignore_params[] = {
 	"replace", /* TODO implement */
 	0
 };
+#endif
 
-static int materialLoad(struct IFile *file, struct ICollection *coll, struct Material *output, struct Stack *tmp) {
-	char *buffer = stackAlloc(tmp, file->size + 1);
-	int retval = 0;
-	if (!buffer) {
-		PRINT("Out of temp memory");
-		return 0;
-	}
+static ParserCallbackResult materialReadShader(ParserState *state, StringView s);
+static ParserCallbackResult materialReadKeyOrSection(ParserState *state, StringView s);
+static ParserCallbackResult materialReadValue(ParserState *state, StringView s);
+static ParserCallbackResult materialEnd(ParserState *state, StringView s);
 
-	if (file->size != file->read(file, 0, file->size, buffer)) return 0;
-	buffer[file->size] = '\0';
+static ParserCallbackResult materialOpenShader(ParserState *state, StringView s) {
+	MaterialContext *ctx = state->user_data;
+	ctx->shader = s;
 
-	struct MaterialContext ctx;
-	ctx.mat = output;
-	ctx.tok.cursor = buffer;
-	ctx.tok.end = NULL;
+	state->callbacks.string = materialReadKeyOrSection;
+	state->callbacks.curlyOpen = parserError;
 
-#define EXPECT_TOKEN(type) \
-	if (getNextToken(&ctx.tok) != type) { \
-		PRINTF("Unexpected token at position %zd, expecting %d; left: %s", ctx.tok.cursor - buffer, type, ctx.tok.cursor); \
-		goto exit; \
-	}
+	return Parser_Continue;
+}
 
-	EXPECT_TOKEN(Token_String);
-	const char *shader = ctx.tok.str_start;
-	const int shader_length = ctx.tok.str_length;
+static ParserCallbackResult materialReadShader(ParserState *state, StringView s) {
+	MaterialContext *ctx = state->user_data;
+	ctx->shader = s;
 
-	EXPECT_TOKEN(Token_CurlyOpen);
+	PRINTF("Material shader %.*s", PRI_SVV(ctx->shader));
 
-	for (;;) {
-		const enum KeyValueResult result = getNextKeyValue(&ctx);
-		if (result == KeyValue_End)
-			break;
-		if (result != KeyValue_Read) {
-			retval = -1;
-			goto exit;
-		}
+	state->callbacks.string = parserError;
+	state->callbacks.curlyOpen = materialOpenShader;
+	return Parser_Continue;
+}
 
-		int skip = 0;
-		for (const char *const *ignore = ignore_params; ignore[0] != 0; ++ignore)
-			if (strncasecmp(ignore[0], ctx.key, ctx.key_length) == 0) {
-				skip = 1;
-				break;
-			}
-		if (skip) continue;
-
-		if (strncasecmp("$basetexture", ctx.key, ctx.key_length) == 0) {
-			output->base_texture[0] = textureGet(ctx.value, coll, tmp);
-		} else if (strncasecmp("$basetexture2", ctx.key, ctx.key_length) == 0) {
-			output->base_texture[1] = textureGet(ctx.value, coll, tmp);
-		} else if (strncasecmp("$basetexturetransform", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$basetexturetransform2", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$detail", ctx.key, ctx.key_length) == 0) {
-			//output->detail = textureGet(ctx.value, coll, tmp);
-		} else if (strncasecmp("$detailscale", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$detailblendfactor", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$detailblendmode", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$parallaxmap", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$parallaxmapscale", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$bumpmap", ctx.key, ctx.key_length) == 0) {
-			/* output->bump = textureGet(ctx.value, coll, tmp); */
-		} else if (strncasecmp("$envmap", ctx.key, ctx.key_length) == 0) {
-			/* output->envmap = textureGet(ctx.value, coll, tmp); */
-		} else if (strncasecmp("$fogenable", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$fogcolor", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$alphatest", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$translucent", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$envmapcontrast", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$envmapsaturation", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$envmaptint", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$normalmapalphaenvmapmask", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$envmapmask", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$nodiffusebumplighting", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$AlphaTestReference", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$basealphaenvmapmask", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$selfillum", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("$reflectivity", ctx.key, ctx.key_length) == 0) {
-		} else if (strncasecmp("include", ctx.key, ctx.key_length) == 0) {
-			char *vmt = strstr(ctx.value, ".vmt");
-			if (vmt)
-				*vmt = '\0';
-			if (strstr(ctx.value, "materials/") == ctx.value)
-				*output = *materialGet(ctx.value + 10, coll, tmp);
-		} else {
-			PRINTF("Material shader:%.*s, unknown param %.*s = %s",
-					shader_length, shader, ctx.key_length, ctx.key, ctx.value);
-		}
-	} /* for all properties */
+static ParserCallbackResult materialReadKeyOrSection(ParserState *state, StringView s) {
+	MaterialContext *ctx = state->user_data;
+	ctx->key = s;
+	state->callbacks.string = materialReadValue;
+	state->callbacks.curlyClose = state->callbacks.curlyOpen = parserError;
+	return Parser_Continue;
+}
+
+static ParserCallbackResult materialReadValue(ParserState *state, StringView s) {
+	MaterialContext *ctx = state->user_data;
+
+	if (s.length > 127)
+		return Parser_Error;
+
+	char value[128];
+	memcpy(value, s.str, s.length);
+	value[s.length] = '\0';
+
+	if (strncasecmp("$basetexture", ctx->key.str, ctx->key.length) == 0) {
+		ctx->mat->base_texture[0] = textureGet(value, ctx->collection, ctx->temp);
+	} else if (strncasecmp("$basetexture2", ctx->key.str, ctx->key.length) == 0) {
+		ctx->mat->base_texture[1] = textureGet(value, ctx->collection, ctx->temp);
+	} else if (strncasecmp("$basetexturetransform", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$basetexturetransform2", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$detail", ctx->key.str, ctx->key.length) == 0) {
+		//output->detail = textureGet(ctx.value, ctx->collection, ctx->temp);
+	} else if (strncasecmp("$detailscale", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$detailblendfactor", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$detailblendmode", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$parallaxmap", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$parallaxmapscale", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$bumpmap", ctx->key.str, ctx->key.length) == 0) {
+		/* output->bump = textureGet(ctx.value, ctx->collection, ctx->temp); */
+	} else if (strncasecmp("$envmap", ctx->key.str, ctx->key.length) == 0) {
+		/* output->envmap = textureGet(ctx.value, ctx->collection, ctx->temp); */
+	} else if (strncasecmp("$fogenable", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$fogcolor", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$alphatest", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$translucent", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$envmapcontrast", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$envmapsaturation", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$envmaptint", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$normalmapalphaenvmapmask", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$envmapmask", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$nodiffusebumplighting", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$AlphaTestReference", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$basealphaenvmapmask", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$selfillum", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("$reflectivity", ctx->key.str, ctx->key.length) == 0) {
+	} else if (strncasecmp("include", ctx->key.str, ctx->key.length) == 0) {
+		char *vmt = strstr(value, ".vmt");
+		if (vmt)
+			*vmt = '\0';
+		if (strstr(value, "materials/") == value)
+			*ctx->mat = *materialGet(value + 10, ctx->collection, ctx->temp);
+	} else {
+		PRINTF("Material shader:%.*s, unknown param %.*s = %s",
+				ctx->shader.length, ctx->shader.str, ctx->key.length, ctx->key.str, value);
+	}
+
+	state->callbacks.string = materialReadKeyOrSection;
+	state->callbacks.curlyClose = materialEnd;
+	state->callbacks.curlyOpen = parserError;
+	return Parser_Continue;
+}
 
-	if (!output->base_texture[0]) {
-		PRINTF("Material with shader %.*s doesn't have base texture", shader_length, shader);
-		output->shader = MaterialShader_LightmappedAverageColor;
+static ParserCallbackResult materialEnd(ParserState *state, StringView s) {
+	(void)(s);
+	MaterialContext *ctx = state->user_data;
+
+	if (!ctx->mat->base_texture[0]) {
+		PRINTF("Material with ctx->shader %.*s doesn't have base texture", ctx->shader.length, ctx->shader.str);
+		ctx->mat->shader = MaterialShader_LightmappedAverageColor;
 		// HACK to notice these materials
-		output->average_color = aVec3f(1.f, 0.f, 1.f);
+		ctx->mat->average_color = aVec3f(1.f, 0.f, 1.f);
 	} else {
-		output->shader = MaterialShader_LightmappedGeneric;
-		output->average_color = output->base_texture[0]->avg_color;
+		ctx->mat->shader = MaterialShader_LightmappedGeneric;
+		ctx->mat->average_color = ctx->mat->base_texture[0]->avg_color;
 	}
 
-	retval = 1;
+	return Parser_Exit;
+}
+
+static int materialLoad(struct IFile *file, struct ICollection *coll, struct Material *output, struct Stack *tmp) {
+	char *buffer = stackAlloc(tmp, file->size);
+
+	if (!buffer) {
+		PRINT("Out of temp memory");
+		return 0;
+	}
+
+	if (file->size != file->read(file, 0, file->size, buffer)) return 0;
+
+	MaterialContext ctx = {
+		.collection = coll,
+		.temp = tmp,
+		.mat = output
+	};
+
+	ParserState parser = {
+		.user_data = &ctx,
+		.callbacks = {
+			.curlyOpen = parserError,
+			.curlyClose = parserError,
+			.string = materialReadShader
+		}
+	};
+
+	StringView buf_sv = { .str = buffer, .length = file->size };
 
-exit:
-	if (retval != 1)
-		PRINTF("Error parsing material with shader %.*s: %s", shader_length, shader, ctx.tok.cursor);
+	int success = ParseResult_Success == parserParse(&parser, buf_sv);
 
 	stackFreeUpToPosition(tmp, buffer);
 
-	return retval;
+	return success;
 }
 
 const struct Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp) {

+ 1 - 1
src/render.c

@@ -723,5 +723,5 @@ void renderBegin() {
 }
 
 void renderEnd(const struct Camera *camera) {
-	renderSkybox(camera, r.closest_map.model);
+	if (0) renderSkybox(camera, r.closest_map.model);
 }

+ 61 - 22
src/vmfparser.c

@@ -1,40 +1,79 @@
 #include "vmfparser.h"
 #include "libc.h"
 
-enum TokenType getNextToken(struct TokenContext *tok) {
-	enum TokenType type = Token_Skip;
-	const char *c = tok->cursor;
+ParseResult parserParse(ParserState *state, StringView string) {
+	const char *c = string.str;
+	const char * const end = string.str + string.length;
 
-#define CHECK_END ((tok->end && tok->end == c) || *c == '\0')
+#define CHECK_END (end == c || *c == '\0')
 
-	while (type == Token_Skip) {
+	int nest = 0;
+	for (;;) {
 		while(!CHECK_END && isspace(*c)) ++c;
-		if (CHECK_END) return Token_End;
+		if (CHECK_END)
+			return nest == 0 ? ParseResult_Success : ParseResult_Error;
 
-		tok->str_start = c;
-		tok->str_length = 0;
+		ParserCallbackResult cb_result = Parser_Continue;
+		StringView str = { .str = c, .length = 0 };
+		int quote = 0;
 		switch(*c) {
-			case '\"':
-				tok->str_start = ++c;
-				while(!CHECK_END && *c != '\"') ++c;
-				type = (*c == '\"') ? Token_String : Token_Error;
+			case '{':
+				++nest;
+				cb_result = state->callbacks.curlyOpen(state, str);
+				++c;
+				break;
+			case '}':
+				if (nest < 1)
+					return ParseResult_Error;
+				--nest;
+				cb_result = state->callbacks.curlyClose(state, str);
+				++c;
 				break;
-			case '{': type = Token_CurlyOpen; break;
-			case '}': type = Token_CurlyClose; break;
 			case '/':
 				if (*++c == '/') {
 					while(!CHECK_END && *c != '\n') ++c;
-					type = Token_Skip;
 				} else
-					type = Token_Error;
+					return ParseResult_Error;
 				break;
+			case '\"':
+				str.str = ++c;
+				quote = 1;
+
+				// fall through
 			default:
-				while (!CHECK_END && isgraph(*c)) ++c;
-				type = (c == tok->str_start) ? Token_Error : Token_String;
+				if (quote) {
+					for (;; ++c) {
+						if (CHECK_END)
+							return ParseResult_Error;
+						if (*c == '\"')
+							break;
+					}
+				} else {
+					while (!CHECK_END && isgraph(*c)) ++c;
+				}
+
+				str.length = c - str.str;
+				cb_result = state->callbacks.string(state, str);
+				++c;
 		} /* switch(*c) */
-	} /* while skip */
 
-	tok->str_length = c - tok->str_start;
-	tok->cursor = c + 1;
-	return type;
+		switch (cb_result) {
+		case Parser_Continue:
+			break;
+		case Parser_Exit:
+			return ParseResult_Success;
+		default:
+			return ParseResult_Error;
+		}
+	} /* forever */
+}
+
+ParserCallbackResult parserError(ParserState *state, StringView s) {
+	(void)state; (void)s;
+	return Parser_Error;
+}
+
+ParserCallbackResult parserIgnore(ParserState *state, StringView s) {
+	(void)state; (void)s;
+	return Parser_Continue;
 }

+ 29 - 14
src/vmfparser.h

@@ -1,19 +1,34 @@
 #pragma once
 
-enum TokenType {
-	Token_Skip,
-	Token_String,
-	Token_CurlyOpen,
-	Token_CurlyClose,
-	Token_Error,
-	Token_End
-};
-struct TokenContext {
-	const char *cursor;
-	const char *end;
+#include "common.h"
+
+typedef enum {
+	Parser_Continue,
+	Parser_Exit,
+	Parser_Error
+} ParserCallbackResult;
+
+struct ParserState;
+typedef struct ParserState ParserState;
 
-	const char *str_start;
-	int str_length;
+typedef ParserCallbackResult (*ParserCallback)(ParserState *state, StringView s);
+
+struct ParserState {
+	void *user_data;
+	struct {
+		ParserCallback curlyOpen;
+		ParserCallback curlyClose;
+		ParserCallback string;
+	} callbacks;
 };
 
-enum TokenType getNextToken(struct TokenContext *tok);
+typedef enum {
+	ParseResult_Success,
+	ParseResult_Error
+} ParseResult;
+
+ParseResult parserParse(ParserState *state, StringView string);
+
+// Utility callback function for specifying semantically invalid tokens
+ParserCallbackResult parserError(ParserState *state, StringView s);
+ParserCallbackResult parserIgnore(ParserState *state, StringView s);