diff --git a/doc/notes/import syntax.md b/doc/notes/import syntax.md new file mode 100644 index 00000000..4718a0eb --- /dev/null +++ b/doc/notes/import syntax.md @@ -0,0 +1,261 @@ +So we need some syntax to distinguish between a relative import and a logical +import. I'm not sure which way to go, and I'd like some feedback (or possibly +other alternate ideas I haven't considered). + +My two favorites are: + +``` +// Use +use "relative/path" +import "logical/path" + +// Node-style +import "./relative/path" +import "logical/path" +``` + +If you folks are OK with "use", that's my preference. But otherwise, the Node +style will definitely work too. I'm open to other ideas as well, including a few +below, but I'd like to not bikeshed this forever. + +## Background + +There are four general approaches we can take: + +### Use a modifier ("modifier") + +Both kinds of imports start with `import`, but then we use a second keyword +afterwards to identify either a relative or logical import. We could use *two* +keywords -- one for each kind -- but that's unnecessarily verbose. Instead, we +use the presence or absence of the keyword to distinguish. In other words: + +``` +import foo "string" +import "string" +``` + +The specific questions we have to answer are: + +1. Which kind of import gets the keyword? Ideally, the most common kind of + import would be the one that doesn't need an extra keyword. + +2. What keyword? This is surprisingly hard. Probably some kind of preposition. + +### Use different keywords ("keyword") + +Instead of using `import` for both logical and relative imports, we could have +two keywords, one for each kind. The specific questions to answer then are: + +1. Which kind of import gets `import`? +2. What's the other keyword? + +### Use different syntax for the path ("syntax") + +Instead of always using a string literal to identify what's being imported, we +could use a different kind of token or tokens for the different kinds of import. +For example, a string literal for one kind, and an identifier token for the +other: + +import identifier +import "string literal" + +The specific questions are: + +1. Which kind of import uses a string literal? +2. What's the syntax for the other kind? + +### Use a signifier in the import string itself to distinguish ("string") + +An import is always `import` followed by a string literal. Then we use some +specific markers inside the string literal itself to distinguish the two kinds. +For example, Node says that an import string starting with "./" or "../" is +relative and other import strings are logical. + +The specific question to answer is what kind of signifier we'd use. I think +Node's convention is the only real contender here, though. + +One feature this style has that none of the others do is that it means the +language syntax itself has no notion of logical and relative imports. This +means there is no overhead or complexity for host applications where that +distinction isn't meaningful. + +## Contenders + +These are options I'm open to, in roughly descending order of preference: + +### Node-style (string) + +If the string starts with "./" or "../", it's relative. + +``` +import "./relative/path" +import "logical/path" +``` + +This is how Node works, so there's prior art. It keeps the language completely +simple. It does feel sort of arbitrary and magical to me, but it's the simplest, +most expedient solution. + +### Use (keyword) + +The `use` keyword is for relative imports, `import` is for logical. + +``` +use "relative/path" +import "logical/path" +``` + +The `use` keyword comes from Pascal, but that's not very widely known. I kind +of like this. It's short, and `use` feels "nearer" to me than "import" so it +has the right connotation. (You can't "use" something unless you have it near +to hand.) + +It adds a little complexity to the language and VM. We have to support both +keywords and pass that "use versus import" bit through the name resolution +process. But that's pretty minor. + +### Slashes (syntax) + +If the path is a string literal, it's relative. Otherwise, it is a +slash-separated series of unquoted identifiers. + +``` +import "relative/path" +import logical/path +``` + +This means you can't (easily) use reserved words as names of logical imports. +This was my initial pitch. I still like how it looks, but I seem to be in the +minority. + +### Relative (modifier) + +The `relative` modifier is for relative imports. + +``` +import relative "relative/path" +import "logical/path" +``` + +It's explicit, which is good. It is unfortunately verbose. I think `relative` +is too useful of a word to make into a reserved word, which means it would have +to be a contextual keyword (i.e. treated like a reserved word after `import` +but behaving like a regular identifier elsewhere). I'm not generally a fan of +contextual keywords—they tend to make things like syntax highlighters more +difficult to create—so I try to avoid them. + +## Rejected + +I considered these ideas, but don't think they are good enough approaches for +various reasons: + +### Package identifier (syntax) + +If an unquoted identifier appears before the import string, then it's a logical +import within that package. Otherwise, it's relative. + +``` +import "relative/path" +import logical "path" +``` + +This was one of my initial ideas. It has the same problem as other unquoted +imports in that it makes it harder to have odd package names. It means the VM +has to understand this syntax and figure out how to display package names in +stack traces and stuff, so there is some extra complexity involved. + +The form where you have both a package name and a relative path within that +package is pretty unusual and likely unintuitive to users. + +### Dotted (syntax) + +If the path is a string literal, it's relative. Otherwise, it is a +dot-separated series of unquoted identifiers. + +``` +import "relative/path" +import logical.path +``` + +Similar to slashes, but using dots. This helps make logical imports look more +visually distinct from relative ones. But it also makes them look more similar +to getter calls, which they aren't related to at all. + +### Include (keyword) + +The `include` keyword is for relative imports, `import` is for logical. + +``` +include "relative/path" +import "logical/path" +``` + +Ruby uses `include` for applying mixins. "Include" reads to me more like some +kind of transclusion thing, so it feels a little weird. + +### Require (keyword) + +The `require` keyword is for relative imports, `import` is for logical. + +``` +require "relative/path" +import "logical/path" +``` + +Node uses "require" and ES6 uses "import" so this is kind of confusing. Ruby +uses `require` and `require_relative`, so using `require` for a relative import +is kind of confusing. Lua also uses `require`, but for both relative and +logical. Overall, this feels murky and unhelpful to me. + +### Angle-brackets (syntax) + +As in C/C++, an import string can be in angle brackets or quotes. Angle brackets +are for logical imports, quotes for relative. + +``` +import "relative/path" +import +``` + +Hard pass. It requires context-sensitive tokenization (!) in C and we definitely +don't want to go there. + +### URI scheme (string) + +An import string starting with "package:" and maybe "wren:" is treated as +logical, like they are URIs with an explicit scheme. Others are relative. + +``` +import "relative/path" +import "package:logical/path" +import "wren:random" +``` + +This is (roughly) how Dart works. I'm not a fan. I think it's too verbose for +logical imports. + +### Package (modifier) + +A `package` modifier indicates a logical import. Others are relative. + +``` +import "relative/path" +import package "logical/path" +``` + +Pretty long, and I'm not too crazy about baking "package" into the language and +VM. + +### From (modifier) + +A `from` modifier indicates, uh, one kind of import. + +``` +import "some/path" +import from "other/path" +``` + +It looks nice, but it's totally unclear to me whether logical imports should +get `from` or relative ones. Also kind of confusing in that Python and ES6 use +`from` in their notation for importing explicit variables from a module (where +Wren uses `for`). diff --git a/src/cli/main.c b/src/cli/main.c index 786ba31c..56f0c9d0 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -22,14 +22,19 @@ int main(int argc, const char* argv[]) osSetArguments(argc, argv); + WrenInterpretResult result; if (argc == 1) { - runRepl(); + result = runRepl(); } else { - runFile(argv[1]); + result = runFile(argv[1]); } - return 0; + // Exit with an error code if the script failed. + if (result == WREN_RESULT_COMPILE_ERROR) return 65; // EX_DATAERR. + if (result == WREN_RESULT_RUNTIME_ERROR) return 70; // EX_SOFTWARE. + + return getExitCode(); } diff --git a/src/cli/path.c b/src/cli/path.c index a3362a3a..460cef07 100644 --- a/src/cli/path.c +++ b/src/cli/path.c @@ -43,6 +43,75 @@ static void appendSlice(Path* path, Slice slice) path->chars[path->length] = '\0'; } +static bool isSeparator(char c) +{ + // Slash is a separator on POSIX and Windows. + if (c == '/') return true; + + // Backslash is only a separator on Windows. +#ifdef _WIN32 + if (c == '\\') return true; +#endif + + return false; +} + +#ifdef _WIN32 +static bool isDriveLetter(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} +#endif + +// Gets the length of the prefix of [path] that defines its absolute root. +// +// Returns 1 the leading "/". On Windows, also handles drive letters ("C:" or +// "C:\"). +// +// If the path is not absolute, returns 0. +static size_t absolutePrefixLength(const char* path) +{ +#ifdef _WIN32 + // Drive letter. + if (isDriveLetter(path[0]) && path[1] == ':') + { + if (isSeparator(path->chars[2])) + { + // Fully absolute path. + return 3; + } else { + // "Half-absolute" path like "C:", which is relative to the current + // working directory on drive. It's absolute for our purposes. + return 2; + } + } + + // TODO: UNC paths. + +#endif + + // POSIX-style absolute path or absolute path in the current drive on Windows. + if (isSeparator(path[0])) return 1; + + // Not absolute. + return 0; +} + +PathType pathType(const char* path) +{ + if (absolutePrefixLength(path) > 0) return PATH_TYPE_ABSOLUTE; + + // See if it must be relative. + if ((path[0] == '.' && isSeparator(path[1])) || + (path[0] == '.' && path[1] == '.' && isSeparator(path[2]))) + { + return PATH_TYPE_RELATIVE; + } + + // Otherwise, we don't know. + return PATH_TYPE_SIMPLE; +} + Path* pathNew(const char* string) { Path* path = (Path*)malloc(sizeof(Path)); @@ -67,7 +136,7 @@ void pathDirName(Path* path) // Find the last path separator. for (size_t i = path->length - 1; i < path->length; i--) { - if (path->chars[i] == '/') + if (isSeparator(path->chars[i])) { path->length = i; path->chars[i] = '\0'; @@ -86,7 +155,7 @@ void pathRemoveExtension(Path* path) { // If we hit a path separator before finding the extension, then the last // component doesn't have one. - if (path->chars[i] == '/') return; + if (isSeparator(path->chars[i])) return; if (path->chars[i] == '.') { @@ -98,7 +167,7 @@ void pathRemoveExtension(Path* path) void pathJoin(Path* path, const char* string) { - if (path->length > 0 && path->chars[path->length - 1] != '/') + if (path->length > 0 && !isSeparator(path->chars[path->length - 1])) { pathAppendChar(path, '/'); } @@ -106,37 +175,6 @@ void pathJoin(Path* path, const char* string) pathAppendString(path, string); } - -#ifdef _WIN32 -static bool isDriveLetter(char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} -#endif - -bool pathIsAbsolute(Path* path) -{ -#ifdef _WIN32 - // Absolute path in the current drive. - if (path->length >= 1 && path->chars[0] == '\\') return true; - - // Drive letter. - if (path->length >= 3 && - isDriveLetter(path->chars[0]) && - path->chars[1] == ':' && - path->chars[2] == '\\') - { - return true; - } - - // UNC path. - return path->length >= 2 && path->chars[0] == '\\' && path->chars[1] == '\\'; -#else - // Otherwise, assume POSIX-style paths. - return path->length >= 1 && path->chars[0] == '/'; -#endif -} - void pathAppendChar(Path* path, char c) { ensureCapacity(path, path->length + 1); @@ -152,10 +190,8 @@ void pathAppendString(Path* path, const char* string) appendSlice(path, slice); } -Path* pathNormalize(Path* path) +void pathNormalize(Path* path) { - Path* result = pathNew(""); - // Split the path into components. Slice components[MAX_COMPONENTS]; int numComponents = 0; @@ -167,7 +203,7 @@ Path* pathNormalize(Path* path) int leadingDoubles = 0; for (;;) { - if (*end == '\0' || *end == '/') + if (*end == '\0' || isSeparator(*end)) { // Add the current component. if (start != end) @@ -207,7 +243,7 @@ Path* pathNormalize(Path* path) } // Skip over separators. - while (*end != '\0' && *end == '/') end++; + while (*end != '\0' && isSeparator(*end)) end++; start = end; if (*end == '\0') break; @@ -216,13 +252,22 @@ Path* pathNormalize(Path* path) end++; } - // Preserve the absolute prefix, if any. + // Preserve the path type. We don't want to turn, say, "./foo" into "foo" + // because that changes the semantics of how that path is handled when used + // as an import string. bool needsSeparator = false; - if (path->length > 0 && path->chars[0] == '/') + + Path* result = pathNew(""); + size_t prefixLength = absolutePrefixLength(path->chars); + if (prefixLength > 0) { - pathAppendChar(result, '/'); + // It's an absolute path, so preserve the absolute prefix. + Slice slice; + slice.start = path->chars; + slice.end = path->chars + prefixLength; + appendSlice(result, slice); } - else + else if (leadingDoubles > 0) { // Add any leading "..". for (int i = 0; i < leadingDoubles; i++) @@ -232,6 +277,13 @@ Path* pathNormalize(Path* path) needsSeparator = true; } } + else if (path->chars[0] == '.' && isSeparator(path->chars[1])) + { + // Preserve a leading "./", since we use that to distinguish relative from + // logical imports. + pathAppendChar(result, '.'); + needsSeparator = true; + } for (int i = 0; i < numComponents; i++) { @@ -242,7 +294,11 @@ Path* pathNormalize(Path* path) if (result->length == 0) pathAppendChar(result, '.'); - return result; + // Copy back into the original path. + free(path->chars); + path->capacity = result->capacity; + path->chars = result->chars; + path->length = result->length; } char* pathToString(Path* path) diff --git a/src/cli/path.h b/src/cli/path.h index 114346ee..e87bc5da 100644 --- a/src/cli/path.h +++ b/src/cli/path.h @@ -16,6 +16,22 @@ typedef struct size_t capacity; } Path; +// Categorizes what form a path is. +typedef enum +{ + // An absolute path, starting with "/" on POSIX systems, a drive letter on + // Windows, etc. + PATH_TYPE_ABSOLUTE, + + // An explicitly relative path, starting with "./" or "../". + PATH_TYPE_RELATIVE, + + // A path that has no leading prefix, like "foo/bar". + PATH_TYPE_SIMPLE, +} PathType; + +PathType pathType(const char* path); + // Creates a new empty path. Path* pathNew(const char* string); @@ -31,9 +47,6 @@ void pathRemoveExtension(Path* path); // Appends [string] to [path]. void pathJoin(Path* path, const char* string); -// Return true if [path] is an absolute path for the host operating system. -bool pathIsAbsolute(Path* path); - // Appends [c] to the path, growing the buffer if needed. void pathAppendChar(Path* path, char c); @@ -43,8 +56,8 @@ void pathAppendString(Path* path, const char* string); // Simplifies the path string as much as possible. // // Applies and removes any "." or ".." components, collapses redundant "/" -// characters, etc. -Path* pathNormalize(Path* path); +// characters, and normalizes all path separators to "/". +void pathNormalize(Path* path); // Allocates a new string exactly the right length and copies this path to it. char* pathToString(Path* path); diff --git a/src/cli/stat.h b/src/cli/stat.h new file mode 100644 index 00000000..f54e6b6f --- /dev/null +++ b/src/cli/stat.h @@ -0,0 +1,22 @@ +#ifndef stat_h +#define stat_h + +// Utilities to smooth over working with stat() in a cross-platform way. + +// Windows doesn't define all of the Unix permission and mode flags by default, +// so map them ourselves. +#if defined(WIN32) || defined(WIN64) + #include + + // Map to Windows permission flags. + #define S_IRUSR _S_IREAD + #define S_IWUSR _S_IWRITE + + #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) + #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) + + // Not supported on Windows. + #define O_SYNC 0 +#endif + +#endif diff --git a/src/cli/vm.c b/src/cli/vm.c index 863e85e6..51ce1d17 100644 --- a/src/cli/vm.c +++ b/src/cli/vm.c @@ -5,6 +5,7 @@ #include "modules.h" #include "path.h" #include "scheduler.h" +#include "stat.h" #include "vm.h" // The single VM instance that the CLI uses. @@ -19,6 +20,7 @@ static uv_loop_t* loop; // TODO: This isn't currently used, but probably will be when package imports // are supported. If not then, then delete this. static char* rootDirectory = NULL; +static Path* wrenModulesDirectory = NULL; // The exit code to use unless some other error overrides it. int defaultExitCode = 0; @@ -61,46 +63,99 @@ static char* readFile(const char* path) return buffer; } +static bool isDirectory(Path* path) +{ + uv_fs_t request; + uv_fs_stat(loop, &request, path->chars, NULL); + // TODO: Check request.result value? + + bool result = request.result == 0 && S_ISDIR(request.statbuf.st_mode); + + uv_fs_req_cleanup(&request); + return result; +} + +static Path* realPath(Path* path) +{ + uv_fs_t request; + uv_fs_realpath(loop, &request, path->chars, NULL); + + Path* result = pathNew((char*)request.ptr); + + uv_fs_req_cleanup(&request); + return result; +} + +// Starting at [rootDirectory], walks up containing directories looking for a +// nearby "wren_modules" directory. If found, stores it in +// [wrenModulesDirectory]. +// +// If [wrenModulesDirectory] has already been found, does nothing. +static void findModulesDirectory() +{ + if (wrenModulesDirectory != NULL) return; + + Path* searchDirectory = pathNew(rootDirectory); + Path* lastPath = realPath(searchDirectory); + + // Keep walking up directories as long as we find them. + for (;;) + { + Path* modulesDirectory = pathNew(searchDirectory->chars); + pathJoin(modulesDirectory, "wren_modules"); + + if (isDirectory(modulesDirectory)) + { + pathNormalize(modulesDirectory); + wrenModulesDirectory = modulesDirectory; + pathFree(lastPath); + break; + } + + pathFree(modulesDirectory); + + // Walk up directories until we hit the root. We can tell that because + // adding ".." yields the same real path. + pathJoin(searchDirectory, ".."); + Path* thisPath = realPath(searchDirectory); + if (strcmp(lastPath->chars, thisPath->chars) == 0) + { + pathFree(thisPath); + break; + } + + pathFree(lastPath); + lastPath = thisPath; + } + + pathFree(searchDirectory); +} + // Applies the CLI's import resolution policy. The rules are: // -// * If [name] starts with "./" or "../", it is a relative import, relative to -// [importer]. The resolved path is [name] concatenated onto the directory +// * If [module] starts with "./" or "../", it is a relative import, relative +// to [importer]. The resolved path is [name] concatenated onto the directory // containing [importer] and then normalized. // -// For example, importing "./a/./b/../c" from "d/e/f" gives you "d/e/a/c". -// -// * Otherwise, it is a "package" import. This isn't implemented yet. -// +// For example, importing "./a/./b/../c" from "./d/e/f" gives you "./d/e/a/c". static const char* resolveModule(WrenVM* vm, const char* importer, - const char* name) + const char* module) { - size_t nameLength = strlen(name); + // Logical import strings are used as-is and need no resolution. + if (pathType(module) == PATH_TYPE_SIMPLE) return module; - // See if it's a relative import. - if (nameLength > 2 && - ((name[0] == '.' && name[1] == '/') || - (name[0] == '.' && name[1] == '.' && name[2] == '/'))) - { - // Get the directory containing the importing module. - Path* relative = pathNew(importer); - pathDirName(relative); - - // Add the relative import path. - pathJoin(relative, name); - Path* normal = pathNormalize(relative); - pathFree(relative); - - char* resolved = pathToString(normal); - pathFree(normal); - return resolved; - } - else - { - // TODO: Implement package imports. For now, treat any non-relative import - // as an import relative to the current working directory. - } + // Get the directory containing the importing module. + Path* path = pathNew(importer); + pathDirName(path); - return name; + // Add the relative import path. + pathJoin(path, module); + + pathNormalize(path); + char* resolved = pathToString(path); + + pathFree(path); + return resolved; } // Attempts to read the source for [module] relative to the current root @@ -110,23 +165,40 @@ static const char* resolveModule(WrenVM* vm, const char* importer, // module was found but could not be read. static char* readModule(WrenVM* vm, const char* module) { - // Since the module has already been resolved, it should now be either a - // valid relative path, or a package-style name. - - // TODO: Implement package imports. + Path* filePath; + if (pathType(module) == PATH_TYPE_SIMPLE) + { + // If there is no "wren_modules" directory, then the only logical imports + // we can handle are built-in ones. Let the VM try to handle it. + findModulesDirectory(); + if (wrenModulesDirectory == NULL) return readBuiltInModule(module); + + // TODO: Should we explicitly check for the existence of the module's base + // directory inside "wren_modules" here? + + // Look up the module in "wren_modules". + filePath = pathNew(wrenModulesDirectory->chars); + pathJoin(filePath, module); + + // If the module is a single bare name, treat it as a module with the same + // name inside the package. So "foo" means "foo/foo". + if (strchr(module, '/') == NULL) pathJoin(filePath, module); + } + else + { + // The module path is already a file path. + filePath = pathNew(module); + } // Add a ".wren" file extension. - Path* modulePath = pathNew(module); - pathAppendString(modulePath, ".wren"); - - char* source = readFile(modulePath->chars); - pathFree(modulePath); - - if (source != NULL) return source; + pathAppendString(filePath, ".wren"); - // TODO: This used to look for a file named "/module.wren" if - // ".wren" could not be found. Do we still want to support that with - // the new relative import and package stuff? + char* source = readFile(filePath->chars); + pathFree(filePath); + + // If we didn't find it, it may be a module built into the CLI or VM, so keep + // going. + if (source != NULL) return source; // Otherwise, see if it's a built-in module. return readBuiltInModule(module); @@ -222,9 +294,11 @@ static void freeVM() wrenFreeVM(vm); uv_tty_reset_mode(); + + if (wrenModulesDirectory != NULL) pathFree(wrenModulesDirectory); } -void runFile(const char* path) +WrenInterpretResult runFile(const char* path) { char* source = readFile(path); if (source == NULL) @@ -233,19 +307,36 @@ void runFile(const char* path) exit(66); } + // If it looks like a relative path, make it explicitly relative so that we + // can distinguish it from logical paths. + // TODO: It might be nice to be able to run scripts from within a surrounding + // "wren_modules" directory by passing in a simple path like "foo/bar". In + // that case, here, we could check to see whether the give path exists inside + // "wren_modules" or as a relative path and choose to add "./" or not based + // on that. + Path* module = pathNew(path); + if (pathType(module->chars) == PATH_TYPE_SIMPLE) + { + Path* relative = pathNew("."); + pathJoin(relative, path); + + pathFree(module); + module = relative; + } + + pathRemoveExtension(module); + // Use the directory where the file is as the root to resolve imports // relative to. - Path* directory = pathNew(path); + Path* directory = pathNew(module->chars); + pathDirName(directory); rootDirectory = pathToString(directory); pathFree(directory); - Path* moduleName = pathNew(path); - pathRemoveExtension(moduleName); - initVM(); - WrenInterpretResult result = wrenInterpret(vm, moduleName->chars, source); + WrenInterpretResult result = wrenInterpret(vm, module->chars, source); if (afterLoadFn != NULL) afterLoadFn(vm); @@ -258,29 +349,29 @@ void runFile(const char* path) free(source); free(rootDirectory); - pathFree(moduleName); + pathFree(module); - // Exit with an error code if the script failed. - if (result == WREN_RESULT_COMPILE_ERROR) exit(65); // EX_DATAERR. - if (result == WREN_RESULT_RUNTIME_ERROR) exit(70); // EX_SOFTWARE. - - if (defaultExitCode != 0) exit(defaultExitCode); + return result; } -int runRepl() +WrenInterpretResult runRepl() { + rootDirectory = "."; initVM(); printf("\\\\/\"-\n"); printf(" \\_/ wren v%s\n", WREN_VERSION_STRING); - wrenInterpret(vm, "repl", "import \"repl\"\n"); + WrenInterpretResult result = wrenInterpret(vm, "", "import \"repl\"\n"); - uv_run(loop, UV_RUN_DEFAULT); + if (result == WREN_RESULT_SUCCESS) + { + uv_run(loop, UV_RUN_DEFAULT); + } freeVM(); - - return 0; + + return result; } WrenVM* getVM() @@ -293,6 +384,11 @@ uv_loop_t* getLoop() return loop; } +int getExitCode() +{ + return defaultExitCode; +} + void setExitCode(int exitCode) { defaultExitCode = exitCode; diff --git a/src/cli/vm.h b/src/cli/vm.h index 6018dcee..cb5f8d15 100644 --- a/src/cli/vm.h +++ b/src/cli/vm.h @@ -5,12 +5,10 @@ #include "wren.h" // Executes the Wren script at [path] in a new VM. -// -// Exits if the script failed or could not be loaded. -void runFile(const char* path); +WrenInterpretResult runFile(const char* path); // Runs the Wren interactive REPL. -int runRepl(); +WrenInterpretResult runRepl(); // Gets the currently running VM. WrenVM* getVM(); @@ -18,6 +16,9 @@ WrenVM* getVM(); // Gets the event loop the VM is using. uv_loop_t* getLoop(); +// Get the exit code the CLI should exit with when done. +int getExitCode(); + // Set the exit code the CLI should exit with when done. void setExitCode(int exitCode); diff --git a/src/module/io.c b/src/module/io.c index ee4ee895..ac5a5f6d 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -4,28 +4,13 @@ #include "uv.h" #include "scheduler.h" +#include "stat.h" #include "vm.h" #include "wren.h" #include #include -// Windows doesn't define all of the Unix permission and mode flags by default, -// so map them ourselves. -#if defined(WIN32) || defined(WIN64) - #include - - // Map to Windows permission flags. - #define S_IRUSR _S_IREAD - #define S_IWUSR _S_IWRITE - - #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) - #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) - - // Not supported on Windows. - #define O_SYNC 0 -#endif - typedef struct sFileRequestData { WrenHandle* fiber; diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 0afb9123..dc016f00 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -723,7 +723,7 @@ static Value resolveModule(WrenVM* vm, Value name) return name; } -Value wrenImportModule(WrenVM* vm, Value name) +static Value importModule(WrenVM* vm, Value name) { name = resolveModule(vm, name); @@ -1308,7 +1308,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) { Value name = fn->constants.data[READ_SHORT()]; - Value result = wrenImportModule(vm, name); + Value result = importModule(vm, name); if (!IS_NULL(fiber->error)) RUNTIME_ERROR(); // Make a slot on the stack for the module's closure to place the return diff --git a/test/api/call.c b/test/api/call.c index 7f4264e2..f8d5c7d7 100644 --- a/test/api/call.c +++ b/test/api/call.c @@ -6,7 +6,7 @@ void callRunTests(WrenVM* vm) { wrenEnsureSlots(vm, 1); - wrenGetVariable(vm, "test/api/call", "Call", 0); + wrenGetVariable(vm, "./test/api/call", "Call", 0); WrenHandle* callClass = wrenGetSlotHandle(vm, 0); WrenHandle* noParams = wrenMakeCallHandle(vm, "noParams"); diff --git a/test/api/get_variable.c b/test/api/get_variable.c index 5dece4cf..b6d1d706 100644 --- a/test/api/get_variable.c +++ b/test/api/get_variable.c @@ -4,23 +4,23 @@ static void beforeDefined(WrenVM* vm) { - wrenGetVariable(vm, "test/api/get_variable", "A", 0); + wrenGetVariable(vm, "./test/api/get_variable", "A", 0); } static void afterDefined(WrenVM* vm) { - wrenGetVariable(vm, "test/api/get_variable", "A", 0); + wrenGetVariable(vm, "./test/api/get_variable", "A", 0); } static void afterAssigned(WrenVM* vm) { - wrenGetVariable(vm, "test/api/get_variable", "A", 0); + wrenGetVariable(vm, "./test/api/get_variable", "A", 0); } static void otherSlot(WrenVM* vm) { wrenEnsureSlots(vm, 3); - wrenGetVariable(vm, "test/api/get_variable", "B", 2); + wrenGetVariable(vm, "./test/api/get_variable", "B", 2); // Move it into return position. const char* string = wrenGetSlotString(vm, 2); @@ -29,7 +29,7 @@ static void otherSlot(WrenVM* vm) static void otherModule(WrenVM* vm) { - wrenGetVariable(vm, "test/api/get_variable_module", "Variable", 0); + wrenGetVariable(vm, "./test/api/get_variable_module", "Variable", 0); } WrenForeignMethodFn getVariableBindMethod(const char* signature) diff --git a/test/api/main.c b/test/api/main.c index 52610afa..bf8445d1 100644 --- a/test/api/main.c +++ b/test/api/main.c @@ -25,7 +25,7 @@ static WrenForeignMethodFn bindForeignMethod( WrenVM* vm, const char* module, const char* className, bool isStatic, const char* signature) { - if (strncmp(module, "test/", 5) != 0) return NULL; + if (strncmp(module, "./test/", 7) != 0) return NULL; // For convenience, concatenate all of the method qualifiers into a single // signature string. @@ -78,7 +78,7 @@ static WrenForeignClassMethods bindForeignClass( WrenVM* vm, const char* module, const char* className) { WrenForeignClassMethods methods = { NULL, NULL }; - if (strncmp(module, "test/", 5) != 0) return methods; + if (strncmp(module, "./test/", 7) != 0) return methods; foreignClassBindClass(className, &methods); if (methods.allocate != NULL) return methods; diff --git a/test/api/reset_stack_after_call_abort.c b/test/api/reset_stack_after_call_abort.c index c94687fe..a4d09af3 100644 --- a/test/api/reset_stack_after_call_abort.c +++ b/test/api/reset_stack_after_call_abort.c @@ -6,7 +6,7 @@ void resetStackAfterCallAbortRunTests(WrenVM* vm) { wrenEnsureSlots(vm, 1); - wrenGetVariable(vm, "test/api/reset_stack_after_call_abort", "Test", 0); + wrenGetVariable(vm, "./test/api/reset_stack_after_call_abort", "Test", 0); WrenHandle* testClass = wrenGetSlotHandle(vm, 0); WrenHandle* abortFiber = wrenMakeCallHandle(vm, "abortFiber()"); diff --git a/test/api/reset_stack_after_foreign_construct.c b/test/api/reset_stack_after_foreign_construct.c index ce829a8b..52e1f720 100644 --- a/test/api/reset_stack_after_foreign_construct.c +++ b/test/api/reset_stack_after_foreign_construct.c @@ -22,7 +22,8 @@ void resetStackAfterForeignConstructBindClass( void resetStackAfterForeignConstructRunTests(WrenVM* vm) { wrenEnsureSlots(vm, 1); - wrenGetVariable(vm, "test/api/reset_stack_after_foreign_construct", "Test", 0); + wrenGetVariable(vm, + "./test/api/reset_stack_after_foreign_construct", "Test", 0); WrenHandle* testClass = wrenGetSlotHandle(vm, 0); WrenHandle* callConstruct = wrenMakeCallHandle(vm, "callConstruct()"); diff --git a/test/language/foreign/unknown_method.wren b/test/language/foreign/unknown_method.wren index 2cdf01be..1c38ed8c 100644 --- a/test/language/foreign/unknown_method.wren +++ b/test/language/foreign/unknown_method.wren @@ -1,3 +1,3 @@ class Foo { - foreign someUnknownMethod // expect runtime error: Could not find foreign method 'someUnknownMethod' for class Foo in module 'test/language/foreign/unknown_method'. + foreign someUnknownMethod // expect runtime error: Could not find foreign method 'someUnknownMethod' for class Foo in module './test/language/foreign/unknown_method'. } diff --git a/test/language/module/compile_error/compile_error.wren b/test/language/module/compile_error/compile_error.wren index f136e882..8c8df453 100644 --- a/test/language/module/compile_error/compile_error.wren +++ b/test/language/module/compile_error/compile_error.wren @@ -1,2 +1,2 @@ System.print("before") // expect: before -import "./module" for Module // expect runtime error: Could not compile module 'test/language/module/compile_error/module'. +import "./module" for Module // expect runtime error: Could not compile module './test/language/module/compile_error/module'. diff --git a/test/language/module/logical_dir/logical_dir.wren b/test/language/module/logical_dir/logical_dir.wren new file mode 100644 index 00000000..421143fa --- /dev/null +++ b/test/language/module/logical_dir/logical_dir.wren @@ -0,0 +1,6 @@ +// Import a module from within a named package. +import "foo/within_foo" for Foo +// expect: ran foo module +// expect: ran bar module + +System.print(Foo) // expect: from foo diff --git a/test/language/module/logical_dir/wren_modules/bar/within_bar.wren b/test/language/module/logical_dir/wren_modules/bar/within_bar.wren new file mode 100644 index 00000000..701bee91 --- /dev/null +++ b/test/language/module/logical_dir/wren_modules/bar/within_bar.wren @@ -0,0 +1,5 @@ +// nontest +var Bar = "from bar" +System.print("ran bar module") + +import "foo/within_foo" diff --git a/test/language/module/logical_dir/wren_modules/foo/within_foo.wren b/test/language/module/logical_dir/wren_modules/foo/within_foo.wren new file mode 100644 index 00000000..4d55dd19 --- /dev/null +++ b/test/language/module/logical_dir/wren_modules/foo/within_foo.wren @@ -0,0 +1,5 @@ +// nontest +var Foo = "from foo" +System.print("ran foo module") + +import "bar/within_bar" diff --git a/test/language/module/logical_short/logical_short.wren b/test/language/module/logical_short/logical_short.wren new file mode 100644 index 00000000..baaa5d66 --- /dev/null +++ b/test/language/module/logical_short/logical_short.wren @@ -0,0 +1,5 @@ +// Import a module whose name is the same as the package. +import "foo" for Module +// expect: ran module + +System.print(Module) // expect: from module diff --git a/test/language/module/logical_short/wren_modules/foo/foo.wren b/test/language/module/logical_short/wren_modules/foo/foo.wren new file mode 100644 index 00000000..a1289a13 --- /dev/null +++ b/test/language/module/logical_short/wren_modules/foo/foo.wren @@ -0,0 +1,3 @@ +// nontest +var Module = "from module" +System.print("ran module") diff --git a/test/language/module/unknown_module.wren b/test/language/module/unknown_module.wren index a80cc297..1cc88d13 100644 --- a/test/language/module/unknown_module.wren +++ b/test/language/module/unknown_module.wren @@ -1 +1 @@ -import "./does_not_exist" for DoesNotExist // expect runtime error: Could not load module 'test/language/module/does_not_exist'. +import "./does_not_exist" for DoesNotExist // expect runtime error: Could not load module './test/language/module/does_not_exist'. diff --git a/test/language/module/unknown_variable/unknown_variable.wren b/test/language/module/unknown_variable/unknown_variable.wren index 23f347c9..dc172f45 100644 --- a/test/language/module/unknown_variable/unknown_variable.wren +++ b/test/language/module/unknown_variable/unknown_variable.wren @@ -1,3 +1,3 @@ // Should execute the module: // expect: ran module -import "./module" for DoesNotExist // expect runtime error: Could not find a variable named 'DoesNotExist' in module 'test/language/module/unknown_variable/module'. +import "./module" for DoesNotExist // expect runtime error: Could not find a variable named 'DoesNotExist' in module './test/language/module/unknown_variable/module'. diff --git a/test/language/module/use_nearest_modules_dir/a/b/use_nearest_modules_dir.wren b/test/language/module/use_nearest_modules_dir/a/b/use_nearest_modules_dir.wren new file mode 100644 index 00000000..b9208661 --- /dev/null +++ b/test/language/module/use_nearest_modules_dir/a/b/use_nearest_modules_dir.wren @@ -0,0 +1,3 @@ +// Stops as soon as it finds a wren_modules directory, regardless of whether or +// not it contains the desired module. +import "foo" // expect runtime error: Could not load module 'foo'. diff --git a/test/language/module/use_nearest_modules_dir/wren_modules/foo/foo.wren b/test/language/module/use_nearest_modules_dir/wren_modules/foo/foo.wren new file mode 100644 index 00000000..21d3f865 --- /dev/null +++ b/test/language/module/use_nearest_modules_dir/wren_modules/foo/foo.wren @@ -0,0 +1,2 @@ +// nontest +System.print("ran foo module") diff --git a/test/language/module/walk_up_for_modules_dir/a/b/walk_up_for_modules_dir.wren b/test/language/module/walk_up_for_modules_dir/a/b/walk_up_for_modules_dir.wren new file mode 100644 index 00000000..345c728d --- /dev/null +++ b/test/language/module/walk_up_for_modules_dir/a/b/walk_up_for_modules_dir.wren @@ -0,0 +1,3 @@ +// Walk up parent directories from the root script to find "wren_modules". +import "foo" +// expect: ran foo module diff --git a/test/language/module/walk_up_for_modules_dir/wren_modules/foo/foo.wren b/test/language/module/walk_up_for_modules_dir/wren_modules/foo/foo.wren new file mode 100644 index 00000000..21d3f865 --- /dev/null +++ b/test/language/module/walk_up_for_modules_dir/wren_modules/foo/foo.wren @@ -0,0 +1,2 @@ +// nontest +System.print("ran foo module") diff --git a/test/meta/get_module_variables.wren b/test/meta/get_module_variables.wren index 34dc6a7d..0347b344 100644 --- a/test/meta/get_module_variables.wren +++ b/test/meta/get_module_variables.wren @@ -1,6 +1,6 @@ import "meta" for Meta -var variables = Meta.getModuleVariables("test/meta/get_module_variables") +var variables = Meta.getModuleVariables("./test/meta/get_module_variables") // Includes implicitly imported core stuff. System.print(variables.contains("Object")) // expect: true diff --git a/test/unit/path_test.c b/test/unit/path_test.c index a175dc49..14ce2ac9 100644 --- a/test/unit/path_test.c +++ b/test/unit/path_test.c @@ -9,40 +9,39 @@ static void expectNormalize(const char* input, const char* expected) { Path* path = pathNew(input); - Path* result = pathNormalize(path); + pathNormalize(path); - if (strcmp(result->chars, expected) != 0) + if (strcmp(path->chars, expected) != 0) { printf("FAIL %-30s Want %s\n", input, expected); - printf(" Got %s\n\n", result->chars); + printf(" Got %s\n\n", path->chars); fail(); } else { #if SHOW_PASSES - printf("PASS %-30s -> %s\n", input, result->chars); + printf("PASS %-30s -> %s\n", input, path->chars); #endif pass(); } pathFree(path); - pathFree(result); } static void testNormalize() { - // simple cases + // Simple cases. expectNormalize("", "."); expectNormalize(".", "."); expectNormalize("..", ".."); expectNormalize("a", "a"); expectNormalize("/", "/"); - // collapses redundant separators + // Collapses redundant separators. expectNormalize("a/b/c", "a/b/c"); expectNormalize("a//b///c////d", "a/b/c/d"); - // eliminates "." parts + // Eliminates "." parts, except one at the beginning. expectNormalize("./", "."); expectNormalize("/.", "/"); expectNormalize("/./", "/"); @@ -50,10 +49,10 @@ static void testNormalize() expectNormalize("a/./b", "a/b"); expectNormalize("a/.b/c", "a/.b/c"); expectNormalize("a/././b/./c", "a/b/c"); - expectNormalize("././a", "a"); + expectNormalize("././a", "./a"); expectNormalize("a/./.", "a"); - // eliminates ".." parts + // Eliminates ".." parts. expectNormalize("..", ".."); expectNormalize("../", ".."); expectNormalize("../../..", "../../.."); @@ -68,7 +67,7 @@ static void testNormalize() expectNormalize("a/b/c/../../d/e/..", "a/d"); expectNormalize("a/b/../../../../c", "../../c"); - // does not walk before root on absolute paths + // Does not walk before root on absolute paths. expectNormalize("..", ".."); expectNormalize("../", ".."); expectNormalize("/..", "/"); @@ -84,7 +83,7 @@ static void testNormalize() expectNormalize("a/b/../../../../c", "../../c"); expectNormalize("a/b/c/../../..d/./.e/f././", "a/..d/.e/f."); - // removes trailing separators + // Removes trailing separators. expectNormalize("./", "."); expectNormalize(".//", "."); expectNormalize("a/", "a"); @@ -94,7 +93,7 @@ static void testNormalize() expectNormalize("foo/bar/baz", "foo/bar/baz"); expectNormalize("foo", "foo"); expectNormalize("foo/bar/", "foo/bar"); - expectNormalize("./foo/././bar/././", "foo/bar"); + expectNormalize("./foo/././bar/././", "./foo/bar"); } void testPath() diff --git a/util/test.py b/util/test.py index adfa9b62..7010f431 100755 --- a/util/test.py +++ b/util/test.py @@ -32,7 +32,7 @@ EXPECT_ERROR_PATTERN = re.compile(r'// expect error(?! line)') EXPECT_ERROR_LINE_PATTERN = re.compile(r'// expect error line (\d+)') EXPECT_RUNTIME_ERROR_PATTERN = re.compile(r'// expect (handled )?runtime error: (.+)') ERROR_PATTERN = re.compile(r'\[.* line (\d+)\] Error') -STACK_TRACE_PATTERN = re.compile(r'\[test/.* line (\d+)\] in') +STACK_TRACE_PATTERN = re.compile(r'\[\./test/.* line (\d+)\] in') STDIN_PATTERN = re.compile(r'// stdin: (.*)') SKIP_PATTERN = re.compile(r'// skip: (.*)') NONTEST_PATTERN = re.compile(r'// nontest') diff --git a/util/xcode/wren.xcodeproj/project.pbxproj b/util/xcode/wren.xcodeproj/project.pbxproj index 3e729748..4250c61d 100644 --- a/util/xcode/wren.xcodeproj/project.pbxproj +++ b/util/xcode/wren.xcodeproj/project.pbxproj @@ -24,10 +24,7 @@ 293B255A1CEFD8C7005D9537 /* repl.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 293B25561CEFD8C7005D9537 /* repl.wren.inc */; }; 293D46961BB43F9900200083 /* call.c in Sources */ = {isa = PBXBuildFile; fileRef = 293D46941BB43F9900200083 /* call.c */; }; 2940E98D2063EC030054503C /* resolution.c in Sources */ = {isa = PBXBuildFile; fileRef = 2940E98B2063EC020054503C /* resolution.c */; }; - 2940E9BB2066067D0054503C /* path_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 2940E9BA2066067D0054503C /* path_test.c */; }; 2940E9BC206607830054503C /* path.c in Sources */ = {isa = PBXBuildFile; fileRef = 2952CD1B1FA9941700985F5F /* path.c */; }; - 2940E9BE2066C3300054503C /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 2940E9BD2066C3300054503C /* main.c */; }; - 2940E9C12066C35E0054503C /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 2940E9BF2066C35E0054503C /* test.c */; }; 2949AA8D1C2F14F000B106BA /* get_variable.c in Sources */ = {isa = PBXBuildFile; fileRef = 2949AA8B1C2F14F000B106BA /* get_variable.c */; }; 29512C811B91F8EB008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; }; 29512C821B91F901008C10E6 /* libuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 29512C801B91F8EB008C10E6 /* libuv.a */; }; @@ -47,6 +44,9 @@ 29A4273A1BDBE435001E6E22 /* wren_opt_random.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29A427331BDBE435001E6E22 /* wren_opt_random.wren.inc */; }; 29A4273B1BDBE435001E6E22 /* wren_opt_random.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29A427331BDBE435001E6E22 /* wren_opt_random.wren.inc */; }; 29AD96611D0A57F800C4DFE7 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 29AD965F1D0A57F800C4DFE7 /* error.c */; }; + 29B59F0820FC37B700767E48 /* path_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 29B59F0320FC37B600767E48 /* path_test.c */; }; + 29B59F0920FC37B700767E48 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29B59F0420FC37B700767E48 /* main.c */; }; + 29B59F0A20FC37B700767E48 /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 29B59F0520FC37B700767E48 /* test.c */; }; 29C80D5A1D73332A00493837 /* reset_stack_after_foreign_construct.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C80D581D73332A00493837 /* reset_stack_after_foreign_construct.c */; }; 29C8A9331AB71FFF00DEC81D /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A9311AB71FFF00DEC81D /* vm.c */; }; 29C946981C88F14F00B4A4F3 /* new_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 29C946961C88F14F00B4A4F3 /* new_vm.c */; }; @@ -133,11 +133,6 @@ 2940E98B2063EC020054503C /* resolution.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = resolution.c; path = ../../test/api/resolution.c; sourceTree = ""; }; 2940E98C2063EC020054503C /* resolution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = resolution.h; path = ../../test/api/resolution.h; sourceTree = ""; }; 2940E9B8206605DE0054503C /* unit_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unit_test; sourceTree = BUILT_PRODUCTS_DIR; }; - 2940E9B92066067D0054503C /* path_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = path_test.h; path = ../../../test/unit/path_test.h; sourceTree = ""; }; - 2940E9BA2066067D0054503C /* path_test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = path_test.c; path = ../../../test/unit/path_test.c; sourceTree = ""; }; - 2940E9BD2066C3300054503C /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../../test/unit/main.c; sourceTree = ""; }; - 2940E9BF2066C35E0054503C /* test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = test.c; path = ../../../test/unit/test.c; sourceTree = ""; }; - 2940E9C02066C35E0054503C /* test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = test.h; path = ../../../test/unit/test.h; sourceTree = ""; }; 2949AA8B1C2F14F000B106BA /* get_variable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = get_variable.c; path = ../../test/api/get_variable.c; sourceTree = ""; }; 2949AA8C1C2F14F000B106BA /* get_variable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = get_variable.h; path = ../../test/api/get_variable.h; sourceTree = ""; }; 29512C7F1B91F86E008C10E6 /* api_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = api_test; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -145,6 +140,7 @@ 2952CD1B1FA9941700985F5F /* path.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = path.c; path = ../../src/cli/path.c; sourceTree = ""; }; 2952CD1C1FA9941700985F5F /* path.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = path.h; path = ../../src/cli/path.h; sourceTree = ""; }; 296371B31AC713D000079FDA /* wren_opcodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = ""; }; + 29703E57206DC7B7004004DC /* stat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stat.h; path = ../../src/cli/stat.h; sourceTree = ""; }; 29729F2E1BA70A620099CA20 /* io.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = io.c; path = ../../src/module/io.c; sourceTree = ""; }; 29729F301BA70A620099CA20 /* io.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = io.wren.inc; path = ../../src/module/io.wren.inc; sourceTree = ""; }; 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_primitive.c; path = ../../src/vm/wren_primitive.c; sourceTree = ""; }; @@ -162,6 +158,11 @@ 29AB1F061816E3AD004B501E /* wren */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = wren; sourceTree = BUILT_PRODUCTS_DIR; }; 29AD965F1D0A57F800C4DFE7 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = error.c; path = ../../test/api/error.c; sourceTree = ""; }; 29AD96601D0A57F800C4DFE7 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = error.h; path = ../../test/api/error.h; sourceTree = ""; }; + 29B59F0320FC37B600767E48 /* path_test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = path_test.c; path = ../../test/unit/path_test.c; sourceTree = ""; }; + 29B59F0420FC37B700767E48 /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/unit/main.c; sourceTree = ""; }; + 29B59F0520FC37B700767E48 /* test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = test.c; path = ../../test/unit/test.c; sourceTree = ""; }; + 29B59F0620FC37B700767E48 /* path_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = path_test.h; path = ../../test/unit/path_test.h; sourceTree = ""; }; + 29B59F0720FC37B700767E48 /* test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = test.h; path = ../../test/unit/test.h; sourceTree = ""; }; 29C80D581D73332A00493837 /* reset_stack_after_foreign_construct.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = reset_stack_after_foreign_construct.c; path = ../../test/api/reset_stack_after_foreign_construct.c; sourceTree = ""; }; 29C80D591D73332A00493837 /* reset_stack_after_foreign_construct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = reset_stack_after_foreign_construct.h; path = ../../test/api/reset_stack_after_foreign_construct.h; sourceTree = ""; }; 29C8A9311AB71FFF00DEC81D /* vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vm.c; path = ../../src/cli/vm.c; sourceTree = ""; }; @@ -264,6 +265,7 @@ 291647C51BA5EC5E006142EE /* modules.c */, 2952CD1C1FA9941700985F5F /* path.h */, 2952CD1B1FA9941700985F5F /* path.c */, + 29703E57206DC7B7004004DC /* stat.h */, 29C8A9321AB71FFF00DEC81D /* vm.h */, 29C8A9311AB71FFF00DEC81D /* vm.c */, ); @@ -278,18 +280,6 @@ name = include; sourceTree = ""; }; - 2940E98E206605CB0054503C /* unit_test */ = { - isa = PBXGroup; - children = ( - 2940E9BD2066C3300054503C /* main.c */, - 2940E9BA2066067D0054503C /* path_test.c */, - 2940E9B92066067D0054503C /* path_test.h */, - 2940E9BF2066C35E0054503C /* test.c */, - 2940E9C02066C35E0054503C /* test.h */, - ); - path = unit_test; - sourceTree = ""; - }; 29AB1EFD1816E3AD004B501E = { isa = PBXGroup; children = ( @@ -299,7 +289,7 @@ 29AF31EE1BD2E37F00AAD156 /* optional */, 29205CA01AB4E6470073018D /* vm */, 29D0099A1B7E394F000CE58C /* api_test */, - 2940E98E206605CB0054503C /* unit_test */, + 29B59F0B20FC37BD00767E48 /* unit_test */, 29512C801B91F8EB008C10E6 /* libuv.a */, 29AB1F071816E3AD004B501E /* Products */, ); @@ -328,6 +318,18 @@ name = optional; sourceTree = ""; }; + 29B59F0B20FC37BD00767E48 /* unit_test */ = { + isa = PBXGroup; + children = ( + 29B59F0420FC37B700767E48 /* main.c */, + 29B59F0320FC37B600767E48 /* path_test.c */, + 29B59F0620FC37B700767E48 /* path_test.h */, + 29B59F0520FC37B700767E48 /* test.c */, + 29B59F0720FC37B700767E48 /* test.h */, + ); + name = unit_test; + sourceTree = ""; + }; 29D0099A1B7E394F000CE58C /* api_test */ = { isa = PBXGroup; children = ( @@ -454,10 +456,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 29B59F0820FC37B700767E48 /* path_test.c in Sources */, 2940E9BC206607830054503C /* path.c in Sources */, - 2940E9BB2066067D0054503C /* path_test.c in Sources */, - 2940E9BE2066C3300054503C /* main.c in Sources */, - 2940E9C12066C35E0054503C /* test.c in Sources */, + 29B59F0920FC37B700767E48 /* main.c in Sources */, + 29B59F0A20FC37B700767E48 /* test.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };