summaryrefslogtreecommitdiff
path: root/src/core/command_parse.c
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-08 03:54:55 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-08 03:54:55 +0000
commit20cb29c2f8c5c87bc590896854a20b1473ceb358 (patch)
tree2857f41513a56ad41af97b57362639298aa7f033 /src/core/command_parse.c
#2
Diffstat (limited to 'src/core/command_parse.c')
-rw-r--r--src/core/command_parse.c347
1 files changed, 347 insertions, 0 deletions
diff --git a/src/core/command_parse.c b/src/core/command_parse.c
new file mode 100644
index 0000000..f96bf6c
--- /dev/null
+++ b/src/core/command_parse.c
@@ -0,0 +1,347 @@
+#include "hybbx/command.h"
+#include "hybbx/limits.h"
+#include "hybbx/util.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char *hybbx_strdup(const char *s)
+{
+ size_t len;
+ char *copy;
+
+ if (s == NULL) {
+ return NULL;
+ }
+
+ len = strlen(s) + 1;
+ if (!hybbx_size_ok(len)) {
+ return NULL;
+ }
+ copy = malloc(len);
+ if (copy != NULL) {
+ memcpy(copy, s, len);
+ }
+ return copy;
+}
+
+static const char *skip_leading_space(const char *line)
+{
+ if (line == NULL) {
+ return NULL;
+ }
+
+ while (*line != '\0' && isspace((unsigned char)*line)) {
+ line++;
+ }
+
+ return line;
+}
+
+static char *ltrim_copy(char *s)
+{
+ char *start;
+
+ if (s == NULL) {
+ return NULL;
+ }
+
+ start = (char *)skip_leading_space(s);
+ if (start != s) {
+ memmove(s, start, strlen(start) + 1);
+ }
+
+ return s;
+}
+
+static int str_ieq(const char *a, const char *b)
+{
+ if (a == NULL || b == NULL) {
+ return 0;
+ }
+
+ while (*a != '\0' && *b != '\0') {
+ char ca = (char)(*a >= 'A' && *a <= 'Z' ? *a + 32 : *a);
+ char cb = (char)(*b >= 'A' && *b <= 'Z' ? *b + 32 : *b);
+
+ if (ca != cb) {
+ return 0;
+ }
+ a++;
+ b++;
+ }
+
+ return *a == '\0' && *b == '\0';
+}
+
+static int strip_one_command_alias(char *rest, const char *alias)
+{
+ size_t alias_len;
+
+ if (alias == NULL || rest == NULL) {
+ return -1;
+ }
+
+ alias_len = strlen(alias);
+
+ if (str_ieq(rest, alias)) {
+ return 1;
+ }
+
+ if (strncmp(rest, alias, alias_len) == 0 &&
+ isspace((unsigned char)rest[alias_len])) {
+ memmove(rest, rest + alias_len, strlen(rest + alias_len) + 1);
+ ltrim_copy(rest);
+ return rest[0] == '\0' ? 1 : 0;
+ }
+
+ return -1;
+}
+
+static int strip_command_alias(char *rest)
+{
+ static const char *aliases[] = { "commands", "command", "cmd", NULL };
+ int rc;
+ size_t i;
+
+ if (rest == NULL) {
+ return 0;
+ }
+
+ if (rest[0] == '\0') {
+ return 1;
+ }
+
+ for (i = 0; aliases[i] != NULL; i++) {
+ rc = strip_one_command_alias(rest, aliases[i]);
+ if (rc >= 0) {
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+hybbx_command_scope_t hybbx_command_classify(const char *line)
+{
+ const char *p = skip_leading_space(line);
+
+ if (p == NULL || *p == '\0') {
+ return HYBBX_CMD_SCOPE_LOCAL;
+ }
+
+ if (*p == '/') {
+ return HYBBX_CMD_SCOPE_HYBBX;
+ }
+
+ if (*p == ';' || *p == '#') {
+ return HYBBX_CMD_SCOPE_COMMENT;
+ }
+
+ return HYBBX_CMD_SCOPE_LOCAL;
+}
+
+int hybbx_command_is_comment(const char *line)
+{
+ return hybbx_command_classify(line) == HYBBX_CMD_SCOPE_COMMENT;
+}
+
+int hybbx_command_is_hybbx(const char *line)
+{
+ return hybbx_command_classify(line) == HYBBX_CMD_SCOPE_HYBBX;
+}
+
+static size_t count_tokens(const char *line)
+{
+ size_t count = 0;
+ size_t i = 0;
+ int in_token = 0;
+
+ while (line[i] != '\0') {
+ if (!isspace((unsigned char)line[i])) {
+ if (!in_token) {
+ count++;
+ in_token = 1;
+ }
+ } else {
+ in_token = 0;
+ }
+ i++;
+ }
+
+ return count;
+}
+
+static hybbx_result_t split_tokens(char *line, char **tokens, size_t max)
+{
+ size_t count = 0;
+ size_t i = 0;
+
+ while (line[i] != '\0' && count < max) {
+ while (line[i] != '\0' && isspace((unsigned char)line[i])) {
+ i++;
+ }
+ if (line[i] == '\0') {
+ break;
+ }
+ tokens[count++] = line + i;
+ while (line[i] != '\0' && !isspace((unsigned char)line[i])) {
+ i++;
+ }
+ if (line[i] != '\0') {
+ line[i] = '\0';
+ i++;
+ }
+ }
+
+ return HYBBX_OK;
+}
+
+static hybbx_result_t parse_hybbx_line(const char *line, hybbx_parsed_command_t *out)
+{
+ char *rest;
+ char *work;
+ size_t token_count;
+ char **tokens;
+ size_t i;
+ hybbx_result_t rc;
+
+ if (line[0] != '/') {
+ return HYBBX_ERR_INVALID;
+ }
+
+ rest = hybbx_strdup(line + 1);
+ if (rest == NULL) {
+ return HYBBX_ERR_NOMEM;
+ }
+
+ ltrim_copy(rest);
+
+ if (strip_command_alias(rest)) {
+ out->verb = hybbx_strdup("help");
+ if (out->verb == NULL) {
+ free(rest);
+ return HYBBX_ERR_NOMEM;
+ }
+ free(rest);
+ return HYBBX_OK;
+ }
+
+ token_count = count_tokens(rest);
+ if (token_count == 0) {
+ out->verb = hybbx_strdup("help");
+ free(rest);
+ if (out->verb == NULL) {
+ return HYBBX_ERR_NOMEM;
+ }
+ return HYBBX_OK;
+ }
+
+ if (token_count > HYBBX_CMD_TOKEN_MAX) {
+ free(rest);
+ return HYBBX_ERR_INVALID;
+ }
+
+ work = hybbx_strdup(rest);
+ free(rest);
+ if (work == NULL) {
+ return HYBBX_ERR_NOMEM;
+ }
+
+ tokens = calloc(token_count, sizeof(*tokens));
+ if (tokens == NULL) {
+ free(work);
+ return HYBBX_ERR_NOMEM;
+ }
+
+ rc = split_tokens(work, tokens, token_count);
+ if (rc != HYBBX_OK) {
+ free(tokens);
+ free(work);
+ return rc;
+ }
+
+ out->verb = hybbx_strdup(tokens[0]);
+ if (out->verb == NULL) {
+ free(tokens);
+ free(work);
+ return HYBBX_ERR_NOMEM;
+ }
+
+ out->argc = token_count > 1 ? token_count - 1 : 0;
+ if (out->argc > 0) {
+ out->argv = calloc(out->argc, sizeof(*out->argv));
+ if (out->argv == NULL) {
+ free(tokens);
+ free(work);
+ hybbx_command_free(out);
+ return HYBBX_ERR_NOMEM;
+ }
+
+ for (i = 0; i < out->argc; i++) {
+ out->argv[i] = hybbx_strdup(tokens[i + 1]);
+ if (out->argv[i] == NULL) {
+ free(tokens);
+ free(work);
+ hybbx_command_free(out);
+ return HYBBX_ERR_NOMEM;
+ }
+ }
+ }
+
+ free(tokens);
+ free(work);
+ return HYBBX_OK;
+}
+
+hybbx_result_t hybbx_command_parse(const char *line,
+ hybbx_parsed_command_t *out)
+{
+ char *trimmed;
+ hybbx_result_t rc;
+
+ if (line == NULL || out == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ memset(out, 0, sizeof(*out));
+
+ trimmed = hybbx_strdup(skip_leading_space(line));
+ if (trimmed == NULL) {
+ return HYBBX_ERR_NOMEM;
+ }
+
+ out->scope = hybbx_command_classify(trimmed);
+ out->line = trimmed;
+
+ if (out->scope != HYBBX_CMD_SCOPE_HYBBX) {
+ return HYBBX_OK;
+ }
+
+ rc = parse_hybbx_line(trimmed, out);
+ if (rc != HYBBX_OK) {
+ hybbx_command_free(out);
+ }
+
+ return rc;
+}
+
+void hybbx_command_free(hybbx_parsed_command_t *cmd)
+{
+ size_t i;
+
+ if (cmd == NULL) {
+ return;
+ }
+
+ free(cmd->line);
+ free(cmd->verb);
+
+ for (i = 0; i < cmd->argc; i++) {
+ free(cmd->argv[i]);
+ }
+ free(cmd->argv);
+
+ memset(cmd, 0, sizeof(*cmd));
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com