diff --git a/.github/workflows/continuous-integration-v2.yml b/.github/workflows/continuous-integration-v2.yml index b547260..39fc9d8 100644 --- a/.github/workflows/continuous-integration-v2.yml +++ b/.github/workflows/continuous-integration-v2.yml @@ -14,48 +14,73 @@ on: - v2 workflow_dispatch: -#CI workflows using a matrix +#CI workflows using the matrix strategy, skipping GDB if it's not supported for the platform jobs: run-test-cases: continue-on-error: true strategy: matrix: platforms: - - { os: ubuntu-latest, preinstall: sudo apt-get install gdb, gdb_skip: false } - - { os: windows-latest, preinstall: , gdb_skip: false } - - { os: macos-latest, preinstall: , gdb_skip: true } + - { os: ubuntu-latest, preinstall: sudo apt-get install gdb, gdb_enabled: true } + - { os: windows-latest, preinstall: , gdb_enabled: true } + - { os: macos-latest, preinstall: , gdb_enabled: false } commands: - - { exec: make tests, gdb: false } - - { exec: make tests-gdb, gdb: true } + - { exec: make test-cases, gdb: false } + - { exec: make test-cases-gdb, gdb: true } runs-on: ${{ matrix.platforms.os }} steps: - uses: actions/checkout@v4 - name: Preinstall dependencies + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true run: ${{ matrix.platforms.preinstall }} - - name: run the test cases - if: matrix.commands.gdb == false || matrix.platforms.gdb_skip == false + - name: run the tests + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true run: ${{ matrix.commands.exec }} - - #TODO: hook this up to real script files, preferably in the test section - run-test-repl-scripts: - continue-on-error: true + + run-test-integrations: needs: run-test-cases + continue-on-error: true strategy: matrix: platforms: - - { os: ubuntu-latest } - - { os: windows-latest } - - { os: macos-latest } + - { os: ubuntu-latest, preinstall: sudo apt-get install gdb, gdb_enabled: true } + - { os: windows-latest, preinstall: , gdb_enabled: true } + - { os: macos-latest, preinstall: , gdb_enabled: false } commands: - - { build: make repl, run: out/repl.exe -f '../scripts/example.toy' } - - { build: make repl, run: out/repl.exe -f '../scripts/example-print.toy' } - - { build: make repl, run: out/repl.exe -f '../scripts/example-variables.toy' } + - { exec: make test-integrations, gdb: false } + - { exec: make test-integrations-gdb, gdb: true } runs-on: ${{ matrix.platforms.os }} steps: - uses: actions/checkout@v4 - - name: compile the repl - run: ${{ matrix.commands.build }} - - name: run the repl scripts - run: ${{ matrix.commands.run }} + - name: Preinstall dependencies + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true + run: ${{ matrix.platforms.preinstall }} + - name: run the tests + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true + run: ${{ matrix.commands.exec }} + + run-test-benchmarks: + if: false #Not ready yet + needs: run-test-integrations + continue-on-error: true + strategy: + matrix: + platforms: + - { os: ubuntu-latest, preinstall: sudo apt-get install gdb, gdb_enabled: true } + - { os: windows-latest, preinstall: , gdb_enabled: true } + - { os: macos-latest, preinstall: , gdb_enabled: false } + commands: + - { exec: make test-benchmarks, gdb: false } + - { exec: make test-benchmarks-gdb, gdb: true } + + runs-on: ${{ matrix.platforms.os }} + steps: + - uses: actions/checkout@v4 + - name: Preinstall dependencies + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true + run: ${{ matrix.platforms.preinstall }} + - name: run the tests + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true + run: ${{ matrix.commands.exec }} diff --git a/.github/workflows/standalone_tests.yml b/.github/workflows/standalone_tests.yml new file mode 100644 index 0000000..69755ba --- /dev/null +++ b/.github/workflows/standalone_tests.yml @@ -0,0 +1,29 @@ +name: Standalone Tests + +#trigger when these occur +on: + workflow_dispatch: + +#These tests are more stand-alone than the others +jobs: + run-test-cases: + continue-on-error: true + strategy: + matrix: + platforms: + - { os: ubuntu-latest, preinstall: sudo apt-get install gdb, gdb_enabled: true } + - { os: windows-latest, preinstall: , gdb_enabled: true } + - { os: macos-latest, preinstall: , gdb_enabled: false } + commands: + - { exec: make -C tests/standalone -k, gdb: false } + - { exec: make -C tests/standalone gdb -k, gdb: true } + + runs-on: ${{ matrix.platforms.os }} + steps: + - uses: actions/checkout@v4 + - name: Preinstall dependencies + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true + run: ${{ matrix.platforms.preinstall }} + - name: run the tests + if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true + run: ${{ matrix.commands.exec }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca81755..57eedc7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ v2 is a ground-up rewrite, with additions, changes and deletions to the language The [Issue Tracker](https://github.com/Ratstail91/Toy/issues) is a good place to see what tasks and issues are currently waiting to be addressed. The [toy.h](https://github.com/Ratstail91/Toy/blob/v2/source/toy.h) source file is a quick way to see what building blocks are available in the source code. There are also a number of comments prepended with `TODO` scattered throughout the source code, as reminders of planned features. -The [test cases](https://github.com/Ratstail91/Toy/tree/v2/tests/cases), which test individual parts of the code in isolation, can be a good way to see how those parts are used. Likewise, the [REPL](https://github.com/Ratstail91/Toy/tree/v2/repl) shows a practical usage of Toy. +The [tests directory](https://github.com/Ratstail91/Toy/tree/v2/tests), which holds a collection of automated tests for the CI pipeline, can be a good way to see how those parts are used. Likewise, the [REPL](https://github.com/Ratstail91/Toy/tree/v2/repl) shows a practical usage of Toy. *v2 is under heavy development, and as such may not be in a working state yet. Your patience and feedback can help, but missing features such as a documentation website are coming, eventually.* @@ -30,10 +30,10 @@ graph TB Toy_Value ---> Toy_String Toy_Value ---> Toy_Stack Toy_Value ---> Toy_Table - Toy_Array + Toy_Value ---> Toy_Array ``` -In addition, [toy_common.h](https://github.com/Ratstail91/Toy/blob/v2/source/toy_common.h) grants platform portability and version info, while [toy_console_colors.h](https://github.com/Ratstail91/Toy/blob/v2/source/toy_console_colors.h) provides string constants as macros that help with console output (where supported). +In addition, [toy_common.h](https://github.com/Ratstail91/Toy/blob/v2/source/toy_common.h) grants platform portability and version info, while [toy_console_colors.h](https://github.com/Ratstail91/Toy/blob/v2/source/toy_console_colors.h) provides string constants as macros that can help with console output (where supported). # Coding Habits @@ -43,15 +43,29 @@ Here's a few coding habits that I use to keep the source code consistent. While When adding a new piece of code, it must be thoroughly tested via a [test case](https://github.com/Ratstail91/Toy/tree/v2/tests/cases). If it has multiple features, they should be tested individually, and in combination with each other. Any kind of corner case which can cause an issue on any supported platform must be resolved (I'm happy to help with this, if needed). +Once a feature has been tested on its own, it can be added to or expanded in the [integration tests](https://github.com/Ratstail91/Toy/tree/v2/tests/integrations). + This is probably the most important habit listed here. While I'm not too fussy as to how the tests are written, they do need to prove that the code works flawlessly. Toy is intended to be used by others (potentially many others), so please write simple and straight forward tests to ensure correctness. ## Tabs, 4 Characters Wide -I use tabs over spaces, with a width of 4. I don't have a linter, please don't make me use one. +I use tabs over spaces, with a width of 4. I don't have a linter, please don't make me use one. For those who care, here's my `.vimrc`: + +```bash +" Load the defaults +runtime defaults.vim + +" my custom stuff +set tabstop=4 +set shiftwidth=4 + +set autoindent +set smartindent +``` ## Error Messages -Fatal errors have this general format: +Fatal errors in the source code have this general format: ```c fprintf(stderr, TOY_CC_ERROR "ERROR: [Info]\n" TOY_CC_RESET); @@ -60,7 +74,7 @@ exit(-1); The use of `fprintf()` will ensure the error is written to the console, and allows extra information to be printed - just replace `[Info]` with the relevant output. These kinds of fatal errors are intended to catch issues with the language itself, rather than errors in the Toy scripts. -In the test cases, the `exit(-1)` is instead replaced with `return -1` to allow `main()` to clean up that test set, and run others if needed. +In the test cases, the `exit(-1)` is instead replaced with `return -1` to allow `main()` to clean up that test case, and run others if needed. ## Naming Things @@ -124,5 +138,5 @@ The directories in the repository's root have certain intended uses. If you find | scripts | Storage for various example scripts written in Toy that can be loaded and executed by the repl. | | source | The source directory for the core of the Toy programming language. | | tests | The source directory for the testing systems. Within, `cases/` is used for test cases, `benchmarks/` for benchmarking, etc. | -| tools | The source directory for various external tools. | +| tools | The source directory for various standalone tools. | diff --git a/README.md b/README.md index c9382c1..ff8cbc1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This repository holds the reference implementation for Toy version 2.x, written * Simple C-like syntax * Intermediate AST representation * Strong, but optional type system -* First-class functions +* First-class functions and types * Extensible via external libraries * Can re-direct output, error and assertion failure messages * Open source under the zlib license @@ -39,14 +39,13 @@ var foobar = 42; Supported platforms are: `linux-latest`, `windows-latest`, `macos-latest`, using [GitHub's standard runners](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories). -To build the library, run `make source`. -To build the library and repl, run `make repl`. -To build and run the test cases, run `make tests`. -To build and run the test cases under gdb, run `make tests-gdb`. +To build the shared library, run `make source`. +To build the shared library and repl, run `make repl`. +To build and run the standard available tests, run `make tests`. # Tools -*Coming Soon.* +*Coming Soon, see #126 for details.* # License @@ -56,7 +55,7 @@ This source code is covered by the zlib license (see [LICENSE.md](LICENSE.md)). For a guide on how you can contribute, see [CONTRIBUTING.md](CONTRIBUTING.md). -@8051Enthusiast - `fixAlignment()` trick +@8051Enthusiast - `fixAlignment()` trick @hiperiondev - v1 Disassembler, v1 porting support and feedback @add00 - v1 Library support @gruelingpine185 - Unofficial v1 MacOS support diff --git a/makefile b/makefile index aef91a7..0c2a85d 100644 --- a/makefile +++ b/makefile @@ -6,11 +6,15 @@ #directories export TOY_SOURCEDIR=source +export TOY_REPLDIR=repl +export TOY_CASESDIR=tests/cases +export TOY_INTEGRATIONSDIR=tests/integrations +export TOY_BENCHMARKSDIR=tests/benchmarks export TOY_OUTDIR=out export TOY_OBJDIR=obj #targets -all: +#all: .PHONY: source source: @@ -20,13 +24,39 @@ source: repl: source $(MAKE) -C repl -k +#various kinds of available tests .PHONY: tests -tests: clean - $(MAKE) -C tests -k +tests: clean test-cases test-integrations test-benchmarks -.PHONY: tests-gdb -tests-gdb: clean - $(MAKE) -C tests all-gdb -k +.PHONY: test-cases +test-cases: + $(MAKE) -C $(TOY_CASESDIR) -k + +.PHONY: test-integrations +test-integrations: + $(MAKE) -C $(TOY_INTEGRATIONSDIR) -k + +.PHONY: test-benchmarks +test-benchmarks: + $(MAKE) -C $(TOY_BENCHMARKSDIR) -k + +#same as above, but with GDB +.PHONY: test-gdb +test-gdb: clean test-cases-gdb test-integrations-gdb test-benchmarks-gdb + +.PHONY: test-cases-gdb +test-cases-gdb: + $(MAKE) -C $(TOY_CASESDIR) gdb -k + +.PHONY: test-integrations-gdb +test-integrations-gdb: + $(MAKE) -C $(TOY_INTEGRATIONSDIR) gdb -k + +.PHONY: test-benchmarks-gdb +test-benchmarks-gdb: + $(MAKE) -C $(TOY_BENCHMARKSDIR) gdb -k + +#TODO: mustfail tests #util targets $(TOY_OUTDIR): diff --git a/repl/main.c b/repl/main.c index b852400..cabc614 100644 --- a/repl/main.c +++ b/repl/main.c @@ -1,12 +1,27 @@ #include "toy.h" -#include "toy_print.h" #include #include #include //utilities +#define APPEND(dest, src) \ + strncpy((dest) + (strlen(dest)), (src), strlen((src)) + 1); + +#if defined(_WIN32) || defined(_WIN64) + #define FLIPSLASH(str) for (int i = 0; str[i]; i++) str[i] = str[i] == '/' ? '\\' : str[i]; +#else + #define FLIPSLASH(str) for (int i = 0; str[i]; i++) str[i] = str[i] == '\\' ? '/' : str[i]; +#endif + unsigned char* readFile(char* path, int* size) { + //BUGFIX: fix the path based on platform - it might be slower, but it's better than dealing with platform crap + int pathLength = strlen(path); + char realPath[pathLength + 1]; + strncpy(realPath, path, pathLength); + realPath[pathLength] = '\0'; + FLIPSLASH(realPath); + //open the file FILE* file = fopen(path, "rb"); if (file == NULL) { @@ -26,29 +41,38 @@ unsigned char* readFile(char* path, int* size) { return NULL; } - // + //read the file if (fread(buffer, sizeof(unsigned char), *size, file) < *size) { fclose(file); *size = -2; //singal a read error return NULL; } - fclose(file); - buffer[(*size)++] = '\0'; + + //clean up and return + fclose(file); return buffer; } -int getDirPath(char* dest, const char* src) { - //extract the directory from src, and store it in dest +int getFilePath(char* dest, const char* src) { + char* p = NULL; -#if defined(_WIN32) || defined(_WIN64) - char* p = strrchr(src, '\\'); -#else - char* p = strrchr(src, '/'); -#endif + //find the last slash, regardless of platform + p = strrchr(src, '\\'); + if (p == NULL) { + p = strrchr(src, '/'); + } + if (p == NULL) { + int len = strlen(src); + strncpy(dest, src, len); + return len; + } - int len = p != NULL ? p - src + 1 : 0; + //determine length of the path + int len = p - src + 1; + + //copy to the dest strncpy(dest, src, len); dest[len] = '\0'; @@ -56,30 +80,31 @@ int getDirPath(char* dest, const char* src) { } int getFileName(char* dest, const char* src) { - //extract the directory from src, and store it in dest + char* p = NULL; -#if defined(_WIN32) || defined(_WIN64) - char* p = strrchr(src, '\\') + 1; -#else - char* p = strrchr(src, '/') + 1; -#endif + //find the last slash, regardless of platform + p = strrchr(src, '\\'); + if (p == NULL) { + p = strrchr(src, '/'); + } + if (p == NULL) { + int len = strlen(src); + strncpy(dest, src, len); + return len; + } + p++; //skip the slash + + //determine length of the file name int len = strlen(p); + + //copy to the dest strncpy(dest, p, len); dest[len] = '\0'; return len; } -#define APPEND(dest, src) \ - strncpy((dest) + (strlen(dest)), (src), strlen((src)) + 1); - -#if defined(_WIN32) || defined(_WIN64) - #define FLIPSLASH(str) for (int i = 0; str[i]; i++) str[i] = str[i] == '/' ? '\\' : str[i]; -#else - #define FLIPSLASH(str) for (int i = 0; str[i]; i++) str[i] = str[i] == '\\' ? '/' : str[i]; -#endif - //handle command line arguments typedef struct CmdLine { bool error; @@ -96,6 +121,8 @@ void usageCmdLine(int argc, const char* argv[]) { void helpCmdLine(int argc, const char* argv[]) { usageCmdLine(argc, argv); + printf("The Toy Programming Language, leave arguments blank for an interactive REPL.\n\n"); + printf(" -h, --help\t\t\tShow this help then exit.\n"); printf(" -v, --version\t\t\tShow version and copyright information then exit.\n"); printf(" -f, --file infile\t\tParse, compile and execute the source file then exit.\n"); @@ -159,7 +186,7 @@ CmdLine parseCmdLine(int argc, const char* argv[]) { exit(-1); } - getDirPath(cmd.infile, argv[0]); + getFilePath(cmd.infile, argv[0]); APPEND(cmd.infile, argv[i]); FLIPSLASH(cmd.infile); } diff --git a/repl/makefile b/repl/makefile index 704d5df..b1a451d 100644 --- a/repl/makefile +++ b/repl/makefile @@ -46,37 +46,6 @@ $(REPL_OUTDIR)/$(REPL_TARGETNAME): $(REPL_OBJFILES) ifeq ($(shell uname),Darwin) #dylib fix otool -L $@ install_name_tool -add_rpath @executable_path/. $@ - install_name_tool -change ../out/libToy.dylib @executable_path/libToy.dylib $@ + install_name_tool -change $(REPL_OUTDIR)/libToy.dylib @executable_path/libToy.dylib $@ otool -L $@ endif - -#util commands -.PHONY: clean -clean: -ifeq ($(shell uname),Linux) - find . -type f -name '*.o' -delete - find . -type f -name '*.a' -delete - find . -type f -name '*.exe' -delete - find . -type f -name '*.dll' -delete - find . -type f -name '*.lib' -delete - find . -type f -name '*.so' -delete - find . -type f -name '*.dylib' -delete - find . -type d -name 'out' -delete - find . -type d -name 'obj' -delete -else ifeq ($(OS),Windows_NT) - $(RM) *.o *.a *.exe *.dll *.lib *.so *.dylib - $(RM) out - $(RM) obj -else ifeq ($(shell uname),Darwin) - find . -type f -name '*.o' -delete - find . -type f -name '*.a' -delete - find . -type f -name '*.exe' -delete - find . -type f -name '*.dll' -delete - find . -type f -name '*.lib' -delete - find . -type f -name '*.so' -delete - find . -type f -name '*.dylib' -delete - find . -type d -name 'out' -delete - find . -type d -name 'obj' -delete -else - @echo "Deletion failed - what platform is this?" -endif diff --git a/tests/benchmarks/.gitkeep b/scripts/.gitkeep similarity index 100% rename from tests/benchmarks/.gitkeep rename to scripts/.gitkeep diff --git a/scripts/example-print.toy b/scripts/example-print.toy deleted file mode 100644 index 4c3a1ff..0000000 --- a/scripts/example-print.toy +++ /dev/null @@ -1,11 +0,0 @@ -//print statement -print 42; - -//it can handle complex expressions -print 3 * 5; - -//strings should work -print "Hello world!"; - -//so should concat -print "Hello" .. "world!"; \ No newline at end of file diff --git a/scripts/example-variables.toy b/scripts/example-variables.toy deleted file mode 100644 index 08d37bb..0000000 --- a/scripts/example-variables.toy +++ /dev/null @@ -1,7 +0,0 @@ -//declare a variable -var foobar = 42; - -//defaults as null -var empty; - - diff --git a/scripts/example.toy b/scripts/example.toy deleted file mode 100644 index fe33835..0000000 --- a/scripts/example.toy +++ /dev/null @@ -1,2 +0,0 @@ -//expression -(1 + 2) * (3 + 4); diff --git a/source/makefile b/source/makefile index 6d30727..60d163b 100644 --- a/source/makefile +++ b/source/makefile @@ -53,34 +53,3 @@ $(SRC_OBJDIR): #compilation steps $(SRC_OBJDIR)/%.o: $(SRC_SOURCEDIR)/%.c $(CC) -c -o $@ $< $(addprefix -I,$(SRC_SOURCEDIR)) $(CFLAGS) - -#util commands -.PHONY: clean -clean: -ifeq ($(shell uname),Linux) - find . -type f -name '*.o' -delete - find . -type f -name '*.a' -delete - find . -type f -name '*.exe' -delete - find . -type f -name '*.dll' -delete - find . -type f -name '*.lib' -delete - find . -type f -name '*.so' -delete - find . -type f -name '*.dylib' -delete - find . -type d -name 'out' -delete - find . -type d -name 'obj' -delete -else ifeq ($(OS),Windows_NT) - $(RM) *.o *.a *.exe *.dll *.lib *.so *.dylib - $(RM) out - $(RM) obj -else ifeq ($(shell uname),Darwin) - find . -type f -name '*.o' -delete - find . -type f -name '*.a' -delete - find . -type f -name '*.exe' -delete - find . -type f -name '*.dll' -delete - find . -type f -name '*.lib' -delete - find . -type f -name '*.so' -delete - find . -type f -name '*.dylib' -delete - find . -type d -name 'out' -delete - find . -type d -name 'obj' -delete -else - @echo "Deletion failed - what platform is this?" -endif diff --git a/source/toy_scope.c b/source/toy_scope.c index 0c15757..51ab98a 100644 --- a/source/toy_scope.c +++ b/source/toy_scope.c @@ -59,6 +59,10 @@ Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) { } Toy_Scope* Toy_popScope(Toy_Scope* scope) { + if (scope == NULL) { + return NULL; + } + decrementRefCount(scope); if (scope->refCount == 0) { diff --git a/source/toy_stack.c b/source/toy_stack.c index d835d3a..04e2513 100644 --- a/source/toy_stack.c +++ b/source/toy_stack.c @@ -24,7 +24,9 @@ Toy_Stack* Toy_allocateStack() { void Toy_freeStack(Toy_Stack* stack) { //TODO: slip in a call to free the complex values here - free(stack); + if (stack != NULL) { + free(stack); + } } void Toy_pushStack(Toy_Stack** stackHandle, Toy_Value value) { diff --git a/.notes/Reminders.txt b/tests/benchmarks/JavaScript/.gitkeep similarity index 100% rename from .notes/Reminders.txt rename to tests/benchmarks/JavaScript/.gitkeep diff --git a/tests/cases/gdb_init b/tests/cases/gdb_init new file mode 100644 index 0000000..6c33498 --- /dev/null +++ b/tests/cases/gdb_init @@ -0,0 +1,2 @@ +set breakpoint pending on + diff --git a/tests/makefile b/tests/cases/makefile similarity index 66% rename from tests/makefile rename to tests/cases/makefile index 746e13d..5a59993 100644 --- a/tests/makefile +++ b/tests/cases/makefile @@ -15,9 +15,9 @@ else endif #directories -TEST_ROOTDIR=.. +TEST_ROOTDIR=../.. TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR) -TEST_CASESDIR=cases +TEST_CASESDIR=. TEST_OUTDIR=out TEST_OBJDIR=obj @@ -27,7 +27,7 @@ TEST_SOURCEFILES=$(wildcard $(TEST_SOURCEDIR)/*.c) TEST_CASESFILES=$(wildcard $(TEST_CASESDIR)/test_*.c) #build the object files, compile the test cases, and run -all: clean build-source build-cases build-link build-run +all: build-source build-cases build-link build-run #targets for each step .PHONY: build-source @@ -64,42 +64,11 @@ $(TEST_OUTDIR)/%.run: $(TEST_OUTDIR)/%.exe $< #debugging targets -all-gdb: clean build-source build-cases build-link build-run-gdb +gdb: build-source build-cases build-link build-run-gdb .PHONY: build-run-gdb build-run-gdb: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.run-gdb))) .PRECIOUS: $(TEST_OUTDIR)/%.run-gdb $(TEST_OUTDIR)/%.run-gdb: $(TEST_OUTDIR)/%.exe - gdb $< -ex "run" --batch - -#util commands -.PHONY: clean -clean: -ifeq ($(shell uname),Linux) - find . -type f -name '*.o' -delete - find . -type f -name '*.a' -delete - find . -type f -name '*.exe' -delete - find . -type f -name '*.dll' -delete - find . -type f -name '*.lib' -delete - find . -type f -name '*.so' -delete - find . -type f -name '*.dylib' -delete - find . -type d -name 'out' -delete - find . -type d -name 'obj' -delete -else ifeq ($(OS),Windows_NT) - $(RM) *.o *.a *.exe *.dll *.lib *.so *.dylib - $(RM) out - $(RM) obj -else ifeq ($(shell uname),Darwin) - find . -type f -name '*.o' -delete - find . -type f -name '*.a' -delete - find . -type f -name '*.exe' -delete - find . -type f -name '*.dll' -delete - find . -type f -name '*.lib' -delete - find . -type f -name '*.so' -delete - find . -type f -name '*.dylib' -delete - find . -type d -name 'out' -delete - find . -type d -name 'obj' -delete -else - @echo "Deletion failed - what platform is this?" -endif + gdb $< -ix gdb_init -ex=run --batch --return-child-result --args "$<" diff --git a/tests/integrations/gdb_init b/tests/integrations/gdb_init new file mode 100644 index 0000000..6b811a4 --- /dev/null +++ b/tests/integrations/gdb_init @@ -0,0 +1,12 @@ +set breakpoint pending on + +break main if argc > 1 + +command 1 +set $i=0 +while($i < argc) + p argv[$i++] +end +continue +end + diff --git a/tests/integrations/makefile b/tests/integrations/makefile new file mode 100644 index 0000000..969cab5 --- /dev/null +++ b/tests/integrations/makefile @@ -0,0 +1,58 @@ +#compiler settings +CC=gcc +CFLAGS+=-std=c17 -g -Wall -Werror -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable -Wformat=2 +LIBS+=-lm +LDFLAGS+= + +ifeq ($(shell uname),Linux) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(OS),Windows_NT) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(shell uname),Darwin) +LDFLAGS=-Wl,-dead_strip +else + @echo "LDFLAGS set failed - what platform is this?" +endif + +#directories +TEST_ROOTDIR=../.. +TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR) +TEST_REPLDIR=$(TEST_ROOTDIR)/$(TOY_REPLDIR) +TEST_SCRIPTDIR=. + +TEST_OUTDIR=out +TEST_OBJDIR=obj + +#file names +TEST_SCRIPTFILES=$(wildcard $(TEST_SCRIPTDIR)/test_*.toy) +TEST_REPLNAME=repl.exe + +#build the source and repl, and run +all: source repl run + +run: $(TEST_SCRIPTFILES:.toy=.toy-run) + +%.toy-run: %.toy + $(TEST_OUTDIR)/$(TEST_REPLNAME) -f ../$< + +#same as above, but with gdb +gdb: source repl run-gdb + +run-gdb: $(TEST_SCRIPTFILES:.toy=.toy-gdb) + +%.toy-gdb: %.toy + gdb $(TEST_OUTDIR)/$(TEST_REPLNAME) -ix gdb_init -ex=run --batch --return-child-result --args "$(TEST_OUTDIR)/$(TEST_REPLNAME)" "-f" "../$<" + +#compile the source and repl first +source: $(TEST_OBJDIR) $(TEST_OUTDIR) + $(MAKE) SRC_OUTDIR=../$(TOY_INTEGRATIONSDIR)/$(TEST_OUTDIR) -C $(TEST_SOURCEDIR) + +repl: $(TEST_OBJDIR) $(TEST_OUTDIR) source + $(MAKE) REPL_TARGETNAME=$(TEST_REPLNAME) REPL_OUTDIR=../$(TOY_INTEGRATIONSDIR)/$(TEST_OUTDIR) -C $(TEST_REPLDIR) + +#util targets +$(TEST_OUTDIR): + mkdir $(TEST_OUTDIR) + +$(TEST_OBJDIR): + mkdir $(TEST_OBJDIR) diff --git a/tests/integrations/test_expressions.toy b/tests/integrations/test_expressions.toy new file mode 100644 index 0000000..6a60545 --- /dev/null +++ b/tests/integrations/test_expressions.toy @@ -0,0 +1,3 @@ +//basic expressions with no side effects (other than debug stack dumps) +(1 + 2) * (3 + 4); + diff --git a/tests/integrations/test_print.toy b/tests/integrations/test_print.toy new file mode 100644 index 0000000..26e615c --- /dev/null +++ b/tests/integrations/test_print.toy @@ -0,0 +1,15 @@ +//basic print statement +print 42; + +//print complex expressions +print 3 * 5; + +//print a string +print "Hello world!"; + +//print a concatenated string +print "Hello" .. "world!"; + +//TODO: in the repl, -s to supress output, or -d to print debugging info + +//TODO: the `assert` keyword will be useful for these \ No newline at end of file diff --git a/tests/integrations/test_variables_and_scopes.toy b/tests/integrations/test_variables_and_scopes.toy new file mode 100644 index 0000000..d63fbdb --- /dev/null +++ b/tests/integrations/test_variables_and_scopes.toy @@ -0,0 +1,6 @@ +//declare a variable with an initial value +var answer = 42; + +//declare a variable without an initial value +var empty; + diff --git a/tests/mustfails/.gitkeep b/tests/mustfails/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/standalone/gdb_init b/tests/standalone/gdb_init new file mode 100644 index 0000000..043b1d8 --- /dev/null +++ b/tests/standalone/gdb_init @@ -0,0 +1 @@ +set breakpoint pending on diff --git a/tests/standalone/makefile b/tests/standalone/makefile new file mode 100644 index 0000000..9ede8a2 --- /dev/null +++ b/tests/standalone/makefile @@ -0,0 +1,60 @@ +#compiler settings +CC=gcc +CFLAGS+=-std=c17 -g -Wall -Werror -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable -Wformat=2 +LIBS+=-lm +LDFLAGS+= + +ifeq ($(shell uname),Linux) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(OS),Windows_NT) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(shell uname),Darwin) +LDFLAGS=-Wl,-dead_strip +else + @echo "LDFLAGS set failed - what platform is this?" +endif + +#directories +TEST_SRCDIR= + +TEST_OUTDIR=out/ +TEST_OBJDIR=obj/ + +#file names +TEST_SRCFILES=$(wildcard $(TEST_SRCDIR)*.c) + +#kick off +all: $(TEST_OBJDIR) $(TEST_OUTDIR) build run + +gdb: $(TEST_OBJDIR) $(TEST_OUTDIR) build gdb-run + +#build +build: $(TEST_OBJDIR)$(TEST_SRCFILES:.c=.o) + +.PRECIOUS: $(TEST_OBJDIR)%.o +$(TEST_OBJDIR)%.o: $(TEST_SRCDIR)%.c + $(CC) -c -o $@ $< $(CFLAGS) -fdata-sections -ffunction-sections + + +.PRECIOUS: $(TEST_OUTDIR)%.exe +$(TEST_OUTDIR)%.exe: $(TEST_OBJDIR)%.o + $(CC) -o $@ $< $(CFLAGS) $(LIBS) $(LDFLAGS) + +#run +run: $(addprefix $(TEST_OUTDIR),$(TEST_SRCFILES:.c=.exe-run)) + +$(TEST_OUTDIR)%.exe-run: $(TEST_OUTDIR)%.exe + $< + +#gdb-run +gdb-run: $(addprefix $(TEST_OUTDIR),$(TEST_SRCFILES:.c=.exe-gdb-run)) + +$(TEST_OUTDIR)%.exe-gdb-run: $(TEST_OUTDIR)%.exe + gdb $< -ix gdb_init -ex=run --batch --return-child-result + +#util targets +$(TEST_OUTDIR): + mkdir $(TEST_OUTDIR) + +$(TEST_OBJDIR): + mkdir $(TEST_OBJDIR) diff --git a/tests/standalone/platform_behaviours.c b/tests/standalone/platform_behaviours.c new file mode 100644 index 0000000..506e211 --- /dev/null +++ b/tests/standalone/platform_behaviours.c @@ -0,0 +1,147 @@ +#include +#include +#include + +//utilities +#define APPEND(dest, src) \ + strncpy((dest) + (strlen(dest)), (src), strlen((src)) + 1); + +#if defined(_WIN32) || defined(_WIN64) + #define FLIPSLASH(str) for (int i = 0; str[i]; i++) str[i] = str[i] == '/' ? '\\' : str[i]; +#else + #define FLIPSLASH(str) for (int i = 0; str[i]; i++) str[i] = str[i] == '\\' ? '/' : str[i]; +#endif + +unsigned char* readFile(char* path, int* size) { + //BUGFIX: fix the path based on platform - it might be slower, but it's better than dealing with platform crap + int pathLength = strlen(path); + char realPath[pathLength + 1]; + strncpy(realPath, path, pathLength); + realPath[pathLength] = '\0'; + FLIPSLASH(realPath); + + //open the file + FILE* file = fopen(path, "rb"); + if (file == NULL) { + *size = -1; //missing file error + return NULL; + } + + //determine the file's length + fseek(file, 0L, SEEK_END); + *size = ftell(file); + rewind(file); + + //make some space + unsigned char* buffer = malloc(*size + 1); + if (buffer == NULL) { + fclose(file); + return NULL; + } + + //read the file + if (fread(buffer, sizeof(unsigned char), *size, file) < *size) { + fclose(file); + *size = -2; //singal a read error + return NULL; + } + + buffer[(*size)++] = '\0'; + + //clean up and return + fclose(file); + return buffer; +} + +int getFilePath(char* dest, const char* src) { + char* p = NULL; + + //find the last slash, regardless of platform + p = strrchr(src, '\\'); + if (p == NULL) { + p = strrchr(src, '/'); + } + if (p == NULL) { + int len = strlen(src); + strncpy(dest, src, len); + return len; + } + + //determine length of the path + int len = p - src + 1; + + //copy to the dest + strncpy(dest, src, len); + dest[len] = '\0'; + + return len; +} + +int getFileName(char* dest, const char* src) { + char* p = NULL; + + //find the last slash, regardless of platform + p = strrchr(src, '\\'); + if (p == NULL) { + p = strrchr(src, '/'); + } + if (p == NULL) { + int len = strlen(src); + strncpy(dest, src, len); + return len; + } + + p++; //skip the slash + + //determine length of the file name + int len = strlen(p); + + //copy to the dest + strncpy(dest, p, len); + dest[len] = '\0'; + + return len; +} + +int main() { + //check the platform + printf("Platform: "); +#if defined(__linux__) + printf("Linux"); +#elif defined(_WIN64) + printf("Win64"); +#elif defined(_WIN32) + printf("Win32"); +#elif defined(__APPLE__) + printf("macOS"); +#else + printf("Unknown"); +#endif + + printf("\n"); + + //run each test + { + char src[256] = "../folder/file.txt"; + char dest[256]; + getFilePath(dest, src); + printf("Path: %s\n", dest); + } + + { + char src[256] = "../folder/file.txt"; + char dest[256]; + getFileName(dest, src); + printf("Name: %s\n", dest); + } + + { + char src[256] = "../folder/file.txt"; + char dest[256]; + getFilePath(dest, src); + APPEND(dest, "target.txt"); + printf("Target: %s\n", dest); + } + + return 0; +} \ No newline at end of file