1
0
forked from Mirror/wren

Merge branch 'smarter-imports'

This commit is contained in:
Bob Nystrom
2018-07-16 07:00:08 -07:00
78 changed files with 1691 additions and 252 deletions

View File

@ -49,23 +49,23 @@ all: debug release
ci: ci_32 ci_64
ci_32:
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=c ARCH=32 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=c ARCH=32 vm cli api_test
$(V) ./util/test.py --suffix=d-32 $(suite)
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=cpp ARCH=32 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=cpp ARCH=32 vm cli api_test
$(V) ./util/test.py --suffix=d-cpp-32 $(suite)
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=c ARCH=32 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=c ARCH=32 vm cli api_test
$(V) ./util/test.py --suffix=-32 $(suite)
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=cpp ARCH=32 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=cpp ARCH=32 vm cli api_test
$(V) ./util/test.py --suffix=-cpp-32 $(suite)
ci_64:
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=c ARCH=64 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=c ARCH=64 vm cli api_test
$(V) ./util/test.py --suffix=d-64 $(suite)
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=cpp ARCH=64 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=debug LANG=cpp ARCH=64 vm cli api_test
$(V) ./util/test.py --suffix=d-cpp-64 $(suite)
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=c ARCH=64 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=c ARCH=64 vm cli api_test
$(V) ./util/test.py --suffix=-64 $(suite)
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=cpp ARCH=64 vm cli test
$(V) $(MAKE) -f util/wren.mk MODE=release LANG=cpp ARCH=64 vm cli api_test
$(V) ./util/test.py --suffix=-cpp-64 $(suite)
# Remove all build outputs and intermediate files. Does not remove downloaded
@ -77,13 +77,17 @@ clean:
# Run the tests against the debug build of Wren.
test: debug
$(V) $(MAKE) -f util/wren.mk MODE=debug test
$(V) $(MAKE) -f util/wren.mk MODE=debug api_test
$(V) ./util/test.py $(suite)
benchmark: release
$(V) $(MAKE) -f util/wren.mk test
$(V) $(MAKE) -f util/wren.mk api_test
$(V) ./util/benchmark.py -l wren $(suite)
unit_test:
$(V) $(MAKE) -f util/wren.mk MODE=debug unit_test
$(V) ./build/debug/test/unit_wrend
# Generate the Wren site.
docs:
mkdir -p build
@ -105,4 +109,4 @@ gh-pages: docs
amalgamation: src/include/wren.h src/vm/*.h src/vm/*.c
./util/generate_amalgamation.py > build/wren.c
.PHONY: all amalgamation builtin clean debug docs gh-pages release test vm watchdocs ci ci_32 ci_64
.PHONY: all amalgamation benchmark builtin clean debug docs gh-pages release test vm watchdocs ci ci_32 ci_64

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

@ -1,4 +1,4 @@
import "cthulu" for Cthulu
import "./cthulu" for Cthulu
class Lovecraft {
construct new() {}

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

310
src/cli/path.c Normal file
View File

@ -0,0 +1,310 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "path.h"
// The maximum number of components in a path. We can't normalize a path that
// contains more than this number of parts. The number here assumes a max path
// length of 4096, which is common on Linux, and then assumes each component is
// at least two characters, "/", and a single-letter directory name.
#define MAX_COMPONENTS 2048
typedef struct {
const char* start;
const char* end;
} Slice;
static void ensureCapacity(Path* path, size_t capacity)
{
// Capacity always needs to be one greater than the actual length to have
// room for the null byte, which is stored in the buffer, but not counted in
// the length. A zero-character path still needs a one-character array to
// store the '\0'.
capacity++;
if (path->capacity >= capacity) return;
// Grow by doubling in size.
size_t newCapacity = 16;
while (newCapacity < capacity) newCapacity *= 2;
path->chars = (char*)realloc(path->chars, newCapacity);
path->capacity = newCapacity;
}
static void appendSlice(Path* path, Slice slice)
{
size_t length = slice.end - slice.start;
ensureCapacity(path, path->length + length);
memcpy(path->chars + path->length, slice.start, length);
path->length += length;
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));
path->chars = (char*)malloc(1);
path->chars[0] = '\0';
path->length = 0;
path->capacity = 0;
pathAppendString(path, string);
return path;
}
void pathFree(Path* path)
{
if (path->chars) free(path->chars);
free(path);
}
void pathDirName(Path* path)
{
// Find the last path separator.
for (size_t i = path->length - 1; i < path->length; i--)
{
if (isSeparator(path->chars[i]))
{
path->length = i;
path->chars[i] = '\0';
return;
}
}
// If we got here, there was no separator so it must be a single component.
path->length = 0;
path->chars[0] = '\0';
}
void pathRemoveExtension(Path* path)
{
for (size_t i = path->length - 1; i < path->length; i--)
{
// If we hit a path separator before finding the extension, then the last
// component doesn't have one.
if (isSeparator(path->chars[i])) return;
if (path->chars[i] == '.')
{
path->length = i;
path->chars[path->length] = '\0';
}
}
}
void pathJoin(Path* path, const char* string)
{
if (path->length > 0 && !isSeparator(path->chars[path->length - 1]))
{
pathAppendChar(path, '/');
}
pathAppendString(path, string);
}
void pathAppendChar(Path* path, char c)
{
ensureCapacity(path, path->length + 1);
path->chars[path->length++] = c;
path->chars[path->length] = '\0';
}
void pathAppendString(Path* path, const char* string)
{
Slice slice;
slice.start = string;
slice.end = string + strlen(string);
appendSlice(path, slice);
}
void pathNormalize(Path* path)
{
// Split the path into components.
Slice components[MAX_COMPONENTS];
int numComponents = 0;
char* start = path->chars;
char* end = path->chars;
// Split into parts and handle "." and "..".
int leadingDoubles = 0;
for (;;)
{
if (*end == '\0' || isSeparator(*end))
{
// Add the current component.
if (start != end)
{
size_t length = end - start;
if (length == 1 && start[0] == '.')
{
// Skip "." components.
}
else if (length == 2 && start[0] == '.' && start[1] == '.')
{
// Walk out of directories on "..".
if (numComponents > 0)
{
// Discard the previous component.
numComponents--;
}
else
{
// Can't back out any further, so preserve the "..".
leadingDoubles++;
}
}
else
{
if (numComponents >= MAX_COMPONENTS)
{
fprintf(stderr, "Path cannot have more than %d path components.\n",
MAX_COMPONENTS);
exit(1);
}
components[numComponents].start = start;
components[numComponents].end = end;
numComponents++;
}
}
// Skip over separators.
while (*end != '\0' && isSeparator(*end)) end++;
start = end;
if (*end == '\0') break;
}
end++;
}
// 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;
Path* result = pathNew("");
size_t prefixLength = absolutePrefixLength(path->chars);
if (prefixLength > 0)
{
// 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 if (leadingDoubles > 0)
{
// Add any leading "..".
for (int i = 0; i < leadingDoubles; i++)
{
if (needsSeparator) pathAppendChar(result, '/');
pathAppendString(result, "..");
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++)
{
if (needsSeparator) pathAppendChar(result, '/');
appendSlice(result, components[i]);
needsSeparator = true;
}
if (result->length == 0) pathAppendChar(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)
{
char* string = (char*)malloc(path->length + 1);
memcpy(string, path->chars, path->length);
string[path->length] = '\0';
return string;
}

65
src/cli/path.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef path_h
#define path_h
// Path manipulation functions.
typedef struct
{
// Dynamically allocated array of characters.
char* chars;
// The number of characters currently in use in [chars], not including the
// null terminator.
size_t length;
// Size of the allocated [chars] buffer.
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);
// Releases the method associated with [path].
void pathFree(Path* path);
// Strips off the last component of the path name.
void pathDirName(Path* path);
// Strips off the file extension from the last component of the path.
void pathRemoveExtension(Path* path);
// Appends [string] to [path].
void pathJoin(Path* path, const char* string);
// Appends [c] to the path, growing the buffer if needed.
void pathAppendChar(Path* path, char c);
// Appends [string] to the path, growing the buffer if needed.
void pathAppendString(Path* path, const char* string);
// Simplifies the path string as much as possible.
//
// Applies and removes any "." or ".." components, collapses redundant "/"
// 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);
#endif

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

@ -0,0 +1,32 @@
#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.
#ifndef S_IRUSR
#define S_IRUSR _S_IREAD
#endif
#ifndef S_IWUSR
#define S_IWUSR _S_IWRITE
#endif
#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
// Not supported on Windows.
#define O_SYNC 0
#endif
#endif

View File

@ -3,7 +3,9 @@
#include "io.h"
#include "modules.h"
#include "path.h"
#include "scheduler.h"
#include "stat.h"
#include "vm.h"
// The single VM instance that the CLI uses.
@ -15,7 +17,10 @@ static WrenForeignMethodFn afterLoadFn = NULL;
static uv_loop_t* loop;
static char const* rootDirectory = NULL;
// 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;
@ -44,7 +49,7 @@ static char* readFile(const char* path)
}
// Read the entire file.
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
size_t bytesRead = fread(buffer, 1, fileSize, file);
if (bytesRead < fileSize)
{
fprintf(stderr, "Could not read file \"%s\".\n", path);
@ -58,25 +63,99 @@ static char* readFile(const char* path)
return buffer;
}
// Converts the module [name] to a file path.
static char* wrenFilePath(const char* name)
static bool isDirectory(Path* path)
{
// The module path is relative to the root directory and with ".wren".
size_t rootLength = rootDirectory == NULL ? 0 : strlen(rootDirectory);
size_t nameLength = strlen(name);
size_t pathLength = rootLength + nameLength + 5;
char* path = (char*)malloc(pathLength + 1);
uv_fs_t request;
uv_fs_stat(loop, &request, path->chars, NULL);
// TODO: Check request.result value?
if (rootDirectory != NULL)
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 (;;)
{
memcpy(path, rootDirectory, rootLength);
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;
}
memcpy(path + rootLength, name, nameLength);
memcpy(path + rootLength + nameLength, ".wren", 5);
path[pathLength] = '\0';
pathFree(searchDirectory);
}
// Applies the CLI's import resolution policy. The rules are:
//
// * 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".
static const char* resolveModule(WrenVM* vm, const char* importer,
const char* module)
{
// Logical import strings are used as-is and need no resolution.
if (pathType(module) == PATH_TYPE_SIMPLE) return module;
return path;
// Get the directory containing the importing module.
Path* path = pathNew(importer);
pathDirName(path);
// 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
@ -86,32 +165,43 @@ static char* wrenFilePath(const char* name)
// module was found but could not be read.
static char* readModule(WrenVM* vm, const char* module)
{
char* source = readBuiltInModule(module);
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.
pathAppendString(filePath, ".wren");
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;
// First try to load the module with a ".wren" extension.
char* modulePath = wrenFilePath(module);
char* moduleContents = readFile(modulePath);
free(modulePath);
if (moduleContents != NULL) return moduleContents;
// If no contents could be loaded treat the module name as specifying a
// directory and try to load the "module.wren" file in the directory.
size_t moduleLength = strlen(module);
size_t moduleDirLength = moduleLength + 7;
char* moduleDir = (char*)malloc(moduleDirLength + 1);
memcpy(moduleDir, module, moduleLength);
memcpy(moduleDir + moduleLength, "/module", 7);
moduleDir[moduleDirLength] = '\0';
char* moduleDirPath = wrenFilePath(moduleDir);
free(moduleDir);
moduleContents = readFile(moduleDirPath);
free(moduleDirPath);
return moduleContents;
// Otherwise, see if it's a built-in module.
return readBuiltInModule(module);
}
// Binds foreign methods declared in either built in modules, or the injected
@ -179,6 +269,7 @@ static void initVM()
config.bindForeignMethodFn = bindForeignMethod;
config.bindForeignClassFn = bindForeignClass;
config.resolveModuleFn = resolveModule;
config.loadModuleFn = readModule;
config.writeFn = write;
config.errorFn = reportError;
@ -203,22 +294,12 @@ static void freeVM()
wrenFreeVM(vm);
uv_tty_reset_mode();
if (wrenModulesDirectory != NULL) pathFree(wrenModulesDirectory);
}
void runFile(const char* path)
WrenInterpretResult runFile(const char* path)
{
// Use the directory where the file is as the root to resolve imports
// relative to.
char* root = NULL;
const char* lastSlash = strrchr(path, '/');
if (lastSlash != NULL)
{
root = (char*)malloc(lastSlash - path + 2);
memcpy(root, path, lastSlash - path + 1);
root[lastSlash - path + 1] = '\0';
rootDirectory = root;
}
char* source = readFile(path);
if (source == NULL)
{
@ -226,9 +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(module->chars);
pathDirName(directory);
rootDirectory = pathToString(directory);
pathFree(directory);
initVM();
WrenInterpretResult result = wrenInterpret(vm, source);
WrenInterpretResult result = wrenInterpret(vm, module->chars, source);
if (afterLoadFn != NULL) afterLoadFn(vm);
@ -240,29 +348,30 @@ void runFile(const char* path)
freeVM();
free(source);
free(root);
free(rootDirectory);
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, "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()
@ -275,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

@ -58,16 +58,21 @@ typedef void (*WrenForeignMethodFn)(WrenVM* vm);
// collection.
typedef void (*WrenFinalizerFn)(void* data);
// Gives the host a chance to canonicalize the imported module name,
// potentially taking into account the (previously resolved) name of the module
// that contains the import. Typically, this is used to implement relative
// imports.
typedef const char* (*WrenResolveModuleFn)(WrenVM* vm,
const char* importer, const char* name);
// Loads and returns the source code for the module [name].
typedef char* (*WrenLoadModuleFn)(WrenVM* vm, const char* name);
// Returns a pointer to a foreign method on [className] in [module] with
// [signature].
typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature);
const char* module, const char* className, bool isStatic,
const char* signature);
// Displays a string of text to the user.
typedef void (*WrenWriteFn)(WrenVM* vm, const char* text);
@ -87,14 +92,15 @@ typedef enum
// Reports an error to the user.
//
// An error detected during compile time is reported by calling this once with
// `WREN_ERROR_COMPILE`, the name of the module and line where the error occurs,
// and the compiler's error message.
// [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line]
// where the error occurs, and the compiler's error [message].
//
// A runtime error is reported by calling this once with `WREN_ERROR_RUNTIME`,
// no module or line, and the runtime error's message. After that, a series of
// `WREN_ERROR_STACK_TRACE` calls are made for each line in the stack trace.
// Each of those has the module and line where the method or function is
// defined and [message] is the name of the method or function.
// A runtime error is reported by calling this once with [type]
// `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's
// [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are
// made for each line in the stack trace. Each of those has the resolved
// [module] and [line] where the method or function is defined and [message] is
// the name of the method or function.
typedef void (*WrenErrorFn)(
WrenVM* vm, WrenErrorType type, const char* module, int line,
const char* message);
@ -115,7 +121,7 @@ typedef struct
} WrenForeignClassMethods;
// Returns a pair of pointers to the foreign methods used to allocate and
// finalize the data for instances of [className] in [module].
// finalize the data for instances of [className] in resolved [module].
typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(
WrenVM* vm, const char* module, const char* className);
@ -126,6 +132,32 @@ typedef struct
// If `NULL`, defaults to a built-in function that uses `realloc` and `free`.
WrenReallocateFn reallocateFn;
// The callback Wren uses to resolve a module name.
//
// Some host applications may wish to support "relative" imports, where the
// meaning of an import string depends on the module that contains it. To
// support that without baking any policy into Wren itself, the VM gives the
// host a chance to resolve an import string.
//
// Before an import is loaded, it calls this, passing in the name of the
// module that contains the import and the import string. The host app can
// look at both of those and produce a new "canonical" string that uniquely
// identifies the module. This string is then used as the name of the module
// going forward. It is what is passed to [loadModuleFn], how duplicate
// imports of the same module are detected, and how the module is reported in
// stack traces.
//
// If you leave this function NULL, then the original import string is
// treated as the resolved string.
//
// If an import cannot be resolved by the embedder, it should return NULL and
// Wren will report that as a runtime error.
//
// Wren will take ownership of the string you return and free it for you, so
// it should be allocated using the same allocation function you provide
// above.
WrenResolveModuleFn resolveModuleFn;
// The callback Wren uses to load a module.
//
// Since Wren does not talk directly to the file system, it relies on the
@ -200,7 +232,8 @@ typedef struct
//
// For example, say that this is 50. After a garbage collection, when there
// are 400 bytes of memory still in use, the next collection will be triggered
// after a total of 600 bytes are allocated (including the 400 already in use.)
// after a total of 600 bytes are allocated (including the 400 already in
// use.)
//
// Setting this to a smaller number wastes less memory, but triggers more
// frequent garbage collections.
@ -255,8 +288,10 @@ void wrenFreeVM(WrenVM* vm);
// Immediately run the garbage collector to free unused memory.
void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm].
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source);
// Runs [source], a string of Wren source code in a new fiber in [vm] in the
// context of resolved [module].
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
const char* source);
// Creates a handle that can be used to invoke a method with [signature] on
// using a receiver and arguments that are set up on the stack.
@ -435,10 +470,11 @@ void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
// an element, use `-1` for the index.
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
// Looks up the top level variable with [name] in [module] and stores it in
// [slot].
// Looks up the top level variable with [name] in resolved [module] and stores
// it in [slot].
void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
int slot);
// Sets the current fiber to be aborted, and uses the value in [slot] as the
// runtime error object.
void wrenAbortFiber(WrenVM* vm, int slot);

View File

@ -4,38 +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.
#ifndef S_IRUSR
#define S_IRUSR _S_IREAD
#endif
#ifndef S_IWUSR
#define S_IWUSR _S_IWRITE
#endif
#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
// Not supported on Windows.
#define O_SYNC 0
#endif
typedef struct sFileRequestData
{
WrenHandle* fiber;

View File

@ -14,9 +14,17 @@ void metaCompile(WrenVM* vm)
bool printErrors = wrenGetSlotBool(vm, 3);
// TODO: Allow passing in module?
ObjClosure* closure = wrenCompileSource(vm, "main", source,
isExpression, printErrors);
// Look up the module surrounding the callsite. This is brittle. The -2 walks
// up the callstack assuming that the meta module has one level of
// indirection before hitting the user's code. Any change to meta may require
// this constant to be tweaked.
ObjFiber* currentFiber = vm->fiber;
ObjFn* fn = currentFiber->frames[currentFiber->numFrames - 2].closure->fn;
ObjString* module = fn->module->name;
ObjClosure* closure = wrenCompileSource(vm, module->value, source,
isExpression, printErrors);
// Return the result. We can't use the public API for this since we have a
// bare ObjClosure*.
if (closure == NULL)
@ -29,7 +37,8 @@ void metaCompile(WrenVM* vm)
}
}
void metaGetModuleVariables(WrenVM* vm) {
void metaGetModuleVariables(WrenVM* vm)
{
wrenEnsureSlots(vm, 3);
Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]);

View File

@ -1609,9 +1609,7 @@ static void patchJump(Compiler* compiler, int offset)
static bool finishBlock(Compiler* compiler)
{
// Empty blocks do nothing.
if (match(compiler, TOKEN_RIGHT_BRACE)) {
return false;
}
if (match(compiler, TOKEN_RIGHT_BRACE)) return false;
// If there's no line after the "{", it's a single-expression body.
if (!matchLine(compiler))
@ -1622,9 +1620,7 @@ static bool finishBlock(Compiler* compiler)
}
// Empty blocks (with just a newline inside) do nothing.
if (match(compiler, TOKEN_RIGHT_BRACE)) {
return false;
}
if (match(compiler, TOKEN_RIGHT_BRACE)) return false;
// Compile the definition list.
do
@ -2723,6 +2719,7 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
case CODE_CONSTRUCT:
case CODE_FOREIGN_CONSTRUCT:
case CODE_FOREIGN_CLASS:
case CODE_END_MODULE:
return 0;
case CODE_LOAD_LOCAL:
@ -3325,9 +3322,13 @@ static void classDefinition(Compiler* compiler, bool isForeign)
// import "foo" for Bar, Baz
//
// We compile a single IMPORT_MODULE "foo" instruction to load the module
// itself. Then, for each imported name, we declare a variable and them emit a
// IMPORT_VARIABLE instruction to load the variable from the other module and
// assign it to the new variable in this one.
// itself. When that finishes executing the imported module, it leaves the
// ObjModule in vm->lastModule. Then, for Bar and Baz, we:
//
// * Declare a variable in the current scope with that name.
// * Emit an IMPORT_VARIABLE instruction to load the variable's value from the
// other module.
// * Compile the code to store that value in the variable in this scope.
static void import(Compiler* compiler)
{
ignoreNewlines(compiler);
@ -3337,9 +3338,9 @@ static void import(Compiler* compiler)
// Load the module.
emitShortArg(compiler, CODE_IMPORT_MODULE, moduleConstant);
// Discard the unused result value from calling the module's fiber.
// Discard the unused result value from calling the module body's closure.
emitOp(compiler, CODE_POP);
// The for clause is optional.
if (!match(compiler, TOKEN_FOR)) return;
@ -3348,16 +3349,15 @@ static void import(Compiler* compiler)
{
ignoreNewlines(compiler);
int slot = declareNamedVariable(compiler);
// Define a string constant for the variable name.
int variableConstant = addConstant(compiler,
wrenNewStringLength(compiler->parser->vm,
compiler->parser->previous.start,
compiler->parser->previous.length));
// Load the variable from the other module.
emitShortArg(compiler, CODE_IMPORT_VARIABLE, moduleConstant);
emitShort(compiler, variableConstant);
emitShortArg(compiler, CODE_IMPORT_VARIABLE, variableConstant);
// Store the result in the variable here.
defineVariable(compiler, slot);
@ -3474,7 +3474,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
}
}
emitOp(&compiler, CODE_NULL);
emitOp(&compiler, CODE_END_MODULE);
}
emitOp(&compiler, CODE_RETURN);

View File

@ -1173,7 +1173,7 @@ void wrenInitializeCore(WrenVM* vm)
// '---------' '-------------------' -'
// The rest of the classes can now be defined normally.
wrenInterpretInModule(vm, NULL, coreModuleSource);
wrenInterpret(vm, NULL, coreModuleSource);
vm->boolClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Bool"));
PRIMITIVE(vm->boolClass, "toString", bool_toString);

View File

@ -313,6 +313,10 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
break;
}
case CODE_END_MODULE:
printf("END_MODULE\n");
break;
case CODE_IMPORT_MODULE:
{
int name = READ_SHORT();
@ -324,16 +328,13 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
case CODE_IMPORT_VARIABLE:
{
int module = READ_SHORT();
int variable = READ_SHORT();
printf("%-16s %5d '", "IMPORT_VARIABLE", module);
wrenDumpValue(fn->constants.data[module]);
printf("' %5d '", variable);
printf("%-16s %5d '", "IMPORT_VARIABLE", variable);
wrenDumpValue(fn->constants.data[variable]);
printf("'\n");
break;
}
case CODE_END:
printf("END\n");
break;

View File

@ -190,6 +190,11 @@ OPCODE(METHOD_INSTANCE, -2)
// closure.
OPCODE(METHOD_STATIC, -2)
// This is executed at the end of the module's body. Pushes NULL onto the stack
// as the "return value" of the import statement and stores the module as the
// most recently imported one.
OPCODE(END_MODULE, 1)
// Import a module whose name is the string stored at [arg] in the constant
// table.
//
@ -198,9 +203,9 @@ OPCODE(METHOD_STATIC, -2)
// value when resuming a caller.)
OPCODE(IMPORT_MODULE, 1)
// Import a variable from a previously-imported module. The module's name is at
// [arg1] in the constant table and the variable name is at [arg2]. Pushes the
// loaded variable onto the stack.
// Import a variable from the most recently imported module. The name of the
// variable to import is at [arg] in the constant table. Pushes the loaded
// variable's value.
OPCODE(IMPORT_VARIABLE, 1)
// This pseudo-instruction indicates the end of the bytecode. It should

View File

@ -80,7 +80,8 @@ uint32_t calculateRange(WrenVM* vm, ObjRange* range, uint32_t* length,
// list[0..-1] and list[0...list.count] can be used to copy a list even when
// empty.
if (range->from == *length &&
range->to == (range->isInclusive ? -1.0 : (double)*length)) {
range->to == (range->isInclusive ? -1.0 : (double)*length))
{
*length = 0;
return 0;
}

View File

@ -38,6 +38,7 @@ static void* defaultReallocate(void* ptr, size_t newSize)
void wrenInitConfiguration(WrenConfiguration* config)
{
config->reallocateFn = defaultReallocate;
config->resolveModuleFn = NULL;
config->loadModuleFn = NULL;
config->bindForeignMethodFn = NULL;
config->bindForeignClassFn = NULL;
@ -696,11 +697,45 @@ void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
finalizer(foreign->data);
}
Value wrenImportModule(WrenVM* vm, Value name)
// Let the host resolve an imported module name if it wants to.
static Value resolveModule(WrenVM* vm, Value name)
{
// If the module is already loaded, we don't need to do anything.
if (!IS_UNDEFINED(wrenMapGet(vm->modules, name))) return NULL_VAL;
// If the host doesn't care to resolve, leave the name alone.
if (vm->config.resolveModuleFn == NULL) return name;
ObjFiber* fiber = vm->fiber;
ObjFn* fn = fiber->frames[fiber->numFrames - 1].closure->fn;
ObjString* importer = fn->module->name;
const char* resolved = vm->config.resolveModuleFn(vm, importer->value,
AS_CSTRING(name));
if (resolved == NULL)
{
vm->fiber->error = wrenStringFormat(vm,
"Could not resolve module '@' imported from '@'.",
name, OBJ_VAL(importer));
return NULL_VAL;
}
// If they resolved to the exact same string, we don't need to copy it.
if (resolved == AS_CSTRING(name)) return name;
// Copy the string into a Wren String object.
name = wrenNewString(vm, resolved);
DEALLOCATE(vm, (char*)resolved);
return name;
}
static Value importModule(WrenVM* vm, Value name)
{
name = resolveModule(vm, name);
// If the module is already loaded, we don't need to do anything.
Value existing = wrenMapGet(vm->modules, name);
if (!IS_UNDEFINED(existing)) return existing;
wrenPushRoot(vm, AS_OBJ(name));
const char* source = NULL;
bool allocatedSource = true;
@ -729,6 +764,7 @@ Value wrenImportModule(WrenVM* vm, Value name)
if (source == NULL)
{
vm->fiber->error = wrenStringFormat(vm, "Could not load module '@'.", name);
wrenPopRoot(vm); // name.
return NULL_VAL;
}
@ -743,13 +779,36 @@ Value wrenImportModule(WrenVM* vm, Value name)
{
vm->fiber->error = wrenStringFormat(vm,
"Could not compile module '@'.", name);
wrenPopRoot(vm); // name.
return NULL_VAL;
}
wrenPopRoot(vm); // name.
// Return the closure that executes the module.
return OBJ_VAL(moduleClosure);
}
static Value getModuleVariable(WrenVM* vm, ObjModule* module,
Value variableName)
{
ObjString* variable = AS_STRING(variableName);
uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames,
variable->value,
variable->length);
// It's a runtime error if the imported variable does not exist.
if (variableEntry != UINT32_MAX)
{
return module->variables.data[variableEntry];
}
vm->fiber->error = wrenStringFormat(vm,
"Could not find a variable named '@' in module '@'.",
variableName, OBJ_VAL(module->name));
return NULL_VAL;
}
// The main bytecode interpreter loop. This is where the magic happens. It is
// also, as you can imagine, highly performance critical. Returns `true` if the
// fiber completed without error.
@ -1247,18 +1306,22 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
DISPATCH();
}
CASE_CODE(END_MODULE):
{
vm->lastModule = fn->module;
PUSH(NULL_VAL);
DISPATCH();
}
CASE_CODE(IMPORT_MODULE):
{
Value name = fn->constants.data[READ_SHORT()];
// Make a slot on the stack for the module's fiber to place the return
// value. It will be popped after this fiber is resumed. Store the
// imported module's closure in the slot in case a GC happens when
// invoking the closure.
PUSH(wrenImportModule(vm, name));
PUSH(importModule(vm, fn->constants.data[READ_SHORT()]));
if (!IS_NULL(fiber->error)) RUNTIME_ERROR();
// If we get a closure, call it to execute the module body.
if (IS_CLOSURE(PEEK()))
{
@ -1267,16 +1330,21 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
callFunction(vm, fiber, closure, 1);
LOAD_FRAME();
}
else
{
// The module has already been loaded. Remember it so we can import
// variables from it if needed.
vm->lastModule = AS_MODULE(PEEK());
}
DISPATCH();
}
CASE_CODE(IMPORT_VARIABLE):
{
Value module = fn->constants.data[READ_SHORT()];
Value variable = fn->constants.data[READ_SHORT()];
Value result = wrenGetModuleVariable(vm, module, variable);
ASSERT(vm->lastModule != NULL, "Should have already imported module.");
Value result = getModuleVariable(vm, vm->lastModule, variable);
if (!IS_NULL(fiber->error)) RUNTIME_ERROR();
PUSH(result);
@ -1407,13 +1475,8 @@ void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle)
DEALLOCATE(vm, handle);
}
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source)
{
return wrenInterpretInModule(vm, "main", source);
}
WrenInterpretResult wrenInterpretInModule(WrenVM* vm, const char* module,
const char* source)
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* module,
const char* source)
{
ObjClosure* closure = wrenCompileSource(vm, module, source, false, true);
if (closure == NULL) return WREN_RESULT_COMPILE_ERROR;
@ -1452,21 +1515,7 @@ Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName)
return NULL_VAL;
}
ObjString* variable = AS_STRING(variableName);
uint32_t variableEntry = wrenSymbolTableFind(&module->variableNames,
variable->value,
variable->length);
// It's a runtime error if the imported variable does not exist.
if (variableEntry != UINT32_MAX)
{
return module->variables.data[variableEntry];
}
vm->fiber->error = wrenStringFormat(vm,
"Could not find a variable named '@' in module '@'.",
variableName, moduleName);
return NULL_VAL;
return getModuleVariable(vm, module, variableName);
}
Value wrenFindVariable(WrenVM* vm, ObjModule* module, const char* name)

View File

@ -49,6 +49,12 @@ struct WrenVM
// whose key is null) for the module's name and the value is the ObjModule
// for the module.
ObjMap* modules;
// The most recently imported module. More specifically, the module whose
// code has most recently finished executing.
//
// Not treated like a GC root since the module is already in [modules].
ObjModule* lastModule;
// Memory management data:

View File

@ -30,7 +30,7 @@ static void call(WrenVM* vm)
wrenInitConfiguration(&config);
WrenVM* otherVM = wrenNewVM(&config);
wrenInterpret(otherVM, testScript);
wrenInterpret(otherVM, "main", testScript);
WrenHandle* method = wrenMakeCallHandle(otherVM, "method(_,_,_,_)");

View File

@ -6,7 +6,7 @@
void callRunTests(WrenVM* vm)
{
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "main", "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, "main", "A", 0);
wrenGetVariable(vm, "./test/api/get_variable", "A", 0);
}
static void afterDefined(WrenVM* vm)
{
wrenGetVariable(vm, "main", "A", 0);
wrenGetVariable(vm, "./test/api/get_variable", "A", 0);
}
static void afterAssigned(WrenVM* vm)
{
wrenGetVariable(vm, "main", "A", 0);
wrenGetVariable(vm, "./test/api/get_variable", "A", 0);
}
static void otherSlot(WrenVM* vm)
{
wrenEnsureSlots(vm, 3);
wrenGetVariable(vm, "main", "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, "get_variable_module", "Variable", 0);
wrenGetVariable(vm, "./test/api/get_variable_module", "Variable", 0);
}
WrenForeignMethodFn getVariableBindMethod(const char* signature)

View File

@ -1,4 +1,4 @@
import "get_variable_module"
import "./get_variable_module"
class GetVariable {
foreign static beforeDefined()

View File

@ -14,6 +14,7 @@
#include "new_vm.h"
#include "reset_stack_after_call_abort.h"
#include "reset_stack_after_foreign_construct.h"
#include "resolution.h"
#include "slots.h"
#include "user_data.h"
@ -23,8 +24,8 @@ const char* testName;
static WrenForeignMethodFn bindForeignMethod(
WrenVM* vm, const char* module, const char* className,
bool isStatic, const char* signature)
{
if (strcmp(module, "main") != 0) return NULL;
{
if (strncmp(module, "./test/", 7) != 0) return NULL;
// For convenience, concatenate all of the method qualifiers into a single
// signature string.
@ -58,6 +59,9 @@ static WrenForeignMethodFn bindForeignMethod(
method = newVMBindMethod(fullName);
if (method != NULL) return method;
method = resolutionBindMethod(fullName);
if (method != NULL) return method;
method = slotsBindMethod(fullName);
if (method != NULL) return method;
@ -74,7 +78,7 @@ static WrenForeignClassMethods bindForeignClass(
WrenVM* vm, const char* module, const char* className)
{
WrenForeignClassMethods methods = { NULL, NULL };
if (strcmp(module, "main") != 0) return methods;
if (strncmp(module, "./test/", 7) != 0) return methods;
foreignClassBindClass(className, &methods);
if (methods.allocate != NULL) return methods;
@ -91,7 +95,8 @@ static WrenForeignClassMethods bindForeignClass(
return methods;
}
static void afterLoad(WrenVM* vm) {
static void afterLoad(WrenVM* vm)
{
if (strstr(testName, "/call.wren") != NULL)
{
callRunTests(vm);

View File

@ -7,7 +7,7 @@ static void nullConfig(WrenVM* vm)
WrenVM* otherVM = wrenNewVM(NULL);
// We should be able to execute code.
WrenInterpretResult result = wrenInterpret(otherVM, "1 + 2");
WrenInterpretResult result = wrenInterpret(otherVM, "main", "1 + 2");
wrenSetSlotBool(vm, 0, result == WREN_RESULT_SUCCESS);
wrenFreeVM(otherVM);

View File

@ -6,7 +6,7 @@
void resetStackAfterCallAbortRunTests(WrenVM* vm)
{
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "main", "Test", 0);
wrenGetVariable(vm, "./test/api/reset_stack_after_call_abort", "Test", 0);
WrenHandle* testClass = wrenGetSlotHandle(vm, 0);
WrenHandle* abortFiber = wrenMakeCallHandle(vm, "abortFiber()");
@ -25,4 +25,4 @@ void resetStackAfterCallAbortRunTests(WrenVM* vm)
wrenReleaseHandle(vm, testClass);
wrenReleaseHandle(vm, abortFiber);
wrenReleaseHandle(vm, afterConstruct);
}
}

View File

@ -22,7 +22,8 @@ void resetStackAfterForeignConstructBindClass(
void resetStackAfterForeignConstructRunTests(WrenVM* vm)
{
wrenEnsureSlots(vm, 1);
wrenGetVariable(vm, "main", "Test", 0);
wrenGetVariable(vm,
"./test/api/reset_stack_after_foreign_construct", "Test", 0);
WrenHandle* testClass = wrenGetSlotHandle(vm, 0);
WrenHandle* callConstruct = wrenMakeCallHandle(vm, "callConstruct()");
@ -41,4 +42,4 @@ void resetStackAfterForeignConstructRunTests(WrenVM* vm)
wrenReleaseHandle(vm, testClass);
wrenReleaseHandle(vm, callConstruct);
wrenReleaseHandle(vm, afterConstruct);
}
}

149
test/api/resolution.c Normal file
View File

@ -0,0 +1,149 @@
#include <stdio.h>
#include <string.h>
#include "resolution.h"
static void write(WrenVM* vm, const char* text)
{
printf("%s", text);
}
static void reportError(WrenVM* vm, WrenErrorType type,
const char* module, int line, const char* message)
{
if (type == WREN_ERROR_RUNTIME) printf("%s\n", message);
}
static char* loadModule(WrenVM* vm, const char* module)
{
printf("loading %s\n", module);
const char* source;
if (strcmp(module, "main/baz/bang") == 0)
{
source = "import \"foo|bar\"";
}
else
{
source = "System.print(\"ok\")";
}
char* string = malloc(strlen(source) + 1);
strcpy(string, source);
return string;
}
static void runTestVM(WrenVM* vm, WrenConfiguration* configuration,
const char* source)
{
configuration->writeFn = write;
configuration->errorFn = reportError;
configuration->loadModuleFn = loadModule;
WrenVM* otherVM = wrenNewVM(configuration);
// We should be able to execute code.
WrenInterpretResult result = wrenInterpret(otherVM, "main", source);
if (result != WREN_RESULT_SUCCESS)
{
wrenSetSlotString(vm, 0, "error");
}
else
{
wrenSetSlotString(vm, 0, "success");
}
wrenFreeVM(otherVM);
}
static void noResolver(WrenVM* vm)
{
WrenConfiguration configuration;
wrenInitConfiguration(&configuration);
// Should default to no resolution function.
if (configuration.resolveModuleFn != NULL)
{
wrenSetSlotString(vm, 0, "Did not have null resolve function.");
return;
}
runTestVM(vm, &configuration, "import \"foo/bar\"");
}
static const char* resolveToNull(WrenVM* vm, const char* importer,
const char* name)
{
return NULL;
}
static void returnsNull(WrenVM* vm)
{
WrenConfiguration configuration;
wrenInitConfiguration(&configuration);
configuration.resolveModuleFn = resolveToNull;
runTestVM(vm, &configuration, "import \"foo/bar\"");
}
static const char* resolveChange(WrenVM* vm, const char* importer,
const char* name)
{
// Concatenate importer and name.
size_t length = strlen(importer) + 1 + strlen(name) + 1;
char* result = malloc(length);
strcpy(result, importer);
strcat(result, "/");
strcat(result, name);
// Replace "|" with "/".
for (size_t i = 0; i < length; i++)
{
if (result[i] == '|') result[i] = '/';
}
return result;
}
static void changesString(WrenVM* vm)
{
WrenConfiguration configuration;
wrenInitConfiguration(&configuration);
configuration.resolveModuleFn = resolveChange;
runTestVM(vm, &configuration, "import \"foo|bar\"");
}
static void shared(WrenVM* vm)
{
WrenConfiguration configuration;
wrenInitConfiguration(&configuration);
configuration.resolveModuleFn = resolveChange;
runTestVM(vm, &configuration, "import \"foo|bar\"\nimport \"foo/bar\"");
}
static void importer(WrenVM* vm)
{
WrenConfiguration configuration;
wrenInitConfiguration(&configuration);
configuration.resolveModuleFn = resolveChange;
runTestVM(vm, &configuration, "import \"baz|bang\"");
}
WrenForeignMethodFn resolutionBindMethod(const char* signature)
{
if (strcmp(signature, "static Resolution.noResolver()") == 0) return noResolver;
if (strcmp(signature, "static Resolution.returnsNull()") == 0) return returnsNull;
if (strcmp(signature, "static Resolution.changesString()") == 0) return changesString;
if (strcmp(signature, "static Resolution.shared()") == 0) return shared;
if (strcmp(signature, "static Resolution.importer()") == 0) return importer;
return NULL;
}
void resolutionBindClass(const char* className, WrenForeignClassMethods* methods)
{
// methods->allocate = foreignClassAllocate;
}

4
test/api/resolution.h Normal file
View File

@ -0,0 +1,4 @@
#include "wren.h"
WrenForeignMethodFn resolutionBindMethod(const char* signature);
void resolutionBindClass(const char* className, WrenForeignClassMethods* methods);

39
test/api/resolution.wren Normal file
View File

@ -0,0 +1,39 @@
class Resolution {
foreign static noResolver()
foreign static returnsNull()
foreign static changesString()
foreign static shared()
foreign static importer()
}
// If no resolver function is configured, the default resolver just passes
// along the import string unchanged.
System.print(Resolution.noResolver())
// expect: loading foo/bar
// expect: ok
// expect: success
// If the resolver returns NULL, it's reported as an error.
System.print(Resolution.returnsNull())
// expect: Could not resolve module 'foo/bar' imported from 'main'.
// expect: error
// The resolver function can change the string.
System.print(Resolution.changesString())
// expect: loading main/foo/bar
// expect: ok
// expect: success
// Imports both "foo/bar" and "foo|bar", but only loads the module once because
// they resolve to the same module.
System.print(Resolution.shared())
// expect: loading main/foo/bar
// expect: ok
// expect: success
// The string passed as importer is the resolver string of the importing module.
System.print(Resolution.importer())
// expect: loading main/baz/bang
// expect: loading main/baz/bang/foo/bar
// expect: ok
// expect: success

View File

@ -1,7 +1,7 @@
var fiber = Fiber.new {
System.print("fiber 1")
import "yield_from_import_module"
import "./yield_from_import_module"
System.print("fiber 2")
}

View File

@ -1,3 +1,3 @@
class Foo {
foreign someUnknownMethod // expect runtime error: Could not find foreign method 'someUnknownMethod' for class Foo in module 'main'.
foreign someUnknownMethod // expect runtime error: Could not find foreign method 'someUnknownMethod' for class Foo in module './test/language/foreign/unknown_method'.
}

View File

@ -1,4 +1,4 @@
import "module" for Module, Other
import "./module" for Module, Other
System.print(Module) // expect: before

View File

@ -1,2 +1,2 @@
System.print("before") // expect: before
import "module" for Module // expect runtime error: Could not compile module 'module'.
import "./module" for Module // expect runtime error: Could not compile module './test/language/module/compile_error/module'.

View File

@ -3,7 +3,7 @@ System.print("start a")
var A = "a value"
System.print("a defined %(A)")
import "b" for B
import "./b" for B
System.print("a imported %(B)")
System.print("end a")

View File

@ -3,7 +3,7 @@ System.print("start b")
var B = "b value"
System.print("b defined %(B)")
import "a" for A
import "./a" for A
System.print("b imported %(A)")
System.print("end b")

View File

@ -1,4 +1,4 @@
import "a"
import "./a"
// Shared module should only run once:
// expect: start a

View File

@ -1,4 +1,4 @@
import "module"
import "./module"
// expect: Bool
// expect: Class
// expect: Fiber

View File

@ -1,7 +1,7 @@
var Module = "outer"
if (true) {
import "module" for Module
import "./module" for Module
// expect: ran module
System.print(Module) // expect: from 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 "module" NoString // expect error
import "./module" NoString // expect error

View File

@ -1,3 +1,3 @@
import "something" for Index
import "./something/module" for Index
System.print(Index) // expect: index

View File

@ -1,4 +1,4 @@
import "module" for Module1, Module2, Module3, Module4, Module5
import "./module" for Module1, Module2, Module3, Module4, Module5
// Only execute module body once:
// expect: ran module

View File

@ -1,2 +1,2 @@
var Collides
import "module" for Collides // expect error
import "./module" for Collides // expect error

View File

@ -1,9 +1,9 @@
import
"module"
"./module"
import "module" for
import "./module" for
A,

View File

@ -1,2 +1,2 @@
import "module"
import "./module"
// expect: ran module

View File

@ -0,0 +1,2 @@
// nontest
System.print("module_3")

View File

@ -0,0 +1,8 @@
import "./sub/module"
import "./sub/././///dir/module"
// expect: sub/module
// expect: sub/module_2
// expect: sub/dir/module
// expect: sub/dir/module_2
// expect: sub/module_3
// expect: module_3

View File

@ -0,0 +1,3 @@
// nontest
System.print("sub/dir/module")
import "./module_2"

View File

@ -0,0 +1,4 @@
// nontest
System.print("sub/dir/module_2")
import "../module_3"
import "../../module_3"

View File

@ -0,0 +1,3 @@
// nontest
System.print("sub/module")
import "./module_2"

View File

@ -0,0 +1,2 @@
// nontest
System.print("sub/module_2")

View File

@ -0,0 +1,2 @@
// nontest
System.print("sub/module_3")

View File

@ -1,5 +1,5 @@
// nontest
System.print("a")
import "shared" for Shared
import "./shared" for Shared
var A = "a %(Shared)"
System.print("a done")

View File

@ -1,5 +1,5 @@
// nontest
System.print("b")
import "shared" for Shared
import "./shared" for Shared
var B = "b %(Shared)"
System.print("b done")

View File

@ -1,5 +1,5 @@
import "a" for A
import "b" for B
import "./a" for A
import "./b" for B
// Shared module should only run once:
// expect: a

View File

@ -1,4 +1,4 @@
import "module" for Module
import "./module" for Module
// expect: ran module
System.print(Module) // expect: from module

View File

@ -1 +1 @@
import "does_not_exist" for DoesNotExist // expect runtime error: Could not load 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 '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("main")
var variables = Meta.getModuleVariables("./test/meta/get_module_variables")
// Includes implicitly imported core stuff.
System.print(variables.contains("Object")) // expect: true

9
test/unit/main.c Normal file
View File

@ -0,0 +1,9 @@
#include "path_test.h"
#include "test.h"
int main()
{
testPath();
return showTestResults();
}

104
test/unit/path_test.c Normal file
View File

@ -0,0 +1,104 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "path.h"
#include "test.h"
static void expectNormalize(const char* input, const char* expected)
{
Path* path = pathNew(input);
pathNormalize(path);
if (strcmp(path->chars, expected) != 0)
{
printf("FAIL %-30s Want %s\n", input, expected);
printf(" Got %s\n\n", path->chars);
fail();
}
else
{
#if SHOW_PASSES
printf("PASS %-30s -> %s\n", input, path->chars);
#endif
pass();
}
pathFree(path);
}
static void testNormalize()
{
// Simple cases.
expectNormalize("", ".");
expectNormalize(".", ".");
expectNormalize("..", "..");
expectNormalize("a", "a");
expectNormalize("/", "/");
// Collapses redundant separators.
expectNormalize("a/b/c", "a/b/c");
expectNormalize("a//b///c////d", "a/b/c/d");
// Eliminates "." parts, except one at the beginning.
expectNormalize("./", ".");
expectNormalize("/.", "/");
expectNormalize("/./", "/");
expectNormalize("./.", ".");
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");
// Eliminates ".." parts.
expectNormalize("..", "..");
expectNormalize("../", "..");
expectNormalize("../../..", "../../..");
expectNormalize("../../../", "../../..");
expectNormalize("/..", "/");
expectNormalize("/../../..", "/");
expectNormalize("/../../../a", "/a");
expectNormalize("a/..", ".");
expectNormalize("a/b/..", "a");
expectNormalize("a/../b", "b");
expectNormalize("a/./../b", "b");
expectNormalize("a/b/c/../../d/e/..", "a/d");
expectNormalize("a/b/../../../../c", "../../c");
// Does not walk before root on absolute paths.
expectNormalize("..", "..");
expectNormalize("../", "..");
expectNormalize("/..", "/");
expectNormalize("a/..", ".");
expectNormalize("../a", "../a");
expectNormalize("/../a", "/a");
expectNormalize("/../a", "/a");
expectNormalize("a/b/..", "a");
expectNormalize("../a/b/..", "../a");
expectNormalize("a/../b", "b");
expectNormalize("a/./../b", "b");
expectNormalize("a/b/c/../../d/e/..", "a/d");
expectNormalize("a/b/../../../../c", "../../c");
expectNormalize("a/b/c/../../..d/./.e/f././", "a/..d/.e/f.");
// Removes trailing separators.
expectNormalize("./", ".");
expectNormalize(".//", ".");
expectNormalize("a/", "a");
expectNormalize("a/b/", "a/b");
expectNormalize("a/b///", "a/b");
expectNormalize("foo/bar/baz", "foo/bar/baz");
expectNormalize("foo", "foo");
expectNormalize("foo/bar/", "foo/bar");
expectNormalize("./foo/././bar/././", "./foo/bar");
}
void testPath()
{
// TODO: Test other functions.
testNormalize();
}

1
test/unit/path_test.h Normal file
View File

@ -0,0 +1 @@
void testPath();

29
test/unit/test.c Normal file
View File

@ -0,0 +1,29 @@
#include <stdio.h>
#include "test.h"
int passes = 0;
int failures = 0;
void pass()
{
passes++;
}
void fail()
{
failures++;
}
int showTestResults()
{
if (failures > 0)
{
printf("%d out of %d tests failed. :(\n", failures, passes + failures);
return 1;
}
printf("All %d tests passed!\n", passes + failures);
return 0;
}

7
test/unit/test.h Normal file
View File

@ -0,0 +1,7 @@
// Set this to 1 to output passing tests.
#define SHOW_PASSES 0
void pass();
void fail();
int showTestResults();

View File

@ -40,7 +40,7 @@ import sys
WREN_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
WREN_BIN = os.path.join(WREN_DIR, 'bin')
BENCHMARK_DIR = os.path.join(WREN_DIR, 'test', 'benchmark')
BENCHMARK_DIR = os.path.join('test', 'benchmark')
# How many times to run a given benchmark.
NUM_TRIALS = 10
@ -154,7 +154,7 @@ def run_trial(benchmark, language):
# of the normal Wren build.
if benchmark[0].startswith("api_"):
executable_args = [
os.path.join(WREN_DIR, "build", "release", "test", "wren")
os.path.join(WREN_DIR, "build", "release", "test", "api_wren")
]
args = []

View File

@ -25,14 +25,14 @@ config_dir = ("debug" if is_debug else "release") + config
WREN_DIR = dirname(dirname(realpath(__file__)))
WREN_APP = join(WREN_DIR, 'bin', 'wren' + args.suffix)
TEST_APP = join(WREN_DIR, 'build', config_dir, 'test', 'wren' + args.suffix)
TEST_APP = join(WREN_DIR, 'build', config_dir, 'test', 'api_wren' + args.suffix)
EXPECT_PATTERN = re.compile(r'// expect: ?(.*)')
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'\[main 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

@ -31,8 +31,11 @@ MODULE_SOURCES := $(wildcard src/module/*.c)
VM_HEADERS := $(wildcard src/vm/*.h) $(wildcard src/vm/*.wren.inc)
VM_SOURCES := $(wildcard src/vm/*.c)
TEST_HEADERS := $(wildcard test/api/*.h)
TEST_SOURCES := $(wildcard test/api/*.c)
API_TEST_HEADERS := $(wildcard test/api/*.h)
API_TEST_SOURCES := $(wildcard test/api/*.c)
UNIT_TEST_HEADERS := $(wildcard test/unit/*.h)
UNIT_TEST_SOURCES := $(wildcard test/unit/*.c)
BUILD_DIR := build
@ -120,11 +123,12 @@ endif
CFLAGS := $(C_OPTIONS) $(C_WARNINGS)
OPT_OBJECTS := $(addprefix $(BUILD_DIR)/optional/, $(notdir $(OPT_SOURCES:.c=.o)))
CLI_OBJECTS := $(addprefix $(BUILD_DIR)/cli/, $(notdir $(CLI_SOURCES:.c=.o)))
MODULE_OBJECTS := $(addprefix $(BUILD_DIR)/module/, $(notdir $(MODULE_SOURCES:.c=.o)))
VM_OBJECTS := $(addprefix $(BUILD_DIR)/vm/, $(notdir $(VM_SOURCES:.c=.o)))
TEST_OBJECTS := $(patsubst test/api/%.c, $(BUILD_DIR)/test/%.o, $(TEST_SOURCES))
OPT_OBJECTS := $(addprefix $(BUILD_DIR)/optional/, $(notdir $(OPT_SOURCES:.c=.o)))
CLI_OBJECTS := $(addprefix $(BUILD_DIR)/cli/, $(notdir $(CLI_SOURCES:.c=.o)))
MODULE_OBJECTS := $(addprefix $(BUILD_DIR)/module/, $(notdir $(MODULE_SOURCES:.c=.o)))
VM_OBJECTS := $(addprefix $(BUILD_DIR)/vm/, $(notdir $(VM_SOURCES:.c=.o)))
API_TEST_OBJECTS := $(patsubst test/api/%.c, $(BUILD_DIR)/test/api/%.o, $(API_TEST_SOURCES))
UNIT_TEST_OBJECTS := $(patsubst test/unit/%.c, $(BUILD_DIR)/test/unit/%.o, $(UNIT_TEST_SOURCES))
LIBUV_DIR := deps/libuv
LIBUV := build/libuv$(LIBUV_ARCH).a
@ -152,7 +156,10 @@ static: lib/lib$(WREN).a
cli: bin/$(WREN)
# Builds the API test executable.
test: $(BUILD_DIR)/test/$(WREN)
api_test: $(BUILD_DIR)/test/api_$(WREN)
# Builds the unit test executable.
unit_test: $(BUILD_DIR)/test/unit_$(WREN)
# Command-line interpreter.
bin/$(WREN): $(OPT_OBJECTS) $(CLI_OBJECTS) $(MODULE_OBJECTS) $(VM_OBJECTS) \
@ -173,13 +180,22 @@ lib/lib$(WREN).$(SHARED_EXT): $(OPT_OBJECTS) $(VM_OBJECTS)
$(V) mkdir -p lib
$(V) $(CC) $(CFLAGS) -shared $(SHARED_LIB_FLAGS) -o $@ $^
# Test executable.
$(BUILD_DIR)/test/$(WREN): $(OPT_OBJECTS) $(MODULE_OBJECTS) $(TEST_OBJECTS) \
$(VM_OBJECTS) $(BUILD_DIR)/cli/modules.o $(BUILD_DIR)/cli/vm.o $(LIBUV)
# API test executable.
$(BUILD_DIR)/test/api_$(WREN): $(OPT_OBJECTS) $(MODULE_OBJECTS) $(API_TEST_OBJECTS) \
$(VM_OBJECTS) $(BUILD_DIR)/cli/modules.o $(BUILD_DIR)/cli/vm.o \
$(BUILD_DIR)/cli/path.o $(LIBUV)
@ printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS)"
$(V) mkdir -p $(BUILD_DIR)/test
$(V) mkdir -p $(BUILD_DIR)/test/api
$(V) $(CC) $(CFLAGS) $^ -o $@ -lm $(LIBUV_LIBS)
# Unit test executable.
$(BUILD_DIR)/test/unit_$(WREN): $(OPT_OBJECTS) $(MODULE_OBJECTS) $(UNIT_TEST_OBJECTS) \
$(VM_OBJECTS) $(BUILD_DIR)/cli/modules.o $(BUILD_DIR)/cli/vm.o \
$(BUILD_DIR)/cli/path.o $(LIBUV)
@ printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS)"
$(V) mkdir -p $(BUILD_DIR)/test/unit
$(V) $(CC) $(CFLAGS) $^ -o $@
# CLI object files.
$(BUILD_DIR)/cli/%.o: src/cli/%.c $(CLI_HEADERS) $(MODULE_HEADERS) \
$(VM_HEADERS) $(LIBUV)
@ -194,7 +210,7 @@ $(BUILD_DIR)/module/%.o: src/module/%.c $(CLI_HEADERS) $(MODULE_HEADERS) \
$(V) mkdir -p $(BUILD_DIR)/module
$(V) $(CC) -c $(CFLAGS) $(CLI_FLAGS) -o $@ $(FILE_FLAG) $<
# Aux object files.
# Optional object files.
$(BUILD_DIR)/optional/%.o: src/optional/%.c $(VM_HEADERS) $(OPT_HEADERS)
@ printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)"
$(V) mkdir -p $(BUILD_DIR)/optional
@ -206,9 +222,16 @@ $(BUILD_DIR)/vm/%.o: src/vm/%.c $(VM_HEADERS)
$(V) mkdir -p $(BUILD_DIR)/vm
$(V) $(CC) -c $(CFLAGS) -Isrc/include -Isrc/optional -Isrc/vm -o $@ $(FILE_FLAG) $<
# Test object files.
$(BUILD_DIR)/test/%.o: test/api/%.c $(OPT_HEADERS) $(MODULE_HEADERS) \
$(VM_HEADERS) $(TEST_HEADERS) $(LIBUV)
# API test object files.
$(BUILD_DIR)/test/api/%.o: test/api/%.c $(OPT_HEADERS) $(MODULE_HEADERS) \
$(VM_HEADERS) $(API_TEST_HEADERS) $(LIBUV)
@ printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)"
$(V) mkdir -p $(dir $@)
$(V) $(CC) -c $(CFLAGS) $(CLI_FLAGS) -o $@ $(FILE_FLAG) $<
# Unit test object files.
$(BUILD_DIR)/test/unit/%.o: test/unit/%.c $(OPT_HEADERS) $(MODULE_HEADERS) \
$(VM_HEADERS) $(UNIT_TEST_HEADERS) $(LIBUV)
@ printf "%10s %-30s %s\n" $(CC) $< "$(C_OPTIONS)"
$(V) mkdir -p $(dir $@)
$(V) $(CC) -c $(CFLAGS) $(CLI_FLAGS) -o $@ $(FILE_FLAG) $<
@ -231,4 +254,4 @@ src/module/%.wren.inc: src/module/%.wren util/wren_to_c_string.py
@ printf "%10s %-30s %s\n" str $<
$(V) ./util/wren_to_c_string.py $@ $<
.PHONY: all cli test vm
.PHONY: all api_test cli unit_test vm

View File

@ -23,9 +23,12 @@
293B25591CEFD8C7005D9537 /* repl.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 293B25561CEFD8C7005D9537 /* repl.wren.inc */; };
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 */; };
2940E9BC206607830054503C /* path.c in Sources */ = {isa = PBXBuildFile; fileRef = 2952CD1B1FA9941700985F5F /* path.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 */; };
2952CD1D1FA9941700985F5F /* path.c in Sources */ = {isa = PBXBuildFile; fileRef = 2952CD1B1FA9941700985F5F /* path.c */; };
29729F311BA70A620099CA20 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29729F2E1BA70A620099CA20 /* io.c */; };
29729F321BA70A620099CA20 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 29729F2E1BA70A620099CA20 /* io.c */; };
29729F331BA70A620099CA20 /* io.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29729F301BA70A620099CA20 /* io.wren.inc */; };
@ -41,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 */; };
@ -67,6 +73,15 @@
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
2940E9B4206605DE0054503C /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
29AB1F041816E3AD004B501E /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@ -115,11 +130,17 @@
293B25561CEFD8C7005D9537 /* repl.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = repl.wren.inc; path = ../../src/module/repl.wren.inc; sourceTree = "<group>"; };
293D46941BB43F9900200083 /* call.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = call.c; path = ../../test/api/call.c; sourceTree = "<group>"; };
293D46951BB43F9900200083 /* call.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = call.h; path = ../../test/api/call.h; sourceTree = "<group>"; };
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; };
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; };
29512C801B91F8EB008C10E6 /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = ../../build/libuv.a; sourceTree = "<group>"; };
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>"; };
@ -137,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>"; };
@ -161,6 +187,13 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
2940E9B2206605DE0054503C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
29AB1F031816E3AD004B501E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -230,6 +263,9 @@
29205C8E1AB4E5C90073018D /* main.c */,
291647C61BA5EC5E006142EE /* modules.h */,
291647C51BA5EC5E006142EE /* modules.c */,
2952CD1C1FA9941700985F5F /* path.h */,
2952CD1B1FA9941700985F5F /* path.c */,
29703E57206DC7B7004004DC /* stat.h */,
29C8A9321AB71FFF00DEC81D /* vm.h */,
29C8A9311AB71FFF00DEC81D /* vm.c */,
);
@ -253,6 +289,7 @@
29AF31EE1BD2E37F00AAD156 /* optional */,
29205CA01AB4E6470073018D /* vm */,
29D0099A1B7E394F000CE58C /* api_test */,
29B59F0B20FC37BD00767E48 /* unit_test */,
29512C801B91F8EB008C10E6 /* libuv.a */,
29AB1F071816E3AD004B501E /* Products */,
);
@ -263,6 +300,7 @@
children = (
29AB1F061816E3AD004B501E /* wren */,
29512C7F1B91F86E008C10E6 /* api_test */,
2940E9B8206605DE0054503C /* unit_test */,
);
name = Products;
sourceTree = "<group>";
@ -280,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 = (
@ -304,6 +354,8 @@
29D880651DC8ECF600025364 /* reset_stack_after_call_abort.h */,
29C80D581D73332A00493837 /* reset_stack_after_foreign_construct.c */,
29C80D591D73332A00493837 /* reset_stack_after_foreign_construct.h */,
2940E98B2063EC020054503C /* resolution.c */,
2940E98C2063EC020054503C /* resolution.h */,
29D009AA1B7E39A8000CE58C /* slots.c */,
29D009AB1B7E39A8000CE58C /* slots.h */,
29D24DB01E82C0A2006618CC /* user_data.c */,
@ -315,6 +367,23 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
2940E98F206605DE0054503C /* unit_test */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2940E9B5206605DE0054503C /* Build configuration list for PBXNativeTarget "unit_test" */;
buildPhases = (
2940E990206605DE0054503C /* Sources */,
2940E9B2206605DE0054503C /* Frameworks */,
2940E9B4206605DE0054503C /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = unit_test;
productName = api_test;
productReference = 2940E9B8206605DE0054503C /* unit_test */;
productType = "com.apple.product-type.tool";
};
29AB1F051816E3AD004B501E /* wren */ = {
isa = PBXNativeTarget;
buildConfigurationList = 29AB1F0F1816E3AD004B501E /* Build configuration list for PBXNativeTarget "wren" */;
@ -377,11 +446,23 @@
targets = (
29AB1F051816E3AD004B501E /* wren */,
29D0099E1B7E397D000CE58C /* api_test */,
2940E98F206605DE0054503C /* unit_test */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
2940E990206605DE0054503C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
29B59F0820FC37B700767E48 /* path_test.c in Sources */,
2940E9BC206607830054503C /* path.c in Sources */,
29B59F0920FC37B700767E48 /* main.c in Sources */,
29B59F0A20FC37B700767E48 /* test.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
29AB1F021816E3AD004B501E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -390,10 +471,12 @@
29205C991AB4E6430073018D /* wren_compiler.c in Sources */,
2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */,
291647C71BA5EC5E006142EE /* modules.c in Sources */,
2952CD1D1FA9941700985F5F /* path.c in Sources */,
29A4273A1BDBE435001E6E22 /* wren_opt_random.wren.inc in Sources */,
29205C9A1AB4E6430073018D /* wren_core.c in Sources */,
2901D7641B74F4050083A2C8 /* timer.c in Sources */,
29729F331BA70A620099CA20 /* io.wren.inc in Sources */,
2940E98D2063EC030054503C /* resolution.c in Sources */,
29C8A9331AB71FFF00DEC81D /* vm.c in Sources */,
291647C41BA5EA45006142EE /* scheduler.c in Sources */,
29A427341BDBE435001E6E22 /* wren_opt_meta.c in Sources */,
@ -454,6 +537,50 @@
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
2940E9B6206605DE0054503C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = c99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_PEDANTIC = NO;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
LIBRARY_SEARCH_PATHS = ../../build;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = "../../deps/libuv/include ../../src/module";
};
name = Debug;
};
2940E9B7206605DE0054503C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = c99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_PEDANTIC = NO;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
LIBRARY_SEARCH_PATHS = ../../build;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = "../../deps/libuv/include ../../src/module";
};
name = Release;
};
29AB1F0D1816E3AD004B501E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -599,6 +726,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
2940E9B5206605DE0054503C /* Build configuration list for PBXNativeTarget "unit_test" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2940E9B6206605DE0054503C /* Debug */,
2940E9B7206605DE0054503C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
29AB1F011816E3AD004B501E /* Build configuration list for PBXProject "wren" */ = {
isa = XCConfigurationList;
buildConfigurations = (