Compare commits
10 commits
5b46a05ce7
...
aae039ae29
Author | SHA1 | Date | |
---|---|---|---|
aae039ae29 | |||
2fbbfc61c0 | |||
8c591796fb | |||
7d64438f70 | |||
900bce9b8e | |||
2698409e4c | |||
49b44ad4bc | |||
c71e36e7c8 | |||
630530109b | |||
aae3b85ce7 |
12 changed files with 275 additions and 38 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
17
src/error.c
17
src/error.c
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
14
test/string/string.cmake
Normal 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
17
test/string/utf/utf.cmake
Normal 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.
|
||||
|
146
test/string/utf/utf8_to_nchr.cpp
Normal file
146
test/string/utf/utf8_to_nchr.cpp
Normal 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.
|
||||
*/
|
Loading…
Reference in a new issue