Parsing errors for unbalanced sets and text literals.

This commit is contained in:
Miguel Lechon
2021-03-10 18:32:29 +01:00
parent 35ef98a5fd
commit 701b77e118
16 changed files with 219 additions and 50 deletions
+7
View File
@@ -12,6 +12,7 @@ cl %compile_flags% ..\samples\static_site_generator\static_site_generator.c
cl %compile_flags% ..\samples\output_parse\output_parse.c
cl %compile_flags% ..\samples\c_code_generation.c
cl %compile_flags% ..\samples\node_errors\node_errors.c
cl %compile_flags% ..\samples\syntax_errors\syntax_errors.c
echo.
echo ~~~ Build All Tests ~~~
cl %compile_flags% ..\tests\sanity_tests.c
@@ -63,3 +64,9 @@ echo ~~~ Running Error Generation Sample ~~~
pushd build
node_errors.exe %~dp0\samples\node_errors\node_errors.md
popd
echo.
echo ~~~ Running Syntax Error Detection and Reporting Sample ~~~
pushd build
for /f "tokens=*" %%f in ('dir /s/b ..\samples\syntax_errors\*.md ^| sort') do syntax_errors.exe %%f
popd
+8 -1
View File
@@ -24,6 +24,7 @@ $CC $compile_flags ../samples/static_site_generator/static_site_generator.c -o s
$CC $compile_flags ../samples/output_parse/output_parse.c -o output_parse
$CC $compile_flags ../samples/c_code_generation.c -o c_code_generation
$CC $compile_flags ../samples/node_errors/node_errors.c -o node_errors
$CC $compile_flags ../samples/syntax_errors/syntax_errors.c -o syntax_errors
echo
echo ~~~ Build All Tests ~~~
$CC $compile_flags ../tests/sanity_tests.c -o sanity_tests
@@ -58,9 +59,15 @@ pushd build
./c_code_generation
popd
echo.
echo
echo ~~~ Running Error Generation Sample ~~~
pushd build
./node_errors ../samples/node_errors/node_errors.md
popd
echo
echo ~~~ Running Syntax Error Detection and Reporting Sample ~~~
pushd build
./syntax_errors ../samples/syntax_errors/*.md
popd
+7
View File
@@ -14,6 +14,7 @@ clang %compile_flags% ..\samples\static_site_generator\static_site_generator.c -
clang %compile_flags% ..\samples\output_parse\output_parse.c -o output_parse.exe
clang %compile_flags% ..\samples\c_code_generation.c -o c_code_generation.exe
clang %compile_flags% ..\samples\node_errors\node_errors.c -o node_errors.exe
clang %compile_flags% ..\samples\syntax_errors\syntax_errors.c -o syntax_errors.exe
echo.
echo ~~~ Build All Tests ~~~
clang %compile_flags% ..\tests\sanity_tests.c -o sanity_tests.exe
@@ -65,3 +66,9 @@ echo ~~~ Running Error Generation Sample ~~~
pushd build
node_errors.exe %~dp0\samples\node_errors\node_errors.md
popd
echo.
echo ~~~ Running Syntax Error Detection and Reporting Sample ~~~
pushd build
for /f "tokens=*" %%f in ('dir /s/b ..\samples\syntax_errors\*.md ^| sort') do syntax_errors.exe %%f
popd
+1 -1
View File
@@ -16,7 +16,7 @@ int main(int argument_count, char **arguments)
" f: ([4 + 5]S32),\n"
" g: ([FOO + BAR]I32),\n"
"}\n\n");
MD_Node *code = MD_ParseWholeString(MD_S8Lit("Generated Test Code"), example_code);
MD_Node *code = MD_ParseWholeString(MD_S8Lit("Generated Test Code"), example_code).node;
printf("Source Metadesk Code:\n");
printf("%.*s\n\n", MD_StringExpand(example_code));
+1 -1
View File
@@ -10,7 +10,7 @@ int main(int argument_count, char **arguments)
MD_Node *last = MD_NilNode();
for(int i = 1; i < argument_count; i += 1)
{
MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i]));
MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i])).node;
MD_PushSibling(&first, &last, MD_NilNode(), root);
}
+1 -1
View File
@@ -28,7 +28,7 @@ int main(int argument_count, char **arguments)
MD_Node *last = MD_NilNode();
for(int i = 1; i < argument_count; i += 1)
{
MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i]));
MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i])).node;
MD_PushSibling(&first, &last, MD_NilNode(), root);
}
+1 -1
View File
@@ -63,7 +63,7 @@ int main(int argument_count, char **arguments)
MD_Node *last = MD_NilNode();
for(int i = 1; i < argument_count; i += 1)
{
MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i]));
MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i])).node;
MD_PushSibling(&first, &last, MD_NilNode(), root);
}
@@ -51,7 +51,7 @@ int main(int argument_count, char **arguments)
SiteInfo site_info = {0};
{
printf("Parsing site metadata at \"%.*s\"...\n", MD_StringExpand(site_info_path));
MD_Node *site_info_file = MD_ParseWholeFile(site_info_path);
MD_Node *site_info_file = MD_ParseWholeFile(site_info_path).node;
site_info = ParseSiteInfo(site_info_file);
}
@@ -74,7 +74,7 @@ int main(int argument_count, char **arguments)
MD_String8 path = MD_PushStringF("%.*s/%.*s",
MD_StringExpand(folder),
MD_StringExpand(file_info.filename));
MD_PushSibling(&first_root, &last_root, MD_NilNode(), MD_ParseWholeFile(path));
MD_PushSibling(&first_root, &last_root, MD_NilNode(), MD_ParseWholeFile(path).node);
}
}
}
+1
View File
@@ -0,0 +1 @@
{
+1
View File
@@ -0,0 +1 @@
(
+1
View File
@@ -0,0 +1 @@
}
+1
View File
@@ -0,0 +1 @@
'
+25
View File
@@ -0,0 +1,25 @@
// Sample code to demonstrate syntax error detection and reporting
#include "md.h"
#include "md.c"
int main(int argument_count, char **arguments)
{
for(int i = 1; i < argument_count; i += 1)
{
MD_ParseResult parse = MD_ParseWholeFile(MD_S8CString(arguments[i]));
if(parse.first_error)
{
for(MD_Error *error = parse.first_error; error; error = error->next)
{
MD_OutputError(stderr, error);
}
}
else
{
printf("No error in file %s\n", arguments[i]);
}
}
return 0;
}
+7 -2
View File
@@ -482,6 +482,8 @@ struct MD_Error
MD_String8 string;
MD_String8 filename;
MD_Node *node;
MD_b32 catastrophic;
MD_CodeLoc location;
};
typedef struct MD_ParseCtx MD_ParseCtx;
@@ -496,6 +498,7 @@ struct MD_ParseCtx
MD_String8 file_contents;
MD_NodeTable namespace_table;
MD_Node *selected_namespace;
MD_b32 catastrophic_error;
};
typedef struct MD_ParseResult MD_ParseResult;
@@ -619,6 +622,7 @@ MD_FUNCTION MD_b32 MD_CharIsAlphaUpper(MD_u8 c);
MD_FUNCTION MD_b32 MD_CharIsAlphaLower(MD_u8 c);
MD_FUNCTION MD_b32 MD_CharIsDigit(MD_u8 c);
MD_FUNCTION MD_b32 MD_CharIsSymbol(MD_u8 c);
MD_FUNCTION MD_b32 MD_CharIsReservedSymbol(MD_u8 c);
MD_FUNCTION MD_b32 MD_CharIsSpace(MD_u8 c);
MD_FUNCTION MD_u8 MD_CharToUpper(MD_u8 c);
MD_FUNCTION MD_u8 MD_CharToLower(MD_u8 c);
@@ -707,8 +711,8 @@ MD_FUNCTION MD_b32 MD_Parse_TokenMatch(MD_Token token, MD_String8 string
MD_FUNCTION MD_b32 MD_Parse_Require(MD_ParseCtx *ctx, MD_String8 string, MD_TokenKind kind);
MD_FUNCTION MD_b32 MD_Parse_RequireKind(MD_ParseCtx *ctx, MD_TokenKind kind, MD_Token *out_token);
MD_FUNCTION MD_ParseResult MD_ParseOneNode (MD_String8 filename, MD_String8 contents);
MD_FUNCTION MD_Node * MD_ParseWholeString (MD_String8 filename, MD_String8 contents);
MD_FUNCTION MD_Node * MD_ParseWholeFile (MD_String8 filename);
MD_FUNCTION MD_ParseResult MD_ParseWholeString (MD_String8 filename, MD_String8 contents);
MD_FUNCTION MD_ParseResult MD_ParseWholeFile (MD_String8 filename);
//~ Tree/List Building
MD_FUNCTION MD_b32 MD_NodeIsNil(MD_Node *node);
@@ -745,6 +749,7 @@ MD_FUNCTION void MD_NodeWarning(MD_Node *node, MD_String8 str);
MD_FUNCTION void MD_NodeMessageF(MD_Node *node, MD_MessageKind kind, char *fmt, ...);
MD_FUNCTION void MD_NodeErrorF(MD_Node *node, char *fmt, ...);
MD_FUNCTION void MD_NodeWarningF(MD_Node *node, char *fmt, ...);
MD_FUNCTION void MD_OutputError(FILE *f, MD_Error *error);
//~ Tree Comparison/Verification
MD_FUNCTION MD_b32 MD_NodeMatch(MD_Node *a, MD_Node *b, MD_StringMatchFlags str_flags, MD_NodeMatchFlags node_flags);
+153 -39
View File
@@ -110,6 +110,13 @@ MD_CharIsSymbol(MD_u8 c)
c == '?' || c == '|' || c == '\\');
}
MD_FUNCTION_IMPL MD_b32
MD_CharIsReservedSymbol(MD_u8 c)
{
return (c == '{' || c == '}' || c == '(' || c == ')' ||
c == '[' || c == ']' || c == '#');
}
MD_FUNCTION_IMPL MD_b32
MD_CharIsSpace(MD_u8 c)
{
@@ -1091,6 +1098,23 @@ MD_TokenizerScanEscaped(MD_u8 *at, MD_u8 *one_past_last, MD_u8 c)
return at;
}
MD_PRIVATE_FUNCTION_IMPL MD_u32
_MD_ComputeTextLiteralChop(MD_u32 bytes_to_skip, MD_u8 *beg, MD_u8 *end)
{
// NOTE(mal): By counting the bytes to chop off text literals and encoding exact token.string and
// token.outer_string we delegate the responsibility of generating errors to the parser
// That way, we can avoid generating errors during peeks and save them for actual token
// consumption. That allows predictable error ordering.
MD_u32 result = 0;
MD_u8 *skip_end = beg + bytes_to_skip;
MD_u8 *chop_beg = end - bytes_to_skip;
if(skip_end <= chop_beg && MD_StringMatch(MD_S8(beg, bytes_to_skip), MD_S8(chop_beg, bytes_to_skip), 0))
{
result = bytes_to_skip;
}
return result;
}
MD_FUNCTION_IMPL MD_Token
MD_Parse_LexNext(MD_ParseCtx *ctx)
{
@@ -1184,17 +1208,18 @@ MD_Parse_LexNext(MD_ParseCtx *ctx)
token.kind = MD_TokenKind_StringLiteral;
if (at + 2 < one_past_last && at[1] == '`' && at[2] == '`')
{
skip_n = chop_n = 3;
skip_n = 3;
at += 3;
MD_TokenizerScan(!(at + 2 < one_past_last && at[0] == '`' && at[1] == '`' && at[2] == '`'));
at += 3;
}
else
{
skip_n = chop_n = 1;
skip_n = 1;
at += 1;
at = MD_TokenizerScanEscaped(at, one_past_last, '`');
}
chop_n = _MD_ComputeTextLiteralChop(skip_n, first, at);
}break;
// NOTE(allen): Strings
@@ -1203,17 +1228,18 @@ MD_Parse_LexNext(MD_ParseCtx *ctx)
token.kind = MD_TokenKind_StringLiteral;
if (at + 2 < one_past_last && at[1] == '"' && at[2] == '"')
{
skip_n = chop_n = 3;
skip_n = 3;
at += 3;
MD_TokenizerScan(!(at + 2 < one_past_last && at[0] == '"' && at[1] == '"' && at[2] == '"'));
at += 3;
}
else
{
skip_n = chop_n = 1;
skip_n = 1;
at += 1;
at = MD_TokenizerScanEscaped(at, one_past_last, '"');
}
chop_n = _MD_ComputeTextLiteralChop(skip_n, first, at);
}break;
case '\'':
@@ -1221,7 +1247,7 @@ MD_Parse_LexNext(MD_ParseCtx *ctx)
if (at + 2 < one_past_last && at[1] == '\'' && at[2] == '\'')
{
token.kind = MD_TokenKind_StringLiteral;
skip_n = chop_n = 3;
skip_n = 3;
at += 3;
MD_TokenizerScan(!(at + 2 < one_past_last && at[0] == '\'' && at[1] == '\'' && at[2] == '\''));
at += 3;
@@ -1229,10 +1255,11 @@ MD_Parse_LexNext(MD_ParseCtx *ctx)
else
{
token.kind = MD_TokenKind_CharLiteral;
skip_n = chop_n = 1;
skip_n = 1;
at += 1;
at = MD_TokenizerScanEscaped(at, one_past_last, '\'');
}
chop_n = _MD_ComputeTextLiteralChop(skip_n, first, at);
}break;
// NOTE(allen): Identifiers, Numbers, Operators
@@ -1362,15 +1389,62 @@ MD_Parse_RequireKind(MD_ParseCtx *ctx, MD_TokenKind kind, MD_Token *out_token)
return result;
}
MD_PRIVATE_FUNCTION_IMPL void
_MD_Error(MD_ParseCtx *ctx, char *fmt, ...)
MD_PRIVATE_FUNCTION_IMPL MD_CodeLoc
_MD_CodeLocFromFileOffset(MD_String8 filename, MD_u8 * file_contents, MD_u8 *at)
{
MD_Error *error = _MD_PushArray(_MD_GetCtx(), MD_Error, 1);
error->filename = ctx->filename;
va_list args;
va_start(args, fmt);
error->string = MD_PushStringFV(fmt, args);
va_end(args);
MD_CodeLoc loc;
loc.filename = filename;
loc.line = 1;
loc.column = 1;
for(MD_u64 i = 0; file_contents[i]; i += 1)
{
if(file_contents[i] == '\n')
{
loc.line += 1;
loc.column = 1;
}
else
{
loc.column += 1;
}
if(file_contents + i >= at)
{
break;
}
}
return loc;
}
MD_PRIVATE_FUNCTION_IMPL void
_MD_Error(MD_ParseCtx *ctx, MD_Node *node, MD_u8 *at, MD_b32 catastrophic, char *fmt, ...)
{
if(!ctx->catastrophic_error) // NOTE(mal): Can't trust parsing errors after first catastrophic error
{
MD_Error *error = _MD_PushArray(_MD_GetCtx(), MD_Error, 1);
error->node = node;
error->catastrophic = catastrophic;
error->location = _MD_CodeLocFromFileOffset(ctx->filename, ctx->file_contents.str, at-1);
error->filename = ctx->filename;
va_list args;
va_start(args, fmt);
error->string = MD_PushStringFV(fmt, args);
va_end(args);
if(ctx->last_error)
{
ctx->last_error->next = error;
ctx->last_error = error;
}
else
{
ctx->first_error = ctx->last_error = error;
}
if(catastrophic)
{
ctx->catastrophic_error = 1;
}
}
}
MD_PRIVATE_FUNCTION_IMPL MD_Node *
@@ -1431,6 +1505,16 @@ _MD_SetNodeFlagsByToken(MD_Node *node, MD_Token token)
#undef Flag
}
MD_PRIVATE_FUNCTION_IMPL MD_b32
_MD_StringLiteralIsBalanced(MD_Token token)
{
MD_b32 result = 0;
MD_u64 front_len = token.string.str - token.outer_string.str;
MD_u64 back_len = (token.outer_string.str + token.outer_string.size) - (token.string.str + token.string.size);
result = (front_len == back_len);
return result;
}
MD_PRIVATE_FUNCTION_IMPL MD_ParseResult
_MD_ParseOneNode(MD_ParseCtx *ctx)
{
@@ -1542,6 +1626,31 @@ _MD_ParseOneNode(MD_ParseCtx *ctx)
{
result.node = MD_MakeNodeFromToken(MD_NodeKind_Label, ctx->filename, ctx->file_contents.str, ctx->at, token);
_MD_SetNodeFlagsByToken(result.node, token);
if(token.kind == MD_TokenKind_CharLiteral || token.kind == MD_TokenKind_StringLiteral)
{
if(!_MD_StringLiteralIsBalanced(token))
{
_MD_Error(ctx, result.node, at_first, 1, "Unbalanced text literal \"%.*s\"",
MD_StringExpand(token.outer_string));
}
}
else if(token.kind == MD_TokenKind_Symbol && token.string.size == 1 && MD_CharIsReservedSymbol(token.string.str[0]))
{
MD_u8 c = token.string.str[0];
const char *error_message = 0;
if(c == '}' || c == ']' || c == ')')
{
error_message = "Unbalanced";
}
else
{
error_message = "Unexpected reserved symbol";
}
_MD_Error(ctx, result.node, at_first, 1, "%s \"%c\"", error_message, c);
}
// NOTE(rjf): Children
if(MD_Parse_Require(ctx, MD_S8Lit(":"), MD_TokenKind_Symbol))
{
@@ -1587,9 +1696,10 @@ _MD_ParseOneNode(MD_ParseCtx *ctx)
comment_after = comment_token.string;
}
result.bytes_parsed = (MD_u64)(ctx->at - at_first);
if(!MD_NodeIsNil(result.node))
{
result.bytes_parsed = (MD_u64)(ctx->at - at_first);
result.node->first_tag = first_tag;
result.node->last_tag = last_tag;
for(MD_Node *tag = first_tag; !MD_NodeIsNil(tag); tag = tag->next)
@@ -1671,13 +1781,21 @@ _MD_ParseSet(MD_ParseCtx *ctx, MD_Node *parent, _MD_ParseSetFlags flags,
goto end_parse;
}
}
MD_ParseResult parse = _MD_ParseOneNode(ctx);
MD_Node *child = parse.node;
child->flags |= next_child_flags;
next_child_flags = 0;
if(MD_NodeIsNil(child))
{
if(brace || paren || bracket)
{
char delimiter_char = 0;
if(brace) delimiter_char = '{';
else if(paren) delimiter_char = '(';
else if(bracket) delimiter_char = '[';
_MD_Error(ctx, parse.node, ctx->at - parse.bytes_parsed, 1, "Unbalanced \"%c\"", delimiter_char);
}
goto end_parse;
}
else
@@ -1771,9 +1889,10 @@ MD_ParseOneNode(MD_String8 filename, MD_String8 contents)
return _MD_ParseOneNode(&ctx);
}
MD_FUNCTION MD_Node *
MD_FUNCTION MD_ParseResult
MD_ParseWholeString(MD_String8 filename, MD_String8 contents)
{
MD_ParseResult result = MD_ZERO_STRUCT;
MD_Node *root = MD_MakeNodeFromString(MD_NodeKind_File, filename, contents.str, contents.str, MD_PushStringF("`DD Parsed From \"%.*s\"`", MD_StringExpand(filename)));
if(contents.size > 0)
{
@@ -1806,11 +1925,14 @@ MD_ParseWholeString(MD_String8 filename, MD_String8 contents)
next_child_flags |= MD_NodeFlag_AfterSemicolon;
}
}
result.bytes_parsed = (MD_u64)(ctx.at - contents.str);
result.first_error = ctx.first_error;
}
return root;
result.node = root;
return result;
}
MD_FUNCTION_IMPL MD_Node *
MD_FUNCTION_IMPL MD_ParseResult
MD_ParseWholeFile(MD_String8 filename)
{
return MD_ParseWholeString(filename, MD_LoadEntireFile(filename));
@@ -2020,26 +2142,7 @@ MD_NodeHasTag(MD_Node *node, MD_String8 tag_string)
MD_FUNCTION_IMPL MD_CodeLoc
MD_CodeLocFromNode(MD_Node *node)
{
MD_CodeLoc loc;
loc.filename = node->filename;
loc.line = 1;
loc.column = 1;
for(MD_u64 i = 0; node->file_contents[i]; i += 1)
{
if(node->file_contents[i] == '\n')
{
loc.line += 1;
loc.column = 1;
}
else
{
loc.column += 1;
}
if(node->file_contents + i == node->at)
{
break;
}
}
MD_CodeLoc loc = _MD_CodeLocFromFileOffset(node->filename, node->file_contents, node->at);
return loc;
}
@@ -2144,6 +2247,17 @@ MD_NodeWarningF(MD_Node *node, char *fmt, ...)
va_end(args);
}
MD_FUNCTION_IMPL void
MD_OutputError(FILE *f, MD_Error *error)
{
const char *kind_name = "error";
fprintf(stderr, "%.*s:%i:%i: %s: %.*s\n",
MD_StringExpand(error->location.filename),
error->location.line, error->location.column,
kind_name,
MD_StringExpand(error->string));
}
MD_GLOBAL MD_Expr _md_nil_expr =
{
&_md_nil_node,
+2 -2
View File
@@ -473,7 +473,7 @@ static int StringCompare(MD_String8 a, MD_String8 b)
int main(int argument_count, char **arguments)
{
MD_Node *grammar = MD_ParseWholeFile(MD_S8Lit("tests/grammar.md"));
MD_Node *grammar = MD_ParseWholeFile(MD_S8Lit("tests/grammar.md")).node;
// NOTE(mal): In order to get a BNF-like syntax, I feed the MD output through two transformations:
// 1) Tag []-style sets as optional
@@ -688,7 +688,7 @@ int main(int argument_count, char **arguments)
MD_u32 i_test = 0;
for(Test *test = first_test; test; test = test->next)
{
MD_Node *file_node = MD_ParseWholeString(MD_S8Lit(""), test->input);
MD_Node *file_node = MD_ParseWholeString(MD_S8Lit(""), test->input).node;
file_node->string = file_node->whole_string = (MD_String8){0};
#if DEBUG_PRINT_GENERATED_TESTS