From 3ccb4b9ec3505b6ce00c7ad20c13e4919234c62c Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 14 Aug 2024 00:57:25 +0200 Subject: [PATCH] `$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. --- .github/workflows/main.yml | 9 ---- lib/std/core/builtin.c3 | 15 ++---- lib/std/core/dstring.c3 | 50 ++++++++++++++++++ lib/std/io/os/rmtree.c3 | 2 +- lib/std/io/path.c3 | 71 +++++++++++++++++++++---- lib/std/os/env.c3 | 18 +++++-- lib/std/os/macos/darwin.c3 | 2 +- releasenotes.md | 5 ++ src/build/libraries.c | 4 +- src/compiler/compiler.c | 48 ++++++++++++----- src/compiler/compiler_internal.h | 3 +- src/compiler/parse_global.c | 10 ++++ src/compiler/sema_passes.c | 89 +++++++++++++++++++------------- src/utils/file_utils.c | 75 ++++++++++----------------- src/utils/find_msvc.c | 2 +- src/utils/lib.h | 5 +- test/unit/stdlib/core/dstring.c3 | 8 +++ test/unit/stdlib/io/path.c3 | 38 ++++++++++++-- 18 files changed, 309 insertions(+), 145 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7ac3b21bb..d4dc837c0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index 9fef9518a..b925cfb55 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -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 diff --git a/lib/std/core/dstring.c3 b/lib/std/core/dstring.c3 index 406014ef8..9d5c78646 100644 --- a/lib/std/core/dstring.c3 +++ b/lib/std/core/dstring.c3 @@ -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; diff --git a/lib/std/io/os/rmtree.c3 b/lib/std/io/os/rmtree.c3 index 08b5da4e3..536cc6378 100644 --- a/lib/std/io/os/rmtree.c3 +++ b/lib/std/io/os/rmtree.c3 @@ -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)!; diff --git a/lib/std/io/path.c3 b/lib/std/io/path.c3 index 617c17ec1..1776fe4cd 100644 --- a/lib/std/io/path.c3 +++ b/lib/std/io/path.c3 @@ -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; diff --git a/lib/std/os/env.c3 b/lib/std/os/env.c3 index 06a9be55e..bbe1ebd6f 100644 --- a/lib/std/os/env.c3 +++ b/lib/std/os/env.c3 @@ -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 ""; + return SearchResult.MISSING?; $endif } \ No newline at end of file diff --git a/lib/std/os/macos/darwin.c3 b/lib/std/os/macos/darwin.c3 index 4e46836be..d81b59acd 100644 --- a/lib/std/os/macos/darwin.c3 +++ b/lib/std/os/macos/darwin.c3 @@ -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++) { diff --git a/releasenotes.md b/releasenotes.md index 1d17c1112..3f9ed21b7 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -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 diff --git a/src/build/libraries.c b/src/build/libraries.c index b0919895c..f261ac3ac 100644 --- a/src/build/libraries.c +++ b/src/build/libraries.c @@ -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)); } } } \ No newline at end of file diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index d713ab3de..e10f1ca6a 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -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); } diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 173a23f68..7d2c65c20 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -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); diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index da31c2285..d38eaf62d 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -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; diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index d3ad439ee..34d479e6d 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -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)); - } - 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."); + 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) diff --git a/src/utils/file_utils.c b/src/utils/file_utils.c index 1f5cb4ec8..b80f715ad 100644 --- a/src/utils/file_utils.c +++ b/src/utils/file_utils.c @@ -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) diff --git a/src/utils/find_msvc.c b/src/utils/find_msvc.c index 3eaea34f2..667528f1c 100644 --- a/src/utils/find_msvc.c +++ b/src/utils/find_msvc.c @@ -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."); } diff --git a/src/utils/lib.h b/src/utils/lib.h index fefcef1f4..bd24d2c4c 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -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); diff --git a/test/unit/stdlib/core/dstring.c3 b/test/unit/stdlib/core/dstring.c3 index 94c40a0bd..d0b79ecd5 100644 --- a/test/unit/stdlib/core/dstring.c3 +++ b/test/unit/stdlib/core/dstring.c3 @@ -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() { { diff --git a/test/unit/stdlib/io/path.c3 b/test/unit/stdlib/io/path.c3 index 448cdd418..84f6e85be 100644 --- a/test/unit/stdlib/io/path.c3 +++ b/test/unit/stdlib/io/path.c3 @@ -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 } \ No newline at end of file