mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
More refactorings in the stdlib. More Path functions. Updated Win32 format for types. Fix bug with codegen of defer if ... More string functions.
This commit is contained in:
committed by
Christoffer Lerno
parent
39dd3e40a6
commit
d2a16961cf
@@ -133,6 +133,19 @@ macro bool os_is_win32()
|
||||
return OS_TYPE == OsType.WIN32;
|
||||
}
|
||||
|
||||
macro bool os_is_darwin()
|
||||
{
|
||||
$switch (OS_TYPE):
|
||||
$case IOS:
|
||||
$case MACOSX:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
return true;
|
||||
$default:
|
||||
return false;
|
||||
$endswitch;
|
||||
}
|
||||
|
||||
macro bool os_is_posix()
|
||||
{
|
||||
$switch (OS_TYPE):
|
||||
|
||||
@@ -1,232 +1,3 @@
|
||||
module std::core::string;
|
||||
|
||||
typedef ZString = distinct inline char*;
|
||||
typedef Char32 = uint;
|
||||
typedef Char16 = ushort;
|
||||
|
||||
const uint SURROGATE_OFFSET @private = 0x10000;
|
||||
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
|
||||
const uint SURROGATE_MASK @private = 0xFC00;
|
||||
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
|
||||
const uint SURROGATE_BITS @private = 10;
|
||||
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
fault NumberConversion
|
||||
{
|
||||
EMPTY_STRING,
|
||||
NEGATIVE_VALUE,
|
||||
MALFORMED_INTEGER,
|
||||
INTEGER_OVERFLOW,
|
||||
MALFORMED_FLOAT,
|
||||
FLOAT_OUT_OF_RANGE,
|
||||
}
|
||||
|
||||
macro String printf(String fmt, ..., Allocator* using = mem::heap())
|
||||
{
|
||||
if (using == mem::temp())
|
||||
{
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.str();
|
||||
}
|
||||
@pool()
|
||||
{
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.copy_str(using);
|
||||
};
|
||||
}
|
||||
|
||||
macro String tprintf(String fmt, ...)
|
||||
{
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.str();
|
||||
}
|
||||
|
||||
macro bool char_in_set(char c, String set)
|
||||
{
|
||||
foreach (ch : set) if (ch == c) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] string
|
||||
* @param [in] to_trim
|
||||
**/
|
||||
fn String String.trim(String string, String to_trim = "\t\n\r ")
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = string.len;
|
||||
while (start < len && char_in_set(string[start], to_trim)) start++;
|
||||
if (start == len) return string[:0];
|
||||
usz end = len - 1;
|
||||
while (end > start && char_in_set(string[end], to_trim)) end--;
|
||||
return string[start..end];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn bool String.starts_with(String string, String needle)
|
||||
{
|
||||
if (needle.len > string.len) return false;
|
||||
if (!needle.len) return true;
|
||||
return string[:needle.len] == needle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @param [&inout] using "The allocator, defaults to the heap allocator"
|
||||
* @param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
* @require needle.len > 0 "The needle must be at least 1 character long"
|
||||
* @ensure return.len > 0
|
||||
**/
|
||||
fn String[] String.split(String s, String needle, usz max = 0, Allocator* using = mem::heap())
|
||||
{
|
||||
usz capacity = 16;
|
||||
usz i = 0;
|
||||
String* holder = malloc(String, capacity, .using = using);
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
usz! index = i == max - 1 ? SearchResult.MISSING! : s.index_of(needle);
|
||||
String res @noinit;
|
||||
if (try index)
|
||||
{
|
||||
res = s[:index];
|
||||
s = s[index + needle.len..];
|
||||
}
|
||||
else
|
||||
{
|
||||
res = s;
|
||||
no_more = true;
|
||||
}
|
||||
if (i == capacity)
|
||||
{
|
||||
capacity *= 2;
|
||||
holder = realloc(holder, String.sizeof * capacity, .using = using);
|
||||
}
|
||||
holder[i++] = res;
|
||||
}
|
||||
return holder[:i];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is identical to String.split, but implicitly uses the
|
||||
* temporary allocator.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
**/
|
||||
fn String[] tsplit(String s, String needle, usz max = 0)
|
||||
{
|
||||
return s.split(needle, max, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first incidence of a string.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @pure
|
||||
* @ensure return < s.len
|
||||
* @require needle.len > 0 "The needle must be len 1 or more"
|
||||
**/
|
||||
fn usz! String.index_of(String s, String needle)
|
||||
{
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[0];
|
||||
foreach (usz i, char c : s)
|
||||
{
|
||||
if (c == search)
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start;
|
||||
search = needle[match];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[0];
|
||||
}
|
||||
}
|
||||
return SearchResult.MISSING!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the last incidence of a string.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @pure
|
||||
* @ensure return < s.len
|
||||
* @require needle.len > 0 "The needle must be len 1 or more"
|
||||
**/
|
||||
fn usz! String.rindex_of(String s, String needle)
|
||||
{
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[^1];
|
||||
foreach_r (usz i, char c : s)
|
||||
{
|
||||
if (c == search)
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start - needle.len + 1;
|
||||
search = needle[^(match + 1)];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[^1];
|
||||
}
|
||||
}
|
||||
return SearchResult.MISSING!;
|
||||
}
|
||||
|
||||
fn ZString String.zstr_copy(String s, Allocator* using = mem::heap())
|
||||
{
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
}
|
||||
|
||||
fn ZString String.zstr_tcopy(String s) => s.zstr_copy(mem::temp()) @inline;
|
||||
|
||||
fn String String.copy(String s, Allocator* using = mem::heap())
|
||||
{
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (String)str[:len];
|
||||
}
|
||||
|
||||
fn String String.tcopy(String s) => s.copy(mem::temp()) @inline;
|
||||
|
||||
fn String ZString.copy(ZString z, Allocator* using = mem::heap()) => z.as_str().copy(using) @inline;
|
||||
fn String ZString.tcopy(ZString z) => z.as_str().copy(mem::temp()) @inline;
|
||||
|
||||
module std::core::str;
|
||||
|
||||
fn VarString join(String[] s, String joiner)
|
||||
|
||||
@@ -1,326 +1,299 @@
|
||||
module std::core::string;
|
||||
import libc;
|
||||
|
||||
typedef VarString = distinct void*;
|
||||
typedef DynStr = VarString;
|
||||
typedef DynString = VarString;
|
||||
typedef VString = VarString;
|
||||
typedef Text = VarString;
|
||||
typedef ZString = distinct inline char*;
|
||||
typedef Char32 = uint;
|
||||
typedef Char16 = ushort;
|
||||
|
||||
const usz MIN_CAPACITY = 16;
|
||||
const uint SURROGATE_OFFSET @private = 0x10000;
|
||||
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
|
||||
const uint SURROGATE_MASK @private = 0xFC00;
|
||||
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
|
||||
const uint SURROGATE_BITS @private = 10;
|
||||
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
|
||||
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
|
||||
|
||||
fn VarString new_with_capacity(usz capacity, Allocator* allocator = mem::heap())
|
||||
fault NumberConversion
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = malloc(StringData, 1, .using = allocator, .end_padding = capacity);
|
||||
data.allocator = allocator;
|
||||
data.len = 0;
|
||||
data.capacity = capacity;
|
||||
return (VarString)data;
|
||||
EMPTY_STRING,
|
||||
NEGATIVE_VALUE,
|
||||
MALFORMED_INTEGER,
|
||||
INTEGER_OVERFLOW,
|
||||
MALFORMED_FLOAT,
|
||||
FLOAT_OUT_OF_RANGE,
|
||||
}
|
||||
|
||||
fn VarString new(String c)
|
||||
macro String printf(String fmt, ..., Allocator* using = mem::heap())
|
||||
{
|
||||
usz len = c.len;
|
||||
VarString str = new_with_capacity(len);
|
||||
StringData* data = str.data();
|
||||
if (len)
|
||||
if (using == mem::temp())
|
||||
{
|
||||
data.len = len;
|
||||
mem::copy(&data.chars, c.ptr, len);
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.str();
|
||||
}
|
||||
return (VarString)data;
|
||||
}
|
||||
|
||||
fn ZString VarString.zstr(VarString str)
|
||||
{
|
||||
StringData* data = str.data();
|
||||
if (!data) return (ZString)"";
|
||||
if (data.capacity == data.len)
|
||||
@pool()
|
||||
{
|
||||
str.reserve(1);
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
else if (data.chars[data.len] != 0)
|
||||
{
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
return (ZString)&data.chars[0];
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.copy_str(using);
|
||||
};
|
||||
}
|
||||
|
||||
fn usz VarString.capacity(VarString this)
|
||||
macro String tprintf(String fmt, ...)
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().capacity;
|
||||
DString str;
|
||||
str.tinit();
|
||||
str.printf(fmt, $vasplat());
|
||||
return str.str();
|
||||
}
|
||||
|
||||
fn usz VarString.len(VarString this)
|
||||
macro bool char_in_set(char c, String set)
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().len;
|
||||
foreach (ch : set) if (ch == c) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require new_size <= this.len()
|
||||
*/
|
||||
fn void VarString.chop(VarString this, usz new_size)
|
||||
{
|
||||
if (!this) return;
|
||||
this.data().len = new_size;
|
||||
}
|
||||
|
||||
fn String VarString.str(VarString str)
|
||||
{
|
||||
StringData* data = (StringData*)str;
|
||||
if (!data) return "";
|
||||
return (String)data.chars[:data.len];
|
||||
}
|
||||
|
||||
fn void VarString.append_utf32(VarString* str, Char32[] chars)
|
||||
{
|
||||
str.reserve(chars.len);
|
||||
foreach (Char32 c : chars)
|
||||
{
|
||||
str.append_char32(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < str.len()
|
||||
* @param [in] string
|
||||
* @param [in] to_trim
|
||||
**/
|
||||
fn void VarString.set(VarString str, usz index, char c)
|
||||
fn String String.trim(String string, String to_trim = "\t\n\r ")
|
||||
{
|
||||
str.data().chars[index] = c;
|
||||
}
|
||||
|
||||
fn void VarString.append_repeat(VarString* str, char c, usz times)
|
||||
{
|
||||
if (times == 0) return;
|
||||
str.reserve(times);
|
||||
StringData* data = str.data();
|
||||
for (usz i = 0; i < times; i++)
|
||||
{
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
usz start = 0;
|
||||
usz len = string.len;
|
||||
while (start < len && char_in_set(string[start], to_trim)) start++;
|
||||
if (start == len) return string[:0];
|
||||
usz end = len - 1;
|
||||
while (end > start && char_in_set(string[end], to_trim)) end--;
|
||||
return string[start..end];
|
||||
}
|
||||
|
||||
/**
|
||||
* @require c <= 0x10ffff
|
||||
*/
|
||||
fn void VarString.append_char32(VarString* str, Char32 c)
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn bool String.starts_with(String string, String needle)
|
||||
{
|
||||
if (c < 0x7f)
|
||||
{
|
||||
str.reserve(1);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)c;
|
||||
return;
|
||||
}
|
||||
if (c < 0x7ff)
|
||||
{
|
||||
str.reserve(2);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xC0 | c >> 6);
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
if (c < 0xffff)
|
||||
{
|
||||
str.reserve(3);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xE0 | c >> 12);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
str.reserve(4);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xF0 | c >> 18);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
if (needle.len > string.len) return false;
|
||||
if (!needle.len) return true;
|
||||
return string[:needle.len] == needle;
|
||||
}
|
||||
|
||||
fn VarString VarString.tcopy(VarString* str) => str.copy(mem::temp());
|
||||
|
||||
fn VarString VarString.copy(VarString* str, Allocator* allocator = null)
|
||||
/**
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn bool String.ends_with(String string, String needle)
|
||||
{
|
||||
if (!str)
|
||||
if (needle.len > string.len) return false;
|
||||
if (!needle.len) return true;
|
||||
return string[^needle.len..] == needle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the front of the string if the prefix exists.
|
||||
*
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn String String.strip(String string, String needle)
|
||||
{
|
||||
if (!needle.len || !string.starts_with(needle)) return string;
|
||||
return string[needle.len..];
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the end of the string if the suffix exists.
|
||||
*
|
||||
* @param [in] string
|
||||
* @param [in] needle
|
||||
**/
|
||||
fn String String.strip_end(String string, String needle)
|
||||
{
|
||||
if (!needle.len || !string.ends_with(needle)) return string;
|
||||
// Note that this is the safe way if we want to support zero length.
|
||||
return string[:(string.len - needle.len)];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @param [&inout] using "The allocator, defaults to the heap allocator"
|
||||
* @param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
* @require needle.len > 0 "The needle must be at least 1 character long"
|
||||
* @ensure return.len > 0
|
||||
**/
|
||||
fn String[] String.split(String s, String needle, usz max = 0, Allocator* using = mem::heap())
|
||||
{
|
||||
usz capacity = 16;
|
||||
usz i = 0;
|
||||
String* holder = malloc(String, capacity, .using = using);
|
||||
bool no_more = false;
|
||||
while (!no_more)
|
||||
{
|
||||
if (allocator) return new_with_capacity(0, allocator);
|
||||
return (VarString)null;
|
||||
usz! index = i == max - 1 ? SearchResult.MISSING! : s.index_of(needle);
|
||||
String res @noinit;
|
||||
if (try index)
|
||||
{
|
||||
res = s[:index];
|
||||
s = s[index + needle.len..];
|
||||
}
|
||||
else
|
||||
{
|
||||
res = s;
|
||||
no_more = true;
|
||||
}
|
||||
if (i == capacity)
|
||||
{
|
||||
capacity *= 2;
|
||||
holder = realloc(holder, String.sizeof * capacity, .using = using);
|
||||
}
|
||||
holder[i++] = res;
|
||||
}
|
||||
if (!allocator) allocator = mem::heap();
|
||||
StringData* data = str.data();
|
||||
VarString new_string = new_with_capacity(data.capacity, allocator);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
return holder[:i];
|
||||
}
|
||||
|
||||
fn ZString VarString.copy_zstr(VarString* str, Allocator* allocator = mem::heap())
|
||||
/**
|
||||
* This function is identical to String.split, but implicitly uses the
|
||||
* temporary allocator.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @param max "Max number of elements, 0 means no limit, defaults to 0"
|
||||
**/
|
||||
fn String[] tsplit(String s, String needle, usz max = 0)
|
||||
{
|
||||
usz str_len = str.len();
|
||||
if (!str_len)
|
||||
return s.split(needle, max, mem::temp()) @inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first incidence of a string.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @pure
|
||||
* @ensure return < s.len
|
||||
* @require needle.len > 0 "The needle must be len 1 or more"
|
||||
**/
|
||||
fn usz! String.index_of(String s, String needle)
|
||||
{
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[0];
|
||||
foreach (usz i, char c : s)
|
||||
{
|
||||
return (ZString)calloc(1, .using = allocator);
|
||||
if (c == search)
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start;
|
||||
search = needle[match];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[0];
|
||||
}
|
||||
}
|
||||
char* zstr = malloc(str_len + 1, .using = allocator);
|
||||
StringData* data = str.data();
|
||||
mem::copy(zstr, &data.chars, str_len);
|
||||
zstr[str_len] = 0;
|
||||
return (ZString)zstr;
|
||||
return SearchResult.MISSING!;
|
||||
}
|
||||
|
||||
fn String VarString.copy_str(VarString* str, Allocator* allocator = mem::heap())
|
||||
/**
|
||||
* Find the index of the last incidence of a string.
|
||||
*
|
||||
* @param [in] s
|
||||
* @param [in] needle
|
||||
* @pure
|
||||
* @ensure return < s.len
|
||||
* @require needle.len > 0 "The needle must be len 1 or more"
|
||||
**/
|
||||
fn usz! String.rindex_of(String s, String needle)
|
||||
{
|
||||
return (String)str.copy_zstr(allocator)[:str.len()];
|
||||
}
|
||||
|
||||
fn String VarString.tcopy_str(VarString* str) => str.copy_str(mem::temp()) @inline;
|
||||
|
||||
fn bool VarString.equals(VarString str, VarString other_string)
|
||||
{
|
||||
StringData *str1 = str.data();
|
||||
StringData *str2 = other_string.data();
|
||||
if (str1 == str2) return true;
|
||||
if (!str1) return str2.len == 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usz str1_len = str1.len;
|
||||
if (str1_len != str2.len) return false;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
usz match = 0;
|
||||
usz needed = needle.len;
|
||||
usz index_start = 0;
|
||||
char search = needle[^1];
|
||||
foreach_r (usz i, char c : s)
|
||||
{
|
||||
if (str1.chars[i] != str2.chars[i]) return false;
|
||||
if (c == search)
|
||||
{
|
||||
if (!match) index_start = i;
|
||||
match++;
|
||||
if (match == needed) return index_start - needle.len + 1;
|
||||
search = needle[^(match + 1)];
|
||||
continue;
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
match = 0;
|
||||
search = needle[^1];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return SearchResult.MISSING!;
|
||||
}
|
||||
|
||||
fn void VarString.destroy(VarString* str)
|
||||
fn ZString String.zstr_copy(String s, Allocator* using = mem::heap())
|
||||
{
|
||||
if (!*str) return;
|
||||
StringData* data = str.data();
|
||||
if (!data) return;
|
||||
free(data, .using = data.allocator);
|
||||
*str = (VarString)null;
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
}
|
||||
|
||||
fn bool VarString.less(VarString str, VarString other_string)
|
||||
fn String String.concat(String s1, String s2, Allocator* using = mem::heap())
|
||||
{
|
||||
StringData* str1 = str.data();
|
||||
StringData* str2 = other_string.data();
|
||||
if (str1 == str2) return false;
|
||||
if (!str1) return str2.len != 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usz str1_len = str1.len;
|
||||
usz str2_len = str2.len;
|
||||
if (str1_len != str2_len) return str1_len < str2_len;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
{
|
||||
if (str1.chars[i] >= str2.chars[i]) return false;
|
||||
}
|
||||
return true;
|
||||
usz full_len = s1.len + s2.len;
|
||||
char* str = malloc(full_len + 1, .using = using);
|
||||
usz s1_len = s1.len;
|
||||
mem::copy(str, s1.ptr, s1_len);
|
||||
mem::copy(str + s1_len, s2.ptr, s2.len);
|
||||
str[full_len] = 0;
|
||||
return (String)str[:full_len];
|
||||
}
|
||||
|
||||
fn void VarString.append_chars(VarString* this, String str)
|
||||
fn String String.tconcat(String s1, String s2) => s1.concat(s2, mem::temp());
|
||||
|
||||
|
||||
fn ZString String.zstr_tcopy(String s) => s.zstr_copy(mem::temp()) @inline;
|
||||
|
||||
fn String String.copy(String s, Allocator* using = mem::heap())
|
||||
{
|
||||
usz other_len = str.len;
|
||||
if (!other_len) return;
|
||||
if (!*this)
|
||||
{
|
||||
*this = new(str);
|
||||
return;
|
||||
}
|
||||
this.reserve(other_len);
|
||||
StringData* data = (StringData*)*this;
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
data.len += other_len;
|
||||
usz len = s.len;
|
||||
char* str = malloc(len + 1, .using = using);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (String)str[:len];
|
||||
}
|
||||
|
||||
fn Char32[] VarString.copy_utf32(VarString* this, Allocator* allocator = mem::heap())
|
||||
fn String String.tcopy(String s) => s.copy(mem::temp()) @inline;
|
||||
|
||||
fn String ZString.copy(ZString z, Allocator* using = mem::heap()) => z.as_str().copy(using) @inline;
|
||||
fn String ZString.tcopy(ZString z) => z.as_str().copy(mem::temp()) @inline;
|
||||
|
||||
fn Char16[]! String.to_utf16(String s, Allocator* using = mem::heap())
|
||||
{
|
||||
return str::utf8to32(this.str(), allocator) @inline!!;
|
||||
usz len16 = conv::utf16len_for_utf8(s);
|
||||
Char16* data = malloc_checked(Char16, len16 + 1, .using = using)?;
|
||||
conv::utf8to16_unsafe(s, data)?;
|
||||
data[len16] = 0;
|
||||
return data[:len16];
|
||||
}
|
||||
|
||||
fn void VarString.append_string(VarString* this, VarString str)
|
||||
fn String! from_utf16(Char16[] utf16, Allocator* using = mem::heap())
|
||||
{
|
||||
StringData* other = (StringData*)str;
|
||||
if (!other) return;
|
||||
this.append(str.str());
|
||||
}
|
||||
|
||||
fn void VarString.clear(VarString* str)
|
||||
{
|
||||
str.data().len = 0;
|
||||
}
|
||||
|
||||
fn void VarString.append_char(VarString* str, char c)
|
||||
{
|
||||
if (!*str)
|
||||
{
|
||||
*str = new_with_capacity(MIN_CAPACITY);
|
||||
}
|
||||
str.reserve(1);
|
||||
StringData* data = (StringData*)*str;
|
||||
data.chars[data.len++] = c;
|
||||
usz len = conv::utf8len_for_utf16(utf16);
|
||||
char* data = malloc_checked(len + 1, .using = using)?;
|
||||
conv::utf16to8_unsafe(utf16, data)?;
|
||||
data[len] = 0;
|
||||
return (String)data[:len];
|
||||
}
|
||||
|
||||
|
||||
macro void VarString.append(VarString* str, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type):
|
||||
$case char:
|
||||
$case ichar:
|
||||
str.append_char(value);
|
||||
$case VarString:
|
||||
str.append_string(value);
|
||||
$case String:
|
||||
str.append_chars(value);
|
||||
$case Char32:
|
||||
str.append_char32(value);
|
||||
$default:
|
||||
$if (@convertible(value, Char32)):
|
||||
str.append_char32(value);
|
||||
$elif (@convertible(value, String)):
|
||||
str.append_chars(value);
|
||||
$else:
|
||||
$assert(false, "Unsupported type for append – use printf instead.");
|
||||
$endif;
|
||||
$endswitch;
|
||||
}
|
||||
|
||||
|
||||
fn StringData* VarString.data(VarString str) @inline @private
|
||||
{
|
||||
return (StringData*)str;
|
||||
}
|
||||
|
||||
fn void VarString.reserve(VarString* str, usz addition) @private
|
||||
{
|
||||
StringData* data = str.data();
|
||||
if (!data)
|
||||
{
|
||||
*str = string::new_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usz len = data.len + addition;
|
||||
if (data.capacity >= len) return;
|
||||
usz new_capacity = data.capacity *= 2;
|
||||
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
|
||||
*str = (VarString)realloc(data, StringData.sizeof + new_capacity, .using = data.allocator);
|
||||
}
|
||||
|
||||
fn VarString VarString.new_concat(VarString a, VarString b, Allocator* allocator = mem::heap())
|
||||
{
|
||||
VarString string = new_with_capacity(a.len() + b.len(), allocator);
|
||||
string.append(a);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
struct StringData @private
|
||||
{
|
||||
Allocator* allocator;
|
||||
usz len;
|
||||
usz capacity;
|
||||
char[*] chars;
|
||||
}
|
||||
fn Char16[]! String.to_temp_utf16(String s) => s.to_utf16(mem::temp());
|
||||
|
||||
326
lib/std/core/varstring.c3
Normal file
326
lib/std/core/varstring.c3
Normal file
@@ -0,0 +1,326 @@
|
||||
module std::core::string;
|
||||
import libc;
|
||||
|
||||
typedef VarString = distinct void*;
|
||||
typedef DynStr = VarString;
|
||||
typedef DynString = VarString;
|
||||
typedef VString = VarString;
|
||||
typedef Text = VarString;
|
||||
|
||||
const usz MIN_CAPACITY = 16;
|
||||
|
||||
fn VarString new_with_capacity(usz capacity, Allocator* allocator = mem::heap())
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
StringData* data = malloc(StringData, 1, .using = allocator, .end_padding = capacity);
|
||||
data.allocator = allocator;
|
||||
data.len = 0;
|
||||
data.capacity = capacity;
|
||||
return (VarString)data;
|
||||
}
|
||||
|
||||
fn VarString new(String c)
|
||||
{
|
||||
usz len = c.len;
|
||||
VarString str = new_with_capacity(len);
|
||||
StringData* data = str.data();
|
||||
if (len)
|
||||
{
|
||||
data.len = len;
|
||||
mem::copy(&data.chars, c.ptr, len);
|
||||
}
|
||||
return (VarString)data;
|
||||
}
|
||||
|
||||
fn ZString VarString.zstr(VarString str)
|
||||
{
|
||||
StringData* data = str.data();
|
||||
if (!data) return (ZString)"";
|
||||
if (data.capacity == data.len)
|
||||
{
|
||||
str.reserve(1);
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
else if (data.chars[data.len] != 0)
|
||||
{
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
return (ZString)&data.chars[0];
|
||||
}
|
||||
|
||||
fn usz VarString.capacity(VarString this)
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().capacity;
|
||||
}
|
||||
|
||||
fn usz VarString.len(VarString this)
|
||||
{
|
||||
if (!this) return 0;
|
||||
return this.data().len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require new_size <= this.len()
|
||||
*/
|
||||
fn void VarString.chop(VarString this, usz new_size)
|
||||
{
|
||||
if (!this) return;
|
||||
this.data().len = new_size;
|
||||
}
|
||||
|
||||
fn String VarString.str(VarString str)
|
||||
{
|
||||
StringData* data = (StringData*)str;
|
||||
if (!data) return "";
|
||||
return (String)data.chars[:data.len];
|
||||
}
|
||||
|
||||
fn void VarString.append_utf32(VarString* str, Char32[] chars)
|
||||
{
|
||||
str.reserve(chars.len);
|
||||
foreach (Char32 c : chars)
|
||||
{
|
||||
str.append_char32(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require index < str.len()
|
||||
**/
|
||||
fn void VarString.set(VarString str, usz index, char c)
|
||||
{
|
||||
str.data().chars[index] = c;
|
||||
}
|
||||
|
||||
fn void VarString.append_repeat(VarString* str, char c, usz times)
|
||||
{
|
||||
if (times == 0) return;
|
||||
str.reserve(times);
|
||||
StringData* data = str.data();
|
||||
for (usz i = 0; i < times; i++)
|
||||
{
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require c <= 0x10ffff
|
||||
*/
|
||||
fn void VarString.append_char32(VarString* str, Char32 c)
|
||||
{
|
||||
if (c < 0x7f)
|
||||
{
|
||||
str.reserve(1);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)c;
|
||||
return;
|
||||
}
|
||||
if (c < 0x7ff)
|
||||
{
|
||||
str.reserve(2);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xC0 | c >> 6);
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
if (c < 0xffff)
|
||||
{
|
||||
str.reserve(3);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xE0 | c >> 12);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
str.reserve(4);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xF0 | c >> 18);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
}
|
||||
|
||||
fn VarString VarString.tcopy(VarString* str) => str.copy(mem::temp());
|
||||
|
||||
fn VarString VarString.copy(VarString* str, Allocator* allocator = null)
|
||||
{
|
||||
if (!str)
|
||||
{
|
||||
if (allocator) return new_with_capacity(0, allocator);
|
||||
return (VarString)null;
|
||||
}
|
||||
if (!allocator) allocator = mem::heap();
|
||||
StringData* data = str.data();
|
||||
VarString new_string = new_with_capacity(data.capacity, allocator);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString VarString.copy_zstr(VarString* str, Allocator* allocator = mem::heap())
|
||||
{
|
||||
usz str_len = str.len();
|
||||
if (!str_len)
|
||||
{
|
||||
return (ZString)calloc(1, .using = allocator);
|
||||
}
|
||||
char* zstr = malloc(str_len + 1, .using = allocator);
|
||||
StringData* data = str.data();
|
||||
mem::copy(zstr, &data.chars, str_len);
|
||||
zstr[str_len] = 0;
|
||||
return (ZString)zstr;
|
||||
}
|
||||
|
||||
fn String VarString.copy_str(VarString* str, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return (String)str.copy_zstr(allocator)[:str.len()];
|
||||
}
|
||||
|
||||
fn String VarString.tcopy_str(VarString* str) => str.copy_str(mem::temp()) @inline;
|
||||
|
||||
fn bool VarString.equals(VarString str, VarString other_string)
|
||||
{
|
||||
StringData *str1 = str.data();
|
||||
StringData *str2 = other_string.data();
|
||||
if (str1 == str2) return true;
|
||||
if (!str1) return str2.len == 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usz str1_len = str1.len;
|
||||
if (str1_len != str2.len) return false;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
{
|
||||
if (str1.chars[i] != str2.chars[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void VarString.destroy(VarString* str)
|
||||
{
|
||||
if (!*str) return;
|
||||
StringData* data = str.data();
|
||||
if (!data) return;
|
||||
free(data, .using = data.allocator);
|
||||
*str = (VarString)null;
|
||||
}
|
||||
|
||||
fn bool VarString.less(VarString str, VarString other_string)
|
||||
{
|
||||
StringData* str1 = str.data();
|
||||
StringData* str2 = other_string.data();
|
||||
if (str1 == str2) return false;
|
||||
if (!str1) return str2.len != 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usz str1_len = str1.len;
|
||||
usz str2_len = str2.len;
|
||||
if (str1_len != str2_len) return str1_len < str2_len;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
{
|
||||
if (str1.chars[i] >= str2.chars[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void VarString.append_chars(VarString* this, String str)
|
||||
{
|
||||
usz other_len = str.len;
|
||||
if (!other_len) return;
|
||||
if (!*this)
|
||||
{
|
||||
*this = new(str);
|
||||
return;
|
||||
}
|
||||
this.reserve(other_len);
|
||||
StringData* data = (StringData*)*this;
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
fn Char32[] VarString.copy_utf32(VarString* this, Allocator* allocator = mem::heap())
|
||||
{
|
||||
return str::utf8to32(this.str(), allocator) @inline!!;
|
||||
}
|
||||
|
||||
fn void VarString.append_string(VarString* this, VarString str)
|
||||
{
|
||||
StringData* other = (StringData*)str;
|
||||
if (!other) return;
|
||||
this.append(str.str());
|
||||
}
|
||||
|
||||
fn void VarString.clear(VarString* str)
|
||||
{
|
||||
str.data().len = 0;
|
||||
}
|
||||
|
||||
fn void VarString.append_char(VarString* str, char c)
|
||||
{
|
||||
if (!*str)
|
||||
{
|
||||
*str = new_with_capacity(MIN_CAPACITY);
|
||||
}
|
||||
str.reserve(1);
|
||||
StringData* data = (StringData*)*str;
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
|
||||
|
||||
macro void VarString.append(VarString* str, value)
|
||||
{
|
||||
var $Type = $typeof(value);
|
||||
$switch ($Type):
|
||||
$case char:
|
||||
$case ichar:
|
||||
str.append_char(value);
|
||||
$case VarString:
|
||||
str.append_string(value);
|
||||
$case String:
|
||||
str.append_chars(value);
|
||||
$case Char32:
|
||||
str.append_char32(value);
|
||||
$default:
|
||||
$if (@convertible(value, Char32)):
|
||||
str.append_char32(value);
|
||||
$elif (@convertible(value, String)):
|
||||
str.append_chars(value);
|
||||
$else:
|
||||
$assert(false, "Unsupported type for append – use printf instead.");
|
||||
$endif;
|
||||
$endswitch;
|
||||
}
|
||||
|
||||
|
||||
fn StringData* VarString.data(VarString str) @inline @private
|
||||
{
|
||||
return (StringData*)str;
|
||||
}
|
||||
|
||||
fn void VarString.reserve(VarString* str, usz addition) @private
|
||||
{
|
||||
StringData* data = str.data();
|
||||
if (!data)
|
||||
{
|
||||
*str = string::new_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usz len = data.len + addition;
|
||||
if (data.capacity >= len) return;
|
||||
usz new_capacity = data.capacity *= 2;
|
||||
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
|
||||
*str = (VarString)realloc(data, StringData.sizeof + new_capacity, .using = data.allocator);
|
||||
}
|
||||
|
||||
fn VarString VarString.new_concat(VarString a, VarString b, Allocator* allocator = mem::heap())
|
||||
{
|
||||
VarString string = new_with_capacity(a.len() + b.len(), allocator);
|
||||
string.append(a);
|
||||
string.append(b);
|
||||
return string;
|
||||
}
|
||||
|
||||
struct StringData @private
|
||||
{
|
||||
Allocator* allocator;
|
||||
usz len;
|
||||
usz capacity;
|
||||
char[*] chars;
|
||||
}
|
||||
@@ -1,182 +1,3 @@
|
||||
module std::io::dir;
|
||||
import std::io::os;
|
||||
// In progress.
|
||||
typedef Path = distinct String;
|
||||
|
||||
const PREFERRED_SEPARATOR = env::os_is_win32() ? '\\' : '/';
|
||||
|
||||
fault PathResult
|
||||
{
|
||||
INVALID_PATH
|
||||
}
|
||||
|
||||
fn String! getcwd(Allocator* using = mem::heap())
|
||||
{
|
||||
return os::getcwd(using);
|
||||
}
|
||||
|
||||
fn String! tgetcwd()
|
||||
{
|
||||
return getcwd(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
macro bool is_separator(char c)
|
||||
{
|
||||
$if (env::os_is_win32()):
|
||||
return c == '/' || c == '\\';
|
||||
$else:
|
||||
return c == '/';
|
||||
$endif;
|
||||
}
|
||||
|
||||
const bool[256] RESERVED_PATH_CHAR_POSIX = {
|
||||
[0] = true,
|
||||
['/'] = true,
|
||||
};
|
||||
const bool[256] RESERVED_PATH_CHAR_WIN32 = {
|
||||
[0..31] = true,
|
||||
['>'] = true,
|
||||
['<'] = true,
|
||||
[':'] = true,
|
||||
['\"'] = true,
|
||||
['/'] = true,
|
||||
['\\'] = true,
|
||||
['|'] = true,
|
||||
['?'] = true,
|
||||
['*'] = true,
|
||||
};
|
||||
|
||||
macro bool is_reserved_path_char(char c)
|
||||
{
|
||||
$if (env::os_is_win32()):
|
||||
return RESERVED_PATH_CHAR_WIN32[c];
|
||||
$else:
|
||||
return RESERVED_PATH_CHAR_POSIX[c];
|
||||
$endif;
|
||||
}
|
||||
|
||||
fn usz! root_name_len(String path) @private
|
||||
{
|
||||
usz len = path.len;
|
||||
if (!len) return 0;
|
||||
$if (env::os_is_win32()):
|
||||
switch (path[0])
|
||||
{
|
||||
case '\\':
|
||||
if (len < 2 || path[1] != '\\') break;
|
||||
if (len == 2 || is_separator(path[2])) return PathResult.INVALID_PATH!;
|
||||
for (usz i = 2; i < len; i++)
|
||||
{
|
||||
char c = path[i];
|
||||
if (is_separator(c)) return i;
|
||||
if (is_reserved_path_char(c)) return PathResult.INVALID_PATH!;
|
||||
}
|
||||
return len;
|
||||
case 'A'..'Z':
|
||||
case 'a'..'z':
|
||||
if (len < 2 || path[1] != ':') break;
|
||||
if (len < 3 || !is_separator(path[2])) return PathResult.INVALID_PATH!;
|
||||
return 2;
|
||||
}
|
||||
$endif;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn void! normalize_path(String* path_ref) @private
|
||||
{
|
||||
String path = *path_ref;
|
||||
if (!path.len) return;
|
||||
usz path_start = root_name_len(path)?;
|
||||
usz len = path_start;
|
||||
bool previous_was_separator = false;
|
||||
usz path_len = path.len;
|
||||
for (usz i = path_start; i < path_len; i++)
|
||||
{
|
||||
char c = path[i];
|
||||
// Fold foo///bar into foo/bar
|
||||
if (is_separator(c))
|
||||
{
|
||||
if (previous_was_separator)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
path.ptr[len++] = PREFERRED_SEPARATOR;
|
||||
previous_was_separator = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we get . we have different things that might happen:
|
||||
if (c == '.' && i < path_len - 1)
|
||||
{
|
||||
// Is this ./ or /./ ?
|
||||
if ((previous_was_separator || i == path_start) && is_separator(path[i + 1]))
|
||||
{
|
||||
// Then we skip this
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
// Is this /../ in that case we must walk back and erase(!)
|
||||
if (i < path_len - 2 && previous_was_separator && path[i + 1] == '.' && is_separator(path[i + 2]))
|
||||
{
|
||||
assert(len > path_start);
|
||||
len--;
|
||||
while (len > path_start && !is_separator(path[len - 1]))
|
||||
{
|
||||
len--;
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (i != len) path[len] = c;
|
||||
previous_was_separator = false;
|
||||
len++;
|
||||
}
|
||||
path.ptr[len] = 0;
|
||||
*path_ref = path[:len];
|
||||
}
|
||||
|
||||
fn Path new_path(String path, Allocator* using = using)
|
||||
{
|
||||
String copy = path.copy(using);
|
||||
normalize_path(©)!!;
|
||||
return (Path)copy;
|
||||
}
|
||||
|
||||
fn Path Path.root_name(Path path)
|
||||
{
|
||||
String path_str = (String)path;
|
||||
usz len = root_name_len(path_str)!!;
|
||||
if (!len) return (Path)"";
|
||||
return (Path)path_str[0:len];
|
||||
}
|
||||
|
||||
fn Path Path.root_directory(Path path)
|
||||
{
|
||||
String path_str = (String)path;
|
||||
usz len = path_str.len;
|
||||
if (!len) return (Path)"";
|
||||
$if (env::os_is_win32()):
|
||||
usz root_len = root_name_len(path_str)!!;
|
||||
if (root_len == len || !is_separator(path_str[root_len])) return (Path)"";
|
||||
return (Path)path_str[root_len..root_len];
|
||||
$else:
|
||||
if (!is_separator(path_str[0])) return (Path)"";
|
||||
for (usz i = 1; i < len; i++)
|
||||
{
|
||||
if (is_separator(path_str[i]))
|
||||
{
|
||||
return (Path)path_str[:i];
|
||||
}
|
||||
}
|
||||
return path;
|
||||
$endif;
|
||||
}
|
||||
|
||||
|
||||
fn void Path.destroy(Path path)
|
||||
{
|
||||
free(path.ptr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ module std::io;
|
||||
import libc;
|
||||
|
||||
|
||||
|
||||
|
||||
fn void! File.open(File* file, String filename, String mode)
|
||||
{
|
||||
file.file = os::native_fopen(filename, mode)?;
|
||||
|
||||
@@ -1,90 +1,12 @@
|
||||
module std::io::file;
|
||||
import libc;
|
||||
|
||||
struct FileInfo
|
||||
fn bool is_file(String path)
|
||||
{
|
||||
ulong size;
|
||||
return os::native_is_file(path);
|
||||
}
|
||||
|
||||
fn bool exits(String path)
|
||||
fn usz! get_size(String path)
|
||||
{
|
||||
File f;
|
||||
if (catch(f.open(path, "r"))) return false;
|
||||
(void)f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
$switch (env::OS_TYPE):
|
||||
$case MACOSX:
|
||||
$case IOS:
|
||||
$case WATCHOS:
|
||||
$case TVOS:
|
||||
struct DarwinTimespec @private
|
||||
{
|
||||
long tv_sec;
|
||||
long tv_nsec;
|
||||
}
|
||||
struct Darwin64Stat @private
|
||||
{
|
||||
int st_dev;
|
||||
ushort st_mode;
|
||||
ushort st_nlink;
|
||||
ulong st_ino;
|
||||
uint st_uid;
|
||||
uint st_gid;
|
||||
int st_rdev;
|
||||
DarwinTimespec st_atimespec; // time of last access
|
||||
DarwinTimespec st_mtimespec; // time of last data modification
|
||||
DarwinTimespec st_ctimespec; // time of last status change
|
||||
DarwinTimespec st_birthtimespec; // time of file creation(birth)
|
||||
long st_size;
|
||||
long st_blocks;
|
||||
int st_blocksize;
|
||||
uint st_flags;
|
||||
uint st_gen;
|
||||
int st_lspare;
|
||||
long[2] st_qspare;
|
||||
}
|
||||
extern fn int _stat(ZString str, Darwin64Stat* stat) @extern("stat64");
|
||||
|
||||
fn void! FileInfo.read(FileInfo* info, Path path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
int res = _stat(((String)path).zstr_tcopy(), &stat);
|
||||
if (res != 0)
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EBADF:
|
||||
return IoError.FILE_NOT_VALID!;
|
||||
case errno::EFAULT:
|
||||
unreachable("Invalid stat");
|
||||
case errno::EIO:
|
||||
return IoError.GENERAL_ERROR!;
|
||||
case errno::EACCES:
|
||||
return IoError.NO_PERMISSION!;
|
||||
case errno::ELOOP:
|
||||
return IoError.NO_PERMISSION!;
|
||||
case errno::ENAMETOOLONG:
|
||||
return IoError.NAME_TOO_LONG!;
|
||||
case errno::ENOENT:
|
||||
return IoError.FILE_NOT_FOUND!;
|
||||
case errno::ENOTDIR:
|
||||
return IoError.FILE_NOT_DIR!;
|
||||
case errno::EOVERFLOW:
|
||||
return IoError.GENERAL_ERROR!;
|
||||
default:
|
||||
return IoError.UNKNOWN_ERROR!;
|
||||
}
|
||||
}
|
||||
info.size = stat.st_size;
|
||||
};
|
||||
}
|
||||
$default:
|
||||
macro void! FileInfo.read(FileInfo* info, Path path)
|
||||
{
|
||||
$assert("Unsupported function");
|
||||
}
|
||||
$endswitch;
|
||||
return os::native_file_size(path);
|
||||
}
|
||||
266
lib/std/io/io_path.c3
Normal file
266
lib/std/io/io_path.c3
Normal file
@@ -0,0 +1,266 @@
|
||||
module std::io::path;
|
||||
|
||||
const PREFERRED_SEPARATOR = env::os_is_win32() ? '\\' : '/';
|
||||
|
||||
fault PathResult
|
||||
{
|
||||
INVALID_PATH
|
||||
}
|
||||
|
||||
typedef Path = distinct String;
|
||||
|
||||
|
||||
|
||||
fn Path! getcwd(Allocator* using = mem::heap())
|
||||
{
|
||||
return (Path)os::getcwd(using);
|
||||
}
|
||||
|
||||
fn Path! tgetcwd()
|
||||
{
|
||||
return getcwd(mem::temp()) @inline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn bool is_dir(String path)
|
||||
{
|
||||
return os::native_is_dir(path);
|
||||
}
|
||||
|
||||
macro bool is_separator(char c)
|
||||
{
|
||||
$if (env::os_is_win32()):
|
||||
return c == '/' || c == '\\';
|
||||
$else:
|
||||
return c == '/';
|
||||
$endif;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn Path! new(String path, Allocator* using = mem::heap())
|
||||
{
|
||||
Path copy = (Path)path.copy(using);
|
||||
copy.normalize()?;
|
||||
return (Path)copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the string to the current path.
|
||||
*
|
||||
* @param [in] path
|
||||
* @param [in] filename
|
||||
* @ensure return.len >= filename.len + path.len
|
||||
**/
|
||||
fn Path! Path.append(Path path, String filename, Allocator* using = mem::heap())
|
||||
{
|
||||
if (!path.len) return new(filename, using)?;
|
||||
if (is_separator(path[^1]))
|
||||
{
|
||||
return (Path)path.as_str().concat(filename, using);
|
||||
}
|
||||
// Handle temp nested temp allocations.
|
||||
TempAllocator* temp = mem::temp();
|
||||
usz mark = temp.used;
|
||||
defer if (using != temp) temp.reset(mark);
|
||||
DString dstr = dstring::new_with_capacity(path.len + 1 + filename.len, .using = temp);
|
||||
dstr.append(path.as_str());
|
||||
dstr.append(PREFERRED_SEPARATOR);
|
||||
dstr.append(filename);
|
||||
Path p = using == temp ? (Path)dstr.str() : (Path)dstr.copy_str(using);
|
||||
p.normalize()?;
|
||||
return p;
|
||||
}
|
||||
|
||||
fn Path! temp_directory(Allocator* using = mem::heap())
|
||||
{
|
||||
return os::native_temp_directory(using);
|
||||
}
|
||||
|
||||
fn Path Path.root_name(Path path)
|
||||
{
|
||||
usz len = root_name_len(path.as_str())!!;
|
||||
if (!len) return (Path)"";
|
||||
return path[:len];
|
||||
}
|
||||
|
||||
|
||||
fn usz! root_name_len(String path) @local
|
||||
{
|
||||
usz len = path.len;
|
||||
if (!len) return 0;
|
||||
$if (env::os_is_win32()):
|
||||
switch (path[0])
|
||||
{
|
||||
case '\\':
|
||||
if (len < 2 || path[1] != '\\') break;
|
||||
if (len == 2 || is_separator(path[2])) return PathResult.INVALID_PATH!;
|
||||
for (usz i = 2; i < len; i++)
|
||||
{
|
||||
char c = path[i];
|
||||
if (is_separator(c)) return i;
|
||||
if (is_reserved_path_char(c)) return PathResult.INVALID_PATH!;
|
||||
}
|
||||
return len;
|
||||
case 'A'..'Z':
|
||||
case 'a'..'z':
|
||||
if (len < 2 || path[1] != ':') break;
|
||||
if (len < 3 || !is_separator(path[2])) return PathResult.INVALID_PATH!;
|
||||
return 2;
|
||||
}
|
||||
$endif;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn Path! Path.parent(Path path)
|
||||
{
|
||||
foreach_r(i, c : path)
|
||||
{
|
||||
if (is_separator(c) && i != path.len - 1)
|
||||
{
|
||||
return (Path)path[:i];
|
||||
}
|
||||
}
|
||||
return PathResult.INVALID_PATH!;
|
||||
}
|
||||
|
||||
fn void! Path.normalize(Path* path)
|
||||
{
|
||||
String path_str = path.as_str();
|
||||
if (!path_str.len) return;
|
||||
usz path_start = root_name_len(path_str)?;
|
||||
usz len = path_start;
|
||||
bool previous_was_separator = false;
|
||||
usz path_len = path.len;
|
||||
for (usz i = path_start; i < path_len; i++)
|
||||
{
|
||||
char c = path_str[i];
|
||||
// Fold foo///bar into foo/bar
|
||||
if (is_separator(c))
|
||||
{
|
||||
if (previous_was_separator)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
path_str.ptr[len++] = PREFERRED_SEPARATOR;
|
||||
previous_was_separator = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we get . we have different things that might happen:
|
||||
if (c == '.' && i < path_len - 1)
|
||||
{
|
||||
// Is this ./ or /./ ?
|
||||
if ((previous_was_separator || i == path_start) && is_separator(path_str[i + 1]))
|
||||
{
|
||||
// Then we skip this
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
// Is this /../ in that case we must walk back and erase(!)
|
||||
if (i < path_len - 2 && previous_was_separator && path_str[i + 1] == '.' && is_separator(path_str[i + 2]))
|
||||
{
|
||||
assert(len > path_start);
|
||||
len--;
|
||||
while (len > path_start && !is_separator(path_str[len - 1]))
|
||||
{
|
||||
len--;
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (i != len) path_str[len] = c;
|
||||
previous_was_separator = false;
|
||||
len++;
|
||||
}
|
||||
path_str.ptr[len] = 0;
|
||||
*path = (Path)path_str[:len];
|
||||
}
|
||||
|
||||
fn Path Path.root_directory(Path path)
|
||||
{
|
||||
String path_str = path.as_str();
|
||||
usz len = path_str.len;
|
||||
if (!len) return (Path)"";
|
||||
$if (env::os_is_win32()):
|
||||
usz root_len = root_name_len(path_str)!!;
|
||||
if (root_len == len || !is_separator(path_str[root_len])) return (Path)"";
|
||||
return (Path)path_str[root_len..root_len];
|
||||
$else:
|
||||
if (!is_separator(path_str[0])) return (Path)"";
|
||||
for (usz i = 1; i < len; i++)
|
||||
{
|
||||
if (is_separator(path_str[i]))
|
||||
{
|
||||
return (Path)path_str[:i];
|
||||
}
|
||||
}
|
||||
return path;
|
||||
$endif;
|
||||
}
|
||||
|
||||
fn usz! Path.file_size(Path path)
|
||||
{
|
||||
return os::native_file_size(path.as_str());
|
||||
}
|
||||
|
||||
fn String Path.as_str(Path path)
|
||||
{
|
||||
return (String)path;
|
||||
}
|
||||
|
||||
fn bool Path.file_or_dir_exists(Path path)
|
||||
{
|
||||
return os::native_file_or_dir_exists(path.as_str());
|
||||
}
|
||||
|
||||
fn bool Path.is_dir(Path path)
|
||||
{
|
||||
return os::native_is_dir(path.as_str());
|
||||
}
|
||||
|
||||
fn bool Path.has_suffix(Path path, String str)
|
||||
{
|
||||
return path.as_str().ends_with(str);
|
||||
}
|
||||
|
||||
fn bool Path.is_file(Path path)
|
||||
{
|
||||
return os::native_is_file(path.as_str());
|
||||
}
|
||||
|
||||
|
||||
fn void Path.free(Path path)
|
||||
{
|
||||
free(path.ptr);
|
||||
}
|
||||
|
||||
|
||||
const bool[256] RESERVED_PATH_CHAR_POSIX = {
|
||||
[0] = true,
|
||||
['/'] = true,
|
||||
};
|
||||
const bool[256] RESERVED_PATH_CHAR_WIN32 = {
|
||||
[0..31] = true,
|
||||
['>'] = true,
|
||||
['<'] = true,
|
||||
[':'] = true,
|
||||
['\"'] = true,
|
||||
['/'] = true,
|
||||
['\\'] = true,
|
||||
['|'] = true,
|
||||
['?'] = true,
|
||||
['*'] = true,
|
||||
};
|
||||
|
||||
macro bool is_reserved_path_char(char c)
|
||||
{
|
||||
$if (env::os_is_win32()):
|
||||
return RESERVED_PATH_CHAR_WIN32[c];
|
||||
$else:
|
||||
return RESERVED_PATH_CHAR_POSIX[c];
|
||||
$endif;
|
||||
}
|
||||
|
||||
@@ -185,4 +185,8 @@ extern fn void* _wfopen(Char16*, Char16*) @local;
|
||||
extern fn void* _wfreopen(Char16*, Char16*, CFile) @local;
|
||||
extern fn int _fseeki64(CFile, long, int) @local;
|
||||
extern fn long _ftelli64(CFile) @local;
|
||||
$endif;
|
||||
$endif;
|
||||
|
||||
$if (env::os_is_posix()):
|
||||
extern fn CInt access(ZString path, CInt mode);
|
||||
$endif;
|
||||
|
||||
101
lib/std/io/os/fileinfo_darwin.c3
Normal file
101
lib/std/io/os/fileinfo_darwin.c3
Normal file
@@ -0,0 +1,101 @@
|
||||
module std::io::file::os;
|
||||
import libc;
|
||||
|
||||
$if (env::os_is_darwin()):
|
||||
|
||||
struct DarwinTimespec @private
|
||||
{
|
||||
long tv_sec;
|
||||
long tv_nsec;
|
||||
}
|
||||
struct Darwin64Stat @private
|
||||
{
|
||||
int st_dev;
|
||||
ushort st_mode;
|
||||
ushort st_nlink;
|
||||
ulong st_ino;
|
||||
uint st_uid;
|
||||
uint st_gid;
|
||||
int st_rdev;
|
||||
DarwinTimespec st_atimespec; // time of last access
|
||||
DarwinTimespec st_mtimespec; // time of last data modification
|
||||
DarwinTimespec st_ctimespec; // time of last status change
|
||||
DarwinTimespec st_birthtimespec; // time of file creation(birth)
|
||||
long st_size;
|
||||
long st_blocks;
|
||||
int st_blocksize;
|
||||
uint st_flags;
|
||||
uint st_gen;
|
||||
int st_lspare;
|
||||
long[2] st_qspare;
|
||||
}
|
||||
extern fn int _stat(ZString str, Darwin64Stat* stat) @extern("stat64");
|
||||
|
||||
const S_IFMT = 0o170000; // type of file mask
|
||||
const S_IFIFO = 0o010000; // named pipe (fifo)
|
||||
const S_IFCHR = 0o020000; // character special
|
||||
const S_IFDIR = 0o040000; // directory
|
||||
const S_IFBLK = 0o060000; // block special
|
||||
const S_IFREG = 0o100000; // regular
|
||||
const S_IFLNK = 0o120000; // symbolic link
|
||||
const S_IFSOCK = 0o140000; // socket
|
||||
|
||||
fn usz! native_file_size(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
read_stat(&stat, path)?;
|
||||
return stat.st_size;
|
||||
}
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
return try(read_stat(&stat, path));
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
return try(read_stat(&stat, path)) && (stat.st_mode & S_IFREG);
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
Darwin64Stat stat;
|
||||
return try(read_stat(&stat, path)) && (stat.st_mode & S_IFDIR);
|
||||
}
|
||||
|
||||
fn void! read_stat(Darwin64Stat* stat, String path) @local
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
int res = _stat(path.zstr_tcopy(), stat);
|
||||
if (res != 0)
|
||||
{
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::EBADF:
|
||||
return IoError.FILE_NOT_VALID!;
|
||||
case errno::EFAULT:
|
||||
unreachable("Invalid stat");
|
||||
case errno::EIO:
|
||||
return IoError.GENERAL_ERROR!;
|
||||
case errno::EACCES:
|
||||
return IoError.NO_PERMISSION!;
|
||||
case errno::ELOOP:
|
||||
return IoError.NO_PERMISSION!;
|
||||
case errno::ENAMETOOLONG:
|
||||
return IoError.NAME_TOO_LONG!;
|
||||
case errno::ENOENT:
|
||||
return IoError.FILE_NOT_FOUND!;
|
||||
case errno::ENOTDIR:
|
||||
return IoError.FILE_NOT_DIR!;
|
||||
case errno::EOVERFLOW:
|
||||
return IoError.GENERAL_ERROR!;
|
||||
default:
|
||||
return IoError.UNKNOWN_ERROR!;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
$endif;
|
||||
70
lib/std/io/os/fileinfo_other.c3
Normal file
70
lib/std/io/os/fileinfo_other.c3
Normal file
@@ -0,0 +1,70 @@
|
||||
module std::io::file::os;
|
||||
|
||||
// native_temp_directory, for non Win32
|
||||
$if (!env::os_is_win32()):
|
||||
|
||||
fn Path! native_temp_directory(Allocator* using = mem::heap())
|
||||
{
|
||||
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
|
||||
{
|
||||
String tmpdir = env::get_var(env) ?? "";
|
||||
if (tmpdir) return path::new(tmpdir, using);
|
||||
}
|
||||
return path::new("/tmp", using);
|
||||
}
|
||||
|
||||
$endif;
|
||||
|
||||
$if (!env::os_is_darwin() && !env::os_is_win32()):
|
||||
|
||||
fn usz! native_file_size(String path)
|
||||
{
|
||||
File f;
|
||||
f.open(path, "r")?;
|
||||
defer (void)f.close();
|
||||
return f.seek(0, Seek.END)?;
|
||||
}
|
||||
|
||||
$if (env::os_is_posix() && env::COMPILER_LIBC_AVAILABLE):
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return os::access(path.zstr_tcopy(), 0 /* F_OK */) != -1;
|
||||
};
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
File f;
|
||||
if (catch(f.open(path, "r"))) return false;
|
||||
(void)f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
return native_file_or_dir_exists(path) && !native_is_file(path);
|
||||
}
|
||||
|
||||
$else:
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
unreachable("Tried to call file_or_dir_exists without support.");
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
unreachable("Tried to call is_dir without support.");
|
||||
}
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
unreachable("Tried to call is_file without support.");
|
||||
}
|
||||
|
||||
$endif;
|
||||
|
||||
$endif;
|
||||
71
lib/std/io/os/fileinfo_win32.c3
Normal file
71
lib/std/io/os/fileinfo_win32.c3
Normal file
@@ -0,0 +1,71 @@
|
||||
module std::io::file::os;
|
||||
import std::os::win32::files;
|
||||
|
||||
$if (env::os_is_win32()):
|
||||
|
||||
fn usz! native_file_size(String path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
Char16[] path16 = path.to_temp_utf16()?;
|
||||
Win32_FILE_ATTRIBUTE_DATA data;
|
||||
files::win32_GetFileAttributesExW(path16, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
|
||||
Win32_LARGE_INTEGER size;
|
||||
size.lowPart = data.nFileSizeLow;
|
||||
size.highPart = data.nFileSizeHigh;
|
||||
return (usz)size.quadPart;
|
||||
};
|
||||
}
|
||||
|
||||
fn bool native_file_or_dir_exists(String path)
|
||||
{
|
||||
@pool()
|
||||
{
|
||||
return files::win32_PathFileExistsW(path.to_temp_utf16()) ?? false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fn bool native_is_file(String path)
|
||||
{
|
||||
File f;
|
||||
if (catch(f.open(path, "r"))) return false;
|
||||
(void)f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
fn bool native_is_dir(String path)
|
||||
{
|
||||
return native_file_or_dir_exists(path) && !native_is_file(path);
|
||||
}
|
||||
|
||||
fn Path! native_temp_directory(Allocator* using = mem::heap())
|
||||
{
|
||||
Char16[256] default_buffer;
|
||||
Char16* buff = &default_buffer;
|
||||
// Free if we allocated on the heap.
|
||||
defer if (buff != &default_buffer) free(buff);
|
||||
|
||||
Win32_DWORD len = files::win32_GetTempPathW(default_buffer.len, buff);
|
||||
if (!len) return IoError.GENERAL_ERROR!;
|
||||
// Too long, allocate
|
||||
if (len > default_buffer.len)
|
||||
{
|
||||
// Allocate on the heap
|
||||
buff = tmalloc(Char16, len);
|
||||
if (!files::win32_GetTempPathW(len, buff)) return IoError.GENERAL_ERROR!;
|
||||
}
|
||||
return (Path)string::from_utf16(buff[:len], using);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
}else if(method == file_size_methods::get_attributes){
|
||||
WIN32_FILE_ATTRIBUTE_DATA file_attr_data;
|
||||
if(GetFileAttributesEx(path, GetFileExInfoStandard, &file_attr_data)){
|
||||
file_size.LowPart = file_attr_data.nFileSizeLow;
|
||||
file_size.HighPart = file_attr_data.nFileSizeHigh;
|
||||
}
|
||||
}
|
||||
*/
|
||||
$endif;
|
||||
@@ -1,6 +1,33 @@
|
||||
module std::os::win32::files;
|
||||
|
||||
$if (env::OS_TYPE == OsType.WIN32):
|
||||
$if (env::os_is_win32()):
|
||||
|
||||
enum Win32_GET_FILEEX_INFO_LEVELS
|
||||
{
|
||||
STANDARD,
|
||||
MAX,
|
||||
}
|
||||
|
||||
struct Win32_FILETIME
|
||||
{
|
||||
Win32_DWORD dwLowDateTime;
|
||||
Win32_DWORD dwHighDateTime;
|
||||
}
|
||||
|
||||
struct Win32_FILE_ATTRIBUTE_DATA
|
||||
{
|
||||
Win32_DWORD dwFileAttributes;
|
||||
Win32_FILETIME ftCreationTime;
|
||||
Win32_FILETIME ftLastAccessTime;
|
||||
Win32_FILETIME ftLastWriteTime;
|
||||
Win32_DWORD nFileSizeHigh;
|
||||
Win32_DWORD nFileSizeLow;
|
||||
}
|
||||
|
||||
extern fn bool win32_GetFileAttributesExW(Win32_LPCWSTR, Win32_GET_FILEEX_INFO_LEVELS, Win32_LPVOID) @extern("GetFileAttributesExW");
|
||||
extern fn bool win32_PathFileExistsW(Win32_LPCWSTR) @extern("PathFileExistsW");
|
||||
extern fn Win32_DWORD win32_GetTempPathW(Win32_DWORD nBufferLength, Win32_LPWSTR lpBuffer) @extern("GetTempPathW");
|
||||
|
||||
/*
|
||||
extern ulong _win32_GetCurrentDirectoryW(ulong, Char16* buffer) @extern("GetCurrentDirectoryW");
|
||||
extern bool _win32_CreateSymbolicLinkW(Char16* symlink_file, Char16* target_file, ulong flags) @extern("CreateSymbolicLinkW");
|
||||
|
||||
@@ -1,176 +1,186 @@
|
||||
module std::os::win32;
|
||||
|
||||
typedef Bool = int;
|
||||
typedef Boolean = Byte;
|
||||
typedef Byte = char;
|
||||
typedef CChar = cinterop::CChar;
|
||||
typedef Char = cinterop::CChar;
|
||||
typedef Colorref = DWord;
|
||||
typedef DWord = uint;
|
||||
typedef DWordlong = ulong;
|
||||
typedef DWordPtr = ULongPtr;
|
||||
typedef DWord32 = uint;
|
||||
typedef DWord64 = ulong;
|
||||
typedef Float = float;
|
||||
typedef HAccel = Handle;
|
||||
typedef HalfPtr = int;
|
||||
typedef Handle = PVoid;
|
||||
typedef HBitmap = Handle;
|
||||
typedef HBrush = Handle;
|
||||
typedef HColorspace = Handle;
|
||||
typedef HConv = Handle;
|
||||
typedef HConvlist = Handle;
|
||||
typedef HCursor = HIcon;
|
||||
typedef HDc = Handle;
|
||||
typedef HDDEdata = Handle;
|
||||
typedef HDesk = Handle;
|
||||
typedef HDrop = Handle;
|
||||
typedef HDwp = Handle;
|
||||
typedef HFile = int;
|
||||
typedef HFont = Handle;
|
||||
typedef HGdiobj = Handle;
|
||||
typedef HGlobal = Handle;
|
||||
typedef HHook = Handle;
|
||||
typedef HIcon = Handle;
|
||||
typedef HInstance = Handle;
|
||||
typedef HKey = Handle;
|
||||
typedef HKl = Handle;
|
||||
typedef HLocal = Handle;
|
||||
typedef HMenu = Handle;
|
||||
typedef HMetafile = Handle;
|
||||
typedef HModule = Handle;
|
||||
typedef HMonitor = Handle;
|
||||
typedef HPalette = Handle;
|
||||
typedef HPen = Handle;
|
||||
typedef HResult = Long;
|
||||
typedef HRgn = Handle;
|
||||
typedef HRsrc = Handle;
|
||||
typedef HSz = Handle;
|
||||
typedef HWinsta = Handle;
|
||||
typedef HWnd = Handle;
|
||||
typedef Int = int;
|
||||
typedef IntPtr = iptr;
|
||||
typedef Int8 = ichar;
|
||||
typedef Int16 = short;
|
||||
typedef Int32 = int;
|
||||
typedef Int64 = long;
|
||||
typedef Langid = Word;
|
||||
typedef Lcid = DWord;
|
||||
typedef Lctype = DWord;
|
||||
typedef Lgrpid = DWord;
|
||||
typedef Long = int;
|
||||
typedef Longlong = long;
|
||||
typedef LongPtr = iptr;
|
||||
typedef Long32 = int;
|
||||
typedef Long64 = long;
|
||||
typedef LParam = LongPtr;
|
||||
typedef LPBool = Bool*;
|
||||
typedef LPByte = Byte*;
|
||||
typedef LPColorref = DWord*;
|
||||
typedef LPCStr = CChar*;
|
||||
typedef LPCTstr = LPCWstr;
|
||||
typedef LPCVoid = void*;
|
||||
typedef LPCWstr = WChar*;
|
||||
typedef LPDWord = DWord*;
|
||||
typedef LPHandle = Handle*;
|
||||
typedef LPInt = int*;
|
||||
typedef LPLong = int*;
|
||||
typedef LPStr = CChar*;
|
||||
typedef LPTstr = LPWstr;
|
||||
typedef LPVoid = void*;
|
||||
typedef LPWord = Word*;
|
||||
typedef LPWstr = WChar*;
|
||||
typedef LResult = LongPtr;
|
||||
typedef PBool = Bool*;
|
||||
typedef PBoolean = Boolean*;
|
||||
typedef PByte = Byte*;
|
||||
typedef PChar = Char*;
|
||||
typedef PCstr = Char*;
|
||||
typedef PCTstr = LPCWstr;
|
||||
typedef PCUnicodeString = UnicodeString*;
|
||||
typedef PCWstr = Char16*;
|
||||
typedef PDWord = DWord*;
|
||||
typedef PDWordlong = DWordlong*;
|
||||
typedef PDWordPtr = DWordPtr*;
|
||||
typedef PDWord32 = DWord32*;
|
||||
typedef PDWord64 = DWord64*;
|
||||
typedef PFloat = Float*;
|
||||
typedef PHalfPtr = HalfPtr*;
|
||||
typedef PHandle = Handle*;
|
||||
typedef PHKey = HKey*;
|
||||
typedef PInt = int*;
|
||||
typedef PIntPtr = IntPtr*;
|
||||
typedef PInt8 = Int8*;
|
||||
typedef PInt16 = Int16*;
|
||||
typedef PInt32 = Int32*;
|
||||
typedef PInt64 = Int64*;
|
||||
typedef PLcid = PDWord;
|
||||
typedef PLong = Long*;
|
||||
typedef PLonglong = Longlong*;
|
||||
typedef PLongPtr = LongPtr*;
|
||||
typedef PLong32 = Long32*;
|
||||
typedef PLong64 = Long64*;
|
||||
typedef Pointer32 = uint;
|
||||
typedef Pointer64 = uptr;
|
||||
typedef PointerSigned = iptr;
|
||||
typedef PointerUnsigned = uptr;
|
||||
typedef PShort = Short*;
|
||||
typedef PSizeT = usz*;
|
||||
typedef PSSizeT = isz*;
|
||||
typedef PStr = Char*;
|
||||
typedef PTByte = TByte*;
|
||||
typedef PTChar = TChar*;
|
||||
typedef PTStr = LPWstr;
|
||||
typedef PUChar = UChar*;
|
||||
typedef PUHalfPtr = UHalfPtr*;
|
||||
typedef PUInt = UInt*;
|
||||
typedef PUIntPtr = UIntPtr*;
|
||||
typedef PUInt8 = UInt8*;
|
||||
typedef PUInt16 = UInt16*;
|
||||
typedef PUInt32 = UInt32*;
|
||||
typedef PUInt64 = UInt64*;
|
||||
typedef PULong = ULong*;
|
||||
typedef PULonglong = ULonglong*;
|
||||
typedef PULongPtr = ULongPtr*;
|
||||
typedef PULong32 = ULong32*;
|
||||
typedef PULong64 = ULong64*;
|
||||
typedef PUnicodeString = UnicodeString*;
|
||||
typedef PUShort = UShort*;
|
||||
typedef PVoid = void*;
|
||||
typedef PWChar = WChar*;
|
||||
typedef PWord = Word*;
|
||||
typedef PWStr = WChar*;
|
||||
typedef QWord = ulong;
|
||||
typedef ScHandle = Handle;
|
||||
typedef ScLong = LPVoid;
|
||||
typedef ServiceStatusHandle = Handle;
|
||||
typedef Short = short;
|
||||
typedef SizeT = usz;
|
||||
typedef SSizeT = isz;
|
||||
typedef TByte = WChar;
|
||||
typedef TChar = WChar;
|
||||
typedef UChar = char;
|
||||
typedef UHalfPtr = uint;
|
||||
typedef UInt = uint;
|
||||
typedef UIntPtr = uptr;
|
||||
typedef UInt8 = char;
|
||||
typedef UInt16 = ushort;
|
||||
typedef UInt32 = uint;
|
||||
typedef UInt64 = ulong;
|
||||
typedef ULong = uint;
|
||||
typedef ULonglong = ulong;
|
||||
typedef ULongPtr = ulong;
|
||||
typedef ULong32 = uint;
|
||||
typedef ULong64 = ulong;
|
||||
typedef UShort = ushort;
|
||||
typedef Usn = Longlong;
|
||||
typedef WChar = Char16;
|
||||
typedef Word = ushort;
|
||||
typedef WParam = UIntPtr;
|
||||
typedef Win32_BOOL = int;
|
||||
typedef Win32_BOOLEAN = Win32_BYTE;
|
||||
typedef Win32_BYTE = char;
|
||||
typedef Win32_CCHAR = cinterop::CChar;
|
||||
typedef Win32_CHAR = cinterop::CChar;
|
||||
typedef Win32_COLORREF = Win32_DWORD;
|
||||
typedef Win32_DWORD = uint;
|
||||
typedef Win32_DWORDLONG = ulong;
|
||||
typedef Win32_DWORD_PTR = Win32_ULONG_PTR;
|
||||
typedef Win32_DWORD32 = uint;
|
||||
typedef Win32_DWORD64 = ulong;
|
||||
typedef Win32_FLOAT = float;
|
||||
typedef Win32_HACCEL = Win32_HANDLE;
|
||||
typedef Win32_HALF_PTR = int;
|
||||
typedef Win32_HANDLE = Win32_PVOID;
|
||||
typedef Win32_HBITMAP = Win32_HANDLE;
|
||||
typedef Win32_HBRUSH = Win32_HANDLE;
|
||||
typedef Win32_HCOLORSPACE = Win32_HANDLE;
|
||||
typedef Win32_HCONV = Win32_HANDLE;
|
||||
typedef Win32_HCONVLIST = Win32_HANDLE;
|
||||
typedef Win32_HCURSOR = Win32_HICON;
|
||||
typedef Win32_HDC = Win32_HANDLE;
|
||||
typedef Win32_HDDEDATA = Win32_HANDLE;
|
||||
typedef Win32_HDESK = Win32_HANDLE;
|
||||
typedef Win32_HDROP = Win32_HANDLE;
|
||||
typedef Win32_HDWP = Win32_HANDLE;
|
||||
typedef Win32_HFILE = int;
|
||||
typedef Win32_HFONT = Win32_HANDLE;
|
||||
typedef Win32_HGDIOBJ = Win32_HANDLE;
|
||||
typedef Win32_HGLOBAL = Win32_HANDLE;
|
||||
typedef Win32_HHOOK = Win32_HANDLE;
|
||||
typedef Win32_HICON = Win32_HANDLE;
|
||||
typedef Win32_HINSTANCE = Win32_HANDLE;
|
||||
typedef Win32_HKEY = Win32_HANDLE;
|
||||
typedef Win32_HKL = Win32_HANDLE;
|
||||
typedef Win32_HLOCAL = Win32_HANDLE;
|
||||
typedef Win32_HMENU = Win32_HANDLE;
|
||||
typedef Win32_HMETAFILE = Win32_HANDLE;
|
||||
typedef Win32_HMODULE = Win32_HANDLE;
|
||||
typedef Win32_HMONITOR = Win32_HANDLE;
|
||||
typedef Win32_HPALETTE = Win32_HANDLE;
|
||||
typedef Win32_HPEN = Win32_HANDLE;
|
||||
typedef Win32_HRESULT = Win32_LONG;
|
||||
typedef Win32_HRGN = Win32_HANDLE;
|
||||
typedef Win32_HRSRC = Win32_HANDLE;
|
||||
typedef Win32_HSZ = Win32_HANDLE;
|
||||
typedef Win32_HWINSTA = Win32_HANDLE;
|
||||
typedef Win32_HWND = Win32_HANDLE;
|
||||
typedef Win32_INT = int;
|
||||
typedef Win32_INT_PTR = iptr;
|
||||
typedef Win32_INT8 = ichar;
|
||||
typedef Win32_INT16 = short;
|
||||
typedef Win32_INT32 = int;
|
||||
typedef Win32_INT64 = long;
|
||||
typedef Win32_LANGID = Win32_WORD;
|
||||
typedef Win32_LCID = Win32_DWORD;
|
||||
typedef Win32_LCTYPE = Win32_DWORD;
|
||||
typedef Win32_LGRPID = Win32_DWORD;
|
||||
typedef Win32_LONG = int;
|
||||
typedef Win32_LONGLONG = long;
|
||||
typedef Win32_LONG_PTR = iptr;
|
||||
typedef Win32_LONG32 = int;
|
||||
typedef Win32_LONG64 = long;
|
||||
typedef Win32_LPARAM = Win32_LONG_PTR;
|
||||
typedef Win32_LPBOOL = Win32_BOOL*;
|
||||
typedef Win32_LPBYTE = Win32_BYTE*;
|
||||
typedef Win32_LPCOLORREF = Win32_DWORD*;
|
||||
typedef Win32_LPCSTR = Win32_CCHAR*;
|
||||
typedef Win32_LPCTSTR = Win32_LPCWSTR;
|
||||
typedef Win32_LPCVOID = void*;
|
||||
typedef Win32_LPCWSTR = Win32_WCHAR*;
|
||||
typedef Win32_LPDWORD = Win32_DWORD*;
|
||||
typedef Win32_LPHANDLE = Win32_HANDLE*;
|
||||
typedef Win32_LPINT = int*;
|
||||
typedef Win32_LPLONG = int*;
|
||||
typedef Win32_LPSTR = Win32_CCHAR*;
|
||||
typedef Win32_LPTSTR = Win32_LPWSTR;
|
||||
typedef Win32_LPVOID = void*;
|
||||
typedef Win32_LPWORD = Win32_WORD*;
|
||||
typedef Win32_LPWSTR = Win32_WCHAR*;
|
||||
typedef Win32_LRESULT = Win32_LONG_PTR;
|
||||
typedef Win32_PBOOL = Win32_BOOL*;
|
||||
typedef Win32_PBOOLEAN = Win32_BOOLEAN*;
|
||||
typedef Win32_PBYTE = Win32_BYTE*;
|
||||
typedef Win32_PCHAR = Win32_CHAR*;
|
||||
typedef Win32_PCSTR = Win32_CHAR*;
|
||||
typedef Win32_PCTSTR = Win32_LPCWSTR;
|
||||
typedef Win32_PCUNICODE_STRING = Win32_UNICODE_STRING*;
|
||||
typedef Win32_PCWSTR = Char16*;
|
||||
typedef Win32_PDWORD = Win32_DWORD*;
|
||||
typedef Win32_PDWORDLONG = Win32_DWORDLONG*;
|
||||
typedef Win32_PDWORDPTR = Win32_DWORD_PTR*;
|
||||
typedef Win32_PDWORD32 = Win32_DWORD32*;
|
||||
typedef Win32_PDWORD64 = Win32_DWORD64*;
|
||||
typedef Win32_PFLOAT = Win32_FLOAT*;
|
||||
typedef Win32_PHALFPTR = Win32_HALF_PTR*;
|
||||
typedef Win32_PHANDLE = Win32_HANDLE*;
|
||||
typedef Win32_PHKEY = Win32_HKEY*;
|
||||
typedef Win32_PINT = int*;
|
||||
typedef Win32_PINTPTR = Win32_INT_PTR*;
|
||||
typedef Win32_PINT8 = Win32_INT8*;
|
||||
typedef Win32_PINT16 = Win32_INT16*;
|
||||
typedef Win32_PINT32 = Win32_INT32*;
|
||||
typedef Win32_PINT64 = Win32_INT64*;
|
||||
typedef Win32_PLCID = Win32_PDWORD;
|
||||
typedef Win32_PLONG = Win32_LONG*;
|
||||
typedef Win32_PLONGLONG = Win32_LONGLONG*;
|
||||
typedef Win32_PLONG_PTR = Win32_LONG_PTR*;
|
||||
typedef Win32_PLONG32 = Win32_LONG32*;
|
||||
typedef Win32_PLONG64 = Win32_LONG64*;
|
||||
typedef Win32_POINTER_32 = uint;
|
||||
typedef Win32_POINTER_64 = uptr;
|
||||
typedef Win32_POINTER_SIGNED = iptr;
|
||||
typedef Win32_POINTER_UNSIGNED = uptr;
|
||||
typedef Win32_PSHORT = Win32_SHORT*;
|
||||
typedef Win32_PSIZE_T = usz*;
|
||||
typedef Win32_PSSIZE_T = isz*;
|
||||
typedef Win32_PSTR = Win32_CHAR*;
|
||||
typedef Win32_PTBYTE = Win32_TBYTE*;
|
||||
typedef Win32_PTCHAR = Win32_TCHAR*;
|
||||
typedef Win32_PTSTR = Win32_LPWSTR;
|
||||
typedef Win32_PUCHAR = Win32_UCHAR*;
|
||||
typedef Win32_PUHALFPTR = Win32_UHALF_PTR*;
|
||||
typedef Win32_PUINT = Win32_UINT*;
|
||||
typedef Win32_PUINTPTR = Win32_UINT_PTR*;
|
||||
typedef Win32_PUINT8 = Win32_UINT8*;
|
||||
typedef Win32_PUINT16 = Win32_UINT16*;
|
||||
typedef Win32_PUINT32 = Win32_UINT32*;
|
||||
typedef Win32_PUINT64 = Win32_UINT64*;
|
||||
typedef Win32_PULONG = Win32_ULONG*;
|
||||
typedef Win32_PULONGLONG = Win32_ULONGLONG*;
|
||||
typedef Win32_PULONG_PTR = Win32_ULONG_PTR*;
|
||||
typedef Win32_PULONG32 = Win32_ULONG32*;
|
||||
typedef Win32_PULONG64 = Win32_ULONG64*;
|
||||
typedef Win32_PUNICODE_STRING = Win32_UNICODE_STRING*;
|
||||
typedef Win32_PUSHORT = Win32_USHORT*;
|
||||
typedef Win32_PVOID = void*;
|
||||
typedef Win32_PWCHAR = Win32_WCHAR*;
|
||||
typedef Win32_PWORD = Win32_WORD*;
|
||||
typedef Win32_PWSTR = Win32_WCHAR*;
|
||||
typedef Win32_QWORD = ulong;
|
||||
typedef Win32_SC_HANDLE = Win32_HANDLE;
|
||||
typedef Win32_SC_LOCK = Win32_LPVOID;
|
||||
typedef Win32_SERVICE_STATUS_HANDLE = Win32_HANDLE;
|
||||
typedef Win32_SHORT = short;
|
||||
typedef Win32_SIZE_T = usz;
|
||||
typedef Win32_SSIZE_T = isz;
|
||||
typedef Win32_TBYTE = Win32_WCHAR;
|
||||
typedef Win32_TCHAR = Win32_WCHAR;
|
||||
typedef Win32_UCHAR = char;
|
||||
typedef Win32_UHALF_PTR = uint;
|
||||
typedef Win32_UINT = uint;
|
||||
typedef Win32_UINT_PTR = uptr;
|
||||
typedef Win32_UINT8 = char;
|
||||
typedef Win32_UINT16 = ushort;
|
||||
typedef Win32_UINT32 = uint;
|
||||
typedef Win32_UINT64 = ulong;
|
||||
typedef Win32_ULONG = uint;
|
||||
typedef Win32_ULONGLONG = ulong;
|
||||
typedef Win32_ULONG_PTR = ulong;
|
||||
typedef Win32_ULONG32 = uint;
|
||||
typedef Win32_ULONG64 = ulong;
|
||||
typedef Win32_USHORT = ushort;
|
||||
typedef Win32_USN = Win32_LONGLONG;
|
||||
typedef Win32_WCHAR = Char16;
|
||||
typedef Win32_WORD = ushort;
|
||||
typedef Win32_WPARAM = Win32_UINT_PTR;
|
||||
|
||||
struct UnicodeString
|
||||
struct Win32_UNICODE_STRING
|
||||
{
|
||||
UShort length;
|
||||
UShort maximum_length;
|
||||
PWStr buffer;
|
||||
Win32_USHORT length;
|
||||
Win32_USHORT maximum_length;
|
||||
Win32_PWSTR buffer;
|
||||
}
|
||||
|
||||
union Win32_LARGE_INTEGER
|
||||
{
|
||||
struct
|
||||
{
|
||||
Win32_DWORD lowPart;
|
||||
Win32_LONG highPart;
|
||||
}
|
||||
ulong quadPart;
|
||||
}
|
||||
@@ -661,16 +661,8 @@ RETRY:
|
||||
case AST_BLOCK_EXIT_STMT:
|
||||
case AST_RETURN_STMT:
|
||||
MACRO_COPY_EXPR(ast->return_stmt.expr);
|
||||
if (ast->return_stmt.cleanup == ast->return_stmt.cleanup_fail)
|
||||
{
|
||||
MACRO_COPY_ASTID(ast->return_stmt.cleanup);
|
||||
ast->return_stmt.cleanup_fail = ast->return_stmt.cleanup;
|
||||
}
|
||||
else
|
||||
{
|
||||
MACRO_COPY_ASTID(ast->return_stmt.cleanup);
|
||||
MACRO_COPY_ASTID(ast->return_stmt.cleanup_fail);
|
||||
}
|
||||
MACRO_COPY_ASTID(ast->return_stmt.cleanup);
|
||||
MACRO_COPY_ASTID(ast->return_stmt.cleanup_fail);
|
||||
break;
|
||||
case AST_SWITCH_STMT:
|
||||
case AST_IF_CATCH_SWITCH_STMT:
|
||||
|
||||
@@ -147,6 +147,7 @@ static void linker_setup_windows(const char ***args_ref, LinkerType linker_type)
|
||||
add_arg("ntdll.lib");
|
||||
add_arg("user32.lib");
|
||||
add_arg("shell32.lib");
|
||||
add_arg("Shlwapi.lib");
|
||||
add_arg("legacy_stdio_definitions.lib");
|
||||
|
||||
if (active_target.win.crt_linking == WIN_CRT_STATIC)
|
||||
|
||||
@@ -1426,7 +1426,6 @@ void llvm_emit_panic_on_true(GenContext *c, LLVMValueRef value, const char *pani
|
||||
void llvm_emit_stmt(GenContext *c, Ast *ast)
|
||||
{
|
||||
if (ast->ast_kind != AST_COMPOUND_STMT) EMIT_LOC(c, ast);
|
||||
assert(!c->catch_block && "Did not expect a catch block here.");
|
||||
switch (ast->ast_kind)
|
||||
{
|
||||
case AST_POISONED:
|
||||
|
||||
@@ -331,11 +331,15 @@ static inline bool sema_defer_by_result(AstId defer_top, AstId defer_bottom)
|
||||
|
||||
static inline void sema_inline_return_defers(SemaContext *context, Ast *stmt, AstId defer_top, AstId defer_bottom)
|
||||
{
|
||||
stmt->return_stmt.cleanup_fail = stmt->return_stmt.cleanup = context_get_defers(context, defer_top, defer_bottom, true);
|
||||
stmt->return_stmt.cleanup = context_get_defers(context, defer_top, defer_bottom, true);
|
||||
if (stmt->return_stmt.expr && IS_OPTIONAL(stmt->return_stmt.expr) && sema_defer_by_result(context->active_scope.defer_last, context->block_return_defer))
|
||||
{
|
||||
stmt->return_stmt.cleanup_fail = context_get_defers(context, context->active_scope.defer_last, context->block_return_defer, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
stmt->return_stmt.cleanup_fail = stmt->return_stmt.cleanup ? astid(copy_ast_defer(astptr(stmt->return_stmt.cleanup))) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,12 +491,6 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement
|
||||
if (!first) goto SKIP_ENSURE;
|
||||
if (statement->return_stmt.cleanup)
|
||||
{
|
||||
// If we have the same ast on cleanup / cleanup-fail we need to separate them.
|
||||
if (type_is_optional(expected_rtype) &&
|
||||
statement->return_stmt.cleanup == statement->return_stmt.cleanup_fail)
|
||||
{
|
||||
statement->return_stmt.cleanup_fail = astid(copy_ast_defer(astptr(statement->return_stmt.cleanup)));
|
||||
}
|
||||
Ast *last = ast_last(astptr(statement->return_stmt.cleanup));
|
||||
last->next = first;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define COMPILER_VERSION "0.4.103"
|
||||
#define COMPILER_VERSION "0.4.104"
|
||||
@@ -12,6 +12,43 @@ fn void test_starts_with()
|
||||
assert(!s.starts_with("o"));
|
||||
}
|
||||
|
||||
fn void test_strip()
|
||||
{
|
||||
String s = "ofke";
|
||||
assert(s.strip("of") == "ke");
|
||||
assert(s.strip("ofke") == "");
|
||||
assert(s.strip("ofkes") == "ofke");
|
||||
assert(s.strip("ofkf") == "ofke");
|
||||
assert(s.strip("") == "ofke");
|
||||
s = "";
|
||||
assert(s.strip("") == "");
|
||||
assert(s.strip("o") == "");
|
||||
}
|
||||
|
||||
fn void test_strip_end()
|
||||
{
|
||||
String s = "ofke";
|
||||
assert(s.strip_end("ke") == "of");
|
||||
assert(s.strip_end("ofke") == "");
|
||||
assert(s.strip_end("ofkes") == "ofke");
|
||||
assert(s.strip_end("ofkf") == "ofke");
|
||||
assert(s.strip_end("") == "ofke");
|
||||
s = "";
|
||||
assert(s.strip_end("") == "");
|
||||
assert(s.strip_end("o") == "");
|
||||
}
|
||||
|
||||
fn void test_ends_with()
|
||||
{
|
||||
String s = "ofke";
|
||||
assert(s.ends_with("ke"));
|
||||
assert(s.ends_with("ofke"));
|
||||
assert(!s.ends_with("ofkes"));
|
||||
assert(!s.ends_with("ofkf"));
|
||||
s = "";
|
||||
assert(s.ends_with(""));
|
||||
assert(!s.ends_with("e"));
|
||||
}
|
||||
fn void test_trim()
|
||||
{
|
||||
String s = " \t\nabc ";
|
||||
|
||||
14
test/unit/stdlib/io/path.c3
Normal file
14
test/unit/stdlib/io/path.c3
Normal file
@@ -0,0 +1,14 @@
|
||||
module std::io::path @test;
|
||||
|
||||
|
||||
fn void! test_parent()
|
||||
{
|
||||
Path p = path::new("")?;
|
||||
assert(catch(p.parent()));
|
||||
p = path::new("/")?;
|
||||
assert(catch(p.parent()));
|
||||
p = path::new("/a/b/c")?;
|
||||
assert(path::new("/a/b")? == p.parent()?);
|
||||
p = path::new("/a/b/c/")?;
|
||||
assert(path::new("/a/b")? == p.parent()?);
|
||||
}
|
||||
Reference in New Issue
Block a user