Compare commits

..

10 commits

Author SHA1 Message Date
fef
aae039ae29
string: nstrcat refactor 2021-07-15 23:14:55 +02:00
fef
2fbbfc61c0
string: use memcpy instead of strncpy in nstrmul 2021-07-15 23:10:57 +02:00
fef
8c591796fb
string: always call strcmp() in nstrcmp()
Some people seem to depend on the unstandardized
behavior of some (notably glibc) implementations
of strcmp() where the arithemtic difference
between s1 and s2 is returned, not just any
positive or negative number.  This is explicitly
undocumented in libneo as well, but still doing
it won't hurt either.
2021-07-15 23:07:00 +02:00
fef
7d64438f70
string: fix off-by-one error in leftpad 2021-07-15 23:00:17 +02:00
fef
900bce9b8e
string: add nnstr string constructor 2021-07-15 22:58:15 +02:00
fef
2698409e4c
stddef: define nil to nullptr for C++ 2021-07-15 22:51:24 +02:00
fef
49b44ad4bc
string: fix nstreq brainfart 2021-07-15 22:50:53 +02:00
fef
c71e36e7c8
error: fix memory leaks and other bugs
It was really late when i wrote this okay
2021-07-15 22:50:33 +02:00
fef
630530109b
test: add test cases for utf8_to_nchr 2021-07-15 22:49:49 +02:00
fef
aae3b85ce7
test: improve cmake integration 2021-07-15 22:46:26 +02:00
12 changed files with 275 additions and 38 deletions

View file

@ -8,7 +8,11 @@ extern "C" {
#include "neo/_types.h"
#define nil ((void *)0)
#ifdef __cplusplus
# define nil nullptr
#else
# define nil ((void *)0)
#endif
#if !defined(__cplusplus) && !defined(true)
# define true ((bool)1)

View file

@ -22,6 +22,20 @@ extern "C" {
*/
string *nstr(const char *__restrict s, error *err);
/**
* Copy a regular C string to a neo string, but at most `maxsize` bytes.
*
* The string is copied until a NUL terminator is encountered, or until
* `maxsize` bytes have been read. If `maxsize` is 0, an empty string is
* returned. If `s` is `nil` or allocation fails, an error is yeeted.
* Strings in libneo are reference counted, see `nget` and `nput`.
*
* @param s: String to convert
* @param err: Error pointer
* @returns The converted string, unless an error occurred
*/
string *nnstr(const char *__restrict s, usize maxsize, error *err);
/**
* Get the Unicode code point in a string at the specified index.
*
@ -133,7 +147,7 @@ int nstrcmp(const string *s1, const string *s2, error *err);
* @param err: Error pointer
* @returns Whether the two strings are equal, unless an error occurred
*/
#define nstreq(s1, s2, err) ( (bool)(nstrcmp(s1, s2, err) != 0) )
#define nstreq(s1, s2, err) ( (bool)(nstrcmp(s1, s2, err) == 0) )
/**
* Prepend fill characters to a string to make it a specific length, and return

View file

@ -16,22 +16,22 @@
void yeet(error *err, u32 number, const char *restrict fmt, ...)
{
va_list vargs;
usize msg_capacity = 64;
usize msg_size = 64;
char *msg = nil;
int vsnprintf_ret;
if (fmt != nil) {
do {
msg = nalloc(msg_capacity, nil);
msg = nalloc(msg_size, nil);
va_start(vargs, fmt);
vsnprintf_ret = vsnprintf(msg, msg_capacity, fmt, vargs);
int required_size = vsnprintf(msg, msg_size, fmt, vargs);
va_end(vargs);
if (vsnprintf_ret > msg_capacity) {
msg_capacity = vsnprintf_ret;
/* required_size excludes NUL, therefore >= */
if (required_size >= msg_size) {
msg_size = required_size + 1;
nfree(msg);
msg = nil;
} else if (vsnprintf_ret < 0) {
write(1, "Runtime error\n", 14);
} else if (required_size < 0) {
write(2, "Runtime error\n", strlen("Runtime error\n"));
exit(1);
}
} while (msg == nil);
@ -46,6 +46,7 @@ void yeet(error *err, u32 number, const char *restrict fmt, ...)
err->_number = number;
err->_message = nstr(msg, nil);
nfree(msg);
}
void neat(error *err)

View file

@ -19,7 +19,13 @@ static inline string *leftpad_unsafe(const string *s, usize len, nchar fillchr,
}
usize extra_chars = len - s->_len;
usize size_now = s->_capacity + 1;
/*
* This is actually three bytes larger than the actual size because neo
* strings are terminated with four NUL characters rather than just one,
* but that's okay because if we don't even have enough memory for three
* extra bytes we are screwed anyway.
*/
usize size_now = s->_capacity;
usize size_after = size_now + (extra_chars * fillchr_size);
char *dest = nalloc(size_after, err);
catch(err) {
@ -50,10 +56,10 @@ string *leftpad(const string *s, usize len, nchar fillchr, error *err)
string *padded;
if (len < s->_len) {
if (len < nlen(s)) {
yeet(err, ERANGE, "String is longer than requested length");
padded = nil;
} else if (s->_len == len) {
} else if (nlen(s) == len) {
padded = nstrdup(s, err);
} else {
padded = leftpad_unsafe(s, len, fillchr, err);

View file

@ -1,5 +1,8 @@
/** See the end of this file for copyright and license terms. */
/* strnlen */
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <string.h>
@ -16,10 +19,10 @@ static void nstr_destroy(string *str)
nfree(str);
}
string *nstr(const char *restrict s, error *err)
static string *nstr_unsafe(const char *restrict s, usize size_without_nul, error *err)
{
if (s == nil) {
yeet(err, EFAULT, "String must not be nil");
usize len = utf8_check(s, err);
catch(err) {
return nil;
}
@ -40,24 +43,48 @@ string *nstr(const char *restrict s, error *err)
*
* Yeah, this is definitely never gonna break my legs.
*/
usize nbytes = strlen(s) + 4;
str->_data = nalloc(nbytes, err);
str->_data = nalloc(size_without_nul + 4, err);
catch(err) {
nfree(str);
return nil;
}
strcpy(str->_data, s);
str->_data[nbytes - 3] = '\0';
str->_data[nbytes - 2] = '\0';
str->_data[nbytes - 1] = '\0';
str->_len = utf8_strlen(str->_data);
str->_capacity = nbytes;
str->_len = len;
str->_capacity = size_without_nul + 4;
nref_init(str, nstr_destroy);
memcpy(str->_data, s, size_without_nul);
str->_data[size_without_nul] = '\0';
str->_data[size_without_nul + 1] = '\0';
str->_data[size_without_nul + 2] = '\0';
str->_data[size_without_nul + 3] = '\0';
return str;
}
string *nstr(const char *restrict s, error *err)
{
if (s == nil) {
yeet(err, EFAULT, "String is nil");
return nil;
}
usize size_without_nul = strlen(s);
return nstr_unsafe(s, size_without_nul, err);
}
string *nnstr(const char *restrict s, usize maxsize, error *err)
{
if (s == nil) {
yeet(err, EFAULT, "String is nil");
return nil;
}
usize size_without_nul = strnlen(s, maxsize);
return nstr_unsafe(s, size_without_nul, err);
}
nchar nchrat(const string *s, usize index, error *err)
{
if (s == nil) {

View file

@ -1,5 +1,8 @@
/** See the end of this file for copyright and license terms. */
/* strnlen */
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <string.h>
@ -21,15 +24,17 @@ string *nstrcat(const string *s1, const string *s2, error *err)
return nil;
}
usize s1_size = strlen(s1->_data);
usize s2_size = strlen(s2->_data);
char *cat = nalloc(s1_size + s2_size + 1, err);
usize s1_size_without_nul = strlen(s1->_data);
usize s2_size_without_nul = strlen(s2->_data);
usize cat_size = s1_size_without_nul + s2_size_without_nul + 1;
char *cat = nalloc(cat_size, err);
catch(err) {
return nil;
}
strcpy(cat, s1->_data);
strcpy(cat + s1_size, s2->_data);
memcpy(cat, s1->_data, s1_size_without_nul);
memcpy(cat + s1_size_without_nul, s2->_data, s2_size_without_nul);
cat[s1_size_without_nul + s2_size_without_nul] = '\0';
string *ret = nstr(cat, err);
nfree(cat);

View file

@ -22,13 +22,13 @@ int nstrcmp(const string *s1, const string *s2, error *err)
int ret;
if (nlen(s1) > nlen(s2))
ret = 1;
else if (nlen(s1) < nlen(s2))
ret = -1;
usize maxbytes;
if (s1->_capacity > s2->_capacity)
maxbytes = s2->_capacity;
else
ret = strcmp(s1->_data, s2->_data);
maxbytes = s1->_capacity;
ret = strncmp(s1->_data, s2->_data, maxbytes);
neat(err);
return ret;
}

View file

@ -57,7 +57,7 @@ string *nchrmul(nchar c, usize n, error *err)
char *pos = multiplied;
while (n-- != 0) {
strncpy(pos, &s[0], s_size);
memcpy(pos, &s[0], s_size);
pos += s_size;
}

View file

@ -1,7 +1,8 @@
# See the end of this file for copyright and license terms.
enable_language(CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
include(CTest)
include(FetchContent)
@ -11,13 +12,15 @@ FetchContent_Declare(
GIT_TAG v2.13.6
)
FetchContent_MakeAvailable(Catch2)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib)
include(Catch)
add_executable(neo_test neo_test.cpp)
include(string/string.cmake)
target_compile_features(neo_test PRIVATE cxx_std_17)
target_link_libraries(neo_test PRIVATE neo Catch2::Catch2)
add_test(NAME neo COMMAND neo_test)
catch_discover_tests(neo_test)
# This file is part of libneo.
# Copyright (c) 2021 Fefie <owo@fef.moe>.

14
test/string/string.cmake Normal file
View file

@ -0,0 +1,14 @@
# See the end of this file for copyright and license terms.
include(string/utf/utf.cmake)
# This file is part of libneo.
# Copyright (c) 2021 Fefie <owo@fef.moe>.
#
# libneo is non-violent software: you may only use, redistribute,
# and/or modify it under the terms of the CNPLv6+ as found in
# the LICENSE file in the source code root directory or at
# <https://git.pixie.town/thufie/CNPL>.
#
# libneo comes with ABSOLUTELY NO WARRANTY, to the extent
# permitted by applicable law. See the CNPLv6+ for details.

17
test/string/utf/utf.cmake Normal file
View file

@ -0,0 +1,17 @@
# See the end of this file for copyright and license terms.
target_sources(neo_test PRIVATE
string/utf/utf8_to_nchr.cpp
)
# This file is part of libneo.
# Copyright (c) 2021 Fefie <owo@fef.moe>.
#
# libneo is non-violent software: you may only use, redistribute,
# and/or modify it under the terms of the CNPLv6+ as found in
# the LICENSE file in the source code root directory or at
# <https://git.pixie.town/thufie/CNPL>.
#
# libneo comes with ABSOLUTELY NO WARRANTY, to the extent
# permitted by applicable law. See the CNPLv6+ for details.

View file

@ -0,0 +1,146 @@
/** See the end of this file for copyright and license terms. */
#include <catch2/catch.hpp>
#include <errno.h>
#include <neo.h>
#include <neo/utf.h>
TEST_CASE( "Decode 1-byte character sequence", "[utf]" )
{
error err;
nchar c;
usize size = utf8_to_nchr(&c, ",", &err);
REQUIRE( size == 1 );
REQUIRE( c == ',' );
REQUIRE( errnum(&err) == 0 );
REQUIRE( errmsg(&err) == nil );
}
TEST_CASE( "Decode 2-byte character sequence", "[utf]" )
{
error err;
nchar c;
/* U+03B1 Greek Smol Letter Alpha */
usize size = utf8_to_nchr(&c, "\xce\xb1", &err);
REQUIRE( size == 2 );
REQUIRE( c == 0x03b1 );
REQUIRE( errnum(&err) == 0 );
REQUIRE( errmsg(&err) == nil );
}
TEST_CASE( "Decode 3-byte character sequence", "[utf]" )
{
error err;
nchar c;
/* U+3042 Hiragana Letter A */
usize size = utf8_to_nchr(&c, "\xe3\x81\x82", &err);
REQUIRE( size == 3 );
REQUIRE( c == 0x3042 );
REQUIRE( errnum(&err) == 0 );
REQUIRE( errmsg(&err) == nil );
}
TEST_CASE( "Decode 4-byte character sequence", "[utf]" )
{
error err;
nchar c;
/* U+1F97A The Bottom Emoji(TM) */
usize size = utf8_to_nchr(&c, "\xf0\x9f\xa5\xba", &err);
REQUIRE( size == 4 );
REQUIRE( c == 0x01f97a );
REQUIRE( errnum(&err) == 0 );
REQUIRE( errmsg(&err) == nil );
}
TEST_CASE( "Error on malformed sequence start", "[utf8]" )
{
error err;
nchar c;
utf8_to_nchr(&c, "\xff", &err);
string *expected = nstr("Illegal UTF-8 sequence start byte: 0xff", nil);
string *actual = errmsg(&err);
REQUIRE( c == '\0' );
REQUIRE( errnum(&err) == EINVAL );
REQUIRE( nstreq(expected, actual, nil) );
errput(&err);
}
TEST_CASE( "Error on wrong second byte", "[utf8]" )
{
error err;
nchar c;
utf8_to_nchr(&c, "\xce\xff", &err);
string *expected = nstr("Byte 2 in UTF-8 sequence invalid: 0xff", nil);
string *actual = errmsg(&err);
REQUIRE( c == '\0' );
REQUIRE( errnum(&err) == EINVAL );
REQUIRE( nstreq(expected, actual, nil) );
errput(&err);
}
TEST_CASE( "Error on wrong third byte", "[utf8]" )
{
error err;
nchar c;
utf8_to_nchr(&c, "\xe3\x81\xff", &err);
string *expected = nstr("Byte 3 in UTF-8 sequence invalid: 0xff", nil);
string *actual = errmsg(&err);
REQUIRE( c == '\0' );
REQUIRE( errnum(&err) == EINVAL );
REQUIRE( nstreq(expected, actual, nil) );
errput(&err);
}
TEST_CASE( "Error on wrong fourth byte", "[utf8]" )
{
error err;
nchar c;
utf8_to_nchr(&c, "\xf0\x9f\xa5\xff", &err);
string *expected = nstr("Byte 4 in UTF-8 sequence invalid: 0xff", nil);
string *actual = errmsg(&err);
REQUIRE( c == '\0' );
REQUIRE( errnum(&err) == EINVAL );
REQUIRE( nstreq(expected, actual, nil) );
errput(&err);
}
TEST_CASE( "Error on non canonical encoding", "[utf8]" )
{
error err;
nchar c;
utf8_to_nchr(&c, "\xf0\x80\x80\xa0", &err);
string *expected = nstr("Non canonical UTF-8 encoding: 1 byte character stored in 4 bytes", nil);
string *actual = errmsg(&err);
REQUIRE( c == '\0' );
REQUIRE( errnum(&err) == EINVAL );
REQUIRE( nstreq(expected, actual, nil) );
errput(&err);
}
/*
* This file is part of libneo.
* Copyright (c) 2021 Fefie <owo@fef.moe>.
*
* libneo is non-violent software: you may only use, redistribute,
* and/or modify it under the terms of the CNPLv6+ as found in
* the LICENSE file in the source code root directory or at
* <https://git.pixie.town/thufie/CNPL>.
*
* libneo comes with ABSOLUTELY NO WARRANTY, to the extent
* permitted by applicable law. See the CNPLv6+ for details.
*/