Get logical imports in "wren_modules" working.

There's a lot of changes here and surely some rough edges to iron out.
Also, I need to update the docs. But I want to get closer to landing
this so I can build on it.
This commit is contained in:
Bob Nystrom
2018-07-15 20:09:41 -07:00
parent 8210452970
commit c367fc3bfc
31 changed files with 665 additions and 190 deletions

261
doc/notes/import syntax.md Normal file
View File

@ -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 <logical/path>
```
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`).

View File

@ -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();
}

View File

@ -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)

View File

@ -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);

22
src/cli/stat.h Normal file
View File

@ -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 <sys\stat.h>
// 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

View File

@ -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 "<path>/module.wren" if
// "<path>.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, "<repl>", "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;

View File

@ -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);

View File

@ -4,28 +4,13 @@
#include "uv.h"
#include "scheduler.h"
#include "stat.h"
#include "vm.h"
#include "wren.h"
#include <stdio.h>
#include <fcntl.h>
// Windows doesn't define all of the Unix permission and mode flags by default,
// so map them ourselves.
#if defined(WIN32) || defined(WIN64)
#include <sys\stat.h>
// 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;

View File

@ -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

View File

@ -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");

View File

@ -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)

View File

@ -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;

View File

@ -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()");

View File

@ -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()");

View File

@ -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'.
}

View File

@ -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'.

View File

@ -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

View File

@ -0,0 +1,5 @@
// nontest
var Bar = "from bar"
System.print("ran bar module")
import "foo/within_foo"

View File

@ -0,0 +1,5 @@
// nontest
var Foo = "from foo"
System.print("ran foo module")
import "bar/within_bar"

View File

@ -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

View File

@ -0,0 +1,3 @@
// nontest
var Module = "from module"
System.print("ran module")

View File

@ -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'.

View File

@ -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'.

View File

@ -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'.

View File

@ -0,0 +1,2 @@
// nontest
System.print("ran foo module")

View File

@ -0,0 +1,3 @@
// Walk up parent directories from the root script to find "wren_modules".
import "foo"
// expect: ran foo module

View File

@ -0,0 +1,2 @@
// nontest
System.print("ran foo module")

View File

@ -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

View File

@ -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()

View File

@ -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')

View File

@ -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 = "<group>"; };
2940E98C2063EC020054503C /* resolution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = resolution.h; path = ../../test/api/resolution.h; sourceTree = "<group>"; };
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 = "<group>"; };
2940E9BA2066067D0054503C /* path_test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = path_test.c; path = ../../../test/unit/path_test.c; sourceTree = "<group>"; };
2940E9BD2066C3300054503C /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../../test/unit/main.c; sourceTree = "<group>"; };
2940E9BF2066C35E0054503C /* test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = test.c; path = ../../../test/unit/test.c; sourceTree = "<group>"; };
2940E9C02066C35E0054503C /* test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = test.h; path = ../../../test/unit/test.h; sourceTree = "<group>"; };
2949AA8B1C2F14F000B106BA /* get_variable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = get_variable.c; path = ../../test/api/get_variable.c; sourceTree = "<group>"; };
2949AA8C1C2F14F000B106BA /* get_variable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = get_variable.h; path = ../../test/api/get_variable.h; sourceTree = "<group>"; };
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 = "<group>"; };
2952CD1C1FA9941700985F5F /* path.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = path.h; path = ../../src/cli/path.h; sourceTree = "<group>"; };
296371B31AC713D000079FDA /* wren_opcodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = "<group>"; };
29703E57206DC7B7004004DC /* stat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stat.h; path = ../../src/cli/stat.h; sourceTree = "<group>"; };
29729F2E1BA70A620099CA20 /* io.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = io.c; path = ../../src/module/io.c; sourceTree = "<group>"; };
29729F301BA70A620099CA20 /* io.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = io.wren.inc; path = ../../src/module/io.wren.inc; sourceTree = "<group>"; };
2986F6D51ACF93BA00BCE26C /* wren_primitive.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_primitive.c; path = ../../src/vm/wren_primitive.c; sourceTree = "<group>"; };
@ -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 = "<group>"; };
29AD96601D0A57F800C4DFE7 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = error.h; path = ../../test/api/error.h; sourceTree = "<group>"; };
29B59F0320FC37B600767E48 /* path_test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = path_test.c; path = ../../test/unit/path_test.c; sourceTree = "<group>"; };
29B59F0420FC37B700767E48 /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../test/unit/main.c; sourceTree = "<group>"; };
29B59F0520FC37B700767E48 /* test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = test.c; path = ../../test/unit/test.c; sourceTree = "<group>"; };
29B59F0620FC37B700767E48 /* path_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = path_test.h; path = ../../test/unit/path_test.h; sourceTree = "<group>"; };
29B59F0720FC37B700767E48 /* test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = test.h; path = ../../test/unit/test.h; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
29C8A9311AB71FFF00DEC81D /* vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vm.c; path = ../../src/cli/vm.c; sourceTree = "<group>"; };
@ -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 = "<group>";
};
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 = "<group>";
};
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 = "<group>";
};
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 = "<group>";
};
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;
};