/*	$NetBSD: str.h,v 1.21 2026/01/03 19:57:38 rillig Exp $	*/

/*
 * Copyright (c) 2021 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Roland Illig.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* Memory-efficient string handling. */

/* A read-only string that may need to be freed after use. */
typedef struct FStr {
	const char *str;
	void *freeIt;
} FStr;

/* A read-only range of a character array, NOT null-terminated. */
typedef struct Substring {
	const char *start;
	const char *end;
} Substring;

/*
 * Builds a string, only allocating memory if the string is different from the
 * expected string.
 */
typedef struct LazyBuf {
	char *data;
	size_t len;
	size_t cap;
	const char *expected;
} LazyBuf;

/* The result of splitting a string into words. */
typedef struct Words {
	char **words;
	size_t len;
	void *freeIt;
} Words;

/* The result of splitting a string into words. */
typedef struct SubstringWords {
	Substring *words;
	size_t len;
	void *freeIt;
} SubstringWords;

typedef struct StrMatchResult {
	const char *error;
	bool matched;
} StrMatchResult;


/* Return a string that is the sole owner of str. */
MAKE_INLINE FStr
FStr_InitOwn(char *str)
{
	FStr fstr;
	fstr.str = str;
	fstr.freeIt = str;
	return fstr;
}

/* Return a string that refers to the shared str. */
MAKE_INLINE FStr
FStr_InitRefer(const char *str)
{
	FStr fstr;
	fstr.str = str;
	fstr.freeIt = NULL;
	return fstr;
}

MAKE_INLINE void
FStr_Done(FStr *fstr)
{
	free(fstr->freeIt);
#ifdef CLEANUP
	fstr->str = NULL;
	fstr->freeIt = NULL;
#endif
}


MAKE_STATIC Substring
Substring_Init(const char *start, const char *end)
{
	Substring sub;

	sub.start = start;
	sub.end = end;
	return sub;
}

MAKE_INLINE Substring
Substring_InitStr(const char *str)
{
	return Substring_Init(str, str + strlen(str));
}

MAKE_STATIC size_t
Substring_Length(Substring sub)
{
	return (size_t)(sub.end - sub.start);
}

MAKE_STATIC bool
Substring_IsEmpty(Substring sub)
{
	return sub.start == sub.end;
}

MAKE_INLINE bool
Substring_Equals(Substring sub, const char *str)
{
	size_t len = strlen(str);
	return Substring_Length(sub) == len &&
	       memcmp(sub.start, str, len) == 0;
}

MAKE_INLINE bool
Substring_Eq(Substring sub, Substring str)
{
	size_t len = Substring_Length(sub);
	return len == Substring_Length(str) &&
	       memcmp(sub.start, str.start, len) == 0;
}

MAKE_STATIC bool
Substring_HasPrefix(Substring sub, Substring prefix)
{
	return Substring_Length(sub) >= Substring_Length(prefix) &&
	       memcmp(sub.start, prefix.start, Substring_Length(prefix)) == 0;
}

MAKE_STATIC bool
Substring_HasSuffix(Substring sub, Substring suffix)
{
	size_t suffixLen = Substring_Length(suffix);
	return Substring_Length(sub) >= suffixLen &&
	       memcmp(sub.end - suffixLen, suffix.start, suffixLen) == 0;
}

/* Returns an independent, null-terminated copy of the substring. */
MAKE_STATIC FStr
Substring_Str(Substring sub)
{
	if (Substring_IsEmpty(sub))
		return FStr_InitRefer("");
	return FStr_InitOwn(bmake_strsedup(sub.start, sub.end));
}

MAKE_STATIC const char *
Substring_SkipFirst(Substring sub, char ch)
{
	const char *p;

	for (p = sub.start; p != sub.end; p++)
		if (*p == ch)
			return p + 1;
	return sub.start;
}

MAKE_STATIC const char *
Substring_FindLast(Substring sub, char ch)
{
	const char *p;

	for (p = sub.end; p != sub.start; p--)
		if (p[-1] == ch)
			return p - 1;
	return NULL;
}

MAKE_STATIC Substring
Substring_Dirname(Substring pathname)
{
	const char *p;

	for (p = pathname.end; p != pathname.start; p--)
		if (p[-1] == '/')
			return Substring_Init(pathname.start, p - 1);
	return Substring_InitStr(".");
}

MAKE_STATIC Substring
Substring_Basename(Substring pathname)
{
	const char *p;

	for (p = pathname.end; p != pathname.start; p--)
		if (p[-1] == '/')
			return Substring_Init(p, pathname.end);
	return pathname;
}


MAKE_STATIC void
LazyBuf_Init(LazyBuf *buf, const char *expected)
{
	buf->data = NULL;
	buf->len = 0;
	buf->cap = 0;
	buf->expected = expected;
}

MAKE_INLINE void
LazyBuf_Done(LazyBuf *buf)
{
	free(buf->data);
}

MAKE_STATIC void
LazyBuf_Add(LazyBuf *buf, char ch)
{

	if (buf->data != NULL) {
		if (buf->len == buf->cap) {
			buf->cap *= 2;
			buf->data = bmake_realloc(buf->data, buf->cap);
		}
		buf->data[buf->len++] = ch;

	} else if (ch == buf->expected[buf->len]) {
		buf->len++;
		return;

	} else {
		buf->cap = buf->len + 16;
		buf->data = bmake_malloc(buf->cap);
		memcpy(buf->data, buf->expected, buf->len);
		buf->data[buf->len++] = ch;
	}
}

MAKE_STATIC void
LazyBuf_AddStr(LazyBuf *buf, const char *str)
{
	const char *p;

	for (p = str; *p != '\0'; p++)
		LazyBuf_Add(buf, *p);
}

MAKE_INLINE void
LazyBuf_AddSubstring(LazyBuf *buf, Substring sub)
{
	const char *p;

	for (p = sub.start; p != sub.end; p++)
		LazyBuf_Add(buf, *p);
}

MAKE_STATIC Substring
LazyBuf_Get(const LazyBuf *buf)
{
	const char *start = buf->data != NULL ? buf->data : buf->expected;
	return Substring_Init(start, start + buf->len);
}

/*
 * Returns the content of the buffer as a newly allocated string.
 *
 * See LazyBuf_Get to avoid unnecessary memory allocations.
 */
MAKE_STATIC FStr
LazyBuf_DoneGet(LazyBuf *buf)
{
	if (buf->data != NULL) {
		LazyBuf_Add(buf, '\0');
		return FStr_InitOwn(buf->data);
	}
	return Substring_Str(LazyBuf_Get(buf));
}


Words Str_Words(const char *, bool);

MAKE_INLINE void
Words_Free(Words w)
{
	free(w.words);
	free(w.freeIt);
}


SubstringWords Substring_Words(const char *, bool);

MAKE_INLINE void
SubstringWords_Init(SubstringWords *w)
{
	w->words = NULL;
	w->len = 0;
	w->freeIt = NULL;
}

MAKE_INLINE void
SubstringWords_Free(SubstringWords w)
{
	free(w.words);
	free(w.freeIt);
}


char *str_concat2(const char *, const char *);
char *str_concat3(const char *, const char *, const char *);

StrMatchResult Str_Match(const char *, const char *);

void Str_Intern_Init(void);
#ifdef CLEANUP
void Str_Intern_End(void);
#endif
const char *Str_Intern(const char *);
