$exec may now provide a stdin parameter. Deprecated path.append, path.tappend, getcwd, tgetcwd, path.absolute, ls. Deprecated env::get_config_dir, replaced by env::new_get_config_dir. Added path.has_extension, path.new_append, path.temp_append, new_cwd, temp_cwd, path.new_absolute, new_ls, temp_ls. Added dstring.replace Updated win escapes for exec.

This commit is contained in:
Christoffer Lerno
2024-08-14 00:57:25 +02:00
parent 6bc486400c
commit 3ccb4b9ec3
18 changed files with 309 additions and 145 deletions

View File

@@ -558,15 +558,6 @@ jobs:
cd resources/testfragments
../../build/c3c compile --reloc=none --target wasm32 -g0 --link-libc=no --no-entry -Os wasm4.c3
- name: Install QEMU and Risc-V toolchain
run: |
sudo apt-get install opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf
- name: Compile and run Baremetal Risc-V Example
run: |
cd resources/examples/embedded/riscv-qemu
make C3C_PATH=../../../../build/ run
- name: Build testproject direct linker
run: |
cd resources/testproject

View File

@@ -7,26 +7,17 @@ import libc, std::hash, std::io, std::os::backtrace;
/**
* Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
**/
fault IteratorResult
{
NO_MORE_ELEMENT
}
fault IteratorResult { NO_MORE_ELEMENT }
/**
* Use `SearchResult` when trying to return a value from some collection but the element is missing.
**/
fault SearchResult
{
MISSING
}
fault SearchResult { MISSING }
/**
* Use `CastResult` when an attempt at conversion fails.
**/
fault CastResult
{
TYPE_MISMATCH
}
fault CastResult { TYPE_MISMATCH }
/**
* Stores a variable on the stack, then restores it at the end of the

View File

@@ -48,6 +48,56 @@ fn DString new(String c = "", Allocator allocator = allocator::heap())
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
fn void DString.replace_char(self, char ch, char replacement)
{
StringData* data = self.data();
foreach (&c : data.chars[:data.len])
{
if (*c == ch) *c = replacement;
}
}
fn void DString.replace(&self, String needle, String replacement)
{
StringData* data = self.data();
usz needle_len = needle.len;
if (!data || data.len < needle_len) return;
usz replace_len = replacement.len;
if (needle_len == 1 && replace_len == 1)
{
self.replace_char(needle[0], replacement[0]);
return;
}
@pool(data.allocator) {
String str = self.tcopy_str();
self.clear();
usz len = str.len;
usz match = 0;
foreach (i, c : str)
{
if (c == needle[match])
{
match++;
if (match == needle_len)
{
self.append_chars(replacement);
match = 0;
continue;
}
continue;
}
if (match > 0)
{
self.append_chars(str[i - match:match]);
match = 0;
}
self.append_char(c);
}
if (match > 0) self.append_chars(str[^match:match]);
};
}
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
{
DString string;

View File

@@ -16,7 +16,7 @@ fn void! native_rmtree(Path dir)
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!;
Path new_path = dir.temp_append(name)!;
if (entry.d_type == posix::DT_DIR)
{
native_rmtree(new_path)!;

View File

@@ -15,7 +15,9 @@ fault PathResult
NO_PARENT,
}
struct Path (Printable)
def Path = PathImp;
struct PathImp (Printable)
{
String path_string;
PathEnv env;
@@ -27,7 +29,15 @@ enum PathEnv
POSIX
}
fn Path! getcwd(Allocator allocator = allocator::heap())
fn Path! new_cwd(Allocator allocator = allocator::heap())
{
@pool(allocator)
{
return new(os::getcwd(allocator::temp()), allocator);
};
}
fn Path! getcwd(Allocator allocator = allocator::heap()) @deprecated("Use new_cwd()")
{
@pool(allocator)
{
@@ -39,7 +49,8 @@ fn bool is_dir(Path path) => os::native_is_dir(path.str_view());
fn bool is_file(Path path) => os::native_is_file(path.str_view());
fn usz! file_size(Path path) => os::native_file_size(path.str_view());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path! tgetcwd() => getcwd(allocator::temp()) @inline;
fn Path! temp_cwd() => new_cwd(allocator::temp()) @inline;
fn Path! tgetcwd() @deprecated("Use temp_cwd()") => new_cwd(allocator::temp()) @inline;
fn void! chdir(Path path) => os::native_chdir(path) @inline;
fn Path! temp_directory(Allocator allocator = allocator::heap()) => os::native_temp_directory(allocator);
fn void! delete(Path path) => os::native_remove(path.str_view()) @inline;
@@ -59,7 +70,17 @@ macro bool is_win32_separator(char c)
return c == '/' || c == '\\';
}
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap()) @deprecated("use new_ls")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator);
}
fn PathList! temp_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator::temp()) @inline;
}
fn PathList! new_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
{
$if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
@@ -139,12 +160,17 @@ fn bool Path.equals(self, Path p2)
return self.env == p2.env && self.path_string == p2.path_string;
}
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap()) @deprecated("Use path.new_append(...)")
{
return self.new_append(filename, allocator) @inline;
}
/**
* Append the string to the current path.
*
* @param [in] filename
**/
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap())
fn Path! Path.new_append(self, String filename, Allocator allocator = allocator::heap())
{
if (!self.path_string.len) return new(filename, allocator, self.env)!;
assert(!is_separator(self.path_string[^1], self.env));
@@ -159,7 +185,9 @@ fn Path! Path.append(self, String filename, Allocator allocator = allocator::hea
};
}
fn Path! Path.tappend(self, String filename) => self.append(filename, allocator::temp());
fn Path! Path.temp_append(self, String filename) => self.new_append(filename, allocator::temp());
fn Path! Path.tappend(self, String filename) @deprecated("Use path.temp_append(...)") => self.new_append(filename, allocator::temp());
fn usz Path.start_of_base_name(self) @local
{
@@ -193,10 +221,15 @@ fn bool! Path.is_absolute(self)
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
}
fn Path! Path.absolute(self, Allocator allocator = allocator::heap()) @deprecated("Use path.new_absolute()")
{
return self.new_absolute(allocator) @inline;
}
/**
* @require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
**/
fn Path! Path.absolute(self, Allocator allocator = allocator::heap())
fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
{
String path_str = self.str_view();
if (!path_str.len) return PathResult.INVALID_PATH?;
@@ -220,7 +253,7 @@ fn Path! Path.absolute(self, Allocator allocator = allocator::heap())
};
$else
String cwd = os::getcwd(allocator::temp())!;
return Path { cwd, self.env }.append(path_str, allocator)!;
return Path { cwd, self.env }.new_append(path_str, allocator)!;
$endif
}
@@ -250,6 +283,22 @@ fn String Path.dirname(self)
return path_str[:basename_start - 1];
}
/**
* Test if the path has the given extension, so given the path /foo/bar.c3
* this would be true matching the extension "c3"
*
* @param [in] extension `The extension name (not including the leading '.')`
* @require extension.len > 0 : `The extension cannot be empty`
* @return `true if the extension matches`
**/
fn bool Path.has_extension(self, String extension)
{
String basename = self.basename();
if (basename.len <= extension.len) return false;
if (basename[^extension.len + 1] != '.') return false;
return basename[^extension.len..] == extension;
}
fn String! Path.extension(self)
{
String basename = self.basename();
@@ -470,12 +519,12 @@ fn bool! Path.walk(self, PathWalker w, void* data)
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator allocator)
{
Path abs = self.absolute(allocator)!;
PathList files = ls(abs, .allocator = allocator)!;
Path abs = self.new_absolute(allocator)!;
PathList files = new_ls(abs, .allocator = allocator)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.append(f.str_view(), allocator)!;
f = abs.new_append(f.str_view(), allocator)!;
bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true;

View File

@@ -83,10 +83,15 @@ fn String! get_home_dir(Allocator using = allocator::heap())
return get_var(home, using);
}
fn Path! get_config_dir(Allocator allocator = allocator::heap()) @deprecated("use new_get_config_dir()")
{
return new_get_config_dir(allocator) @inline;
}
/**
* Returns the current user's config directory.
**/
fn Path! get_config_dir(Allocator allocator = allocator::heap())
fn Path! new_get_config_dir(Allocator allocator = allocator::heap())
{
@pool(allocator)
{
@@ -100,7 +105,7 @@ fn Path! get_config_dir(Allocator allocator = allocator::heap())
String s = get_var_temp("XDG_CONFIG_HOME") ?? get_var_temp("HOME")!;
const DIR = ".config";
$endif
return path::temp_new(s).append(DIR, .allocator = allocator);
return path::temp_new(s).new_append(DIR, .allocator = allocator);
$endif
};
}
@@ -126,11 +131,16 @@ fn bool clear_var(String name)
};
}
fn String! executable_path(Allocator allocator = allocator::heap())
fn String! executable_path(Allocator allocator = allocator::heap()) @deprecated("use new_executable_path()")
{
return new_executable_path(allocator) @inline;
}
fn String! new_executable_path(Allocator allocator = allocator::heap())
{
$if env::DARWIN:
return darwin::executable_path(allocator);
$else
return "<Unsupported>";
return SearchResult.MISSING?;
$endif
}

View File

@@ -80,7 +80,7 @@ fn uptr! load_address() @local
{
Darwin_segment_command_64* cmd = darwin::getsegbyname("__TEXT");
if (!cmd) return BacktraceFault.SEGMENT_NOT_FOUND?;
String path = env::executable_path(allocator::temp()) ?? BacktraceFault.EXECUTABLE_PATH_NOT_FOUND?!;
String path = env::new_executable_path(allocator::temp()) ?? BacktraceFault.EXECUTABLE_PATH_NOT_FOUND?!;
uint dyld_count = darwin::_dyld_image_count();
for (uint i = 0; i < dyld_count; i++)
{

View File

@@ -32,6 +32,7 @@
- Pointers are rendered with "0x" prefix when passed to '%s'.
- Add temp allocator scribble.
- Use PIC by default on Linux.
- `$exec` may now provide a stdin parameter.
### Fixes
@@ -74,6 +75,10 @@
- `send` and `recv` added to `libc` for Posix / Win32.
- Add support to destroy temp allocators.
- Deprecated `path.append`, `path.tappend`, `getcwd`, `tgetcwd`, `path.absolute`, `ls`.
- Deprecated `env::get_config_dir`, replaced by `env::new_get_config_dir`.
- Added `path.has_extension`, `path.new_append`, `path.temp_append`, `new_cwd`, `temp_cwd`, `path.new_absolute`, `new_ls`, `temp_ls`.
- Added `dstring.replace`
## 0.6.1 Change list

View File

@@ -295,12 +295,12 @@ void resolve_libraries(BuildTarget *build_target)
FOREACH(const char *, exec, library->execs)
{
printf("] Execute '%s' for library '%s':", exec, library->provides);
puts(execute_cmd(exec, false));
puts(execute_cmd(exec, false, NULL));
}
FOREACH(const char *, exec, target->execs)
{
printf("] Execute '%s' for library '%s':", exec, library->provides);
puts(execute_cmd(exec, false));
puts(execute_cmd(exec, false, NULL));
}
}
}

View File

@@ -1058,12 +1058,12 @@ static void execute_scripts(void)
if (call.len < 3 || call.ptr[call.len - 3] != '.' || call.ptr[call.len - 2] != 'c' ||
call.ptr[call.len - 2] != '3')
{
(void) execute_cmd(exec, false);
(void) execute_cmd(exec, false, NULL);
continue;
}
scratch_buffer_clear();
scratch_buffer_append_len(call.ptr, call.len);
(void) compile_and_invoke(scratch_buffer_to_string(), execs.len ? execs.ptr : "");
(void) compile_and_invoke(scratch_buffer_to_string(), execs.len ? execs.ptr : "", NULL);
}
dir_change(old_path);
free(old_path);
@@ -1247,7 +1247,31 @@ const char *scratch_buffer_interned_as(TokenType* type)
fnv1a(scratch_buffer.str, scratch_buffer.len), type);
}
File *compile_and_invoke(const char *file, const char *args)
void scratch_buffer_append_native_safe_path(const char *data, int len)
{
#if PLATFORM_WINDOWS
scratch_buffer_append("\"");
for (int i = 0; i < len; i++)
{
char c = data[i];
switch (c)
{
case '/':
case '\\':
scratch_buffer_append("\\");
break;
default:
scratch_buffer_append_char(c);
break;
}
}
scratch_buffer_append("\"");
#else
scratch_buffer_append_len(data, len);
#endif
}
File *compile_and_invoke(const char *file, const char *args, const char *stdin_data)
{
char *name;
if (!file_namesplit(compiler_exe_name, &name, NULL))
@@ -1255,25 +1279,23 @@ File *compile_and_invoke(const char *file, const char *args)
error_exit("Failed to extract file name from '%s'", compiler_exe_name);
}
const char *compiler_path = file_append_path(find_executable_path(), name);
scratch_buffer_clear();
scratch_buffer_append(compiler_path);
#if (_MSC_VER)
const char *output = "__c3exec__.exe";
#else
if (PLATFORM_WINDOWS) scratch_buffer_append_char('"');
scratch_buffer_append_native_safe_path(compiler_path, strlen(compiler_path));
const char *output = "__c3exec__";
#endif
scratch_buffer_append(" compile -g0 --single-module=yes");
StringSlice slice = slice_from_string(file);
while (slice.len > 0)
{
StringSlice file_name = slice_next_token(&slice, ';');
if (!file_name.len) continue;
scratch_buffer_append_char(' ');
scratch_buffer_append_len(file_name.ptr, file_name.len);
scratch_buffer_append(" ");
scratch_buffer_append_native_safe_path(file_name.ptr, file_name.len);
}
scratch_buffer_printf(" -o %s", output);
const char *out;
if (!execute_cmd_failable(scratch_buffer_to_string(), &out))
if (PLATFORM_WINDOWS) scratch_buffer_append_char('"');
if (!execute_cmd_failable(scratch_buffer_to_string(), &out, NULL))
{
error_exit("Failed to compile script '%s'.", file);
}
@@ -1285,7 +1307,7 @@ File *compile_and_invoke(const char *file, const char *args)
scratch_buffer_append(output);
scratch_buffer_append(" ");
scratch_buffer_append(args);
if (!execute_cmd_failable(scratch_buffer_to_string(), &out))
if (!execute_cmd_failable(scratch_buffer_to_string(), &out, stdin_data))
{
error_exit("Error invoking script '%s' with arguments %s.", file, args);
}

View File

@@ -348,6 +348,7 @@ typedef struct
{
Expr *filename;
Expr **args;
Expr *stdin_string;
} ExecDecl;
typedef struct
@@ -2251,7 +2252,7 @@ File *source_file_load(const char *filename, bool *already_loaded, const char **
File *source_file_generate(const char *filename);
File *source_file_text_load(const char *filename, const char *content);
File *compile_and_invoke(const char *file, const char *args);
File *compile_and_invoke(const char *file, const char *args, const char *stdin_data);
void compiler_parse(void);
void emit_json(void);

View File

@@ -2649,12 +2649,22 @@ static Decl *parse_exec(ParseContext *c)
advance_and_verify(c, TOKEN_CT_EXEC);
CONSUME_OR_RET(TOKEN_LPAREN, poisoned_decl);
ASSIGN_EXPR_OR_RET(decl->exec_decl.filename, parse_constant_expr(c), poisoned_decl);
// We might just have `$exec("foo")`
if (try_consume(c, TOKEN_RPAREN)) goto END;
// Get the `,`
CONSUME_OR_RET(TOKEN_COMMA, poisoned_decl);
CONSUME_OR_RET(TOKEN_LBRACE, poisoned_decl);
while (try_consume(c, TOKEN_COMMA))
{
ASSIGN_EXPR_OR_RET(Expr *expr, parse_constant_expr(c), poisoned_decl);
vec_add(decl->exec_decl.args, expr);
}
CONSUME_OR_RET(TOKEN_RBRACE, poisoned_decl);
if (try_consume(c, TOKEN_RPAREN)) goto END;
CONSUME_OR_RET(TOKEN_COMMA, poisoned_decl);
ASSIGN_EXPR_OR_RET(decl->exec_decl.stdin_string, parse_constant_expr(c), poisoned_decl);
CONSUME_OR_RET(TOKEN_RPAREN, poisoned_decl);
END:
if (!parse_attributes_for_global(c, decl)) return poisoned_decl;
CONSUME_EOS_OR_RET(poisoned_decl);
return decl;

View File

@@ -184,6 +184,46 @@ static Decl **sema_load_include(CompilationUnit *unit, Decl *decl)
return parse_include_file(file, unit);
}
static bool exec_arg_append_to_scratch(Expr *arg)
{
assert(expr_is_const(arg));
switch (arg->const_expr.const_kind)
{
case CONST_FLOAT:
scratch_buffer_append_double(arg->const_expr.fxx.f);
return true;
case CONST_INTEGER:
scratch_buffer_append(int_to_str(arg->const_expr.ixx, 10, false));
return true;
case CONST_BOOL:
scratch_buffer_append(arg->const_expr.b ? "true" : "false");
return true;
case CONST_ENUM:
case CONST_ERR:
scratch_buffer_append(arg->const_expr.enum_err_val->name);
return true;
case CONST_TYPEID:
if (!arg->const_expr.typeid->name)
{
RETURN_PRINT_ERROR_AT(false, arg, "The type '%s' has no trivial name.",
type_quoted_error_string(arg->const_expr.typeid));
}
scratch_buffer_append(arg->const_expr.typeid->name);
return true;
case CONST_STRING:
scratch_buffer_append(arg->const_expr.bytes.ptr);
return true;
case CONST_POINTER:
scratch_buffer_append_unsigned_int(arg->const_expr.ptr);
return true;
case CONST_BYTES:
case CONST_INITIALIZER:
case CONST_UNTYPED_LIST:
case CONST_MEMBER:
return false;
}
UNREACHABLE
}
static Decl **sema_run_exec(CompilationUnit *unit, Decl *decl)
{
@@ -203,12 +243,20 @@ static Decl **sema_run_exec(CompilationUnit *unit, Decl *decl)
Expr *filename = decl->exec_decl.filename;
bool success = sema_analyse_ct_expr(&context, filename);
FOREACH(Expr *, arg, decl->exec_decl.args) success &= sema_analyse_ct_expr(&context, arg);
Expr *stdin_expr = decl->exec_decl.stdin_string;
if (stdin_expr) success &= sema_analyse_ct_expr(&context, stdin_expr);
sema_context_destroy(&context);
if (!success) return NULL;
if (!expr_is_const_string(filename))
{
RETURN_PRINT_ERROR_AT(NULL, filename, "A filename was expected as the first argument to '$exec'.");
}
const char *stdin_string = NULL;
if (stdin_expr)
{
if (!expr_is_const_string(stdin_expr)) RETURN_PRINT_ERROR_AT(NULL, stdin_expr, "Expected the stdin parameter to be a compile time string.");
stdin_string = stdin_expr->const_expr.bytes.ptr;
}
scratch_buffer_clear();
const char *file_str = filename->const_expr.bytes.ptr;
bool c3_script = str_has_suffix(file_str, ".c3");
@@ -221,43 +269,10 @@ static Decl **sema_run_exec(CompilationUnit *unit, Decl *decl)
{
if (i) scratch_buffer_append(" ");
assert(expr_is_const(arg));
switch (arg->const_expr.const_kind)
if (!exec_arg_append_to_scratch(arg))
{
case CONST_FLOAT:
scratch_buffer_append_double(arg->const_expr.fxx.f);
continue;
case CONST_INTEGER:
scratch_buffer_append(int_to_str(arg->const_expr.ixx, 10, false));
continue;
case CONST_BOOL:
scratch_buffer_append(arg->const_expr.b ? "true" : "false");
continue;
case CONST_ENUM:
case CONST_ERR:
scratch_buffer_append(arg->const_expr.enum_err_val->name);
continue;
case CONST_TYPEID:
if (!arg->const_expr.typeid->name)
{
RETURN_PRINT_ERROR_AT(NULL, arg, "The type '%s' has no trivial name.",
type_quoted_error_string(arg->const_expr.typeid));
RETURN_PRINT_ERROR_AT(NULL, arg, "Bytes, initializers and member references may not be used as arguments.");
}
scratch_buffer_append(arg->const_expr.typeid->name);
continue;
case CONST_STRING:
scratch_buffer_append(arg->const_expr.bytes.ptr);
continue;
case CONST_POINTER:
scratch_buffer_append_unsigned_int(arg->const_expr.ptr);
continue;
case CONST_BYTES:
case CONST_INITIALIZER:
case CONST_UNTYPED_LIST:
case CONST_MEMBER:
RETURN_PRINT_ERROR_AT(NULL, arg,
"Bytes, initializers and member references may not be used as arguments.");
}
UNREACHABLE
}
File *file;
// TODO fix Win32
@@ -273,11 +288,11 @@ static Decl **sema_run_exec(CompilationUnit *unit, Decl *decl)
}
if (c3_script)
{
file = compile_and_invoke(file_str, scratch_buffer_copy());
file = compile_and_invoke(file_str, scratch_buffer_copy(), stdin_string);
}
else
{
const char *output = execute_cmd(scratch_buffer_to_string(), false);
const char *output = execute_cmd(scratch_buffer_to_string(), false, stdin_string);
file = source_file_text_load(scratch_buffer_to_string(), output);
}
if (old_path)

View File

@@ -505,7 +505,7 @@ void file_delete_all_files_in_dir_with_suffix(const char *path, const char *suff
#else
const char *cmd = "rm -f %s/*%s";
#endif
execute_cmd(str_printf(cmd, path, suffix), true);
execute_cmd(str_printf(cmd, path, suffix), true, NULL);
}
#if (_MSC_VER)
@@ -591,64 +591,41 @@ void file_add_wildcard_files(const char ***files, const char *path, bool recursi
#endif
#define BUFSIZE 1024
const char *execute_cmd(const char *cmd, bool ignore_failure)
const char *execute_cmd(const char *cmd, bool ignore_failure, const char *stdin_string)
{
char buffer[BUFSIZE];
char *output = "";
FILE *process = NULL;
#if (_MSC_VER)
if (!(process = _wpopen(win_utf8to16(cmd), L"r")))
{
if (ignore_failure) return "";
error_exit("Failed to open a pipe for command '%s'.", cmd);
}
#else
if (!(process = popen(cmd, "r")))
{
if (ignore_failure) return "";
error_exit("Failed to open a pipe for command '%s'.", cmd);
}
#endif
while (fgets(buffer, BUFSIZE - 1, process))
{
output = str_cat(output, buffer);
}
#if PLATFORM_WINDOWS
int err = _pclose(process);
#else
int err = pclose(process);
#endif
if (err)
const char *result = NULL;
bool success = execute_cmd_failable(cmd, &result, stdin_string);
if (!success)
{
if (ignore_failure) return "";
error_exit("Failed to execute '%s'.", cmd);
}
while (output[0] != 0)
{
switch (output[0])
{
case ' ':
case '\t':
case '\n':
case '\r':
output++;
continue;
default:
break;
}
break;
}
return str_trim(output);
return result;
}
bool execute_cmd_failable(const char *cmd, const char **result)
bool execute_cmd_failable(const char *cmd, const char **result, const char *stdin_string)
{
char buffer[BUFSIZE];
char *output = "";
FILE *process = NULL;
FILE *stdin_file = NULL;
if (stdin_string)
{
cmd = strdup(cmd);
scratch_buffer_clear();
#if (_MSC_VER)
if (!(process = _wpopen(win_utf8to16(cmd), L"r"))) return false;
scratch_buffer_printf("%s < __c3temp.bin", cmd);
#else
scratch_buffer_printf("cat __c3temp.bin | %s", cmd);
#endif
free((char*)cmd);
cmd = scratch_buffer_to_string();
FILE *f = fopen("__c3temp.bin", "w");
fputs(stdin_string, f);
fclose(f);
}
#if (_MSC_VER)
if (!(process = _wpopen(win_utf8to16(cmd), L"rb"))) return false;
#else
if (!(process = popen(cmd, "r"))) return false;
#endif
@@ -661,6 +638,10 @@ bool execute_cmd_failable(const char *cmd, const char **result)
#else
int err = pclose(process);
#endif
if (stdin_string)
{
file_delete_file("__c3temp.bin");
}
if (err) return false;
while (output[0] != 0)

View File

@@ -39,7 +39,7 @@ static char *find_visual_studio(void)
const char *install_path = NULL;
// Call vswhere.exe
if (!execute_cmd_failable(scratch_buffer_to_string(), &install_path))
if (!execute_cmd_failable(scratch_buffer_to_string(), &install_path, NULL))
{
error_exit("Failed to find vswhere.exe to detect MSVC.");
}

View File

@@ -87,8 +87,9 @@ bool file_has_suffix_in_list(const char *file_name, int name_len, const char **s
void file_add_wildcard_files(const char ***files, const char *path, bool recursive, const char **suffix_list, int suffix_count);
const char *file_append_path(const char *path, const char *name);
const char *execute_cmd(const char *cmd, bool ignore_failure);
bool execute_cmd_failable(const char *cmd, const char **result);
const char *execute_cmd(const char *cmd, bool ignore_failure, const char *stdin_string);
bool execute_cmd_failable(const char *cmd, const char **result, const char *stdin_string);
void *cmalloc(size_t size);
void *ccalloc(size_t size, size_t elements);
void memory_init(size_t max_mem);

View File

@@ -2,6 +2,14 @@ module std::core::dstring2 @test;
const TEST_STRING = "hello world";
fn void test_replace()
{
DString hello = dstring::new("Hello world where are you? Are you here too?");
defer hello.free();
hello.replace("u", "ooo");
assert(hello.str_view() == "Hello world where are yoooo? Are yoooo here too?");
}
fn void test_delete()
{
{

View File

@@ -5,8 +5,8 @@ fn void! test_dot()
Path p = path::new(".")!;
assert(@catch(p.parent()));
// It must be possible to form the absolute version.
Path p2 = p.absolute()!;
p2 = p.append("/hello/world")!;
Path p2 = p.new_absolute()!;
p2 = p.new_append("/hello/world")!;
if (p2.env == POSIX)
{
assert(p2.str_view() == "hello/world");
@@ -239,6 +239,36 @@ fn void! test_extension()
}
fn void! test_has_extension()
{
assert(!path::new(`C:\temp\foo.bar\README`, .path_env = PathEnv.WIN32)!.has_extension(`bar\README`));
assert(path::new_windows("file.txt")!.has_extension("txt"));
assert(path::new_posix("file.txt")!.has_extension("txt"));
assert(path::new_windows("a/b/file.txt")!.has_extension("txt"));
assert(path::new_posix("a/b/file.txt")!.has_extension("txt"));
assert(path::new_windows("a\\b\\file.txt")!.has_extension("txt"));
assert(path::new_windows("a.b/file.txt")!.has_extension("txt"));
assert(path::new_posix("a.b/file.txt")!.has_extension("txt"));
assert(path::new_windows("a.b/file.txt")!.has_extension("txt"));
assert(path::new_posix("a.b/file.txt")!.has_extension("txt"));
assert(path::new_windows("a.b\\file.txt")!.has_extension("txt"));
assert(path::new_windows("domain.dot.com")!.has_extension("com"));
assert(path::new_posix("domain.dot.com")!.has_extension("com"));
assert(path::new_windows("image.jpeg")!.has_extension("jpeg"));
assert(path::new_posix("image.jpeg")!.has_extension("jpeg"));
assert(path::new_windows("../filename.ext")!.has_extension("ext"));
assert(path::new_posix("../filename.ext")!.has_extension("ext"));
}
fn void! test_basename()
{
assert(path::new_windows("file.txt").basename()! == "file.txt");
@@ -335,7 +365,7 @@ fn void! test_path_absolute()
$if env::WIN32:
assert(path::new_windows(`C:\abs`).absolute()!.str_view() == `C:\abs`);
$else
assert(path::new_posix("/").absolute()!.str_view() == "/");
assert(path::new_posix(".").absolute()!.str_view() == path::getcwd(allocator::temp())!!.str_view());
assert(path::new_posix("/").new_absolute()!.str_view() == "/");
assert(path::new_posix(".").new_absolute()!.str_view() == path::temp_cwd()!!.str_view());
$endif
}