forked from Mirror/wren
Merge branch 'smarter-imports'
This commit is contained in:
26
Makefile
26
Makefile
@ -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
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`).
|
||||
@ -1,4 +1,4 @@
|
||||
import "cthulu" for Cthulu
|
||||
import "./cthulu" for Cthulu
|
||||
|
||||
class Lovecraft {
|
||||
construct new() {}
|
||||
@ -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
310
src/cli/path.c
Normal 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
65
src/cli/path.h
Normal 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
32
src/cli/stat.h
Normal 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
|
||||
244
src/cli/vm.c
244
src/cli/vm.c
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
117
src/vm/wren_vm.c
117
src/vm/wren_vm.c
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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(_,_,_,_)");
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "get_variable_module"
|
||||
import "./get_variable_module"
|
||||
|
||||
class GetVariable {
|
||||
foreign static beforeDefined()
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
149
test/api/resolution.c
Normal 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
4
test/api/resolution.h
Normal 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
39
test/api/resolution.wren
Normal 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
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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'.
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "module" for Module, Other
|
||||
import "./module" for Module, Other
|
||||
|
||||
System.print(Module) // expect: before
|
||||
|
||||
|
||||
@ -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'.
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "a"
|
||||
import "./a"
|
||||
|
||||
// Shared module should only run once:
|
||||
// expect: start a
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "module"
|
||||
import "./module"
|
||||
// expect: Bool
|
||||
// expect: Class
|
||||
// expect: Fiber
|
||||
|
||||
@ -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
|
||||
|
||||
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 "module" NoString // expect error
|
||||
import "./module" NoString // expect error
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
import "something" for Index
|
||||
import "./something/module" for Index
|
||||
|
||||
System.print(Index) // expect: index
|
||||
@ -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
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
var Collides
|
||||
import "module" for Collides // expect error
|
||||
import "./module" for Collides // expect error
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import
|
||||
|
||||
|
||||
"module"
|
||||
"./module"
|
||||
|
||||
import "module" for
|
||||
import "./module" for
|
||||
|
||||
A,
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
import "module"
|
||||
import "./module"
|
||||
// expect: ran module
|
||||
|
||||
2
test/language/module/relative_import/module_3.wren
Normal file
2
test/language/module/relative_import/module_3.wren
Normal file
@ -0,0 +1,2 @@
|
||||
// nontest
|
||||
System.print("module_3")
|
||||
@ -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
|
||||
3
test/language/module/relative_import/sub/dir/module.wren
Normal file
3
test/language/module/relative_import/sub/dir/module.wren
Normal file
@ -0,0 +1,3 @@
|
||||
// nontest
|
||||
System.print("sub/dir/module")
|
||||
import "./module_2"
|
||||
@ -0,0 +1,4 @@
|
||||
// nontest
|
||||
System.print("sub/dir/module_2")
|
||||
import "../module_3"
|
||||
import "../../module_3"
|
||||
3
test/language/module/relative_import/sub/module.wren
Normal file
3
test/language/module/relative_import/sub/module.wren
Normal file
@ -0,0 +1,3 @@
|
||||
// nontest
|
||||
System.print("sub/module")
|
||||
import "./module_2"
|
||||
2
test/language/module/relative_import/sub/module_2.wren
Normal file
2
test/language/module/relative_import/sub/module_2.wren
Normal file
@ -0,0 +1,2 @@
|
||||
// nontest
|
||||
System.print("sub/module_2")
|
||||
2
test/language/module/relative_import/sub/module_3.wren
Normal file
2
test/language/module/relative_import/sub/module_3.wren
Normal file
@ -0,0 +1,2 @@
|
||||
// nontest
|
||||
System.print("sub/module_3")
|
||||
@ -1,5 +1,5 @@
|
||||
// nontest
|
||||
System.print("a")
|
||||
import "shared" for Shared
|
||||
import "./shared" for Shared
|
||||
var A = "a %(Shared)"
|
||||
System.print("a done")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// nontest
|
||||
System.print("b")
|
||||
import "shared" for Shared
|
||||
import "./shared" for Shared
|
||||
var B = "b %(Shared)"
|
||||
System.print("b done")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "module" for Module
|
||||
import "./module" for Module
|
||||
// expect: ran module
|
||||
|
||||
System.print(Module) // expect: from module
|
||||
|
||||
@ -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'.
|
||||
|
||||
@ -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'.
|
||||
|
||||
@ -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("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
9
test/unit/main.c
Normal 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
104
test/unit/path_test.c
Normal 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
1
test/unit/path_test.h
Normal file
@ -0,0 +1 @@
|
||||
void testPath();
|
||||
29
test/unit/test.c
Normal file
29
test/unit/test.c
Normal 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
7
test/unit/test.h
Normal file
@ -0,0 +1,7 @@
|
||||
// Set this to 1 to output passing tests.
|
||||
#define SHOW_PASSES 0
|
||||
|
||||
void pass();
|
||||
void fail();
|
||||
|
||||
int showTestResults();
|
||||
@ -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 = []
|
||||
|
||||
@ -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')
|
||||
|
||||
57
util/wren.mk
57
util/wren.mk
@ -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
|
||||
|
||||
@ -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 = (
|
||||
|
||||
Reference in New Issue
Block a user