assembly_parser now uses the standard parser

This commit is contained in:
PS 2020-10-10 17:22:31 -07:00
parent 55284cde25
commit e4266ba1ef
2 changed files with 125 additions and 398 deletions

View File

@ -45,362 +45,36 @@ enum assembly_field
AssemblyField_Count, AssemblyField_Count,
}; };
global char* AssemblyFieldIdentifiers[] = { global gs_const_string AssemblyFieldIdentifiers[] = {
"assembly_name", // AssemblyField_AssemblyName ConstString("assembly_name"), // AssemblyField_AssemblyName
"assembly_scale", // AssemblyField_AssemblyScale ConstString("assembly_scale"), // AssemblyField_AssemblyScale
"assembly_center", // AssemblyField_AssemblyCenter ConstString("assembly_center"), // AssemblyField_AssemblyCenter
"led_strip_count", // AssemblyField_LedStripCount ConstString("led_strip_count"), // AssemblyField_LedStripCount
"output_mode", // AssemblyField_OutputMode ConstString("output_mode"), // AssemblyField_OutputMode
"led_strip", // AssemblyField_LedStrip ConstString("led_strip"), // AssemblyField_LedStrip
"output_sacn", // AssemblyField_OutputSACN ConstString("output_sacn"), // AssemblyField_OutputSACN
"start_universe", // AssemblyField_SACN_StartUniverse ConstString("start_universe"), // AssemblyField_SACN_StartUniverse
"start_channel", // AssemblyField_SACN_StartChannel ConstString("start_channel"), // AssemblyField_SACN_StartChannel
"output_uart", // AssemblyField_OutputUART ConstString("output_uart"), // AssemblyField_OutputUART
"channel", // AssemblyField_UART_Channel ConstString("channel"), // AssemblyField_UART_Channel
"com_port", // AssemblyField_UART_ComPort ConstString("com_port"), // AssemblyField_UART_ComPort
"point_placement_type", // AssemblyField_PointPlacementType ConstString("point_placement_type"), // AssemblyField_PointPlacementType
"interpolate_points", // AssemblyField_InterpolatePoints ConstString("interpolate_points"), // AssemblyField_InterpolatePoints
"start", // AssemblyField_Start ConstString("start"), // AssemblyField_Start
"end", // AssemblyField_End ConstString("end"), // AssemblyField_End
"led_count", // AssemblyField_LedCount ConstString("led_count"), // AssemblyField_LedCount
"tags_count", // AssemblyField_TagCount ConstString("tags_count"), // AssemblyField_TagCount
"tag", // AssemblyField_Tag ConstString("tag"), // AssemblyField_Tag
"name", // AssemblyField_Name ConstString("name"), // AssemblyField_Name
"value", // AssemblyField_Value ConstString("value"), // AssemblyField_Value
}; };
struct assembly_error_list
{
gs_string String;
assembly_error_list* Next;
};
struct assembly_tokenizer
{
gs_string Text;
char* At;
gs_const_string FileName;
u32 LineNumber;
bool ParsingIsValid;
gs_memory_arena* ErrorArena;
assembly_error_list* ErrorsRoot;
assembly_error_list* ErrorsTail;
};
internal bool
AtValidPosition(assembly_tokenizer* T)
{
u64 Offset = T->At - T->Text.Str;
bool Result = (Offset < T->Text.Length);
return Result;
}
internal void
AdvanceChar(assembly_tokenizer* T)
{
if (IsNewline(T->At[0]))
{
T->LineNumber += 1;
}
T->At++;
}
internal void
EatWhitespace(assembly_tokenizer* T)
{
while(AtValidPosition(T) && IsNewlineOrWhitespace(T->At[0]))
{
AdvanceChar(T);
}
}
internal void
EatToNewLine(assembly_tokenizer* T)
{
while(AtValidPosition(T) && !IsNewline(T->At[0]))
{
AdvanceChar(T);
}
EatWhitespace(T);
}
internal bool
AdvanceIfTokenEquals(assembly_tokenizer* T, char* Value)
{
bool Result = true;
char* TAt = T->At;
char* VAt = Value;
while (*VAt != 0)
{
if (*TAt != *VAt)
{
Result = false;
break;
}
TAt += 1;
VAt += 1;
}
// TODO(Peter): What if the token is a subset of Value? ie. this would return true for
// T->At = hello_world and Value = hello_
// But the next token we read would fail
if (Result)
{
T->At = TAt;
EatWhitespace(T);
}
return Result;
}
internal void
TokenizerPushError(assembly_tokenizer* T, char* ErrorString)
{
// NOTE(Peter): We can make this more expressive if we need to
assembly_error_list* Error = PushStruct(T->ErrorArena, assembly_error_list);
Error->String = PushString(T->ErrorArena, 512);
PrintF(&Error->String, "%S(%d): %s", T->FileName, T->LineNumber, ErrorString);
SLLPushOrInit(T->ErrorsRoot, T->ErrorsTail, Error);
T->ParsingIsValid = false;
// NOTE(Peter): I'm not sure this is the best idea, but at least this way,
// if there's multiple errors, you'll get a number of them, rather than
// a bunch of erroneous errors happening on the same line
EatToNewLine(T);
}
#define PARSER_FIELD_REQUIRED true
#define PARSER_FIELD_OPTIONAL false
internal bool
ReadFieldIdentifier(assembly_field Field, assembly_tokenizer* T, bool Required = true)
{
bool Result = false;
if (AdvanceIfTokenEquals(T, AssemblyFieldIdentifiers[Field]))
{
if (AdvanceIfTokenEquals(T, ":"))
{
Result = true;
}
else
{
// We always throw an error if we get this far because we know you were trying to
// open the identifier
TokenizerPushError(T, "Field identifier is missing a colon");
}
}
else if (Required)
{
TokenizerPushError(T, "Field Identifier Invalid");
}
return Result;
}
internal bool
ReadFieldEnd(assembly_tokenizer* T)
{
bool Result = AdvanceIfTokenEquals(T, ";");
if (Result)
{
EatWhitespace(T);
}
else
{
TokenizerPushError(T, "Missing a semicolon");
}
return Result;
}
internal gs_string
ReadString(assembly_tokenizer* T)
{
gs_string Result = {};
if (AdvanceIfTokenEquals(T, "\""))
{
char* StringStart = T->At;
while(AtValidPosition(T) && T->At[0] != '\"')
{
T->At++;
}
Result.Str = StringStart;
Result.Size = T->At - StringStart;
Result.Length = Result.Size;
if (AdvanceIfTokenEquals(T, "\""))
{
// Success
}
else
{
TokenizerPushError(T, "String not closed with a \"");
}
}
else
{
TokenizerPushError(T, "Expecting a string, but none was found");
}
return Result;
}
internal gs_string
GetNumberString(assembly_tokenizer* T)
{
gs_string Result = {};
Result.Str = T->At;
while(AtValidPosition(T) && IsNumericExtended(T->At[0]))
{
AdvanceChar(T);
}
Result.Length = T->At - Result.Str;
Result.Size = Result.Length;
return Result;
}
internal r32
ReadFloat(assembly_tokenizer* T)
{
r32 Result = 0;
gs_string NumberString = GetNumberString(T);
Result = (r32)ParseFloat(NumberString.ConstString);
return Result;
}
internal s32
ReadInt(assembly_tokenizer* T)
{
s32 Result = 0;
gs_string NumberString = GetNumberString(T);
Result = (r32)ParseInt(NumberString.ConstString);
return Result;
}
internal gs_string
ReadStringField(assembly_field Field, assembly_tokenizer* T, gs_memory_arena* Arena, bool ShouldNullTerminate = false)
{
gs_string Result = {};
if (ReadFieldIdentifier(Field, T))
{
gs_string ExistingString = ReadString(T);
if (ReadFieldEnd(T))
{
// Success
u64 Length = ExistingString.Length + (ShouldNullTerminate ? 1 : 0);
Result = PushString(Arena, Length);
PrintF(&Result, "%S", ExistingString);
if (ShouldNullTerminate)
{
NullTerminate(&Result);
}
}
}
return Result;
}
internal r32
ReadFloatField(assembly_field Field, assembly_tokenizer* T)
{
r32 Result = 0.0f;
if (ReadFieldIdentifier(Field, T))
{
Result = ReadFloat(T);
if (!ReadFieldEnd(T))
{
T->ParsingIsValid = false;
}
}
return Result;
}
internal s32
ReadIntField(assembly_field Field, assembly_tokenizer* T)
{
r32 Result = 0.0f;
if (ReadFieldIdentifier(Field, T))
{
Result = ReadInt(T);
if (!ReadFieldEnd(T))
{
T->ParsingIsValid = false;
}
}
return Result;
}
internal v3
ReadV3Field(assembly_field Field, assembly_tokenizer* T)
{
v3 Result = {};
if (ReadFieldIdentifier(Field, T))
{
if (AdvanceIfTokenEquals(T, "("))
{
Result.x = ReadFloat(T);
if (AdvanceIfTokenEquals(T, ","))
{
Result.y = ReadFloat(T);
if (AdvanceIfTokenEquals(T, ","))
{
Result.z = ReadFloat(T);
if (AdvanceIfTokenEquals(T, ")"))
{
if (!ReadFieldEnd(T))
{
T->ParsingIsValid = false;
}
}
else
{
TokenizerPushError(T, "Vector 3 doesn't end with a ')'");
}
}
else
{
TokenizerPushError(T, "Vector 3: unable to read a field");
}
}
else
{
TokenizerPushError(T, "Vector 3: unable to read a field");
}
}
else
{
TokenizerPushError(T, "Vector 3: unable to read a field");
}
}
return Result;
}
internal bool
ReadStructOpening(assembly_field Field, assembly_tokenizer* T, bool Required = true)
{
bool Result = false;
if (ReadFieldIdentifier(Field, T, Required))
{
if (AdvanceIfTokenEquals(T, "{"))
{
Result = true;
}
}
return Result;
}
internal bool
ReadStructClosing(assembly_tokenizer* T)
{
bool Result = AdvanceIfTokenEquals(T, "};");
return Result;
}
internal void internal void
StripSetTag(v2_strip* Strip, u32 TagIndex, gs_const_string TagName, gs_const_string TagValue) StripSetTag(v2_strip* Strip, u32 TagIndex, gs_const_string TagName, gs_const_string TagValue)
{ {
@ -415,27 +89,26 @@ ParseAssemblyFile(assembly* Assembly, gs_const_string FileName, gs_string FileTe
{ {
Assembly->LedCountTotal = 0; Assembly->LedCountTotal = 0;
r32 Value = ParseFloat(ConstString("-2.355")); parser Parser = {0};
Parser.String = FileText;
Parser.Identifiers = &AssemblyFieldIdentifiers[0];
Parser.IdentifiersCount = AssemblyField_Count;
Parser.At = Parser.String.Str;
Parser.LineStart = Parser.At;
Parser.Arena = &Assembly->Arena;
assembly_tokenizer Tokenizer = {}; Assembly->Name = Parser_ReadStringValue(&Parser, AssemblyField_AssemblyName);
Tokenizer.Text = FileText; Assembly->Scale = Parser_ReadR32Value(&Parser, AssemblyField_AssemblyScale);
Tokenizer.At = Tokenizer.Text.Str; Assembly->Center = Parser_ReadV3Value(&Parser, AssemblyField_AssemblyCenter);
Tokenizer.ParsingIsValid = true; Assembly->StripCount = Parser_ReadU32Value(&Parser, AssemblyField_LedStripCount);
Tokenizer.ErrorArena = Transient;
Tokenizer.FileName = FileName;
Assembly->Name = ReadStringField(AssemblyField_AssemblyName, &Tokenizer, &Assembly->Arena);
Assembly->Scale = ReadFloatField(AssemblyField_AssemblyScale, &Tokenizer);
Assembly->Center = ReadV3Field(AssemblyField_AssemblyCenter, &Tokenizer);
Assembly->StripCount = ReadIntField(AssemblyField_LedStripCount, &Tokenizer);
Assembly->Strips = PushArray(&Assembly->Arena, v2_strip, Assembly->StripCount); Assembly->Strips = PushArray(&Assembly->Arena, v2_strip, Assembly->StripCount);
gs_string OutputModeString = ReadStringField(AssemblyField_OutputMode, &Tokenizer, Transient); gs_string OutputModeString = Parser_ReadStringValue(&Parser, AssemblyField_OutputMode);
if (StringsEqual(OutputModeString.ConstString, ConstString("UART"))) if (StringsEqual(OutputModeString.ConstString, ConstString("UART")))
{ {
Assembly->OutputMode = NetworkProtocol_UART; Assembly->OutputMode = NetworkProtocol_UART;
Assembly->UARTComPort = ReadStringField(AssemblyField_UART_ComPort, &Tokenizer, &Assembly->Arena, true).ConstString; Assembly->UARTComPort = Parser_ReadStringValue(&Parser, AssemblyField_UART_ComPort, true).ConstString;
} }
else if (StringsEqual(OutputModeString.ConstString, ConstString("SACN"))) else if (StringsEqual(OutputModeString.ConstString, ConstString("SACN")))
{ {
@ -443,45 +116,44 @@ ParseAssemblyFile(assembly* Assembly, gs_const_string FileName, gs_string FileTe
} }
else else
{ {
TokenizerPushError(&Tokenizer, "Invalid output mode specified."); //TokenizerPushError(&Tokenizer, "Invalid output mode specified.");
} }
for (u32 i = 0; i < Assembly->StripCount; i++) for (u32 i = 0; i < Assembly->StripCount; i++)
{ {
v2_strip* StripAt = Assembly->Strips + i; v2_strip* StripAt = Assembly->Strips + i;
if (ReadStructOpening(AssemblyField_LedStrip, &Tokenizer)) if (Parser_ReadOpenStruct(&Parser, AssemblyField_LedStrip))
{ {
if (ReadStructOpening(AssemblyField_OutputSACN, &Tokenizer, PARSER_FIELD_OPTIONAL)) if (Parser_ReadOpenStruct(&Parser, AssemblyField_OutputSACN))
{ {
StripAt->SACNAddr.StartUniverse = ReadIntField(AssemblyField_SACN_StartUniverse, &Tokenizer); StripAt->SACNAddr.StartUniverse = Parser_ReadU32Value(&Parser, AssemblyField_SACN_StartUniverse);
StripAt->SACNAddr.StartChannel = ReadIntField(AssemblyField_SACN_StartChannel, &Tokenizer); StripAt->SACNAddr.StartChannel = Parser_ReadU32Value(&Parser, AssemblyField_SACN_StartChannel);
if (!ReadStructClosing(&Tokenizer)) if (!Parser_ReadCloseStruct(&Parser))
{ {
TokenizerPushError(&Tokenizer, "Struct doesn't close where expected"); //TokenizerPushError(&Tokenizer, "Struct doesn't close where expected");
} }
} }
if (ReadStructOpening(AssemblyField_OutputUART, &Tokenizer, PARSER_FIELD_OPTIONAL)) if (Parser_ReadOpenStruct(&Parser, AssemblyField_OutputUART))
{ {
StripAt->UARTAddr.Channel = (u8)ReadIntField(AssemblyField_UART_Channel, &Tokenizer); StripAt->UARTAddr.Channel = (u8)Parser_ReadU32Value(&Parser, AssemblyField_UART_Channel);
if (!ReadStructClosing(&Tokenizer)) if (!Parser_ReadCloseStruct(&Parser))
{ {
TokenizerPushError(&Tokenizer, "Struct doesn't close where expected"); //TokenizerPushError(&Tokenizer, "Struct doesn't close where expected");
} }
} }
// TODO(Peter): Need to store this // TODO(Peter): Need to store this
gs_string PointPlacementType = ReadStringField(AssemblyField_PointPlacementType, &Tokenizer, &Assembly->Arena); gs_string PointPlacementType = Parser_ReadStringValue(&Parser, AssemblyField_PointPlacementType);
// TODO(Peter): Switch on value of PointPlacementType // TODO(Peter): Switch on value of PointPlacementType
if (ReadStructOpening(AssemblyField_InterpolatePoints, &Tokenizer)) if (Parser_ReadOpenStruct(&Parser, AssemblyField_InterpolatePoints))
{ {
StripAt->StartPosition = ReadV3Field(AssemblyField_Start, &Tokenizer); StripAt->StartPosition = Parser_ReadV3Value(&Parser, AssemblyField_Start);
StripAt->EndPosition = ReadV3Field(AssemblyField_End, &Tokenizer); StripAt->EndPosition = Parser_ReadV3Value(&Parser, AssemblyField_End);
if (!ReadStructClosing(&Tokenizer)) if (!Parser_ReadCloseStruct(&Parser))
{ {
Tokenizer.ParsingIsValid = false;
// TODO(Peter): @ErrorHandling // TODO(Peter): @ErrorHandling
// Have this function prepend the filename and line number. // Have this function prepend the filename and line number.
// Create an error display popup window, or an error log window that takes over a panel automatically // Create an error display popup window, or an error log window that takes over a panel automatically
@ -489,48 +161,47 @@ ParseAssemblyFile(assembly* Assembly, gs_const_string FileName, gs_string FileTe
} }
} }
StripAt->LedCount = ReadIntField(AssemblyField_LedCount, &Tokenizer); StripAt->LedCount = Parser_ReadU32Value(&Parser, AssemblyField_LedCount);
Assembly->LedCountTotal += StripAt->LedCount; Assembly->LedCountTotal += StripAt->LedCount;
StripAt->TagsCount = Parser_ReadU32Value(&Parser, AssemblyField_TagsCount);
StripAt->TagsCount = ReadIntField(AssemblyField_TagsCount, &Tokenizer);
// NOTE(pjs): Always add one tag to the input to leave room for the assembly name // NOTE(pjs): Always add one tag to the input to leave room for the assembly name
StripAt->TagsCount += 1; StripAt->TagsCount += 1;
StripAt->Tags = PushArray(&Assembly->Arena, v2_tag, StripAt->TagsCount); StripAt->Tags = PushArray(&Assembly->Arena, v2_tag, StripAt->TagsCount);
StripSetTag(StripAt, 0, ConstString("assembly"), Assembly->Name.ConstString); StripSetTag(StripAt, 0, ConstString("assembly"), Assembly->Name.ConstString);
for (u32 Tag = 1; Tag < StripAt->TagsCount; Tag++) for (u32 Tag = 1; Tag < StripAt->TagsCount; Tag++)
{ {
if (ReadStructOpening(AssemblyField_Tag, &Tokenizer)) if (Parser_ReadOpenStruct(&Parser, AssemblyField_Tag))
{ {
// TODO(Peter): Need to store the gs_string somewhere we can look it up for display in the interface // TODO(Peter): Need to store the gs_string somewhere we can look it up for display in the interface
// right now they are stored in temp memory and won't persist // right now they are stored in temp memory and won't persist
gs_string TagName = ReadStringField(AssemblyField_Name, &Tokenizer, Transient); gs_string TagName = Parser_ReadStringValue(&Parser, AssemblyField_Name);
gs_string TagValue = ReadStringField(AssemblyField_Value, &Tokenizer, Transient); gs_string TagValue = Parser_ReadStringValue(&Parser, AssemblyField_Value);
StripSetTag(StripAt, Tag, TagName.ConstString, TagValue.ConstString); StripSetTag(StripAt, Tag, TagName.ConstString, TagValue.ConstString);
if (!ReadStructClosing(&Tokenizer)) if (!Parser_ReadCloseStruct(&Parser))
{ {
TokenizerPushError(&Tokenizer, "Struct doesn't close where expected"); //TokenizerPushError(&Tokenizer, "Struct doesn't close where expected");
} }
} }
else else
{ {
TokenizerPushError(&Tokenizer, "Expected a struct opening, but none was found"); //TokenizerPushError(&Tokenizer, "Expected a struct opening, but none was found");
} }
} }
if (!ReadStructClosing(&Tokenizer)) if (!Parser_ReadCloseStruct(&Parser))
{ {
TokenizerPushError(&Tokenizer, "Struct doesn't close where expected"); //TokenizerPushError(&Tokenizer, "Struct doesn't close where expected");
} }
} }
else else
{ {
TokenizerPushError(&Tokenizer, "Expected a struct opening, but none was found"); //TokenizerPushError(&Tokenizer, "Expected a struct opening, but none was found");
} }
} }
return Tokenizer.ParsingIsValid; return true; //Tokenizer.ParsingIsValid;
} }
#define ASSEMBLY_PARSER_CPP #define ASSEMBLY_PARSER_CPP

View File

@ -245,7 +245,7 @@ Parser_ReadString(parser* P, u32 IdentIndex)
} }
internal gs_string internal gs_string
Parser_ReadStringValue(parser* P, gs_const_string Ident) Parser_ReadStringValue(parser* P, gs_const_string Ident, bool ShouldNullTerminate = false)
{ {
// ident: "value"; // ident: "value";
gs_string Result = {}; gs_string Result = {};
@ -265,7 +265,16 @@ Parser_ReadStringValue(parser* P, gs_const_string Ident)
if (Parser_AdvanceIfTokenEquals(P, ConstString("\"")) && if (Parser_AdvanceIfTokenEquals(P, ConstString("\"")) &&
Parser_AdvanceIfLineEnd(P)) Parser_AdvanceIfLineEnd(P))
{ {
Result = PushStringF(P->Arena, FileString.Length, "%S", FileString); u32 StringLength = FileString.Length;
if (ShouldNullTerminate)
{
StringLength += 1;
}
Result = PushStringF(P->Arena, StringLength, "%S", FileString);
if (ShouldNullTerminate)
{
NullTerminate(&Result);
}
} }
else else
{ {
@ -277,10 +286,10 @@ Parser_ReadStringValue(parser* P, gs_const_string Ident)
} }
internal gs_string internal gs_string
Parser_ReadStringValue(parser* P, u32 IdentIndex) Parser_ReadStringValue(parser* P, u32 IdentIndex, bool ShouldNullTerminate = false)
{ {
gs_const_string Ident = Parser_GetIdent(*P, IdentIndex); gs_const_string Ident = Parser_GetIdent(*P, IdentIndex);
return Parser_ReadStringValue(P, Ident); return Parser_ReadStringValue(P, Ident, ShouldNullTerminate);
} }
internal bool internal bool
@ -351,6 +360,15 @@ Parser_ReadU32Value(parser* P, u32 IdentIndex)
return Parser_ReadU32Value(P, Ident); return Parser_ReadU32Value(P, Ident);
} }
internal r32
Parser_ReadR32(parser* P)
{
r32 Result = 0;
gs_const_string NumStr = Parser_ReadNumberString(P);
Result = (r32)ParseFloat(NumStr);
return Result;
}
internal r32 internal r32
Parser_ReadR32Value(parser* P, gs_const_string Ident) Parser_ReadR32Value(parser* P, gs_const_string Ident)
{ {
@ -359,15 +377,16 @@ Parser_ReadR32Value(parser* P, gs_const_string Ident)
if (Parser_AdvanceIfTokenEquals(P, Ident) && if (Parser_AdvanceIfTokenEquals(P, Ident) &&
Parser_AdvanceIfTokenEquals(P, ConstString(":"))) Parser_AdvanceIfTokenEquals(P, ConstString(":")))
{ {
gs_const_string NumStr = Parser_ReadNumberString(P); r32 Value = Parser_ReadR32(P);
if (Parser_AdvanceIfLineEnd(P)) if (Parser_AdvanceIfLineEnd(P))
{ {
Result = (r32)ParseFloat(NumStr); Result = Value;
} }
else else
{ {
// TODO(pjs): Error // TODO(pjs): Error
} }
} }
return Result; return Result;
} }
@ -379,6 +398,43 @@ Parser_ReadR32Value(parser* P, u32 IdentIndex)
return Parser_ReadR32Value(P, Ident); return Parser_ReadR32Value(P, Ident);
} }
internal v3
Parser_ReadV3Value(parser* P, gs_const_string Ident)
{
v3 Result = {0};
if (Parser_AdvanceIfTokenEquals(P, Ident) &&
Parser_AdvanceIfTokenEquals(P, ConstString(":")) &&
Parser_AdvanceIfTokenEquals(P, ConstString("(")))
{
r32 X = Parser_ReadR32(P);
Parser_AdvanceIfTokenEquals(P, ConstString(","));
r32 Y = Parser_ReadR32(P);
Parser_AdvanceIfTokenEquals(P, ConstString(","));
r32 Z = Parser_ReadR32(P);
if (Parser_AdvanceIfTokenEquals(P, ConstString(")")) &&
Parser_AdvanceIfLineEnd(P))
{
Result.x = X;
Result.y = Y;
Result.z = Z;
}
else
{
// TODO(pjs): error
}
}
return Result;
}
internal v3
Parser_ReadV3Value(parser* P, u32 IdentIndex)
{
gs_const_string Ident = Parser_GetIdent(*P, IdentIndex);
return Parser_ReadV3Value(P, Ident);
}
#define FOLDHAUS_SERIALIZER_H #define FOLDHAUS_SERIALIZER_H
#endif // FOLDHAUS_SERIALIZER_H #endif // FOLDHAUS_SERIALIZER_H