Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eaad8d5241 | |||
| 308fba1a8c | |||
| bcc9df928a | |||
| c66e77ec57 | |||
| 9dc9316853 | |||
| a864a1a226 | |||
| c645026620 | |||
| a9ccd65da1 | |||
| 113c067c96 | |||
| 01f710a4f3 | |||
| eb4c44193c | |||
| 0da5201829 | |||
| 214351abaa | |||
| 6be29ed8c5 | |||
| 6341d3337f | |||
| d4f952eafc | |||
| d5bc07d3b3 | |||
| 5a851f6fbe | |||
| d8c6a3ec27 | |||
| b5883e248b | |||
| 52048f2466 | |||
| 6b8e95d250 | |||
| 73965bc0e4 | |||
| 5721edc2d1 | |||
| db52c13613 | |||
| 46e274d182 | |||
| 7290efe069 | |||
| 925f13abf5 | |||
| 9a56a86ec8 | |||
| 3ddda21ff2 | |||
| 0cf92bdeae | |||
| 65acdd2730 | |||
| 1603ea1798 | |||
| 79f2e231db | |||
| ab0720a5ef | |||
| 901940a3d0 | |||
| 4c9a2e5378 | |||
| 1e11e9eea7 | |||
| cce8ae1ea3 | |||
| ce54912232 | |||
| a0acd27be1 | |||
| 23b55fc360 | |||
| 0c005d0af2 | |||
| 38ba2273dd | |||
| 1b8d8cfba0 | |||
| 648a5f84db | |||
| e2dda434f8 | |||
| 5a8e2c0527 | |||
| dea5cf6d43 | |||
| 930115f94c | |||
| 62fe86f99b | |||
| 2fa6b7ee23 | |||
| 401de578a5 | |||
| fb4258f9df | |||
| f885fdaf4c | |||
| cfec1b6911 | |||
| 43b58eb421 | |||
| a63bdaef1c | |||
| 3783c94064 | |||
| d292b33184 | |||
| 64944c24f6 | |||
| 604604e8bc | |||
| 67e49b7477 | |||
| 967963c9d7 | |||
| 36b4a494ae | |||
| 9b469e6eb0 | |||
| f8094fa17e | |||
| 8714c56c3e | |||
| 9faaa311e0 | |||
| f5ba1181c0 | |||
| b06b2d9485 | |||
| e3e9ca7ece | |||
| 81fe278c96 | |||
| 027d093e21 | |||
| 2eaf7fc71a | |||
| c43310f316 | |||
| 6e07c5f2f4 | |||
| 5317a12383 |
@@ -2,7 +2,7 @@ name: Comprehensive Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "dev" ]
|
||||
branches: [ "main", "*" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
|
||||
+56
-32
@@ -1,35 +1,59 @@
|
||||
#Editor generated files
|
||||
*.suo
|
||||
*.ncb
|
||||
*.user
|
||||
compile_commands.json
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
#Directories
|
||||
Release/
|
||||
Debug/
|
||||
Out/
|
||||
release/
|
||||
debug/
|
||||
out/
|
||||
bin/
|
||||
.cache/
|
||||
.vs/
|
||||
|
||||
#Project generated files
|
||||
*.db
|
||||
# Object files
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.dll
|
||||
*.exe
|
||||
*.meta
|
||||
*.log
|
||||
*.out
|
||||
*.stackdump
|
||||
*.tb
|
||||
*.filters
|
||||
[Dd]ocs/
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
#Shell files
|
||||
*.bat
|
||||
*.sh
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
.cproject
|
||||
.project
|
||||
.settings/
|
||||
temp/
|
||||
Release/
|
||||
out/
|
||||
@@ -35,6 +35,8 @@ Run `make install-tools` to install a number of tools, including:
|
||||
|
||||
* VSCode syntax highlighting
|
||||
|
||||
Other tools such as a disassembler are available, as well - simply run `make` in the correct directory.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
@@ -69,6 +71,14 @@ print tally(); //3
|
||||
|
||||
This source code is covered by the zlib license (see [LICENSE.md](LICENSE.md)).
|
||||
|
||||
# Contributions
|
||||
|
||||
@hiperiondev - Disassembler, porting support and feedback
|
||||
@add00 - Library support
|
||||
@gruelingpine185 - Unofficial MacOS support
|
||||
@solar-mist - Minor bugfixes
|
||||
Unnamed Individuals - Feedback
|
||||
|
||||
# Patrons via Patreon
|
||||
|
||||
* Seth A. Robinson
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="repl\drive_system.c" />
|
||||
<ClCompile Include="repl\lib_math.c" />
|
||||
<ClCompile Include="repl\lib_random.c" />
|
||||
<ClCompile Include="repl\lib_runner.c" />
|
||||
<ClCompile Include="repl\lib_standard.c" />
|
||||
@@ -145,6 +146,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="repl\drive_system.h" />
|
||||
<ClInclude Include="repl\lib_math.h" />
|
||||
<ClInclude Include="repl\lib_random.h" />
|
||||
<ClInclude Include="repl\lib_runner.h" />
|
||||
<ClInclude Include="repl\lib_standard.h" />
|
||||
|
||||
@@ -0,0 +1,940 @@
|
||||
#include "lib_fileio.h"
|
||||
#include "toy_memory.h"
|
||||
#include "drive_system.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct Toy_File
|
||||
{
|
||||
FILE* fp;
|
||||
Toy_RefString* mode;
|
||||
Toy_RefString* path;
|
||||
} Toy_File;
|
||||
|
||||
Toy_File* createToyFile(Toy_RefString* mode, Toy_RefString* path) {
|
||||
Toy_File* file = TOY_ALLOCATE(Toy_File, 1);
|
||||
file->fp = NULL;
|
||||
file->mode = Toy_copyRefString(mode);
|
||||
file->path = Toy_copyRefString(path);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
void deleteToyFile(Toy_File* file) {
|
||||
Toy_deleteRefString(file->mode);
|
||||
Toy_deleteRefString(file->path);
|
||||
TOY_FREE(Toy_File, file);
|
||||
}
|
||||
|
||||
static int nativeOpen(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count < 1) {
|
||||
interpreter->errorOutput("Too few arguments open(string, string) expects two arguments\n");
|
||||
return -1;
|
||||
}
|
||||
else if (arguments->count > 2) {
|
||||
interpreter->errorOutput("Too many arguments open(string, string) expects two arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal modeLiteral = arguments->count == 2? Toy_popLiteralArray(arguments) : TOY_TO_STRING_LITERAL(Toy_createRefString("r"));
|
||||
Toy_Literal drivePathLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the drivePath (if it's an identifier)
|
||||
Toy_Literal drivePathLiteralIdn = drivePathLiteral;
|
||||
if (TOY_IS_IDENTIFIER(drivePathLiteral) && Toy_parseIdentifierToValue(interpreter, &drivePathLiteral)) {
|
||||
Toy_freeLiteral(drivePathLiteralIdn);
|
||||
}
|
||||
|
||||
// check the drivePath type
|
||||
if (!TOY_IS_STRING(drivePathLiteral)) {
|
||||
interpreter->errorOutput("Incorrect argument type expected a string as the first argument to open(string, string)\n");
|
||||
Toy_freeLiteral(drivePathLiteral);
|
||||
Toy_freeLiteral(modeLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal filePathLiteral = Toy_getDrivePathLiteral(interpreter, &drivePathLiteral);
|
||||
|
||||
if (TOY_IS_NULL(filePathLiteral)) {
|
||||
interpreter->errorOutput("File not found in the specified drive\n");
|
||||
Toy_freeLiteral(drivePathLiteral);
|
||||
Toy_freeLiteral(filePathLiteral);
|
||||
Toy_freeLiteral(modeLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parse the mode (if it's an identifier)
|
||||
Toy_Literal modeLiteralIdn = modeLiteral;
|
||||
if (TOY_IS_IDENTIFIER(modeLiteral) && Toy_parseIdentifierToValue(interpreter, &modeLiteral)) {
|
||||
Toy_freeLiteral(modeLiteralIdn);
|
||||
}
|
||||
|
||||
// check the mode type
|
||||
if (!TOY_IS_STRING(modeLiteral)) {
|
||||
interpreter->errorOutput("Incorrect argument type expected a string as the second argument to open(string, string)\n");
|
||||
Toy_freeLiteral(drivePathLiteral);
|
||||
Toy_freeLiteral(filePathLiteral);
|
||||
Toy_freeLiteral(modeLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* filePath = Toy_toCString(TOY_AS_STRING(filePathLiteral));
|
||||
size_t filePathLength = Toy_lengthRefString(TOY_AS_STRING(filePathLiteral));
|
||||
|
||||
const char* mode = Toy_toCString(TOY_AS_STRING(modeLiteral));
|
||||
|
||||
// build file object
|
||||
Toy_File* file = createToyFile(TOY_AS_STRING(modeLiteral), TOY_AS_STRING(filePathLiteral));
|
||||
|
||||
// attempt to open file
|
||||
file->fp = fopen(filePath, mode);
|
||||
|
||||
// result
|
||||
Toy_Literal fileLiteral = TOY_TO_NULL_LITERAL;
|
||||
if (file->fp == NULL) {
|
||||
deleteToyFile(file);
|
||||
}
|
||||
else {
|
||||
fileLiteral = TOY_TO_OPAQUE_LITERAL(file, TOY_OPAQUE_TAG_FILE);
|
||||
}
|
||||
|
||||
Toy_pushLiteralArray(&interpreter->stack, fileLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(fileLiteral);
|
||||
Toy_freeLiteral(drivePathLiteral);
|
||||
Toy_freeLiteral(filePathLiteral);
|
||||
Toy_freeLiteral(modeLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeClose(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count != 1) {
|
||||
interpreter->errorOutput("Too many arguments close() expects zero arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type close() expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
int result = 0;
|
||||
if (
|
||||
file->fp != stdout &&
|
||||
file->fp != stdin &&
|
||||
file->fp != NULL
|
||||
) {
|
||||
result = fclose(file->fp);
|
||||
file->fp = NULL;
|
||||
}
|
||||
|
||||
// return the result
|
||||
Toy_Literal resultLiteral = TOY_TO_BOOLEAN_LITERAL(result != EOF);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
deleteToyFile(file);
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeRead(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count < 2) {
|
||||
interpreter->errorOutput("Too few arguments read(type) expects one argument\n");
|
||||
return -1;
|
||||
}
|
||||
else if (arguments->count > 2) {
|
||||
interpreter->errorOutput("Too many arguments read(type) expects one argument\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal typeLiteral = Toy_popLiteralArray(arguments);
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the type (if it's an identifier)
|
||||
Toy_Literal typeLiteralIdn = typeLiteral;
|
||||
if (TOY_IS_IDENTIFIER(typeLiteral) && Toy_parseIdentifierToValue(interpreter, &typeLiteral)) {
|
||||
Toy_freeLiteral(typeLiteralIdn);
|
||||
}
|
||||
|
||||
// check the type type
|
||||
if (!TOY_IS_TYPE(typeLiteral)) {
|
||||
interpreter->errorOutput("Incorrect argument type expected a type as the first argument to read(type)\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(typeLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type, read(type) expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(typeLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
Toy_Literal resultLiteral = TOY_TO_NULL_LITERAL;
|
||||
int error = 0;
|
||||
|
||||
switch (TOY_AS_TYPE(typeLiteral).typeOf) {
|
||||
case TOY_LITERAL_BOOLEAN: {
|
||||
char value = '0';
|
||||
error = fscanf(file->fp, "%c", &value);
|
||||
|
||||
resultLiteral = TOY_TO_BOOLEAN_LITERAL(value != '0');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_LITERAL_INTEGER: {
|
||||
int value = 0;
|
||||
error = fscanf(file->fp, "%i", &value);
|
||||
|
||||
resultLiteral = TOY_TO_INTEGER_LITERAL(value);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_LITERAL_FLOAT: {
|
||||
float value = 0.0f;
|
||||
error = fscanf(file->fp, "%f", &value);
|
||||
|
||||
resultLiteral = TOY_TO_FLOAT_LITERAL(value);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_LITERAL_STRING: { //BUG: needs a terminator to show how much to read
|
||||
char value[TOY_MAX_STRING_LENGTH] = {0};
|
||||
size_t size = fread(value, sizeof(char), TOY_MAX_STRING_LENGTH - 1, file->fp);
|
||||
value[size] = '\0';
|
||||
|
||||
resultLiteral = TOY_TO_STRING_LITERAL(Toy_createRefString(value));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// TODO handle other types
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (error != EOF) {
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
}
|
||||
else {
|
||||
Toy_pushLiteralArray(&interpreter->stack, TOY_TO_NULL_LITERAL);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(typeLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeWrite(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count < 2) {
|
||||
interpreter->errorOutput("Too few arguments write(any) expects one argument\n");
|
||||
return -1;
|
||||
}
|
||||
else if (arguments->count > 2) {
|
||||
interpreter->errorOutput("Too many arguments write(any) expects one argument\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal valueLiteral = Toy_popLiteralArray(arguments);
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the value (if it's an identifier)
|
||||
Toy_Literal valueLiteralIdn = valueLiteral;
|
||||
if (TOY_IS_IDENTIFIER(valueLiteral) && Toy_parseIdentifierToValue(interpreter, &valueLiteral)) {
|
||||
Toy_freeLiteral(valueLiteralIdn);
|
||||
}
|
||||
|
||||
// check the value type
|
||||
if (TOY_IS_NULL(valueLiteral)) {
|
||||
interpreter->errorOutput("Incorrect argument type expected non null value as the first argument to write(any)\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(valueLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type write(any) expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(valueLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
int result = 0;
|
||||
switch (valueLiteral.type) {
|
||||
case TOY_LITERAL_BOOLEAN: {
|
||||
result = fprintf(file->fp, "%i", TOY_AS_BOOLEAN(valueLiteral));
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_LITERAL_INTEGER: {
|
||||
result = fprintf(file->fp, "%i", TOY_AS_INTEGER(valueLiteral));
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_LITERAL_FLOAT: {
|
||||
result = fprintf(file->fp, "%f", TOY_AS_FLOAT(valueLiteral));
|
||||
break;
|
||||
}
|
||||
|
||||
case TOY_LITERAL_STRING: {
|
||||
result = fprintf(file->fp, "%s", Toy_toCString(TOY_AS_STRING(valueLiteral)));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// TODO handle other types
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Toy_Literal resultLiteral = TOY_TO_BOOLEAN_LITERAL(result > 0);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(valueLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeRename(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count < 2) {
|
||||
interpreter->errorOutput("Too few arguments rename(string) expects one argument\n");
|
||||
return -1;
|
||||
}
|
||||
else if (arguments->count > 2) {
|
||||
interpreter->errorOutput("Too many arguments rename(string) expects one argument\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal valueLiteral = Toy_popLiteralArray(arguments);
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the value (if it's an identifier)
|
||||
Toy_Literal valueLiteralIdn = valueLiteral;
|
||||
if (TOY_IS_IDENTIFIER(valueLiteral) && Toy_parseIdentifierToValue(interpreter, &valueLiteral)) {
|
||||
Toy_freeLiteral(valueLiteralIdn);
|
||||
}
|
||||
|
||||
// check the value type
|
||||
if (!TOY_IS_STRING(valueLiteral)) {
|
||||
interpreter->errorOutput("Incorrect argument type expected a string as the first argument to rename(string)\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(valueLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type, rename(string) expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(valueLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal filePathLiteral = Toy_getDrivePathLiteral(interpreter, &valueLiteral);
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
const char* newName = Toy_toCString(TOY_AS_STRING(filePathLiteral));
|
||||
|
||||
// close the file
|
||||
if (file->fp != NULL) {
|
||||
fclose(file->fp);
|
||||
file->fp = NULL;
|
||||
}
|
||||
|
||||
// rename the file
|
||||
int result = rename(Toy_toCString(file->path), newName);
|
||||
|
||||
// open file again
|
||||
file->fp = fopen(newName, Toy_toCString(file->mode));
|
||||
|
||||
// update the file object's name
|
||||
Toy_deleteRefString(file->path);
|
||||
file->path = Toy_createRefString(newName);
|
||||
|
||||
// return result
|
||||
Toy_Literal resultLiteral = TOY_TO_BOOLEAN_LITERAL(result == 0);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(filePathLiteral);
|
||||
Toy_freeLiteral(valueLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeSeek(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count < 3) {
|
||||
interpreter->errorOutput("Too few arguments seek(string, int) expects two arguments\n");
|
||||
return -1;
|
||||
}
|
||||
else if (arguments->count > 3) {
|
||||
interpreter->errorOutput("Too many arguments seek(string, int) expects two arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal offsetLiteral = Toy_popLiteralArray(arguments);
|
||||
Toy_Literal originLiteral = Toy_popLiteralArray(arguments);
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the origin (if it's an identifier)
|
||||
Toy_Literal originLiteralIdn = originLiteral;
|
||||
if (TOY_IS_IDENTIFIER(originLiteral) && Toy_parseIdentifierToValue(interpreter, &originLiteral)) {
|
||||
Toy_freeLiteral(originLiteralIdn);
|
||||
}
|
||||
|
||||
// check the origin type
|
||||
if (!TOY_IS_STRING(originLiteral)) {
|
||||
interpreter->errorOutput("Incorrect argument type expected a string as the first argument to seek(string, int)\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(offsetLiteral);
|
||||
Toy_freeLiteral(originLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parse the offset (if it's an identifier)
|
||||
Toy_Literal offsetLiteralIdn = offsetLiteral;
|
||||
if (TOY_IS_IDENTIFIER(offsetLiteral) && Toy_parseIdentifierToValue(interpreter, &offsetLiteral)) {
|
||||
Toy_freeLiteral(offsetLiteralIdn);
|
||||
}
|
||||
|
||||
// check the offset type
|
||||
if (!TOY_IS_INTEGER(offsetLiteral)) {
|
||||
interpreter->errorOutput("Incorrect argument type expected a int as the second argument to seek(string, int)\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(offsetLiteral);
|
||||
Toy_freeLiteral(originLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type seek(string, int) expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
Toy_freeLiteral(offsetLiteral);
|
||||
Toy_freeLiteral(originLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
Toy_RefString* orginString = TOY_AS_STRING(originLiteral);
|
||||
int offset = TOY_AS_INTEGER(offsetLiteral);
|
||||
|
||||
int origin = -1;
|
||||
if (Toy_equalsRefStringCString(orginString, "bgn")) {
|
||||
origin = SEEK_SET;
|
||||
}
|
||||
else if (Toy_equalsRefStringCString(orginString, "cur")) {
|
||||
origin = SEEK_CUR;
|
||||
}
|
||||
else if (Toy_equalsRefStringCString(orginString, "end")) {
|
||||
origin = SEEK_END;
|
||||
}
|
||||
|
||||
int result = origin >= SEEK_SET && origin <= SEEK_END?
|
||||
fseek(file->fp, offset, origin) : -1;
|
||||
|
||||
Toy_Literal resultLiteral = TOY_TO_BOOLEAN_LITERAL(result == 0);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(originLiteral);
|
||||
Toy_freeLiteral(offsetLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeError(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count != 1) {
|
||||
interpreter->errorOutput("Too many arguments error() expects zero arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type error() expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
int result = ferror(file->fp);
|
||||
|
||||
// return the result
|
||||
Toy_Literal resultLiteral = TOY_TO_BOOLEAN_LITERAL(result != 0);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeCompleted(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count != 1) {
|
||||
interpreter->errorOutput("Too many arguments completed() expects zero arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type completed() expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
int result = feof(file->fp);
|
||||
|
||||
// return the result
|
||||
Toy_Literal resultLiteral = TOY_TO_BOOLEAN_LITERAL(result != 0);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativePosition(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count != 1) {
|
||||
interpreter->errorOutput("Too many arguments position() expects zero arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type position() expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
// pervent integer overflow as ftell returns a long
|
||||
int size = ftell(file->fp) > INT_MAX? INT_MAX : ftell(file->fp);
|
||||
|
||||
// return the result
|
||||
Toy_Literal resultLiteral = TOY_TO_INTEGER_LITERAL(size);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeSize(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count != 1) {
|
||||
interpreter->errorOutput("Too many arguments size() expects zero arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type size() expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
int size = 0;
|
||||
fseek(file->fp, 0, SEEK_END);
|
||||
|
||||
// pervent integer overflow as ftell returns a long
|
||||
if (ftell(file->fp) > INT_MAX) {
|
||||
size = INT_MAX;
|
||||
}
|
||||
else {
|
||||
size = ftell(file->fp);
|
||||
}
|
||||
|
||||
fseek(file->fp, 0, SEEK_SET);
|
||||
|
||||
// return the result
|
||||
Toy_Literal resultLiteral = TOY_TO_INTEGER_LITERAL(size);
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativeMode(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count != 1) {
|
||||
interpreter->errorOutput("Too many arguments mode() expects zero arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type mode() expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
// return the result
|
||||
Toy_Literal resultLiteral = TOY_TO_STRING_LITERAL(Toy_copyRefString(file->mode));
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nativePath(Toy_Interpreter* interpreter, Toy_LiteralArray* arguments) {
|
||||
if (arguments->count != 1) {
|
||||
interpreter->errorOutput("Too many arguments path() expects zero arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_Literal selfLiteral = Toy_popLiteralArray(arguments);
|
||||
|
||||
// parse the self (if it's an identifier)
|
||||
Toy_Literal selfLiteralIdn = selfLiteral;
|
||||
if (TOY_IS_IDENTIFIER(selfLiteral) && Toy_parseIdentifierToValue(interpreter, &selfLiteral)) {
|
||||
Toy_freeLiteral(selfLiteralIdn);
|
||||
}
|
||||
|
||||
// check self type
|
||||
if (!TOY_IS_OPAQUE(selfLiteral) && TOY_GET_OPAQUE_TAG(selfLiteral) != TOY_OPAQUE_TAG_FILE) {
|
||||
interpreter->errorOutput("Incorrect self type path() expects a file type\n");
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Toy_File* file = (Toy_File*)TOY_AS_OPAQUE(selfLiteral);
|
||||
|
||||
// return the result
|
||||
Toy_Literal resultLiteral = TOY_TO_STRING_LITERAL(Toy_copyRefString(file->path));
|
||||
Toy_pushLiteralArray(&interpreter->stack, resultLiteral);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(resultLiteral);
|
||||
Toy_freeLiteral(selfLiteral);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// call the hook
|
||||
typedef struct Natives {
|
||||
char* name;
|
||||
Toy_NativeFn fn;
|
||||
} Natives;
|
||||
|
||||
typedef struct Variable {
|
||||
Toy_Literal key;
|
||||
Toy_Literal identifier;
|
||||
Toy_Literal literal;
|
||||
} Variable;
|
||||
|
||||
// Helper function create a int variable
|
||||
void createToyVariableInt(Variable* variable, char* key, int literal) {
|
||||
variable->key = TOY_TO_STRING_LITERAL(Toy_createRefString(key));
|
||||
variable->identifier = TOY_TO_IDENTIFIER_LITERAL(Toy_createRefString(key));
|
||||
variable->literal = TOY_TO_INTEGER_LITERAL(literal);
|
||||
}
|
||||
|
||||
// Helper function create a file variable
|
||||
void createToyVariableFile(Variable* variable, char* key, Toy_File* literal) {
|
||||
variable->key = TOY_TO_STRING_LITERAL(Toy_createRefString(key));
|
||||
variable->identifier = TOY_TO_IDENTIFIER_LITERAL(Toy_createRefString(key));
|
||||
variable->literal = TOY_TO_OPAQUE_LITERAL(literal, TOY_OPAQUE_TAG_FILE);
|
||||
}
|
||||
|
||||
// Helper function to clean up variables
|
||||
void deleteToyVariables(Variable variables[], int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
Toy_freeLiteral(variables[i].key);
|
||||
Toy_freeLiteral(variables[i].identifier);
|
||||
Toy_freeLiteral(variables[i].literal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Helper to check for naming conflicts with existing variables
|
||||
bool scopeConflict(Toy_Interpreter* interpreter, Variable variables[], int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (Toy_isDeclaredScopeVariable(interpreter->scope, variables[i].literal)) {
|
||||
interpreter->errorOutput("Can't override an existing variable\n");
|
||||
|
||||
deleteToyVariables(variables, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper to place variables into scope should be called after scopeConflict
|
||||
void exposeVariablesToScope(Toy_Interpreter* interpreter, Variable variables[], int size) {
|
||||
Toy_Literal intType = TOY_TO_TYPE_LITERAL(TOY_LITERAL_INTEGER, false);
|
||||
Toy_Literal opaqueType = TOY_TO_TYPE_LITERAL(TOY_LITERAL_OPAQUE, false);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (TOY_IS_INTEGER(variables[i].literal)) {
|
||||
Toy_declareScopeVariable(interpreter->scope, variables[i].identifier, intType);
|
||||
}
|
||||
else if (TOY_IS_OPAQUE(variables[i].literal)) {
|
||||
Toy_declareScopeVariable(interpreter->scope, variables[i].identifier, opaqueType);
|
||||
}
|
||||
|
||||
Toy_setScopeVariable(interpreter->scope, variables[i].identifier, variables[i].literal, true);
|
||||
}
|
||||
|
||||
Toy_freeLiteral(intType);
|
||||
Toy_freeLiteral(opaqueType);
|
||||
}
|
||||
|
||||
int Toy_hookFileIO(Toy_Interpreter* interpreter, Toy_Literal identifier, Toy_Literal alias) {
|
||||
// build the natives list
|
||||
Natives natives[] = {
|
||||
// access
|
||||
{"open", nativeOpen},
|
||||
{"close", nativeClose},
|
||||
|
||||
// operations
|
||||
{"read", nativeRead},
|
||||
{"write", nativeWrite},
|
||||
{"rename", nativeRename},
|
||||
{"seek", nativeSeek},
|
||||
|
||||
// accessors
|
||||
{"error", nativeError},
|
||||
{"completed", nativeCompleted},
|
||||
{"position", nativePosition},
|
||||
{"size", nativeSize},
|
||||
{"mode", nativeMode},
|
||||
{"path", nativePath},
|
||||
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
// global variables
|
||||
const int VARIABLES_SIZE = 5;
|
||||
Variable variables[VARIABLES_SIZE];
|
||||
|
||||
createToyVariableInt(&variables[0], "MAX_FILENAME_SIZE", FILENAME_MAX);
|
||||
createToyVariableInt(&variables[1], "MAX_FILES_OPEN", FOPEN_MAX);
|
||||
createToyVariableInt(&variables[2], "END_OF_FILE", EOF);
|
||||
|
||||
Toy_RefString* outMode = Toy_createRefString("w");
|
||||
Toy_RefString* outName = Toy_createRefString("output");
|
||||
|
||||
static Toy_File* outFile;
|
||||
outFile = createToyFile(outMode, outName);
|
||||
outFile->fp = stdout;
|
||||
|
||||
createToyVariableFile(&variables[3], "output", outFile);
|
||||
|
||||
Toy_deleteRefString(outMode);
|
||||
Toy_deleteRefString(outName);
|
||||
|
||||
Toy_RefString* inMode = Toy_createRefString("r");
|
||||
Toy_RefString* inName = Toy_createRefString("input");
|
||||
|
||||
static Toy_File* inFile;
|
||||
inFile = createToyFile(inMode, inName);
|
||||
inFile->fp = stdin;
|
||||
|
||||
createToyVariableFile(&variables[4], "input", inFile);
|
||||
|
||||
Toy_deleteRefString(inMode);
|
||||
Toy_deleteRefString(inName);
|
||||
|
||||
// store the library in an aliased dictionary
|
||||
if (!TOY_IS_NULL(alias)) {
|
||||
// make sure the name isn't taken
|
||||
if (Toy_isDeclaredScopeVariable(interpreter->scope, alias)) {
|
||||
interpreter->errorOutput("Can't override an existing variable\n");
|
||||
Toy_freeLiteral(alias);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// create the dictionary to load up with functions
|
||||
Toy_LiteralDictionary* dictionary = TOY_ALLOCATE(Toy_LiteralDictionary, 1);
|
||||
Toy_initLiteralDictionary(dictionary);
|
||||
|
||||
// load the dict with functions
|
||||
for (int i = 0; natives[i].name; i++) {
|
||||
Toy_Literal name = TOY_TO_STRING_LITERAL(Toy_createRefString(natives[i].name));
|
||||
Toy_Literal func = TOY_TO_FUNCTION_NATIVE_LITERAL(natives[i].fn);
|
||||
|
||||
Toy_setLiteralDictionary(dictionary, name, func);
|
||||
|
||||
Toy_freeLiteral(name);
|
||||
Toy_freeLiteral(func);
|
||||
}
|
||||
|
||||
// set global variables
|
||||
for (int i = 0; i < VARIABLES_SIZE; i++) {
|
||||
Toy_setLiteralDictionary(dictionary, variables[i].key, variables[i].literal);
|
||||
}
|
||||
|
||||
// build the type
|
||||
Toy_Literal type = TOY_TO_TYPE_LITERAL(TOY_LITERAL_DICTIONARY, true);
|
||||
Toy_Literal anyType = TOY_TO_TYPE_LITERAL(TOY_LITERAL_ANY, true);
|
||||
Toy_Literal fnType = TOY_TO_TYPE_LITERAL(TOY_LITERAL_FUNCTION_NATIVE, true);
|
||||
TOY_TYPE_PUSH_SUBTYPE(&type, anyType);
|
||||
TOY_TYPE_PUSH_SUBTYPE(&type, fnType);
|
||||
|
||||
// set scope
|
||||
Toy_Literal dict = TOY_TO_DICTIONARY_LITERAL(dictionary);
|
||||
Toy_declareScopeVariable(interpreter->scope, alias, type);
|
||||
Toy_setScopeVariable(interpreter->scope, alias, dict, false);
|
||||
|
||||
// cleanup
|
||||
Toy_freeLiteral(dict);
|
||||
Toy_freeLiteral(type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// default
|
||||
for (int i = 0; natives[i].name; i++) {
|
||||
Toy_injectNativeFn(interpreter, natives[i].name, natives[i].fn);
|
||||
}
|
||||
|
||||
if (scopeConflict(interpreter, variables, VARIABLES_SIZE)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
exposeVariablesToScope(interpreter, variables, VARIABLES_SIZE);
|
||||
|
||||
deleteToyVariables(variables, VARIABLES_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_interpreter.h"
|
||||
|
||||
#define TOY_OPAQUE_TAG_FILE 300
|
||||
|
||||
int Toy_hookFileIO(Toy_Interpreter* interpreter, Toy_Literal identifier, Toy_Literal alias);
|
||||
+1152
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "toy_interpreter.h"
|
||||
|
||||
int Toy_hookMath(Toy_Interpreter* interpreter, Toy_Literal identifier, Toy_Literal alias);
|
||||
+1
-1
@@ -2,7 +2,7 @@ CC=gcc
|
||||
|
||||
IDIR+=. ../source
|
||||
CFLAGS+=$(addprefix -I,$(IDIR)) -g -Wall -W -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable
|
||||
LIBS+=-ltoy
|
||||
LIBS+=-ltoy -lm
|
||||
|
||||
ODIR = obj
|
||||
SRC = $(wildcard *.c)
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "lib_standard.h"
|
||||
#include "lib_random.h"
|
||||
#include "lib_runner.h"
|
||||
#include "lib_fileio.h"
|
||||
#include "lib_math.h"
|
||||
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
@@ -30,6 +32,8 @@ void repl(const char* initialInput) {
|
||||
Toy_injectNativeHook(&interpreter, "standard", Toy_hookStandard);
|
||||
Toy_injectNativeHook(&interpreter, "random", Toy_hookRandom);
|
||||
Toy_injectNativeHook(&interpreter, "runner", Toy_hookRunner);
|
||||
Toy_injectNativeHook(&interpreter, "fileio", Toy_hookFileIO);
|
||||
Toy_injectNativeHook(&interpreter, "math", Toy_hookMath);
|
||||
|
||||
for(;;) {
|
||||
if (!initialInput) {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "lib_standard.h"
|
||||
#include "lib_random.h"
|
||||
#include "lib_runner.h"
|
||||
#include "lib_fileio.h"
|
||||
#include "lib_math.h"
|
||||
|
||||
#include "toy_console_colors.h"
|
||||
|
||||
@@ -115,6 +117,8 @@ void Toy_runBinary(const unsigned char* tb, size_t size) {
|
||||
Toy_injectNativeHook(&interpreter, "standard", Toy_hookStandard);
|
||||
Toy_injectNativeHook(&interpreter, "random", Toy_hookRandom);
|
||||
Toy_injectNativeHook(&interpreter, "runner", Toy_hookRunner);
|
||||
Toy_injectNativeHook(&interpreter, "fileio", Toy_hookFileIO);
|
||||
Toy_injectNativeHook(&interpreter, "math", Toy_hookMath);
|
||||
|
||||
Toy_runInterpreter(&interpreter, tb, (int)size);
|
||||
Toy_freeInterpreter(&interpreter);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import math;
|
||||
import fileio;
|
||||
import standard;
|
||||
|
||||
fn circle(radius: int, centerX: int, centerY: int) {
|
||||
for (var y: int = 0; y <= 2 * radius; y++) {
|
||||
for (var x: int = 0; x <= 2 * radius; x++) {
|
||||
var distance: int = round(sqrt((x - radius) * (x - radius) + (y - radius) * (y - radius)));
|
||||
|
||||
if (distance <= radius) {
|
||||
output.write("*");
|
||||
} else {
|
||||
output.write(" ");
|
||||
}
|
||||
}
|
||||
|
||||
output.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn triangle(height: int) {
|
||||
for (var i: int = 1; i <= height; i++) {
|
||||
for (var j: int = 1; j <= height - i; j++) {
|
||||
output.write(" ");
|
||||
}
|
||||
|
||||
for (var j: int = 1; j <= 2 * i - 1; j++) {
|
||||
output.write("*");
|
||||
}
|
||||
|
||||
output.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn square(size: int) {
|
||||
for (var i: int = 0; i < size; ++i) {
|
||||
for (var j: int = 0; j < size; ++j) {
|
||||
output.write("* ");
|
||||
}
|
||||
|
||||
output.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
output.write("Enter:\n0\tfor circle\n1\tfor triangle\n2\tfor square\n> ");
|
||||
var result: any = input.read(int);
|
||||
|
||||
if (result == 0) {
|
||||
circle(2, 5, 5);
|
||||
}
|
||||
else if (result == 1) {
|
||||
triangle(5);
|
||||
}
|
||||
else if (result == 2) {
|
||||
square(5);
|
||||
}
|
||||
else {
|
||||
output.write("invalid input :(\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fn f() {
|
||||
//
|
||||
}
|
||||
|
||||
fn g() {
|
||||
fn i() {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
fn h() {
|
||||
//
|
||||
}
|
||||
@@ -124,6 +124,16 @@ static void freeASTNodeCustom(Toy_ASTNode* node, bool freeSelf) {
|
||||
//NO-OP
|
||||
break;
|
||||
|
||||
case TOY_AST_NODE_AND:
|
||||
Toy_freeASTNode(node->pathAnd.left);
|
||||
Toy_freeASTNode(node->pathAnd.right);
|
||||
break;
|
||||
|
||||
case TOY_AST_NODE_OR:
|
||||
Toy_freeASTNode(node->pathOr.left);
|
||||
Toy_freeASTNode(node->pathOr.right);
|
||||
break;
|
||||
|
||||
case TOY_AST_NODE_PREFIX_INCREMENT:
|
||||
Toy_freeLiteral(node->prefixIncrement.identifier);
|
||||
break;
|
||||
@@ -348,6 +358,26 @@ void Toy_emitASTNodeContinue(Toy_ASTNode** nodeHandle) {
|
||||
*nodeHandle = tmp;
|
||||
}
|
||||
|
||||
void Toy_emitASTNodeAnd(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs) {
|
||||
Toy_ASTNode* tmp = TOY_ALLOCATE(Toy_ASTNode, 1);
|
||||
|
||||
tmp->type = TOY_AST_NODE_AND;
|
||||
tmp->binary.left = *nodeHandle;
|
||||
tmp->binary.right = rhs;
|
||||
|
||||
*nodeHandle = tmp;
|
||||
}
|
||||
|
||||
void Toy_emitASTNodeOr(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs) {
|
||||
Toy_ASTNode* tmp = TOY_ALLOCATE(Toy_ASTNode, 1);
|
||||
|
||||
tmp->type = TOY_AST_NODE_OR;
|
||||
tmp->binary.left = *nodeHandle;
|
||||
tmp->binary.right = rhs;
|
||||
|
||||
*nodeHandle = tmp;
|
||||
}
|
||||
|
||||
void Toy_emitASTNodePrefixIncrement(Toy_ASTNode** nodeHandle, Toy_Literal identifier) {
|
||||
Toy_ASTNode* tmp = TOY_ALLOCATE(Toy_ASTNode, 1);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ typedef enum Toy_ASTNodeType {
|
||||
TOY_AST_NODE_FOR, //for control flow
|
||||
TOY_AST_NODE_BREAK, //for control flow
|
||||
TOY_AST_NODE_CONTINUE, //for control flow
|
||||
TOY_AST_NODE_AND, //for control flow
|
||||
TOY_AST_NODE_OR, //for control flow
|
||||
TOY_AST_NODE_PREFIX_INCREMENT, //increment a variable
|
||||
TOY_AST_NODE_POSTFIX_INCREMENT, //increment a variable
|
||||
TOY_AST_NODE_PREFIX_DECREMENT, //decrement a variable
|
||||
@@ -204,6 +206,24 @@ typedef struct Toy_NodeContinue {
|
||||
Toy_ASTNodeType type;
|
||||
} Toy_NodeContinue;
|
||||
|
||||
//and operator
|
||||
void Toy_emitASTNodeAnd(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs); //handled node becomes lhs
|
||||
|
||||
typedef struct Toy_NodeAnd {
|
||||
Toy_ASTNodeType type;
|
||||
Toy_ASTNode* left;
|
||||
Toy_ASTNode* right;
|
||||
} Toy_NodeAnd;
|
||||
|
||||
//or operator
|
||||
void Toy_emitASTNodeOr(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs); //handled node becomes lhs
|
||||
|
||||
typedef struct Toy_NodeOr {
|
||||
Toy_ASTNodeType type;
|
||||
Toy_ASTNode* left;
|
||||
Toy_ASTNode* right;
|
||||
} Toy_NodeOr;
|
||||
|
||||
//pre-post increment/decrement
|
||||
void Toy_emitASTNodePrefixIncrement(Toy_ASTNode** nodeHandle, Toy_Literal identifier);
|
||||
void Toy_emitASTNodePrefixDecrement(Toy_ASTNode** nodeHandle, Toy_Literal identifier);
|
||||
@@ -263,6 +283,8 @@ union Toy_private_node {
|
||||
Toy_NodeFor pathFor;
|
||||
Toy_NodeBreak pathBreak;
|
||||
Toy_NodeContinue pathContinue;
|
||||
Toy_NodeAnd pathAnd;
|
||||
Toy_NodeOr pathOr;
|
||||
Toy_NodePrefixIncrement prefixIncrement;
|
||||
Toy_NodePrefixDecrement prefixDecrement;
|
||||
Toy_NodePostfixIncrement postfixIncrement;
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ The current minor version of Toy. This value is embedded into the bytecode, and
|
||||
This value MUST fit into an unsigned char.
|
||||
!*/
|
||||
|
||||
#define TOY_VERSION_MINOR 2
|
||||
#define TOY_VERSION_MINOR 3
|
||||
|
||||
/*!
|
||||
### TOY_VERSION_PATCH
|
||||
|
||||
+75
-11
@@ -318,6 +318,12 @@ bool checkNodeInTree(Toy_ASTNode* tree, Toy_ASTNode* node) {
|
||||
case TOY_AST_NODE_FOR:
|
||||
return checkNodeInTree(tree->pathFor.preClause, node) || checkNodeInTree(tree->pathFor.condition, node) || checkNodeInTree(tree->pathFor.postClause, node) || checkNodeInTree(tree->pathFor.thenPath, node);
|
||||
|
||||
case TOY_AST_NODE_AND:
|
||||
return checkNodeInTree(tree->pathAnd.left, node) || checkNodeInTree(tree->pathAnd.right, node);
|
||||
|
||||
case TOY_AST_NODE_OR:
|
||||
return checkNodeInTree(tree->pathOr.left, node) || checkNodeInTree(tree->pathOr.right, node);
|
||||
|
||||
case TOY_AST_NODE_ERROR:
|
||||
case TOY_AST_NODE_LITERAL:
|
||||
case TOY_AST_NODE_BREAK:
|
||||
@@ -429,8 +435,6 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
case TOY_OP_COMPARE_GREATER:
|
||||
case TOY_OP_COMPARE_GREATER_EQUAL:
|
||||
case TOY_OP_INVERT:
|
||||
case TOY_OP_AND:
|
||||
case TOY_OP_OR:
|
||||
//place the rhs result before the outer instruction
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)ret; //1 byte
|
||||
ret = TOY_OP_EOF;
|
||||
@@ -583,9 +587,13 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
Toy_initCompiler(fnCompiler);
|
||||
Toy_writeCompiler(fnCompiler, node->fnDecl.arguments); //can be empty, but not NULL
|
||||
Toy_writeCompiler(fnCompiler, node->fnDecl.returns); //can be empty, but not NULL
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(fnCompiler, node->fnDecl.block, NULL, NULL, -4, rootNode); //can be empty, but not NULL
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
|
||||
//BUGFIX: copied from TOY_AST_NODE_BLOCK, omitting the SCOPE_BEGIN and SCOPE_END opcodes (might squeeze a few bytes out of the interpreter's scopes by declaring one less)
|
||||
for (int i = 0; i < node->fnDecl.block->block.count; i++) {
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(fnCompiler, &(node->fnDecl.block->block.nodes[i]), NULL, NULL, -4, &(node->fnDecl.block->block.nodes[i]));
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
fnCompiler->bytecode[fnCompiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
}
|
||||
|
||||
//adopt the panic state if anything happened
|
||||
@@ -645,7 +653,7 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
for (int i = 0; i < node->fnCall.arguments->fnCollection.count; i++) { //reverse order, to count from the beginning in the interpreter
|
||||
//sub-calls
|
||||
if (node->fnCall.arguments->fnCollection.nodes[i].type != TOY_AST_NODE_LITERAL) {
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, &node->fnCall.arguments->fnCollection.nodes[i], breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode);
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, &node->fnCall.arguments->fnCollection.nodes[i], breakAddressesPtr, continueAddressesPtr, jumpOffsets, node); //BUGFIX: use node as rootNode, to allow indexing within argument lists
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
@@ -833,12 +841,20 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
compiler->count += sizeof(unsigned short); //2 bytes
|
||||
|
||||
//write the body
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_SCOPE_BEGIN; //1 byte
|
||||
bool closeScope = false;
|
||||
if (node->pathFor.thenPath->type != TOY_AST_NODE_BLOCK) {
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_SCOPE_BEGIN; //1 byte
|
||||
closeScope = true;
|
||||
}
|
||||
|
||||
override = Toy_writeCompilerWithJumps(compiler, node->pathFor.thenPath, &breakAddresses, &continueAddresses, jumpOffsets, rootNode);
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_SCOPE_END; //1 byte
|
||||
|
||||
if (closeScope) {
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_SCOPE_END; //1 byte
|
||||
}
|
||||
|
||||
//for-breaks actually jump to the bottom
|
||||
int jumpToIncrement = compiler->count;
|
||||
@@ -849,6 +865,9 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
|
||||
//BUGFIX: clear the stack after each loop
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_POP_STACK; //1 byte
|
||||
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_JUMP; //1 byte
|
||||
unsigned short tmpVal = jumpToStart + jumpOffsets;
|
||||
memcpy(compiler->bytecode + compiler->count, &tmpVal, sizeof(tmpVal));
|
||||
@@ -872,9 +891,6 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
memcpy(compiler->bytecode + point, &tmpVal, sizeof(tmpVal));
|
||||
}
|
||||
|
||||
//clear the stack after use
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_POP_STACK; //1 byte
|
||||
|
||||
//cleanup
|
||||
Toy_freeLiteralArray(&breakAddresses);
|
||||
Toy_freeLiteralArray(&continueAddresses);
|
||||
@@ -917,6 +933,54 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode
|
||||
}
|
||||
break;
|
||||
|
||||
case TOY_AST_NODE_AND: {
|
||||
//process the lhs
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, node->pathAnd.left, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode);
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
|
||||
//insert the AND opcode to signal a possible jump
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_AND; //1 byte
|
||||
int jumpToEnd = compiler->count;
|
||||
compiler->count += sizeof(unsigned short); //2 bytes
|
||||
|
||||
//process the rhs
|
||||
override = Toy_writeCompilerWithJumps(compiler, node->pathAnd.right, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode);
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
|
||||
//set the spot to jump to, to proceed
|
||||
unsigned short tmpVal = compiler->count + jumpOffsets;
|
||||
memcpy(compiler->bytecode + jumpToEnd, &tmpVal, sizeof(tmpVal));
|
||||
}
|
||||
break;
|
||||
|
||||
case TOY_AST_NODE_OR: {
|
||||
//process the lhs
|
||||
Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, node->pathOr.left, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode);
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
|
||||
//insert the AND opcode to signal a possible jump
|
||||
compiler->bytecode[compiler->count++] = TOY_OP_OR; //1 byte
|
||||
int jumpToEnd = compiler->count;
|
||||
compiler->count += sizeof(unsigned short); //2 bytes
|
||||
|
||||
//process the rhs
|
||||
override = Toy_writeCompilerWithJumps(compiler, node->pathOr.right, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode);
|
||||
if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy
|
||||
compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte
|
||||
}
|
||||
|
||||
//set the spot to jump to, to proceed
|
||||
unsigned short tmpVal = compiler->count + jumpOffsets;
|
||||
memcpy(compiler->bytecode + jumpToEnd, &tmpVal, sizeof(tmpVal));
|
||||
}
|
||||
break;
|
||||
|
||||
case TOY_AST_NODE_FN_RETURN: {
|
||||
//read each returned literal onto the stack, and return the number of values to return
|
||||
for (int i = 0; i < node->returns.returns->fnCollection.count; i++) {
|
||||
|
||||
+68
-43
@@ -582,7 +582,7 @@ static bool execVarDecl(Toy_Interpreter* interpreter, bool lng) {
|
||||
typeIndex = (int)readByte(interpreter->bytecode, &interpreter->count);
|
||||
}
|
||||
|
||||
Toy_Literal identifier = interpreter->literalCache.literals[identifierIndex];
|
||||
Toy_Literal identifier = Toy_copyLiteral(interpreter->literalCache.literals[identifierIndex]);
|
||||
Toy_Literal type = Toy_copyLiteral(interpreter->literalCache.literals[typeIndex]);
|
||||
|
||||
Toy_Literal typeIdn = type;
|
||||
@@ -597,6 +597,10 @@ static bool execVarDecl(Toy_Interpreter* interpreter, bool lng) {
|
||||
interpreter->errorOutput("Can't redefine the variable \"");
|
||||
Toy_printLiteralCustom(identifier, interpreter->errorOutput);
|
||||
interpreter->errorOutput("\"\n");
|
||||
|
||||
Toy_freeLiteral(identifier);
|
||||
Toy_freeLiteral(type);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -623,14 +627,16 @@ static bool execVarDecl(Toy_Interpreter* interpreter, bool lng) {
|
||||
Toy_printLiteralCustom(identifier, interpreter->errorOutput);
|
||||
interpreter->errorOutput("\"\n");
|
||||
|
||||
Toy_freeLiteral(identifier);
|
||||
Toy_freeLiteral(type);
|
||||
Toy_freeLiteral(val);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Toy_freeLiteral(val);
|
||||
Toy_freeLiteral(identifier);
|
||||
Toy_freeLiteral(type);
|
||||
Toy_freeLiteral(val);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -649,8 +655,15 @@ static bool execFnDecl(Toy_Interpreter* interpreter, bool lng) {
|
||||
functionIndex = (int)readByte(interpreter->bytecode, &interpreter->count);
|
||||
}
|
||||
|
||||
Toy_Literal identifier = interpreter->literalCache.literals[identifierIndex];
|
||||
Toy_Literal function = interpreter->literalCache.literals[functionIndex];
|
||||
Toy_Literal identifier = Toy_copyLiteral(interpreter->literalCache.literals[identifierIndex]);
|
||||
Toy_Literal function = Toy_copyLiteral(interpreter->literalCache.literals[functionIndex]);
|
||||
|
||||
if (!TOY_IS_IDENTIFIER(identifier) || !TOY_IS_FUNCTION(function)) {
|
||||
interpreter->errorOutput("Failed to declare a function, unknown literal error\n");
|
||||
Toy_freeLiteral(identifier);
|
||||
Toy_freeLiteral(function);
|
||||
return false;
|
||||
}
|
||||
|
||||
TOY_AS_FUNCTION(function).scope = Toy_pushScope(interpreter->scope); //hacked in (needed for closure persistance)
|
||||
|
||||
@@ -660,6 +673,10 @@ static bool execFnDecl(Toy_Interpreter* interpreter, bool lng) {
|
||||
interpreter->errorOutput("Can't redefine the function \"");
|
||||
Toy_printLiteralCustom(identifier, interpreter->errorOutput);
|
||||
interpreter->errorOutput("\"\n");
|
||||
|
||||
Toy_freeLiteral(identifier);
|
||||
Toy_freeLiteral(function);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -667,6 +684,10 @@ static bool execFnDecl(Toy_Interpreter* interpreter, bool lng) {
|
||||
interpreter->errorOutput("Incorrect type assigned to variable \"");
|
||||
Toy_printLiteralCustom(identifier, interpreter->errorOutput);
|
||||
interpreter->errorOutput("\"\n");
|
||||
|
||||
Toy_freeLiteral(identifier);
|
||||
Toy_freeLiteral(function);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -674,6 +695,8 @@ static bool execFnDecl(Toy_Interpreter* interpreter, bool lng) {
|
||||
TOY_AS_FUNCTION(function).scope = NULL;
|
||||
|
||||
Toy_freeLiteral(type);
|
||||
Toy_freeLiteral(identifier);
|
||||
Toy_freeLiteral(function);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1042,57 +1065,63 @@ static bool execCompareLessEqual(Toy_Interpreter* interpreter, bool invert) {
|
||||
}
|
||||
|
||||
static bool execAnd(Toy_Interpreter* interpreter) {
|
||||
Toy_Literal rhs = Toy_popLiteralArray(&interpreter->stack);
|
||||
Toy_Literal lhs = Toy_popLiteralArray(&interpreter->stack);
|
||||
|
||||
Toy_Literal rhsIdn = rhs;
|
||||
if (TOY_IS_IDENTIFIER(rhs) && Toy_parseIdentifierToValue(interpreter, &rhs)) {
|
||||
Toy_freeLiteral(rhsIdn);
|
||||
}
|
||||
|
||||
Toy_Literal lhsIdn = lhs;
|
||||
if (TOY_IS_IDENTIFIER(lhs) && Toy_parseIdentifierToValue(interpreter, &lhs)) {
|
||||
Toy_freeLiteral(lhsIdn);
|
||||
}
|
||||
|
||||
//short-circuit - broken, see issue #73
|
||||
//short-circuit - if not true
|
||||
if (!TOY_IS_TRUTHY(lhs)) {
|
||||
Toy_pushLiteralArray(&interpreter->stack, lhs);
|
||||
|
||||
int target = (int)readShort(interpreter->bytecode, &interpreter->count);
|
||||
|
||||
if (target + interpreter->codeStart > interpreter->length) {
|
||||
interpreter->errorOutput("[internal] AND Jump out of range\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//actually jump
|
||||
interpreter->count = target + interpreter->codeStart;
|
||||
}
|
||||
else {
|
||||
Toy_pushLiteralArray(&interpreter->stack, rhs);
|
||||
readShort(interpreter->bytecode, &interpreter->count); //discard
|
||||
}
|
||||
|
||||
Toy_freeLiteral(lhs);
|
||||
Toy_freeLiteral(rhs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool execOr(Toy_Interpreter* interpreter) {
|
||||
Toy_Literal rhs = Toy_popLiteralArray(&interpreter->stack);
|
||||
Toy_Literal lhs = Toy_popLiteralArray(&interpreter->stack);
|
||||
|
||||
Toy_Literal rhsIdn = rhs;
|
||||
if (TOY_IS_IDENTIFIER(rhs) && Toy_parseIdentifierToValue(interpreter, &rhs)) {
|
||||
Toy_freeLiteral(rhsIdn);
|
||||
}
|
||||
|
||||
Toy_Literal lhsIdn = lhs;
|
||||
if (TOY_IS_IDENTIFIER(lhs) && Toy_parseIdentifierToValue(interpreter, &lhs)) {
|
||||
Toy_freeLiteral(lhsIdn);
|
||||
}
|
||||
|
||||
//short-circuit - broken, see issue #73
|
||||
//short-circuit - if is true
|
||||
if (TOY_IS_TRUTHY(lhs)) {
|
||||
Toy_pushLiteralArray(&interpreter->stack, lhs);
|
||||
|
||||
int target = (int)readShort(interpreter->bytecode, &interpreter->count);
|
||||
|
||||
if (target + interpreter->codeStart > interpreter->length) {
|
||||
interpreter->errorOutput("[internal] OR Jump out of range\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//actually jump
|
||||
interpreter->count = target + interpreter->codeStart;
|
||||
}
|
||||
else {
|
||||
Toy_pushLiteralArray(&interpreter->stack, rhs);
|
||||
readShort(interpreter->bytecode, &interpreter->count); //discard
|
||||
}
|
||||
|
||||
Toy_freeLiteral(lhs);
|
||||
Toy_freeLiteral(rhs);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1985,27 +2014,9 @@ static void readInterpreterSections(Toy_Interpreter* interpreter) {
|
||||
}
|
||||
break;
|
||||
|
||||
case TOY_LITERAL_TYPE: {
|
||||
//what the literal is
|
||||
Toy_LiteralType literalType = (Toy_LiteralType)readByte(interpreter->bytecode, &interpreter->count);
|
||||
unsigned char constant = readByte(interpreter->bytecode, &interpreter->count);
|
||||
|
||||
Toy_Literal typeLiteral = TOY_TO_TYPE_LITERAL(literalType, constant);
|
||||
|
||||
//save the type
|
||||
Toy_pushLiteralArray(&interpreter->literalCache, typeLiteral);
|
||||
|
||||
#ifndef TOY_EXPORT
|
||||
if (Toy_commandLine.verbose) {
|
||||
printf("(type ");
|
||||
Toy_printLiteral(typeLiteral);
|
||||
printf(")\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case TOY_LITERAL_TYPE_INTERMEDIATE: {
|
||||
case TOY_LITERAL_TYPE:
|
||||
case TOY_LITERAL_TYPE_INTERMEDIATE:
|
||||
{
|
||||
//what the literal represents
|
||||
Toy_LiteralType literalType = (Toy_LiteralType)readByte(interpreter->bytecode, &interpreter->count);
|
||||
unsigned char constant = readByte(interpreter->bytecode, &interpreter->count);
|
||||
@@ -2322,6 +2333,13 @@ bool Toy_callLiteralFn(Toy_Interpreter* interpreter, Toy_Literal func, Toy_Liter
|
||||
Toy_freeLiteral(argIdn);
|
||||
}
|
||||
|
||||
//BUGFIX: coerce ints to floats, if the function requires floats
|
||||
if (TOY_IS_INTEGER(arg) && TOY_IS_TYPE(paramArray->literals[i + 1]) && TOY_AS_TYPE(paramArray->literals[i + 1]).typeOf == TOY_LITERAL_FLOAT) {
|
||||
Toy_Literal f = TOY_TO_FLOAT_LITERAL( (float)TOY_AS_INTEGER(arg) );
|
||||
Toy_freeLiteral(arg);
|
||||
arg = f;
|
||||
}
|
||||
|
||||
if (!Toy_setScopeVariable(inner.scope, paramArray->literals[i], arg, false)) {
|
||||
interpreter->errorOutput("[internal] Could not define parameter (bad type?)\n");
|
||||
|
||||
@@ -2416,6 +2434,13 @@ bool Toy_callLiteralFn(Toy_Interpreter* interpreter, Toy_Literal func, Toy_Liter
|
||||
for (int i = 0; i < returnsFromInner.count && returnValue; i++) {
|
||||
Toy_Literal ret = Toy_popLiteralArray(&returnsFromInner);
|
||||
|
||||
//BUGFIX: coerce the returned integers to floats, if specified
|
||||
if (returnArray->count > 0 && TOY_AS_TYPE(returnArray->literals[i]).typeOf == TOY_LITERAL_FLOAT && TOY_IS_INTEGER(ret)) {
|
||||
Toy_Literal f = TOY_TO_FLOAT_LITERAL( (float)TOY_AS_INTEGER(ret) );
|
||||
Toy_freeLiteral(ret);
|
||||
ret = f;
|
||||
}
|
||||
|
||||
//check the return types
|
||||
if (returnArray->count > 0 && TOY_AS_TYPE(returnArray->literals[i]).typeOf != ret.type) {
|
||||
interpreter->errorOutput("Bad type found in return value\n");
|
||||
|
||||
+3
-3
@@ -237,7 +237,7 @@ static Toy_Token makeKeywordOrIdentifier(Toy_Lexer* lexer) {
|
||||
|
||||
//scan for a keyword
|
||||
for (int i = 0; Toy_keywordTypes[i].keyword; i++) {
|
||||
if (strlen(Toy_keywordTypes[i].keyword) == (long unsigned int)(lexer->current - lexer->start) && !strncmp(Toy_keywordTypes[i].keyword, &lexer->source[lexer->start], lexer->current - lexer->start)) {
|
||||
if (strlen(Toy_keywordTypes[i].keyword) == (size_t)(lexer->current - lexer->start) && !strncmp(Toy_keywordTypes[i].keyword, &lexer->source[lexer->start], lexer->current - lexer->start)) {
|
||||
Toy_Token token;
|
||||
|
||||
token.type = Toy_keywordTypes[i].type;
|
||||
@@ -317,10 +317,10 @@ Toy_Token Toy_private_scanLexer(Toy_Lexer* lexer) {
|
||||
if (advance(lexer) != '&') {
|
||||
return makeErrorToken(lexer, "Unexpected '&'");
|
||||
} else {
|
||||
return makeToken(lexer, TOY_TOKEN_AND);
|
||||
return makeToken(lexer, TOY_TOKEN_AND_AND);
|
||||
}
|
||||
|
||||
case '|': return makeToken(lexer, match(lexer, '|') ? TOY_TOKEN_OR : TOY_TOKEN_PIPE);
|
||||
case '|': return makeToken(lexer, match(lexer, '|') ? TOY_TOKEN_OR_OR : TOY_TOKEN_PIPE);
|
||||
|
||||
case '?': return makeToken(lexer, TOY_TOKEN_QUESTION);
|
||||
case ':': return makeToken(lexer, TOY_TOKEN_COLON);
|
||||
|
||||
@@ -29,8 +29,8 @@ typedef enum Toy_Opcode {
|
||||
TOY_OP_SCOPE_BEGIN,
|
||||
TOY_OP_SCOPE_END,
|
||||
|
||||
TOY_OP_TYPE_DECL, //declare a type to be used (as a literal)
|
||||
TOY_OP_TYPE_DECL_LONG, //declare a type to be used (as a long literal)
|
||||
TOY_OP_TYPE_DECL_removed,
|
||||
TOY_OP_TYPE_DECL_LONG_removed,
|
||||
|
||||
TOY_OP_VAR_DECL, //declare a variable to be used (as a literal)
|
||||
TOY_OP_VAR_DECL_LONG, //declare a variable to be used (as a long literal)
|
||||
|
||||
+38
-12
@@ -168,6 +168,10 @@ static Toy_Opcode compound(Toy_Parser* parser, Toy_ASTNode** nodeHandle) {
|
||||
consume(parser, TOY_TOKEN_COMMA, "Expected ',' in array or dictionary");
|
||||
}
|
||||
|
||||
if (match(parser, TOY_TOKEN_BRACKET_RIGHT)) { //allow for trailing commas
|
||||
break;
|
||||
}
|
||||
|
||||
iterations++;
|
||||
|
||||
Toy_ASTNode* left = NULL;
|
||||
@@ -335,6 +339,28 @@ static Toy_Opcode grouping(Toy_Parser* parser, Toy_ASTNode** nodeHandle) {
|
||||
}
|
||||
}
|
||||
|
||||
static Toy_Opcode circuit(Toy_Parser* parser, Toy_ASTNode** nodeHandle) {
|
||||
advance(parser);
|
||||
|
||||
//handle short-circuitable operators - && ||
|
||||
switch (parser->previous.type) {
|
||||
case TOY_TOKEN_AND_AND: {
|
||||
parsePrecedence(parser, nodeHandle, PREC_AND + 1);
|
||||
return TOY_OP_AND;
|
||||
}
|
||||
|
||||
case TOY_TOKEN_OR_OR: {
|
||||
parsePrecedence(parser, nodeHandle, PREC_OR + 1);
|
||||
return TOY_OP_OR;
|
||||
}
|
||||
|
||||
default: {
|
||||
error(parser, parser->previous, "Unexpected token passed to grouping precedence rule");
|
||||
return TOY_OP_EOF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Toy_Opcode binary(Toy_Parser* parser, Toy_ASTNode** nodeHandle) {
|
||||
advance(parser);
|
||||
|
||||
@@ -428,16 +454,6 @@ static Toy_Opcode binary(Toy_Parser* parser, Toy_ASTNode** nodeHandle) {
|
||||
return TOY_OP_COMPARE_GREATER_EQUAL;
|
||||
}
|
||||
|
||||
case TOY_TOKEN_AND: {
|
||||
parsePrecedence(parser, nodeHandle, PREC_AND + 1);
|
||||
return TOY_OP_AND;
|
||||
}
|
||||
|
||||
case TOY_TOKEN_OR: {
|
||||
parsePrecedence(parser, nodeHandle, PREC_OR + 1);
|
||||
return TOY_OP_OR;
|
||||
}
|
||||
|
||||
default:
|
||||
error(parser, parser->previous, "Unexpected token passed to binary precedence rule");
|
||||
return TOY_OP_EOF;
|
||||
@@ -998,8 +1014,8 @@ ParseRule parseRules[] = { //must match the token types
|
||||
{NULL, binary, PREC_COMPARISON},// TOKEN_GREATER,
|
||||
{NULL, binary, PREC_COMPARISON},// TOKEN_LESS_EQUAL,
|
||||
{NULL, binary, PREC_COMPARISON},// TOKEN_GREATER_EQUAL,
|
||||
{NULL, binary, PREC_AND},// TOKEN_AND,
|
||||
{NULL, binary, PREC_OR},// TOKEN_OR,
|
||||
{NULL, circuit, PREC_AND},// TOKEN_AND,
|
||||
{NULL, circuit, PREC_OR},// TOKEN_OR,
|
||||
|
||||
//other operators
|
||||
{NULL, question, PREC_TERNARY}, //TOKEN_QUESTION,
|
||||
@@ -1281,6 +1297,16 @@ static void parsePrecedence(Toy_Parser* parser, Toy_ASTNode** nodeHandle, Preced
|
||||
continue;
|
||||
}
|
||||
|
||||
if (opcode == TOY_OP_AND) {
|
||||
Toy_emitASTNodeAnd(nodeHandle, rhsNode);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (opcode == TOY_OP_OR) {
|
||||
Toy_emitASTNodeOr(nodeHandle, rhsNode);
|
||||
continue;
|
||||
}
|
||||
|
||||
Toy_emitASTNodeBinary(nodeHandle, rhsNode, opcode);
|
||||
|
||||
//optimise away the constants
|
||||
|
||||
@@ -74,8 +74,8 @@ typedef enum Toy_TokenType {
|
||||
TOY_TOKEN_GREATER,
|
||||
TOY_TOKEN_LESS_EQUAL,
|
||||
TOY_TOKEN_GREATER_EQUAL,
|
||||
TOY_TOKEN_AND,
|
||||
TOY_TOKEN_OR,
|
||||
TOY_TOKEN_AND_AND,
|
||||
TOY_TOKEN_OR_OR,
|
||||
|
||||
//other operators
|
||||
TOY_TOKEN_QUESTION,
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ CC=gcc
|
||||
|
||||
IDIR +=. ../source ../repl
|
||||
CFLAGS +=$(addprefix -I,$(IDIR)) -g -Wall -W -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable
|
||||
LIBS +=
|
||||
LIBS +=-lm
|
||||
ODIR = obj
|
||||
TARGETS = $(wildcard ../source/*.c) $(wildcard ../repl/lib_*.c) ../repl/repl_tools.c ../repl/drive_system.c
|
||||
TESTS = $(wildcard test_*.c)
|
||||
@@ -15,7 +15,7 @@ all: $(OBJ) $(TESTS:%.c=../$(TOY_OUTDIR)/%.exe)
|
||||
../$(TOY_OUTDIR)/%.exe: $(ODIR)/%.o
|
||||
@$(CC) -o $@ $< $(TARGETS:../source/%.c=$(ODIR)/%.o) $(CFLAGS) $(LIBS)
|
||||
ifeq ($(shell uname)$(DISABLE_VALGRIND),Linux)
|
||||
valgrind --leak-check=full --track-origins=yes $@
|
||||
valgrind --leak-check=full --track-origins=yes --show-leak-kinds=all $@
|
||||
else
|
||||
$@
|
||||
endif
|
||||
|
||||
@@ -10,4 +10,20 @@
|
||||
}
|
||||
|
||||
|
||||
//test function coercion
|
||||
{
|
||||
fn f(arg: float) {
|
||||
assert typeof arg == float, "argument coercion failed";
|
||||
}
|
||||
|
||||
f(42);
|
||||
|
||||
fn g(): float {
|
||||
return 42;
|
||||
}
|
||||
|
||||
assert typeof g() == float, "return coercion failed";
|
||||
}
|
||||
|
||||
|
||||
print "All good";
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
fn max(lhs, rhs) {
|
||||
if (lhs > rhs) {
|
||||
return lhs;
|
||||
}
|
||||
else {
|
||||
return rhs;
|
||||
}
|
||||
}
|
||||
|
||||
var array = [42];
|
||||
|
||||
var result = null;
|
||||
|
||||
//problematic line
|
||||
result = max(0, array[0]);
|
||||
|
||||
assert result == 42, "Indexing in argument list failed";
|
||||
|
||||
|
||||
print "All good";
|
||||
@@ -0,0 +1 @@
|
||||
Hello, World!
|
||||
@@ -0,0 +1,4 @@
|
||||
1
|
||||
8
|
||||
12.5
|
||||
test
|
||||
@@ -0,0 +1,3 @@
|
||||
08
|
||||
12.500000
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
@@ -0,0 +1,164 @@
|
||||
import fileio;
|
||||
|
||||
var PATH: string const = "scripts:/lib/file/fileio.txt";
|
||||
|
||||
// reset file to orginal state
|
||||
fn reset() {
|
||||
var writer = open(PATH, "w");
|
||||
writer.write("Hello, World!\n");
|
||||
|
||||
writer.close();
|
||||
}
|
||||
|
||||
// test global constants
|
||||
{
|
||||
assert MAX_FILENAME_SIZE > 0, "MAX_FILENAME_SIZE failed";
|
||||
assert MAX_FILES_OPEN > 0, "MAX_FILES_OPEN failed";
|
||||
assert END_OF_FILE == -1, "END_OF_FILE failed";
|
||||
}
|
||||
|
||||
// test read
|
||||
{
|
||||
var reader = open("scripts:/lib/file/inputs.txt", "r");
|
||||
|
||||
assert reader.read(bool) == true, "read true bool failed";
|
||||
assert reader.read(int) == 8, "read int failed";
|
||||
assert reader.read(float) == 12.5, "read float failed";
|
||||
assert reader.read(string) == "\ntest", "read string failed";
|
||||
|
||||
// invaild types
|
||||
assert reader.read(type) == null, "read type failed";
|
||||
assert reader.read(any) == null, "read any failed";
|
||||
|
||||
reader.close();
|
||||
}
|
||||
|
||||
// test write
|
||||
{
|
||||
var writer = open("scripts:/lib/file/outputs.txt", "w");
|
||||
assert writer.write(false) == true, "write bool failed";
|
||||
assert writer.write(8) == true, "write int failed";
|
||||
assert writer.write("\n") == true, "write string failed";
|
||||
assert writer.write(12.5) == true, "write float failed";
|
||||
assert writer.write("\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n") == true, "write long string failed";
|
||||
|
||||
// invaild types
|
||||
assert writer.write([0, 1, 2]) == false, "write array failed";
|
||||
assert writer.write(["hi": "world"]) == false, "write dict failed";
|
||||
|
||||
writer.close();
|
||||
}
|
||||
|
||||
// test open and close
|
||||
{
|
||||
var reader = open(PATH);
|
||||
assert reader != null, "open failed in reading mode";
|
||||
|
||||
assert reader.close() == true, "close failed";
|
||||
|
||||
var file = open("scripts:/doesNotExist", "r");
|
||||
assert file == null, "open failed on nonexisting file";
|
||||
}
|
||||
|
||||
// test append
|
||||
{
|
||||
var appender = open(PATH, "a");
|
||||
assert appender != null, "open failed on appending file";
|
||||
|
||||
assert appender.write("appended text") == true, "append failed";
|
||||
|
||||
appender.close();
|
||||
reset();
|
||||
}
|
||||
|
||||
// test read extended
|
||||
{
|
||||
var reader = open(PATH, "r+");
|
||||
assert reader != null, "open failed on read extended mode";
|
||||
|
||||
assert reader.write("writen text") == true, "write in read extended failed";
|
||||
|
||||
var result = reader.read(string);
|
||||
// print result;
|
||||
// assert (result == "d!\n" || result == "d!\\r\n"), "read in read extended failed";
|
||||
|
||||
reader.close();
|
||||
reset();
|
||||
}
|
||||
|
||||
// test write extended
|
||||
{
|
||||
var writer = open(PATH, "w+");
|
||||
assert writer != null, "open failed on write extended mode";
|
||||
|
||||
assert writer.write("writen text") == true, "write in write extended failed";
|
||||
|
||||
writer.seek("bgn", 0);
|
||||
|
||||
assert writer.read(string) == "writen text", "read in write extended failed";
|
||||
|
||||
writer.close();
|
||||
reset();
|
||||
}
|
||||
|
||||
// test rename
|
||||
{
|
||||
var reader = open(PATH, "r");
|
||||
assert reader.rename("scripts:/lib/file/newName.txt") == true, "rename failed";
|
||||
|
||||
reader.rename(PATH);
|
||||
reader.close();
|
||||
}
|
||||
|
||||
// test seek
|
||||
{
|
||||
var reader = open(PATH, "r");
|
||||
assert reader.seek("bgn", 6) == true, "seek from bgn failed";
|
||||
|
||||
var contents = reader.read(string);
|
||||
assert contents == " World!\n", "seek failed to move file position (1st)";
|
||||
|
||||
assert reader.seek("end", -2) == true, "seek from end failed";
|
||||
|
||||
contents = reader.read(string);
|
||||
print ">>>(" + contents + ")" + string( contents.length() );
|
||||
assert contents == "\n", "seek failed to move file position (2nd)";
|
||||
|
||||
assert reader.seek("cur", -2) == true, "seek from cur failed";
|
||||
|
||||
contents = reader.read(string);
|
||||
assert contents == "\n", "seek failed to move file position (3rd)";
|
||||
|
||||
assert reader.seek("CUR", 0) == false, "seek origin failed (1st)";
|
||||
assert reader.seek("End", 0) == false, "seek origin failed (2nd)";
|
||||
assert reader.seek("beG", 0) == false, "seek origin failed (3rd)";
|
||||
assert reader.seek("xxx", 0) == false, "seek origin failed (4th)";
|
||||
|
||||
reader.close();
|
||||
}
|
||||
|
||||
// test accessors
|
||||
{
|
||||
var reader = open(PATH, "r");
|
||||
|
||||
assert reader.error() == false, "error failed";
|
||||
assert reader.completed() == false, "completed failed";
|
||||
assert reader.position() == 0, "position failed";
|
||||
assert reader.size() == 14, "size failed";
|
||||
assert reader.mode() == "r", "mode failed";
|
||||
|
||||
reader.read(string);
|
||||
|
||||
assert reader.error() == false, "error failed";
|
||||
assert reader.completed() == true, "completed after read failed";
|
||||
assert reader.position() == 14, "position failed";
|
||||
assert reader.size() == 14, "size failed";
|
||||
assert reader.mode() == "r", "mode failed";
|
||||
|
||||
reader.close();
|
||||
}
|
||||
|
||||
input.close();
|
||||
output.close();
|
||||
|
||||
print "All good";
|
||||
@@ -0,0 +1,187 @@
|
||||
import math;
|
||||
|
||||
|
||||
// test pow
|
||||
{
|
||||
assert pow(5, 3) == 125, "pow(5, 3) failed";
|
||||
assert pow(-5, 3) == -125, "pow(-5, 3) failed";
|
||||
assert pow(-5.5, 3) == -166.375, "pow(-5.5, 3) failed";
|
||||
assert pow(0, 1) == 0.0, "pow(0, 1) failed";
|
||||
assert pow(-0.0, 1) == -0.0, "pow(0, 1) failed";
|
||||
}
|
||||
|
||||
// test sqrt
|
||||
{
|
||||
assert sqrt(25) == 5, "sqrt(25) failed";
|
||||
assert sqrt(256.0) == 16, "sqrt(256.0) failed";
|
||||
assert checkIsNaN(sqrt(-256.0)), "sqrt(-256.0) failed";
|
||||
assert sqrt(1) == 1, "sqrt(1) failed";
|
||||
assert sqrt(0) == 0, "sqrt(0) failed";
|
||||
}
|
||||
|
||||
// test cbrt
|
||||
{
|
||||
assert cbrt(64) == 4, "cbrt(64) failed";
|
||||
assert cbrt(4096.0) == 16, "cbrt(4096.0) failed";
|
||||
assert cbrt(-64) == -4, "cbrt(-64) failed";
|
||||
assert cbrt(1) == 1, "cbrt(1) failed";
|
||||
assert cbrt(0) == 0, "cbrt(0) failed";
|
||||
}
|
||||
|
||||
// test hypot
|
||||
{
|
||||
assert hypot(3, 4) == 5, "hypot(3, 4) failed";
|
||||
}
|
||||
|
||||
// test toRad
|
||||
{
|
||||
assert toRadians(0) == 0, "toRadians(0) failed";
|
||||
assert toRadians(180) == PI, "toRadians(180) failed";
|
||||
assert toRadians(360) == 2 * PI, "toRadians(360) failed";
|
||||
}
|
||||
|
||||
|
||||
// test toDeg
|
||||
{
|
||||
assert toDegrees(0) == 0, "toDegrees(0) failed";
|
||||
assert toDegrees(PI) == 180, "toDegrees(PI) failed";
|
||||
assert toDegrees(2 * PI) == 360, "toDegrees(2*PI) failed";
|
||||
}
|
||||
|
||||
// test sin
|
||||
{
|
||||
assert epsilionCompare(sin(PI), 0), "sin(PI) failed";
|
||||
assert epsilionCompare(sin(PI / 2), 1), "sin(PI/2) failed";
|
||||
assert epsilionCompare(sin(0), 0), "sin(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test cos
|
||||
{
|
||||
assert epsilionCompare(cos(PI), -1), "cos(PI) failed";
|
||||
assert epsilionCompare(cos(PI / 2), 0), "cos(PI/2) failed";
|
||||
assert epsilionCompare(cos(0), 1), "cos(0) failed";
|
||||
}
|
||||
|
||||
// test tan
|
||||
{
|
||||
assert epsilionCompare(tan(PI), 0), "tan(PI) failed";
|
||||
assert epsilionCompare(tan(PI / 4), 1), "tan(PI/4) failed";
|
||||
assert epsilionCompare(tan(0), 0), "tan(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test asin
|
||||
{
|
||||
assert epsilionCompare(asin(1), 1.570796), "asin(1) failed";
|
||||
assert epsilionCompare(asin(-0.5), -0.523599), "asin(-0.5) failed";
|
||||
assert epsilionCompare(asin(0), 0), "asin(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test acos
|
||||
{
|
||||
assert epsilionCompare(acos(1), 0), "acos(1) failed";
|
||||
assert epsilionCompare(acos(0.5), 1.047198), "acos(0.5) failed";
|
||||
assert epsilionCompare(acos(0), 1.570796), "acos(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test atan
|
||||
{
|
||||
assert epsilionCompare(atan(1), 0.785398), "acos(1) failed";
|
||||
assert epsilionCompare(atan(INFINITY), 1.570796), "atan(INFINITY) failed";
|
||||
assert epsilionCompare(atan(0), 0), "atan(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test atan2
|
||||
{
|
||||
assert epsilionCompare(atans(0, 0), 0), "atan2(0, 0) failed";
|
||||
assert epsilionCompare(atans(7, 0), 1.570796), "atans(7, 0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test sinh
|
||||
{
|
||||
assert epsilionCompare(sinh(1), 1.175201), "sinh(1) failed";
|
||||
assert epsilionCompare(sinh(-1), -1.175201), "sinh(-1) failed";
|
||||
assert epsilionCompare(sinh(0), 0), "sinh(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test cosh
|
||||
{
|
||||
assert epsilionCompare(cosh(1), 1.543081), "cosh(1) failed";
|
||||
assert epsilionCompare(cosh(-1), 1.543081), "cosh(-1) failed";
|
||||
assert epsilionCompare(cosh(0), 1), "cosh(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test tanh
|
||||
{
|
||||
assert epsilionCompare(tanh(1), 0.761594), "tanh(1) failed";
|
||||
assert epsilionCompare(tanh(-1), -0.761594), "tanh(-1) failed";
|
||||
assert epsilionCompare(tanh(0), 0), "tanh(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test asinh
|
||||
{
|
||||
assert epsilionCompare(asinh(1), 0.881374), "asinh(1) failed";
|
||||
assert epsilionCompare(asinh(-1), -0.881374), "asinh(-1) failed";
|
||||
assert epsilionCompare(asinh(0), 0), "asinh(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test acosh
|
||||
{
|
||||
assert epsilionCompare(acosh(1), 0), "acosh(1) failed";
|
||||
assert checkIsNaN(acosh(-1)) == true, "acosh(-1) failed";
|
||||
assert checkIsNaN(acosh(0)) == true, "acosh(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test atanh
|
||||
{
|
||||
assert checkIsInfinite(atanh(1)) == true, "atanh(1) failed";
|
||||
assert checkIsInfinite(atanh(-1)) == true, "atanh(-1) failed";
|
||||
assert epsilionCompare(atanh(0), 0), "atanh(0) failed";
|
||||
}
|
||||
|
||||
|
||||
// test checkIsNaN
|
||||
{
|
||||
assert checkIsNaN(NAN) == true, "checkIsNaN(NAN) failed";
|
||||
assert checkIsNaN(INFINITY) == false, "checkIsNaN(INFINITY) failed";
|
||||
assert checkIsNaN(0.0) == false, "checkIsNaN(0.0) failed";
|
||||
assert checkIsNaN(INFINITY - INFINITY) == true, "checkIsNaN(INFINITY - INFINITY) failed";
|
||||
}
|
||||
|
||||
|
||||
// test checkIsFinite
|
||||
{
|
||||
assert checkIsFinite(NAN) == false, "checkIsFinite(NAN) failed";
|
||||
assert checkIsFinite(INFINITY) == false, "checkIsFinite(INFINITY) failed";
|
||||
assert checkIsFinite(0.0) == true, "checkIsFinite(0.0) failed";
|
||||
assert checkIsFinite(1) == true, "checkIsFinite(1) failed";
|
||||
}
|
||||
|
||||
|
||||
// test checkIsInfinite
|
||||
{
|
||||
assert checkIsInfinite(NAN) == false, "checkIsInfinite(NAN) failed";
|
||||
assert checkIsInfinite(INFINITY) == true, "checkIsInfinite(INFINITY) failed";
|
||||
assert checkIsInfinite(0.0) == false, "checkIsInfinite(0.0) failed";
|
||||
assert checkIsInfinite(1) == false, "checkIsInfinite(1) failed";
|
||||
}
|
||||
|
||||
// test epsilionCompare
|
||||
{
|
||||
assert epsilionCompare(1, 1) == true, "epsilionCompare(1, 1) failed";
|
||||
assert epsilionCompare(1, 1.000001) == true, "epsilionCompare(1, 1.000001) failed";
|
||||
assert epsilionCompare(1, 1.001) == false, "epsilionCompare(1, 1.001) failed";
|
||||
assert epsilionCompare(0, 0) == true, "epsilionCompare(0, 0) failed";
|
||||
}
|
||||
|
||||
print "All good";
|
||||
@@ -0,0 +1,14 @@
|
||||
//These operators should short-circuit
|
||||
assert (true && false) == false, "(true && false) == false failed";
|
||||
assert (false && true) == false, "(false && true) == false failed";
|
||||
|
||||
assert (true || false) == true, "(true || false) == true failed";
|
||||
assert (false || true) == true, "(false || true) == true failed";
|
||||
|
||||
|
||||
//make sure the right value is being returned when chained
|
||||
assert "a" && "b" && "c" == "c", "chained && failed";
|
||||
assert "a" || "b" || "c" == "a", "chained || failed";
|
||||
|
||||
|
||||
print "All good";
|
||||
@@ -1,9 +0,0 @@
|
||||
//explicitly support && and || short circuits
|
||||
|
||||
assert 1 && 2 == 2, "&& short-circuit failed";
|
||||
|
||||
assert 1 || 2 == 1, "|| short-circuit failed";
|
||||
|
||||
|
||||
print "All good";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
var array = [
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9, //explicitly leave a trailing comma
|
||||
];
|
||||
|
||||
|
||||
print "All good";
|
||||
+10
-3
@@ -18,6 +18,7 @@ static void noPrintFn(const char* output) {
|
||||
//NO OP
|
||||
}
|
||||
|
||||
int failedAssertions = 0;
|
||||
int ignoredAssertions = 0;
|
||||
static void noAssertFn(const char* output) {
|
||||
if (strncmp(output, "!ignore", 7) == 0) {
|
||||
@@ -27,6 +28,7 @@ static void noAssertFn(const char* output) {
|
||||
fprintf(stderr, TOY_CC_ERROR "Assertion failure: ");
|
||||
fprintf(stderr, "%s", output);
|
||||
fprintf(stderr, "\n" TOY_CC_RESET); //default new line
|
||||
failedAssertions++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +129,7 @@ int main() {
|
||||
"index-assignment-left-bugfix.toy",
|
||||
"index-dictionaries.toy",
|
||||
"index-strings.toy",
|
||||
"indexing-in-argument-list-bugfix.toy",
|
||||
"jumps.toy",
|
||||
"jumps-in-functions.toy",
|
||||
"logicals.toy",
|
||||
@@ -138,8 +141,9 @@ int main() {
|
||||
"panic-within-functions.toy",
|
||||
"polyfill-insert.toy",
|
||||
"polyfill-remove.toy",
|
||||
"short-circuiting-support.toy",
|
||||
"short-circuit.toy",
|
||||
"ternary-expressions.toy",
|
||||
"trailing-comma-bugfix.toy",
|
||||
"types.toy",
|
||||
NULL
|
||||
};
|
||||
@@ -160,7 +164,10 @@ int main() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
|
||||
return 0;
|
||||
if (failedAssertions == 0) {
|
||||
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
|
||||
}
|
||||
|
||||
return failedAssertions;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "../repl/lib_standard.h"
|
||||
#include "../repl/lib_random.h"
|
||||
#include "../repl/lib_runner.h"
|
||||
#include "../repl/lib_fileio.h"
|
||||
#include "../repl/lib_math.h"
|
||||
|
||||
//supress the print output
|
||||
static void noPrintFn(const char* output) {
|
||||
@@ -42,7 +44,7 @@ void runBinaryWithLibrary(const unsigned char* tb, size_t size, const char* libr
|
||||
Toy_initInterpreter(&interpreter);
|
||||
|
||||
//NOTE: supress print output for testing
|
||||
Toy_setInterpreterPrint(&interpreter, noPrintFn);
|
||||
// Toy_setInterpreterPrint(&interpreter, noPrintFn);
|
||||
Toy_setInterpreterAssert(&interpreter, assertWrapper);
|
||||
Toy_setInterpreterError(&interpreter, errorWrapper);
|
||||
|
||||
@@ -76,6 +78,8 @@ int main() {
|
||||
{"standard.toy", "standard", Toy_hookStandard},
|
||||
{"runner.toy", "runner", Toy_hookRunner},
|
||||
{"random.toy", "random", Toy_hookRandom},
|
||||
{"fileio.toy", "fileio", Toy_hookFileIO},
|
||||
{"math.toy", "math", Toy_hookMath},
|
||||
{NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Project: https://github.com/likle/cargs
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <cargs.h>
|
||||
#include <memory.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CAG_OPTION_PRINT_DISTANCE 4
|
||||
#define CAG_OPTION_PRINT_MIN_INDENTION 20
|
||||
|
||||
static void cag_option_print_value(const cag_option *option,
|
||||
size_t *accessor_length, FILE *destination) {
|
||||
if (option->value_name != NULL) {
|
||||
*accessor_length += fprintf(destination, "=%s", option->value_name);
|
||||
}
|
||||
}
|
||||
|
||||
static void cag_option_print_letters(const cag_option *option, bool *first,
|
||||
size_t *accessor_length, FILE *destination) {
|
||||
const char *access_letter;
|
||||
access_letter = option->access_letters;
|
||||
if (access_letter != NULL) {
|
||||
while (*access_letter) {
|
||||
if (*first) {
|
||||
*accessor_length += fprintf(destination, "-%c", *access_letter);
|
||||
*first = false;
|
||||
} else {
|
||||
*accessor_length += fprintf(destination, ", -%c",
|
||||
*access_letter);
|
||||
}
|
||||
++access_letter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cag_option_print_name(const cag_option *option, bool *first,
|
||||
size_t *accessor_length, FILE *destination) {
|
||||
if (option->access_name != NULL) {
|
||||
if (*first) {
|
||||
*accessor_length += fprintf(destination, "--%s",
|
||||
option->access_name);
|
||||
} else {
|
||||
*accessor_length += fprintf(destination, ", --%s",
|
||||
option->access_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size_t cag_option_get_print_indention(const cag_option *options,
|
||||
size_t option_count) {
|
||||
size_t option_index, indention, result;
|
||||
const cag_option *option;
|
||||
|
||||
result = CAG_OPTION_PRINT_MIN_INDENTION;
|
||||
|
||||
for (option_index = 0; option_index < option_count; ++option_index) {
|
||||
indention = CAG_OPTION_PRINT_DISTANCE;
|
||||
option = &options[option_index];
|
||||
if (option->access_letters != NULL && *option->access_letters) {
|
||||
indention += strlen(option->access_letters) * 4 - 2;
|
||||
if (option->access_name != NULL) {
|
||||
indention += strlen(option->access_name) + 4;
|
||||
}
|
||||
} else if (option->access_name != NULL) {
|
||||
indention += strlen(option->access_name) + 2;
|
||||
}
|
||||
|
||||
if (option->value_name != NULL) {
|
||||
indention += strlen(option->value_name) + 1;
|
||||
}
|
||||
|
||||
if (indention > result) {
|
||||
result = indention;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void cag_option_print(const cag_option *options, size_t option_count,
|
||||
FILE *destination) {
|
||||
size_t option_index, indention, i, accessor_length;
|
||||
const cag_option *option;
|
||||
bool first;
|
||||
|
||||
indention = cag_option_get_print_indention(options, option_count);
|
||||
|
||||
for (option_index = 0; option_index < option_count; ++option_index) {
|
||||
option = &options[option_index];
|
||||
accessor_length = 0;
|
||||
first = true;
|
||||
|
||||
fputs(" ", destination);
|
||||
|
||||
cag_option_print_letters(option, &first, &accessor_length, destination);
|
||||
cag_option_print_name(option, &first, &accessor_length, destination);
|
||||
cag_option_print_value(option, &accessor_length, destination);
|
||||
|
||||
for (i = accessor_length; i < indention; ++i) {
|
||||
fputs(" ", destination);
|
||||
}
|
||||
|
||||
fputs(" ", destination);
|
||||
fputs(option->description, destination);
|
||||
|
||||
fprintf(destination, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void cag_option_prepare(cag_option_context *context, const cag_option *options,
|
||||
size_t option_count, int argc, char **argv) {
|
||||
// This just initialized the values to the beginning of all the arguments.
|
||||
context->options = options;
|
||||
context->option_count = option_count;
|
||||
context->argc = argc;
|
||||
context->argv = argv;
|
||||
context->index = 1;
|
||||
context->inner_index = 0;
|
||||
context->forced_end = false;
|
||||
}
|
||||
|
||||
static const cag_option* cag_option_find_by_name(cag_option_context *context,
|
||||
char *name, size_t name_size) {
|
||||
const cag_option *option;
|
||||
size_t i;
|
||||
|
||||
// We loop over all the available options and stop as soon as we have found
|
||||
// one. We don't use any hash map table, since there won't be that many
|
||||
// arguments anyway.
|
||||
for (i = 0; i < context->option_count; ++i) {
|
||||
option = &context->options[i];
|
||||
|
||||
// The option might not have an item name, we can just skip those.
|
||||
if (option->access_name == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to compare the name of the access name. We can use the name_size or
|
||||
// this comparison, since we are guaranteed to have null-terminated access
|
||||
// names.
|
||||
if (strncmp(option->access_name, name, name_size) == 0) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const cag_option* cag_option_find_by_letter(cag_option_context *context,
|
||||
char letter) {
|
||||
const cag_option *option;
|
||||
size_t i;
|
||||
|
||||
// We loop over all the available options and stop as soon as we have found
|
||||
// one. We don't use any look up table, since there won't be that many
|
||||
// arguments anyway.
|
||||
for (i = 0; i < context->option_count; ++i) {
|
||||
option = &context->options[i];
|
||||
|
||||
// If this option doesn't have any access letters we will skip them.
|
||||
if (option->access_letters == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify whether this option has the access letter in it's access letter
|
||||
// string. If it does, then this is our option.
|
||||
if (strchr(option->access_letters, letter) != NULL) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cag_option_parse_value(cag_option_context *context,
|
||||
const cag_option *option, char **c) {
|
||||
// And now let's check whether this option is supposed to have a value, which
|
||||
// is the case if there is a value name set. The value can be either submitted
|
||||
// with a '=' sign or a space, which means we would have to jump over to the
|
||||
// next argv index. This is somewhat ugly, but we do it to behave the same as
|
||||
// the other option parsers.
|
||||
if (option->value_name != NULL) {
|
||||
if (**c == '=') {
|
||||
context->value = ++(*c);
|
||||
} else {
|
||||
// If the next index is larger or equal to the argument count, then the
|
||||
// parameter for this option is missing. The user will know about this,
|
||||
// since the value pointer of the context will be NULL because we don't
|
||||
// set it here in that case.
|
||||
if (context->argc > context->index + 1) {
|
||||
// We consider this argv to be the value, no matter what the contents
|
||||
// are.
|
||||
++context->index;
|
||||
*c = context->argv[context->index];
|
||||
context->value = *c;
|
||||
}
|
||||
}
|
||||
|
||||
// Move c to the end of the value, to not confuse the caller about our
|
||||
// position.
|
||||
while (**c) {
|
||||
++(*c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cag_option_parse_access_name(cag_option_context *context, char **c) {
|
||||
const cag_option *option;
|
||||
char *n;
|
||||
|
||||
// Now we need to extract the access name, which is any symbol up to a '=' or
|
||||
// a '\0'.
|
||||
n = *c;
|
||||
while (**c && **c != '=') {
|
||||
++*c;
|
||||
}
|
||||
|
||||
// Now this will obviously always be true, but we are paranoid. Sometimes. It
|
||||
// doesn't hurt to check.
|
||||
assert(*c >= n);
|
||||
|
||||
// Figure out which option this name belongs to. This might return NULL if the
|
||||
// name is not registered, which means the user supplied an unknown option. In
|
||||
// that case we return true to indicate that we finished with this option. We
|
||||
// have to skip the value parsing since we don't know whether the user thinks
|
||||
// this option has one or not. Since we don't set any identifier specifically,
|
||||
// it will remain '?' within the context.
|
||||
option = cag_option_find_by_name(context, n, (size_t) (*c - n));
|
||||
if (option == NULL) {
|
||||
// Since this option is invalid, we will move on to the next index. There is
|
||||
// nothing we can do about this.
|
||||
++context->index;
|
||||
return;
|
||||
}
|
||||
|
||||
// We found an option and now we can specify the identifier within the
|
||||
// context.
|
||||
context->identifier = option->identifier;
|
||||
|
||||
// And now we try to parse the value. This function will also check whether
|
||||
// this option is actually supposed to have a value.
|
||||
cag_option_parse_value(context, option, c);
|
||||
|
||||
// And finally we move on to the next index.
|
||||
++context->index;
|
||||
}
|
||||
|
||||
static void cag_option_parse_access_letter(cag_option_context *context,
|
||||
char **c) {
|
||||
const cag_option *option;
|
||||
char *n = *c;
|
||||
char *v;
|
||||
|
||||
// Figure out which option this letter belongs to. This might return NULL if
|
||||
// the letter is not registered, which means the user supplied an unknown
|
||||
// option. In that case we return true to indicate that we finished with this
|
||||
// option. We have to skip the value parsing since we don't know whether the
|
||||
// user thinks this option has one or not. Since we don't set any identifier
|
||||
// specifically, it will remain '?' within the context.
|
||||
option = cag_option_find_by_letter(context, n[context->inner_index]);
|
||||
if (option == NULL) {
|
||||
++context->index;
|
||||
context->inner_index = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// We found an option and now we can specify the identifier within the
|
||||
// context.
|
||||
context->identifier = option->identifier;
|
||||
|
||||
// And now we try to parse the value. This function will also check whether
|
||||
// this option is actually supposed to have a value.
|
||||
v = &n[++context->inner_index];
|
||||
cag_option_parse_value(context, option, &v);
|
||||
|
||||
// Check whether we reached the end of this option argument.
|
||||
if (*v == '\0') {
|
||||
++context->index;
|
||||
context->inner_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void cag_option_shift(cag_option_context *context, int start, int option,
|
||||
int end) {
|
||||
char *tmp;
|
||||
int a_index, shift_index, shift_count, left_index, right_index;
|
||||
|
||||
shift_count = option - start;
|
||||
|
||||
// There is no shift is required if the start and the option have the same
|
||||
// index.
|
||||
if (shift_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lets loop through the option strings first, which we will move towards the
|
||||
// beginning.
|
||||
for (a_index = option; a_index < end; ++a_index) {
|
||||
// First remember the current option value, because we will have to save
|
||||
// that later at the beginning.
|
||||
tmp = context->argv[a_index];
|
||||
|
||||
// Let's loop over all option values and shift them one towards the end.
|
||||
// This will override the option value we just stored temporarily.
|
||||
for (shift_index = 0; shift_index < shift_count; ++shift_index) {
|
||||
left_index = a_index - shift_index;
|
||||
right_index = a_index - shift_index - 1;
|
||||
context->argv[left_index] = context->argv[right_index];
|
||||
}
|
||||
|
||||
// Now restore the saved option value at the beginning.
|
||||
context->argv[a_index - shift_count] = tmp;
|
||||
}
|
||||
|
||||
// The new index will be before all non-option values, in such a way that they
|
||||
// all will be moved again in the next fetch call.
|
||||
context->index = end - shift_count;
|
||||
}
|
||||
|
||||
static bool cag_option_is_argument_string(const char *c) {
|
||||
return *c == '-' && *(c + 1) != '\0';
|
||||
}
|
||||
|
||||
static int cag_option_find_next(cag_option_context *context) {
|
||||
int next_index, next_option_index;
|
||||
char *c;
|
||||
|
||||
// Prepare to search the next option at the next index.
|
||||
next_index = context->index;
|
||||
next_option_index = next_index;
|
||||
|
||||
// Grab a pointer to the string and verify that it is not the end. If it is
|
||||
// the end, we have to return false to indicate that we finished.
|
||||
c = context->argv[next_option_index];
|
||||
if (context->forced_end || c == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check whether it is a '-'. We need to find the next option - and an option
|
||||
// always starts with a '-'. If there is a string "-\0", we don't consider it
|
||||
// as an option neither.
|
||||
while (!cag_option_is_argument_string(c)) {
|
||||
c = context->argv[++next_option_index];
|
||||
if (c == NULL) {
|
||||
// We reached the end and did not find any argument anymore. Let's tell
|
||||
// our caller that we reached the end.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that we found an option which can be processed. The index of the
|
||||
// next option will be returned.
|
||||
return next_option_index;
|
||||
}
|
||||
|
||||
bool cag_option_fetch(cag_option_context *context) {
|
||||
char *c;
|
||||
int old_index, new_index;
|
||||
|
||||
// Reset our identifier to a question mark, which indicates an "unknown"
|
||||
// option. The value is set to NULL, to make sure we are not carrying the
|
||||
// parameter from the previous option to this one.
|
||||
context->identifier = '?';
|
||||
context->value = NULL;
|
||||
|
||||
// Check whether there are any options left to parse and remember the old
|
||||
// index as well as the new index. In the end we will move the option junk to
|
||||
// the beginning, so that non option arguments can be read.
|
||||
old_index = context->index;
|
||||
new_index = cag_option_find_next(context);
|
||||
if (new_index >= 0) {
|
||||
context->index = new_index;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Grab a pointer to the beginning of the option. At this point, the next
|
||||
// character must be a '-', since if it was not the prepare function would
|
||||
// have returned false. We will skip that symbol and proceed.
|
||||
c = context->argv[context->index];
|
||||
assert(*c == '-');
|
||||
++c;
|
||||
|
||||
// Check whether this is a long option, starting with a double "--".
|
||||
if (*c == '-') {
|
||||
++c;
|
||||
|
||||
// This might be a double "--" which indicates the end of options. If this
|
||||
// is the case, we will not move to the next index. That ensures that
|
||||
// another call to the fetch function will not skip the "--".
|
||||
if (*c == '\0') {
|
||||
context->forced_end = true;
|
||||
} else {
|
||||
// We parse now the access name. All information about it will be written
|
||||
// to the context.
|
||||
cag_option_parse_access_name(context, &c);
|
||||
}
|
||||
} else {
|
||||
// This is no long option, so we can just parse an access letter.
|
||||
cag_option_parse_access_letter(context, &c);
|
||||
}
|
||||
|
||||
// Move the items so that the options come first followed by non-option
|
||||
// arguments.
|
||||
cag_option_shift(context, old_index, new_index, context->index);
|
||||
|
||||
return context->forced_end == false;
|
||||
}
|
||||
|
||||
char cag_option_get(const cag_option_context *context) {
|
||||
// We just return the identifier here.
|
||||
return context->identifier;
|
||||
}
|
||||
|
||||
const char* cag_option_get_value(const cag_option_context *context) {
|
||||
// We just return the internal value pointer of the context.
|
||||
return context->value;
|
||||
}
|
||||
|
||||
int cag_option_get_index(const cag_option_context *context) {
|
||||
// Either we point to a value item,
|
||||
return context->index;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Project: https://github.com/likle/cargs
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
#ifndef CARGS_H_
|
||||
#define CARGS_H_
|
||||
|
||||
/**
|
||||
* This is a simple alternative cross-platform implementation of getopt, which
|
||||
* is used to parse argument strings submitted to the executable (argc and argv
|
||||
* which are received in the main function).
|
||||
*/
|
||||
|
||||
#ifndef CAG_LIBRARY_H
|
||||
#define CAG_LIBRARY_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
#define CAG_EXPORT __declspec(dllexport)
|
||||
#define CAG_IMPORT __declspec(dllimport)
|
||||
#elif __GNUC__ >= 4
|
||||
#define CAG_EXPORT __attribute__((visibility("default")))
|
||||
#define CAG_IMPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define CAG_EXPORT
|
||||
#define CAG_IMPORT
|
||||
#endif
|
||||
|
||||
#if defined(CAG_SHARED)
|
||||
#if defined(CAG_EXPORTS)
|
||||
#define CAG_PUBLIC CAG_EXPORT
|
||||
#else
|
||||
#define CAG_PUBLIC CAG_IMPORT
|
||||
#endif
|
||||
#else
|
||||
#define CAG_PUBLIC
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* An option is used to describe a flag/argument option submitted when the
|
||||
* program is run.
|
||||
*/
|
||||
typedef struct cag_option {
|
||||
const char identifier;
|
||||
const char *access_letters;
|
||||
const char *access_name;
|
||||
const char *value_name;
|
||||
const char *description;
|
||||
} cag_option;
|
||||
|
||||
/**
|
||||
* A context is used to iterate over all options provided. It stores the parsing
|
||||
* state.
|
||||
*/
|
||||
typedef struct cag_option_context {
|
||||
const struct cag_option *options;
|
||||
size_t option_count;
|
||||
int argc;
|
||||
char **argv;
|
||||
int index;
|
||||
int inner_index;
|
||||
bool forced_end;
|
||||
char identifier;
|
||||
char *value;
|
||||
} cag_option_context;
|
||||
|
||||
/**
|
||||
* This is just a small macro which calculates the size of an array.
|
||||
*/
|
||||
#define CAG_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
/**
|
||||
* @brief Prints all options to the terminal.
|
||||
*
|
||||
* This function prints all options to the terminal. This can be used to
|
||||
* generate the output for a "--help" option.
|
||||
*
|
||||
* @param options The options which will be printed.
|
||||
* @param option_count The option count which will be printed.
|
||||
* @param destination The destination where the output will be printed.
|
||||
*/
|
||||
CAG_PUBLIC void cag_option_print(const cag_option *options, size_t option_count,
|
||||
FILE *destination);
|
||||
|
||||
/**
|
||||
* @brief Prepare argument options context for parsing.
|
||||
*
|
||||
* This function prepares the context for iteration and initializes the context
|
||||
* with the supplied options and arguments. After the context has been prepared,
|
||||
* it can be used to fetch arguments from it.
|
||||
*
|
||||
* @param context The context which will be initialized.
|
||||
* @param options The registered options which are available for the program.
|
||||
* @param option_count The amount of options which are available for the
|
||||
* program.
|
||||
* @param argc The amount of arguments the user supplied in the main function.
|
||||
* @param argv A pointer to the arguments of the main function.
|
||||
*/
|
||||
CAG_PUBLIC void cag_option_prepare(cag_option_context *context,
|
||||
const cag_option *options, size_t option_count, int argc, char **argv);
|
||||
|
||||
/**
|
||||
* @brief Fetches an option from the argument list.
|
||||
*
|
||||
* This function fetches a single option from the argument list. The context
|
||||
* will be moved to that item. Information can be extracted from the context
|
||||
* after the item has been fetched.
|
||||
* The arguments will be re-ordered, which means that non-option arguments will
|
||||
* be moved to the end of the argument list. After all options have been
|
||||
* fetched, all non-option arguments will be positioned after the index of
|
||||
* the context.
|
||||
*
|
||||
* @param context The context from which we will fetch the option.
|
||||
* @return Returns true if there was another option or false if the end is
|
||||
* reached.
|
||||
*/
|
||||
CAG_PUBLIC bool cag_option_fetch(cag_option_context *context);
|
||||
|
||||
/**
|
||||
* @brief Gets the identifier of the option.
|
||||
*
|
||||
* This function gets the identifier of the option, which should be unique to
|
||||
* this option and can be used to determine what kind of option this is.
|
||||
*
|
||||
* @param context The context from which the option was fetched.
|
||||
* @return Returns the identifier of the option.
|
||||
*/
|
||||
CAG_PUBLIC char cag_option_get(const cag_option_context *context);
|
||||
|
||||
/**
|
||||
* @brief Gets the value from the option.
|
||||
*
|
||||
* This function gets the value from the option, if any. If the option does not
|
||||
* contain a value, this function will return NULL.
|
||||
*
|
||||
* @param context The context from which the option was fetched.
|
||||
* @return Returns a pointer to the value or NULL if there is no value.
|
||||
*/
|
||||
CAG_PUBLIC const char* cag_option_get_value(const cag_option_context *context);
|
||||
|
||||
/**
|
||||
* @brief Gets the current index of the context.
|
||||
*
|
||||
* This function gets the index within the argv arguments of the context. The
|
||||
* context always points to the next item which it will inspect. This is
|
||||
* particularly useful to inspect the original argument array, or to get
|
||||
* non-option arguments after option fetching has finished.
|
||||
*
|
||||
* @param context The context from which the option was fetched.
|
||||
* @return Returns the current index of the context.
|
||||
*/
|
||||
CAG_PUBLIC int cag_option_get_index(const cag_option_context *context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* CARGS_H_ */
|
||||
|
||||
@@ -0,0 +1,946 @@
|
||||
/*
|
||||
* disassembler.c
|
||||
*
|
||||
* Created on: 10 ago. 2023
|
||||
* Original Author: Emiliano Augusto Gonzalez (egonzalez . hiperion @ gmail . com)
|
||||
*
|
||||
* Further modified by Kayne Ruse, and added to the Toy Programming Language tool repository.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "disassembler_utils.h"
|
||||
#include "disassembler.h"
|
||||
|
||||
#define SPC(n) printf("%.*s", n, "| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |");
|
||||
#define EP(x) [x] = #x
|
||||
|
||||
const char *OP_STR[] = {
|
||||
EP(DIS_OP_EOF), //
|
||||
EP(DIS_OP_PASS), //
|
||||
EP(DIS_OP_ASSERT), //
|
||||
EP(DIS_OP_PRINT), //
|
||||
EP(DIS_OP_LITERAL), //
|
||||
EP(DIS_OP_LITERAL_LONG), //
|
||||
EP(DIS_OP_LITERAL_RAW), //
|
||||
EP(DIS_OP_NEGATE), //
|
||||
EP(DIS_OP_ADDITION), //
|
||||
EP(DIS_OP_SUBTRACTION), //
|
||||
EP(DIS_OP_MULTIPLICATION), //
|
||||
EP(DIS_OP_DIVISION), //
|
||||
EP(DIS_OP_MODULO), //
|
||||
EP(DIS_OP_GROUPING_BEGIN), //
|
||||
EP(DIS_OP_GROUPING_END), //
|
||||
EP(DIS_OP_SCOPE_BEGIN), //
|
||||
EP(DIS_OP_SCOPE_END), //
|
||||
EP(DIS_OP_TYPE_DECL_removed), //
|
||||
EP(DIS_OP_TYPE_DECL_LONG_removed), //
|
||||
EP(DIS_OP_VAR_DECL), //
|
||||
EP(DIS_OP_VAR_DECL_LONG), //
|
||||
EP(DIS_OP_FN_DECL), //
|
||||
EP(DIS_OP_FN_DECL_LONG), //
|
||||
EP(DIS_OP_VAR_ASSIGN), //
|
||||
EP(DIS_OP_VAR_ADDITION_ASSIGN), //
|
||||
EP(DIS_OP_VAR_SUBTRACTION_ASSIGN), //
|
||||
EP(DIS_OP_VAR_MULTIPLICATION_ASSIGN), //
|
||||
EP(DIS_OP_VAR_DIVISION_ASSIGN), //
|
||||
EP(DIS_OP_VAR_MODULO_ASSIGN), //
|
||||
EP(DIS_OP_TYPE_CAST), //
|
||||
EP(DIS_OP_TYPE_OF), //
|
||||
EP(DIS_OP_IMPORT), //
|
||||
EP(DIS_OP_EXPORT_removed), //
|
||||
EP(DIS_OP_INDEX), //
|
||||
EP(DIS_OP_INDEX_ASSIGN), //
|
||||
EP(DIS_OP_INDEX_ASSIGN_INTERMEDIATE), //
|
||||
EP(DIS_OP_DOT), //
|
||||
EP(DIS_OP_COMPARE_EQUAL), //
|
||||
EP(DIS_OP_COMPARE_NOT_EQUAL), //
|
||||
EP(DIS_OP_COMPARE_LESS), //
|
||||
EP(DIS_OP_COMPARE_LESS_EQUAL), //
|
||||
EP(DIS_OP_COMPARE_GREATER), //
|
||||
EP(DIS_OP_COMPARE_GREATER_EQUAL), //
|
||||
EP(DIS_OP_INVERT), //
|
||||
EP(DIS_OP_AND), //
|
||||
EP(DIS_OP_OR), //
|
||||
EP(DIS_OP_JUMP), //
|
||||
EP(DIS_OP_IF_FALSE_JUMP), //
|
||||
EP(DIS_OP_FN_CALL), //
|
||||
EP(DIS_OP_FN_RETURN), //
|
||||
EP(DIS_OP_POP_STACK), //
|
||||
EP(DIS_OP_TERNARY), //
|
||||
EP(DIS_OP_FN_END), //
|
||||
};
|
||||
|
||||
const char *LIT_STR[] = {
|
||||
EP(DIS_LITERAL_NULL), //
|
||||
EP(DIS_LITERAL_BOOLEAN), //
|
||||
EP(DIS_LITERAL_INTEGER), //
|
||||
EP(DIS_LITERAL_FLOAT), //
|
||||
EP(DIS_LITERAL_STRING), //
|
||||
EP(DIS_LITERAL_ARRAY), //
|
||||
EP(DIS_LITERAL_DICTIONARY), //
|
||||
EP(DIS_LITERAL_FUNCTION), //
|
||||
EP(DIS_LITERAL_IDENTIFIER), //
|
||||
EP(DIS_LITERAL_TYPE), //
|
||||
EP(DIS_LITERAL_OPAQUE), //
|
||||
EP(DIS_LITERAL_ANY), //
|
||||
EP(DIS_LITERAL_TYPE_INTERMEDIATE), //
|
||||
EP(DIS_LITERAL_ARRAY_INTERMEDIATE), //
|
||||
EP(DIS_LITERAL_DICTIONARY_INTERMEDIATE), //
|
||||
EP(DIS_LITERAL_FUNCTION_INTERMEDIATE), //
|
||||
EP(DIS_LITERAL_FUNCTION_ARG_REST), //
|
||||
EP(DIS_LITERAL_FUNCTION_NATIVE), //
|
||||
EP(DIS_LITERAL_FUNCTION_HOOK), //
|
||||
EP(DIS_LITERAL_INDEX_BLANK), //
|
||||
};
|
||||
|
||||
enum DIS_ARG_TYPE {
|
||||
DIS_ARG_NONE, //
|
||||
DIS_ARG_BYTE, //
|
||||
DIS_ARG_WORD, //
|
||||
DIS_ARG_INTEGER, //
|
||||
DIS_ARG_FLOAT, //
|
||||
DIS_ARG_STRING //
|
||||
};
|
||||
|
||||
const uint8_t OP_ARGS[DIS_OP_END_OPCODES][3] = {
|
||||
// | first arg | second arg | jump |
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_EOF
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_PASS
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_ASSERT
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_PRINT
|
||||
{ DIS_ARG_BYTE, DIS_ARG_NONE, false }, // DIS_OP_LITERAL
|
||||
{ DIS_ARG_WORD, DIS_ARG_NONE, false }, // DIS_OP_LITERAL_LONG
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_LITERAL_RAW
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_NEGATE
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_ADDITION
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_SUBTRACTION
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_MULTIPLICATION
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_DIVISION
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_MODULO
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_GROUPING_BEGIN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_GROUPING_END
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_SCOPE_BEGIN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_SCOPE_END
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_TYPE_DECL_removed
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_TYPE_DECL_LONG_removed
|
||||
{ DIS_ARG_BYTE, DIS_ARG_BYTE, false }, // DIS_OP_VAR_DECL
|
||||
{ DIS_ARG_WORD, DIS_ARG_WORD, false }, // DIS_OP_VAR_DECL_LONG
|
||||
{ DIS_ARG_BYTE, DIS_ARG_BYTE, false }, // DIS_OP_FN_DECL
|
||||
{ DIS_ARG_WORD, DIS_ARG_WORD, false }, // DIS_OP_FN_DECL_LONG
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_VAR_ASSIGN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_VAR_ADDITION_ASSIGN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_VAR_SUBTRACTION_ASSIGN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_VAR_MULTIPLICATION_ASSIGN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_VAR_DIVISION_ASSIGN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_VAR_MODULO_ASSIGN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_TYPE_CAST
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_TYPE_OF
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_IMPORT
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_EXPORT_removed
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_INDEX
|
||||
{ DIS_ARG_BYTE, DIS_ARG_NONE, false }, // DIS_OP_INDEX_ASSIGN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_INDEX_ASSIGN_INTERMEDIATE
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_DOT
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_COMPARE_EQUAL
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_COMPARE_NOT_EQUAL
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_COMPARE_LESS
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_COMPARE_LESS_EQUAL
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_COMPARE_GREATER
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_COMPARE_GREATER_EQUAL
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_INVERT
|
||||
{ DIS_ARG_WORD, DIS_ARG_NONE, true }, // DIS_OP_AND
|
||||
{ DIS_ARG_WORD, DIS_ARG_NONE, true }, // DIS_OP_OR
|
||||
{ DIS_ARG_WORD, DIS_ARG_NONE, true }, // DIS_OP_JUMP
|
||||
{ DIS_ARG_WORD, DIS_ARG_NONE, true }, // DIS_OP_IF_FALSE_JUMP
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_FN_CALL
|
||||
{ DIS_ARG_WORD, DIS_ARG_NONE, false }, // DIS_OP_FN_RETURN
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_POP_STACK
|
||||
{ DIS_ARG_NONE, DIS_ARG_NONE, false }, // DIS_OP_TERNARY
|
||||
};
|
||||
|
||||
typedef struct dis_program_s {
|
||||
uint8_t *program;
|
||||
uint32_t len;
|
||||
uint32_t pc;
|
||||
} dis_program_t;
|
||||
|
||||
typedef struct fun_code_s {
|
||||
uint32_t start;
|
||||
uint32_t len;
|
||||
char *fun;
|
||||
} fun_code_t;
|
||||
|
||||
typedef struct lit_s {
|
||||
char *fun;
|
||||
char *str;
|
||||
} *lit_t;
|
||||
|
||||
uint32_t jump_label;
|
||||
uint32_t function_queue_len = 0;
|
||||
uint32_t lit_fn_queue_len = 0;
|
||||
queue_node_t *function_queue_front = NULL;
|
||||
queue_node_t *function_queue_rear = NULL;
|
||||
queue_node_t *lit_fn_queue_front = NULL;
|
||||
queue_node_t *lit_fn_queue_rear = NULL;
|
||||
|
||||
static void dis_print_opcode(uint8_t op);
|
||||
|
||||
static uint8_t readByte(const uint8_t *tb, uint32_t *count) {
|
||||
uint8_t ret = *(uint8_t*) (tb + *count);
|
||||
*count += 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint16_t readWord(const uint8_t *tb, uint32_t *count) {
|
||||
uint16_t ret = 0;
|
||||
memcpy(&ret, tb + *count, 2);
|
||||
*count += 2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int32_t readInt(const uint8_t *tb, uint32_t *count) {
|
||||
int ret = 0;
|
||||
memcpy(&ret, tb + *count, 4);
|
||||
*count += 4;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static float readFloat(const uint8_t *tb, uint32_t *count) {
|
||||
float ret = 0;
|
||||
memcpy(&ret, tb + *count, 4);
|
||||
*count += 4;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char* readString(const uint8_t *tb, uint32_t *count) {
|
||||
const unsigned char *ret = tb + *count;
|
||||
*count += strlen((char*) ret) + 1; //+1 for null character
|
||||
return (char*) ret;
|
||||
}
|
||||
|
||||
static void consumeByte(uint8_t byte, uint8_t *tb, uint32_t *count) {
|
||||
if (byte != tb[*count]) {
|
||||
printf("[internal] Failed to consume the correct byte (expected %u, found %u)\n", byte, tb[*count]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void dis_disassembler_init(dis_program_t **prg) {
|
||||
(*prg) = malloc(sizeof(struct dis_program_s));
|
||||
(*prg)->program = NULL;
|
||||
(*prg)->len = 0;
|
||||
(*prg)->pc = 0;
|
||||
}
|
||||
|
||||
static void dis_disassembler_deinit(dis_program_t **prg) {
|
||||
if((*prg)->program != NULL)
|
||||
free((*prg)->program);
|
||||
free((*prg));
|
||||
}
|
||||
|
||||
static uint8_t dis_load_file(const char *filename, dis_program_t **prg, bool alt_fmt) {
|
||||
FILE *f;
|
||||
size_t fsize, bytes;
|
||||
uint32_t count = 0;
|
||||
uint8_t buf = 0;
|
||||
|
||||
f = fopen(filename, "r");
|
||||
if (f == NULL) {
|
||||
printf("Not able to open the file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
fsize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
(*prg)->program = malloc(fsize * sizeof(uint8_t));
|
||||
|
||||
while ((bytes = fread(&buf, sizeof(uint8_t), 1, f)) == 1)
|
||||
(*prg)->program[count++] = buf;
|
||||
|
||||
(*prg)->len = fsize;
|
||||
|
||||
if (!alt_fmt)
|
||||
printf("\nFile: %s\nSize: %zu\n", filename, fsize);
|
||||
else
|
||||
printf("\n.comment File: %s, Size: %zu\n", filename, fsize);
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dis_read_header(dis_program_t **prg, bool alt_fmt) {
|
||||
const unsigned char major = readByte((*prg)->program, &((*prg)->pc));
|
||||
const unsigned char minor = readByte((*prg)->program, &((*prg)->pc));
|
||||
const unsigned char patch = readByte((*prg)->program, &((*prg)->pc));
|
||||
const char *build = readString((*prg)->program, &((*prg)->pc));
|
||||
|
||||
if (!alt_fmt)
|
||||
printf("[Header Version: %d.%d.%d (%s)]\n", major, minor, patch, build);
|
||||
else
|
||||
printf(".comment Header Version: %d.%d.%d (%s)\n", major, minor, patch, build);
|
||||
}
|
||||
|
||||
static void dis_print_opcode(uint8_t op) {
|
||||
if (op == 255) {
|
||||
printf("SECTION_END");
|
||||
return;
|
||||
}
|
||||
|
||||
if (op < DIS_OP_END_OPCODES)
|
||||
printf("%s", (OP_STR[op] + 7));
|
||||
else
|
||||
printf("(OP UNKNOWN [%c])", op);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define S_OP(n, p) \
|
||||
switch (OP_ARGS[opcode][n]) { \
|
||||
case DIS_ARG_NONE: \
|
||||
break; \
|
||||
case DIS_ARG_BYTE: \
|
||||
uint = readByte((*prg)->program, &pc); \
|
||||
if (p) printf(" b(%d)", uint); \
|
||||
break; \
|
||||
case DIS_ARG_WORD: \
|
||||
uint = readWord((*prg)->program, &pc);\
|
||||
if (p) printf(" w(%d)", uint); \
|
||||
break; \
|
||||
case DIS_ARG_INTEGER: \
|
||||
intg = readInt((*prg)->program, &pc); \
|
||||
if (p) printf(" i(%d)", intg); \
|
||||
break; \
|
||||
case DIS_ARG_FLOAT: \
|
||||
flt = readFloat((*prg)->program, &pc); \
|
||||
if (p) printf(" f(%f)", flt); \
|
||||
break; \
|
||||
case DIS_ARG_STRING: \
|
||||
str = readString((*prg)->program, &pc); \
|
||||
if (p) printf(" s(%s)", str); \
|
||||
break; \
|
||||
default: \
|
||||
printf("ERROR, unknown argument type\n"); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
static void dis_disassemble_section(dis_program_t **prg, uint32_t pc, uint32_t len, uint8_t spaces, bool is_function, options_t config) {
|
||||
uint8_t opcode = 0;
|
||||
uint16_t uint = 0;
|
||||
int32_t intg = 0;
|
||||
float flt = 0;
|
||||
char *str = NULL;
|
||||
|
||||
// first 4 bytes of the program section within a function are actually specifying the parameter and return lists
|
||||
if (is_function) {
|
||||
printf("\n");
|
||||
uint16_t args = readWord((*prg)->program, &pc);
|
||||
uint16_t rets = readWord((*prg)->program, &pc);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| ");
|
||||
} else
|
||||
printf(" .comment args:%d, rets:%d", args, rets);
|
||||
}
|
||||
|
||||
uint32_t pc_start = pc;
|
||||
|
||||
uint32_t labels_qty = 0;
|
||||
uint16_t *label_line = NULL;
|
||||
uint32_t *label_id = NULL;
|
||||
if (config.alt_format_flag) {
|
||||
// first pass: search jump labels
|
||||
label_line = malloc(sizeof(uint16_t));
|
||||
label_id = malloc(sizeof(uint32_t));
|
||||
|
||||
while (pc < len) {
|
||||
label_line = realloc(label_line, (labels_qty + 1) * sizeof(uint16_t));
|
||||
label_id = realloc(label_id, (labels_qty + 1) * sizeof(uint32_t));
|
||||
|
||||
opcode = (*prg)->program[pc];
|
||||
if (config.alt_format_flag && (opcode == 255 || opcode == 0)) {
|
||||
++pc;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (opcode > DIS_OP_END_OPCODES)
|
||||
continue;
|
||||
|
||||
++pc;
|
||||
|
||||
S_OP(0, 0);
|
||||
|
||||
if (OP_ARGS[opcode][2]) {
|
||||
label_line[labels_qty] = uint;
|
||||
label_id[labels_qty] = jump_label++;
|
||||
++labels_qty;
|
||||
}
|
||||
|
||||
S_OP(1, 0);
|
||||
}
|
||||
|
||||
pc = pc_start;
|
||||
}
|
||||
|
||||
while (pc < len) {
|
||||
opcode = (*prg)->program[pc];
|
||||
|
||||
if (config.alt_format_flag) {
|
||||
for (uint32_t lbl = 0; lbl < labels_qty; lbl++) {
|
||||
if (pc - pc_start == label_line[lbl]) {
|
||||
printf("\nJL_%04d_:", label_id[lbl]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.alt_format_flag && (opcode == 255 || opcode == 0)) {
|
||||
++pc;
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| ");
|
||||
printf("[%05d](%03d) ", (pc++) - pc_start, opcode);
|
||||
} else {
|
||||
printf(" ");
|
||||
pc++;
|
||||
}
|
||||
|
||||
dis_print_opcode(opcode);
|
||||
|
||||
if (opcode >= DIS_OP_END_OPCODES)
|
||||
continue;
|
||||
|
||||
if (config.alt_format_flag) {
|
||||
if (OP_ARGS[opcode][2]) {
|
||||
uint = readWord((*prg)->program, &pc);
|
||||
for (uint32_t lbl = 0; lbl < labels_qty; lbl++) {
|
||||
if (uint == label_line[lbl]) {
|
||||
printf(" JL_%04d_", label_id[lbl]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
S_OP(0, 1);
|
||||
} else
|
||||
S_OP(0, 1);
|
||||
|
||||
S_OP(1, 1);
|
||||
}
|
||||
|
||||
if (config.alt_format_flag) {
|
||||
free(label_line);
|
||||
free(label_id);
|
||||
}
|
||||
|
||||
if (config.alt_format_flag && (*prg)->program[pc - 5] != DIS_OP_FN_RETURN)
|
||||
printf("\n FN_RETURN w(0)");
|
||||
}
|
||||
|
||||
#define LIT_ADD(a, b, c) b[c] = a; ++c;
|
||||
static void dis_read_interpreter_sections(dis_program_t **prg, uint32_t *pc, uint8_t spaces, char *tree, options_t config) {
|
||||
uint32_t literal_count = 0;
|
||||
uint8_t literal_type[65536];
|
||||
char *lit_str = NULL;
|
||||
|
||||
const unsigned short literalCount = readWord((*prg)->program, pc);
|
||||
|
||||
if(!config.group_flag)
|
||||
printf("\n");
|
||||
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| ");
|
||||
printf(" ");
|
||||
printf("--- ( Reading %d literals from cache ) ---\n", literalCount);
|
||||
}
|
||||
|
||||
if (config.alt_format_flag)
|
||||
lit_str = calloc(1, sizeof(char));
|
||||
|
||||
for (int i = 0; i < literalCount; i++) {
|
||||
const unsigned char literalType = readByte((*prg)->program, pc);
|
||||
|
||||
switch (literalType) {
|
||||
case DIS_LITERAL_NULL:
|
||||
LIT_ADD(DIS_LITERAL_NULL, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( null )\n", i);
|
||||
} else {
|
||||
str_append(&lit_str, " .lit NULL\n");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_BOOLEAN: {
|
||||
const bool b = readByte((*prg)->program, pc);
|
||||
LIT_ADD(DIS_LITERAL_BOOLEAN, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( boolean %s )\n", i, b ? "true" : "false");
|
||||
} else {
|
||||
char bs[10];
|
||||
sprintf(bs, "%s\n", b ? "true" : "false");
|
||||
str_append(&lit_str, " .lit BOOLEAN ");
|
||||
str_append(&lit_str, bs);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_INTEGER: {
|
||||
const int d = readInt((*prg)->program, pc);
|
||||
LIT_ADD(DIS_LITERAL_INTEGER, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( integer %d )\n", i, d);
|
||||
} else {
|
||||
char ds[20];
|
||||
sprintf(ds, "%d\n", d);
|
||||
str_append(&lit_str, " .lit INTEGER ");
|
||||
str_append(&lit_str, ds);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_FLOAT: {
|
||||
const float f = readFloat((*prg)->program, pc);
|
||||
LIT_ADD(DIS_LITERAL_FLOAT, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( float %f )\n", i, f);
|
||||
} else {
|
||||
char fs[20];
|
||||
sprintf(fs, "%f\n", f);
|
||||
str_append(&lit_str, " .lit FLOAT ");
|
||||
str_append(&lit_str, fs);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_STRING: {
|
||||
const char *s = readString((*prg)->program, pc);
|
||||
LIT_ADD(DIS_LITERAL_STRING, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( string \"%s\" )\n", i, s);
|
||||
} else {
|
||||
str_append(&lit_str, " .lit STRING \"");
|
||||
str_append(&lit_str, s);
|
||||
str_append(&lit_str, "\"\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_ARRAY_INTERMEDIATE:
|
||||
case DIS_LITERAL_ARRAY: {
|
||||
unsigned short length = readWord((*prg)->program, pc);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( array ", i);
|
||||
} else {
|
||||
str_append(&lit_str, " .lit ARRAY ");
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = readWord((*prg)->program, pc);
|
||||
if (!config.alt_format_flag) {
|
||||
printf("%d ", index);
|
||||
} else {
|
||||
char ds[20];
|
||||
sprintf(ds, "%d ", index);
|
||||
str_append(&lit_str, ds);
|
||||
|
||||
}
|
||||
LIT_ADD(DIS_LITERAL_NULL, literal_type, literal_count);
|
||||
if (!(i % 15) && i != 0) {
|
||||
if (!config.alt_format_flag) {
|
||||
printf("\\\n");
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf(" ");
|
||||
} else {
|
||||
str_append(&lit_str, "\\\n ");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!config.alt_format_flag) {
|
||||
printf(")");
|
||||
printf("\n");
|
||||
} else {
|
||||
str_append(&lit_str, "\n");
|
||||
}
|
||||
|
||||
LIT_ADD(DIS_LITERAL_ARRAY, literal_type, literal_count);
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_DICTIONARY_INTERMEDIATE:
|
||||
case DIS_LITERAL_DICTIONARY: {
|
||||
unsigned short length = readWord((*prg)->program, pc);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( dictionary ", i);
|
||||
} else {
|
||||
str_append(&lit_str, " .lit DICTIONARY ");
|
||||
}
|
||||
for (int i = 0; i < length / 2; i++) {
|
||||
int key = readWord((*prg)->program, pc);
|
||||
int val = readWord((*prg)->program, pc);
|
||||
|
||||
if (!config.alt_format_flag)
|
||||
printf("(key: %d, val:%d) ", key, val);
|
||||
else {
|
||||
char s[100];
|
||||
sprintf(s, "%d,%d ", key, val);
|
||||
str_append(&lit_str, s);
|
||||
}
|
||||
|
||||
if (!(i % 5) && i != 0) {
|
||||
if (!config.alt_format_flag) {
|
||||
printf("\\\n");
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf(" ");
|
||||
} else {
|
||||
str_append(&lit_str, "\\\n ");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!config.alt_format_flag) {
|
||||
printf(")");
|
||||
printf("\n");
|
||||
} else {
|
||||
str_append(&lit_str, "\n");
|
||||
}
|
||||
LIT_ADD(DIS_LITERAL_DICTIONARY, literal_type, literal_count);
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_FUNCTION: {
|
||||
unsigned short index = readWord((*prg)->program, pc);
|
||||
LIT_ADD(DIS_LITERAL_FUNCTION_INTERMEDIATE, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( function index: %d )\n", i, index);
|
||||
} else {
|
||||
char s[100];
|
||||
sprintf(s, " .lit FUNCTION %d\n", index);
|
||||
str_append(&lit_str, s);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_IDENTIFIER: {
|
||||
const char *str = readString((*prg)->program, pc);
|
||||
LIT_ADD(DIS_LITERAL_IDENTIFIER, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( identifier %s )\n", i, str);
|
||||
} else {
|
||||
str_append(&lit_str, " .lit IDENTIFIER ");
|
||||
str_append(&lit_str, str);
|
||||
str_append(&lit_str, "\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_TYPE:
|
||||
case DIS_LITERAL_TYPE_INTERMEDIATE: {
|
||||
uint8_t literalType = readByte((*prg)->program, pc);
|
||||
uint8_t constant = readByte((*prg)->program, pc);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( type %s: %d)\n", i, (LIT_STR[literalType] + 12), constant);
|
||||
} else {
|
||||
char s[100];
|
||||
sprintf(s, " .lit TYPE %s %d", (LIT_STR[literalType] + 12), constant);
|
||||
str_append(&lit_str, s);
|
||||
}
|
||||
|
||||
if (literalType == DIS_LITERAL_ARRAY) {
|
||||
uint16_t vt = readWord((*prg)->program, pc);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("\n ( subtype: %d)\n", vt);
|
||||
} else {
|
||||
char s[100];
|
||||
sprintf(s, " SUBTYPE %d\n", vt);
|
||||
str_append(&lit_str, s);
|
||||
}
|
||||
} else
|
||||
if (literalType == DIS_LITERAL_DICTIONARY) {
|
||||
uint8_t kt = readWord((*prg)->program, pc);
|
||||
uint8_t vt = readWord((*prg)->program, pc);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("\n ( subtype: [%d, %d] )\n\n\n", kt, vt);
|
||||
} else {
|
||||
char s[100];
|
||||
sprintf(s, " SUBTYPE %d,%d\n", kt, vt);
|
||||
str_append(&lit_str, s);
|
||||
}
|
||||
} else {
|
||||
if (!config.alt_format_flag)
|
||||
printf("\n");
|
||||
else
|
||||
str_append(&lit_str, "\n");
|
||||
}
|
||||
|
||||
LIT_ADD(literalType, literal_type, literal_count);
|
||||
}
|
||||
break;
|
||||
|
||||
case DIS_LITERAL_INDEX_BLANK:
|
||||
LIT_ADD(DIS_LITERAL_INDEX_BLANK, literal_type, literal_count);
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("[%05d] ( blank )\n", i);
|
||||
} else {
|
||||
str_append(&lit_str, " .lit BLANK\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.group_flag) {
|
||||
printf(lit_str);
|
||||
} else {
|
||||
lit_t fn_str = (lit_t)(lit_fn_queue_rear->data);
|
||||
fn_str->str = calloc(1, strlen(lit_str) + 1);
|
||||
strcpy(fn_str->str, lit_str);
|
||||
}
|
||||
free(lit_str);
|
||||
|
||||
consumeByte(DIS_OP_SECTION_END, (*prg)->program, pc);
|
||||
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| ");
|
||||
printf("--- ( end literal section ) ---\n");
|
||||
}
|
||||
|
||||
int functionCount = readWord((*prg)->program, pc);
|
||||
int functionSize = readWord((*prg)->program, pc);
|
||||
|
||||
if (functionCount) {
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("|\n");
|
||||
SPC(spaces);
|
||||
printf("| ");
|
||||
printf("--- ( fn count: %d, total size: %d ) ---\n", functionCount, functionSize);
|
||||
}
|
||||
|
||||
uint32_t fcnt = 0;
|
||||
char tree_local[2048];
|
||||
|
||||
for (uint32_t i = 0; i < literal_count; i++) {
|
||||
if (literal_type[i] == DIS_LITERAL_FUNCTION_INTERMEDIATE) {
|
||||
size_t size = (size_t) readWord((*prg)->program, pc);
|
||||
|
||||
uint32_t fpc_start = *pc;
|
||||
uint32_t fpc_end = *pc + size - 1;
|
||||
|
||||
tree_local[0] = '\0';
|
||||
if (!config.alt_format_flag) {
|
||||
sprintf(tree_local, "%s.%d", tree, fcnt);
|
||||
if (tree_local[0] == '_')
|
||||
memcpy(tree_local, tree_local + 1, strlen(tree_local));
|
||||
} else {
|
||||
sprintf(tree_local, "%s_%d", tree, fcnt);
|
||||
if (tree_local[0] == '_')
|
||||
memcpy(tree_local, tree_local + 1, strlen(tree_local));
|
||||
}
|
||||
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| |\n");
|
||||
SPC(spaces);
|
||||
printf("| | ");
|
||||
printf("( fun %s [ start: %d, end: %d ] )", tree_local, fpc_start, fpc_end);
|
||||
} else {
|
||||
if (!config.group_flag)
|
||||
printf("\nLIT_FUN_%s:", tree_local);
|
||||
else {
|
||||
lit_t new_lit = malloc(sizeof(struct lit_s));
|
||||
new_lit->fun = calloc(1, strlen(tree_local) + 1);
|
||||
strcpy(new_lit->fun, tree_local);
|
||||
dis_enqueue((void*) new_lit, &lit_fn_queue_front, &lit_fn_queue_rear, &lit_fn_queue_len);
|
||||
}
|
||||
}
|
||||
|
||||
if ((*prg)->program[*pc + size - 1] != DIS_OP_FN_END) {
|
||||
printf("\nERROR: Failed to find function end\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dis_read_interpreter_sections(prg, &fpc_start, spaces + 4, tree_local, config);
|
||||
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("| | |\n");
|
||||
SPC(spaces + 4);
|
||||
printf("| ");
|
||||
printf("--- ( reading code for %s ) ---", tree_local);
|
||||
dis_disassemble_section(prg, fpc_start, fpc_end, spaces + 4, true, config);
|
||||
printf("\n");
|
||||
SPC(spaces + 4);
|
||||
printf("| ");
|
||||
printf("--- ( end code section ) ---\n");
|
||||
} else {
|
||||
fun_code_t *fun = malloc(sizeof(struct fun_code_s));
|
||||
fun->fun = malloc(strlen(tree_local) + 1);
|
||||
strcpy(fun->fun, tree_local);
|
||||
fun->start = fpc_start;
|
||||
fun->len = fpc_end;
|
||||
dis_enqueue((void*) fun, &function_queue_front, &function_queue_rear, &function_queue_len);
|
||||
}
|
||||
|
||||
fcnt++;
|
||||
*pc += size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.alt_format_flag) {
|
||||
SPC(spaces);
|
||||
printf("|\n");
|
||||
SPC(spaces);
|
||||
printf("| ");
|
||||
printf("--- ( end fn section ) ---\n");
|
||||
}
|
||||
}
|
||||
|
||||
consumeByte(DIS_OP_SECTION_END, (*prg)->program, pc);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void disassemble(const char *filename, options_t config) {
|
||||
dis_program_t *prg;
|
||||
|
||||
jump_label = 0;
|
||||
|
||||
dis_disassembler_init(&prg);
|
||||
if (dis_load_file(filename, &prg, config.alt_format_flag)) {
|
||||
dis_disassembler_deinit(&prg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dis_read_header(&prg, config.alt_format_flag);
|
||||
|
||||
printf("\n.start MAIN\n");
|
||||
|
||||
consumeByte(DIS_OP_SECTION_END, prg->program, &(prg->pc));
|
||||
|
||||
if (!config.group_flag) {
|
||||
if (config.alt_format_flag)
|
||||
printf("\nLIT_MAIN:");
|
||||
|
||||
dis_read_interpreter_sections(&prg, &(prg->pc), 0, "", config);
|
||||
|
||||
if (!config.alt_format_flag) {
|
||||
printf("|\n| ");
|
||||
printf("--- ( reading main code ) ---");
|
||||
} else
|
||||
printf("\nMAIN:");
|
||||
|
||||
dis_disassemble_section(&prg, prg->pc, prg->len, 0, false, config);
|
||||
|
||||
if (!config.alt_format_flag) {
|
||||
printf("\n| ");
|
||||
printf("--- ( end main code section ) ---");
|
||||
} else
|
||||
printf("\n");
|
||||
|
||||
if (config.alt_format_flag) {
|
||||
while (function_queue_front != NULL) {
|
||||
fun_code_t *fun = (fun_code_t*) function_queue_front->data;
|
||||
printf("\nFUN_%s:", fun->fun);
|
||||
free(fun->fun);
|
||||
|
||||
dis_disassemble_section(&prg, fun->start, fun->len, 0, true, config);
|
||||
|
||||
dis_dequeue(&function_queue_front, &function_queue_rear, &function_queue_len);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
config.alt_format_flag = true;
|
||||
|
||||
lit_t new_lit = malloc(sizeof(struct lit_s));
|
||||
new_lit->fun = calloc(1, 6 * sizeof(char));
|
||||
strcpy(new_lit->fun, "MAIN");
|
||||
dis_enqueue((void*) new_lit, &lit_fn_queue_front, &lit_fn_queue_rear, &lit_fn_queue_len);
|
||||
|
||||
dis_read_interpreter_sections(&prg, &(prg->pc), 0, "", config);
|
||||
printf("\n");
|
||||
|
||||
while (lit_fn_queue_front != NULL) {
|
||||
lit_t litf = (lit_t) lit_fn_queue_front->data;
|
||||
|
||||
if (!strcmp(litf->fun, "MAIN")) {
|
||||
printf("MAIN:\n");
|
||||
printf("%s", litf->str);
|
||||
dis_disassemble_section(&prg, prg->pc, prg->len, 0, false, config);
|
||||
free(litf->fun);
|
||||
free(litf->str);
|
||||
dis_dequeue(&lit_fn_queue_front, &lit_fn_queue_rear, &lit_fn_queue_len);
|
||||
printf("\n\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("FUNCTION_%s:\n", litf->fun);
|
||||
printf("%s", litf->str);
|
||||
|
||||
queue_node_t *fqf = function_queue_front;
|
||||
while (fqf != NULL) {
|
||||
fun_code_t *fun = (fun_code_t*) fqf->data;
|
||||
if (!strcmp(fun->fun, litf->fun)) {
|
||||
dis_disassemble_section(&prg, fun->start, fun->len, 0, true, config);
|
||||
break;
|
||||
}
|
||||
fqf = fqf->next;
|
||||
}
|
||||
|
||||
free(litf->fun);
|
||||
free(litf->str);
|
||||
dis_dequeue(&lit_fn_queue_front, &lit_fn_queue_rear, &lit_fn_queue_len);
|
||||
|
||||
printf("\n\n");
|
||||
}
|
||||
|
||||
while (function_queue_front != NULL) {
|
||||
free(((fun_code_t*)(function_queue_front->data))->fun);
|
||||
dis_dequeue(&function_queue_front, &function_queue_rear, &function_queue_len);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
dis_disassembler_deinit(&prg);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* disassembler.c
|
||||
*
|
||||
* Created on: 10 ago. 2023
|
||||
* Original Author: Emiliano Augusto Gonzalez (egonzalez . hiperion @ gmail . com)
|
||||
*
|
||||
* Further modified by Kayne Ruse, and added to the Toy Programming Language tool repository.
|
||||
*/
|
||||
|
||||
#ifndef DISASSEMBLER_H_
|
||||
#define DISASSEMBLER_H_
|
||||
|
||||
typedef struct options_s {
|
||||
bool alt_format_flag;
|
||||
bool group_flag;
|
||||
} options_t;
|
||||
|
||||
typedef enum DIS_OPCODES {
|
||||
DIS_OP_EOF, //
|
||||
|
||||
// do nothing
|
||||
DIS_OP_PASS, //
|
||||
|
||||
// basic statements
|
||||
DIS_OP_ASSERT, //
|
||||
DIS_OP_PRINT, //
|
||||
|
||||
// data
|
||||
DIS_OP_LITERAL, //
|
||||
DIS_OP_LITERAL_LONG, //
|
||||
DIS_OP_LITERAL_RAW, //
|
||||
|
||||
// arithmetic operators
|
||||
DIS_OP_NEGATE, //
|
||||
DIS_OP_ADDITION, //
|
||||
DIS_OP_SUBTRACTION, //
|
||||
DIS_OP_MULTIPLICATION, //
|
||||
DIS_OP_DIVISION, //
|
||||
DIS_OP_MODULO, //
|
||||
DIS_OP_GROUPING_BEGIN, //
|
||||
DIS_OP_GROUPING_END, //
|
||||
|
||||
// variable stuff
|
||||
DIS_OP_SCOPE_BEGIN, //
|
||||
DIS_OP_SCOPE_END, //
|
||||
|
||||
DIS_OP_TYPE_DECL_removed, // deprecated
|
||||
DIS_OP_TYPE_DECL_LONG_removed, // deprecated
|
||||
|
||||
DIS_OP_VAR_DECL, //
|
||||
DIS_OP_VAR_DECL_LONG, //
|
||||
|
||||
DIS_OP_FN_DECL, //
|
||||
DIS_OP_FN_DECL_LONG, //
|
||||
|
||||
DIS_OP_VAR_ASSIGN, //
|
||||
DIS_OP_VAR_ADDITION_ASSIGN, //
|
||||
DIS_OP_VAR_SUBTRACTION_ASSIGN, //
|
||||
DIS_OP_VAR_MULTIPLICATION_ASSIGN, //
|
||||
DIS_OP_VAR_DIVISION_ASSIGN, //
|
||||
DIS_OP_VAR_MODULO_ASSIGN, //
|
||||
|
||||
DIS_OP_TYPE_CAST, //
|
||||
DIS_OP_TYPE_OF, //
|
||||
|
||||
DIS_OP_IMPORT, //
|
||||
DIS_OP_EXPORT_removed, // deprecated
|
||||
|
||||
// for indexing
|
||||
DIS_OP_INDEX, //
|
||||
DIS_OP_INDEX_ASSIGN, //
|
||||
DIS_OP_INDEX_ASSIGN_INTERMEDIATE, //
|
||||
DIS_OP_DOT, //
|
||||
|
||||
// comparison of values
|
||||
DIS_OP_COMPARE_EQUAL, //
|
||||
DIS_OP_COMPARE_NOT_EQUAL, //
|
||||
DIS_OP_COMPARE_LESS, //
|
||||
DIS_OP_COMPARE_LESS_EQUAL, //
|
||||
DIS_OP_COMPARE_GREATER, //
|
||||
DIS_OP_COMPARE_GREATER_EQUAL, //
|
||||
DIS_OP_INVERT, //
|
||||
|
||||
// logical operators
|
||||
DIS_OP_AND, //
|
||||
DIS_OP_OR, //
|
||||
|
||||
// jumps, and conditional jumps (absolute)
|
||||
DIS_OP_JUMP, //
|
||||
DIS_OP_IF_FALSE_JUMP, //
|
||||
DIS_OP_FN_CALL, //
|
||||
DIS_OP_FN_RETURN, //
|
||||
|
||||
// pop the stack at the end of a complex statement
|
||||
DIS_OP_POP_STACK, //
|
||||
|
||||
//ternary shorthand
|
||||
DIS_OP_TERNARY, //
|
||||
|
||||
//meta
|
||||
DIS_OP_FN_END, // different from SECTION_END
|
||||
DIS_OP_END_OPCODES, // mark for end opcodes list. Not valid opcode
|
||||
DIS_OP_SECTION_END = 255,
|
||||
} dis_opcode_t;
|
||||
|
||||
typedef enum DIS_LITERAL_TYPE {
|
||||
DIS_LITERAL_NULL, //
|
||||
DIS_LITERAL_BOOLEAN, //
|
||||
DIS_LITERAL_INTEGER, //
|
||||
DIS_LITERAL_FLOAT, //
|
||||
DIS_LITERAL_STRING, //
|
||||
DIS_LITERAL_ARRAY, //
|
||||
DIS_LITERAL_DICTIONARY, //
|
||||
DIS_LITERAL_FUNCTION, //
|
||||
DIS_LITERAL_IDENTIFIER, //
|
||||
DIS_LITERAL_TYPE, //
|
||||
DIS_LITERAL_OPAQUE, //
|
||||
DIS_LITERAL_ANY, //
|
||||
|
||||
// these are meta-level types - not for general use
|
||||
DIS_LITERAL_TYPE_INTERMEDIATE, // used to process types in the compiler only
|
||||
DIS_LITERAL_ARRAY_INTERMEDIATE, // used to process arrays in the compiler only
|
||||
DIS_LITERAL_DICTIONARY_INTERMEDIATE, // used to process dictionaries in the compiler only
|
||||
DIS_LITERAL_FUNCTION_INTERMEDIATE, // used to process functions in the compiler only
|
||||
DIS_LITERAL_FUNCTION_ARG_REST, // used to process function rest parameters only
|
||||
DIS_LITERAL_FUNCTION_NATIVE, // for handling native functions only
|
||||
DIS_LITERAL_FUNCTION_HOOK, // for handling hook functions within literals only
|
||||
DIS_LITERAL_INDEX_BLANK, // for blank indexing i.e. arr[:]
|
||||
} dis_literal_type_t;
|
||||
|
||||
extern void disassemble(const char *filename, options_t config);
|
||||
|
||||
#endif /* DISASSEMBLER_H_ */
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* utils.c
|
||||
*
|
||||
* Created on: 10 ago. 2023
|
||||
* Original Author: Emiliano Augusto Gonzalez (egonzalez . hiperion @ gmail . com)
|
||||
*
|
||||
* Further modified by Kayne Ruse, and added to the Toy Programming Language tool repository.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "disassembler_utils.h"
|
||||
|
||||
void dis_enqueue(void *x, queue_node_t **queue_front, queue_node_t **queue_rear, uint32_t *len) {
|
||||
queue_node_t *temp;
|
||||
|
||||
temp = (queue_node_t*) malloc(sizeof(struct queue_node_s));
|
||||
temp->data = x;
|
||||
temp->next = NULL;
|
||||
|
||||
if ((*queue_front) == NULL && (*queue_rear) == NULL) {
|
||||
(*queue_front) = (*queue_rear) = temp;
|
||||
++(*len);
|
||||
return;
|
||||
}
|
||||
(*queue_rear)->next = temp;
|
||||
(*queue_rear) = temp;
|
||||
|
||||
++(*len);
|
||||
}
|
||||
|
||||
void dis_dequeue(queue_node_t **queue_front, queue_node_t **queue_rear, uint32_t *len) {
|
||||
struct queue_node_s *temp = (*queue_front);
|
||||
|
||||
if ((*queue_front) == NULL) {
|
||||
printf("Error : QUEUE is empty!!");
|
||||
return;
|
||||
}
|
||||
if ((*queue_front) == (*queue_rear))
|
||||
(*queue_front) = (*queue_rear) = NULL;
|
||||
|
||||
else
|
||||
(*queue_front) = (*queue_front)->next;
|
||||
|
||||
--(*len);
|
||||
free(temp->data);
|
||||
free(temp);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
void str_append(char **str, const char *app) {
|
||||
if ((*str) == NULL)
|
||||
return;
|
||||
|
||||
*str = realloc(*str, (strlen(*str) + strlen(app) + 1) * sizeof(char));
|
||||
memcpy((*str) + strlen(*str), app, strlen(app) + 1);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* utils.h
|
||||
*
|
||||
* Created on: 10 ago. 2023
|
||||
* Original Author: Emiliano Augusto Gonzalez (egonzalez . hiperion @ gmail . com)
|
||||
*
|
||||
* Further modified by Kayne Ruse, and added to the Toy Programming Language tool repository.
|
||||
*/
|
||||
|
||||
#ifndef UTILS_H_
|
||||
#define UTILS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct queue_node_s {
|
||||
void *data;
|
||||
struct queue_node_s *next;
|
||||
} queue_node_t;
|
||||
|
||||
void dis_enqueue(void *x, queue_node_t **queue_front, queue_node_t **queue_rear, uint32_t *len);
|
||||
void dis_dequeue(queue_node_t **queue_front, queue_node_t **queue_rear, uint32_t *len);
|
||||
|
||||
void str_append(char **str, const char *app);
|
||||
|
||||
#endif /* UTILS_H_ */
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cargs.h"
|
||||
#include "disassembler.h"
|
||||
|
||||
static struct cag_option options[] = {
|
||||
{
|
||||
.identifier = 'a',
|
||||
.access_letters = "a",
|
||||
.access_name = NULL,
|
||||
.value_name = NULL,
|
||||
.description = "Alternate format"
|
||||
}, {
|
||||
.identifier = 'g',
|
||||
.access_letters = "g",
|
||||
.access_name = NULL,
|
||||
.value_name = NULL,
|
||||
.description = "Group literals with functions"
|
||||
}, {
|
||||
.identifier = 'h',
|
||||
.access_letters = "h",
|
||||
.access_name = "help",
|
||||
.description = "Shows the command help"
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char identifier;
|
||||
cag_option_context context;
|
||||
options_t config = { false, false };
|
||||
|
||||
cag_option_prepare(&context, options, CAG_ARRAY_SIZE(options), argc, argv);
|
||||
while (cag_option_fetch(&context)) {
|
||||
identifier = cag_option_get(&context);
|
||||
switch (identifier) {
|
||||
case 'a':
|
||||
config.alt_format_flag = true;
|
||||
break;
|
||||
case 'g':
|
||||
config.group_flag = true;
|
||||
config.alt_format_flag = true;
|
||||
break;
|
||||
case 'h':
|
||||
printf("Usage: disassembler [OPTION] file\n");
|
||||
cag_option_print(options, CAG_ARRAY_SIZE(options), stdout);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
disassemble(argv[context.index], config);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
CC=gcc
|
||||
|
||||
IDIR+=.
|
||||
CFLAGS+=$(addprefix -I,$(IDIR)) -g -Wall -W -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable
|
||||
LIBS+=
|
||||
|
||||
ODIR = obj
|
||||
SRC = $(wildcard *.c)
|
||||
OBJ = $(addprefix $(ODIR)/,$(SRC:.c=.o))
|
||||
OUTDIR=../../out
|
||||
OUT=$(OUTDIR)/disassembler
|
||||
|
||||
all: $(OBJ)
|
||||
$(CC) $(CFLAGS) -o $(OUT) $(OBJ) $(LIBS)
|
||||
|
||||
$(OBJ): | $(ODIR)
|
||||
|
||||
$(ODIR):
|
||||
mkdir $(ODIR)
|
||||
|
||||
$(ODIR)/%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
$(RM) -r $(ODIR)
|
||||
Reference in New Issue
Block a user