diff --git a/src/all_types.hpp b/src/all_types.hpp index c6e0dc3d17..ee71a0ec28 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1088,6 +1088,7 @@ enum BuiltinFnId { BuiltinFnIdCImport, BuiltinFnIdErrName, BuiltinFnIdBreakpoint, + BuiltinFnIdEmbedFile, }; struct BuiltinFnEntry { diff --git a/src/analyze.cpp b/src/analyze.cpp index 244bdc5fc6..38835a0d86 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -4101,6 +4101,45 @@ static TypeTableEntry *analyze_err_name(CodeGen *g, ImportTableEntry *import, return str_type; } +static TypeTableEntry *analyze_embed_file(CodeGen *g, ImportTableEntry *import, + BlockContext *context, AstNode *node) +{ + assert(node->type == NodeTypeFnCallExpr); + + AstNode **first_param_node = &node->data.fn_call_expr.params.at(0); + Buf *rel_file_path = resolve_const_expr_str(g, import, context, first_param_node); + if (!rel_file_path) { + return g->builtin_types.entry_invalid; + } + + // figure out absolute path to resource + Buf source_dir_path = BUF_INIT; + os_path_dirname(import->path, &source_dir_path); + + Buf file_path = BUF_INIT; + os_path_resolve(&source_dir_path, rel_file_path, &file_path); + + // load from file system into const expr + Buf file_contents = BUF_INIT; + int err; + if ((err = os_fetch_file_path(&file_path, &file_contents))) { + if (err == ErrorFileNotFound) { + add_node_error(g, node, + buf_sprintf("unable to find '%s'", buf_ptr(&file_path))); + return g->builtin_types.entry_invalid; + } else { + add_node_error(g, node, + buf_sprintf("unable to open '%s': %s", buf_ptr(&file_path), err_str(err))); + return g->builtin_types.entry_invalid; + } + } + + // TODO add dependency on the file we embedded so that we know if it changes + // we'll have to invalidate the cache + + return resolve_expr_const_val_as_string_lit(g, node, &file_contents); +} + static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, TypeTableEntry *expected_type, AstNode *node) { @@ -4434,6 +4473,8 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry case BuiltinFnIdBreakpoint: mark_impure_fn(context); return g->builtin_types.entry_void; + case BuiltinFnIdEmbedFile: + return analyze_embed_file(g, import, context, node); } zig_unreachable(); } diff --git a/src/codegen.cpp b/src/codegen.cpp index ddac9b72e0..0f2a63d3fc 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -507,6 +507,7 @@ static LLVMValueRef gen_builtin_fn_call_expr(CodeGen *g, AstNode *node) { case BuiltinFnIdMaxValue: case BuiltinFnIdMemberCount: case BuiltinFnIdConstEval: + case BuiltinFnIdEmbedFile: // caught by constant expression eval codegen zig_unreachable(); case BuiltinFnIdCompileVar: @@ -3896,6 +3897,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn_with_arg_count(g, BuiltinFnIdImport, "import", 1); create_builtin_fn_with_arg_count(g, BuiltinFnIdCImport, "c_import", 1); create_builtin_fn_with_arg_count(g, BuiltinFnIdErrName, "err_name", 1); + create_builtin_fn_with_arg_count(g, BuiltinFnIdEmbedFile, "embed_file", 1); } static void init(CodeGen *g, Buf *source_path) { diff --git a/src/eval.cpp b/src/eval.cpp index b3120118c6..1f2a05a247 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -696,6 +696,7 @@ static bool eval_fn_call_builtin(EvalFn *ef, AstNode *node, ConstExprValue *out_ case BuiltinFnIdImport: case BuiltinFnIdCImport: case BuiltinFnIdErrName: + case BuiltinFnIdEmbedFile: zig_panic("TODO"); case BuiltinFnIdBreakpoint: case BuiltinFnIdInvalid: diff --git a/src/os.cpp b/src/os.cpp index 73483a5e81..4b5d898aa7 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -91,6 +91,10 @@ void os_spawn_process(const char *exe, ZigList &args, int *return_ #endif } +void os_path_dirname(Buf *full_path, Buf *out_dirname) { + return os_path_split(full_path, out_dirname, nullptr); +} + void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename) { int last_index = buf_len(full_path) - 1; if (last_index >= 0 && buf_ptr(full_path)[last_index] == '/') { @@ -99,13 +103,17 @@ void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename) { for (int i = last_index; i >= 0; i -= 1) { uint8_t c = buf_ptr(full_path)[i]; if (c == '/') { - buf_init_from_mem(out_dirname, buf_ptr(full_path), i); - buf_init_from_mem(out_basename, buf_ptr(full_path) + i + 1, buf_len(full_path) - (i + 1)); + if (out_dirname) { + buf_init_from_mem(out_dirname, buf_ptr(full_path), i); + } + if (out_basename) { + buf_init_from_mem(out_basename, buf_ptr(full_path) + i + 1, buf_len(full_path) - (i + 1)); + } return; } } - buf_init_from_mem(out_dirname, ".", 1); - buf_init_from_buf(out_basename, full_path); + if (out_dirname) buf_init_from_mem(out_dirname, ".", 1); + if (out_basename) buf_init_from_buf(out_basename, full_path); } void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path) { @@ -146,6 +154,26 @@ int os_path_real(Buf *rel_path, Buf *out_abs_path) { #endif } +bool os_path_is_absolute(Buf *path) { +#if defined(ZIG_OS_WINDOWS) +#error "missing os_path_is_absolute implementation" +#elif defined(ZIG_OS_POSIX) + return buf_ptr(path)[0] == '/'; +#else +#error "missing os_path_is_absolute implementation" +#endif +} + +void os_path_resolve(Buf *ref_path, Buf *target_path, Buf *out_abs_path) { + if (os_path_is_absolute(target_path)) { + buf_init_from_buf(out_abs_path, target_path); + return; + } + + os_path_join(ref_path, target_path, out_abs_path); + return; +} + int os_fetch_file(FILE *f, Buf *out_buf) { static const ssize_t buf_size = 0x2000; buf_resize(out_buf, buf_size); diff --git a/src/os.hpp b/src/os.hpp index ae43f8fe91..f545170138 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -18,9 +18,12 @@ void os_spawn_process(const char *exe, ZigList &args, int *return_ int os_exec_process(const char *exe, ZigList &args, int *return_code, Buf *out_stderr, Buf *out_stdout); +void os_path_dirname(Buf *full_path, Buf *out_dirname); void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename); void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path); int os_path_real(Buf *rel_path, Buf *out_abs_path); +void os_path_resolve(Buf *ref_path, Buf *target_path, Buf *out_abs_path); +bool os_path_is_absolute(Buf *path); void os_write_file(Buf *full_path, Buf *contents); diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 7b5b90e4ae..fdc03b0ae0 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -600,6 +600,20 @@ fn do_test() -> %void { } fn its_gonna_pass() -> %void { } )SOURCE", "before\nafter\ndefer3\ndefer1\n"); + + + { + TestCase *tc = add_simple_case("@embed_file", R"SOURCE( +const foo_txt = @embed_file("foo.txt"); +const io = @import("std").io; + +pub fn main(args: [][]u8) -> %void { + %%io.stdout.printf(foo_txt); +} + )SOURCE", "1234\nabcd\n"); + + add_source_file(tc, "foo.txt", "1234\nabcd\n"); + } } @@ -1173,6 +1187,10 @@ fn fibbonaci(x: i32) -> i32 { ".tmp_source.zig:3:1: error: function evaluation exceeded 1000 branches", ".tmp_source.zig:2:37: note: called from here", ".tmp_source.zig:4:40: note: quota exceeded here"); + + add_compile_fail_case("@embed_file with bogus file", R"SOURCE( +const resource = @embed_file("bogus.txt"); + )SOURCE", 1, ".tmp_source.zig:2:18: error: unable to find './bogus.txt'"); } //////////////////////////////////////////////////////////////////////////////