1
0
forked from Mirror/wren
Files
wren/test/test.c
2021-04-03 22:09:19 -07:00

481 lines
12 KiB
C

#include "./test.h"
//path helpers
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;
}
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';
}
void pathAppendString(Path* path, const char* string)
{
Slice slice;
slice.start = string;
slice.end = string + strlen(string);
appendSlice(path, slice);
}
inline 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
inline 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.
inline static size_t absolutePrefixLength(const char* path)
{
#ifdef _WIN32
// Drive letter.
if (isDriveLetter(path[0]) && path[1] == ':')
{
if (isSeparator(path[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 pathAppendChar(Path* path, char c)
{
ensureCapacity(path, path->length + 1);
path->chars[path->length++] = c;
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 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;
free(result);
}
char* pathToString(Path* path)
{
char* string = (char*)malloc(path->length + 1);
memcpy(string, path->chars, path->length);
string[path->length] = '\0';
return string;
}
//file helpers
// Reads the contents of the file at [path] and returns it as a heap allocated
// string.
//
// Returns `NULL` if the path could not be found. Exits if it was found but
// could not be read.
char* readFile(const char* path)
{
FILE* file = fopen(path, "rb");
if (file == NULL) return NULL;
// Find out how big the file is.
fseek(file, 0L, SEEK_END);
size_t fileSize = ftell(file);
rewind(file);
// Allocate a buffer for it.
char* buffer = (char*)malloc(fileSize + 1);
if (buffer == NULL)
{
fprintf(stderr, "Could not read file \"%s\".\n", path);
exit(WREN_EX_IOERR);
}
// Read the entire file.
size_t bytesRead = fread(buffer, 1, fileSize, file);
if (bytesRead < fileSize)
{
fprintf(stderr, "Could not read file \"%s\".\n", path);
exit(WREN_EX_IOERR);
}
// Terminate the string.
buffer[bytesRead] = '\0';
fclose(file);
return buffer;
}
//VM bindings
void vm_write(WrenVM* vm, const char* text)
{
printf("%s", text);
}
void reportError(WrenVM* vm, WrenErrorType type,
const char* module, int line, const char* message)
{
switch (type)
{
case WREN_ERROR_COMPILE:
fprintf(stderr, "[%s line %d] %s\n", module, line, message);
break;
case WREN_ERROR_RUNTIME:
fprintf(stderr, "%s\n", message);
break;
case WREN_ERROR_STACK_TRACE:
fprintf(stderr, "[%s line %d] in %s\n", module, line, message);
break;
}
}
void readModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result)
{
if (result.source) {
free((void*)result.source);
result.source = NULL;
}
}
WrenLoadModuleResult readModule(WrenVM* vm, const char* module)
{
//source may or may not be null
WrenLoadModuleResult result = {0};
#ifdef WREN_TRY
return result;
#endif
Path* filePath = pathNew(module);
// Add a ".wren" file extension.
pathAppendString(filePath, ".wren");
char* source = readFile(filePath->chars);
pathFree(filePath);
result.source = source;
result.onComplete = readModuleComplete;
return result;
}
// 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".
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;
// 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;
}
//main helpers
bool isModuleAnAPITest(const char* module)
{
if(strncmp(module, "test/api", 8) == 0) return true;
if(strncmp(module, "test/benchmark", 14) == 0) return true;
return false;
}
WrenInterpretResult runFile(WrenVM* vm, const char* path)
{
char* source = readFile(path);
if (source == NULL)
{
fprintf(stderr, "Could not find file \"%s\".\n", path);
exit(WREN_EX_NOINPUT);
}
// If it looks like a relative path, make it explicitly relative so that we
// can distinguish it from logical paths.
Path* module = pathNew(path);
if (pathType(module->chars) == PATH_TYPE_SIMPLE)
{
Path* relative = pathNew(".");
pathJoin(relative, path);
pathFree(module);
module = relative;
}
pathRemoveExtension(module);
WrenInterpretResult result = wrenInterpret(vm, module->chars, source);
pathFree(module);
free(source);
return result;
}
int handle_args(int argc, const char* argv[])
{
if (argc < 2)
{
printf("This is a Wren test runner.\nUsage: wren_test [file]\n");
return WREN_EX_USAGE;
}
if (argc == 2 && strcmp(argv[1], "--version") == 0)
{
printf("wren_test is running on Wren version %s\n", WREN_VERSION_STRING);
return 1;
}
return 0;
}