#ifndef TSP_INTUIT_READLINE_H
#define TSP_INTUIT_READLINE_H
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
static inline bool tsp_rl_isdigit(int c) { return c >= '0' && c <= '9'; }
static inline bool tsp_rl_isalpha(int c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
static inline bool tsp_rl_isword(int c) {
return tsp_rl_isalpha(c) || tsp_rl_isdigit(c) || c == '_';
}
static inline bool tsp_rl_isspace(int c) {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' ||
c == '\v';
}
static inline bool tsp_rl_is_trailing_keyword(const char *w, size_t n) {
static const char *const kw[] = {
"if", "unless", "while", "until", "for", "foreach", "and", "or", "xor",
"not", "cmp", "eq", "ne", "lt", "gt", "le", "ge", "x"};
for (size_t i = 0; i < sizeof(kw) / sizeof(kw[0]); i++) {
if (strlen(kw[i]) == n && memcmp(w, kw[i], n) == 0) return true;
}
return false;
}
static inline bool tsp_rl_is_infix_word(const char *w, size_t n) {
static const char *const kw[] = {"and", "or", "xor", "not", "cmp", "eq",
"ne", "lt", "gt", "le", "ge", "x"};
for (size_t i = 0; i < sizeof(kw) / sizeof(kw[0]); i++) {
if (strlen(kw[i]) == n && memcmp(w, kw[i], n) == 0) return true;
}
return false;
}
static inline bool tsp_rl_content_has_infix_op(const char *s, size_t len) {
size_t i = 0;
while (i < len) {
while (i < len && tsp_rl_isspace((unsigned char)s[i])) i++;
size_t start = i;
bool clean = true;
while (i < len && !tsp_rl_isspace((unsigned char)s[i])) {
if (!tsp_rl_isalpha((unsigned char)s[i])) clean = false;
i++;
}
if (clean && i > start && tsp_rl_is_infix_word(s + start, i - start))
return true;
}
return false;
}
static inline bool tsp_rl_is_glob_meta(int c) {
return c == '*' || c == '?' || c == '[' || c == ']' || c == '{' || c == '}' ||
c == '~';
}
static inline bool tsp_rl_content_is_globshaped(const char *s, size_t len) {
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)s[i];
if (tsp_rl_is_glob_meta(c) || c == '/') return true;
if (c == '.') {
bool prev_word = (i > 0) && tsp_rl_isword((unsigned char)s[i - 1]);
bool next_word = (i + 1 < len) && tsp_rl_isword((unsigned char)s[i + 1]);
if (prev_word && next_word) return true;
}
}
return false;
}
static inline bool tsp_rl_is_filehandle(const char *buf, size_t len) {
size_t i = 0;
if (i >= len || tsp_rl_isspace((unsigned char)buf[i])) return false;
if (buf[i] == '$') i++;
if (i >= len) return false;
bool last_was_word = false;
while (i < len) {
if (tsp_rl_isword((unsigned char)buf[i])) {
last_was_word = true;
i++;
} else if (buf[i] == ':') {
if (!last_was_word) return false;
if (i + 1 >= len || buf[i + 1] != ':') return false;
last_was_word = false;
i += 2;
} else if (buf[i] == '\'') {
if (!last_was_word) return false;
last_was_word = false;
i++;
} else if (tsp_rl_isspace((unsigned char)buf[i])) {
if (!last_was_word) return false;
for (i++; i < len; i++)
if (!tsp_rl_isspace((unsigned char)buf[i])) return false;
return true;
} else {
return false;
}
}
return last_was_word;
}
static inline bool tsp_is_fileglob(const char *content, size_t clen,
const char *after, size_t alen) {
if (clen == 0) return true;
if (tsp_rl_content_has_infix_op(content, clen)) return false;
if (!tsp_rl_content_is_globshaped(content, clen) &&
!tsp_rl_is_filehandle(content, clen))
return false;
size_t i = 0;
while (i < alen && tsp_rl_isspace((unsigned char)after[i])) i++;
if (i >= alen) return true;
unsigned char nc = (unsigned char)after[i];
if (tsp_rl_isalpha(nc)) {
size_t start = i;
while (i < alen && tsp_rl_isword((unsigned char)after[i])) i++;
if (tsp_rl_is_trailing_keyword(after + start, i - start)) return true;
return true;
}
if (nc == '$' || nc == '@' || nc == '%' || nc == '"' || nc == '\'' ||
tsp_rl_isdigit(nc) || nc == '(' || nc == '[' || nc == '{') {
return false;
}
return true;
}
#endif