瀏覽代碼

simplify vmf-like format parser further, break materials for now

Ivan Avdeev 6 年之前
父節點
當前提交
dec296e1c3
共有 4 個文件被更改,包括 243 次插入251 次删除
  1. 87 174
      src/bsp.c
  2. 9 0
      src/material.c
  3. 122 56
      src/vmfparser.c
  4. 25 21
      src/vmfparser.h

+ 87 - 174
src/bsp.c

@@ -769,91 +769,32 @@ typedef struct {
 	StringView value;
 } EntityProp;
 
+#define ENTITY_LIST_PROPS \
+	ENTITY_PROP(ClassName, classname) \
+	ENTITY_PROP(TargetName, targetname) \
+	ENTITY_PROP(Origin, origin) \
+	ENTITY_PROP(SkyName, skyname) \
+	ENTITY_PROP(Landmark, landmark) \
+	ENTITY_PROP(Map, map) \
+
+typedef enum {
+#define ENTITY_PROP(name, string) \
+	EntityPropIndex_##name,
+ENTITY_LIST_PROPS
+#undef ENTITY_PROP
+} EntityPropIndex;
+
 typedef struct {
+	BSPLoadModelContext *ctx;
 	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;
-
-	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;
-		}
-	}
-
-	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;
+} Entity;
 
-	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, StringView src) {
-	EntityProp props[] = {
-		{"targetname", {NULL, 0}},
-		{"origin", {NULL, 0}}
-	};
+typedef BSPLoadResult (*BspProcessEntityProc)(BSPLoadModelContext *ctx, const Entity *entity);
 
-	const enum BSPLoadResult result = bspReadEntityProps(src, props, COUNTOF(props));
-
-	if (result != BSPLoadResult_Success)
-		return result;
+BSPLoadResult bspProcessEntityInfoLandmark(BSPLoadModelContext *ctx, const Entity *entity) {
+	const StringView target_name = entity->props[EntityPropIndex_TargetName].value;
+	const StringView origin = entity->props[EntityPropIndex_Origin].value;
 
 	struct BSPModel *model = ctx->model;
 	if (model->landmarks_count == BSP_MAX_LANDMARKS) {
@@ -862,23 +803,22 @@ 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 (target_name.length >= (int)sizeof(landmark->name)) {
 		PRINTF("Landmark name \"%.*s\" is too long",
-			PRI_SVV(props[0].value));
+			PRI_SVV(target_name));
 		return BSPLoadResult_ErrorMemory;
 	}
 
-	memcpy(landmark->name, props[0].value.str, props[0].value.length);
-	landmark->name[props[0].value.length] = '\0';
+	memcpy(landmark->name, target_name.str, target_name.length);
+	landmark->name[target_name.length] = '\0';
 
-	// FIXME props[1].value is not null-terminated suman
-	if (3 != sscanf(props[1].value.str, "%f %f %f",
+	// FIXME props[EntityPropIndex_Origin].value is not null-terminated suman
+	if (3 != sscanf(origin.str, "%f %f %f",
 			&landmark->origin.x,
 			&landmark->origin.y,
 			&landmark->origin.z))
 	{
-		PRINTF("Cannot read x, y, z from origin=\"%.*s\"",
-			PRI_SVV(props[1].value));
+		PRINTF("Cannot read x, y, z from origin=\"%.*s\"", PRI_SVV(origin));
 		return BSPLoadResult_ErrorFileFormat;
 	}
 
@@ -887,118 +827,91 @@ enum BSPLoadResult bspReadEntityInfoLandmark(struct BSPLoadModelContext *ctx, St
 	return BSPLoadResult_Success;
 }
 
-enum BSPLoadResult bspReadEntityTriggerChangelevel(struct BSPLoadModelContext *ctx, StringView src) {
+BSPLoadResult bspProcessEntityTriggerChangelevel(struct BSPLoadModelContext *ctx, const Entity *entity) {
 	(void)ctx;
-	EntityProp props[] = {
-		{"landmark", {NULL, 0}},
-		{"map", {NULL, 0}}
-	};
-
-	const enum BSPLoadResult result = bspReadEntityProps(src, props, COUNTOF(props));
-
-	if (result != BSPLoadResult_Success)
-		return result;
-
-	openSourceAddMap(props[1].value.str, props[1].value.length);
-
+	const StringView map = entity->props[EntityPropIndex_Map].value;
+	openSourceAddMap(map.str, map.length);
 	return BSPLoadResult_Success;
 }
 
-enum BSPLoadResult bspReadEntityWorldspawn(struct BSPLoadModelContext *ctx, StringView src) {
+BSPLoadResult bspProcessEntityWorldspawn(struct BSPLoadModelContext *ctx, const Entity *entity) {
 	(void)ctx;
-	EntityProp props[] = {
-		{"skyname", {NULL, 0}},
-	};
-
-	const enum BSPLoadResult result = bspReadEntityProps(src, props, COUNTOF(props));
-
-	if (result != BSPLoadResult_Success)
-		return result;
+	const StringView skyname = entity->props[EntityPropIndex_SkyName].value;
 
-	if (props[0].value.length > 0) {
-		const StringView sky = { props[0].value.str, props[0].value.length };
+	if (skyname.length > 0) {
+		const StringView sky = { skyname.str, skyname.length };
 		bspLoadSkybox(sky, ctx->collection, ctx->persistent, ctx->model);
 	}
 
 	return BSPLoadResult_Success;
 }
 
-/*
-enum BSPLoadResult bspReadEntityAndDumpProps(struct BSPLoadModelContext *ctx, StringView src) {
-	(void)ctx;
-	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;
+static struct {
+	const char *classname;
+	BspProcessEntityProc proc;
+} entity_procs[] = {
+	{"info_landmark", bspProcessEntityInfoLandmark},
+	{"trigger_changelevel", bspProcessEntityTriggerChangelevel},
+	{"worldspawn", bspProcessEntityWorldspawn},
+};
 
-	return Parser_Continue;
-}
+static VMFAction bspReadEntityProps(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) {
+	Entity *entity = state->user_data;
 
-static ParserCallbackResult entitySearchReadString(ParserState *state, StringView s) {
-	EntityReadContext *ctx = state->user_data;
+	switch (entry) {
+		case VMFEntryType_KeyValue:
+			for (int i = 0; i < entity->props_count; ++i) {
+				if (strncmp(entity->props[i].name, kv->key.str, kv->key.length) == 0) {
+					entity->props[i].value = kv->value;
+					break;
+				}
+			}
+			break;
+		case VMFEntryType_SectionOpen:
+			for (int i = 0; i < entity->props_count; ++i) {
+				entity->props[i].value.str = NULL;
+				entity->props[i].value.length = 0;
+			}
 
-#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); \
+			break;
+		case VMFEntryType_SectionClose:
+			for (int i = 0; i < (int)COUNTOF(entity_procs); ++i) {
+				const StringView classname = entity->props[EntityPropIndex_ClassName].value;
+				if (strncmp(entity_procs[i].classname, classname.str, classname.length) == 0) {
+					entity_procs[i].proc(entity->ctx, entity);
+					break;
+				}
+			}
+			break;
 	}
 
-	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;
+	return VMFAction_Continue;
 }
 
-enum BSPLoadResult bspReadEntities(struct BSPLoadModelContext *ctx, const char *str, int length) {
+BSPLoadResult bspReadEntities(BSPLoadModelContext *ctx, const char *str, int length) {
 	ctx->model->landmarks_count = 0;
 
-	EntityReadContext ents_context = {
-		.ctx = ctx,
-		.entity_begin = str,
-		.end = str + length
+	EntityProp props[] = {
+#define ENTITY_PROP(name, string) \
+		{#string, {NULL, 0}},
+ENTITY_LIST_PROPS
+#undef ENTITY_PROP
 	};
 
-	ParserState parser = {
-		.user_data = &ents_context,
-		.callbacks = {
-			.curlyOpen = entitySearchBegin,
-			.curlyClose = parserError,
-			.string = parserError
-		}
+	Entity entity = {
+		.ctx = ctx,
+		.props = props,
+		.props_count = COUNTOF(props),
 	};
 
-	const StringView ent_sv = { .str = str, .length = length };
+	VMFState parser_state = {
+		.user_data = &entity,
+		.data = { .str = str, .length = length },
+		.callback = bspReadEntityProps,
+	};
 
-return ParseResult_Success == parserParse(&parser, ent_sv)
-	? BSPLoadResult_Success : BSPLoadResult_ErrorFileFormat;
+	return VMFResult_Success == vmfParse(&parser_state)
+		? BSPLoadResult_Success : BSPLoadResult_ErrorFileFormat;
 }
 
 static int lumpRead(const char *name, const struct VBSPLumpHeader *header,

+ 9 - 0
src/material.c

@@ -23,6 +23,7 @@ static const char * const ignore_params[] = {
 };
 #endif
 
+#if 0
 static ParserCallbackResult materialReadShader(ParserState *state, StringView s);
 static ParserCallbackResult materialReadKeyOrSection(ParserState *state, StringView s);
 static ParserCallbackResult materialReadValue(ParserState *state, StringView s);
@@ -131,8 +132,15 @@ static ParserCallbackResult materialEnd(ParserState *state, StringView s) {
 
 	return Parser_Exit;
 }
+#endif
 
 static int materialLoad(struct IFile *file, struct ICollection *coll, struct Material *output, struct Stack *tmp) {
+	(void)file;
+	(void)coll;
+	(void)output;
+	(void)tmp;
+	return 0;
+#if 0
 	char *buffer = stackAlloc(tmp, file->size);
 
 	if (!buffer) {
@@ -164,6 +172,7 @@ static int materialLoad(struct IFile *file, struct ICollection *coll, struct Mat
 	stackFreeUpToPosition(tmp, buffer);
 
 	return success;
+#endif
 }
 
 const struct Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp) {

+ 122 - 56
src/vmfparser.c

@@ -1,79 +1,145 @@
 #include "vmfparser.h"
 #include "libc.h"
 
-ParseResult parserParse(ParserState *state, StringView string) {
-	const char *c = string.str;
-	const char * const end = string.str + string.length;
+typedef enum {
+	VMFTokenType_End,
+	VMFTokenType_Error,
+	VMFTokenType_String,
+	VMFTokenType_Open,
+	VMFTokenType_Close
+} VMFTokenType;
+
+typedef struct {
+	VMFTokenType type;
+	StringView string;
+} VMFToken;
+
+static VMFToken readNextToken(VMFState *state) {
+	const char *c = state->data.str;
+	const char * const end = state->data.str + state->data.length;
+	const char *error_message = NULL;
 
 #define CHECK_END (end == c || *c == '\0')
+#define REPORT_ERROR(msg) \
+	do {\
+		error_message = msg; \
+		token.type = VMFTokenType_Error; \
+		goto exit; \
+	} while(0)
+
+	VMFToken token;
+	token.string.str = NULL;
+	token.string.length = 0;
+	token.type = VMFTokenType_End;
 
-	int nest = 0;
 	for (;;) {
-		while(!CHECK_END && isspace(*c)) ++c;
-		if (CHECK_END)
-			return nest == 0 ? ParseResult_Success : ParseResult_Error;
+		for (;; ++c) {
+			if (CHECK_END) {
+				--c;
+				goto exit;
+			}
+			if (!isspace(*c))
+				break;
+		}
 
-		ParserCallbackResult cb_result = Parser_Continue;
-		StringView str = { .str = c, .length = 0 };
-		int quote = 0;
 		switch(*c) {
 			case '{':
-				++nest;
-				cb_result = state->callbacks.curlyOpen(state, str);
-				++c;
+				token.string.str = c;
+				token.string.length = 1;
+				token.type = VMFTokenType_Open;
 				break;
 			case '}':
-				if (nest < 1)
-					return ParseResult_Error;
-				--nest;
-				cb_result = state->callbacks.curlyClose(state, str);
-				++c;
+				token.string.str = c;
+				token.string.length = 1;
+				token.type = VMFTokenType_Close;
 				break;
 			case '/':
-				if (*++c == '/') {
-					while(!CHECK_END && *c != '\n') ++c;
-				} else
-					return ParseResult_Error;
-				break;
+				++c;
+				if (CHECK_END || *c != '/')
+					REPORT_ERROR("'/' expected");
+				while(!CHECK_END && *c != '\n') ++c;
+				continue;
 			case '\"':
-				str.str = ++c;
-				quote = 1;
-
-				// fall through
-			default:
-				if (quote) {
-					for (;; ++c) {
-						if (CHECK_END)
-							return ParseResult_Error;
-						if (*c == '\"')
-							break;
-					}
-				} else {
-					while (!CHECK_END && isgraph(*c)) ++c;
+				token.string.str = ++c;
+				for (;; ++c) {
+					if (CHECK_END)
+						REPORT_ERROR("\" is not closed at the end");
+					if (*c == '\"')
+						break;
 				}
+				token.string.length = c - token.string.str;
+				token.type = VMFTokenType_String;
+				break;
 
-				str.length = c - str.str;
-				cb_result = state->callbacks.string(state, str);
-				++c;
+			default:
+				token.string.str = c;
+				while (!CHECK_END && isgraph(*c)) ++c;
+				token.string.length = c - token.string.str;
+				token.type = VMFTokenType_String;
 		} /* switch(*c) */
 
-		switch (cb_result) {
-		case Parser_Continue:
-			break;
-		case Parser_Exit:
-			return ParseResult_Success;
-		default:
-			return ParseResult_Error;
-		}
+		break;
 	} /* forever */
-}
 
-ParserCallbackResult parserError(ParserState *state, StringView s) {
-	(void)state; (void)s;
-	return Parser_Error;
-}
+exit:
+	if (error_message)
+		PRINTF("Parsing error \"%s\" @%d (%.*s)", error_message,
+				(int)(c - state->data.str), (int)(end - c < 32 ? end - c : 32), c);
+	else
+		PRINTF("Token %d, (%.*s)", token.type, PRI_SVV(token.string));
 
-ParserCallbackResult parserIgnore(ParserState *state, StringView s) {
-	(void)state; (void)s;
-	return Parser_Continue;
+	state->data.str = c + 1;
+	state->data.length = end - c - 1;
+	return token;
 }
+
+VMFResult vmfParse(VMFState *state) {
+	VMFKeyValue kv;
+	VMFAction action = VMFAction_Continue;
+
+	for (;;) {
+		switch (action) {
+			case VMFAction_Continue:
+				break;
+			case VMFAction_Exit:
+				return VMFResult_Success;
+			case VMFAction_SemanticError:
+				return VMFResult_SemanticError;
+		}
+
+		kv.key.str = kv.value.str = "";
+		kv.key.length = kv.value.length = 0;
+
+		VMFToken token = readNextToken(state);
+		switch (token.type) {
+			case VMFTokenType_String:
+				kv.key = token.string;
+				break;
+			case VMFTokenType_Open:
+				action = state->callback(state, VMFEntryType_SectionOpen, &kv);
+				continue;
+			case VMFTokenType_Close:
+				action = state->callback(state, VMFEntryType_SectionClose, NULL);
+				continue;
+			case VMFTokenType_End:
+				return VMFResult_Success;
+			default:
+				PRINTF("Unexpected token %d", token.type);
+				return VMFResult_SyntaxError;
+		}
+
+		token = readNextToken(state);
+		switch (token.type) {
+			case VMFTokenType_String:
+				kv.value = token.string;
+				action = state->callback(state, VMFEntryType_KeyValue, &kv);
+				continue;
+			case VMFTokenType_Open:
+				action = state->callback(state, VMFEntryType_SectionOpen, &kv);
+				continue;
+			default:
+				PRINTF("Unexpected token %d", token.type);
+				return VMFResult_SyntaxError;
+		}
+	} // forever
+} // vmfParse

+ 25 - 21
src/vmfparser.h

@@ -2,33 +2,37 @@
 
 #include "common.h"
 
+struct VMFState;
+typedef struct VMFState VMFState;
+
+typedef enum {
+	VMFEntryType_KeyValue,
+	VMFEntryType_SectionOpen,
+	VMFEntryType_SectionClose
+} VMFEntryType;
+
 typedef enum {
-	Parser_Continue,
-	Parser_Exit,
-	Parser_Error
-} ParserCallbackResult;
+	VMFAction_Continue,
+	VMFAction_Exit,
+	VMFAction_SemanticError
+} VMFAction;
 
-struct ParserState;
-typedef struct ParserState ParserState;
+typedef struct {
+	StringView key, value;
+} VMFKeyValue;
 
-typedef ParserCallbackResult (*ParserCallback)(ParserState *state, StringView s);
+typedef VMFAction (*VMFCallback)(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv);
 
-struct ParserState {
+struct VMFState {
 	void *user_data;
-	struct {
-		ParserCallback curlyOpen;
-		ParserCallback curlyClose;
-		ParserCallback string;
-	} callbacks;
+	StringView data;
+	VMFCallback callback;
 };
 
 typedef enum {
-	ParseResult_Success,
-	ParseResult_Error
-} ParseResult;
-
-ParseResult parserParse(ParserState *state, StringView string);
+	VMFResult_Success,
+	VMFResult_SyntaxError,
+	VMFResult_SemanticError
+} VMFResult;
 
-// Utility callback function for specifying semantically invalid tokens
-ParserCallbackResult parserError(ParserState *state, StringView s);
-ParserCallbackResult parserIgnore(ParserState *state, StringView s);
+VMFResult vmfParse(VMFState *state);