#define _GNU_SOURCE #include #include #include #include #include #include "parser.h" /* Consume all (non-newline) whitespace. * Returns the number of whitespace characters consumed. */ int ConsumeWhitespace(char *s) { int i = 0; while (s[i]) { switch (s[i]) { case ' ': case '\t': case '\r': case '\f': case '\v': break; default: goto end; } i++; } end: return i; } /* Return a pointer to the first line-ending character * in this line (i.e. CR in case of CRLF, LF in case of just LF, * or NUL if this line does not end with CR or LF). */ char *LineEnd(char *line) { while (*line) { switch (*line) { case '\r': if (line[1] == '\n') return line; break; case '\n': return line; break; default: break; } line++; } return line; } /* Parse a line in-place, without allocating any new memory */ enum gem_parse_result GemParseLine(struct gem_parse_state *s, struct gem_line *l) { int i = 0; char *line; char *line_end; char *line_next; enum gem_parse_result res = PARSE_LINE; line = s->buf; line_end = LineEnd(s->buf); if (*line_end == 0) line_next = line_end; else if (*line_end == '\n') line_next = line_end + 1; else if (*line_end == '\r') line_next = line_end + 2; *line_end = 0; if (strncmp(line, "```", 3) == 0) { s->preformatted = !s->preformatted; res = PARSE_NOLINE; } else if (s->preformatted) { l->type = LT_PRETEXT; l->text = line; } else if (*line == '>') { i++; i += ConsumeWhitespace(&line[i]); l->type = LT_QUOTE; l->text = &line[i]; } else if (*line == '#') { i = strchrnul(line, ' ') - line; if (i > 3) i = 3; l->level = i; i += ConsumeWhitespace(&line[i]); l->text = &line[i]; l->type = LT_HEADING; } else if (*line == '*') { i++; i += ConsumeWhitespace(&line[i]); l->type = LT_LISTITEM; l->text = &line[i]; } else if (strncmp(line, "=>", 2) == 0) { l->type = LT_LINK; i = 2; i += ConsumeWhitespace(&line[i]); l->url = &line[i]; for (; line[i] && !isspace(line[i]); i++) ; if (line[i] == 0) { l->text = l->url; } else { line[i++] = 0; i += ConsumeWhitespace(&line[i]); l->text = &line[i]; } } else { l->type = LT_TEXT; l->text = line; } s->buf = line_next; return res; } const char *gem_typename(enum gem_linetype lt) { switch (lt) { case LT_TEXT: return "text"; case LT_HEADING: return "heading"; case LT_LINK: return "link"; case LT_QUOTE: return "quote"; case LT_LISTITEM: return "list item"; case LT_PRETEXT: return "preformatted text"; default: return "unknown type"; } } // clang-format off static int VALID_CODES[] = { 10, 11, 20, 30, 31, 40, 41, 42, 43, 44, 50, 51, 52, 53, 54, 60, 61, 62 }; // clang-format on int is_valid_response_code(int code) { for (int i = 0; i < sizeof(VALID_CODES) / sizeof(int); ++i) { if (code == VALID_CODES[i]) return true; } return false; } struct gem_response GemParseResponse(char *line) { char c; struct gem_response resp; int state = 0; int resp_code; for (int idx = 0; c = line[idx]; ++idx) { switch (state) { case 0: // expecting response code: two digits if (c == ' ') { line[idx] = '\0'; resp_code = atoi(line); line[idx] = ' '; if (resp_code < 10 || resp_code > 69) { resp.type = RT_INVALID; goto end; } else if (!is_valid_response_code(resp_code)) { resp_code /= 10; resp_code *= 10; } resp.type = (enum gem_resptype)resp_code; state = 1; } else if (!isdigit(c)) { resp.type = RT_INVALID; goto end; } break; case 1: if (c == '\r' || c == '\n' || c == ' ') { line[idx] = '\0'; resp.data = line + 3; goto end; } break; } } if (!resp.data) { resp.data = line + 3; } end: return resp; }