kill old parser

This commit is contained in:
ryanfleury
2021-06-29 20:48:37 -06:00
parent 055c152279
commit f73f643aed
2 changed files with 14 additions and 936 deletions
+11 -933
View File
@@ -1913,6 +1913,17 @@ MD_ParseOneNode(MD_String8 string, MD_u64 offset)
off = colon_check_off;
//- rjf: prohibit tags here
// TODO(allen): This poking in an error "from afar" thing seems
// like a bad sign to me. First it took a bit of digging for me to
// understand how this code actually detects the errors it says it
// does. Second it's kind of unclear that this should be illegal.
// I mean we can do these:
// `label: @tag child`
// `label: child @tag {children}`
// `label: @tag child`
// I do get *why* this is an odd thing to allow, but it's weird either way.
// Third, looks like this also is throwing out an error in the totally legal case:
// `label:{@tag {bar}}`
for(MD_u64 tag_check_off = off; tag_check_off < string.size;)
{
MD_Token token = MD_TokenFromString(MD_StringSkip(string, tag_check_off));
@@ -2024,939 +2035,6 @@ MD_ParseWholeString(MD_String8 filename, MD_String8 contents)
return result;
}
#if 0
MD_FUNCTION_IMPL void
MD_PushNodeError(MD_ParseCtx *ctx, MD_Node *node, MD_MessageKind kind, MD_String8 str){
// TODO(allen): pass over this... the catastrophic error logic is a bit hard
// for me to follow.
// NOTE(mal): Sort errors. Traverse the whole list assuming it will be short.
// The alternative is to drop a prev pointer into MD_Error and search backwards
MD_Error *prev_error = 0;
for(MD_Error *e = ctx->first_error; e; e = e->next)
{
if(e->node->at < node->at)
{
prev_error = e;
}
else
{
break;
}
}
// NOTE(mal): Ignore errors after first catastrophic error
if(ctx->error_level < MD_MessageKind_CatastrophicError || !prev_error || prev_error->next)
{
// TODO(allen): put memory on ctx? put memory on persistent arena?
// alloc and fill error
MD_Error *error = MD_PushArray(MD_Error, 1);
error->node = node;
error->kind = kind;
error->string = str;
// insert error
if(prev_error)
{
error->next = prev_error->next;
prev_error->next = error;
}
else
{
error->next = ctx->first_error;
ctx->first_error = error;
}
if(ctx->last_error == prev_error)
{
ctx->last_error = error;
}
// set error level
if(kind > ctx->error_level)
{
ctx->error_level = kind;
}
}
}
#endif
#if 0
MD_FUNCTION void
MD_PushNodeErrorF(MD_ParseCtx *ctx, MD_Node *node, MD_MessageKind kind, char *fmt, ...){
// TODO(allen): use memory from ctx? use persistent memory?
va_list args;
va_start(args, fmt);
MD_PushNodeError(ctx, node, kind, MD_PushStringFV(fmt, args));
va_end(args);
}
#endif
#if 0
MD_FUNCTION void
MD_PushTokenError(MD_ParseCtx *ctx, MD_Token token, MD_MessageKind kind, MD_String8 str){
MD_Node *stub_file = MD_MakeNode(MD_NodeKind_ErrorMarker, ctx->file_contents, ctx->file_contents, ctx->file_contents.str);
MD_Node *stub = MD_MakeNode(MD_NodeKind_ErrorMarker, token.string, token.outer_string, token.outer_string.str);
MD_PushNodeError(ctx, stub, kind, str);
MD_PushChild(stub_file, stub);
}
#endif
#if 0
MD_FUNCTION void
MD_PushTokenErrorF(MD_ParseCtx *ctx, MD_Token token, MD_MessageKind kind, char *fmt, ...){
// TODO(allen): use memory from ctx? use persistent memory?
va_list args;
va_start(args, fmt);
MD_PushTokenError(ctx, token, kind, MD_PushStringFV(fmt, args));
va_end(args);
}
#endif
#if 0
MD_FUNCTION_IMPL MD_ParseCtx
MD_Parse_InitializeCtx(MD_String8 filename, MD_String8 contents)
{
MD_ParseCtx ctx = MD_ZERO_STRUCT;
ctx.at = contents.str;
ctx.file_contents = contents;
ctx.filename = filename;
return ctx;
}
#endif
#if 0
MD_FUNCTION_IMPL void
MD_Parse_Bump(MD_ParseCtx *ctx, MD_Token token)
{
ctx->at = token.outer_string.str + token.outer_string.size;
}
#endif
#if 0
MD_FUNCTION_IMPL void
MD_Parse_BumpNext(MD_ParseCtx *ctx)
{
MD_Parse_Bump(ctx, MD_Parse_LexNext(ctx));
}
#endif
#if 0
MD_FUNCTION_IMPL MD_Token
MD_Parse_LexNext(MD_ParseCtx *ctx)
{
MD_Token token;
MD_MemoryZero(&token, sizeof(token));
MD_u8 *one_past_last = ctx->file_contents.str + ctx->file_contents.size;
MD_u8 *first = ctx->at;
if (first < one_past_last)
{
MD_u8 *at = first;
MD_u32 skip_n = 0;
MD_u32 chop_n = 0;
#define MD_TokenizerScan(cond) for (; at < one_past_last && (cond); at += 1)
switch (*at)
{
// NOTE(allen): Whitespace parsing
case '\n':
{
token.kind = MD_TokenKind_Newline;
at += 1;
}break;
case ' ': case '\r': case '\t': case '\f': case '\v':
{
token.kind = MD_TokenKind_Whitespace;
at += 1;
MD_TokenizerScan(*at == ' ' || *at == '\r' || *at == '\t' || *at == '\f' || *at == '\v');
}break;
// NOTE(allen): Comment parsing
case '/':
{
if (at + 1 < one_past_last)
{
if (at[1] == '/')
{
// TODO(allen): This seems like an odd choice to me. What about two spaces!?
// What about an extra /? I'm wondering if there are other places where we make
// this kind of judgement call a lot, or is this the only one? Maybe the user
// should just always skip-chop whitespace if they want to clean this kind of
// thing up? They're going to have to if they ever use two spaces anyways, right?
// NOTE(rjf): Trim off the first //, and a space after it if one is there.
if(at+2 < one_past_last &&
at[2] == ' ')
{
skip_n = 3;
}
else
{
skip_n = 2;
}
at += skip_n;
token.kind = MD_TokenKind_Comment;
MD_TokenizerScan(*at != '\n' && *at != '\r');
}
else if (at[1] == '*')
{
// TODO(allen): proposal:
// 1. only set `kind = Comment` in the `counter == 0` case
// 2. otherwise set `kind = RunOnComment` (or something)
// maybe also emit an error *from here* or signal to a system that
// runs later that there are token errors to emit.
// Or: keep the `kind = Comment` but in the `counter != 0` case
// set some kind of error/unclosed/run-on flag on the token.
at += 2;
token.kind = MD_TokenKind_Comment;
skip_n = 2;
int counter = 1;
for (;at < one_past_last && counter > 0; at += 1)
{
if (at + 1 < one_past_last)
{
if (at[0] == '*' && at[1] == '/')
{
at += 1;
counter -= 1;
}
else if (at[0] == '/' && at[1] == '*')
{
at += 1;
counter += 1;
}
}
}
if(counter == 0)
{
chop_n = 2;
}
}
}
if (token.kind == MD_TokenKind_Nil) goto symbol_lex;
}break;
// NOTE(allen): Strings
case '"':
case '\'':
case '`':
{
// TODO(allen): proposal:
// go see the proposal in the block comment lexer, same idea here?
// determine delimiter setup
MD_u8 d = *at;
MD_b32 is_triplet = (at + 2 < one_past_last && at[1] == d && at[2] == d);
// lex triple-delimiter string
if (is_triplet)
{
skip_n = 3;
at += 3;
MD_u32 consecutive_d = 0;
for (;;)
{
// fail condition
if (at >= one_past_last){
break;
}
if(at[0] == d)
{
consecutive_d += 1;
at += 1;
// close condition
if (consecutive_d == 3){
chop_n = 3;
break;
}
}
else
{
consecutive_d = 0;
// escaping rule
if(at[0] == '\\')
{
at += 1;
if(at < one_past_last && (at[0] == d || at[0] == '\\'))
{
at += 1;
}
}
else{
at += 1;
}
}
}
}
// lex single-delimiter string
if (!is_triplet)
{
skip_n = 1;
at += 1;
for (;at < one_past_last;)
{
// close condition
if (*at == d){
at += 1;
chop_n = 1;
break;
}
// fail condition
if (*at == '\n'){
break;
}
// escaping rule
if (at[0] == '\\'){
at += 1;
if (at < one_past_last && (at[0] == d || at[0] == '\\')){
at += 1;
}
}
else{
at += 1;
}
}
}
// set token kind
if(is_triplet)
{
switch(d)
{
case '\'': token.kind = MD_TokenKind_StringLiteralSingleQuoteTriplet; break;
case '"': token.kind = MD_TokenKind_StringLiteralDoubleQuoteTriplet; break;
case '`': token.kind = MD_TokenKind_StringLiteralTickTriplet; break;
default: break;
}
}
else
{
switch(d)
{
case '\'': token.kind = MD_TokenKind_StringLiteralSingleQuote; break;
case '"': token.kind = MD_TokenKind_StringLiteralDoubleQuote; break;
case '`': token.kind = MD_TokenKind_StringLiteralTick; break;
default: break;
}
}
}break;
// NOTE(allen): Identifiers, Numbers, Operators
default:
{
if (MD_CharIsAlpha(*at) || *at == '_')
{
token.kind = MD_TokenKind_Identifier;
at += 1;
MD_TokenizerScan(MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '_');
}
else if (MD_CharIsDigit(*at) ||
(at + 1 < one_past_last && at[0] == '-' && MD_CharIsDigit(at[1])))
{
token.kind = MD_TokenKind_NumericLiteral;
at += 1;
MD_TokenizerScan(MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '.');
}
else if (MD_CharIsSymbol(*at))
{
symbol_lex:
token.kind = MD_TokenKind_Symbol;
at += 1;
}
else
{
token.kind = MD_TokenKind_BadCharacter;
at += 1;
}
}break;
}
token.outer_string = MD_S8Range(first, at);
token.string = MD_StringSubstring(token.outer_string, skip_n, token.outer_string.size - chop_n);
ctx->at = at;
}
return token;
}
#endif
#if 0
MD_FUNCTION_IMPL MD_Token
MD_Parse_PeekSkipSome(MD_ParseCtx *ctx, MD_TokenGroups skip_groups)
{
MD_ParseCtx ctx_restore = *ctx;
MD_b32 skip_comment = (skip_groups & MD_TokenGroup_Comment);
MD_b32 skip_whitespace = (skip_groups & MD_TokenGroup_Whitespace);
MD_b32 skip_regular = (skip_groups & MD_TokenGroup_Regular);
MD_Token result;
MD_MemoryZero(&result, sizeof(result));
loop:
{
result = MD_Parse_LexNext(ctx);
if ((skip_comment && MD_TokenKindIsComment(result.kind)) ||
(skip_whitespace && MD_TokenKindIsWhitespace(result.kind)) ||
(skip_regular && MD_TokenKindIsRegular(result.kind))){
MD_Parse_Bump(ctx, result);
goto loop;
}
}
{
// TODO(allen): I'm not a fan of what this implies.
*ctx = ctx_restore;
}
return result;
}
#endif
#if 0
MD_FUNCTION_IMPL MD_b32
MD_Parse_Require(MD_ParseCtx *ctx, MD_String8 string, MD_TokenKind kind)
{
int result = 0;
MD_Token token_any = MD_Parse_PeekSkipSome(ctx, 0);
MD_Token token_regular;
if(MD_StringMatch(token_any.string, string, 0) && token_any.kind == kind)
{
result = 1;
MD_Parse_Bump(ctx, token_any);
goto end;
}
token_regular = MD_Parse_PeekSkipSome(ctx, MD_TokenGroup_Comment|MD_TokenGroup_Whitespace);
if(MD_StringMatch(token_regular.string, string, 0) && token_regular.kind == kind)
{
result = 1;
MD_Parse_Bump(ctx, token_regular);
goto end;
}
end:;
return result;
}
#endif
#if 0
MD_FUNCTION_IMPL MD_b32
MD_Parse_RequireKind(MD_ParseCtx *ctx, MD_TokenKind kind, MD_Token *out_token)
{
int result = 0;
MD_TokenGroups skip_groups = MD_TokenGroup_Comment|MD_TokenGroup_Whitespace;
if (MD_TokenKindIsWhitespace(kind))
{
skip_groups &= ~MD_TokenGroup_Whitespace;
}
if (MD_TokenKindIsComment(kind))
{
skip_groups &= ~MD_TokenGroup_Comment;
}
MD_Token token = MD_Parse_PeekSkipSome(ctx, skip_groups);
if(token.kind == kind)
{
result = 1;
MD_Parse_Bump(ctx, token);
if(out_token)
{
*out_token = token;
}
}
return result;
}
#endif
#if 0
MD_FUNCTION_IMPL void
MD_Parse_Set(MD_ParseCtx *ctx, MD_Node *parent, MD_ParseSetFlags flags)
{
MD_Token initial_token = MD_Parse_PeekSkipSome(ctx, MD_TokenGroup_Comment|MD_TokenGroup_Whitespace);
// check for set opener
MD_u8 set_opener = 0;
if((flags & MD_ParseSetFlag_Brace) &&
MD_Parse_Require(ctx, MD_S8Lit("{"), MD_TokenKind_Symbol))
{
set_opener = '{';
}
else if((flags & MD_ParseSetFlag_Paren) &&
MD_Parse_Require(ctx, MD_S8Lit("("), MD_TokenKind_Symbol))
{
set_opener = '(';
}
else if((flags & MD_ParseSetFlag_Bracket) &&
MD_Parse_Require(ctx, MD_S8Lit("["), MD_TokenKind_Symbol))
{
set_opener = '[';
}
// attach left-symbol flag to parent
switch (set_opener){
case '{':
{
parent->flags |= MD_NodeFlag_BraceLeft;
}break;
case '(':
{
parent->flags |= MD_NodeFlag_ParenLeft;
}break;
case '[':
{
parent->flags |= MD_NodeFlag_BracketLeft;
}break;
}
// determine set close rule
MD_b32 close_with_brace = 0;
MD_b32 close_with_paren = 0;
MD_b32 close_with_separator = 0;
switch (set_opener){
default:
{
close_with_separator = (!!(flags & MD_ParseSetFlag_Implicit));
}break;
case '{':
{
close_with_brace = 1;
}break;
case '(':
case '[':
{
close_with_paren = 1;
}break;
}
// NOTE(rjf): Parse children.
if((set_opener != 0) || close_with_separator)
{
MD_NodeFlags next_child_flags = 0;
for(;;)
{
if(close_with_brace)
{
if(MD_Parse_Require(ctx, MD_S8Lit("}"), MD_TokenKind_Symbol))
{
parent->flags |= MD_NodeFlag_BraceRight;
goto end_parse;
}
}
else if(close_with_paren)
{
if((flags & MD_ParseSetFlag_Paren) &&
MD_Parse_Require(ctx, MD_S8Lit(")"), MD_TokenKind_Symbol))
{
parent->flags |= MD_NodeFlag_ParenRight;
goto end_parse;
}
else if((flags & MD_ParseSetFlag_Bracket) &&
MD_Parse_Require(ctx, MD_S8Lit("]"), MD_TokenKind_Symbol))
{
parent->flags |= MD_NodeFlag_BracketRight;
goto end_parse;
}
}
else
{
MD_Token peek = MD_Parse_PeekSkipSome(ctx, MD_TokenGroup_Whitespace | MD_TokenGroup_Comment);
if(peek.kind == MD_TokenKind_Symbol &&
(MD_StringMatch(peek.string, MD_S8Lit("}"), 0) ||
MD_StringMatch(peek.string, MD_S8Lit(")"), 0) ||
MD_StringMatch(peek.string, MD_S8Lit("]"), 0)))
{
goto end_parse;
}
}
// NOTE(allen): parse the next node
MD_ParseResult parse = MD_ParseOneNodeFromCtx(ctx);
MD_Node *child = parse.node;
if(MD_NodeIsNil(child))
{
if(set_opener != 0)
{
MD_PushTokenErrorF(ctx, initial_token, MD_MessageKind_CatastrophicError,
"Unbalanced \"%c\"", set_opener);
}
goto end_parse;
}
// connect node into graph
MD_PushChild(parent, child);
// check trailing symbol
MD_u32 symbol_flags = 0;
if (!close_with_separator){
if(MD_Parse_Require(ctx, MD_S8Lit(","), MD_TokenKind_Symbol))
{
symbol_flags = MD_NodeFlag_BeforeComma;
}
else if(MD_Parse_Require(ctx, MD_S8Lit(";"), MD_TokenKind_Symbol))
{
symbol_flags = MD_NodeFlag_BeforeSemicolon;
}
}
// fill flags from surrounding context
child->flags |= next_child_flags|symbol_flags;
// setup next_child_flags
next_child_flags = MD_NodeFlag_AfterFromBefore(symbol_flags);
// separator close condition
if(close_with_separator)
{
MD_Token next_token = MD_Parse_PeekSkipSome(ctx, 0);
if(next_token.kind == MD_TokenKind_Newline ||
(next_token.kind == MD_TokenKind_Symbol &&
(MD_StringMatch(next_token.string, MD_S8Lit(","), 0) ||
MD_StringMatch(next_token.string, MD_S8Lit(";"), 0) ||
MD_StringMatch(next_token.string, MD_S8Lit("}"), 0) ||
MD_StringMatch(next_token.string, MD_S8Lit("]"), 0) ||
MD_StringMatch(next_token.string, MD_S8Lit(")"), 0))))
{
goto end_parse;
}
}
// TODO(allen): I find it kind of concerning that ParseWholeString and
// ParseOneNode are both doing this. I did some refactors in the
// ParseWholeString to break it down into flatter blocks, not realizing
// very similar logic happens here too.
// I also see that these really are slightly different, but it seems
// like it should be possible to express the whole-string case as a
// special case of this and avoid the duplication.
}
}
end_parse:;
}
#endif
#if 0
MD_FUNCTION_IMPL MD_ParseResult
MD_ParseOneNodeFromCtx(MD_ParseCtx *ctx)
{
MD_u8 *at_first = ctx->at;
MD_ParseResult result = MD_ZERO_STRUCT;
result.node = MD_NilNode();
MD_Error *ctx_last_error = ctx->last_error;
MD_Token token;
MD_MemoryZero(&token, sizeof(token));
MD_Node *first_tag = 0;
MD_Node *last_tag = 0;
_MD_ParseTagList(ctx, &first_tag, &last_tag);
// NOTE(rjf): Parse the comment preceding this node.
MD_String8 comment_before = MD_ZERO_STRUCT;
{
MD_Token comment_token = MD_ZERO_STRUCT;
for(;;)
{
MD_Token token = MD_Parse_PeekSkipSome(ctx, 0);
if(token.kind == MD_TokenKind_Comment)
{
comment_token = token;
MD_Parse_Bump(ctx, token);
}
else if(token.kind == MD_TokenKind_Newline)
{
MD_Parse_Bump(ctx, token);
if(MD_Parse_RequireKind(ctx, MD_TokenKind_Comment, &comment_token))
{
// NOTE(mal): If more than one comment, use the last comment
}
else if(MD_Parse_RequireKind(ctx, MD_TokenKind_Newline, 0))
{
MD_MemoryZero(&comment_token, sizeof(comment_token));
}
}
else if(MD_TokenKindIsWhitespace(token.kind))
{
MD_Parse_Bump(ctx, token);
}
else
{
break;
}
}
comment_before = comment_token.string;
// TODO(allen): I find this odd. Wouldn't it have been easier to generate this
// durring or right after the lexing phase?
if(!_MD_CommentIsSyntacticallyCorrect(comment_token))
{
MD_String8 capped = MD_StringPrefix(comment_token.outer_string, MD_UNTERMINATED_TOKEN_LEN_CAP);
MD_PushTokenErrorF(ctx, comment_token, MD_MessageKind_CatastrophicError,
"Unterminated comment \"%.*s\"", MD_StringExpand(capped));
}
}
MD_TokenGroups skip_groups = MD_TokenGroup_Whitespace|MD_TokenGroup_Comment;
MD_Token next_token = MD_Parse_PeekSkipSome(ctx, skip_groups);
retry:
// NOTE(rjf): Unnamed Sets
if(next_token.kind == MD_TokenKind_Symbol &&
(MD_StringMatch(next_token.string, MD_S8Lit("("), 0) ||
MD_StringMatch(next_token.string, MD_S8Lit("{"), 0) ||
MD_StringMatch(next_token.string, MD_S8Lit("["), 0)))
{
result.node = MD_MakeNode(MD_NodeKind_Label, MD_S8Lit(""), MD_S8Lit(""), next_token.outer_string.str);
MD_Parse_Set(ctx, result.node,
MD_ParseSetFlag_Paren |
MD_ParseSetFlag_Brace |
MD_ParseSetFlag_Bracket);
goto end_parse;
}
// NOTE(rjf): Labels
else if(next_token.kind == MD_TokenKind_Identifier ||
next_token.kind == MD_TokenKind_NumericLiteral ||
next_token.kind == MD_TokenKind_StringLiteralTick ||
next_token.kind == MD_TokenKind_StringLiteralSingleQuote ||
next_token.kind == MD_TokenKind_StringLiteralDoubleQuote ||
next_token.kind == MD_TokenKind_StringLiteralTickTriplet ||
next_token.kind == MD_TokenKind_StringLiteralSingleQuoteTriplet ||
next_token.kind == MD_TokenKind_StringLiteralDoubleQuoteTriplet ||
next_token.kind == MD_TokenKind_Symbol )
{
MD_Parse_Bump(ctx, next_token);
result.node = MD_MakeNode(MD_NodeKind_Label, next_token.string, next_token.outer_string, next_token.outer_string.str);
result.node->flags |= MD_NodeFlagsFromTokenKind(next_token.kind);
// TODO(rjf): Before we were just able to check one kind. I think preserving
// which kind of string literal was used is very important, for the same reason
// that preserving which symbols were used to delimit a set is important.
// But, having to manage this "group of kinds" is a little bit annoying.
// Maybe we should define the set of legal kinds for certain syntactic
// contexts somewhere unified, so that the parser is never duplicating this?
//
// It's also possible that it never matters and we only ever use this group
// in one place, but I just got that "we're duplicating stuff" allergy that
// I usually get.
//
// If that turned out to be a good idea, maybe we could do something like
// MD_TokenKindIsLegalLabelHead, MD_TokenKindNeedsBalancing?? I don't know.
if(next_token.kind == MD_TokenKind_StringLiteralTick ||
next_token.kind == MD_TokenKind_StringLiteralSingleQuote ||
next_token.kind == MD_TokenKind_StringLiteralDoubleQuote ||
next_token.kind == MD_TokenKind_StringLiteralTickTriplet ||
next_token.kind == MD_TokenKind_StringLiteralSingleQuoteTriplet ||
next_token.kind == MD_TokenKind_StringLiteralDoubleQuoteTriplet)
{
if(!_MD_TokenBoundariesAreBalanced(next_token))
{
MD_String8 capped = MD_StringPrefix(next_token.outer_string, MD_UNTERMINATED_TOKEN_LEN_CAP);
MD_PushNodeErrorF(ctx, result.node, MD_MessageKind_CatastrophicError,
"Unterminated text literal \"%.*s\"", MD_StringExpand(capped));
}
}
else if(next_token.kind == MD_TokenKind_Symbol && next_token.string.size == 1 && MD_CharIsReservedSymbol(next_token.string.str[0]))
{
MD_u8 c = next_token.string.str[0];
if(c == '}' || c == ']' || c == ')')
{
MD_PushTokenErrorF(ctx, next_token, MD_MessageKind_CatastrophicError, "Unbalanced \"%c\"", c);
}
else
{
MD_PushTokenErrorF(ctx, next_token, MD_MessageKind_Error, "Unexpected reserved symbol \"%c\"",
c);
}
}
// NOTE(rjf): Children
if(MD_Parse_Require(ctx, MD_S8Lit(":"), MD_TokenKind_Symbol))
{
MD_Parse_Set(ctx, result.node,
MD_ParseSetFlag_Paren |
MD_ParseSetFlag_Brace |
MD_ParseSetFlag_Bracket |
MD_ParseSetFlag_Implicit);
// TODO(allen): This poking in an error "from afar" thing seems
// like a bad sign to me. First it took a bit of digging for me to
// understand how this code actually detects the errors it says it
// does. Second it's kind of unclear that this should be illegal.
// I mean we can do these:
// `label: @tag child`
// `label: child @tag {children}`
// `label: @tag child`
// I do get *why* this is an odd thing to allow, but it's weird either way.
// Third, looks like this also is throwing out an error in the totally legal case:
// `label:{@tag {bar}}`
// NOTE(mal): Generate error for tags in positions such as "label:@tag {children}"
MD_Node *fc = result.node->first_child;
if(fc == result.node->last_child && !MD_NodeIsNil(fc->first_tag) && // NOTE(mal): One child. Tagged.
fc->kind == MD_NodeKind_Label && fc->whole_string.size == 0) // NOTE(mal): Unlabeled set
{
for(MD_EachNode(tag, fc->first_tag))
{
MD_PushNodeErrorF(ctx, tag, MD_MessageKind_Error,
"Invalid position for tag \"%.*s\"", MD_StringExpand(tag->string));
}
}
}
goto end_parse;
}
else if(MD_Parse_RequireKind(ctx, MD_TokenKind_BadCharacter, &token))
{
MD_String8List bytes = {0};
for(int i_byte = 0; i_byte < token.outer_string.size; ++i_byte)
{
// TODO(allen): tighten up with good integer <-> string helpers
MD_PushStringToList(&bytes, MD_PushStringF("0x%02X", token.outer_string.str[i_byte]));
}
MD_String8 byte_string = MD_JoinStringList(bytes, MD_S8Lit(" "));
MD_PushTokenErrorF(ctx, token, MD_MessageKind_Error,
"Non-ASCII character \"%.*s\"", MD_StringExpand(byte_string));
goto retry;
}
end_parse:;
// NOTE(rjf): Parse comments after nodes.
MD_String8 comment_after = MD_ZERO_STRUCT;
{
MD_Token comment_token = MD_ZERO_STRUCT;
for(;;)
{
MD_Token token = MD_Parse_PeekSkipSome(ctx, 0);
if(token.kind == MD_TokenKind_Comment)
{
comment_token = token;
MD_Parse_Bump(ctx, token);
break;
}
else if(token.kind == MD_TokenKind_Newline)
{
break;
}
else if(MD_TokenKindIsWhitespace(token.kind))
{
MD_Parse_Bump(ctx, token);
}
else
{
break;
}
}
comment_after = comment_token.string;
// TODO(allen): I find this odd. Wouldn't it have been easier to generate this
// durring or right after the lexing phase?
if(!_MD_CommentIsSyntacticallyCorrect(comment_token))
{
MD_String8 capped = MD_StringPrefix(comment_token.outer_string, MD_UNTERMINATED_TOKEN_LEN_CAP);
MD_PushTokenErrorF(ctx, comment_token, MD_MessageKind_CatastrophicError,
"Unterminated comment \"%.*s\"", MD_StringExpand(capped));
}
}
result.bytes_parsed = (MD_u64)(ctx->at - at_first);
result.first_error = ctx_last_error ? ctx_last_error->next : 0;
if(!MD_NodeIsNil(result.node))
{
result.node->first_tag = first_tag;
result.node->last_tag = last_tag;
for(MD_Node *tag = first_tag; !MD_NodeIsNil(tag); tag = tag->next)
{
tag->parent = result.node;
}
result.node->comment_before = comment_before;
result.node->comment_after = comment_after;
}
return result;
}
#endif
#if 0
MD_FUNCTION_IMPL MD_ParseResult
MD_ParseOneNode(MD_String8 filename, MD_String8 contents)
{
MD_ParseCtx ctx = MD_Parse_InitializeCtx(filename, contents);
return MD_ParseOneNodeFromCtx(&ctx);
}
#endif
#if 0
MD_FUNCTION_IMPL MD_ParseResult
MD_ParseWholeString(MD_String8 filename, MD_String8 contents)
{
MD_ParseResult result = MD_ZERO_STRUCT;
MD_String8 root_string = filename;
MD_Node *root = MD_MakeNode(MD_NodeKind_File, root_string, root_string, contents.str);
if(contents.size > 0)
{
// NOTE(allen): setup parse context
MD_ParseCtx ctx = MD_Parse_InitializeCtx(filename, contents);
// NOTE(allen): parse loop
MD_NodeFlags next_child_flags = 0;
for(;;)
{
// NOTE(allen): parse the next node
MD_ParseResult parse = MD_ParseOneNodeFromCtx(&ctx);
MD_Node *child = parse.node;
if(MD_NodeIsNil(child))
{
break;
}
// connect node into graph
MD_PushChild(root, child);
// check trailing symbol
MD_u32 symbol_flags = 0;
if(MD_Parse_Require(&ctx, MD_S8Lit(","), MD_TokenKind_Symbol))
{
symbol_flags = MD_NodeFlag_BeforeComma;
}
else if(MD_Parse_Require(&ctx, MD_S8Lit(";"), MD_TokenKind_Symbol))
{
symbol_flags = MD_NodeFlag_BeforeSemicolon;
}
// fill flags from surrounding context
child->flags |= next_child_flags|symbol_flags;
// setup next_child_flags
next_child_flags = MD_NodeFlag_AfterFromBefore(symbol_flags);
}
result.bytes_parsed = (MD_u64)(ctx.at - contents.str);
result.first_error = ctx.first_error;
}
result.node = root;
return result;
}
#endif
MD_FUNCTION_IMPL MD_ParseResult
MD_ParseWholeFile(MD_String8 filename)
{
+3 -3
View File
@@ -787,7 +787,7 @@ int main(void)
MD_String8 *string = test_strings;
for (int i = 0; i < MD_ArrayCount(test_strings); i += 1, string += 1){
MD_ParseResult result = MD_ParseWholeString(file_name, *string);
TestResult((result.first_error == 0) &&
TestResult((result.errors.first == 0) &&
(result.node->first_child == result.node->last_child) &&
(result.node->first_child->flags & MD_NodeFlag_Numeric));
}
@@ -820,7 +820,7 @@ int main(void)
MD_String8 *string = test_strings;
for (int i = 0; i < MD_ArrayCount(test_strings); i += 1, string += 1){
MD_ParseResult result = MD_ParseWholeString(file_name, *string);
TestResult((result.first_error == 0) &&
TestResult((result.errors.first == 0) &&
(result.node->first_child == result.node->last_child) &&
(result.node->first_child->flags & MD_NodeFlag_Numeric));
}
@@ -844,7 +844,7 @@ int main(void)
MD_String8 *string = test_strings;
for (int i = 0; i < MD_ArrayCount(test_strings); i += 1, string += 1){
MD_ParseResult result = MD_ParseWholeString(file_name, *string);
TestResult((result.first_error == 0) &&
TestResult((result.errors.first == 0) &&
(result.node->first_child != result.node->last_child));
}
}