hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-02 19:16:00 +0200
committerhachem <im@hachem.wtf>2026-09-02 19:16:00 +0200
commit5681e15e44820598a818e869bd6caf938ecf8ed1 (patch)
tree76c1868d9c1650930bdc976511688202b0a5c504
parent633ab05d3c608f9d28da48c7af4e5f363cf3c510 (diff)
feat: whole word matching
-rw-r--r--jafm.c80
1 files changed, 56 insertions, 24 deletions
diff --git a/jafm.c b/jafm.c
index 4b06db5..c3ea05a 100644
--- a/jafm.c
+++ b/jafm.c
@@ -122,7 +122,32 @@ static bool is_definition_line(const char* line)
|| strstr(line, "enum") != NULL || strstr(line, "typedef") != NULL;
}
-static long count_occurrences(const char* line, const char* query)
+static bool is_identifier_char(char character)
+{
+ return isalnum((unsigned char) character) || character == '_';
+}
+
+static bool is_whole_word(const char* line, const char* found, size_t query_length)
+{
+ if (found != line && is_identifier_char(found[-1]))
+ return false;
+
+ return !is_identifier_char(found[query_length]);
+}
+
+static const char* find_query(const char* line, const char* query,
+ size_t query_length, bool exact)
+{
+ for (const char* cursor = strstr(line, query);
+ cursor != NULL;
+ cursor = strstr(cursor + query_length, query))
+ if (!exact || is_whole_word(line, cursor, query_length))
+ return cursor;
+
+ return NULL;
+}
+
+static long count_occurrences(const char* line, const char* query, bool exact)
{
size_t query_length = strlen(query);
if (query_length == 0)
@@ -132,20 +157,16 @@ static long count_occurrences(const char* line, const char* query)
for (const char* cursor = strstr(line, query);
cursor != NULL;
cursor = strstr(cursor + query_length, query))
- count++;
+ if (!exact || is_whole_word(line, cursor, query_length))
+ count++;
return count;
}
-static bool is_identifier_char(char character)
-{
- return isalnum((unsigned char) character) || character == '_';
-}
-
-static void extract_symbol(const char* line, const char* query, char* symbol,
- size_t symbol_size)
+static void extract_symbol(const char* line, const char* query, bool exact,
+ char* symbol, size_t symbol_size)
{
- const char* found = strstr(line, query);
+ const char* found = find_query(line, query, strlen(query), exact);
if (found == NULL)
return;
@@ -207,6 +228,7 @@ struct Scan
int best_tier;
char symbol[SYMBOL_SIZE];
char kind[KIND_SIZE];
+ bool exact;
const char* want_kind;
bool has_wanted;
int best_wanted_tier;
@@ -229,7 +251,7 @@ static void scan_line(struct Scan* scan, const char* line, const char* query)
return;
}
- long hits = count_occurrences(line, query);
+ long hits = count_occurrences(line, query, scan->exact);
if (hits == 0)
return;
@@ -258,7 +280,7 @@ static void scan_line(struct Scan* scan, const char* line, const char* query)
if (tier > scan->best_tier)
{
scan->best_tier = tier;
- extract_symbol(line, query, scan->symbol, sizeof(scan->symbol));
+ extract_symbol(line, query, scan->exact, scan->symbol, sizeof(scan->symbol));
classify_kind(line, scan->symbol, scan->kind, sizeof(scan->kind));
}
@@ -266,7 +288,7 @@ static void scan_line(struct Scan* scan, const char* line, const char* query)
{
char symbol[SYMBOL_SIZE];
char kind[KIND_SIZE];
- extract_symbol(line, query, symbol, sizeof(symbol));
+ extract_symbol(line, query, scan->exact, symbol, sizeof(symbol));
classify_kind(line, symbol, kind, sizeof(kind));
if (strcmp(kind, scan->want_kind) == 0)
@@ -381,14 +403,15 @@ static void scan_buffer(char* buffer, const char* query, struct Scan* scan)
}
}
-static void page_find(const char* path, const char* query, const char* want_kind,
- struct Finding* finding)
+static void page_find(const char* path, const char* query, bool exact,
+ const char* want_kind, struct Finding* finding)
{
char* buffer = ends_with(path, ".gz") ? read_gzip(path) : read_plain(path);
if (buffer == NULL)
return;
struct Scan scan = {0};
+ scan.exact = exact;
scan.want_kind = want_kind;
scan_buffer(buffer, query, &scan);
free(buffer);
@@ -751,12 +774,12 @@ static void collect_sections(const char* directory, struct TaskList* tasks)
(void) closedir(directory_stream);
}
-static void scan_task(const struct Task* task, const char* query,
+static void scan_task(const struct Task* task, const char* query, bool exact,
const char* want_kind, struct MatchList* matches)
{
const char* path = task->path;
struct Finding finding = {0};
- page_find(path, query, want_kind, &finding);
+ page_find(path, query, exact, want_kind, &finding);
if (want_kind != NULL && finding.score == 0)
return;
@@ -782,6 +805,7 @@ struct Worker
size_t start;
size_t end;
const char* query;
+ bool exact;
const char* want_kind;
struct MatchList matches;
};
@@ -790,8 +814,8 @@ static void* worker_main(void* argument)
{
struct Worker* worker = argument;
for (size_t index = worker->start; index < worker->end; index++)
- scan_task(&worker->tasks[index], worker->query, worker->want_kind,
- &worker->matches);
+ scan_task(&worker->tasks[index], worker->query, worker->exact,
+ worker->want_kind, &worker->matches);
return NULL;
}
@@ -807,7 +831,7 @@ static size_t worker_count(size_t task_count, size_t requested)
return count == 0 ? 1 : count;
}
-static void scan_tasks(const struct TaskList* tasks, const char* query,
+static void scan_tasks(const struct TaskList* tasks, const char* query, bool exact,
const char* want_kind, struct MatchList* matches,
size_t requested)
{
@@ -832,6 +856,7 @@ static void scan_tasks(const struct TaskList* tasks, const char* query,
workers[index].start = offset;
workers[index].end = offset + chunk;
workers[index].query = query;
+ workers[index].exact = exact;
workers[index].want_kind = want_kind;
offset += chunk;
(void) pthread_create(&handles[index], NULL, worker_main, &workers[index]);
@@ -940,7 +965,8 @@ static bool is_valid_kind(const char* kind)
static void print_usage(FILE* stream, const char* program)
{
- (void) fprintf(stream, "usage: %s [-1] [-l] [-t type] [-j threads] <symbol>\n",
+ (void) fprintf(stream,
+ "usage: %s [-1] [-l] [-e] [-t type] [-j threads] <symbol>\n",
program);
(void) fprintf(stream, " %s [-v | -h]\n", program);
}
@@ -954,6 +980,7 @@ static void print_help(const char* program)
(void) printf("\noptions:\n"
" -1, --first open the best match without asking\n"
" -l, --list just print what it found, open nothing\n"
+ " -e, --exact match the symbol as a whole word only\n"
" -t, --type KIND only show matches where the symbol is a KIND\n"
" (struct, union, enum, typedef, function)\n"
" -j, --jobs N scan with N worker threads (default %d)\n"
@@ -989,6 +1016,7 @@ int main(int argc, char** argv)
{
{ "first", no_argument, NULL, '1'},
{ "list", no_argument, NULL, 'l'},
+ { "exact", no_argument, NULL, 'e'},
{ "type", required_argument, NULL, 't'},
{ "jobs", required_argument, NULL, 'j'},
{ "version", no_argument, NULL, 'v'},
@@ -999,11 +1027,12 @@ int main(int argc, char** argv)
size_t requested_threads = 0;
bool open_first = false;
bool list_only = false;
+ bool exact = false;
const char* want_kind = NULL;
- for (int option = getopt_long(argc, argv, "1lt:j:vh", long_options, NULL);
+ for (int option = getopt_long(argc, argv, "1let:j:vh", long_options, NULL);
option != -1;
- option = getopt_long(argc, argv, "1lt:j:vh", long_options, NULL))
+ option = getopt_long(argc, argv, "1let:j:vh", long_options, NULL))
switch (option)
{
case '1':
@@ -1012,6 +1041,9 @@ int main(int argc, char** argv)
case 'l':
list_only = true;
break;
+ case 'e':
+ exact = true;
+ break;
case 't':
if (!is_valid_kind(optarg))
{
@@ -1082,7 +1114,7 @@ int main(int argc, char** argv)
collect_sections(directory, &tasks);
struct MatchList matches = {0};
- scan_tasks(&tasks, query, want_kind, &matches, requested_threads);
+ scan_tasks(&tasks, query, exact, want_kind, &matches, requested_threads);
task_list_free(&tasks);
if (matches.count == 0)