forked from Mirror/wren
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:
261
doc/notes/import syntax.md
Normal file
261
doc/notes/import syntax.md
Normal 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`).
|
||||
@ -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();
|
||||
}
|
||||
|
||||
144
src/cli/path.c
144
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)
|
||||
|
||||
@ -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
22
src/cli/stat.h
Normal 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
|
||||
222
src/cli/vm.c
222
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 "<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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()");
|
||||
|
||||
@ -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()");
|
||||
|
||||
@ -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'.
|
||||
}
|
||||
|
||||
@ -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'.
|
||||
|
||||
6
test/language/module/logical_dir/logical_dir.wren
Normal file
6
test/language/module/logical_dir/logical_dir.wren
Normal 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
|
||||
@ -0,0 +1,5 @@
|
||||
// nontest
|
||||
var Bar = "from bar"
|
||||
System.print("ran bar module")
|
||||
|
||||
import "foo/within_foo"
|
||||
@ -0,0 +1,5 @@
|
||||
// nontest
|
||||
var Foo = "from foo"
|
||||
System.print("ran foo module")
|
||||
|
||||
import "bar/within_bar"
|
||||
5
test/language/module/logical_short/logical_short.wren
Normal file
5
test/language/module/logical_short/logical_short.wren
Normal 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
|
||||
@ -0,0 +1,3 @@
|
||||
// nontest
|
||||
var Module = "from module"
|
||||
System.print("ran module")
|
||||
@ -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'.
|
||||
|
||||
@ -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'.
|
||||
|
||||
@ -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'.
|
||||
@ -0,0 +1,2 @@
|
||||
// nontest
|
||||
System.print("ran foo module")
|
||||
@ -0,0 +1,3 @@
|
||||
// Walk up parent directories from the root script to find "wren_modules".
|
||||
import "foo"
|
||||
// expect: ran foo module
|
||||
@ -0,0 +1,2 @@
|
||||
// nontest
|
||||
System.print("ran foo module")
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user