├── .clang-format ├── .clang-tidy ├── .clangd ├── .github └── workflows │ ├── linux-build.yml │ ├── macos-build.yml │ └── windows-build.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── apps ├── CMakeLists.txt ├── common │ ├── CMakeLists.txt │ ├── path.c │ └── path.h ├── jstar │ ├── CMakeLists.txt │ ├── console_print.c │ ├── console_print.h │ ├── dynload.h │ ├── highlighter.c │ ├── highlighter.h │ ├── hints.c │ ├── hints.h │ ├── icon.rc │ ├── import.c │ ├── import.h │ ├── jstar.ico │ └── main.c └── jstarc │ ├── CMakeLists.txt │ └── main.c ├── cmake ├── JStarConfig.cmake ├── MinGWRuntime.cmake ├── conf.h.in ├── jstar.pc.in └── profileconf.h.in ├── extern ├── CMakeLists.txt └── argparse │ ├── CMakeLists.txt │ ├── LICENSE │ ├── argparse.c │ └── argparse.h ├── include └── jstar │ ├── buffer.h │ ├── conf.h │ ├── jstar.h │ └── parse │ ├── ast.h │ ├── lex.h │ ├── parser.h │ ├── token.def │ └── vector.h ├── profile ├── CMakeLists.txt ├── profiler.c └── profiler.h ├── scripts └── bin2incl.py └── src ├── CMakeLists.txt ├── buffer.c ├── code.c ├── code.h ├── compiler.c ├── compiler.h ├── disassemble.c ├── disassemble.h ├── endianness.h ├── gc.c ├── gc.h ├── hashtable.h ├── import.c ├── import.h ├── int_hashtable.c ├── int_hashtable.h ├── jstar.c ├── jstar_limits.h ├── lib ├── builtins.c ├── builtins.h ├── core │ ├── core.c │ ├── core.h │ ├── core.jsc │ ├── core.jsr │ ├── excs.c │ ├── excs.h │ ├── excs.jsc │ ├── excs.jsr │ ├── iter.c │ ├── iter.h │ ├── iter.jsc │ ├── iter.jsr │ ├── std.c │ ├── std.h │ ├── std.jsc │ └── std.jsr ├── debug.c ├── debug.h ├── debug.jsc ├── debug.jsr ├── io.c ├── io.h ├── io.jsc ├── io.jsr ├── math.c ├── math.h ├── math.jsc ├── math.jsr ├── re.c ├── re.h ├── re.jsc ├── re.jsr ├── sys.c ├── sys.h ├── sys.jsc └── sys.jsr ├── object.c ├── object.h ├── object_types.h ├── opcode.c ├── opcode.def ├── opcode.h ├── parse ├── ast.c ├── lex.c └── parser.c ├── serialize.c ├── serialize.h ├── special_methods.def ├── symbol.h ├── util.h ├── value.c ├── value.h ├── value_hashtable.c ├── value_hashtable.h ├── vm.c └── vm.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | 4 | AllowShortFunctionsOnASingleLine: Inline 5 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 6 | 7 | ColumnLimit: 100 8 | IndentWidth: 4 9 | MaxEmptyLinesToKeep: 1 10 | 11 | AlignConsecutiveMacros: true 12 | SpaceBeforeParens: Never 13 | 14 | IndentPPDirectives: BeforeHash 15 | IndentCaseLabels: false 16 | 17 | PenaltyBreakAssignment: 25 18 | PenaltyBreakBeforeFirstCallParameter: 50 19 | 20 | ... 21 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: 'no-bugprone-sizeof-expression' 2 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | Diagnostics: 2 | UnusedIncludes: Strict 3 | 4 | CompileFlags: 5 | Add: -Wno-unknown-warning-option 6 | Remove: [-m*, -f*] 7 | -------------------------------------------------------------------------------- /.github/workflows/linux-build.yml: -------------------------------------------------------------------------------- 1 | name: linux-build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | submodules: 'recursive' 12 | - name: configure 13 | run: | 14 | mkdir build 15 | cd build 16 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_INSTALL_PREFIX=release/jstar 17 | - name: build 18 | run: | 19 | cd build 20 | make -j 21 | - name: package binaries 22 | run: | 23 | cd build 24 | mkdir -p release/jstar 25 | make install 26 | - name: upload binaries 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: jstar-x64-linux 30 | path: build/release/jstar 31 | -------------------------------------------------------------------------------- /.github/workflows/macos-build.yml: -------------------------------------------------------------------------------- 1 | name: macos-build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: macos-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | submodules: 'recursive' 12 | - name: configure 13 | run: | 14 | mkdir build 15 | cd build 16 | cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=release/jstar 17 | - name: build 18 | run: | 19 | cd build 20 | make -j 21 | - name: package binaries 22 | run: | 23 | cd build 24 | mkdir -p release/jstar 25 | make install 26 | - name: upload binaries 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: jstar-x64-macos 30 | path: build/release/jstar 31 | -------------------------------------------------------------------------------- /.github/workflows/windows-build.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: windows-build 3 | 4 | on: [push] 5 | 6 | jobs: 7 | build: 8 | runs-on: windows-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | submodules: 'recursive' 13 | - name: configure 14 | run: | 15 | mkdir build 16 | cd build 17 | cmake .. -DCMAKE_BUILD_TYPE=Release -DJSTAR_COMPUTED_GOTOS=OFF -DCMAKE_INSTALL_PREFIX=release/jstar 18 | - name: Setup MSBuild.exe 19 | uses: microsoft/setup-msbuild@v2 20 | - name: build 21 | run: | 22 | cd build 23 | msbuild -t:Build -p:Configuration=Release -m jstar.sln 24 | - name: package binaries 25 | run: | 26 | cd build 27 | mkdir -p release/jstar 28 | msbuild -p:Configuration=Release INSTALL.vcxproj 29 | - name: upload binaries 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: jstar-x64-windows 33 | path: build/release/jstar 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | out/ 3 | *.jsc.inc 4 | /*.json 5 | test* 6 | .ccls* 7 | .cache* 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/cwalk"] 2 | path = extern/cwalk 3 | url = https://github.com/bamless/cwalk.git 4 | [submodule "extern/dirent"] 5 | path = extern/dirent 6 | url = https://github.com/bamless/dirent.git 7 | [submodule "extern/replxx"] 8 | path = extern/replxx 9 | url = https://github.com/bamless/replxx.git 10 | branch = jstar 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | 3 | project(jstar) 4 | set(CMAKE_PROJECT_DESCRIPTION "A lightweight embeddable scripting language") 5 | set(CMAKE_PROJECT_HOMEPAGE_URL "https://bamless.github.io/jstar/") 6 | 7 | set(JSTAR_VERSION_MAJOR 2) 8 | set(JSTAR_VERSION_MINOR 0) 9 | set(JSTAR_VERSION_PATCH 0) 10 | set(JSTAR_VERSION ${JSTAR_VERSION_MAJOR}.${JSTAR_VERSION_MINOR}.${JSTAR_VERSION_PATCH}) 11 | 12 | set(CMAKE_C_STANDARD 99) 13 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 14 | 15 | # General options 16 | option(JSTAR_INSTALL "Generate install targets" ON) 17 | option(JSTAR_COMPUTED_GOTOS "Use computed gotos for VM eval loop" ON) 18 | option(JSTAR_NAN_TAGGING "Use NaN tagging technique to store the VM internal type" ON) 19 | option(JSTAR_DBG_PRINT_EXEC "Trace the execution of the VM" OFF) 20 | option(JSTAR_DBG_PRINT_GC "Trace the execution of the garbage collector" OFF) 21 | option(JSTAR_DBG_STRESS_GC "Stress the garbage collector by calling it on every allocation" OFF) 22 | option(JSTAR_DBG_CACHE_STATS "Enable inline cache statistics" OFF) 23 | option(JSTAR_INSTRUMENT "Enable function instrumentation" OFF) 24 | 25 | # Options for optional libraries 26 | option(JSTAR_SYS "Include the 'sys' module in the language" ON) 27 | option(JSTAR_IO "Include the 'io' module in the language" ON) 28 | option(JSTAR_MATH "Include the 'math' module in the language" ON) 29 | option(JSTAR_DEBUG "Include the 'debug' module in the language" ON) 30 | option(JSTAR_RE "Include the 're' module in the language" ON) 31 | 32 | # Setup config file 33 | configure_file ( 34 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/conf.h.in 35 | ${CMAKE_CURRENT_SOURCE_DIR}/include/jstar/conf.h 36 | ) 37 | 38 | # Set default build type if not specified 39 | get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 40 | if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) 41 | message(STATUS "Setting build type to 'Release' as none was specified.") 42 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) 43 | endif() 44 | 45 | # Check for link time optimization support 46 | if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") 47 | include(CheckIPOSupported) 48 | check_ipo_supported(RESULT LTO) 49 | if(LTO) 50 | message(STATUS "J* link-time optimization enabled") 51 | endif() 52 | endif() 53 | 54 | # Set output directories 55 | if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) 56 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 57 | endif() 58 | if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) 59 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 60 | endif() 61 | if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) 62 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 63 | endif() 64 | 65 | # Set compiler flags 66 | if(CMAKE_C_COMPILER_ID STREQUAL "GNU") 67 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter") 68 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -fomit-frame-pointer -fno-plt -s") 69 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s") 70 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT MSVC) 71 | set(CMAKE_C_FLAGS "-Wall -Wextra -Wno-unused-parameter") 72 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Qunused-arguments -O3 -fomit-frame-pointer -fno-plt -s") 73 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s") 74 | elseif(MSVC) 75 | # Disable secure warnings for the useless _s variants of standard functions. 76 | # These shouldn't even exist in c99 but MSVC will complain about them because it's MSVC. 77 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 78 | add_definitions(-DWIN32_LEAN_AND_MEAN) 79 | add_definitions(-DNOMINMAX) 80 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4244 /wd4267 /wd5105 /wd4116") 81 | endif() 82 | 83 | # Find Python, needed to run build-time scripts 84 | find_package(Python COMPONENTS Interpreter) 85 | 86 | # Instrumentation 87 | add_subdirectory(profile) 88 | 89 | # J*, cli, compiler and external dependencies 90 | add_subdirectory(src) 91 | add_subdirectory(apps) 92 | add_subdirectory(extern) 93 | 94 | # ----------------------------------------------------------------------------- 95 | # Installation 96 | # ----------------------------------------------------------------------------- 97 | 98 | if(JSTAR_INSTALL) 99 | include(GNUInstallDirs) 100 | 101 | # Install export files 102 | install(EXPORT jstar-export 103 | FILE JStarTargets.cmake 104 | NAMESPACE jstar:: 105 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jstar 106 | ) 107 | 108 | include(CMakePackageConfigHelpers) 109 | write_basic_package_version_file( 110 | ${CMAKE_CURRENT_BINARY_DIR}/JStarConfigVersion.cmake 111 | VERSION ${JSTAR_VERSION} 112 | COMPATIBILITY AnyNewerVersion 113 | ) 114 | 115 | install(FILES 116 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/JStarConfig.cmake 117 | ${CMAKE_CURRENT_BINARY_DIR}/JStarConfigVersion.cmake 118 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jstar 119 | ) 120 | 121 | # Install license files 122 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE DESTINATION share/licenses/jstar) 123 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/extern/argparse/LICENSE DESTINATION share/licenses/jstar/argparse) 124 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/extern/cwalk/LICENSE.md DESTINATION share/licenses/jstar/cwalk) 125 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/extern/dirent/LICENSE DESTINATION share/licenses/jstar/dirent) 126 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/extern/replxx/LICENSE.md DESTINATION share/licenses/jstar/replxx) 127 | 128 | # On Windows install required runtime dlls alongside exe 129 | include(cmake/MinGWRuntime.cmake) 130 | include(InstallRequiredSystemLibraries) 131 | 132 | # Packaging support 133 | set(CPACK_PACKAGE_VENDOR "bamless") 134 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "J* - A Lightweight Scripting Language") 135 | set(CPACK_PACKAGE_EXECUTABLES jstar;jstar) 136 | set(CPACK_PACKAGE_VERSION ${JSTAR_VERSION}) 137 | set(CPACK_PACKAGE_VERSION_MAJOR ${JSTAR_VERSION_MAJOR}) 138 | set(CPACK_PACKAGE_VERSION_MINOR ${JSTAR_VERSION_MINOR}) 139 | set(CPACK_PACKAGE_VERSION_PATCH ${JSTAR_VERSION_PATCH}) 140 | set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE) 141 | set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md) 142 | set(CPACK_PACKAGE_INSTALL_DIRECTORY "jstar") 143 | include(CPack) 144 | endif() 145 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fabrizio Pietrucci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # J*: A Lightweight Embeddable Scripting Language 2 | 3 |

4 | J* Programming Language 5 |

6 | 7 | ![linux-build](https://github.com/bamless/jstar/workflows/linux-build/badge.svg) 8 | ![windows-build](https://github.com/bamless/jstar/workflows/windows-build/badge.svg) 9 | ![macos-build](https://github.com/bamless/jstar/workflows/macos-build/badge.svg) 10 | 11 | **J\*** is a dynamic embeddable scripting language designed to be as easy as possible to embed into 12 | another program. It arises from the need of having a modern scripting language with built-in 13 | support for OOP whilst mantaning simplicity of use and a low memory footprint. It can be viewed as 14 | a middle ground between Python, a more complete scripting language with lots of features and 15 | libraries, and Lua, a small and compact language that is simple to embed but doesn't provide OOP 16 | functionalities out of the box. 17 | J* tries to take the best of both worlds, implementing a fully featured class system while 18 | maintaining a small footprint and employing the use of a stack based API for communication 19 | among the language and host program, rendering embedding simple. 20 | 21 | **J\*** is: 22 | - **Small**. The implementation spans only a handful of files and the memory footprint is low 23 | thanks to a minimal standard library that provides only essential functionalities 24 | - **Easy to use**. The API is contained in a single header file and employs a stack based approach 25 | similar to the one of Lua, freeing the user from the burden of keeping track of memory owned by 26 | the language 27 | - **Fully object oriented**. Every entity, from numbers to class instances, is an object in **J\*** 28 | - **Modular**. A fully fledged module system makes it easy to split your code across multiple files 29 | - **Easily extensible**. The language can be easily extended by creating C functions callable from 30 | **J\*** using the API, or by importing [C extensions](https://github.com/bamless/jsocket) 31 | provided as dynamic libraries. 32 | 33 | To get a feel of the language, [try it in your browser](https://bamless.github.io/jstar/demo)! 34 | 35 | # The **jstar** command line interface 36 | 37 | Besides the language implementation, a simple command line interface called `jstar` is provided to 38 | start using the language without embedding it into another program. 39 | If the `jstar` binary is executed without 40 | arguments it behaves like your usual read-eval-print loop, accepting a line at a time and executing 41 | it: 42 | ```lua 43 | J*>> var helloWorld = 'Hello, World!' 44 | J*>> print(helloWorld) 45 | Hello, World! 46 | J*>> _ 47 | ``` 48 | You can even write multiline code, it will look like this: 49 | ```lua 50 | J*>> for var i = 0; i < 3; i += 1 51 | .... print('Hello, World!') 52 | .... end 53 | Hello, World! 54 | Hello, World! 55 | Hello, World! 56 | J*>> _ 57 | ``` 58 | When you eventually get bored, simply press Ctrl+d or Ctrl+c to exit the interpreter. 59 | 60 | If you instead want to execute code written in some file, you can pass it as an argument to `jstar`. 61 | All arguments after the first will be passed to the language as script arguments, you can then read 62 | them from the script this way: 63 | ```lua 64 | if #argv > 0 65 | print("First argument: ", argv[0]) 66 | else 67 | raise Exception("No args provided") 68 | end 69 | ``` 70 | The `jstar` executable can also accept various options that modify the behaviour of the command line 71 | app. To see all of them alongside a description, simply pass the `-h` option to the executable. 72 | 73 | In addition to being a useful tool to directly use the programming language, the command line 74 | interface is also a good starting point to learn how **J\*** can be embedded in a program, as it 75 | uses the API to implement all of its functionalities. You can find the code in 76 | [**apps/jstar/**](https://github.com/bamless/jstar/blob/master/apps/jstar/). 77 | 78 | # The **jstarc** compiler 79 | Another application, called `jstarc`, is provided alongside the cli and the language runtime. As the 80 | name implies, this is a compiler that takes in **J\*** source files, compiles them to bytecode 81 | and stores them on file. 82 | 83 | Below is a typical usage of `jstarc`: 84 | ```bash 85 | jstarc src/file.jsr -o file.jsc 86 | ``` 87 | You can even pass in a directory if you want to compile all `jsr` files contained in it: 88 | ```bash 89 | # This compiles all *.jsr files in `dir` and stores them in a directory `out` 90 | # Both directories have to exist 91 | 92 | jstarc dir/ -o out/ 93 | ``` 94 | 95 | The output `.jsc` files behave in the same way as normal `.jsr` source files. You can pass them 96 | to the `jstar` cli app to execute them and can be even imported by other **J\*** files. 97 | 98 | Compiled files are not faster to execute than normal source files, as the **J\*** vm will always 99 | compile source to bytecote before execution, but have nonetheless some nice advantages: 100 | - **Compactness**. compiled files are more compact than source files and generally take up less 101 | space 102 | - **Faster startup**. Reading a compiled file is orders of magnitude faster than parsing and 103 | compiling source code, so there's almost no delay between importing and actual execution 104 | - **Obfuscation**. If you don't want your source to be viewed, compiled files are a nice option 105 | since all the source and almost all debug information are stripped 106 | - **Platform indipendence**. Compiled files are cross-platform, just like normal source files. This 107 | means that they can be compiled once and shared across all systems that have a J* interpreter. 108 | 109 | # Linting and IDE support 110 | 111 | Check out the [Pulsar](https://github.com/bamless/pulsar) static analyzer for code linting and 112 | static analysis from the command line. 113 | Check the [VSCode J* extension](https://marketplace.visualstudio.com/items?itemName=bamless.vsc-jstar-extension&utm_source=VSCode.pro&utm_campaign=AhmadAwais) 114 | for linting and syntax highlighting support in VSCode. 115 | 116 | # Special thanks 117 | Special thanks to Bob Nystrom and the invaluable [crafting interpreters](https://craftinginterpreters.com/) 118 | book, on which the VM is based. 119 | 120 | My gratitude goes to the [Lua](http://www.lua.org/) project as well, for inspiring the stack-based 121 | C API and its amazing [pattern matching](https://www.lua.org/pil/20.2.html) library, on which the 122 | `re` module is based on. 123 | Also, the [closures in Lua](https://www.cs.tufts.edu/~nr/cs257/archive/roberto-ierusalimschy/closures-draft.pdf) 124 | and [implementation of Lua 5](https://www.lua.org/doc/jucs05.pdf) articles were crucial for some 125 | parts of the implementation. 126 | 127 | # Compilation 128 | 129 | The **J\*** library requires a C99 compiler, CMake (>= 3.9) and Python (>= 2.7) to be built, 130 | and is known to compile on OSX (Apple clang), Windows (both MSVC and MinGW-w64) and Linux (GCC, 131 | clang). 132 | 133 | To build the provided **command line interface** `jstar`, a C++11 compiler is required as one of its 134 | dependencies, is written in C++. 135 | 136 | You can clone the latest **J\*** sources using git: 137 | 138 | ```bash 139 | git clone --recurse-submodules https://github.com/bamless/jstar.git 140 | ``` 141 | 142 | After cloning, use CMake to generate build files for your build system of choice and build the `all` 143 | target to generate the language dynamic/static libraries and the command line interface. On 144 | UNIX-like systems this can be simply achieved by issuing this in the command line: 145 | 146 | ```bash 147 | cd jstar; mkdir build; cd build; cmake ..; make -j 148 | ``` 149 | 150 | Once the build process is complete, you can install **J\*** by typing: 151 | 152 | ```bash 153 | sudo make install 154 | ``` 155 | 156 | Various CMake options are available to switch on or off certain functionalities of the interpreter: 157 | 158 | | Option name | Default | Description | 159 | | :------------------: | :-----: | :---------- | 160 | | JSTAR_NAN_TAGGING | ON | Use the NaN tagging technique for storing the VM internal type. Decrases the memory footprint of the interpreter and increases speed | 161 | | JSTAR_COMPUTED_GOTOS | ON | Use computed gotos to implement the VM eval loop. Branch predictor friendly, increases performance. Not all compilers support computed gotos (MSVC for example), so if you're using one of them disable this option | 162 | | JSTAR_INSTALL | ON | Generate install targets for the chosen build system. Turn this off if including J* from another CMake project | 163 | | JSTAR_SYS | ON | Include the 'sys' module in the language | 164 | | JSTAR_IO | ON | Include the 'io' module in the language | 165 | | JSTAR_MATH | ON | Include the 'math' module in the language | 166 | | JSTAR_DEBUG | ON | Include the 'debug' module in the language | 167 | | JSTAR_RE | ON | Include the 're' module in the language | 168 | | JSTAR_DBG_PRINT_EXEC | OFF | Trace the execution of instructions of the virtual machine | 169 | | JSTAR_DBG_STRESS_GC | OFF | Stress the garbage collector by calling it on every allocation | 170 | | JSTAR_DBG_PRINT_GC | OFF | Trace the execution of the garbage collector | 171 | | JSTAR_INSTRUMENT | OFF | Enable instrumentation timers scattered throughout the code. Running J* will then produce 3 json files importable from `chrome://tracing` to view a timeline of executed functions. Supported only when using the GCC compiler on POSIX systems | 172 | 173 | # Binaries 174 | 175 | Precompiled binaries are provided for Windows and Linux for every major release. You can find them 176 | [here](https://github.com/bamless/jstar/releases). 177 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(common) 2 | add_subdirectory(jstar) 3 | add_subdirectory(jstarc) -------------------------------------------------------------------------------- /apps/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Static library 2 | add_library(common STATIC path.h path.c) 3 | target_link_libraries(common PRIVATE cwalk) 4 | target_include_directories(common 5 | PUBLIC 6 | ${CMAKE_CURRENT_SOURCE_DIR} 7 | PRIVATE 8 | ${PROJECT_BINARY_DIR} 9 | ${PROJECT_SOURCE_DIR}/profile 10 | ) 11 | 12 | # Enable link-time optimization if supported 13 | if(LTO) 14 | set_target_properties(common PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 15 | endif() 16 | -------------------------------------------------------------------------------- /apps/common/path.c: -------------------------------------------------------------------------------- 1 | #include "path.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "profiler.h" 9 | 10 | #define INIT_CAPACITY 16 11 | 12 | #if defined(_WIN32) && (defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__)) 13 | #include 14 | #define getcwd _getcwd 15 | #else 16 | #include 17 | #endif 18 | 19 | Path pathNew(void) { 20 | return (Path){0}; 21 | } 22 | 23 | Path pathCopy(const Path* o) { 24 | Path p = *o; 25 | p.data = malloc(p.capacity); 26 | memcpy(p.data, o->data, p.size); 27 | p.data[p.size] = '\0'; 28 | return p; 29 | } 30 | 31 | void pathFree(Path* p) { 32 | free(p->data); 33 | } 34 | 35 | static void ensureCapacity(Path* p, size_t cap) { 36 | if(!p->capacity) { 37 | p->capacity = INIT_CAPACITY; 38 | } 39 | while(p->capacity < cap) { 40 | p->capacity *= 2; 41 | } 42 | p->data = realloc(p->data, p->capacity); 43 | } 44 | 45 | void pathClear(Path* p) { 46 | p->size = 0; 47 | if(p->data) p->data[0] = '\0'; 48 | } 49 | 50 | void pathAppend(Path* p, const char* str, size_t length) { 51 | ensureCapacity(p, p->size + length + 1); 52 | memcpy(p->data + p->size, str, length); 53 | p->size += length; 54 | p->data[p->size] = '\0'; 55 | } 56 | 57 | void pathAppendStr(Path* p, const char* str) { 58 | pathAppend(p, str, strlen(str)); 59 | } 60 | 61 | void pathJoinStr(Path* p, const char* str) { 62 | PROFILE_FUNC() 63 | if(p->size && p->data[p->size - 1] != PATH_SEP_CHAR && *str != PATH_SEP_CHAR) { 64 | pathAppend(p, PATH_SEP, 1); 65 | } 66 | pathAppendStr(p, str); 67 | } 68 | 69 | void pathJoin(Path* p, const Path* o) { 70 | PROFILE_FUNC() 71 | pathJoinStr(p, o->data); 72 | } 73 | 74 | void pathDirname(Path* p) { 75 | size_t dirPos; 76 | cwk_path_get_dirname(p->data, &dirPos); 77 | p->size = dirPos; 78 | p->data[p->size] = '\0'; 79 | } 80 | 81 | const char* pathGetExtension(const Path* p, size_t* length) { 82 | const char* ext; 83 | if(!cwk_path_get_extension(p->data, &ext, length)) { 84 | return NULL; 85 | } 86 | return ext; 87 | } 88 | 89 | bool pathHasExtension(const Path* p) { 90 | return p && cwk_path_has_extension(p->data); 91 | } 92 | 93 | bool pathIsRelative(const Path* p) { 94 | return p && cwk_path_is_relative(p->data); 95 | } 96 | 97 | bool pathIsAbsolute(const Path* p) { 98 | return p && cwk_path_is_absolute(p->data); 99 | } 100 | 101 | void pathChangeExtension(Path* p, const char* newExt) { 102 | size_t newSize = 0; 103 | do { 104 | if(newSize) ensureCapacity(p, newSize + 1); 105 | newSize = cwk_path_change_extension(p->data, newExt, p->data, p->capacity); 106 | } while(newSize >= p->capacity); 107 | p->size = newSize; 108 | } 109 | 110 | void pathNormalize(Path* p) { 111 | size_t newSize = 0; 112 | do { 113 | if(newSize) ensureCapacity(p, newSize + 1); 114 | newSize = cwk_path_normalize(p->data, p->data, p->capacity); 115 | } while(newSize >= p->capacity); 116 | p->size = newSize; 117 | } 118 | 119 | static char* getCurrentDirectory(void) { 120 | size_t cwdLen = 128; 121 | char* cwd = malloc(cwdLen); 122 | while(!getcwd(cwd, cwdLen)) { 123 | if(errno != ERANGE) { 124 | int saveErrno = errno; 125 | free(cwd); 126 | errno = saveErrno; 127 | return NULL; 128 | } 129 | cwdLen *= 2; 130 | cwd = realloc(cwd, cwdLen); 131 | } 132 | return cwd; 133 | } 134 | 135 | void pathToAbsolute(Path* p) { 136 | Path absolute = pathAbsolute(p); 137 | free(p->data); 138 | *p = absolute; 139 | } 140 | 141 | void pathReplace(Path* p, size_t off, char c, char r) { 142 | assert(off <= p->size); 143 | for(size_t i = off; i < p->size; i++) { 144 | if(p->data[i] == c) { 145 | p->data[i] = r; 146 | } 147 | } 148 | } 149 | 150 | void pathTruncate(Path* p, size_t off) { 151 | assert(off <= p->size); 152 | p->size = off; 153 | p->data[p->size] = '\0'; 154 | } 155 | 156 | size_t pathIntersectOffset(const Path* p, const Path* o) { 157 | return cwk_path_get_intersection(p->data, o->data); 158 | } 159 | 160 | Path pathIntersect(const Path* p1, const Path* p2) { 161 | Path ret = pathNew(); 162 | size_t intersect = cwk_path_get_intersection(p1->data, p2->data); 163 | pathAppend(&ret, p1->data, intersect); 164 | return ret; 165 | } 166 | 167 | Path pathAbsolute(const Path* p) { 168 | char* cwd = getCurrentDirectory(); 169 | if(!cwd) { 170 | return (Path){0}; 171 | } 172 | 173 | Path absolute = pathNew(); 174 | 175 | size_t newSize = 0; 176 | do { 177 | if(newSize) ensureCapacity(&absolute, newSize + 1); 178 | newSize = cwk_path_get_absolute(cwd, p->data, absolute.data, absolute.capacity); 179 | } while(newSize >= absolute.capacity); 180 | 181 | absolute.size = newSize; 182 | free(cwd); 183 | 184 | return absolute; 185 | } 186 | -------------------------------------------------------------------------------- /apps/common/path.h: -------------------------------------------------------------------------------- 1 | #ifndef PATH_H 2 | #define PATH_H 3 | 4 | #include 5 | #include 6 | 7 | #if defined(_WIN32) && (defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__)) 8 | #define PATH_SEP "\\" 9 | #define PATH_SEP_CHAR '\\' 10 | #else 11 | #define PATH_SEP "/" 12 | #define PATH_SEP_CHAR '/' 13 | #endif 14 | 15 | typedef struct Path { 16 | char* data; 17 | size_t size, capacity; 18 | } Path; 19 | 20 | Path pathNew(void); 21 | Path pathCopy(const Path* o); 22 | void pathFree(Path* p); 23 | 24 | void pathClear(Path* p); 25 | void pathAppend(Path* p, const char* str, size_t length); 26 | void pathAppendStr(Path* p, const char* str); 27 | void pathJoinStr(Path* p1, const char* str); 28 | void pathJoin(Path* p, const Path* o); 29 | void pathDirname(Path* p); 30 | const char* pathGetExtension(const Path* p, size_t* length); 31 | bool pathHasExtension(const Path* p); 32 | bool pathIsRelative(const Path* p); 33 | bool pathIsAbsolute(const Path* p); 34 | void pathChangeExtension(Path* p, const char* newExt); 35 | void pathNormalize(Path* p); 36 | void pathToAbsolute(Path* p); 37 | void pathReplace(Path* p, size_t off, char c, char r); 38 | void pathTruncate(Path* p, size_t off); 39 | size_t pathIntersectOffset(const Path* p, const Path* o); 40 | 41 | Path pathIntersect(const Path* p1, const Path* p2); 42 | Path pathAbsolute(const Path* p); 43 | 44 | #endif -------------------------------------------------------------------------------- /apps/jstar/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Cli app 2 | add_executable(cli 3 | console_print.h 4 | console_print.c 5 | dynload.h 6 | highlighter.h 7 | highlighter.c 8 | hints.h 9 | hints.c 10 | import.h 11 | import.c 12 | main.c 13 | ) 14 | 15 | if(WIN32) 16 | target_sources(cli PRIVATE icon.rc) 17 | endif() 18 | 19 | set(EXTRA_LIBS) 20 | if(UNIX) 21 | set(EXTRA_LIBS dl) 22 | endif() 23 | 24 | target_link_libraries(cli PRIVATE jstar common replxx argparse ${EXTRA_LIBS}) 25 | target_include_directories(cli PRIVATE ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/profile) 26 | set_target_properties(cli PROPERTIES OUTPUT_NAME "jstar") 27 | 28 | if(JSTAR_INSTRUMENT) 29 | target_link_libraries(cli PRIVATE profile) 30 | endif() 31 | 32 | # Enable link-time optimization if supported 33 | if(LTO) 34 | set_target_properties(cli PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 35 | endif() 36 | 37 | # Install target 38 | if(JSTAR_INSTALL) 39 | include(GNUInstallDirs) 40 | 41 | # Setup relative rpath on unix and macos 42 | if(APPLE) 43 | set_target_properties(cli PROPERTIES INSTALL_RPATH "@executable_path/../${CMAKE_INSTALL_LIBDIR}") 44 | elseif(UNIX) 45 | set_target_properties(cli PROPERTIES INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") 46 | endif() 47 | 48 | install(TARGETS cli 49 | EXPORT jstar-export 50 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 51 | ) 52 | endif() 53 | -------------------------------------------------------------------------------- /apps/jstar/console_print.c: -------------------------------------------------------------------------------- 1 | #include "console_print.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | #define isatty _isatty 10 | #define fileno _fileno 11 | #else 12 | #include 13 | #endif 14 | 15 | static const char* colors[] = { 16 | [COLOR_RESET] = "\033[0m", 17 | [COLOR_BLACK] = "\033[0;22;30m", 18 | [COLOR_RED] = "\033[0;22;31m", 19 | [COLOR_GREEN] = "\033[0;22;32m", 20 | [COLOR_BROWN] = "\033[0;22;33m", 21 | [COLOR_BLUE] = "\033[0;22;34m", 22 | [COLOR_MAGENTA] = "\033[0;22;35m", 23 | [COLOR_CYAN] = "\033[0;22;36m", 24 | [COLOR_LIGHT_GRAY] = "\033[0;22;37m", 25 | [COLOR_GRAY] = "\033[0;1;90m", 26 | [COLOR_BRIGHTRED] = "\033[0;1;91m", 27 | [COLOR_BRIGHTGREEN] = "\033[0;1;92m", 28 | [COLOR_YELLOW] = "\033[0;1;93m", 29 | [COLOR_BRIGHTBLUE] = "\033[0;1;94m", 30 | [COLOR_BRIGHTMAGENTA] = "\033[0;1;95m", 31 | [COLOR_BRIGHTCYAN] = "\033[0;1;96m", 32 | [COLOR_WHITE] = "\033[0;1;97m", 33 | [COLOR_NONE] = "", 34 | }; 35 | 36 | static FILE* replxxStdToFile(ReplxxStdFile std) { 37 | switch(std) { 38 | case REPLXX_STDOUT: 39 | return stdout; 40 | case REPLXX_STDERR: 41 | return stderr; 42 | case REPLXX_STDIN: 43 | return stdin; 44 | } 45 | assert(false); 46 | return NULL; 47 | } 48 | 49 | int vfConsolePrint(Replxx* replxx, ReplxxStdFile std, Color color, const char* fmt, va_list ap) { 50 | FILE* stdFile = replxxStdToFile(std); 51 | if(replxx_is_color_enabled(replxx) && isatty(fileno(stdFile))) { 52 | int written = 0; 53 | written += replxx_fprint(replxx, std, colors[color]); 54 | written += replxx_vfprint(replxx, std, fmt, ap); 55 | written += replxx_fprint(replxx, std, colors[COLOR_RESET]); 56 | return written; 57 | } else { 58 | return vfprintf(stdFile, fmt, ap); 59 | } 60 | } 61 | 62 | int fConsolePrint(Replxx* replxx, ReplxxStdFile std, Color color, const char* fmt, ...) { 63 | va_list args; 64 | va_start(args, fmt); 65 | int written = vfConsolePrint(replxx, std, color, fmt, args); 66 | va_end(args); 67 | return written; 68 | } 69 | 70 | int consolePrint(Replxx* replxx, Color color, const char* fmt, ...) { 71 | va_list args; 72 | va_start(args, fmt); 73 | int written = vfConsolePrint(replxx, REPLXX_STDOUT, color, fmt, args); 74 | va_end(args); 75 | return written; 76 | } -------------------------------------------------------------------------------- /apps/jstar/console_print.h: -------------------------------------------------------------------------------- 1 | #ifndef PRINT_H 2 | #define PRINT_H 3 | 4 | #include 5 | #include 6 | 7 | typedef enum Color { 8 | COLOR_RESET, 9 | COLOR_BLACK, 10 | COLOR_RED, 11 | COLOR_GREEN, 12 | COLOR_BROWN, 13 | COLOR_BLUE, 14 | COLOR_MAGENTA, 15 | COLOR_CYAN, 16 | COLOR_LIGHT_GRAY, 17 | COLOR_GRAY, 18 | COLOR_BRIGHTRED, 19 | COLOR_BRIGHTGREEN, 20 | COLOR_YELLOW, 21 | COLOR_BRIGHTBLUE, 22 | COLOR_BRIGHTMAGENTA, 23 | COLOR_BRIGHTCYAN, 24 | COLOR_WHITE, 25 | COLOR_NONE, 26 | } Color; 27 | 28 | // Wraps replxx colored output functions with a more familiar printf-like syntax 29 | int vfConsolePrint(Replxx* replxx, ReplxxStdFile std, Color color, const char* fmt, va_list ap); 30 | int fConsolePrint(Replxx* replxx, ReplxxStdFile std, Color color, const char* fmt, ...); 31 | int consolePrint(Replxx* replxx, Color color, const char* fmt, ...); 32 | 33 | #endif -------------------------------------------------------------------------------- /apps/jstar/dynload.h: -------------------------------------------------------------------------------- 1 | #ifndef DYNLOAD_H 2 | #define DYNLOAD_H 3 | 4 | #include "jstar/jstar.h" 5 | 6 | /** 7 | * Wrap platform specific shared library load functions. 8 | * Used by the import system to resolve native module extensions. 9 | */ 10 | 11 | #if defined(JSTAR_POSIX) 12 | #include 13 | #define dynload(path) dlopen(path, RTLD_NOW) 14 | #define dynfree(handle) dlclose(handle) 15 | #define dynsim(handle, symbol) dlsym(handle, symbol) 16 | #elif defined(JSTAR_WINDOWS) 17 | #include 18 | #define dynload(path) LoadLibrary(path) 19 | #define dynfree(handle) FreeLibrary(handle) 20 | #define dynsim(handle, symbol) (void*)GetProcAddress(handle, symbol) 21 | #else 22 | // Fallback for platforms for which we don't support shared library native extensions 23 | #define dynload(path) ((void*)0) 24 | #define dynfree(handle) ((void)0) 25 | #define dynsim(handle, symbol) ((void*)0) 26 | #endif 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /apps/jstar/highlighter.c: -------------------------------------------------------------------------------- 1 | #include "highlighter.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "jstar/conf.h" 7 | #include "jstar/parse/lex.h" 8 | #include "replxx.h" 9 | 10 | // ----------------------------------------------------------------------------- 11 | // COLOR THEME DEFINITION 12 | // ----------------------------------------------------------------------------- 13 | 14 | #define CLASS_NAME_COLOR REPLXX_COLOR_YELLOW 15 | #define IDENTIFIER_CALL_COLOR REPLXX_COLOR_YELLOW 16 | 17 | JSR_STATIC_ASSERT(TOK_EOF == 78, "Token count has changed, update highlighter"); 18 | static const ReplxxColor theme[TOK_EOF] = { 19 | // Keywords 20 | #define KEYWORD_COLOR REPLXX_COLOR_BLUE 21 | [TOK_AND] = KEYWORD_COLOR, 22 | [TOK_OR] = KEYWORD_COLOR, 23 | [TOK_CLASS] = KEYWORD_COLOR, 24 | [TOK_ELSE] = KEYWORD_COLOR, 25 | [TOK_FOR] = KEYWORD_COLOR, 26 | [TOK_FUN] = KEYWORD_COLOR, 27 | [TOK_CTOR] = KEYWORD_COLOR, 28 | [TOK_NAT] = KEYWORD_COLOR, 29 | [TOK_IF] = KEYWORD_COLOR, 30 | [TOK_ELIF] = KEYWORD_COLOR, 31 | [TOK_RETURN] = KEYWORD_COLOR, 32 | [TOK_YIELD] = KEYWORD_COLOR, 33 | [TOK_WHILE] = KEYWORD_COLOR, 34 | [TOK_IMPORT] = KEYWORD_COLOR, 35 | [TOK_IN] = KEYWORD_COLOR, 36 | [TOK_BEGIN] = KEYWORD_COLOR, 37 | [TOK_END] = KEYWORD_COLOR, 38 | [TOK_AS] = KEYWORD_COLOR, 39 | [TOK_IS] = KEYWORD_COLOR, 40 | [TOK_TRY] = KEYWORD_COLOR, 41 | [TOK_ENSURE] = KEYWORD_COLOR, 42 | [TOK_EXCEPT] = KEYWORD_COLOR, 43 | [TOK_RAISE] = KEYWORD_COLOR, 44 | [TOK_WITH] = KEYWORD_COLOR, 45 | [TOK_CONTINUE] = KEYWORD_COLOR, 46 | [TOK_BREAK] = KEYWORD_COLOR, 47 | 48 | // `this` and `super` keywords 49 | #define METHOD_KEYWORD_COLOR REPLXX_COLOR_BLUE 50 | [TOK_SUPER] = METHOD_KEYWORD_COLOR, 51 | 52 | // Storage keywords 53 | #define STORAGE_KEYWORD_COLOR REPLXX_COLOR_BLUE 54 | [TOK_VAR] = STORAGE_KEYWORD_COLOR, 55 | [TOK_STATIC] = STORAGE_KEYWORD_COLOR, 56 | 57 | // Punctuation 58 | #define PUNCTUATION_COLOR REPLXX_COLOR_DEFAULT 59 | [TOK_SEMICOLON] = PUNCTUATION_COLOR, 60 | [TOK_PIPE] = PUNCTUATION_COLOR, 61 | [TOK_LPAREN] = PUNCTUATION_COLOR, 62 | [TOK_RPAREN] = PUNCTUATION_COLOR, 63 | [TOK_LSQUARE] = PUNCTUATION_COLOR, 64 | [TOK_RSQUARE] = PUNCTUATION_COLOR, 65 | [TOK_LCURLY] = PUNCTUATION_COLOR, 66 | [TOK_RCURLY] = PUNCTUATION_COLOR, 67 | [TOK_COLON] = PUNCTUATION_COLOR, 68 | [TOK_COMMA] = PUNCTUATION_COLOR, 69 | [TOK_DOT] = PUNCTUATION_COLOR, 70 | 71 | // Literals 72 | [TOK_NUMBER] = REPLXX_COLOR_GREEN, 73 | [TOK_TRUE] = REPLXX_COLOR_CYAN, 74 | [TOK_FALSE] = REPLXX_COLOR_CYAN, 75 | [TOK_STRING] = REPLXX_COLOR_BLUE, 76 | [TOK_UNTERMINATED_STR] = REPLXX_COLOR_BLUE, 77 | [TOK_NULL] = REPLXX_COLOR_MAGENTA, 78 | 79 | // Misc 80 | [TOK_ARROW] = REPLXX_COLOR_RED, 81 | [TOK_AT] = REPLXX_COLOR_RED, 82 | 83 | // Error 84 | [TOK_ERR] = REPLXX_COLOR_RED, 85 | }; 86 | 87 | // ----------------------------------------------------------------------------- 88 | // HIGHLIGHTER FUNCTION 89 | // ----------------------------------------------------------------------------- 90 | 91 | static void setTokColor(const char* in, const JStarTok* tok, ReplxxColor color, ReplxxColor* out) { 92 | size_t startOffset = tok->lexeme - in; 93 | for(size_t i = startOffset; i < startOffset + tok->length; i++) { 94 | out[i] = color; 95 | } 96 | } 97 | 98 | void highlighter(const char* input, ReplxxColor* colors, int size, void* userData) { 99 | JStarLex lex; 100 | jsrInitLexer(&lex, input, strlen(input)); 101 | 102 | JStarTok prev, tok; 103 | jsrNextToken(&lex, &tok); 104 | prev = tok; 105 | 106 | while(tok.type != TOK_EOF && tok.type != TOK_NEWLINE) { 107 | if(tok.type == TOK_LPAREN && prev.type == TOK_IDENTIFIER) { 108 | setTokColor(input, &prev, IDENTIFIER_CALL_COLOR, colors); 109 | } 110 | 111 | ReplxxColor themeColor = theme[tok.type]; 112 | 113 | if(tok.type == TOK_IDENTIFIER && (prev.type == TOK_CLASS || prev.type == TOK_IS)) { 114 | themeColor = CLASS_NAME_COLOR; 115 | } 116 | if(tok.type == TOK_IDENTIFIER && tok.length == strlen("this") && 117 | strncmp(tok.lexeme, "this", (size_t)tok.length) == 0) { 118 | themeColor = METHOD_KEYWORD_COLOR; 119 | } 120 | 121 | if(themeColor) { 122 | setTokColor(input, &tok, themeColor, colors); 123 | } 124 | 125 | prev = tok; 126 | jsrNextToken(&lex, &tok); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /apps/jstar/highlighter.h: -------------------------------------------------------------------------------- 1 | #ifndef HIGHLIGHTER_H 2 | #define HIGHLIGHTER_H 3 | 4 | #include 5 | 6 | // Replxx highlighter callback with J* syntax support 7 | void highlighter(const char* input, ReplxxColor* colors, int size, void* userData); 8 | 9 | #endif -------------------------------------------------------------------------------- /apps/jstar/hints.c: -------------------------------------------------------------------------------- 1 | #include "hints.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "jstar/jstar.h" 8 | #include "jstar/parse/lex.h" 9 | 10 | JSR_STATIC_ASSERT(TOK_EOF == 78, "Token count has changed, update keywords if needed"); 11 | // NULL terminated array of all J* keywords. 12 | static const char* keywords[] = { 13 | "or", "if", "in", "as", "is", "and", "for", "fun", "construct", 14 | "var", "end", "try", "else", "elif", "null", "true", "with", "class", 15 | "false", "super", "while", "begin", "raise", "break", "native", "return", "yield", 16 | "import", "ensure", "except", "static", "continue", NULL, 17 | }; 18 | 19 | // Add all matching keywords to the hints array. 20 | static void hintKeywords(const char* ctxStart, int ctxLen, replxx_hints* hints) { 21 | for(const char** kw = keywords; *kw; kw++) { 22 | int kwLen = strlen(*kw); 23 | if(kwLen > ctxLen && strncmp(ctxStart, *kw, ctxLen) == 0) { 24 | replxx_add_hint(hints, *kw); 25 | } 26 | } 27 | } 28 | 29 | // Add all matching global names to the hints array. 30 | // We assert on errors as all calls should succeed on a correctly functioning J* VM. 31 | static void hintNames(JStarVM* vm, const char* ctxStart, int ctxLen, replxx_hints* hints) { 32 | bool ok = jsrGetGlobal(vm, JSR_MAIN_MODULE, "__this__"); 33 | assert(ok); 34 | (void)ok; 35 | 36 | JStarResult res = jsrCallMethod(vm, "globals", 0); 37 | if(res != JSR_SUCCESS) { 38 | jsrPop(vm); 39 | return; 40 | } 41 | 42 | bool err; 43 | jsrPushNull(vm); 44 | 45 | while(jsrIter(vm, -2, -1, &err)) { 46 | assert(!err); 47 | 48 | bool ok = jsrNext(vm, -2, -1); 49 | assert(ok && jsrIsString(vm, -1)); 50 | (void)ok; 51 | 52 | const char* global = jsrGetString(vm, -1); 53 | int globalLen = jsrGetStringSz(vm, -1); 54 | 55 | if(globalLen > ctxLen && strncmp(ctxStart, global, ctxLen) == 0) { 56 | replxx_add_hint(hints, global); 57 | } 58 | 59 | jsrPop(vm); 60 | } 61 | 62 | jsrPop(vm); 63 | jsrPop(vm); 64 | } 65 | 66 | void hints(const char* input, replxx_hints* hints, int* ctxLen, ReplxxColor* color, void* udata) { 67 | if(!*ctxLen) { 68 | return; 69 | } 70 | 71 | JStarVM* vm = udata; 72 | *color = REPLXX_COLOR_GRAY; 73 | const char* ctxStart = input + strlen(input) - *ctxLen; 74 | 75 | hintNames(vm, ctxStart, *ctxLen, hints); 76 | hintKeywords(ctxStart, *ctxLen, hints); 77 | } 78 | -------------------------------------------------------------------------------- /apps/jstar/hints.h: -------------------------------------------------------------------------------- 1 | #ifndef HINTS_H 2 | #define HINTS_H 3 | 4 | #include 5 | 6 | // Replxx hints callback with global name resolution support 7 | void hints(const char* input, replxx_hints* hints, int* ctxLen, ReplxxColor* color, void* ud); 8 | 9 | #endif -------------------------------------------------------------------------------- /apps/jstar/icon.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "jstar.ico" 2 | -------------------------------------------------------------------------------- /apps/jstar/import.c: -------------------------------------------------------------------------------- 1 | #include "import.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "dynload.h" 8 | #include "jstar/parse/vector.h" 9 | #include "path.h" 10 | #include "profiler.h" 11 | 12 | #define PACKAGE_FILE "__package__" // Name of the file executed during package imports 13 | #define JSR_EXT ".jsr" // Normal J* source file extension 14 | #define JSC_EXT ".jsc" // Compiled J* file extension 15 | #define JSTAR_PATH "JSTARPATH" // Env variable containing a list of import paths 16 | #define IMPORT_PATHS "importPaths" // Name of the global holding the import paths list 17 | #define OPEN_NATIVE_EXT "jsrOpenModule" // Function called when loading native extension modules 18 | 19 | // Platform specific separator for the `JSTARPATH` environment variable 20 | #ifdef JSTAR_WINDOWS 21 | #define IMPORT_PATHS_SEP ';' 22 | #else 23 | #define IMPORT_PATHS_SEP ':' 24 | #endif 25 | 26 | // Platform specific shared library suffix 27 | #if defined(JSTAR_WINDOWS) 28 | #define DL_SUFFIX ".dll" 29 | #elif defined(JSTAR_MACOS) || defined(JSTAR_IOS) 30 | #define DL_SUFFIX ".dylib" 31 | #elif defined(JSTAR_POSIX) 32 | #define DL_SUFFIX ".so" 33 | #else 34 | #define DL_SUFFIX "" 35 | #endif 36 | 37 | // Static global `Path`s used to construct the final import paths. 38 | // Since imports are always sequential (and thus we need not to worry about concurrency), 39 | // having them as globals saves a lot of allocations during imports. 40 | static Path import; 41 | static Path nativeExt; 42 | 43 | // Vector that keeps track of loaded shared libraries. Used during shutdown to free resources. 44 | static ext_vector(void*) sharedLibs; 45 | 46 | // Init the `importPaths` list by appending the script directory (or the current working 47 | // directory if no script was provided) and all the paths present in the JSTARPATH env variable. 48 | // All paths are converted to absolute ones. 49 | static void initImportPaths(JStarVM* vm, const char* scriptPath, bool ignoreEnv) { 50 | jsrGetGlobal(vm, JSR_CORE_MODULE, IMPORT_PATHS); 51 | 52 | Path mainImport = pathNew(); 53 | if(scriptPath) { 54 | pathAppendStr(&mainImport, scriptPath); 55 | pathDirname(&mainImport); 56 | } else { 57 | pathAppendStr(&mainImport, "./"); 58 | } 59 | 60 | pathToAbsolute(&mainImport); 61 | 62 | jsrPushString(vm, mainImport.data); 63 | jsrListAppend(vm, -2); 64 | jsrPop(vm); 65 | 66 | pathFree(&mainImport); 67 | 68 | // Add all other paths appearing in the JSTARPATH environment variable 69 | const char* jstarPath; 70 | if(!ignoreEnv && (jstarPath = getenv(JSTAR_PATH))) { 71 | Path importPath = pathNew(); 72 | 73 | size_t pathLen = strlen(jstarPath); 74 | for(size_t i = 0, last = 0; i <= pathLen; i++) { 75 | if(jstarPath[i] == IMPORT_PATHS_SEP || i == pathLen) { 76 | pathAppend(&importPath, jstarPath + last, i - last); 77 | pathToAbsolute(&importPath); 78 | 79 | // Add it to the list 80 | jsrPushString(vm, importPath.data); 81 | jsrListAppend(vm, -2); 82 | jsrPop(vm); 83 | 84 | pathClear(&importPath); 85 | last = i + 1; 86 | } 87 | } 88 | 89 | pathFree(&importPath); 90 | } 91 | 92 | // Add the CWD (`./`) as a last importPath 93 | jsrPushString(vm, "./"); 94 | jsrListAppend(vm, -2); 95 | jsrPop(vm); 96 | 97 | jsrPop(vm); 98 | } 99 | 100 | void initImports(JStarVM* vm, const char* scriptPath, bool ignoreEnv) { 101 | initImportPaths(vm, scriptPath, ignoreEnv); 102 | import = pathNew(); 103 | nativeExt = pathNew(); 104 | sharedLibs = NULL; 105 | } 106 | 107 | void freeImports(void) { 108 | pathFree(&import); 109 | pathFree(&nativeExt); 110 | ext_vec_foreach(void** dynlib, sharedLibs) { 111 | dynfree(*dynlib); 112 | } 113 | ext_vec_free(sharedLibs); 114 | } 115 | 116 | // Loads a native extension module and returns its `native registry` to J* 117 | static JStarNativeReg* loadNativeExtension(const Path* modulePath) { 118 | PROFILE_FUNC() 119 | pathAppend(&nativeExt, modulePath->data, modulePath->size); 120 | pathChangeExtension(&nativeExt, DL_SUFFIX); 121 | 122 | void* dynlib; 123 | { 124 | PROFILE("loadNativeExtension::dynload") 125 | dynlib = dynload(nativeExt.data); 126 | if(!dynlib) { 127 | return NULL; 128 | } 129 | } 130 | 131 | JStarNativeReg* (*registry)(void); 132 | { 133 | PROFILE("loadNativeExtension::dynsim") 134 | registry = dynsim(dynlib, OPEN_NATIVE_EXT); 135 | if(!registry) { 136 | dynfree(dynlib); 137 | return NULL; 138 | } 139 | } 140 | 141 | // Track the loaded shared library in the global list of all open shared libraries 142 | ext_vec_push_back(sharedLibs, dynlib); 143 | 144 | return (*registry)(); 145 | } 146 | 147 | // Reads a whole file into memory and returns its content and length 148 | static void* readFile(const Path* p, size_t* length) { 149 | PROFILE_FUNC() 150 | FILE* f = fopen(p->data, "rb"); 151 | if(!f) { 152 | return NULL; 153 | } 154 | 155 | if(fseek(f, 0, SEEK_END)) { 156 | fclose(f); 157 | return NULL; 158 | } 159 | 160 | long size = ftell(f); 161 | if(size < 0) { 162 | fclose(f); 163 | return NULL; 164 | } 165 | 166 | *length = size; 167 | 168 | if(fseek(f, 0, SEEK_SET)) { 169 | fclose(f); 170 | return NULL; 171 | } 172 | 173 | uint8_t* data = malloc(size); 174 | if(!data) { 175 | fclose(f); 176 | return NULL; 177 | } 178 | 179 | if(fread(data, 1, size, f) != *length) { 180 | fclose(f); 181 | free(data); 182 | return NULL; 183 | } 184 | 185 | fclose(f); 186 | return data; 187 | } 188 | 189 | // Callback called by J* when an import statement is finished. 190 | // Used to reset global state and free the previously read code. 191 | static void finalizeImport(void* userData) { 192 | pathClear(&import); 193 | pathClear(&nativeExt); 194 | char* data = userData; 195 | free(data); 196 | } 197 | 198 | // Creates a `JStarImportResult` and sets all relevant fields such as 199 | // the finalization callback and the native registry structure 200 | static JStarImportResult createImportResult(char* data, size_t length, const Path* path) { 201 | PROFILE_FUNC() 202 | JStarImportResult res; 203 | res.finalize = &finalizeImport; 204 | res.code = data; 205 | res.codeLength = length; 206 | res.path = path->data; 207 | res.reg = loadNativeExtension(path); 208 | res.userData = data; 209 | return res; 210 | } 211 | 212 | JStarImportResult importCallback(JStarVM* vm, const char* moduleName) { 213 | PROFILE_FUNC() 214 | 215 | // Retrieve the import paths list from the core module 216 | if(!jsrGetGlobal(vm, JSR_CORE_MODULE, IMPORT_PATHS)) { 217 | jsrPop(vm); 218 | return (JStarImportResult){0}; 219 | } 220 | 221 | if(!jsrIsList(vm, -1)) { 222 | jsrPop(vm); 223 | return (JStarImportResult){0}; 224 | } 225 | 226 | size_t importLen = jsrListGetLength(vm, -1); 227 | 228 | { 229 | PROFILE("importCallback::resolutionLoop") 230 | 231 | for(size_t i = 0; i < importLen; i++) { 232 | jsrListGet(vm, i, -1); 233 | if(!jsrIsString(vm, -1)) { 234 | jsrPop(vm); 235 | continue; 236 | } 237 | 238 | pathAppend(&import, jsrGetString(vm, -1), jsrGetStringSz(vm, -1)); 239 | size_t moduleStart = import.size; 240 | 241 | pathJoinStr(&import, moduleName); 242 | size_t moduleEnd = import.size; 243 | 244 | pathReplace(&import, moduleStart, '.', PATH_SEP_CHAR); 245 | 246 | char* data; 247 | size_t length; 248 | 249 | // Try loading a package (__package__ file inside a directory) 250 | pathJoinStr(&import, PACKAGE_FILE); 251 | 252 | // Try binary package 253 | pathChangeExtension(&import, JSC_EXT); 254 | if((data = readFile(&import, &length)) != NULL) { 255 | jsrPopN(vm, 2); 256 | return createImportResult(data, length, &import); 257 | } 258 | 259 | // Try source package 260 | pathChangeExtension(&import, JSR_EXT); 261 | if((data = readFile(&import, &length)) != NULL) { 262 | jsrPopN(vm, 2); 263 | return createImportResult(data, length, &import); 264 | } 265 | 266 | // If no package is found, try to load a module 267 | pathTruncate(&import, moduleEnd); 268 | 269 | // Try binary module 270 | pathChangeExtension(&import, JSC_EXT); 271 | if((data = readFile(&import, &length)) != NULL) { 272 | jsrPopN(vm, 2); 273 | return createImportResult(data, length, &import); 274 | } 275 | 276 | // Try source module 277 | pathChangeExtension(&import, JSR_EXT); 278 | if((data = readFile(&import, &length)) != NULL) { 279 | jsrPopN(vm, 2); 280 | return createImportResult(data, length, &import); 281 | } 282 | 283 | pathClear(&import); 284 | jsrPop(vm); 285 | } 286 | } 287 | 288 | jsrPop(vm); 289 | return (JStarImportResult){0}; 290 | } 291 | -------------------------------------------------------------------------------- /apps/jstar/import.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORT_H 2 | #define IMPORT_H 3 | 4 | #include "jstar/jstar.h" 5 | 6 | // Inits the`CLI` app import system 7 | void initImports(JStarVM* vm, const char* scriptPath, bool ignoreEnv); 8 | // Frees all resources associated with the import system 9 | void freeImports(void); 10 | 11 | // Callback called by the J* VM when it encounters an `import` statement 12 | JStarImportResult importCallback(JStarVM* vm, const char* moduleName); 13 | 14 | #endif -------------------------------------------------------------------------------- /apps/jstar/jstar.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/apps/jstar/jstar.ico -------------------------------------------------------------------------------- /apps/jstarc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Executable 2 | add_executable(jstarc main.c) 3 | target_link_libraries(jstarc PRIVATE jstar common argparse) 4 | target_include_directories(jstarc PRIVATE ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/profile) 5 | if(WIN32) 6 | target_link_libraries(jstarc PRIVATE dirent) 7 | endif() 8 | 9 | if(JSTAR_INSTRUMENT) 10 | target_link_libraries(jstarc PRIVATE profile) 11 | endif() 12 | 13 | # Enable link-time optimization if supported 14 | if(LTO) 15 | set_target_properties(jstarc PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 16 | endif() 17 | 18 | # Install target 19 | if(JSTAR_INSTALL) 20 | include(GNUInstallDirs) 21 | 22 | # Setup relative rpath on unix and macos 23 | if(APPLE) 24 | set_target_properties(jstarc PROPERTIES INSTALL_RPATH "@executable_path/../${CMAKE_INSTALL_LIBDIR}") 25 | elseif(UNIX) 26 | set_target_properties(jstarc PROPERTIES INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") 27 | endif() 28 | 29 | install(TARGETS jstarc 30 | EXPORT jstar-export 31 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 32 | ) 33 | endif() 34 | -------------------------------------------------------------------------------- /cmake/JStarConfig.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | include("${CMAKE_CURRENT_LIST_DIR}/JStarConfigVersion.cmake") 3 | include("${CMAKE_CURRENT_LIST_DIR}/JStarTargets.cmake") 4 | message(STATUS "Found J* version ${PACKAGE_VERSION}") 5 | -------------------------------------------------------------------------------- /cmake/MinGWRuntime.cmake: -------------------------------------------------------------------------------- 1 | if(MINGW) 2 | get_filename_component(MINGW_PATH ${CMAKE_CXX_COMPILER} PATH) 3 | set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS 4 | ${MINGW_PATH}/libgcc_s_seh-1.dll 5 | ${MINGW_PATH}/libstdc++-6.dll 6 | ${MINGW_PATH}/libwinpthread-1.dll 7 | ) 8 | endif() -------------------------------------------------------------------------------- /cmake/conf.h.in: -------------------------------------------------------------------------------- 1 | #ifndef JSTAR_CONF_H 2 | #define JSTAR_CONF_H 3 | 4 | // Version 5 | #define JSTAR_VERSION_MAJOR @JSTAR_VERSION_MAJOR@ 6 | #define JSTAR_VERSION_MINOR @JSTAR_VERSION_MINOR@ 7 | #define JSTAR_VERSION_PATCH @JSTAR_VERSION_PATCH@ 8 | #define JSTAR_VERSION_STRING "@JSTAR_VERSION_MAJOR@.@JSTAR_VERSION_MINOR@.@JSTAR_VERSION_PATCH@" 9 | 10 | // Increasing version number, used for range checking 11 | #define JSTAR_VERSION \ 12 | (JSTAR_VERSION_MAJOR * 100000 + JSTAR_VERSION_MINOR * 1000 + JSTAR_VERSION_PATCH) 13 | 14 | // compiler and platform on which this J* binary was compiled 15 | #define JSTAR_COMPILER "@CMAKE_C_COMPILER_ID@ @CMAKE_C_COMPILER_VERSION@" 16 | #define JSTAR_PLATFORM "@CMAKE_SYSTEM_NAME@" 17 | 18 | // Options 19 | #cmakedefine JSTAR_COMPUTED_GOTOS 20 | #cmakedefine JSTAR_NAN_TAGGING 21 | #cmakedefine JSTAR_DBG_PRINT_EXEC 22 | #cmakedefine JSTAR_DBG_PRINT_GC 23 | #cmakedefine JSTAR_DBG_STRESS_GC 24 | #cmakedefine JSTAR_DBG_CACHE_STATS 25 | 26 | #cmakedefine JSTAR_SYS 27 | #cmakedefine JSTAR_IO 28 | #cmakedefine JSTAR_MATH 29 | #cmakedefine JSTAR_DEBUG 30 | #cmakedefine JSTAR_RE 31 | 32 | // Platform detection 33 | #if defined(_WIN32) && (defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__)) 34 | #define JSTAR_WINDOWS 35 | #elif defined(__linux__) 36 | #define JSTAR_LINUX 37 | #define JSTAR_POSIX 38 | #elif defined(__ANDROID__) 39 | #define JSTAR_ANDROID 40 | #define JSTAR_POSIX 41 | #elif defined(__FreeBSD__) 42 | #define JSTAR_FREEBSD 43 | #define JSTAR_POSIX 44 | #elif defined(__OpenBSD__) 45 | #define JSTAR_OPENBSD 46 | #define JSTAR_POSIX 47 | #elif defined(__EMSCRIPTEN__) 48 | #define JSTAR_EMSCRIPTEN 49 | #elif defined(__APPLE__) || defined(__MACH__) 50 | #include 51 | 52 | #if TARGET_OS_IPHONE == 1 53 | #define JSTAR_IOS 54 | #elif TARGET_OS_MAC == 1 55 | #define JSTAR_MACOS 56 | #endif 57 | 58 | #define JSTAR_POSIX 59 | #endif 60 | 61 | // Macro for symbol exporting 62 | #ifndef JSTAR_STATIC 63 | #if defined(_WIN32) && defined(_MSC_VER) 64 | #if defined(jstar_EXPORTS) 65 | #define JSTAR_API __declspec(dllexport) 66 | #else 67 | #define JSTAR_API __declspec(dllimport) 68 | #endif 69 | #elif defined(__GNUC__) || defined(__clang__) 70 | #if defined(jstar_EXPORTS) 71 | #define JSTAR_API __attribute__((visibility("default"))) 72 | #else 73 | #define JSTAR_API 74 | #endif 75 | #else 76 | #define JSTAR_API 77 | #endif 78 | #else 79 | #define JSTAR_API 80 | #endif 81 | 82 | // Debug assertions 83 | #ifndef NDEBUG 84 | #include // IWYU pragma: keep 85 | #include // IWYU pragma: keep 86 | 87 | #define JSR_ASSERT(cond, msg) \ 88 | ((cond) ? ((void)0) \ 89 | : (fprintf(stderr, "%s [line:%d] in %s(): %s failed: %s\n", __FILE__, __LINE__, \ 90 | __func__, #cond, msg), \ 91 | abort())) 92 | 93 | #define JSR_UNREACHABLE() \ 94 | (fprintf(stderr, "%s [line:%d] in %s(): Reached unreachable code.\n", __FILE__, __LINE__, \ 95 | __func__), \ 96 | abort()) 97 | #else 98 | #define JSR_ASSERT(cond, msg) ((void)0) 99 | 100 | #if defined(__GNUC__) || defined(__clang__) 101 | #define JSR_UNREACHABLE() __builtin_unreachable() 102 | #elif defined(_MSC_VER) 103 | #include 104 | #define JSR_UNREACHABLE() __assume(0) 105 | #else 106 | #define JSR_UNREACHABLE() 107 | #endif 108 | #endif 109 | 110 | // Janky C99 static assert macro 111 | #ifndef static_assert 112 | #define JSR_CONCAT2_(pre, post) pre##post 113 | #define JSR_CONCAT_(pre, post) JSR_CONCAT2_(pre, post) 114 | #define JSR_STATIC_ASSERT(cond, msg) \ 115 | typedef struct { \ 116 | int static_assertion_failed : !!(cond); \ 117 | } JSR_CONCAT_(static_assertion_failed_, __COUNTER__) 118 | #else 119 | #define JSR_STATIC_ASSERT(cond, msg) static_assert(cond, msg) 120 | #endif 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /cmake/jstar.pc.in: -------------------------------------------------------------------------------- 1 | # this template is filled-in by CMake `configure_file(... @ONLY)` 2 | # the `@....@` are filled in by CMake configure_file(), 3 | # from variables set in your CMakeLists.txt or by CMake itself 4 | # 5 | # Good tutoral for understanding .pc files: 6 | # https://people.freedesktop.org/~dbn/pkg-config-guide.html 7 | 8 | prefix="@CMAKE_INSTALL_PREFIX@" 9 | exec_prefix="${prefix}" 10 | libdir="${prefix}/@CMAKE_INSTALL_LIBDIR@" 11 | includedir="${prefix}/@CMAKE_INSTALL_INCLUDEDIR@" 12 | 13 | Name: @PROJECT_NAME@ 14 | Description: @CMAKE_PROJECT_DESCRIPTION@ 15 | URL: @CMAKE_PROJECT_HOMEPAGE_URL@ 16 | Version: @JSTAR_VERSION@ 17 | Cflags: -I"${includedir}" 18 | Libs: -L"${libdir}" -ljstar 19 | Libs.private: -l@EXTRA_LIBS@ 20 | -------------------------------------------------------------------------------- /cmake/profileconf.h.in: -------------------------------------------------------------------------------- 1 | #ifndef PROFILECONF_H 2 | #define PROFILECONF_H 3 | 4 | #cmakedefine JSTAR_INSTRUMENT 5 | 6 | #endif -------------------------------------------------------------------------------- /extern/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Git QUIET) 2 | if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") 3 | # Update submodules as needed 4 | option(GIT_SUBMODULE "Check submodules during build" ON) 5 | if(GIT_SUBMODULE) 6 | message(STATUS "Submodule update") 7 | execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive 8 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 9 | RESULT_VARIABLE GIT_SUBMOD_RESULT) 10 | if(NOT GIT_SUBMOD_RESULT EQUAL "0") 11 | message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") 12 | endif() 13 | endif() 14 | endif() 15 | 16 | set(IGNORE_WARNINGS TRUE) 17 | add_subdirectory(argparse EXCLUDE_FROM_ALL) 18 | add_subdirectory(cwalk EXCLUDE_FROM_ALL) 19 | add_subdirectory(dirent EXCLUDE_FROM_ALL) 20 | 21 | set(REPLXX_BUILD_EXAMPLES OFF CACHE BOOL "Build the examples" FORCE) 22 | set(REPLXX_BUILD_PACKAGE OFF CACHE BOOL "Generate package target" FORCE) 23 | add_subdirectory(replxx EXCLUDE_FROM_ALL) 24 | -------------------------------------------------------------------------------- /extern/argparse/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(argparse) 2 | 3 | add_library(argparse argparse.c) 4 | target_include_directories(argparse PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 5 | -------------------------------------------------------------------------------- /extern/argparse/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2013 Yecheng Fu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /extern/argparse/argparse.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012-2015 Yecheng Fu 3 | * All rights reserved. 4 | * 5 | * Use of this source code is governed by a MIT-style license that can be found 6 | * in the LICENSE file. 7 | */ 8 | #ifndef ARGPARSE_H 9 | #define ARGPARSE_H 10 | 11 | /* For c++ compatibility */ 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #include 17 | 18 | struct argparse; 19 | struct argparse_option; 20 | 21 | typedef int argparse_callback (struct argparse *self, 22 | const struct argparse_option *option); 23 | 24 | enum argparse_flag { 25 | ARGPARSE_STOP_AT_NON_OPTION = 1 << 0, 26 | ARGPARSE_IGNORE_UNKNOWN_ARGS = 1 << 1, 27 | }; 28 | 29 | enum argparse_option_type { 30 | /* special */ 31 | ARGPARSE_OPT_END, 32 | ARGPARSE_OPT_GROUP, 33 | /* options with no arguments */ 34 | ARGPARSE_OPT_BOOLEAN, 35 | ARGPARSE_OPT_BIT, 36 | /* options with arguments (optional or required) */ 37 | ARGPARSE_OPT_INTEGER, 38 | ARGPARSE_OPT_FLOAT, 39 | ARGPARSE_OPT_STRING, 40 | }; 41 | 42 | enum argparse_option_flags { 43 | OPT_NONEG = 1, /* disable negation */ 44 | }; 45 | 46 | /** 47 | * argparse option 48 | * 49 | * `type`: 50 | * holds the type of the option, you must have an ARGPARSE_OPT_END last in your 51 | * array. 52 | * 53 | * `short_name`: 54 | * the character to use as a short option name, '\0' if none. 55 | * 56 | * `long_name`: 57 | * the long option name, without the leading dash, NULL if none. 58 | * 59 | * `value`: 60 | * stores pointer to the value to be filled. 61 | * 62 | * `help`: 63 | * the short help message associated to what the option does. 64 | * Must never be NULL (except for ARGPARSE_OPT_END). 65 | * 66 | * `callback`: 67 | * function is called when corresponding argument is parsed. 68 | * 69 | * `data`: 70 | * associated data. Callbacks can use it like they want. 71 | * 72 | * `flags`: 73 | * option flags. 74 | */ 75 | struct argparse_option { 76 | enum argparse_option_type type; 77 | const char short_name; 78 | const char *long_name; 79 | void *value; 80 | const char *help; 81 | argparse_callback *callback; 82 | intptr_t data; 83 | int flags; 84 | }; 85 | 86 | /** 87 | * argpparse 88 | */ 89 | struct argparse { 90 | // user supplied 91 | const struct argparse_option *options; 92 | const char *const *usages; 93 | int flags; 94 | const char *description; // a description after usage 95 | const char *epilog; // a description at the end 96 | // internal context 97 | int argc; 98 | const char **argv; 99 | const char **out; 100 | int cpidx; 101 | const char *optvalue; // current option value 102 | }; 103 | 104 | // built-in callbacks 105 | int argparse_help_cb(struct argparse *self, 106 | const struct argparse_option *option); 107 | int argparse_help_cb_no_exit(struct argparse *self, 108 | const struct argparse_option *option); 109 | 110 | // built-in option macros 111 | #define OPT_END() { .type = ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 } 112 | #define OPT_BOOLEAN(...) { .type = ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } 113 | #define OPT_BIT(...) { .type = ARGPARSE_OPT_BIT, __VA_ARGS__ } 114 | #define OPT_INTEGER(...) { .type = ARGPARSE_OPT_INTEGER, __VA_ARGS__ } 115 | #define OPT_FLOAT(...) { .type = ARGPARSE_OPT_FLOAT, __VA_ARGS__ } 116 | #define OPT_STRING(...) { .type = ARGPARSE_OPT_STRING, __VA_ARGS__ } 117 | #define OPT_GROUP(h) { .type = ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 } 118 | #define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \ 119 | "show this help message and exit", \ 120 | argparse_help_cb, 0, OPT_NONEG) 121 | 122 | int argparse_init(struct argparse *self, struct argparse_option *options, 123 | const char *const *usages, int flags); 124 | void argparse_describe(struct argparse *self, const char *description, 125 | const char *epilog); 126 | int argparse_parse(struct argparse *self, int argc, const char **argv); 127 | void argparse_usage(struct argparse *self); 128 | 129 | #ifdef __cplusplus 130 | } 131 | #endif 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /include/jstar/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef JSTAR_BUFFER_H 2 | #define JSTAR_BUFFER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "conf.h" 8 | 9 | struct JStarVM; 10 | 11 | // Dynamic Buffer that holds memory allocated by the J* garbage collector. 12 | // This memory is owned by J*, but cannot be collected until the buffer 13 | // is pushed on the stack using the jsrBufferPush method. 14 | // Can be used for efficient creation of Strings in the native API or 15 | // to efficiently store binary data. 16 | typedef struct JStarBuffer { 17 | struct JStarVM* vm; 18 | size_t capacity, size; 19 | char* data; 20 | } JStarBuffer; 21 | 22 | // ----------------------------------------------------------------------------- 23 | // JSTARBUFFER API 24 | // ----------------------------------------------------------------------------- 25 | 26 | /* 27 | * The following functions are safe to call prior to runtime initialization, with the obvious 28 | * exception of `jsrBufferPush`. 29 | */ 30 | 31 | JSTAR_API void jsrBufferInit(struct JStarVM* vm, JStarBuffer* b); 32 | JSTAR_API void jsrBufferInitCapacity(struct JStarVM* vm, JStarBuffer* b, size_t capacity); 33 | JSTAR_API void jsrBufferAppend(JStarBuffer* b, const char* str, size_t len); 34 | JSTAR_API void jsrBufferAppendStr(JStarBuffer* b, const char* str); 35 | JSTAR_API void jsrBufferAppendvf(JStarBuffer* b, const char* fmt, va_list ap); 36 | JSTAR_API void jsrBufferAppendf(JStarBuffer* b, const char* fmt, ...); 37 | JSTAR_API void jsrBufferTrunc(JStarBuffer* b, size_t len); 38 | JSTAR_API void jsrBufferCut(JStarBuffer* b, size_t len); 39 | JSTAR_API void jsrBufferReplaceChar(JStarBuffer* b, size_t start, char c, char r); 40 | JSTAR_API void jsrBufferPrepend(JStarBuffer* b, const char* str, size_t len); 41 | JSTAR_API void jsrBufferPrependStr(JStarBuffer* b, const char* str); 42 | JSTAR_API void jsrBufferAppendChar(JStarBuffer* b, char c); 43 | JSTAR_API void jsrBufferShrinkToFit(JStarBuffer* b); 44 | JSTAR_API void jsrBufferClear(JStarBuffer* b); 45 | 46 | // If not pushed with jsrBufferPush the buffer must be freed 47 | JSTAR_API void jsrBufferFree(JStarBuffer* b); 48 | 49 | // Once the buffer is pushed on the J* stack it becomes a String and can't be modified further 50 | // One can reuse the JStarBuffer struct by re-initializing it using the jsrBufferInit method. 51 | JSTAR_API void jsrBufferPush(JStarBuffer* b); 52 | 53 | #endif // BUFFER_H 54 | -------------------------------------------------------------------------------- /include/jstar/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef JSTAR_CONF_H 2 | #define JSTAR_CONF_H 3 | 4 | // Version 5 | #define JSTAR_VERSION_MAJOR 2 6 | #define JSTAR_VERSION_MINOR 0 7 | #define JSTAR_VERSION_PATCH 0 8 | #define JSTAR_VERSION_STRING "2.0.0" 9 | 10 | // Increasing version number, used for range checking 11 | #define JSTAR_VERSION \ 12 | (JSTAR_VERSION_MAJOR * 100000 + JSTAR_VERSION_MINOR * 1000 + JSTAR_VERSION_PATCH) 13 | 14 | // compiler and platform on which this J* binary was compiled 15 | #define JSTAR_COMPILER "GNU 14.2.1" 16 | #define JSTAR_PLATFORM "Linux" 17 | 18 | // Options 19 | #define JSTAR_COMPUTED_GOTOS 20 | #define JSTAR_NAN_TAGGING 21 | /* #undef JSTAR_DBG_PRINT_EXEC */ 22 | /* #undef JSTAR_DBG_PRINT_GC */ 23 | /* #undef JSTAR_DBG_STRESS_GC */ 24 | /* #undef JSTAR_DBG_CACHE_STATS */ 25 | 26 | #define JSTAR_SYS 27 | #define JSTAR_IO 28 | #define JSTAR_MATH 29 | #define JSTAR_DEBUG 30 | #define JSTAR_RE 31 | 32 | // Platform detection 33 | #if defined(_WIN32) && (defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__)) 34 | #define JSTAR_WINDOWS 35 | #elif defined(__linux__) 36 | #define JSTAR_LINUX 37 | #define JSTAR_POSIX 38 | #elif defined(__ANDROID__) 39 | #define JSTAR_ANDROID 40 | #define JSTAR_POSIX 41 | #elif defined(__FreeBSD__) 42 | #define JSTAR_FREEBSD 43 | #define JSTAR_POSIX 44 | #elif defined(__OpenBSD__) 45 | #define JSTAR_OPENBSD 46 | #define JSTAR_POSIX 47 | #elif defined(__EMSCRIPTEN__) 48 | #define JSTAR_EMSCRIPTEN 49 | #elif defined(__APPLE__) || defined(__MACH__) 50 | #include 51 | 52 | #if TARGET_OS_IPHONE == 1 53 | #define JSTAR_IOS 54 | #elif TARGET_OS_MAC == 1 55 | #define JSTAR_MACOS 56 | #endif 57 | 58 | #define JSTAR_POSIX 59 | #endif 60 | 61 | // Macro for symbol exporting 62 | #ifndef JSTAR_STATIC 63 | #if defined(_WIN32) && defined(_MSC_VER) 64 | #if defined(jstar_EXPORTS) 65 | #define JSTAR_API __declspec(dllexport) 66 | #else 67 | #define JSTAR_API __declspec(dllimport) 68 | #endif 69 | #elif defined(__GNUC__) || defined(__clang__) 70 | #if defined(jstar_EXPORTS) 71 | #define JSTAR_API __attribute__((visibility("default"))) 72 | #else 73 | #define JSTAR_API 74 | #endif 75 | #else 76 | #define JSTAR_API 77 | #endif 78 | #else 79 | #define JSTAR_API 80 | #endif 81 | 82 | // Debug assertions 83 | #ifndef NDEBUG 84 | #include // IWYU pragma: keep 85 | #include // IWYU pragma: keep 86 | 87 | #define JSR_ASSERT(cond, msg) \ 88 | ((cond) ? ((void)0) \ 89 | : (fprintf(stderr, "%s [line:%d] in %s(): %s failed: %s\n", __FILE__, __LINE__, \ 90 | __func__, #cond, msg), \ 91 | abort())) 92 | 93 | #define JSR_UNREACHABLE() \ 94 | (fprintf(stderr, "%s [line:%d] in %s(): Reached unreachable code.\n", __FILE__, __LINE__, \ 95 | __func__), \ 96 | abort()) 97 | #else 98 | #define JSR_ASSERT(cond, msg) ((void)0) 99 | 100 | #if defined(__GNUC__) || defined(__clang__) 101 | #define JSR_UNREACHABLE() __builtin_unreachable() 102 | #elif defined(_MSC_VER) 103 | #include 104 | #define JSR_UNREACHABLE() __assume(0) 105 | #else 106 | #define JSR_UNREACHABLE() 107 | #endif 108 | #endif 109 | 110 | // Janky C99 static assert macro 111 | #ifndef static_assert 112 | #define JSR_CONCAT2_(pre, post) pre##post 113 | #define JSR_CONCAT_(pre, post) JSR_CONCAT2_(pre, post) 114 | #define JSR_STATIC_ASSERT(cond, msg) \ 115 | typedef struct { \ 116 | int static_assertion_failed : !!(cond); \ 117 | } JSR_CONCAT_(static_assertion_failed_, __COUNTER__) 118 | #else 119 | #define JSR_STATIC_ASSERT(cond, msg) static_assert(cond, msg) 120 | #endif 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /include/jstar/parse/lex.h: -------------------------------------------------------------------------------- 1 | #ifndef JSTAR_LEX_H 2 | #define JSTAR_LEX_H 3 | 4 | #include 5 | 6 | #include "../conf.h" 7 | 8 | JSTAR_API extern const char* JStarTokName[]; 9 | 10 | typedef enum JStarTokType { 11 | #define TOKEN(tok, _) tok, 12 | #include "token.def" 13 | } JStarTokType; 14 | 15 | typedef struct JStarTok { 16 | JStarTokType type; 17 | const char* lexeme; 18 | int length, line; 19 | } JStarTok; 20 | 21 | typedef struct JStarLex { 22 | const char* source; 23 | size_t sourceLen; 24 | const char* tokenStart; 25 | const char* current; 26 | int currLine; 27 | } JStarLex; 28 | 29 | JSTAR_API void jsrInitLexer(JStarLex* lex, const char* src, size_t len); 30 | JSTAR_API void jsrNextToken(JStarLex* lex, JStarTok* tok); 31 | JSTAR_API void jsrLexRewind(JStarLex* lex, JStarTok* tok); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /include/jstar/parse/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef JSTAR_PARSER_H 2 | #define JSTAR_PARSER_H 3 | 4 | #include 5 | 6 | #include "ast.h" 7 | #include "jstar/conf.h" 8 | 9 | typedef void (*ParseErrorCB)(const char* file, int line, const char* error, void* userData); 10 | 11 | JSTAR_API JStarStmt* jsrParse(const char* path, const char* src, size_t len, ParseErrorCB errFn, 12 | void* data); 13 | JSTAR_API JStarExpr* jsrParseExpression(const char* path, const char* src, size_t len, 14 | ParseErrorCB errFn, void* data); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/jstar/parse/token.def: -------------------------------------------------------------------------------- 1 | TOKEN(TOK_LPAREN, "(") 2 | TOKEN(TOK_RPAREN, ")") 3 | TOKEN(TOK_LSQUARE, "[") 4 | TOKEN(TOK_RSQUARE, "]") 5 | TOKEN(TOK_LCURLY, "{") 6 | TOKEN(TOK_RCURLY, "}") 7 | 8 | TOKEN(TOK_BANG, "!") 9 | TOKEN(TOK_BANG_EQ, "!=") 10 | TOKEN(TOK_COMMA, ",") 11 | TOKEN(TOK_DOT, ".") 12 | TOKEN(TOK_ELLIPSIS, "...") 13 | TOKEN(TOK_ARROW, "=>") 14 | 15 | TOKEN(TOK_EQUAL, "=") 16 | TOKEN(TOK_PLUS_EQ, "+=") 17 | TOKEN(TOK_MINUS_EQ, "-=") 18 | TOKEN(TOK_DIV_EQ, "/=") 19 | TOKEN(TOK_MULT_EQ, "*=") 20 | TOKEN(TOK_MOD_EQ, "%=") 21 | 22 | TOKEN(TOK_EQUAL_EQUAL, "==") 23 | TOKEN(TOK_GT, ">") 24 | TOKEN(TOK_GE, ">=") 25 | TOKEN(TOK_LT, "<") 26 | TOKEN(TOK_LE, "<=") 27 | TOKEN(TOK_PLUS, "+") 28 | TOKEN(TOK_MINUS, "-") 29 | TOKEN(TOK_DIV, "/") 30 | TOKEN(TOK_MULT, "*") 31 | TOKEN(TOK_MOD, "%") 32 | TOKEN(TOK_POW, "^") 33 | TOKEN(TOK_AT, "@") 34 | TOKEN(TOK_HASH, "#") 35 | TOKEN(TOK_HASH_HASH, "##") 36 | 37 | TOKEN(TOK_PIPE, "|") 38 | TOKEN(TOK_AMPER, "&") 39 | TOKEN(TOK_TILDE, "~") 40 | TOKEN(TOK_LSHIFT, "<<") 41 | TOKEN(TOK_RSHIFT, ">>") 42 | TOKEN(TOK_COLON, ":") 43 | TOKEN(TOK_SEMICOLON, ";") 44 | TOKEN(TOK_IN, "in") 45 | 46 | TOKEN(TOK_BEGIN, "begin") 47 | TOKEN(TOK_END, "end") 48 | 49 | TOKEN(TOK_IDENTIFIER, "IDENTIFIER") 50 | TOKEN(TOK_STRING, "STRING") 51 | TOKEN(TOK_NUMBER, "NUMBER") 52 | 53 | TOKEN(TOK_AND, "and") 54 | TOKEN(TOK_OR, "or") 55 | TOKEN(TOK_CLASS, "class") 56 | TOKEN(TOK_ELSE, "else") 57 | TOKEN(TOK_FALSE, "false") 58 | TOKEN(TOK_NAT, "native") 59 | TOKEN(TOK_FUN, "fun") 60 | TOKEN(TOK_CTOR, "construct") 61 | TOKEN(TOK_FOR, "for") 62 | TOKEN(TOK_IF, "if") 63 | TOKEN(TOK_ELIF, "elif") 64 | TOKEN(TOK_NULL, "null") 65 | TOKEN(TOK_PRINT, "print") 66 | TOKEN(TOK_RETURN, "return") 67 | TOKEN(TOK_YIELD, "yield") 68 | TOKEN(TOK_IMPORT, "import") 69 | TOKEN(TOK_AS, "as") 70 | TOKEN(TOK_IS, "is") 71 | TOKEN(TOK_SUPER, "super") 72 | TOKEN(TOK_TRUE, "true") 73 | TOKEN(TOK_VAR, "var") 74 | TOKEN(TOK_WHILE, "while") 75 | TOKEN(TOK_CONTINUE, "continue") 76 | TOKEN(TOK_BREAK, "break") 77 | TOKEN(TOK_STATIC, "static") 78 | 79 | TOKEN(TOK_TRY, "try") 80 | TOKEN(TOK_EXCEPT, "except") 81 | TOKEN(TOK_ENSURE, "ensure") 82 | TOKEN(TOK_RAISE, "raise") 83 | TOKEN(TOK_WITH, "with") 84 | 85 | TOKEN(TOK_UNTERMINATED_STR, "unterminated string") 86 | TOKEN(TOK_NEWLINE, "newline") 87 | TOKEN(TOK_ERR, "error") 88 | 89 | // This must be the last token 90 | TOKEN(TOK_EOF, "end of file") 91 | #undef TOKEN 92 | -------------------------------------------------------------------------------- /include/jstar/parse/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef JSTAR_VECTOR_H 2 | #define JSTAR_VECTOR_H 3 | 4 | #include "../conf.h" 5 | 6 | #include 7 | #include 8 | 9 | // Utility macro for iterating in a foreach style 10 | #define ext_vec_foreach(elem, vec) \ 11 | for(size_t __cont = 1, __i = 0; __cont && __i < ext_vec_size(vec); __cont = !__cont, __i++) \ 12 | for(elem = ext_vec_iterator(vec, __i); __cont; __cont = !__cont) 13 | 14 | // ----------------------------------------------------------------------------- 15 | // ALLOCATION 16 | // ----------------------------------------------------------------------------- 17 | 18 | // Utility macro for declaring a vector 19 | #define ext_vector(T) T* 20 | 21 | // Release vector resources 22 | #define ext_vec_free(vec) \ 23 | do { \ 24 | if(vec) free(ext_vec_header_(vec)); \ 25 | } while(0) 26 | 27 | // ----------------------------------------------------------------------------- 28 | // CAPACITY 29 | // ----------------------------------------------------------------------------- 30 | 31 | #define ext_vec_size(vec) ((vec) ? (ext_vec_header_(vec)->size) : (size_t)0) 32 | #define ext_vec_capacity(vec) ((vec) ? (ext_vec_header_(vec)->capacity) : (size_t)0) 33 | #define ext_vec_empty(vec) (ext_vec_size(vec) == 0) 34 | 35 | // ----------------------------------------------------------------------------- 36 | // ELEMENT ACCESS 37 | // ----------------------------------------------------------------------------- 38 | 39 | #define ext_vec_front(vec) ((vec)[0]) 40 | #define ext_vec_back(vec) ((vec)[ext_vec_size(vec) - 1]) 41 | 42 | // ----------------------------------------------------------------------------- 43 | // ITERATORS 44 | // ----------------------------------------------------------------------------- 45 | 46 | #define ext_vec_begin(vec) (vec) 47 | #define ext_vec_end(vec) ((vec) ? (vec) + ext_vec_header_(vec)->size : NULL) 48 | 49 | #define ext_vec_iterator(vec, i) ((vec) + i) 50 | #define ext_vec_iterator_index(vec, it) (it - (vec)) 51 | 52 | // ----------------------------------------------------------------------------- 53 | // MODIFIERS 54 | // ----------------------------------------------------------------------------- 55 | 56 | #define ext_vec_push_back(vec, e) \ 57 | do { \ 58 | ext_vec_maybe_grow_(vec, 1); \ 59 | size_t size = ext_vec_size(vec); \ 60 | (vec)[size] = (e); \ 61 | ext_vec_set_size_(vec, size + 1); \ 62 | } while(0) 63 | 64 | #define ext_vec_push_back_all(vec, arr, size) \ 65 | do { \ 66 | ext_vec_reserve(vec, ext_vec_size(vec) + size); \ 67 | for(size_t i = 0; i < size; i++) { \ 68 | ext_vec_push_back(vec, arr[i]); \ 69 | } \ 70 | } while(0) 71 | 72 | #define ext_vec_pop_back(vec) \ 73 | do { \ 74 | JSR_ASSERT(ext_vec_size(vec) != 0, "Cannot pop_back on empty vector"); \ 75 | ext_vec_set_size_(vec, ext_vec_size(vec) - 1); \ 76 | } while(0) 77 | 78 | #define ext_vec_insert(vec, i, e) \ 79 | do { \ 80 | ext_vec_maybe_grow_(vec, 1); \ 81 | size_t size = ext_vec_size(vec); \ 82 | JSR_ASSERT(i < size + 1, "Buffer overflow"); \ 83 | size_t shift = (size - i) * sizeof(*(vec)); \ 84 | memmove((vec) + i + 1, (vec) + i, shift); \ 85 | (vec)[i] = (e); \ 86 | ext_vec_set_size_(vec, size + 1); \ 87 | } while(0) 88 | 89 | #define ext_vec_erase(vec, i) \ 90 | do { \ 91 | size_t size = ext_vec_size(vec); \ 92 | JSR_ASSERT(i < size, "Buffer overflow"); \ 93 | size_t shift = (size - i - 1) * sizeof(*(vec)); \ 94 | memmove((vec) + i, (vec) + i + 1, shift); \ 95 | ext_vec_set_size_(vec, size - 1); \ 96 | } while(0); 97 | 98 | #define ext_vec_clear(vec) \ 99 | do { \ 100 | if(vec) ext_vec_header_(vec)->size = 0; \ 101 | } while(0) 102 | 103 | #define ext_vec_reserve(vec, amount) \ 104 | do { \ 105 | if(!(vec)) { \ 106 | vec_header_t* header = malloc(sizeof(*header) + (amount) * sizeof(*(vec))); \ 107 | JSR_ASSERT(header, "Out of memory"); \ 108 | header->capacity = (amount); \ 109 | header->size = 0; \ 110 | (vec) = (void*)(header->data); \ 111 | } else if(ext_vec_capacity(vec) < (amount)) { \ 112 | vec_header_t* header = ext_vec_header_(vec); \ 113 | header = realloc(header, sizeof(*header) + (amount) * sizeof(*(vec))); \ 114 | JSR_ASSERT(header, "Out of memory"); \ 115 | header->capacity = (amount); \ 116 | (vec) = (void*)(header->data); \ 117 | } \ 118 | } while(0) 119 | 120 | #define ext_vec_resize(vec, new_size, elem) \ 121 | do { \ 122 | size_t size = ext_vec_size(vec); \ 123 | if(new_size < size) { \ 124 | ext_vec_set_size_(vec, new_size); \ 125 | } else { \ 126 | ext_vec_reserve(vec, new_size); \ 127 | for(size_t i = size; i < new_size; i++) { \ 128 | (vec)[i] = elem; \ 129 | } \ 130 | ext_vec_set_size_(vec, new_size); \ 131 | } \ 132 | } while(0) 133 | 134 | #define ext_vec_shrink_to_fit(vec) \ 135 | do { \ 136 | if(vec) { \ 137 | vec_header_t* header = ext_vec_header_(vec); \ 138 | if(header->size) { \ 139 | header = realloc(header, sizeof(*header) + sizeof(*(vec)) * header->size); \ 140 | JSR_ASSERT(header, "Out of memory"); \ 141 | header->capacity = header->size; \ 142 | (vec) = (void*)(header->data); \ 143 | } else { \ 144 | free(header); \ 145 | (vec) = NULL; \ 146 | } \ 147 | } \ 148 | } while(0) 149 | 150 | // ----------------------------------------------------------------------------- 151 | // PRIVATE - DON'T USE DIRECTLY 152 | // ----------------------------------------------------------------------------- 153 | 154 | typedef struct { 155 | size_t capacity, size; // Capacity (allocated memory) and size (slots used in the vector) 156 | char data[]; // The actual start of the vector memory 157 | } vec_header_t; 158 | 159 | #define ext_vec_maybe_grow_(vec, amount) \ 160 | do { \ 161 | size_t capacity = ext_vec_capacity(vec); \ 162 | size_t size = ext_vec_size(vec); \ 163 | if(size + (amount) > capacity) { \ 164 | size_t new_capacity = capacity ? capacity * 2 : 1; \ 165 | while(size + (amount) > new_capacity) new_capacity *= 2; \ 166 | ext_vec_reserve(vec, new_capacity); \ 167 | } \ 168 | } while(0) 169 | 170 | #define ext_vec_header_(vec) ((vec_header_t*)((char*)(vec) - sizeof(vec_header_t))) 171 | #define ext_vec_set_capacity_(vec, cap) (ext_vec_header_(vec)->capacity = cap) 172 | #define ext_vec_set_size_(vec, sz) (ext_vec_header_(vec)->size = sz) 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /profile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(profile profiler.h profiler.c) 2 | set_property(TARGET profile PROPERTY POSITION_INDEPENDENT_CODE ON) 3 | target_include_directories(profile PRIVATE ${PROJECT_BINARY_DIR}) 4 | 5 | configure_file ( 6 | ${PROJECT_SOURCE_DIR}/cmake/profileconf.h.in 7 | ${PROJECT_BINARY_DIR}/profileconf.h 8 | ) 9 | 10 | if(LTO) 11 | set_target_properties(profile PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 12 | endif() -------------------------------------------------------------------------------- /profile/profiler.c: -------------------------------------------------------------------------------- 1 | #include "profiler.h" 2 | 3 | #ifdef JSTAR_INSTRUMENT 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | static FILE* sessionFile = NULL; 10 | static int profileCount = 0; 11 | 12 | static void writeHeader(void) { 13 | fputs("{\"otherData\": {},\"traceEvents\":[", sessionFile); 14 | fflush(sessionFile); 15 | } 16 | 17 | static void writeFooter(void) { 18 | fputs("]}", sessionFile); 19 | fflush(sessionFile); 20 | } 21 | 22 | void startProfileSession(const char* filePath) { 23 | sessionFile = fopen(filePath, "w"); 24 | if(!sessionFile) { 25 | fprintf(stderr, "Cannot open session file\n"); 26 | abort(); 27 | } 28 | writeHeader(); 29 | } 30 | 31 | void endProfileSession(void) { 32 | writeFooter(); 33 | 34 | int res = fclose(sessionFile); 35 | if(res) { 36 | fprintf(stderr, "Cannot close session file\n"); 37 | abort(); 38 | } 39 | 40 | sessionFile = NULL; 41 | profileCount = 0; 42 | } 43 | 44 | static void writeInstrumentRecord(const char* name, uint64_t startNano, uint64_t endNano) { 45 | if(!sessionFile) { 46 | fprintf(stderr, "No session started\n"); 47 | abort(); 48 | } 49 | 50 | double timestamp = startNano / 1000.0; 51 | double elapsed = (endNano - startNano) / 1000.0; 52 | 53 | if(profileCount++ > 0) { 54 | fputc(',', sessionFile); 55 | } 56 | fprintf(sessionFile, 57 | "{\"cat\":\"function\",\"dur\":%lf,\"name\":\"%s\",\"ph\":\"X\",\"pid\":0,\"tid\":0," 58 | "\"ts\":%lf}", 59 | elapsed, name, timestamp); 60 | } 61 | 62 | InstrumentationTimer startProfileTimer(const char* name) { 63 | struct timespec tp; 64 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp); 65 | return (InstrumentationTimer){.name = name, .start = tp.tv_sec * 1000000000LL + tp.tv_nsec}; 66 | } 67 | 68 | void endProfileTimer(const InstrumentationTimer* timer) { 69 | struct timespec tp; 70 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp); 71 | writeInstrumentRecord(timer->name, timer->start, tp.tv_sec * 1000000000LL + tp.tv_nsec); 72 | } 73 | 74 | #endif -------------------------------------------------------------------------------- /profile/profiler.h: -------------------------------------------------------------------------------- 1 | #ifndef PROFILER_H 2 | #define PROFILER_H 3 | 4 | #include "profileconf.h" 5 | 6 | #ifdef JSTAR_INSTRUMENT 7 | 8 | #include 9 | 10 | typedef struct InstrumentationTimer { 11 | const char* name; 12 | uint64_t start; 13 | } InstrumentationTimer; 14 | 15 | void startProfileSession(const char* filePath); 16 | void endProfileSession(void); 17 | 18 | InstrumentationTimer startProfileTimer(const char* name); 19 | void endProfileTimer(const InstrumentationTimer* timer); 20 | 21 | #endif 22 | 23 | #ifdef JSTAR_INSTRUMENT 24 | #define PROFILE_LINE2_(name, line) \ 25 | InstrumentationTimer _timer_##line \ 26 | __attribute__((__cleanup__(endProfileTimer))) = startProfileTimer(name); 27 | 28 | #define PROFILE_LINE_(name, line) PROFILE_LINE2_(name, line) 29 | 30 | #define PROFILE_BEGIN_SESSION(name) startProfileSession(name); 31 | #define PROFILE_END_SESSION() endProfileSession(); 32 | 33 | #define PROFILE(name) PROFILE_LINE_(name, __LINE__) 34 | #define PROFILE_FUNC() PROFILE(__func__) 35 | #else 36 | #define PROFILE_BEGIN_SESSION(name) 37 | #define PROFILE_END_SESSION() 38 | #define PROFILE_FUNC() 39 | #define PROFILE(name) 40 | #endif 41 | 42 | #endif // PROFILER_H 43 | -------------------------------------------------------------------------------- /scripts/bin2incl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os.path 4 | from argparse import ArgumentParser 5 | 6 | WARNING = "// WARNING: this is a file generated automatically by the build process, do not modify\n" 7 | 8 | argparser = ArgumentParser() 9 | argparser.add_argument("file", help="J* compiled file to convert") 10 | argparser.add_argument("out", help="The name of the generated header") 11 | 12 | args = argparser.parse_args() 13 | 14 | name = os.path.basename(args.file).replace(".", "_") 15 | include_builder = [WARNING, "const char* {} = ".format(name)] 16 | 17 | with open(args.file, "rb") as f: 18 | # convert binary file to hex byte array 19 | include_builder.append('"') 20 | 21 | byte = f.read(1) 22 | while byte: 23 | include_builder.append("\\x{:02x}".format(ord(byte))) 24 | byte = f.read(1) 25 | 26 | include_builder.append('";') 27 | include_builder.append("\n") 28 | 29 | # write file length 30 | include_builder.append("const size_t {}_len = {};".format(name, os.path.getsize(args.file))) 31 | 32 | with open(args.out, "w") as out: 33 | out.write("".join(include_builder)) 34 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # Sources 3 | # ----------------------------------------------------------------------------- 4 | 5 | set(JSTAR_SOURCES 6 | ${PROJECT_SOURCE_DIR}/include/jstar/jstar.h 7 | ${PROJECT_SOURCE_DIR}/include/jstar/buffer.h 8 | ${PROJECT_SOURCE_DIR}/include/jstar/conf.h 9 | ${PROJECT_SOURCE_DIR}/include/jstar/parse/ast.h 10 | ${PROJECT_SOURCE_DIR}/include/jstar/parse/lex.h 11 | ${PROJECT_SOURCE_DIR}/include/jstar/parse/parser.h 12 | ${PROJECT_SOURCE_DIR}/include/jstar/parse/vector.h 13 | 14 | parse/ast.c 15 | parse/lex.c 16 | parse/parser.c 17 | 18 | lib/core/core.c 19 | lib/core/core.h 20 | lib/core/std.h 21 | lib/core/std.c 22 | lib/core/excs.h 23 | lib/core/excs.c 24 | lib/core/iter.h 25 | lib/core/iter.c 26 | lib/builtins.h 27 | lib/builtins.c 28 | 29 | buffer.c 30 | code.c 31 | code.h 32 | compiler.c 33 | compiler.h 34 | disassemble.c 35 | disassemble.h 36 | endianness.h 37 | gc.c 38 | gc.h 39 | hashtable.h 40 | import.c 41 | import.h 42 | int_hashtable.h 43 | int_hashtable.c 44 | jstar.c 45 | jstar_limits.h 46 | object.c 47 | object.h 48 | opcode.h 49 | opcode.c 50 | serialize.c 51 | serialize.h 52 | util.h 53 | value.c 54 | value.h 55 | value_hashtable.h 56 | value_hashtable.c 57 | vm.c 58 | vm.h 59 | ) 60 | 61 | # J* standard library files 62 | set(JSTAR_STDLIB 63 | lib/core/core.jsc 64 | lib/core/std.jsc 65 | lib/core/excs.jsc 66 | lib/core/iter.jsc 67 | ) 68 | 69 | # Add optional module source files 70 | if(JSTAR_SYS) 71 | list(APPEND JSTAR_SOURCES lib/sys.h lib/sys.c) 72 | list(APPEND JSTAR_STDLIB lib/sys.jsc) 73 | endif() 74 | if(JSTAR_IO) 75 | list(APPEND JSTAR_SOURCES lib/io.h lib/io.c) 76 | list(APPEND JSTAR_STDLIB lib/io.jsc) 77 | endif() 78 | if(JSTAR_MATH) 79 | list(APPEND JSTAR_SOURCES lib/math.h lib/math.c) 80 | list(APPEND JSTAR_STDLIB lib/math.jsc) 81 | endif() 82 | if(JSTAR_DEBUG) 83 | list(APPEND JSTAR_SOURCES lib/debug.h lib/debug.c) 84 | list(APPEND JSTAR_STDLIB lib/debug.jsc) 85 | endif() 86 | if(JSTAR_RE) 87 | list(APPEND JSTAR_SOURCES lib/re.h lib/re.c) 88 | list(APPEND JSTAR_STDLIB lib/re.jsc) 89 | endif() 90 | 91 | # Generate J* sandard library source headers 92 | set(JSTAR_STDLIB_HEADERS) 93 | foreach(jsr ${JSTAR_STDLIB}) 94 | list(APPEND JSTAR_STDLIB_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/${jsr}.inc) 95 | add_custom_command( 96 | OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/${jsr}.inc 97 | COMMAND ${Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/bin2incl.py ${CMAKE_CURRENT_SOURCE_DIR}/${jsr} ${CMAKE_CURRENT_SOURCE_DIR}/${jsr}.inc 98 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${jsr} 99 | ) 100 | endforeach() 101 | 102 | # ----------------------------------------------------------------------------- 103 | # Prepare extra libraries 104 | # ----------------------------------------------------------------------------- 105 | 106 | # set extra libraries that we need to link 107 | set(EXTRA_LIBS) 108 | if(UNIX) 109 | set(EXTRA_LIBS m) 110 | endif() 111 | 112 | if(JSTAR_COMPUTED_GOTOS) 113 | if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU") 114 | # disable crossjumping optimization on vm.c for an extra ~15% dispatch performance 115 | set_property(SOURCE vm.c PROPERTY COMPILE_FLAGS -fno-crossjumping) 116 | endif() 117 | endif() 118 | 119 | # ----------------------------------------------------------------------------- 120 | # Library targets 121 | # ----------------------------------------------------------------------------- 122 | 123 | # static library 124 | add_library(jstar_static STATIC ${JSTAR_SOURCES} ${JSTAR_STDLIB_HEADERS} ${JSTAR_STDLIB}) 125 | target_compile_definitions(jstar_static PUBLIC JSTAR_STATIC) 126 | target_link_libraries(jstar_static PUBLIC ${EXTRA_LIBS}) 127 | target_include_directories(jstar_static 128 | PUBLIC 129 | $ 130 | $ 131 | PRIVATE 132 | ${CMAKE_CURRENT_SOURCE_DIR} 133 | ${PROJECT_SOURCE_DIR}/include/jstar 134 | ${PROJECT_SOURCE_DIR}/profile 135 | ${PROJECT_BINARY_DIR} 136 | ) 137 | if(NOT WIN32) 138 | set_target_properties(jstar_static PROPERTIES 139 | OUTPUT_NAME "jstar" 140 | VERSION ${JSTAR_VERSION} 141 | ) 142 | endif() 143 | 144 | #shared library 145 | add_library(jstar SHARED ${JSTAR_SOURCES} ${JSTAR_STDLIB_HEADERS} ${JSTAR_STDLIB}) 146 | target_link_libraries(jstar PUBLIC ${EXTRA_LIBS}) 147 | target_include_directories(jstar 148 | PUBLIC 149 | $ 150 | $ 151 | PRIVATE 152 | ${CMAKE_CURRENT_SOURCE_DIR} 153 | ${PROJECT_SOURCE_DIR}/include/jstar 154 | ${PROJECT_SOURCE_DIR}/profile 155 | ${PROJECT_BINARY_DIR} 156 | ) 157 | set_target_properties(jstar PROPERTIES 158 | PDB_NAME "jstar.dll" 159 | VERSION ${JSTAR_VERSION} 160 | SOVERSION ${JSTAR_VERSION_MAJOR} 161 | C_VISIBILITY_PRESET hidden 162 | ) 163 | 164 | if(JSTAR_INSTRUMENT) 165 | target_link_libraries(jstar PRIVATE profile) 166 | endif() 167 | 168 | # Enable link-time optimization if supported 169 | if(LTO) 170 | set_target_properties(jstar PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 171 | set_target_properties(jstar_static PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) 172 | endif() 173 | 174 | # ----------------------------------------------------------------------------- 175 | # Installation 176 | # ----------------------------------------------------------------------------- 177 | 178 | # Install target 179 | if(JSTAR_INSTALL) 180 | include(GNUInstallDirs) 181 | 182 | # Install J* library 183 | install(TARGETS jstar jstar_static 184 | EXPORT jstar-export 185 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 186 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 187 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 188 | ) 189 | 190 | # Install header files 191 | install(DIRECTORY 192 | ${PROJECT_SOURCE_DIR}/include/ 193 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 194 | PATTERN "*.h.in" EXCLUDE 195 | ) 196 | 197 | # Configure and install pkg-config file 198 | configure_file( 199 | ${PROJECT_SOURCE_DIR}/cmake/jstar.pc.in 200 | ${CMAKE_BINARY_DIR}/jstar.pc 201 | @ONLY 202 | ) 203 | install( 204 | FILES ${CMAKE_BINARY_DIR}/jstar.pc 205 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig 206 | ) 207 | endif() 208 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "gc.h" 8 | #include "jstar.h" 9 | #include "object.h" 10 | #include "value.h" 11 | #include "vm.h" 12 | 13 | #define JSR_BUF_DEFAULT_CAPACITY 16 14 | 15 | static void bufferGrow(JStarBuffer* b, size_t len) { 16 | size_t newSize = b->capacity; 17 | while(newSize < b->size + len) newSize *= 2; 18 | char* newData = gcAlloc(b->vm, b->data, b->capacity, newSize); 19 | b->capacity = newSize; 20 | b->data = newData; 21 | } 22 | 23 | void jsrBufferInit(JStarVM* vm, JStarBuffer* b) { 24 | jsrBufferInitCapacity(vm, b, JSR_BUF_DEFAULT_CAPACITY); 25 | } 26 | 27 | void jsrBufferInitCapacity(JStarVM* vm, JStarBuffer* b, size_t capacity) { 28 | if(capacity < JSR_BUF_DEFAULT_CAPACITY) capacity = JSR_BUF_DEFAULT_CAPACITY; 29 | b->vm = vm; 30 | b->capacity = capacity; 31 | b->size = 0; 32 | b->data = GC_ALLOC(vm, capacity); 33 | } 34 | 35 | void jsrBufferAppend(JStarBuffer* b, const char* str, size_t len) { 36 | if(b->size + len >= b->capacity) { 37 | bufferGrow(b, len + 1); // the >= and the +1 are for the terminating NUL 38 | } 39 | memcpy(&b->data[b->size], str, len); 40 | b->size += len; 41 | b->data[b->size] = '\0'; 42 | } 43 | 44 | void jsrBufferAppendStr(JStarBuffer* b, const char* str) { 45 | jsrBufferAppend(b, str, strlen(str)); 46 | } 47 | 48 | void jsrBufferAppendvf(JStarBuffer* b, const char* fmt, va_list ap) { 49 | size_t availableSpace = b->capacity - b->size; 50 | 51 | va_list cpy; 52 | va_copy(cpy, ap); 53 | size_t written = vsnprintf(&b->data[b->size], availableSpace, fmt, cpy); 54 | va_end(cpy); 55 | 56 | // Not enough space, need to grow and retry 57 | if(written >= availableSpace) { 58 | bufferGrow(b, written + 1); 59 | availableSpace = b->capacity - b->size; 60 | va_copy(cpy, ap); 61 | written = vsnprintf(&b->data[b->size], availableSpace, fmt, cpy); 62 | JSR_ASSERT(written < availableSpace, "Buffer still to small"); 63 | va_end(cpy); 64 | } 65 | 66 | b->size += written; 67 | } 68 | 69 | void jsrBufferAppendf(JStarBuffer* b, const char* fmt, ...) { 70 | va_list ap; 71 | va_start(ap, fmt); 72 | jsrBufferAppendvf(b, fmt, ap); 73 | va_end(ap); 74 | } 75 | 76 | void jsrBufferTrunc(JStarBuffer* b, size_t len) { 77 | if(len >= b->size) return; 78 | b->size = len; 79 | b->data[len] = '\0'; 80 | } 81 | 82 | void jsrBufferCut(JStarBuffer* b, size_t len) { 83 | if(len == 0 || len > b->size) return; 84 | memmove(b->data, b->data + len, b->size - len); 85 | b->size -= len; 86 | b->data[b->size] = '\0'; 87 | } 88 | 89 | void jsrBufferReplaceChar(JStarBuffer* b, size_t start, char c, char r) { 90 | for(size_t i = start; i < b->size; i++) { 91 | if(b->data[i] == c) { 92 | b->data[i] = r; 93 | } 94 | } 95 | } 96 | 97 | void jsrBufferPrepend(JStarBuffer* b, const char* str, size_t len) { 98 | if(b->size + len >= b->capacity) { 99 | bufferGrow(b, len + 1); // the >= and the +1 are for the terminating NUL 100 | } 101 | memmove(b->data + len, b->data, b->size); 102 | memcpy(b->data, str, len); 103 | b->size += len; 104 | b->data[b->size] = '\0'; 105 | } 106 | 107 | void jsrBufferPrependStr(JStarBuffer* b, const char* str) { 108 | jsrBufferPrepend(b, str, strlen(str)); 109 | } 110 | 111 | void jsrBufferAppendChar(JStarBuffer* b, char c) { 112 | if(b->size + 1 >= b->capacity) bufferGrow(b, 2); 113 | b->data[b->size++] = c; 114 | b->data[b->size] = '\0'; 115 | } 116 | 117 | void jsrBufferShrinkToFit(JStarBuffer* b) { 118 | b->data = gcAlloc(b->vm, b->data, b->capacity, b->size); 119 | b->capacity = b->size; 120 | } 121 | 122 | void jsrBufferClear(JStarBuffer* b) { 123 | b->size = 0; 124 | b->data[0] = '\0'; 125 | } 126 | 127 | void jsrBufferPush(JStarBuffer* b) { 128 | JStarVM* vm = b->vm; 129 | push(vm, OBJ_VAL(jsrBufferToString(b))); 130 | } 131 | 132 | void jsrBufferFree(JStarBuffer* b) { 133 | if(b->data != NULL) { 134 | GC_FREE_ARRAY(b->vm, char, b->data, b->capacity); 135 | } 136 | *b = (JStarBuffer){0}; 137 | } 138 | -------------------------------------------------------------------------------- /src/code.c: -------------------------------------------------------------------------------- 1 | #include "code.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "conf.h" 7 | #include "util.h" 8 | 9 | void initCode(Code* c) { 10 | *c = (Code){0}; 11 | initValueArray(&c->consts); 12 | } 13 | 14 | void freeCode(Code* c) { 15 | free(c->bytecode); 16 | free(c->lines); 17 | freeValueArray(&c->consts); 18 | free(c->symbols); 19 | } 20 | 21 | size_t writeByte(Code* c, uint8_t b, int line) { 22 | ARRAY_APPEND(c, size, capacity, bytecode, b); 23 | ARRAY_APPEND(c, lineSize, lineCapacity, lines, line); 24 | return c->size - 1; 25 | } 26 | 27 | int getBytecodeSrcLine(const Code* c, size_t index) { 28 | if(c->lines == NULL) return -1; 29 | JSR_ASSERT(index < c->lineSize, "Line buffer overflow"); 30 | return c->lines[index]; 31 | } 32 | 33 | int addConstant(Code* c, Value constant) { 34 | ValueArray* consts = &c->consts; 35 | if(consts->size == UINT16_MAX) return -1; 36 | 37 | for(int i = 0; i < consts->size; i++) { 38 | if(valueEquals(consts->arr[i], constant)) { 39 | return i; 40 | } 41 | } 42 | 43 | return valueArrayAppend(&c->consts, constant); 44 | } 45 | 46 | int addSymbol(Code* c, uint16_t constant) { 47 | if(c->symbolCount == UINT16_MAX) return -1; 48 | ARRAY_APPEND(c, symbolCount, symbolCapacity, symbols, (Symbol){.constant = constant}); 49 | return c->symbolCount - 1; 50 | } 51 | -------------------------------------------------------------------------------- /src/code.h: -------------------------------------------------------------------------------- 1 | #ifndef CHUNK_H 2 | #define CHUNK_H 3 | 4 | #include 5 | #include 6 | 7 | #include "symbol.h" 8 | #include "value.h" 9 | 10 | // A runtime representation of a J* bytecode chunk. 11 | // Stores the bytecode, the constants and the symbols used in the chunk, as well as metadata 12 | // associated with each opcode (such as the original source line number). 13 | typedef struct Code { 14 | size_t capacity, size; 15 | uint8_t* bytecode; 16 | size_t lineCapacity, lineSize; 17 | int* lines; 18 | ValueArray consts; 19 | size_t symbolCapacity, symbolCount; 20 | Symbol* symbols; 21 | } Code; 22 | 23 | void initCode(Code* c); 24 | void freeCode(Code* c); 25 | 26 | size_t writeByte(Code* c, uint8_t b, int line); 27 | int addConstant(Code* c, Value constant); 28 | int addSymbol(Code* c, uint16_t constant); 29 | int getBytecodeSrcLine(const Code* c, size_t index); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/compiler.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPILER_H 2 | #define COMPILER_H 3 | 4 | #include 5 | 6 | #include "jstar.h" 7 | #include "object_types.h" 8 | #include "parse/ast.h" 9 | 10 | // At most 255 local variables per frame 11 | #define MAX_LOCALS UINT8_MAX 12 | 13 | typedef struct Compiler Compiler; 14 | 15 | ObjFunction* compile(JStarVM* vm, const char* filename, ObjModule* module, const JStarStmt* s); 16 | void reachCompilerRoots(JStarVM* vm, Compiler* c); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/disassemble.c: -------------------------------------------------------------------------------- 1 | #include "disassemble.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "object.h" 9 | #include "opcode.h" 10 | #include "value.h" 11 | 12 | #define INDENT 4 13 | 14 | static uint16_t readShortAt(const uint8_t* code, size_t i) { 15 | return ((uint16_t)code[i] << 8) | code[i + 1]; 16 | } 17 | 18 | static size_t countInstructions(const Code* c) { 19 | size_t count = 0; 20 | for(size_t i = 0; i < c->size;) { 21 | count++; 22 | Opcode instr = c->bytecode[i]; 23 | if(instr == OP_CLOSURE) { 24 | Value func = c->consts.arr[readShortAt(c->bytecode, i + 1)]; 25 | i += AS_FUNC(func)->upvalueCount * 2; 26 | } 27 | i += opcodeArgsNumber(instr) + 1; 28 | } 29 | return count; 30 | } 31 | 32 | static void disassembleCode(const Code* c, int indent) { 33 | for(size_t i = 0; i < c->size;) { 34 | disassembleInstr(c, indent, i); 35 | Opcode instr = c->bytecode[i]; 36 | if(instr == OP_CLOSURE) { 37 | Value func = c->consts.arr[readShortAt(c->bytecode, i + 1)]; 38 | i += AS_FUNC(func)->upvalueCount * 2; 39 | } 40 | i += opcodeArgsNumber(instr) + 1; 41 | } 42 | } 43 | 44 | static void disassemblePrototype(const Prototype* proto, int upvals) { 45 | printf("arguments %d, defaults %d, upvalues %d", (int)proto->argsCount, (int)proto->defCount, 46 | upvals); 47 | if(proto->vararg) printf(", vararg"); 48 | printf("\n"); 49 | } 50 | 51 | void disassembleFunction(const ObjFunction* fn) { 52 | ObjString* mod = fn->proto.module->name; 53 | ObjString* name = fn->proto.name; 54 | size_t instr = countInstructions(&fn->code); 55 | 56 | printf("function "); 57 | if(mod->length != 0) { 58 | printf("%s.%s", mod->data, name->data); 59 | } else { 60 | printf("%s", name->data); 61 | } 62 | printf(" (%zu instructions at %p)\n", instr, (void*)fn); 63 | 64 | disassemblePrototype(&fn->proto, fn->upvalueCount); 65 | disassembleCode(&fn->code, INDENT); 66 | 67 | for(int i = 0; i < fn->code.consts.size; i++) { 68 | Value c = fn->code.consts.arr[i]; 69 | if(IS_FUNC(c)) { 70 | printf("\n"); 71 | disassembleFunction(AS_FUNC(c)); 72 | } else if(IS_NATIVE(c)) { 73 | printf("\n"); 74 | disassembleNative(AS_NATIVE(c)); 75 | } 76 | } 77 | } 78 | 79 | void disassembleNative(const ObjNative* nat) { 80 | ObjString* mod = nat->proto.module->name; 81 | ObjString* name = nat->proto.name; 82 | printf("native "); 83 | if(mod->length != 0) { 84 | printf("%s.%s", mod->data, name->data); 85 | } else { 86 | printf("%s", name->data); 87 | } 88 | printf(" (%p)\n", (void*)nat); 89 | disassemblePrototype(&nat->proto, 0); 90 | } 91 | 92 | static void signedOffsetInstruction(const Code* c, size_t i) { 93 | int16_t off = (int16_t)readShortAt(c->bytecode, i + 1); 94 | printf("%d (to %zu)", off, (size_t)(i + off + 3)); 95 | } 96 | 97 | static void constInstruction(const Code* c, size_t i) { 98 | int arg = readShortAt(c->bytecode, i + 1); 99 | printf("%d (", arg); 100 | printValue(c->consts.arr[arg]); 101 | printf(")"); 102 | } 103 | 104 | static void symbolInstruction(const Code* c, size_t i) { 105 | int arg = readShortAt(c->bytecode, i + 1); 106 | printf("%d (", arg); 107 | printValue(c->consts.arr[c->symbols[arg].constant]); 108 | printf(")"); 109 | } 110 | 111 | static void const2Instruction(const Code* c, size_t i) { 112 | int arg1 = readShortAt(c->bytecode, i + 1); 113 | int arg2 = readShortAt(c->bytecode, i + 3); 114 | printf("%d %d (", arg1, arg2); 115 | printValue(c->consts.arr[arg1]); 116 | printf(", "); 117 | printValue(c->consts.arr[arg2]); 118 | printf(")"); 119 | } 120 | 121 | static void invokeInstruction(const Code* c, size_t i) { 122 | int argc = c->bytecode[i + 1]; 123 | int name = readShortAt(c->bytecode, i + 2); 124 | printf("%d %d (", argc, name); 125 | printValue(c->consts.arr[c->symbols[name].constant]); 126 | printf(")"); 127 | } 128 | 129 | static void unsignedByteInstruction(const Code* c, size_t i) { 130 | printf("%d", c->bytecode[i + 1]); 131 | } 132 | 133 | static void closureInstruction(const Code* c, int indent, size_t i) { 134 | int op = readShortAt(c->bytecode, i + 1); 135 | 136 | printf("%d (", op); 137 | printValue(c->consts.arr[op]); 138 | printf(")"); 139 | 140 | ObjFunction* fn = AS_FUNC(c->consts.arr[op]); 141 | 142 | size_t offset = i + 3; 143 | for(uint8_t j = 0; j < fn->upvalueCount; j++) { 144 | bool isLocal = c->bytecode[offset++]; 145 | int index = c->bytecode[offset++]; 146 | 147 | printf("\n"); 148 | for(int i = 0; i < indent; i++) { 149 | printf(" "); 150 | } 151 | printf("%04zu | %s %d", offset - 2, isLocal ? "local" : "upvalue", index); 152 | } 153 | } 154 | 155 | void disassembleInstr(const Code* c, int indent, size_t instr) { 156 | for(int i = 0; i < indent; i++) { 157 | printf(" "); 158 | } 159 | printf("%.4zu %s ", instr, OpcodeNames[c->bytecode[instr]]); 160 | 161 | switch((Opcode)c->bytecode[instr]) { 162 | case OP_IMPORT: 163 | case OP_IMPORT_FROM: 164 | case OP_NEW_CLASS: 165 | case OP_DEF_METHOD: 166 | case OP_GET_CONST: 167 | constInstruction(c, instr); 168 | break; 169 | case OP_GET_FIELD: 170 | case OP_SET_FIELD: 171 | case OP_INVOKE_0: 172 | case OP_INVOKE_1: 173 | case OP_INVOKE_2: 174 | case OP_INVOKE_3: 175 | case OP_INVOKE_4: 176 | case OP_INVOKE_5: 177 | case OP_INVOKE_6: 178 | case OP_INVOKE_7: 179 | case OP_INVOKE_8: 180 | case OP_INVOKE_9: 181 | case OP_INVOKE_10: 182 | case OP_INVOKE_UNPACK: 183 | case OP_SUPER_UNPACK: 184 | case OP_SUPER_0: 185 | case OP_SUPER_1: 186 | case OP_SUPER_2: 187 | case OP_SUPER_3: 188 | case OP_SUPER_4: 189 | case OP_SUPER_5: 190 | case OP_SUPER_6: 191 | case OP_SUPER_7: 192 | case OP_SUPER_8: 193 | case OP_SUPER_9: 194 | case OP_SUPER_10: 195 | case OP_SUPER_BIND: 196 | case OP_GET_GLOBAL: 197 | case OP_SET_GLOBAL: 198 | case OP_DEFINE_GLOBAL: 199 | symbolInstruction(c, instr); 200 | break; 201 | case OP_IMPORT_NAME: 202 | case OP_NATIVE: 203 | case OP_NATIVE_METHOD: 204 | const2Instruction(c, instr); 205 | break; 206 | case OP_JUMP: 207 | case OP_JUMPT: 208 | case OP_JUMPF: 209 | case OP_FOR_NEXT: 210 | case OP_SETUP_EXCEPT: 211 | case OP_SETUP_ENSURE: 212 | signedOffsetInstruction(c, instr); 213 | break; 214 | case OP_INVOKE: 215 | case OP_SUPER: 216 | invokeInstruction(c, instr); 217 | break; 218 | case OP_POPN: 219 | case OP_CALL: 220 | case OP_NEW_TUPLE: 221 | case OP_GET_LOCAL: 222 | case OP_SET_LOCAL: 223 | case OP_GET_UPVALUE: 224 | case OP_SET_UPVALUE: 225 | unsignedByteInstruction(c, instr); 226 | break; 227 | case OP_CLOSURE: 228 | closureInstruction(c, indent, instr); 229 | break; 230 | case OP_ADD: 231 | case OP_SUB: 232 | case OP_MUL: 233 | case OP_DIV: 234 | case OP_MOD: 235 | case OP_NEG: 236 | case OP_INVERT: 237 | case OP_BAND: 238 | case OP_BOR: 239 | case OP_XOR: 240 | case OP_LSHIFT: 241 | case OP_RSHIFT: 242 | case OP_EQ: 243 | case OP_NOT: 244 | case OP_GT: 245 | case OP_GE: 246 | case OP_LT: 247 | case OP_LE: 248 | case OP_IS: 249 | case OP_POW: 250 | case OP_SUBSCR_SET: 251 | case OP_SUBSCR_GET: 252 | case OP_CALL_0: 253 | case OP_CALL_1: 254 | case OP_CALL_2: 255 | case OP_CALL_3: 256 | case OP_CALL_4: 257 | case OP_CALL_5: 258 | case OP_CALL_6: 259 | case OP_CALL_7: 260 | case OP_CALL_8: 261 | case OP_CALL_9: 262 | case OP_CALL_10: 263 | case OP_CALL_UNPACK: 264 | case OP_FOR_PREP: 265 | case OP_FOR_ITER: 266 | case OP_NEW_LIST: 267 | case OP_APPEND_LIST: 268 | case OP_LIST_TO_TUPLE: 269 | case OP_NEW_TABLE: 270 | case OP_GENERATOR: 271 | case OP_GENERATOR_CLOSE: 272 | case OP_GET_OBJECT: 273 | case OP_SUBCLASS: 274 | case OP_RETURN: 275 | case OP_YIELD: 276 | case OP_NULL: 277 | case OP_END_HANDLER: 278 | case OP_POP_HANDLER: 279 | case OP_RAISE: 280 | case OP_POP: 281 | case OP_CLOSE_UPVALUE: 282 | case OP_DUP: 283 | case OP_UNPACK: 284 | case OP_END: 285 | // Nothing to do for no-arg instructions 286 | break; 287 | } 288 | 289 | printf("\n"); 290 | } 291 | -------------------------------------------------------------------------------- /src/disassemble.h: -------------------------------------------------------------------------------- 1 | #ifndef DISASSEMBLE_H 2 | #define DISASSEMBLE_H 3 | 4 | #include 5 | 6 | #include "code.h" 7 | #include "object_types.h" 8 | 9 | void disassembleFunction(const ObjFunction* fn); 10 | void disassembleNative(const ObjNative* nat); 11 | void disassembleInstr(const Code* c, int indent, size_t istr); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/endianness.h: -------------------------------------------------------------------------------- 1 | #ifndef ENDIANNES_H 2 | #define ENDIANNES_H 3 | 4 | #include "conf.h" 5 | 6 | // ----------------------------------------------------------------------------- 7 | // ENDIANNESS MACROS 8 | // ----------------------------------------------------------------------------- 9 | 10 | #if defined(JSTAR_LINUX) || defined(JSTAR_EMSCRIPTEN) 11 | #include // IWYU pragma: export 12 | #elif defined(JSTAR_MACOS) || defined(JSTAR_IOS) 13 | #include // IWYU pragma: export 14 | 15 | #define htobe16(x) OSSwapHostToBigInt16(x) 16 | #define be16toh(x) OSSwapBigToHostInt16(x) 17 | 18 | #define htobe64(x) OSSwapHostToBigInt64(x) 19 | #define be64toh(x) OSSwapBigToHostInt64(x) 20 | #elif defined(JSTAR_OPENBSD) 21 | #include // IWYU pragma: export 22 | #elif defined(JSTAR_FREEBSD) 23 | #include // IWYU pragma: export 24 | 25 | #define be16toh(x) betoh16(x) 26 | #define be64toh(x) betoh64(x) 27 | #elif defined(JSTAR_WINDOWS) 28 | #if BYTE_ORDER == LITTLE_ENDIAN 29 | #if defined(_MSC_VER) 30 | #include 31 | 32 | #define htobe16(x) _byteswap_ushort(x) 33 | #define be16toh(x) _byteswap_ushort(x) 34 | 35 | #define htobe64(x) _byteswap_uint64(x) 36 | #define be64toh(x) _byteswap_uint64(x) 37 | #elif defined(__GNUC__) 38 | #define htobe16(x) __builtin_bswap16(x) 39 | #define be16toh(x) __builtin_bswap16(x) 40 | 41 | #define htobe64(x) __builtin_bswap64(x) 42 | #define be64toh(x) __builtin_bswap64(x) 43 | #else 44 | #error Unsupported compiler: unknown endianness conversion functions 45 | #endif 46 | #elif BYTE_ORDER == BIG_ENDIAN 47 | #define htobe16(x) (x) 48 | #define be16toh(x) (x) 49 | 50 | #define htobe64(x) (x) 51 | #define be64toh(x) (x) 52 | #else 53 | #error Unsupported platform: unknown endiannes 54 | #endif 55 | #else 56 | #error Unsupported platform: unknown endiannes 57 | #endif 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/gc.c: -------------------------------------------------------------------------------- 1 | #include "gc.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "compiler.h" 8 | #include "int_hashtable.h" 9 | #include "object.h" 10 | #include "profiler.h" 11 | #include "util.h" 12 | #include "value.h" 13 | #include "value_hashtable.h" 14 | #include "vm.h" 15 | 16 | #define REACHED_DEFAULT_SZ 16 17 | #define REACHED_GROW_RATE 2 18 | 19 | void* gcAlloc(JStarVM* vm, void* ptr, size_t oldsize, size_t size) { 20 | vm->allocated += size - oldsize; 21 | if(size > oldsize) { 22 | #ifdef JSTAR_DBG_STRESS_GC 23 | garbageCollect(vm); 24 | #else 25 | if(vm->allocated > vm->nextGC) { 26 | garbageCollect(vm); 27 | } 28 | #endif 29 | } 30 | 31 | if(size == 0) { 32 | free(ptr); 33 | return NULL; 34 | } 35 | 36 | void* mem = realloc(ptr, size); 37 | if(!mem) { 38 | perror("Error"); 39 | abort(); 40 | } 41 | 42 | return mem; 43 | } 44 | 45 | void sweepObjects(JStarVM* vm) { 46 | PROFILE_FUNC() 47 | 48 | Obj** head = &vm->objects; 49 | while(*head != NULL) { 50 | if(!(*head)->reached) { 51 | Obj* u = *head; 52 | *head = u->next; 53 | 54 | #ifdef JSTAR_DBG_PRINT_GC 55 | printf("GC_FREE: unreached object %p type: %s\n", (void*)u, ObjTypeNames[u->type]); 56 | #endif 57 | freeObject(vm, u); 58 | } else { 59 | (*head)->reached = false; 60 | head = &(*head)->next; 61 | } 62 | } 63 | } 64 | 65 | void reachObject(JStarVM* vm, Obj* o) { 66 | if(o == NULL || o->reached) return; 67 | 68 | #ifdef JSTAR_DBG_PRINT_GC 69 | printf("REACHED: Object %p type: %s repr: ", (void*)o, ObjTypeNames[o->type]); 70 | printObj(o); 71 | printf("\n"); 72 | #endif 73 | 74 | o->reached = true; 75 | ARRAY_APPEND(vm, reachedCount, reachedCapacity, reachedStack, o); 76 | } 77 | 78 | void reachValue(JStarVM* vm, Value v) { 79 | if(IS_OBJ(v)) reachObject(vm, AS_OBJ(v)); 80 | } 81 | 82 | static void reachValueArray(JStarVM* vm, ValueArray* a) { 83 | for(int i = 0; i < a->size; i++) { 84 | reachValue(vm, a->arr[i]); 85 | } 86 | } 87 | 88 | static void recursevelyReach(JStarVM* vm, Obj* o) { 89 | #ifdef JSTAR_DBG_PRINT_GC 90 | printf("Recursevely exploring object %p...\n", (void*)o); 91 | #endif 92 | 93 | reachObject(vm, (Obj*)o->cls); 94 | 95 | switch(o->type) { 96 | case OBJ_NATIVE: { 97 | ObjNative* n = (ObjNative*)o; 98 | reachObject(vm, (Obj*)n->proto.name); 99 | reachObject(vm, (Obj*)n->proto.module); 100 | for(uint8_t i = 0; i < n->proto.defCount; i++) { 101 | reachValue(vm, n->proto.defaults[i]); 102 | } 103 | break; 104 | } 105 | case OBJ_FUNCTION: { 106 | ObjFunction* func = (ObjFunction*)o; 107 | reachObject(vm, (Obj*)func->proto.name); 108 | reachObject(vm, (Obj*)func->proto.module); 109 | reachValueArray(vm, &func->code.consts); 110 | for(size_t i = 0; i < func->code.symbolCount; i++) { 111 | reachObject(vm, (Obj*)func->code.symbols[i].cache.key); 112 | } 113 | for(uint8_t i = 0; i < func->proto.defCount; i++) { 114 | reachValue(vm, func->proto.defaults[i]); 115 | } 116 | break; 117 | } 118 | case OBJ_CLASS: { 119 | ObjClass* cls = (ObjClass*)o; 120 | reachObject(vm, (Obj*)cls->name); 121 | reachObject(vm, (Obj*)cls->superCls); 122 | reachValueHashTable(vm, &cls->methods); 123 | reachIntHashTable(vm, &cls->fields); 124 | break; 125 | } 126 | case OBJ_INST: { 127 | ObjInstance* i = (ObjInstance*)o; 128 | for(Value* v = i->fields; v < i->fields + i->capacity; v++) { 129 | reachValue(vm, *v); 130 | } 131 | break; 132 | } 133 | case OBJ_MODULE: { 134 | ObjModule* m = (ObjModule*)o; 135 | reachObject(vm, (Obj*)m->name); 136 | reachObject(vm, (Obj*)m->path); 137 | reachIntHashTable(vm, &m->globalNames); 138 | for(int i = 0; i < m->globalsCapacity; i++) { 139 | reachValue(vm, m->globals[i]); 140 | } 141 | break; 142 | } 143 | case OBJ_LIST: { 144 | ObjList* l = (ObjList*)o; 145 | for(size_t i = 0; i < l->size; i++) { 146 | reachValue(vm, l->arr[i]); 147 | } 148 | break; 149 | } 150 | case OBJ_TUPLE: { 151 | ObjTuple* t = (ObjTuple*)o; 152 | for(size_t i = 0; i < t->size; i++) { 153 | reachValue(vm, t->arr[i]); 154 | } 155 | break; 156 | } 157 | case OBJ_TABLE: { 158 | ObjTable* t = (ObjTable*)o; 159 | if(t->entries != NULL) { 160 | for(size_t i = 0; i < t->capacityMask + 1; i++) { 161 | reachValue(vm, t->entries[i].key); 162 | reachValue(vm, t->entries[i].val); 163 | } 164 | } 165 | break; 166 | } 167 | case OBJ_BOUND_METHOD: { 168 | ObjBoundMethod* b = (ObjBoundMethod*)o; 169 | reachValue(vm, b->receiver); 170 | reachObject(vm, (Obj*)b->method); 171 | break; 172 | } 173 | case OBJ_CLOSURE: { 174 | ObjClosure* closure = (ObjClosure*)o; 175 | reachObject(vm, (Obj*)closure->fn); 176 | for(uint8_t i = 0; i < closure->upvalueCount; i++) { 177 | reachObject(vm, (Obj*)closure->upvalues[i]); 178 | } 179 | break; 180 | } 181 | case OBJ_GENERATOR: { 182 | ObjGenerator* gen = (ObjGenerator*)o; 183 | reachObject(vm, (Obj*)gen->closure); 184 | for(size_t i = 0; i < gen->frame.stackTop; i++) { 185 | reachValue(vm, gen->savedStack[i]); 186 | } 187 | break; 188 | } 189 | case OBJ_UPVALUE: { 190 | ObjUpvalue* upvalue = (ObjUpvalue*)o; 191 | reachValue(vm, *upvalue->addr); 192 | break; 193 | } 194 | case OBJ_STACK_TRACE: { 195 | ObjStackTrace* stackTrace = (ObjStackTrace*)o; 196 | for(int i = 0; i < stackTrace->recordSize; i++) { 197 | reachObject(vm, (Obj*)stackTrace->records[i].funcName); 198 | reachObject(vm, (Obj*)stackTrace->records[i].moduleName); 199 | } 200 | break; 201 | } 202 | case OBJ_USERDATA: 203 | case OBJ_STRING: 204 | break; 205 | } 206 | } 207 | 208 | void garbageCollect(JStarVM* vm) { 209 | PROFILE_FUNC() 210 | 211 | #ifdef JSTAR_DBG_PRINT_GC 212 | size_t prevAlloc = vm->allocated; 213 | puts("*--- Starting GC ---*"); 214 | #endif 215 | 216 | vm->reachedStack = malloc(sizeof(Obj*) * REACHED_DEFAULT_SZ); 217 | vm->reachedCapacity = REACHED_DEFAULT_SZ; 218 | 219 | { 220 | PROFILE("{reach-objects}::garbageCollect") 221 | 222 | reachObject(vm, (Obj*)vm->clsClass); 223 | reachObject(vm, (Obj*)vm->objClass); 224 | reachObject(vm, (Obj*)vm->strClass); 225 | reachObject(vm, (Obj*)vm->boolClass); 226 | reachObject(vm, (Obj*)vm->lstClass); 227 | reachObject(vm, (Obj*)vm->numClass); 228 | reachObject(vm, (Obj*)vm->funClass); 229 | reachObject(vm, (Obj*)vm->modClass); 230 | reachObject(vm, (Obj*)vm->nullClass); 231 | reachObject(vm, (Obj*)vm->stClass); 232 | reachObject(vm, (Obj*)vm->tupClass); 233 | reachObject(vm, (Obj*)vm->excClass); 234 | reachObject(vm, (Obj*)vm->tableClass); 235 | reachObject(vm, (Obj*)vm->udataClass); 236 | 237 | reachObject(vm, (Obj*)vm->argv); 238 | 239 | for(int i = 0; i < METH_SIZE; i++) { 240 | reachObject(vm, (Obj*)vm->specialMethods[i]); 241 | } 242 | 243 | reachObject(vm, (Obj*)vm->excErr); 244 | reachObject(vm, (Obj*)vm->excTrace); 245 | reachObject(vm, (Obj*)vm->excCause); 246 | 247 | reachObject(vm, (Obj*)vm->emptyTup); 248 | reachValueHashTable(vm, &vm->modules); 249 | 250 | for(Value* v = vm->stack; v < vm->sp; v++) { 251 | reachValue(vm, *v); 252 | } 253 | 254 | for(int i = 0; i < vm->frameCount; i++) { 255 | reachObject(vm, vm->frames[i].fn); 256 | } 257 | 258 | for(ObjUpvalue* upvalue = vm->upvalues; upvalue != NULL; upvalue = upvalue->next) { 259 | reachObject(vm, (Obj*)upvalue); 260 | } 261 | 262 | for(JStarSymbol* s = vm->symbols; s != NULL; s = s->next) { 263 | reachObject(vm, s->sym.key); 264 | } 265 | 266 | reachCompilerRoots(vm, vm->currCompiler); 267 | } 268 | 269 | { 270 | PROFILE("{recursively-reach}::garbageCollect") 271 | 272 | while(vm->reachedCount != 0) { 273 | recursevelyReach(vm, vm->reachedStack[--vm->reachedCount]); 274 | } 275 | } 276 | 277 | sweepStrings(&vm->stringPool); 278 | sweepObjects(vm); 279 | 280 | free(vm->reachedStack); 281 | vm->reachedStack = NULL; 282 | vm->reachedCapacity = 0; 283 | vm->reachedCount = 0; 284 | 285 | vm->nextGC = vm->allocated * vm->heapGrowRate; 286 | 287 | #ifdef JSTAR_DBG_PRINT_GC 288 | size_t curr = prevAlloc - vm->allocated; 289 | printf( 290 | "Completed GC, prev allocated: %lu, curr allocated " 291 | "%lu, freed: %lu bytes of memory, next GC: %lu.\n", 292 | prevAlloc, vm->allocated, curr, vm->nextGC); 293 | printf("*--- End of GC ---*\n"); 294 | #endif 295 | } 296 | -------------------------------------------------------------------------------- /src/gc.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_H 2 | #define GC_H 3 | 4 | #include 5 | 6 | #include "jstar.h" 7 | #include "object_types.h" 8 | #include "value.h" 9 | 10 | // Macros to simplify memory allocation 11 | #define GC_ALLOC(vm, size) gcAlloc(vm, NULL, 0, size) 12 | #define GC_FREE(vm, t, obj) gcAlloc(vm, obj, sizeof(t), 0) 13 | #define GC_FREE_ARRAY(vm, t, obj, count) gcAlloc(vm, obj, sizeof(t) * (count), 0) 14 | #define GC_FREE_VAR(vm, t, var, count, obj) gcAlloc(vm, obj, sizeof(t) + sizeof(var) * (count), 0) 15 | 16 | // Allocate (or reallocate) some memory using the J* garbage collector. 17 | // This memory is owned by the GC, but can't be collected until is is exposed as a Value in the 18 | // runtime (for example as an Obj*, or as a part of one). 19 | void* gcAlloc(JStarVM* vm, void* ptr, size_t oldsize, size_t size); 20 | 21 | // Launch a garbage collection. It scans all roots (VM stack, global Strings, etc...) 22 | // marking all the reachable objects (recursively, if needed) and then calls sweepObjects 23 | // to free all unreached ones. 24 | void garbageCollect(JStarVM* vm); 25 | 26 | // Mark an Object/Value as reached 27 | void reachObject(JStarVM* vm, Obj* o); 28 | void reachValue(JStarVM* vm, Value v); 29 | 30 | // Free all unmarked objects 31 | void sweepObjects(JStarVM* vm); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/import.c: -------------------------------------------------------------------------------- 1 | #include "import.h" 2 | 3 | #include 4 | 5 | #include "compiler.h" 6 | #include "jstar.h" 7 | #include "lib/builtins.h" 8 | #include "object.h" 9 | #include "parse/parser.h" 10 | #include "profiler.h" 11 | #include "serialize.h" 12 | #include "value.h" 13 | #include "value_hashtable.h" 14 | #include "vm.h" 15 | 16 | static ObjModule* getOrCreateModule(JStarVM* vm, const char* path, ObjString* name) { 17 | ObjModule* module = getModule(vm, name); 18 | if(module == NULL) { 19 | push(vm, OBJ_VAL(name)); 20 | module = newModule(vm, path, name); 21 | setModule(vm, name, module); 22 | pop(vm); 23 | } 24 | return module; 25 | } 26 | 27 | ObjFunction* compileModule(JStarVM* vm, const char* path, ObjString* name, JStarStmt* program) { 28 | PROFILE_FUNC() 29 | ObjModule* module = getOrCreateModule(vm, path, name); 30 | ObjFunction* fn = compile(vm, path, module, program); 31 | return fn; 32 | } 33 | 34 | JStarResult deserializeModule(JStarVM* vm, const char* path, ObjString* name, const void* code, 35 | size_t len, ObjFunction** out) { 36 | PROFILE_FUNC() 37 | JStarResult res = deserialize(vm, getOrCreateModule(vm, path, name), code, len, out); 38 | if(res == JSR_VERSION_ERR) { 39 | vm->errorCallback(vm, res, path, -1, "Incompatible binary file version"); 40 | } 41 | if(res == JSR_DESERIALIZE_ERR) { 42 | vm->errorCallback(vm, res, path, -1, "Malformed binary file"); 43 | } 44 | return res; 45 | } 46 | 47 | static void registerInParent(JStarVM* vm, ObjModule* module) { 48 | ObjString* name = module->name; 49 | const char* lastDot = strrchr(name->data, '.'); 50 | 51 | // Not a submodule, nothing to do 52 | if(lastDot == NULL) { 53 | return; 54 | } 55 | 56 | const char* simpleName = lastDot + 1; 57 | ObjModule* parent = getModule(vm, copyString(vm, name->data, simpleName - name->data - 1)); 58 | JSR_ASSERT(parent, "Submodule parent could not be found."); 59 | 60 | if(!module->registry) { 61 | module->registry = parent->registry; 62 | } 63 | 64 | moduleSetGlobal(vm, parent, copyString(vm, simpleName, strlen(simpleName)), OBJ_VAL(module)); 65 | } 66 | 67 | void setModule(JStarVM* vm, ObjString* name, ObjModule* module) { 68 | hashTableValuePut(&vm->modules, name, OBJ_VAL(module)); 69 | registerInParent(vm, module); 70 | } 71 | 72 | ObjModule* getModule(JStarVM* vm, ObjString* name) { 73 | Value module; 74 | if(!hashTableValueGet(&vm->modules, name, &module)) { 75 | return NULL; 76 | } 77 | return AS_MODULE(module); 78 | } 79 | 80 | static void parseError(const char* file, int line, const char* error, void* udata) { 81 | JStarVM* vm = udata; 82 | vm->errorCallback(vm, JSR_SYNTAX_ERR, file, line, error); 83 | } 84 | 85 | static ObjModule* importSource(JStarVM* vm, const char* path, ObjString* name, const char* src, 86 | size_t len) { 87 | PROFILE_FUNC() 88 | 89 | JStarStmt* program = jsrParse(path, src, len, parseError, vm); 90 | if(program == NULL) { 91 | return NULL; 92 | } 93 | 94 | ObjFunction* fn = compileModule(vm, path, name, program); 95 | jsrStmtFree(program); 96 | 97 | if(fn == NULL) { 98 | return NULL; 99 | } 100 | 101 | push(vm, OBJ_VAL(fn)); 102 | vm->sp[-1] = OBJ_VAL(newClosure(vm, fn)); 103 | return fn->proto.module; 104 | } 105 | 106 | static ObjModule* importBinary(JStarVM* vm, const char* path, ObjString* name, const void* code, 107 | size_t len) { 108 | PROFILE_FUNC() 109 | JSR_ASSERT(isCompiledCode(code, len), "`code` must be a valid compiled chunk"); 110 | 111 | ObjFunction* fn; 112 | JStarResult res = deserializeModule(vm, path, name, code, len, &fn); 113 | if(res != JSR_SUCCESS) { 114 | return NULL; 115 | } 116 | 117 | push(vm, OBJ_VAL(fn)); 118 | vm->sp[-1] = OBJ_VAL(newClosure(vm, fn)); 119 | return fn->proto.module; 120 | } 121 | 122 | ObjModule* importModule(JStarVM* vm, ObjString* name) { 123 | PROFILE_FUNC() 124 | 125 | if(hashTableValueContainsKey(&vm->modules, name)) { 126 | push(vm, NULL_VAL); 127 | return getModule(vm, name); 128 | } 129 | 130 | size_t len; 131 | const void* bltinCode = readBuiltInModule(name->data, &len); 132 | if(bltinCode != NULL) { 133 | return importBinary(vm, "builtin", name, bltinCode, len); 134 | } 135 | 136 | if(!vm->importCallback) { 137 | return NULL; 138 | } 139 | 140 | // An import callback is similar to a native function call (can use the J* API and can be 141 | // re-entrant), so setup the apiStack to the current stack pointer, so that push/pop operations 142 | // are relative to the current position 143 | size_t apiStackOffset = vm->apiStack - vm->stack; 144 | vm->apiStack = vm->sp; 145 | 146 | JStarImportResult res = vm->importCallback(vm, name->data); 147 | vm->apiStack = vm->stack + apiStackOffset; 148 | 149 | if(!res.code) { 150 | return NULL; 151 | } 152 | 153 | ObjModule* module; 154 | if(isCompiledCode(res.code, res.codeLength)) { 155 | module = importBinary(vm, res.path, name, res.code, res.codeLength); 156 | } else { 157 | module = importSource(vm, res.path, name, res.code, res.codeLength); 158 | } 159 | 160 | if(res.finalize) { 161 | res.finalize(res.userData); 162 | } 163 | 164 | if(module == NULL) { 165 | return NULL; 166 | } 167 | 168 | if(res.reg) { 169 | module->registry = res.reg; 170 | } 171 | 172 | return module; 173 | } 174 | -------------------------------------------------------------------------------- /src/import.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORT_H 2 | #define IMPORT_H 3 | 4 | #include 5 | 6 | #include "jstar.h" 7 | #include "object_types.h" 8 | #include "parse/ast.h" 9 | 10 | /** 11 | * The import system is responsible for loading and compiling J* modules. 12 | * This files defines three sets of functions: 13 | * - Functions for compiling/deserializing a module from source code or bytecode 14 | * - Functions for managing the module cache 15 | * - `importModule` which kickstarts the import process of the VM 16 | */ 17 | 18 | // Compile a module from source code. 19 | // A module with name 'name' is created in the cache if it doesn't already exist. 20 | // Returns a function representing the module's 'main' (top-level scope). Call this function to 21 | // initialize the module. 22 | // On error, returns NULL. 23 | ObjFunction* compileModule(JStarVM* vm, const char* path, ObjString* name, JStarStmt* program); 24 | 25 | // Similar to the above, but deserialize a module from bytecode. 26 | // On success, returns JSR_SUCCESS and sets 'out' to the deserialized function. 27 | // On error, returns an error code and leaves out unchanged. 28 | JStarResult deserializeModule(JStarVM* vm, const char* path, ObjString* name, const void* code, 29 | size_t len, ObjFunction** out); 30 | 31 | // Sets a module in the cache. 32 | void setModule(JStarVM* vm, ObjString* name, ObjModule* module); 33 | 34 | // Retrieves a module from the cache. 35 | ObjModule* getModule(JStarVM* vm, ObjString* name); 36 | 37 | // Import a module by name. 38 | // 39 | // Calls the user provided import callback to resolve the module. 40 | // If the module ins't present in the cache, the module's main function is left on top of the stack. 41 | // Otherwise, `NULL_VAL` is left on top of the stack, signaling that the module has already been 42 | // imported. 43 | // 44 | // Returns the module object on success, NULL on error. 45 | ObjModule* importModule(JStarVM* vm, ObjString* name); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/int_hashtable.c: -------------------------------------------------------------------------------- 1 | #include "int_hashtable.h" 2 | 3 | #include 4 | 5 | #include "gc.h" 6 | #include "hashtable.h" 7 | #include "object.h" // IWYU pragma: keep 8 | 9 | #define TOMB_MARKER -1 10 | #define INVALID_VAL -2 11 | #define IS_INVALID_VAL(v) ((v) == INVALID_VAL) 12 | 13 | DEFINE_HASH_TABLE(Int, int, TOMB_MARKER, INVALID_VAL, IS_INVALID_VAL, 2, 8) 14 | 15 | void reachIntHashTable(JStarVM* vm, const IntHashTable* t) { 16 | if(t->entries == NULL) return; 17 | for(size_t i = 0; i <= t->sizeMask; i++) { 18 | IntEntry* e = &t->entries[i]; 19 | reachObject(vm, (Obj*)e->key); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/int_hashtable.h: -------------------------------------------------------------------------------- 1 | #ifndef INT_HASH_TABLE_H 2 | #define INT_HASH_TABLE_H 3 | 4 | #include "hashtable.h" 5 | #include "jstar.h" 6 | #include "object_types.h" // IWYU pragma: keep 7 | 8 | DECLARE_HASH_TABLE(Int, int) 9 | 10 | void reachIntHashTable(JStarVM* vm, const IntHashTable* t); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/jstar_limits.h: -------------------------------------------------------------------------------- 1 | #ifndef JSTAR_LIMITS_H 2 | #define JSTAR_LIMITS_H 3 | 4 | // Max number of frames, i.e. max J* call recursion depth. 5 | // This limit is arbitrary, and it's only used for preventing infinite recursion. 6 | // The only thing really limiting J* recursion depth is heap size, so you can increase this value 7 | // as you like as long as you have the memory for it. 8 | #define MAX_FRAMES 100000 9 | 10 | // Maximum permitted number of reentrant calls. 11 | // This limits only applies when doing reentrant calls inside the VM (for example: a native function 12 | // calling a J* one, calling a native calling a J* one, and so on...) and it's enforced in order 13 | // to prevent possible c stack overflows, as reentrant calls are implemented as c recurive calls. 14 | // Decrease this value in case you get core dumps when dealing with deep reentrant call hierarchies. 15 | // Conversely, you can increase this value if you need extra reentrant call depth and your platform 16 | // has enough stack space for it. 17 | #define MAX_REENTRANT 1000 18 | 19 | // Maximum number of nested try-excepts allowed. 20 | // Increasing this value will enable nesting more try-excepts, but the memory consuption will be 21 | // increased for each stack frame, leading to an overall increase in memory usage by the VM. 22 | #define MAX_HANDLERS 6 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/lib/builtins.h: -------------------------------------------------------------------------------- 1 | #ifndef BUILTINS_H 2 | #define BUILTINS_H 3 | 4 | #include 5 | 6 | #include "jstar.h" 7 | 8 | JStarNative resolveBuiltIn(const char* module, const char* cls, const char* name); 9 | const void* readBuiltInModule(const char* name, size_t* len); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/lib/core/core.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_H 2 | #define CORE_H 3 | 4 | #include 5 | 6 | #include "jstar.h" 7 | #include "parse/ast.h" 8 | 9 | // J* core module bootstrap 10 | void initCoreModule(JStarVM* vm); 11 | 12 | // Resolve a core module name 13 | bool resolveCoreSymbol(const JStarIdentifier* id); 14 | 15 | // J* core module native functions and methods 16 | 17 | // class Number 18 | JSR_NATIVE(jsr_Number_construct); 19 | JSR_NATIVE(jsr_Number_isInt); 20 | JSR_NATIVE(jsr_Number_string); 21 | JSR_NATIVE(jsr_Number_hash); 22 | // end 23 | 24 | // class Boolean 25 | JSR_NATIVE(jsr_Boolean_construct); 26 | JSR_NATIVE(jsr_Boolean_string); 27 | JSR_NATIVE(jsr_Boolean_hash); 28 | // end 29 | 30 | // class Null 31 | JSR_NATIVE(jsr_Null_string); 32 | // end 33 | 34 | // class Function 35 | JSR_NATIVE(jsr_Function_string); 36 | JSR_NATIVE(jsr_Function_bind); 37 | JSR_NATIVE(jsr_Function_arity); 38 | JSR_NATIVE(jsr_Function_vararg); 39 | JSR_NATIVE(jsr_Function_defaults); 40 | JSR_NATIVE(jsr_Function_getName); 41 | JSR_NATIVE(jsr_Function_getSimpleName); 42 | // end 43 | 44 | // class Generator 45 | JSR_NATIVE(jsr_Generator_isDone); 46 | JSR_NATIVE(jsr_Generator_string); 47 | JSR_NATIVE(jsr_Generator_next); 48 | // end 49 | 50 | // class Module 51 | JSR_NATIVE(jsr_Module_string); 52 | JSR_NATIVE(jsr_Module_globals); 53 | // end 54 | 55 | // class List 56 | JSR_NATIVE(jsr_List_construct); 57 | JSR_NATIVE(jsr_List_add); 58 | JSR_NATIVE(jsr_List_insert); 59 | JSR_NATIVE(jsr_List_removeAt); 60 | JSR_NATIVE(jsr_List_clear); 61 | JSR_NATIVE(jsr_List_sort); 62 | JSR_NATIVE(jsr_List_len); 63 | JSR_NATIVE(jsr_List_plus); 64 | JSR_NATIVE(jsr_List_eq); 65 | JSR_NATIVE(jsr_List_iter); 66 | JSR_NATIVE(jsr_List_next); 67 | // end 68 | 69 | // class Tuple 70 | JSR_NATIVE(jsr_Tuple_construct); 71 | JSR_NATIVE(jsr_Tuple_len); 72 | JSR_NATIVE(jsr_Tuple_add); 73 | JSR_NATIVE(jsr_Tuple_eq); 74 | JSR_NATIVE(jsr_Tuple_iter); 75 | JSR_NATIVE(jsr_Tuple_next); 76 | JSR_NATIVE(jsr_Tuple_hash); 77 | // end 78 | 79 | // class String 80 | JSR_NATIVE(jsr_String_construct); 81 | JSR_NATIVE(jsr_String_findSubstr); 82 | JSR_NATIVE(jsr_String_rfindSubstr); 83 | JSR_NATIVE(jsr_String_charAt); 84 | JSR_NATIVE(jsr_String_startsWith); 85 | JSR_NATIVE(jsr_String_endsWith); 86 | JSR_NATIVE(jsr_String_split); 87 | JSR_NATIVE(jsr_String_strip); 88 | JSR_NATIVE(jsr_String_chomp); 89 | JSR_NATIVE(jsr_String_escaped); 90 | JSR_NATIVE(jsr_String_mul); 91 | JSR_NATIVE(jsr_String_mod); 92 | JSR_NATIVE(jsr_String_len); 93 | JSR_NATIVE(jsr_String_string); 94 | JSR_NATIVE(jsr_String_hash); 95 | JSR_NATIVE(jsr_String_eq); 96 | JSR_NATIVE(jsr_String_iter); 97 | JSR_NATIVE(jsr_String_next); 98 | // end 99 | 100 | // class Table 101 | JSR_NATIVE(jsr_Table_construct); 102 | JSR_NATIVE(jsr_Table_get); 103 | JSR_NATIVE(jsr_Table_set); 104 | JSR_NATIVE(jsr_Table_len); 105 | JSR_NATIVE(jsr_Table_delete); 106 | JSR_NATIVE(jsr_Table_clear); 107 | JSR_NATIVE(jsr_Table_contains); 108 | JSR_NATIVE(jsr_Table_keys); 109 | JSR_NATIVE(jsr_Table_values); 110 | JSR_NATIVE(jsr_Table_iter); 111 | JSR_NATIVE(jsr_Table_next); 112 | JSR_NATIVE(jsr_Table_string); 113 | // end 114 | 115 | // class Enum 116 | JSR_NATIVE(jsr_Enum_construct); 117 | JSR_NATIVE(jsr_Enum_value); 118 | JSR_NATIVE(jsr_Enum_name); 119 | // end 120 | 121 | #endif // CORE_H 122 | -------------------------------------------------------------------------------- /src/lib/core/core.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/core/core.jsc -------------------------------------------------------------------------------- /src/lib/core/excs.c: -------------------------------------------------------------------------------- 1 | #include "excs.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "object.h" 8 | #include "object_types.h" 9 | #include "value.h" 10 | #include "vm.h" 11 | 12 | // class Exception 13 | #define INDENT " " 14 | 15 | static bool recordEquals(FrameRecord* f1, FrameRecord* f2) { 16 | return f1 && f2 && (strcmp(f1->moduleName->data, f2->moduleName->data) == 0) && 17 | (strcmp(f1->funcName->data, f2->funcName->data) == 0) && (f1->line == f2->line); 18 | } 19 | 20 | JSR_NATIVE(jsr_Exception_printStacktrace) { 21 | ObjInstance* exc = AS_INSTANCE(vm->apiStack[0]); 22 | ObjClass* cls = exc->base.cls; 23 | 24 | Value stacktraceVal = NULL_VAL; 25 | instanceGetField(vm, cls, exc, vm->excTrace, &stacktraceVal); 26 | 27 | if(IS_STACK_TRACE(stacktraceVal)) { 28 | Value cause = NULL_VAL; 29 | instanceGetField(vm, cls, exc, vm->excCause, &cause); 30 | 31 | if(isInstance(vm, cause, vm->excClass)) { 32 | push(vm, cause); 33 | if(jsrCallMethod(vm, "printStacktrace", 0) != JSR_SUCCESS) return false; 34 | pop(vm); 35 | fprintf(stderr, "\nAbove Excetption caused:\n"); 36 | } 37 | 38 | ObjStackTrace* stacktrace = AS_STACK_TRACE(stacktraceVal); 39 | 40 | if(stacktrace->recordSize > 0) { 41 | FrameRecord* lastRecord = NULL; 42 | 43 | fprintf(stderr, "Traceback (most recent call last):\n"); 44 | for(int i = stacktrace->recordSize - 1; i >= 0; i--) { 45 | FrameRecord* record = &stacktrace->records[i]; 46 | 47 | if(recordEquals(lastRecord, record)) { 48 | int repetitions = 1; 49 | while(i > 0) { 50 | record = &stacktrace->records[i - 1]; 51 | if(!recordEquals(lastRecord, record)) break; 52 | repetitions++, i--; 53 | } 54 | fprintf(stderr, INDENT "...\n"); 55 | fprintf(stderr, INDENT "[Previous line repeated %d times]\n", repetitions); 56 | continue; 57 | } 58 | 59 | fprintf(stderr, INDENT); 60 | 61 | if(record->line >= 0) { 62 | fprintf(stderr, "[line %d]", record->line); 63 | } else { 64 | fprintf(stderr, "[line ?]"); 65 | } 66 | fprintf(stderr, " module %s in %s\n", record->moduleName->data, 67 | record->funcName->data); 68 | 69 | lastRecord = record; 70 | } 71 | } 72 | } 73 | 74 | Value err = NULL_VAL; 75 | instanceGetField(vm, cls, exc, vm->excErr, &err); 76 | 77 | if(IS_STRING(err) && AS_STRING(err)->length > 0) { 78 | fprintf(stderr, "%s: %s\n", exc->base.cls->name->data, AS_STRING(err)->data); 79 | } else { 80 | fprintf(stderr, "%s\n", exc->base.cls->name->data); 81 | } 82 | 83 | jsrPushNull(vm); 84 | return true; 85 | } 86 | 87 | JSR_NATIVE(jsr_Exception_getStacktrace) { 88 | ObjInstance* exc = AS_INSTANCE(vm->apiStack[0]); 89 | 90 | JStarBuffer buf; 91 | jsrBufferInitCapacity(vm, &buf, 64); 92 | 93 | Value stval = NULL_VAL; 94 | instanceGetField(vm, exc->base.cls, exc, vm->excTrace, &stval); 95 | 96 | if(IS_STACK_TRACE(stval)) { 97 | Value cause = NULL_VAL; 98 | instanceGetField(vm, exc->base.cls, exc, vm->excCause, &cause); 99 | 100 | if(isInstance(vm, cause, vm->excClass)) { 101 | push(vm, cause); 102 | if(jsrCallMethod(vm, "getStacktrace", 0) != JSR_SUCCESS) return false; 103 | Value stackTrace = peek(vm); 104 | if(IS_STRING(stackTrace)) { 105 | jsrBufferAppend(&buf, AS_STRING(stackTrace)->data, AS_STRING(stackTrace)->length); 106 | jsrBufferAppendStr(&buf, "\n\nAbove Exception caused:\n"); 107 | } 108 | pop(vm); 109 | } 110 | 111 | ObjStackTrace* stacktrace = AS_STACK_TRACE(stval); 112 | 113 | if(stacktrace->recordSize > 0) { 114 | FrameRecord* lastRecord = NULL; 115 | 116 | jsrBufferAppendf(&buf, "Traceback (most recent call last):\n"); 117 | for(int i = stacktrace->recordSize - 1; i >= 0; i--) { 118 | FrameRecord* record = &stacktrace->records[i]; 119 | 120 | if(recordEquals(lastRecord, record)) { 121 | int repetitions = 1; 122 | while(i > 0) { 123 | record = &stacktrace->records[i - 1]; 124 | if(!recordEquals(lastRecord, record)) break; 125 | repetitions++, i--; 126 | } 127 | jsrBufferAppendStr(&buf, INDENT "...\n"); 128 | jsrBufferAppendf(&buf, INDENT "[Previous line repeated %d times]\n", 129 | repetitions); 130 | continue; 131 | } 132 | 133 | jsrBufferAppendStr(&buf, " "); 134 | 135 | if(record->line >= 0) { 136 | jsrBufferAppendf(&buf, "[line %d]", record->line); 137 | } else { 138 | jsrBufferAppendStr(&buf, "[line ?]"); 139 | } 140 | 141 | jsrBufferAppendf(&buf, " module %s in %s\n", record->moduleName->data, 142 | record->funcName->data); 143 | 144 | lastRecord = record; 145 | } 146 | } 147 | } 148 | 149 | Value err = NULL_VAL; 150 | instanceGetField(vm, exc->base.cls, exc, vm->excErr, &err); 151 | 152 | if(IS_STRING(err) && AS_STRING(err)->length > 0) { 153 | jsrBufferAppendf(&buf, "%s: %s", exc->base.cls->name->data, AS_STRING(err)->data); 154 | } else { 155 | jsrBufferAppendf(&buf, "%s", exc->base.cls->name->data); 156 | } 157 | 158 | jsrBufferPush(&buf); 159 | return true; 160 | } 161 | // end 162 | -------------------------------------------------------------------------------- /src/lib/core/excs.h: -------------------------------------------------------------------------------- 1 | #ifndef EXCS_H 2 | #define EXCS_H 3 | 4 | #include "jstar.h" 5 | 6 | // Excepttion class fields 7 | #define EXC_ERR "_err" 8 | #define EXC_CAUSE "_cause" 9 | #define EXC_TRACE "_stacktrace" 10 | 11 | // class Exception 12 | JSR_NATIVE(jsr_Exception_printStacktrace); 13 | JSR_NATIVE(jsr_Exception_getStacktrace); 14 | // end 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/lib/core/excs.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/core/excs.jsc -------------------------------------------------------------------------------- /src/lib/core/excs.jsr: -------------------------------------------------------------------------------- 1 | class Exception 2 | construct(err="", cause=null) 3 | this._err = err 4 | this._cause = cause 5 | this._stacktrace = null 6 | end 7 | 8 | fun err() 9 | return this._err 10 | end 11 | 12 | fun cause() 13 | return this._cause 14 | end 15 | 16 | native printStacktrace() 17 | native getStacktrace() 18 | end 19 | 20 | class TypeException is Exception end 21 | class NameException is Exception end 22 | class FieldException is Exception end 23 | class MethodException is Exception end 24 | class ImportException is Exception end 25 | class StackOverflowException is Exception end 26 | class SyntaxException is Exception end 27 | class InvalidArgException is Exception end 28 | class GeneratorException is Exception end 29 | class IndexOutOfBoundException is Exception end 30 | class AssertException is Exception end 31 | class NotImplementedException is Exception end 32 | class ProgramInterrupt is Exception end 33 | -------------------------------------------------------------------------------- /src/lib/core/iter.c: -------------------------------------------------------------------------------- 1 | #include "iter.h" 2 | 3 | #include 4 | #include 5 | 6 | // class Iterable 7 | JSR_NATIVE(jsr_core_iter_join) { 8 | JSR_CHECK(String, 2, "sep"); 9 | 10 | const char* sep = jsrGetString(vm, 2); 11 | size_t sepLen = jsrGetStringSz(vm, 2); 12 | 13 | JStarBuffer joined; 14 | jsrBufferInit(vm, &joined); 15 | 16 | JSR_FOREACH( 17 | 1, 18 | { 19 | if(!jsrIsString(vm, -1)) { 20 | if((jsrCallMethod(vm, "__string__", 0) != JSR_SUCCESS)) { 21 | jsrBufferFree(&joined); 22 | return false; 23 | } 24 | if(!jsrIsString(vm, -1)) { 25 | jsrBufferFree(&joined); 26 | JSR_RAISE(vm, "TypeException", "s.__string__() didn't return a String"); 27 | } 28 | } 29 | jsrBufferAppend(&joined, jsrGetString(vm, -1), jsrGetStringSz(vm, -1)); 30 | jsrBufferAppend(&joined, sep, sepLen); 31 | jsrPop(vm); 32 | }, 33 | jsrBufferFree(&joined)) 34 | 35 | if(joined.size > 0) { 36 | jsrBufferTrunc(&joined, joined.size - sepLen); 37 | } 38 | 39 | jsrBufferPush(&joined); 40 | return true; 41 | } 42 | // end 43 | -------------------------------------------------------------------------------- /src/lib/core/iter.h: -------------------------------------------------------------------------------- 1 | #ifndef ITER_H 2 | #define ITER_H 3 | 4 | #include "jstar.h" 5 | 6 | // class Iterable 7 | JSR_NATIVE(jsr_core_iter_join); 8 | // end 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/lib/core/iter.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/core/iter.jsc -------------------------------------------------------------------------------- /src/lib/core/std.c: -------------------------------------------------------------------------------- 1 | #include "std.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "gc.h" 10 | #include "import.h" 11 | #include "jstar.h" 12 | #include "object.h" 13 | #include "object_types.h" 14 | #include "parse/ast.h" 15 | #include "parse/parser.h" 16 | #include "value.h" 17 | #include "vm.h" 18 | 19 | JSR_NATIVE(jsr_int) { 20 | if(jsrIsNumber(vm, 1)) { 21 | jsrPushNumber(vm, trunc(jsrGetNumber(vm, 1))); 22 | return true; 23 | } 24 | if(jsrIsString(vm, 1)) { 25 | char* end = NULL; 26 | const char* nstr = jsrGetString(vm, 1); 27 | long long n = strtoll(nstr, &end, 10); 28 | 29 | if((n == 0 && end == nstr) || *end != '\0') { 30 | JSR_RAISE(vm, "InvalidArgException", "'%s'.", nstr); 31 | } 32 | if(n == LLONG_MAX) { 33 | JSR_RAISE(vm, "InvalidArgException", "Overflow: '%s'.", nstr); 34 | } 35 | if(n == LLONG_MIN) { 36 | JSR_RAISE(vm, "InvalidArgException", "Underflow: '%s'.", nstr); 37 | } 38 | 39 | jsrPushNumber(vm, n); 40 | return true; 41 | } 42 | JSR_RAISE(vm, "TypeException", "Argument must be a number or a string."); 43 | } 44 | 45 | JSR_NATIVE(jsr_char) { 46 | JSR_CHECK(String, 1, "c"); 47 | const char* str = jsrGetString(vm, 1); 48 | if(jsrGetStringSz(vm, 1) != 1) { 49 | JSR_RAISE(vm, "InvalidArgException", "c must be a String of length 1"); 50 | } 51 | int c = str[0]; 52 | jsrPushNumber(vm, (double)c); 53 | return true; 54 | } 55 | 56 | JSR_NATIVE(jsr_garbageCollect) { 57 | garbageCollect(vm); 58 | jsrPushNull(vm); 59 | return true; 60 | } 61 | 62 | JSR_NATIVE(jsr_ascii) { 63 | JSR_CHECK(Int, 1, "num"); 64 | char c = jsrGetNumber(vm, 1); 65 | jsrPushStringSz(vm, &c, 1); 66 | return true; 67 | } 68 | 69 | JSR_NATIVE(jsr_print) { 70 | jsrPushValue(vm, 1); 71 | if(jsrCallMethod(vm, "__string__", 0) != JSR_SUCCESS) return false; 72 | if(!jsrIsString(vm, -1)) { 73 | JSR_RAISE(vm, "TypeException", "s.__string__() didn't return a String"); 74 | } 75 | 76 | fwrite(jsrGetString(vm, -1), 1, jsrGetStringSz(vm, -1), stdout); 77 | jsrPop(vm); 78 | 79 | JSR_FOREACH( 80 | 2, { 81 | if(jsrCallMethod(vm, "__string__", 0) != JSR_SUCCESS) return false; 82 | if(!jsrIsString(vm, -1)) { 83 | JSR_RAISE(vm, "TypeException", "__string__() didn't return a String"); 84 | } 85 | 86 | printf(" "); 87 | fwrite(jsrGetString(vm, -1), 1, jsrGetStringSz(vm, -1), stdout); 88 | jsrPop(vm); 89 | }, ); 90 | 91 | printf("\n"); 92 | 93 | jsrPushNull(vm); 94 | return true; 95 | } 96 | 97 | static void parseError(const char* file, int line, const char* error, void* udata) { 98 | JStarVM* vm = udata; 99 | vm->errorCallback(vm, JSR_SYNTAX_ERR, file, line, error); 100 | } 101 | 102 | JSR_NATIVE(jsr_eval) { 103 | JSR_CHECK(String, 1, "source"); 104 | 105 | if(vm->frameCount < 1) { 106 | JSR_RAISE(vm, "Exception", "eval() can only be called by another function"); 107 | } 108 | 109 | const char* src = jsrGetString(vm, 1); 110 | size_t len = jsrGetStringSz(vm, 1); 111 | 112 | JStarStmt* program = jsrParse("", src, len, parseError, vm); 113 | if(program == NULL) { 114 | JSR_RAISE(vm, "SyntaxException", "Syntax error"); 115 | } 116 | 117 | Prototype* proto = getPrototype(vm->frames[vm->frameCount - 2].fn); 118 | ObjFunction* fn = compileModule(vm, "", proto->module->name, program); 119 | jsrStmtFree(program); 120 | 121 | if(fn == NULL) { 122 | JSR_RAISE(vm, "SyntaxException", "Syntax error"); 123 | } 124 | 125 | push(vm, OBJ_VAL(fn)); 126 | ObjClosure* closure = newClosure(vm, fn); 127 | pop(vm); 128 | 129 | push(vm, OBJ_VAL(closure)); 130 | if(jsrCall(vm, 0) != JSR_SUCCESS) return false; 131 | pop(vm); 132 | 133 | jsrPushNull(vm); 134 | return true; 135 | } 136 | 137 | JSR_NATIVE(jsr_type) { 138 | push(vm, OBJ_VAL(getClass(vm, peek(vm)))); 139 | return true; 140 | } 141 | -------------------------------------------------------------------------------- /src/lib/core/std.h: -------------------------------------------------------------------------------- 1 | #ifndef STD_H 2 | #define STD_H 3 | 4 | #include "jstar.h" 5 | 6 | JSR_NATIVE(jsr_ascii); 7 | JSR_NATIVE(jsr_char); 8 | JSR_NATIVE(jsr_eval); 9 | JSR_NATIVE(jsr_garbageCollect); 10 | JSR_NATIVE(jsr_int); 11 | JSR_NATIVE(jsr_print); 12 | JSR_NATIVE(jsr_type); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/lib/core/std.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/core/std.jsc -------------------------------------------------------------------------------- /src/lib/core/std.jsr: -------------------------------------------------------------------------------- 1 | native ascii(num) 2 | native char(c) 3 | native eval(source) 4 | native garbageCollect() 5 | native int(n) 6 | native print(s, ...args) 7 | native type(o) 8 | 9 | fun assert(cond, msg="assertion failed", exception=null) 10 | if !cond 11 | raise (exception(msg) if exception else AssertException(msg)) 12 | end 13 | end 14 | 15 | fun typeAssert(arg, cls, name) 16 | if !(arg is cls) 17 | var got, expected = cls.getName(), type(arg).getName() 18 | raise TypeException("{0} must be a {1}, got {2}" % (name, got, expected)) 19 | end 20 | end 21 | 22 | fun partial(fn, arg, ...rest) 23 | return (|...args| => fn(arg, ...(rest + args))) if #rest != 0 else (|...args| => fn(arg, ...args)) 24 | end 25 | 26 | fun compose(fn1, fn2, ...args) 27 | var functions = args.reversed().concat((fn2, fn1)).collect(Tuple) 28 | var second, first = functions 29 | return |...args| => functions.skip(2).reduce(first(second(...args)), |ret, fn| => fn(ret)) 30 | end 31 | -------------------------------------------------------------------------------- /src/lib/debug.c: -------------------------------------------------------------------------------- 1 | #include "debug.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "disassemble.h" 7 | #include "object.h" 8 | #include "value.h" 9 | #include "value_hashtable.h" 10 | #include "vm.h" 11 | 12 | JSR_NATIVE(jsr_printStack) { 13 | for(Value* v = vm->stack; v < vm->sp; v++) { 14 | printf("["); 15 | printValue(*v); 16 | printf("]"); 17 | } 18 | printf("$\n"); 19 | jsrPushNull(vm); 20 | return true; 21 | } 22 | 23 | static bool isDisassemblable(Value v) { 24 | return (IS_OBJ(v) && (IS_CLOSURE(v) || IS_NATIVE(v) || IS_BOUND_METHOD(v) || IS_CLASS(v))); 25 | } 26 | 27 | JSR_NATIVE(jsr_disassemble) { 28 | Value arg = vm->apiStack[1]; 29 | if(!isDisassemblable(arg)) { 30 | JSR_RAISE(vm, "InvalidArgException", "Cannot disassemble a %s", 31 | getClass(vm, arg)->name->data); 32 | } 33 | 34 | if(IS_BOUND_METHOD(arg)) { 35 | arg = OBJ_VAL(AS_BOUND_METHOD(arg)->method); 36 | } else if(IS_CLASS(arg)) { 37 | Value ctor; 38 | if(!hashTableValueGet(&AS_CLASS(arg)->methods, vm->specialMethods[METH_CTOR], &ctor)) { 39 | jsrPushNull(vm); 40 | return true; 41 | } 42 | arg = ctor; 43 | } 44 | 45 | if(IS_NATIVE(arg)) { 46 | disassembleNative(AS_NATIVE(arg)); 47 | } else { 48 | disassembleFunction(AS_CLOSURE(arg)->fn); 49 | } 50 | 51 | jsrPushNull(vm); 52 | return true; 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | #include "jstar.h" 5 | 6 | JSR_NATIVE(jsr_printStack); 7 | JSR_NATIVE(jsr_disassemble); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/lib/debug.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/debug.jsc -------------------------------------------------------------------------------- /src/lib/debug.jsr: -------------------------------------------------------------------------------- 1 | native printStack() 2 | native disassemble(func) 3 | -------------------------------------------------------------------------------- /src/lib/io.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H 2 | #define FILE_H 3 | 4 | #include "jstar.h" 5 | 6 | // class File 7 | JSR_NATIVE(jsr_File_construct); 8 | JSR_NATIVE(jsr_File_read); 9 | JSR_NATIVE(jsr_File_readAll); 10 | JSR_NATIVE(jsr_File_readLine); 11 | JSR_NATIVE(jsr_File_write); 12 | JSR_NATIVE(jsr_File_close); 13 | JSR_NATIVE(jsr_File_seek); 14 | JSR_NATIVE(jsr_File_tell); 15 | JSR_NATIVE(jsr_File_rewind); 16 | JSR_NATIVE(jsr_File_flush); 17 | JSR_NATIVE(jsr_File_reopen); 18 | JSR_NATIVE(jsr_File_fileno); 19 | // end File 20 | 21 | // class Popen 22 | JSR_NATIVE(jsr_Popen_construct); 23 | JSR_NATIVE(jsr_Popen_close); 24 | // end Popen 25 | 26 | // Functions 27 | JSR_NATIVE(jsr_remove); 28 | JSR_NATIVE(jsr_rename); 29 | JSR_NATIVE(jsr_io_init); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/lib/io.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/io.jsc -------------------------------------------------------------------------------- /src/lib/io.jsr: -------------------------------------------------------------------------------- 1 | class IOException is Exception end 2 | class FileNotFoundException is IOException end 3 | 4 | var Seek = Enum{ 5 | .SET : 0, 6 | .CUR : 1, 7 | .END : 2 8 | } 9 | 10 | class File is iter.Iterable 11 | native construct(path, mode, handle=null) 12 | 13 | native tell() 14 | native seek(off, whence=0) 15 | native rewind() 16 | 17 | native read(bytes) 18 | native readAll() 19 | native readLine() 20 | native write(data) 21 | native close() 22 | native flush() 23 | native reopen(path, mode) 24 | native fileno() 25 | 26 | fun writeln(data) 27 | this.write(data) 28 | this.write('\n') 29 | end 30 | 31 | fun size() 32 | var oldpos = this.tell() 33 | this.seek(0, Seek.END) 34 | var size = this.tell() 35 | this.seek(oldpos) 36 | return size 37 | end 38 | 39 | fun __iter__(_) 40 | return this.readLine() 41 | end 42 | 43 | fun __next__(line) 44 | return line 45 | end 46 | 47 | fun __string__() 48 | return "<" + ("closed " if this._closed else "open ") + super() + ">" 49 | end 50 | end 51 | 52 | static class Popen is File 53 | native construct(name, mode) 54 | native close() 55 | end 56 | 57 | fun popen(name, mode="r") 58 | return Popen(name, mode) 59 | end 60 | 61 | native remove(path) 62 | native rename(oldpath, newpath) 63 | 64 | static native init() 65 | init() 66 | -------------------------------------------------------------------------------- /src/lib/math.c: -------------------------------------------------------------------------------- 1 | #include "math.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "object.h" 9 | #include "object_types.h" 10 | #include "value.h" 11 | #include "vm.h" 12 | 13 | #define JSR_PI 3.14159265358979323846 14 | #define JSR_E 2.71828182845904523536 15 | 16 | #define STDLIB_MATH_FUN_X(fun) \ 17 | JSR_NATIVE(jsr_##fun) { \ 18 | if(!jsrCheckNumber(vm, 1, "x")) return false; \ 19 | jsrPushNumber(vm, fun(jsrGetNumber(vm, 1))); \ 20 | return true; \ 21 | } 22 | 23 | #define STDLIB_MATH_FUN_XY(fun) \ 24 | JSR_NATIVE(jsr_##fun) { \ 25 | if(!jsrCheckNumber(vm, 1, "x") || !jsrCheckNumber(vm, 2, "y")) return false; \ 26 | jsrPushNumber(vm, fun(jsrGetNumber(vm, 1), jsrGetNumber(vm, 2))); \ 27 | return true; \ 28 | } 29 | 30 | static double deg(double x) { 31 | return x * (180. / JSR_PI); 32 | } 33 | 34 | static double rad(double x) { 35 | return x * JSR_PI / 180.; 36 | } 37 | 38 | // The MSVC stdlib.h header file seem to define `min` and `max` no matter which compilation options 39 | // or define I try to use, so we need to undefine them manually. 40 | // Nice work Microsoft for distributing non-standard compliant header files with your compiler... 41 | #ifdef max 42 | #undef max 43 | #endif 44 | #ifdef min 45 | #undef min 46 | #endif 47 | 48 | #define max(a, b) ((a) > (b) ? (a) : (b)) 49 | #define min(a, b) ((a) < (b) ? (a) : (b)) 50 | 51 | JSR_NATIVE(jsr_abs) { 52 | JSR_CHECK(Number, 1, "x"); 53 | jsrPushNumber(vm, fabs(jsrGetNumber(vm, 1))); 54 | return true; 55 | } 56 | 57 | STDLIB_MATH_FUN_X(acos) 58 | STDLIB_MATH_FUN_X(asin) 59 | STDLIB_MATH_FUN_X(atan) 60 | 61 | JSR_NATIVE(jsr_atan2) { 62 | JSR_CHECK(Number, 1, "y"); 63 | JSR_CHECK(Number, 2, "x"); 64 | jsrPushNumber(vm, atan2(jsrGetNumber(vm, 1), jsrGetNumber(vm, 2))); 65 | return true; 66 | } 67 | 68 | STDLIB_MATH_FUN_X(ceil) 69 | STDLIB_MATH_FUN_X(cos) 70 | STDLIB_MATH_FUN_X(cosh) 71 | STDLIB_MATH_FUN_X(deg) 72 | STDLIB_MATH_FUN_X(exp) 73 | STDLIB_MATH_FUN_X(floor) 74 | 75 | JSR_NATIVE(jsr_frexp) { 76 | JSR_CHECK(Number, 1, "x"); 77 | double m; 78 | int e; 79 | m = frexp(jsrGetNumber(vm, 1), &e); 80 | ObjTuple* ret = newTuple(vm, 2); 81 | ret->arr[0] = NUM_VAL(m); 82 | ret->arr[1] = NUM_VAL(e); 83 | push(vm, OBJ_VAL(ret)); 84 | return true; 85 | } 86 | 87 | JSR_NATIVE(jsr_ldexp) { 88 | JSR_CHECK(Number, 1, "x"); 89 | JSR_CHECK(Int, 2, "exp"); 90 | jsrPushNumber(vm, ldexp(jsrGetNumber(vm, 1), jsrGetNumber(vm, 2))); 91 | return true; 92 | } 93 | 94 | STDLIB_MATH_FUN_X(log) 95 | STDLIB_MATH_FUN_X(log10) 96 | STDLIB_MATH_FUN_XY(max) 97 | STDLIB_MATH_FUN_XY(min) 98 | STDLIB_MATH_FUN_X(rad) 99 | STDLIB_MATH_FUN_X(sin) 100 | STDLIB_MATH_FUN_X(sinh) 101 | STDLIB_MATH_FUN_X(sqrt) 102 | STDLIB_MATH_FUN_X(tan) 103 | STDLIB_MATH_FUN_X(tanh) 104 | STDLIB_MATH_FUN_X(round) 105 | 106 | JSR_NATIVE(jsr_modf) { 107 | JSR_CHECK(Number, 1, "x"); 108 | double integer, frac; 109 | integer = modf(jsrGetNumber(vm, 1), &frac); 110 | ObjTuple* ret = newTuple(vm, 2); 111 | ret->arr[0] = NUM_VAL(integer); 112 | ret->arr[1] = NUM_VAL(frac); 113 | push(vm, OBJ_VAL(ret)); 114 | return true; 115 | } 116 | 117 | JSR_NATIVE(jsr_random) { 118 | jsrPushNumber(vm, (double)rand() / ((unsigned)RAND_MAX + 1)); 119 | return true; 120 | } 121 | 122 | JSR_NATIVE(jsr_seed) { 123 | JSR_CHECK(Int, 1, "s"); 124 | srand(jsrGetNumber(vm, 1)); 125 | jsrPushNull(vm); 126 | return true; 127 | } 128 | 129 | JSR_NATIVE(jsr_math_init) { 130 | // Init constants 131 | jsrPushNumber(vm, HUGE_VAL); 132 | jsrSetGlobal(vm, NULL, "huge"); 133 | jsrPushNumber(vm, NAN); 134 | jsrSetGlobal(vm, NULL, "nan"); 135 | jsrPushNumber(vm, JSR_PI); 136 | jsrSetGlobal(vm, NULL, "pi"); 137 | jsrPushNumber(vm, JSR_E); 138 | jsrSetGlobal(vm, NULL, "e"); 139 | jsrPushNull(vm); 140 | // Init rand seed 141 | srand(time(NULL)); 142 | return true; 143 | } 144 | -------------------------------------------------------------------------------- /src/lib/math.h: -------------------------------------------------------------------------------- 1 | #ifndef JSR_MATH_H 2 | #define JSR_MATH_H 3 | 4 | #include "jstar.h" 5 | 6 | JSR_NATIVE(jsr_abs); 7 | JSR_NATIVE(jsr_acos); 8 | JSR_NATIVE(jsr_asin); 9 | JSR_NATIVE(jsr_atan); 10 | JSR_NATIVE(jsr_atan2); 11 | JSR_NATIVE(jsr_ceil); 12 | JSR_NATIVE(jsr_cos); 13 | JSR_NATIVE(jsr_cosh); 14 | JSR_NATIVE(jsr_deg); 15 | JSR_NATIVE(jsr_exp); 16 | JSR_NATIVE(jsr_floor); 17 | JSR_NATIVE(jsr_frexp); 18 | JSR_NATIVE(jsr_ldexp); 19 | JSR_NATIVE(jsr_log); 20 | JSR_NATIVE(jsr_log10); 21 | JSR_NATIVE(jsr_max); 22 | JSR_NATIVE(jsr_min); 23 | JSR_NATIVE(jsr_rad); 24 | JSR_NATIVE(jsr_sin); 25 | JSR_NATIVE(jsr_sinh); 26 | JSR_NATIVE(jsr_sqrt); 27 | JSR_NATIVE(jsr_tan); 28 | JSR_NATIVE(jsr_tanh); 29 | JSR_NATIVE(jsr_modf); 30 | JSR_NATIVE(jsr_random); 31 | JSR_NATIVE(jsr_round); 32 | JSR_NATIVE(jsr_seed); 33 | JSR_NATIVE(jsr_math_init); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/lib/math.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/math.jsc -------------------------------------------------------------------------------- /src/lib/math.jsr: -------------------------------------------------------------------------------- 1 | native abs(x) 2 | native acos(x) 3 | native asin(x) 4 | native atan(x) 5 | native atan2(y, x) 6 | native ceil(x) 7 | native cos(x) 8 | native cosh(x) 9 | native deg(x) 10 | native exp(x) 11 | native floor(x) 12 | native frexp(x) 13 | native ldexp(x, exp) 14 | native log(x) 15 | native log10(x) 16 | native max(x, y) 17 | native min(x, y) 18 | native rad(x) 19 | native sin(x) 20 | native sinh(x) 21 | native sqrt(x) 22 | native tan(x) 23 | native tanh(x) 24 | native modf(x) 25 | native random() 26 | native round(x) 27 | native seed(s) 28 | 29 | fun randint(a, b=null) 30 | if b == null 31 | a, b = 0, a 32 | end 33 | 34 | typeAssert(a, Number, "a") 35 | typeAssert(b, Number, "b") 36 | 37 | assert(a.isInt() and b.isInt(), "a and b must be integers") 38 | assert(a < b, "a must be < b") 39 | 40 | return std.int(a + random() * (b - a + 1)) 41 | end 42 | 43 | static native init() 44 | init() 45 | -------------------------------------------------------------------------------- /src/lib/re.h: -------------------------------------------------------------------------------- 1 | #ifndef RE_H 2 | #define RE_H 3 | 4 | #include "jstar.h" 5 | 6 | JSR_NATIVE(jsr_re_match); 7 | JSR_NATIVE(jsr_re_find); 8 | JSR_NATIVE(jsr_re_matchAll); 9 | JSR_NATIVE(jsr_re_substituteAll); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/lib/re.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/re.jsc -------------------------------------------------------------------------------- /src/lib/re.jsr: -------------------------------------------------------------------------------- 1 | class RegexException is Exception end 2 | 3 | native match(str, regex, off=0) 4 | native find(str, regex, off=0) 5 | native substituteAll(str, regex, sub, num=0) 6 | native matchAll(str, regex) 7 | 8 | // TODO: rework as generator 9 | static class MatchIter is iter.Iterable 10 | construct(string, regex) 11 | this.offset = 0 12 | this.lastMatch = null 13 | this.string = string 14 | this.regex = regex 15 | end 16 | 17 | fun _madeProgress(startMatch, endMatch) 18 | return this.lastMatch != startMatch or endMatch - startMatch != 0 19 | end 20 | 21 | fun __iter__(_) 22 | var match = find(this.string, this.regex, this.offset) 23 | if !match return null end 24 | 25 | var matchStart, matchEnd = match 26 | while !this._madeProgress(matchStart, matchEnd) 27 | match = find(this.string, this.regex, this.offset += 1) 28 | if !match return null end 29 | matchStart, matchEnd = match 30 | end 31 | 32 | this.offset = this.lastMatch = matchEnd 33 | if #match == 2 34 | return this.string[matchStart, matchEnd] 35 | elif #match == 3 36 | return match[2] 37 | else 38 | return match[2, #match] 39 | end 40 | end 41 | 42 | fun __next__(match) 43 | return match 44 | end 45 | end 46 | 47 | fun lazyMatchAll(str, regex) 48 | return MatchIter(str, regex) 49 | end 50 | -------------------------------------------------------------------------------- /src/lib/sys.c: -------------------------------------------------------------------------------- 1 | #include "sys.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #if defined(JSTAR_POSIX) 10 | #define USE_POPEN 11 | #include 12 | #elif defined(JSTAR_WINDOWS) 13 | #define USE_POPEN 14 | #include 15 | #define popen _popen 16 | #define pclose _pclose 17 | #endif 18 | 19 | #if defined(JSTAR_WINDOWS) 20 | #define PLATFORM "Windows" 21 | #elif defined(JSTAR_LINUX) 22 | #define PLATFORM "Linux" 23 | #elif defined(JSTAR_MACOS) 24 | #define PLATFORM "OS X" 25 | #elif defined(JSTAR_IOS) 26 | #define PLATFORM "iOS" 27 | #elif defined(JSTAR_ANDROID) 28 | #define PLATFORM "Android" 29 | #elif defined(JSTAR_FREEBSD) 30 | #define PLATFORM "FreeBSD" 31 | #elif defined(JSTAR_OPENBSD) 32 | #define PLATFORM "OpenBSD" 33 | #elif defined(JSTAR_EMSCRIPTEN) 34 | #define PLATFORM "Emscripten" 35 | #else 36 | #define PLATFORM "Unknown" 37 | #endif 38 | 39 | JSR_NATIVE(jsr_isPosix) { 40 | #ifdef JSTAR_POSIX 41 | jsrPushBoolean(vm, true); 42 | #else 43 | jsrPushBoolean(vm, false); 44 | #endif 45 | return true; 46 | } 47 | 48 | JSR_NATIVE(jsr_platform) { 49 | jsrPushString(vm, PLATFORM); 50 | return true; 51 | } 52 | 53 | JSR_NATIVE(jsr_time) { 54 | jsrPushNumber(vm, time(NULL)); 55 | return true; 56 | } 57 | 58 | JSR_NATIVE(jsr_clock) { 59 | jsrPushNumber(vm, (double)clock() / CLOCKS_PER_SEC); 60 | return true; 61 | } 62 | 63 | JSR_NATIVE(jsr_getenv) { 64 | JSR_CHECK(String, 1, "name"); 65 | char* value = getenv(jsrGetString(vm, 1)); 66 | if(value != NULL) { 67 | jsrPushString(vm, value); 68 | return true; 69 | } 70 | jsrPushNull(vm); 71 | return true; 72 | } 73 | 74 | JSR_NATIVE(jsr_exec) { 75 | #ifdef USE_POPEN 76 | JSR_CHECK(String, 1, "cmd"); 77 | 78 | FILE* proc = popen(jsrGetString(vm, 1), "r"); 79 | if(proc == NULL) { 80 | JSR_RAISE(vm, "Exception", strerror(errno)); 81 | } 82 | 83 | JStarBuffer data; 84 | jsrBufferInit(vm, &data); 85 | 86 | char buf[512]; 87 | while(fgets(buf, 512, proc) != NULL) { 88 | jsrBufferAppendStr(&data, buf); 89 | } 90 | 91 | if(ferror(proc)) { 92 | pclose(proc); 93 | jsrBufferFree(&data); 94 | JSR_RAISE(vm, "Exception", strerror(errno)); 95 | } else { 96 | jsrBufferPush(&data); 97 | jsrPushNumber(vm, pclose(proc)); 98 | jsrPushTuple(vm, 2); 99 | } 100 | 101 | return true; 102 | #else 103 | JSR_RAISE(vm, "NotImplementedException", "`exec` not supported on current system."); 104 | #endif 105 | } 106 | 107 | JSR_NATIVE(jsr_exit) { 108 | JSR_CHECK(Int, 1, "n"); 109 | exit(jsrGetNumber(vm, 1)); 110 | } 111 | 112 | JSR_NATIVE(jsr_system) { 113 | const char* cmd = NULL; 114 | if(!jsrIsNull(vm, 1)) { 115 | JSR_CHECK(String, 1, "cmd"); 116 | cmd = jsrGetString(vm, 1); 117 | } 118 | jsrPushNumber(vm, system(cmd)); 119 | return true; 120 | } 121 | -------------------------------------------------------------------------------- /src/lib/sys.h: -------------------------------------------------------------------------------- 1 | #ifndef SYS_H 2 | #define SYS_H 3 | 4 | #include "jstar.h" 5 | 6 | JSR_NATIVE(jsr_time); 7 | JSR_NATIVE(jsr_exec); 8 | JSR_NATIVE(jsr_exit); 9 | JSR_NATIVE(jsr_isPosix); 10 | JSR_NATIVE(jsr_platform); 11 | JSR_NATIVE(jsr_getenv); 12 | JSR_NATIVE(jsr_clock); 13 | JSR_NATIVE(jsr_system); 14 | JSR_NATIVE(jsr_eval); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/lib/sys.jsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamless/jstar/eb9769906fa909a26b85eec233795111c3c78e2f/src/lib/sys.jsc -------------------------------------------------------------------------------- /src/lib/sys.jsr: -------------------------------------------------------------------------------- 1 | native clock() 2 | native exec(cmd) 3 | native exit(n=0) 4 | native getenv(name) 5 | native isPosix() 6 | native platform() 7 | native system(cmd) 8 | native time() 9 | -------------------------------------------------------------------------------- /src/object_types.h: -------------------------------------------------------------------------------- 1 | #ifndef OBJECT_TYPES_H 2 | #define OBJECT_TYPES_H 3 | 4 | /** 5 | * This file contains forward declarations of all the object types used in the J* VM. 6 | * See "object.h" for the actual object definitions. 7 | */ 8 | 9 | // Object type. 10 | // These types are used internally by the object system and are never 11 | // exposed to the user, to whom all values behave like class instances. 12 | // The enum is defined using X-macros in order to automatically generate 13 | // string names of enum constats (see ObjTypeNames array in object.c) 14 | #define OBJTYPE(X) \ 15 | X(OBJ_STRING) \ 16 | X(OBJ_NATIVE) \ 17 | X(OBJ_FUNCTION) \ 18 | X(OBJ_CLASS) \ 19 | X(OBJ_INST) \ 20 | X(OBJ_MODULE) \ 21 | X(OBJ_LIST) \ 22 | X(OBJ_BOUND_METHOD) \ 23 | X(OBJ_STACK_TRACE) \ 24 | X(OBJ_CLOSURE) \ 25 | X(OBJ_GENERATOR) \ 26 | X(OBJ_UPVALUE) \ 27 | X(OBJ_TUPLE) \ 28 | X(OBJ_TABLE) \ 29 | X(OBJ_USERDATA) 30 | 31 | typedef enum ObjType { 32 | #define ENUM_ELEM(elem) elem, 33 | OBJTYPE(ENUM_ELEM) 34 | #undef ENUM_ELEM 35 | } ObjType; 36 | 37 | typedef struct Obj Obj; 38 | 39 | typedef struct ObjString ObjString; 40 | 41 | typedef struct ObjModule ObjModule; 42 | 43 | typedef struct Prototype Prototype; 44 | 45 | typedef struct ObjFunction ObjFunction; 46 | 47 | typedef struct ObjNative ObjNative; 48 | 49 | typedef struct ObjClass ObjClass; 50 | 51 | typedef struct ObjInstance ObjInstance; 52 | 53 | typedef struct ObjList ObjList; 54 | 55 | typedef struct ObjTuple ObjTuple; 56 | 57 | typedef struct TableEntry TableEntry; 58 | typedef struct ObjTable ObjTable; 59 | 60 | typedef struct ObjBoundMethod ObjBoundMethod; 61 | 62 | typedef struct ObjUpvalue ObjUpvalue; 63 | 64 | typedef struct ObjClosure ObjClosure; 65 | 66 | typedef struct SavedHandler SavedHandler; 67 | typedef struct SavedFrame SavedFrame; 68 | typedef struct ObjGenerator ObjGenerator; 69 | 70 | typedef struct FrameRecord FrameRecord; 71 | typedef struct ObjStackTrace ObjStackTrace; 72 | 73 | typedef struct ObjUserdata ObjUserdata; 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /src/opcode.c: -------------------------------------------------------------------------------- 1 | #include "opcode.h" 2 | 3 | // Create string names of opcodes 4 | const char* OpcodeNames[] = { 5 | #define OPCODE(opcode, args, stack) #opcode, 6 | #include "opcode.def" 7 | }; 8 | 9 | static const int argsNumber[] = { 10 | #define OPCODE(opcode, args, stack) args, 11 | #include "opcode.def" 12 | }; 13 | 14 | static const int stackUsage[] = { 15 | #define OPCODE(opcode, args, stack) stack, 16 | #include "opcode.def" 17 | }; 18 | 19 | int opcodeArgsNumber(Opcode op) { 20 | return argsNumber[op]; 21 | } 22 | 23 | int opcodeStackUsage(Opcode op) { 24 | return stackUsage[op]; 25 | } 26 | -------------------------------------------------------------------------------- /src/opcode.def: -------------------------------------------------------------------------------- 1 | // The following is a list of all opcodes used by the VM. Each opcode follows this format: 2 | // (OPCODE, number of args, number of values added or removed from the stack) 3 | OPCODE(OP_ADD, 0, -1) 4 | OPCODE(OP_SUB, 0, -1) 5 | OPCODE(OP_MUL, 0, -1) 6 | OPCODE(OP_DIV, 0, -1) 7 | OPCODE(OP_MOD, 0, -1) 8 | OPCODE(OP_NEG, 0, 0) 9 | OPCODE(OP_INVERT, 0, 0) 10 | OPCODE(OP_BAND, 0, -1) 11 | OPCODE(OP_BOR, 0, -1) 12 | OPCODE(OP_XOR, 0, -1) 13 | OPCODE(OP_LSHIFT, 0, -1) 14 | OPCODE(OP_RSHIFT, 0, -1) 15 | OPCODE(OP_EQ, 0, -1) 16 | OPCODE(OP_NOT, 0, 0) 17 | OPCODE(OP_GT, 0, -1) 18 | OPCODE(OP_GE, 0, -1) 19 | OPCODE(OP_LT, 0, -1) 20 | OPCODE(OP_LE, 0, -1) 21 | OPCODE(OP_IS, 0, -1) 22 | OPCODE(OP_POW, 0, -1) 23 | OPCODE(OP_GET_FIELD, 2, 0) 24 | OPCODE(OP_SET_FIELD, 2, -1) 25 | OPCODE(OP_SUBSCR_SET, 0, -2) 26 | OPCODE(OP_SUBSCR_GET, 0, -1) 27 | OPCODE(OP_CALL, 1, 0) 28 | OPCODE(OP_CALL_0, 0, 0) 29 | OPCODE(OP_CALL_1, 0, -1) 30 | OPCODE(OP_CALL_2, 0, -2) 31 | OPCODE(OP_CALL_3, 0, -3) 32 | OPCODE(OP_CALL_4, 0, -4) 33 | OPCODE(OP_CALL_5, 0, -5) 34 | OPCODE(OP_CALL_6, 0, -6) 35 | OPCODE(OP_CALL_7, 0, -7) 36 | OPCODE(OP_CALL_8, 0, -8) 37 | OPCODE(OP_CALL_9, 0, -9) 38 | OPCODE(OP_CALL_10, 0, -10) 39 | OPCODE(OP_CALL_UNPACK, 0, 0) 40 | OPCODE(OP_INVOKE, 3, 0) 41 | OPCODE(OP_INVOKE_0, 2, 0) 42 | OPCODE(OP_INVOKE_1, 2, -1) 43 | OPCODE(OP_INVOKE_2, 2, -2) 44 | OPCODE(OP_INVOKE_3, 2, -3) 45 | OPCODE(OP_INVOKE_4, 2, -4) 46 | OPCODE(OP_INVOKE_5, 2, -5) 47 | OPCODE(OP_INVOKE_6, 2, -6) 48 | OPCODE(OP_INVOKE_7, 2, -7) 49 | OPCODE(OP_INVOKE_8, 2, -8) 50 | OPCODE(OP_INVOKE_9, 2, -9) 51 | OPCODE(OP_INVOKE_10, 2, -10) 52 | OPCODE(OP_INVOKE_UNPACK, 2, 0) 53 | OPCODE(OP_SUPER, 3, -1) 54 | OPCODE(OP_SUPER_0, 2, -1) 55 | OPCODE(OP_SUPER_1, 2, -2) 56 | OPCODE(OP_SUPER_2, 2, -3) 57 | OPCODE(OP_SUPER_3, 2, -4) 58 | OPCODE(OP_SUPER_4, 2, -5) 59 | OPCODE(OP_SUPER_5, 2, -6) 60 | OPCODE(OP_SUPER_6, 2, -7) 61 | OPCODE(OP_SUPER_7, 2, -8) 62 | OPCODE(OP_SUPER_8, 2, -9) 63 | OPCODE(OP_SUPER_9, 2, -10) 64 | OPCODE(OP_SUPER_10, 2, -11) 65 | OPCODE(OP_SUPER_BIND, 2, -1) 66 | OPCODE(OP_SUPER_UNPACK, 2, -1) 67 | OPCODE(OP_JUMP, 2, 0) 68 | OPCODE(OP_JUMPT, 2, -1) 69 | OPCODE(OP_JUMPF, 2, -1) 70 | OPCODE(OP_FOR_PREP, 0, 2) 71 | OPCODE(OP_FOR_ITER, 0, 2) 72 | OPCODE(OP_FOR_NEXT, 2, -1) 73 | OPCODE(OP_IMPORT, 2, 1) 74 | OPCODE(OP_IMPORT_FROM, 2, 0) 75 | OPCODE(OP_IMPORT_NAME, 4, 1) 76 | OPCODE(OP_NEW_LIST, 0, 1) 77 | OPCODE(OP_APPEND_LIST, 0, -1) 78 | OPCODE(OP_LIST_TO_TUPLE, 0, 0) 79 | OPCODE(OP_NEW_TABLE, 0, 1) 80 | OPCODE(OP_NEW_TUPLE, 1, 1) 81 | OPCODE(OP_CLOSURE, 2, 1) 82 | OPCODE(OP_GENERATOR, 0, 1) 83 | OPCODE(OP_GENERATOR_CLOSE, 0, 0) 84 | OPCODE(OP_GET_OBJECT, 0, 1) 85 | OPCODE(OP_NEW_CLASS, 2, 1) 86 | OPCODE(OP_SUBCLASS, 0, 0) 87 | OPCODE(OP_DEF_METHOD, 2, -1) 88 | OPCODE(OP_GET_CONST, 2, 1) 89 | OPCODE(OP_GET_LOCAL, 1, 1) 90 | OPCODE(OP_GET_UPVALUE, 1, 1) 91 | OPCODE(OP_GET_GLOBAL, 2, 1) 92 | OPCODE(OP_SET_LOCAL, 1, 0) 93 | OPCODE(OP_SET_UPVALUE, 1, 0) 94 | OPCODE(OP_SET_GLOBAL, 2, 0) 95 | OPCODE(OP_DEFINE_GLOBAL, 2, -1) 96 | OPCODE(OP_NATIVE, 4, 1) 97 | OPCODE(OP_NATIVE_METHOD, 4, 1) 98 | OPCODE(OP_RETURN, 0, 0) 99 | OPCODE(OP_YIELD, 0, 0) 100 | OPCODE(OP_NULL, 0, 1) 101 | OPCODE(OP_SETUP_EXCEPT, 2, 0) 102 | OPCODE(OP_SETUP_ENSURE, 2, 0) 103 | OPCODE(OP_END_HANDLER, 0, 0) 104 | OPCODE(OP_POP_HANDLER, 0, 0) 105 | OPCODE(OP_RAISE, 0, 0) 106 | OPCODE(OP_POP, 0, -1) 107 | OPCODE(OP_POPN, 1, 0) 108 | OPCODE(OP_CLOSE_UPVALUE, 0, -1) 109 | OPCODE(OP_DUP, 0, 1) 110 | OPCODE(OP_UNPACK, 1, 0) 111 | OPCODE(OP_END, 0, 0) 112 | #undef OPCODE 113 | -------------------------------------------------------------------------------- /src/opcode.h: -------------------------------------------------------------------------------- 1 | #ifndef OPCODE_H 2 | #define OPCODE_H 3 | 4 | extern const char* OpcodeNames[]; 5 | 6 | // Enum encoding the opcodes of the J* vm. 7 | // The opcodes are generated from the "opcode.def" file. 8 | typedef enum Opcode { 9 | #define OPCODE(opcode, args, stack) opcode, 10 | #include "opcode.def" 11 | } Opcode; 12 | 13 | // Returns the number of arguments of an opcode. 14 | int opcodeArgsNumber(Opcode op); 15 | 16 | // Returns the stack usage of an opcode (positive added to the stack, negative removed) 17 | int opcodeStackUsage(Opcode op); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/parse/lex.c: -------------------------------------------------------------------------------- 1 | #include "parse/lex.h" 2 | 3 | #include 4 | #include 5 | 6 | const char* JStarTokName[] = { 7 | #define TOKEN(tok, name) name, 8 | #include "parse/token.def" 9 | }; 10 | 11 | typedef struct Keyword { 12 | const char* name; 13 | size_t length; 14 | JStarTokType type; 15 | } Keyword; 16 | 17 | // clang-format off 18 | 19 | static Keyword keywords[] = { 20 | {"and", 3, TOK_AND}, 21 | {"or", 2, TOK_OR}, 22 | {"class", 5, TOK_CLASS}, 23 | {"else", 4, TOK_ELSE}, 24 | {"false", 5, TOK_FALSE}, 25 | {"for", 3, TOK_FOR}, 26 | {"fun", 3, TOK_FUN}, 27 | {"construct", 9, TOK_CTOR}, 28 | {"native", 6, TOK_NAT}, 29 | {"if", 2, TOK_IF}, 30 | {"elif", 4, TOK_ELIF}, 31 | {"null", 4, TOK_NULL}, 32 | {"return", 6, TOK_RETURN}, 33 | {"yield", 5, TOK_YIELD}, 34 | {"super", 5, TOK_SUPER}, 35 | {"true", 4, TOK_TRUE}, 36 | {"var", 3, TOK_VAR}, 37 | {"while", 5, TOK_WHILE}, 38 | {"import", 6, TOK_IMPORT}, 39 | {"in", 2, TOK_IN}, 40 | {"begin", 5, TOK_BEGIN}, 41 | {"end", 3, TOK_END}, 42 | {"as", 2, TOK_AS}, 43 | {"is", 2, TOK_IS}, 44 | {"try", 3, TOK_TRY}, 45 | {"ensure", 6, TOK_ENSURE}, 46 | {"except", 6, TOK_EXCEPT}, 47 | {"raise", 5, TOK_RAISE}, 48 | {"with", 4, TOK_WITH}, 49 | {"continue", 8, TOK_CONTINUE}, 50 | {"break", 5, TOK_BREAK}, 51 | {"static", 6, TOK_STATIC}, 52 | // sentinel 53 | {NULL, 0, TOK_EOF} 54 | }; 55 | 56 | // clang-format on 57 | 58 | static char advance(JStarLex* lex) { 59 | lex->current++; 60 | return lex->current[-1]; 61 | } 62 | 63 | static bool isAtEnd(JStarLex* lex) { 64 | return (size_t)(lex->current - lex->source) == lex->sourceLen; 65 | } 66 | 67 | static char peekChar(JStarLex* lex) { 68 | if(isAtEnd(lex)) return '\0'; 69 | return *lex->current; 70 | } 71 | 72 | static char peekChar2(JStarLex* lex) { 73 | if(isAtEnd(lex)) return '\0'; 74 | return lex->current[1]; 75 | } 76 | 77 | static bool match(JStarLex* lex, char c) { 78 | if(isAtEnd(lex)) return false; 79 | if(peekChar(lex) == c) { 80 | advance(lex); 81 | return true; 82 | } 83 | return false; 84 | } 85 | 86 | void jsrInitLexer(JStarLex* lex, const char* src, size_t len) { 87 | lex->source = src; 88 | lex->sourceLen = len; 89 | lex->tokenStart = src; 90 | lex->current = src; 91 | lex->currLine = 1; 92 | 93 | // skip shabang if present 94 | if(peekChar(lex) == '#' && peekChar2(lex) == '!') { 95 | while(!isAtEnd(lex)) { 96 | if(peekChar(lex) == '\n') break; 97 | advance(lex); 98 | } 99 | } 100 | } 101 | 102 | static void skipSpacesAndComments(JStarLex* lex) { 103 | while(!isAtEnd(lex)) { 104 | switch(peekChar(lex)) { 105 | case '\\': 106 | if(peekChar2(lex) == '\n') { 107 | lex->currLine++; 108 | advance(lex); 109 | advance(lex); 110 | } else { 111 | return; 112 | } 113 | break; 114 | case '\r': 115 | case '\t': 116 | case ' ': 117 | advance(lex); 118 | break; 119 | case '/': 120 | if(peekChar2(lex) == '/') { 121 | while(peekChar(lex) != '\n' && !isAtEnd(lex)) advance(lex); 122 | } else { 123 | return; 124 | } 125 | break; 126 | default: 127 | return; 128 | } 129 | } 130 | } 131 | 132 | static bool isAlpha(char c) { 133 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; 134 | } 135 | 136 | static bool isNum(char c) { 137 | return c >= '0' && c <= '9'; 138 | } 139 | 140 | static bool isHex(char c) { 141 | return isNum(c) || (c >= 'a' && c <= 'f'); 142 | } 143 | 144 | static bool isAlphaNum(char c) { 145 | return isAlpha(c) || isNum(c); 146 | } 147 | 148 | static void makeToken(JStarLex* lex, JStarTok* tok, JStarTokType type) { 149 | tok->type = type; 150 | tok->lexeme = lex->tokenStart; 151 | tok->length = (int)(lex->current - lex->tokenStart); 152 | tok->line = lex->currLine; 153 | } 154 | 155 | static void eofToken(JStarLex* lex, JStarTok* tok) { 156 | tok->type = TOK_EOF; 157 | tok->lexeme = lex->current; 158 | tok->length = 0; 159 | tok->line = lex->currLine; 160 | } 161 | 162 | static void integer(JStarLex* lex) { 163 | while(isNum(peekChar(lex))) advance(lex); 164 | } 165 | 166 | static void number(JStarLex* lex, JStarTok* tok) { 167 | integer(lex); 168 | 169 | if(peekChar(lex) == '.' && isNum(peekChar2(lex))) { 170 | advance(lex); 171 | integer(lex); 172 | } 173 | 174 | if(match(lex, 'e')) { 175 | char c = peekChar(lex); 176 | if(c == '-' || c == '+') advance(lex); 177 | integer(lex); 178 | } 179 | 180 | makeToken(lex, tok, TOK_NUMBER); 181 | } 182 | 183 | static void hexNumber(JStarLex* lex, JStarTok* tok) { 184 | while(isHex(peekChar(lex))) advance(lex); 185 | 186 | if(match(lex, 'e')) { 187 | char c = peekChar(lex); 188 | if(c == '-' || c == '+') advance(lex); 189 | integer(lex); 190 | } 191 | 192 | makeToken(lex, tok, TOK_NUMBER); 193 | } 194 | 195 | static bool stringBody(JStarLex* lex, char end) { 196 | while(peekChar(lex) != end && !isAtEnd(lex)) { 197 | if(peekChar(lex) == '\n') lex->currLine++; 198 | if(peekChar(lex) == '\\' && peekChar2(lex) != '\0') advance(lex); 199 | advance(lex); 200 | } 201 | 202 | // unterminated string 203 | if(isAtEnd(lex)) { 204 | return false; 205 | } 206 | 207 | advance(lex); 208 | return true; 209 | } 210 | 211 | static void string(JStarLex* lex, char end, JStarTok* tok) { 212 | if(!stringBody(lex, end)) { 213 | makeToken(lex, tok, TOK_UNTERMINATED_STR); 214 | } else { 215 | makeToken(lex, tok, TOK_STRING); 216 | } 217 | } 218 | 219 | static void identifier(JStarLex* lex, JStarTok* tok) { 220 | while(isAlphaNum(peekChar(lex))) advance(lex); 221 | JStarTokType type = TOK_IDENTIFIER; 222 | 223 | // See if the identifier is a reserved word. 224 | size_t length = lex->current - lex->tokenStart; 225 | for(Keyword* keyword = keywords; keyword->name != NULL; keyword++) { 226 | if(length == keyword->length && memcmp(lex->tokenStart, keyword->name, length) == 0) { 227 | type = keyword->type; 228 | break; 229 | } 230 | } 231 | 232 | makeToken(lex, tok, type); 233 | } 234 | 235 | void jsrNextToken(JStarLex* lex, JStarTok* tok) { 236 | skipSpacesAndComments(lex); 237 | 238 | if(isAtEnd(lex)) { 239 | eofToken(lex, tok); 240 | return; 241 | } 242 | 243 | lex->tokenStart = lex->current; 244 | char c = advance(lex); 245 | 246 | if(c == '0' && match(lex, 'x')) { 247 | hexNumber(lex, tok); 248 | return; 249 | } 250 | if(isNum(c) || (c == '.' && isNum(peekChar(lex)))) { 251 | number(lex, tok); 252 | return; 253 | } 254 | if(isAlpha(c)) { 255 | identifier(lex, tok); 256 | return; 257 | } 258 | 259 | switch(c) { 260 | case '(': 261 | makeToken(lex, tok, TOK_LPAREN); 262 | break; 263 | case ')': 264 | makeToken(lex, tok, TOK_RPAREN); 265 | break; 266 | case ';': 267 | makeToken(lex, tok, TOK_SEMICOLON); 268 | break; 269 | case ':': 270 | makeToken(lex, tok, TOK_COLON); 271 | break; 272 | case '|': 273 | makeToken(lex, tok, TOK_PIPE); 274 | break; 275 | case '&': 276 | makeToken(lex, tok, TOK_AMPER); 277 | break; 278 | case '~': 279 | makeToken(lex, tok, TOK_TILDE); 280 | break; 281 | case ',': 282 | makeToken(lex, tok, TOK_COMMA); 283 | break; 284 | case '[': 285 | makeToken(lex, tok, TOK_LSQUARE); 286 | break; 287 | case ']': 288 | makeToken(lex, tok, TOK_RSQUARE); 289 | break; 290 | case '{': 291 | makeToken(lex, tok, TOK_LCURLY); 292 | break; 293 | case '}': 294 | makeToken(lex, tok, TOK_RCURLY); 295 | break; 296 | case '^': 297 | makeToken(lex, tok, TOK_POW); 298 | break; 299 | case '@': 300 | makeToken(lex, tok, TOK_AT); 301 | break; 302 | case '\'': 303 | case '"': 304 | string(lex, c, tok); 305 | break; 306 | case '.': 307 | if(peekChar(lex) == '.' && peekChar2(lex) == '.') { 308 | advance(lex); 309 | advance(lex); 310 | makeToken(lex, tok, TOK_ELLIPSIS); 311 | } else { 312 | makeToken(lex, tok, TOK_DOT); 313 | } 314 | break; 315 | case '-': 316 | if(match(lex, '=')) 317 | makeToken(lex, tok, TOK_MINUS_EQ); 318 | else 319 | makeToken(lex, tok, TOK_MINUS); 320 | break; 321 | case '+': 322 | if(match(lex, '=')) 323 | makeToken(lex, tok, TOK_PLUS_EQ); 324 | else 325 | makeToken(lex, tok, TOK_PLUS); 326 | break; 327 | case '/': 328 | if(match(lex, '=')) 329 | makeToken(lex, tok, TOK_DIV_EQ); 330 | else 331 | makeToken(lex, tok, TOK_DIV); 332 | break; 333 | case '*': 334 | if(match(lex, '=')) 335 | makeToken(lex, tok, TOK_MULT_EQ); 336 | else 337 | makeToken(lex, tok, TOK_MULT); 338 | break; 339 | case '%': 340 | if(match(lex, '=')) 341 | makeToken(lex, tok, TOK_MOD_EQ); 342 | else 343 | makeToken(lex, tok, TOK_MOD); 344 | break; 345 | case '!': 346 | if(match(lex, '=')) 347 | makeToken(lex, tok, TOK_BANG_EQ); 348 | else 349 | makeToken(lex, tok, TOK_BANG); 350 | break; 351 | case '=': 352 | if(match(lex, '=')) 353 | makeToken(lex, tok, TOK_EQUAL_EQUAL); 354 | else if(match(lex, '>')) 355 | makeToken(lex, tok, TOK_ARROW); 356 | else 357 | makeToken(lex, tok, TOK_EQUAL); 358 | break; 359 | case '<': 360 | if(match(lex, '=')) 361 | makeToken(lex, tok, TOK_LE); 362 | else if(match(lex, '<')) 363 | makeToken(lex, tok, TOK_LSHIFT); 364 | else 365 | makeToken(lex, tok, TOK_LT); 366 | break; 367 | case '>': 368 | if(match(lex, '=')) 369 | makeToken(lex, tok, TOK_GE); 370 | else if(match(lex, '>')) 371 | makeToken(lex, tok, TOK_RSHIFT); 372 | else 373 | makeToken(lex, tok, TOK_GT); 374 | break; 375 | case '#': 376 | if(match(lex, '#')) 377 | makeToken(lex, tok, TOK_HASH_HASH); 378 | else 379 | makeToken(lex, tok, TOK_HASH); 380 | break; 381 | case '\n': 382 | makeToken(lex, tok, TOK_NEWLINE); 383 | lex->currLine++; 384 | break; 385 | default: 386 | makeToken(lex, tok, TOK_ERR); 387 | break; 388 | } 389 | } 390 | 391 | void jsrLexRewind(JStarLex* lex, JStarTok* tok) { 392 | if(tok->lexeme == NULL) return; 393 | lex->tokenStart = lex->current = tok->lexeme; 394 | lex->currLine = tok->line; 395 | } 396 | -------------------------------------------------------------------------------- /src/serialize.h: -------------------------------------------------------------------------------- 1 | #ifndef SERIALIZE_H 2 | #define SERIALIZE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "jstar.h" 8 | #include "object_types.h" 9 | 10 | JStarBuffer serialize(JStarVM* vm, ObjFunction* f); 11 | JStarResult deserialize(JStarVM* vm, ObjModule* mod, const void* code, size_t len, 12 | ObjFunction** out); 13 | bool isCompiledCode(const void* code, size_t len); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/special_methods.def: -------------------------------------------------------------------------------- 1 | SPECIAL_METHOD(METH_CTOR, "@construct") 2 | 3 | SPECIAL_METHOD(METH_ITER, "__iter__") 4 | SPECIAL_METHOD(METH_NEXT, "__next__") 5 | 6 | SPECIAL_METHOD(METH_ADD, "__add__") 7 | SPECIAL_METHOD(METH_SUB, "__sub__") 8 | SPECIAL_METHOD(METH_MUL, "__mul__") 9 | SPECIAL_METHOD(METH_DIV, "__div__") 10 | SPECIAL_METHOD(METH_MOD, "__mod__") 11 | SPECIAL_METHOD(METH_BAND, "__band__") 12 | SPECIAL_METHOD(METH_BOR, "__bor__") 13 | SPECIAL_METHOD(METH_XOR, "__xor__") 14 | SPECIAL_METHOD(METH_LSHFT, "__lshift__") 15 | SPECIAL_METHOD(METH_RSHFT, "__rshift__") 16 | 17 | SPECIAL_METHOD(METH_RADD, "__radd__") 18 | SPECIAL_METHOD(METH_RSUB, "__rsub__") 19 | SPECIAL_METHOD(METH_RMUL, "__rmul__") 20 | SPECIAL_METHOD(METH_RDIV, "__rdiv__") 21 | SPECIAL_METHOD(METH_RMOD, "__rmod__") 22 | SPECIAL_METHOD(METH_RBAND, "__rband__") 23 | SPECIAL_METHOD(METH_RBOR, "__rbor__") 24 | SPECIAL_METHOD(METH_RXOR, "__rxor__") 25 | SPECIAL_METHOD(METH_RLSHFT, "__rlshift__") 26 | SPECIAL_METHOD(METH_RRSHFT, "__rrshift__") 27 | 28 | SPECIAL_METHOD(METH_GET, "__get__") 29 | SPECIAL_METHOD(METH_SET, "__set__") 30 | 31 | SPECIAL_METHOD(METH_NEG, "__neg__") 32 | SPECIAL_METHOD(METH_INV, "__invert__") 33 | 34 | SPECIAL_METHOD(METH_EQ, "__eq__") 35 | SPECIAL_METHOD(METH_LT, "__lt__") 36 | SPECIAL_METHOD(METH_LE, "__le__") 37 | SPECIAL_METHOD(METH_GT, "__gt__") 38 | SPECIAL_METHOD(METH_GE, "__ge__") 39 | 40 | SPECIAL_METHOD(METH_POW, "__pow__") 41 | SPECIAL_METHOD(METH_RPOW, "__rpow__") 42 | 43 | #undef SPECIAL_METHOD 44 | -------------------------------------------------------------------------------- /src/symbol.h: -------------------------------------------------------------------------------- 1 | #ifndef SYMBOL_H 2 | #define SYMBOL_H 3 | 4 | #include 5 | 6 | #include "object_types.h" 7 | #include "value.h" 8 | 9 | // The type of a cached symbol. 10 | // It can be: 11 | // - `SYMBOL_METHOD`, for caching method's lookups. When in this state the `as.method` field is 12 | // valid and contains the resolved method's value. 13 | // - `SYMBOL_BOUND_METHOD`, for caching bound method's lookups. When in this state the `as.method` 14 | // field is valid and contains the resolved method's value. Used primarily to distinguish between 15 | // a regular method lookup and a bound method lookup, so we can instantiate a brand new bound 16 | // method when hitting the cache. 17 | // - `SYMBOL_FIELD`, for caching field's lookups. When in this state the `as.offset` field is valid 18 | // and contains the resolved field's offset inside the object. 19 | // - `SYMBOL_GLOBAL`, for caching global variable's lookups. When in this state the `as.offset` 20 | // field is valid and contains the resolved global variable's offset inside the module. 21 | typedef enum { 22 | SYMBOL_METHOD, 23 | SYMBOL_BOUND_METHOD, 24 | SYMBOL_FIELD, 25 | SYMBOL_GLOBAL, 26 | } SymbolType; 27 | 28 | // Symbol cache used to speed up method/field/global var lookups during VM evaluation. 29 | // It caches the result of a name resolution, so we don't have to look it up again. 30 | typedef struct SymbolCache { 31 | SymbolType type; // The type of the cached symbol 32 | Obj* key; // The key of the cached symbol. Used to invalidate the cache 33 | union { 34 | Value method; // The cached method 35 | size_t offset; // The offset of the cached field or global variable inside of its object 36 | } as; 37 | } SymbolCache; 38 | 39 | // A symbol pointing to a constant in the constant pool. 40 | // Includes a cache to speed up symbol lookups during VM evaluation. 41 | typedef struct Symbol { 42 | uint16_t constant; // The index of the constant in the constant pool 43 | SymbolCache cache; // The cache for the symbol's value 44 | } Symbol; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define ARRAY_DEF_CAP 8 9 | 10 | // Append an item to a dynamic array, resizing it if necessary, using gcAlloc 11 | #define ARRAY_GC_APPEND(vm, arr, count, cap, items, val) \ 12 | do { \ 13 | if((arr)->count >= (arr)->cap) { \ 14 | size_t oldCap = (arr)->cap; \ 15 | (arr)->cap = (arr)->cap == 0 ? ARRAY_DEF_CAP : (arr)->cap * 2; \ 16 | (arr)->items = gcAlloc(vm, (arr)->items, oldCap * sizeof(*(arr)->items), \ 17 | (arr)->cap * sizeof(*(arr)->items)); \ 18 | JSR_ASSERT((arr)->items, "Out of memory"); \ 19 | } \ 20 | (arr)->items[(arr)->count++] = (val); \ 21 | } while(0) 22 | 23 | // Append an item to a dynamic array, resizing it if necessary, using realloc 24 | #define ARRAY_APPEND(arr, count, cap, items, val) \ 25 | do { \ 26 | if((arr)->count >= (arr)->cap) { \ 27 | (arr)->cap = (arr)->cap == 0 ? ARRAY_DEF_CAP : (arr)->cap * 2; \ 28 | (arr)->items = realloc((arr)->items, (arr)->cap * sizeof(*(arr)->items)); \ 29 | JSR_ASSERT((arr)->items, "Out of memory"); \ 30 | } \ 31 | (arr)->items[(arr)->count++] = (val); \ 32 | } while(0) 33 | 34 | // Reinterprets the bits of the value `v` from type F to type T 35 | #define REINTERPRET_CAST(F, T, v) \ 36 | ((union { \ 37 | F from; \ 38 | T to; \ 39 | }){.from = (v)} \ 40 | .to) 41 | 42 | // Compute the approximate maximal length of an integral type in base 10 43 | // The computed value is an integer constant in order to permit stack buffer allocation 44 | #define STRLEN_FOR_INT(t) (((t) - 1 < 0) ? STRLEN_FOR_SIGNED(t) : STRLEN_FOR_UNSIGNED(t)) 45 | #define STRLEN_FOR_SIGNED(t) (STRLEN_FOR_UNSIGNED(t) + 1) 46 | #define STRLEN_FOR_UNSIGNED(t) (((((sizeof(t) * CHAR_BIT)) * 1233) >> 12) + 1) 47 | 48 | // Returns whether `num` has a valid integer representation 49 | #define HAS_INT_REPR(num) ((num) >= (double)INT64_MIN && (num) < -(double)INT64_MIN) 50 | 51 | // Utility function to hash arbitrary data 52 | static inline uint32_t hashBytes(const void* data, size_t length) { 53 | const char* str = data; 54 | uint32_t hash = 2166136261u; 55 | for(size_t i = 0; i < length; i++) { 56 | hash ^= str[i]; 57 | hash *= 16777619; 58 | } 59 | return hash; 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/value.c: -------------------------------------------------------------------------------- 1 | #include "value.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "object.h" 8 | #include "util.h" 9 | 10 | void initValueArray(ValueArray* a) { 11 | *a = (ValueArray){0}; 12 | } 13 | 14 | void freeValueArray(ValueArray* a) { 15 | free(a->arr); 16 | } 17 | 18 | int valueArrayAppend(ValueArray* a, Value v) { 19 | ARRAY_APPEND(a, size, capacity, arr, v); 20 | return a->size - 1; 21 | } 22 | 23 | void printValue(Value val) { 24 | if(IS_OBJ(val)) { 25 | printObj(AS_OBJ(val)); 26 | } else if(IS_BOOL(val)) { 27 | printf(AS_BOOL(val) ? "true" : "false"); 28 | } else if(IS_NUM(val)) { 29 | printf("%.*g", DBL_DIG, AS_NUM(val)); 30 | } else if(IS_HANDLE(val)) { 31 | printf("", AS_HANDLE(val)); 32 | } else if(IS_NULL(val)) { 33 | printf("null"); 34 | } 35 | } 36 | 37 | extern inline bool valueIsInt(Value v); 38 | extern inline bool valueEquals(Value v1, Value v2); 39 | extern inline bool valueToBool(Value v); 40 | -------------------------------------------------------------------------------- /src/value.h: -------------------------------------------------------------------------------- 1 | #ifndef VALUE_H 2 | #define VALUE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "conf.h" 9 | #include "object_types.h" // IWYU pragma: keep 10 | #include "util.h" 11 | 12 | /** 13 | * Here we define the Value type. This is a C type that can store any type 14 | * used by the J* VM. This closes the gap between the dinamically typed 15 | * world of J* and the static one of the C language. Every storage location 16 | * used in the VM is of type Value (for example the vm stack) as to permit 17 | * the storage of any J* variable. 18 | * 19 | * Note that even tough in J* all values are objects, primitive values 20 | * such as numbers, booleans and the null singleton are unboxed: they are stored 21 | * directly in the Value, instead of an Object on the heap. this saves allocations 22 | * and pointer dereferencing when working with such values. 23 | * 24 | * Two implementations of Value are supported: 25 | * - NaN tagging technique (explained below). More memory efficient. 26 | * - Tagged Union. The classic way to implement such a type. it requires more 27 | * than a word of memory to store a single value (tipically 2 words due to padding) 28 | */ 29 | 30 | #ifdef JSTAR_NAN_TAGGING 31 | 32 | /** 33 | * NaN Tagging technique. Instead of using a Tagged union (see below) for 34 | * implementing a type that can hold multiple C types, we use a single 64 35 | * bit integer and exploit the fact that a NaN IEEE double has 52 + 1 36 | * unused bits we can utilize. 37 | * 38 | * If Value doesn't have the NaN bits set, then it's a valid double. This is 39 | * convinient because we don't need extra manipulation to extract the double 40 | * from the value, we only reinterpret the bits. 41 | * 42 | * If the NaN bits and the sign bit are set, then the Value is an Object* 43 | * pointer. We stuff the 64 bit pointer into the 52 bit unused mantissa. 44 | * This is usally enough since operating systems allocate memory starting at 45 | * low adresses, thus leaving the most significant bits of an address at zero. 46 | * 47 | * If the NaN bits are set and the sign bit isn't, it's either a singleton Value 48 | * or an handle value. 49 | * If the two least significant bit of the mantissa aren't both 0, then it is a 50 | * singleton value. Here we use these two bits to differentiate between null (01), 51 | * false (10) and true (11). 52 | * Otherwise it's an Handle (a raw void* C pointer). Similar to the Object* case, 53 | * we stuff the void* pointer in the remaining bits (this time 50). 54 | * 55 | * Using this technique we can store all the needed values used by the J* VM into one 56 | * 64-bit integer, thus saving the extra space needed by the tag in the union (plus padding 57 | * bits). 58 | */ 59 | 60 | typedef uint64_t Value; 61 | 62 | // clang-format off 63 | 64 | #define SIGN ((uint64_t)1 << 63) // The sign bit 65 | #define QNAN ((uint64_t)0x7ffc000000000000) // Quiet NaN used for the NaN tagging technique 66 | 67 | // The last two bits of of a Value that has the NaN 68 | // bits set indentify its type, also called its `tag` 69 | enum Tag { 70 | HANDLE_BITS, // 00 71 | NULL_BITS, // 01 72 | FALSE_BITS, // 10 73 | TRUE_BITS, // 11 74 | END_BITS // End marker 75 | }; 76 | 77 | // Retrieve the tag bits of a Value 78 | #define BITS_MASK (END_BITS - 1) 79 | #define BITS_TAG(val) ((val) & BITS_MASK) 80 | 81 | // Value checking macros 82 | #define IS_NULL(val) ((val) == NULL_VAL) 83 | #define IS_NUM(val) (((val) & (QNAN)) != QNAN) 84 | #define IS_BOOL(val) (((val) & (FALSE_VAL)) == FALSE_VAL) 85 | #define IS_HANDLE(val) (((val) & (SIGN | TRUE_VAL)) == QNAN) 86 | #define IS_OBJ(val) (((val) & (QNAN | SIGN)) == (QNAN | SIGN)) 87 | #define IS_INT(val) valueIsInt(val) 88 | 89 | // Convert from Value to c type 90 | // These don't check the type of the value, one should use IS_* macros before these 91 | #define AS_BOOL(val) ((val) == TRUE_VAL) 92 | #define AS_HANDLE(val) ((void*)(uintptr_t)(((val) & ~QNAN) >> 2)) 93 | #define AS_OBJ(val) ((Obj*)(uintptr_t)((val) & ~(SIGN | QNAN))) 94 | #define AS_NUM(val) REINTERPRET_CAST(Value, double, val) 95 | 96 | // Convert from c type to Value 97 | #define TRUE_VAL ((Value)(QNAN | TRUE_BITS)) 98 | #define FALSE_VAL ((Value)(QNAN | FALSE_BITS)) 99 | #define NULL_VAL ((Value)(QNAN | NULL_BITS)) 100 | #define BOOL_VAL(b) ((b) ? TRUE_VAL : FALSE_VAL) 101 | #define HANDLE_VAL(h) ((Value)(QNAN | (uint64_t)((uintptr_t)(h) << 2))) 102 | #define OBJ_VAL(obj) ((Value)(SIGN | QNAN | (uint64_t)(uintptr_t)(obj))) 103 | #define NUM_VAL(num) REINTERPRET_CAST(double, Value, num) 104 | 105 | // clang-format on 106 | 107 | // Perform a raw equality test of two values 108 | inline bool valueEquals(Value v1, Value v2) { 109 | return IS_NUM(v1) && IS_NUM(v2) ? AS_NUM(v1) == AS_NUM(v2) : v1 == v2; 110 | } 111 | 112 | #else 113 | 114 | typedef enum ValueType { 115 | VAL_NUM, 116 | VAL_BOOL, 117 | VAL_OBJ, 118 | VAL_NULL, 119 | VAL_HANDLE, 120 | } ValueType; 121 | 122 | typedef struct Value { 123 | ValueType type; 124 | union { 125 | bool boolean; 126 | double num; 127 | void* handle; 128 | Obj* obj; 129 | } as; 130 | } Value; 131 | 132 | // clang-format off 133 | 134 | #define IS_HANDLE(val) ((val).type == VAL_HANDLE) 135 | #define IS_OBJ(val) ((val).type == VAL_OBJ) 136 | #define IS_BOOL(val) ((val).type == VAL_BOOL) 137 | #define IS_NUM(val) ((val).type == VAL_NUM) 138 | #define IS_NULL(val) ((val).type == VAL_NULL) 139 | #define IS_INT(val) valueIsInt(val) 140 | 141 | #define AS_HANDLE(val) ((val).as.handle) 142 | #define AS_BOOL(val) ((val).as.boolean) 143 | #define AS_NUM(val) ((val).as.num) 144 | #define AS_OBJ(val) ((val).as.obj) 145 | 146 | #define HANDLE_VAL(h) ((Value){VAL_HANDLE, .as = {.handle = h}}) 147 | #define NUM_VAL(n) ((Value){VAL_NUM, .as = {.num = n}}) 148 | #define BOOL_VAL(b) ((Value){VAL_BOOL, .as = {.boolean = b}}) 149 | #define OBJ_VAL(val) ((Value){VAL_OBJ, .as = {.obj = (Obj*)val}}) 150 | #define TRUE_VAL ((Value){VAL_BOOL, .as = {.boolean = true}}) 151 | #define FALSE_VAL ((Value){VAL_BOOL, .as = {.boolean = false}}) 152 | #define NULL_VAL ((Value){VAL_NULL, .as = {.num = 0}}) 153 | 154 | // clang-format on 155 | 156 | // Perform a raw equality test of two values 157 | inline bool valueEquals(Value v1, Value v2) { 158 | if(v1.type != v2.type) return false; 159 | 160 | switch(v1.type) { 161 | case VAL_NUM: 162 | return v1.as.num == v2.as.num; 163 | case VAL_BOOL: 164 | return v1.as.boolean == v2.as.boolean; 165 | case VAL_OBJ: 166 | return v1.as.obj == v2.as.obj; 167 | case VAL_HANDLE: 168 | return v1.as.handle == v2.as.handle; 169 | case VAL_NULL: 170 | return true; 171 | } 172 | 173 | return false; 174 | } 175 | 176 | #endif 177 | 178 | // Check wheter a Value is an integral Number 179 | inline bool valueIsInt(Value val) { 180 | if(!IS_NUM(val)) return false; 181 | double num = AS_NUM(val); 182 | return trunc(num) == num; 183 | } 184 | 185 | // Return the truth value of a Value 186 | inline bool valueToBool(Value v) { 187 | return IS_BOOL(v) ? AS_BOOL(v) : !IS_NULL(v); 188 | } 189 | 190 | // ----------------------------------------------------------------------------- 191 | // VALUE ARRAY 192 | // ----------------------------------------------------------------------------- 193 | 194 | typedef struct ValueArray { 195 | int capacity, size; 196 | Value* arr; 197 | } ValueArray; 198 | 199 | void initValueArray(ValueArray* a); 200 | void freeValueArray(ValueArray* a); 201 | int valueArrayAppend(ValueArray* a, Value v); 202 | 203 | void printValue(Value val); 204 | 205 | #endif 206 | -------------------------------------------------------------------------------- /src/value_hashtable.c: -------------------------------------------------------------------------------- 1 | #include "value_hashtable.h" 2 | 3 | #include 4 | 5 | #include "gc.h" 6 | #include "hashtable.h" 7 | #include "object.h" // IWYU pragma: keep 8 | #include "value.h" 9 | 10 | #define TOMB_MARKER NULL_VAL 11 | #define INVALID_VAL NULL_VAL 12 | #define IS_INVALID_VAL IS_NULL 13 | 14 | DEFINE_HASH_TABLE(Value, Value, TOMB_MARKER, INVALID_VAL, IS_INVALID_VAL, 2, 8) 15 | 16 | void reachValueHashTable(JStarVM* vm, const ValueHashTable* t) { 17 | if(t->entries == NULL) return; 18 | for(size_t i = 0; i <= t->sizeMask; i++) { 19 | ValueEntry* e = &t->entries[i]; 20 | reachObject(vm, (Obj*)e->key); 21 | reachValue(vm, e->value); 22 | } 23 | } 24 | 25 | void sweepStrings(ValueHashTable* t) { 26 | if(t->entries == NULL) return; 27 | for(size_t i = 0; i <= t->sizeMask; i++) { 28 | ValueEntry* e = &t->entries[i]; 29 | if(e->key && !e->key->base.reached) { 30 | *e = (ValueEntry){NULL, TOMB_MARKER}; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/value_hashtable.h: -------------------------------------------------------------------------------- 1 | #ifndef VALUE_HASH_TABLE_H 2 | #define VALUE_HASH_TABLE_H 3 | 4 | #include "hashtable.h" 5 | #include "jstar.h" 6 | #include "object_types.h" // IWYU pragma: keep 7 | #include "value.h" 8 | 9 | DECLARE_HASH_TABLE(Value, Value) 10 | 11 | void reachValueHashTable(JStarVM* vm, const ValueHashTable* t); 12 | void sweepStrings(ValueHashTable* t); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/vm.h: -------------------------------------------------------------------------------- 1 | #ifndef VM_H 2 | #define VM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "compiler.h" 10 | #include "jstar.h" 11 | #include "jstar_limits.h" 12 | #include "object.h" 13 | #include "object_types.h" 14 | #include "symbol.h" 15 | #include "value.h" 16 | #include "value_hashtable.h" 17 | 18 | // Enum encoding special method names needed at runtime 19 | // Mainly used for operator overloading 20 | typedef enum SpecialMethod { 21 | #define SPECIAL_METHOD(meth, _) meth, 22 | #include "special_methods.def" 23 | METH_SIZE, 24 | } SpecialMethod; 25 | 26 | // Struct that stores the info needed to 27 | // jump to handler code and to restore the 28 | // VM state when handling exceptions 29 | typedef struct Handler { 30 | enum { 31 | HANDLER_ENSURE, 32 | HANDLER_EXCEPT, 33 | } type; // The type of the handler block 34 | uint8_t* address; // The address of handler code 35 | Value* savedSp; // Saved stack state before the try block was enterd 36 | } Handler; 37 | 38 | // Stackframe of a function executing in 39 | // the virtual machine 40 | typedef struct Frame { 41 | uint8_t* ip; // Instruction pointer 42 | Value* stack; // Base of stack for current frame 43 | Obj* fn; // Function associated with the frame (ObjClosure or ObjNative) 44 | ObjGenerator* gen; // Generator of this frame (if any) 45 | Handler handlers[MAX_HANDLERS]; // Exception handlers 46 | uint8_t handlerCount; // Exception handlers count 47 | } Frame; 48 | 49 | // Represents a handle to a resolved method, field or global variable. 50 | // Internally it stores a cache of the symbol lookup. 51 | struct JStarSymbol { 52 | SymbolCache sym; 53 | JStarSymbol* next; 54 | JStarSymbol* prev; 55 | }; 56 | 57 | // The J* VM. This struct stores all the 58 | // state needed to execute J* code. 59 | struct JStarVM { 60 | // Built in classes 61 | ObjClass* clsClass; 62 | ObjClass* objClass; 63 | ObjClass* strClass; 64 | ObjClass* boolClass; 65 | ObjClass* lstClass; 66 | ObjClass* numClass; 67 | ObjClass* funClass; 68 | ObjClass* genClass; 69 | ObjClass* modClass; 70 | ObjClass* nullClass; 71 | ObjClass* stClass; 72 | ObjClass* tupClass; 73 | ObjClass* excClass; 74 | ObjClass* tableClass; 75 | ObjClass* udataClass; 76 | 77 | // Script arguments 78 | ObjList* argv; 79 | 80 | // The empty tuple (singleton) 81 | ObjTuple* emptyTup; 82 | 83 | // Current VM compiler (if any) 84 | Compiler* currCompiler; 85 | 86 | // Cached special method names needed at runtime 87 | ObjString* specialMethods[METH_SIZE]; 88 | 89 | // Cached strings for exception fields 90 | ObjString *excErr, *excTrace, *excCause; 91 | 92 | // Loaded modules 93 | ValueHashTable modules; 94 | 95 | // Current module and core module 96 | ObjModule *module, *core; 97 | 98 | // VM program stack and stack pointer 99 | size_t stackSz; 100 | Value *stack, *sp; 101 | 102 | // Frame stack 103 | Frame* frames; 104 | int frameSz, frameCount; 105 | 106 | // Number of reentrant calls made into the VM 107 | int reentrantCalls; 108 | 109 | // Stack used during native function calls 110 | Value* apiStack; 111 | 112 | // Constant string pool, for interned strings 113 | ValueHashTable stringPool; 114 | 115 | // Linked list of all open upvalues 116 | ObjUpvalue* upvalues; 117 | 118 | // Callback function to report errors 119 | JStarErrorCB errorCallback; 120 | 121 | // Callback used to resolve `import`s 122 | JStarImportCB importCallback; 123 | 124 | // If set, the VM will break the eval loop as soon as possible. 125 | // Can be set asynchronously by a signal handler 126 | volatile sig_atomic_t evalBreak; 127 | 128 | // Custom data associated with the VM 129 | void* customData; 130 | 131 | // Linked list of all created symbols 132 | JStarSymbol* symbols; 133 | 134 | #ifdef JSTAR_DBG_CACHE_STATS 135 | size_t cacheHits, cacheMisses; 136 | #endif 137 | 138 | // ---- Memory management ---- 139 | 140 | // Linked list of all allocated objects (used in 141 | // the sweep phase of GC to free unreached objects) 142 | Obj* objects; 143 | 144 | size_t allocated; // Bytes currently allocated 145 | size_t nextGC; // Bytes at which the next GC will be triggered 146 | int heapGrowRate; // Rate at which the heap will grow after a GC 147 | 148 | // Stack used to recursevely reach all the fields of reached objects 149 | Obj** reachedStack; 150 | size_t reachedCapacity, reachedCount; 151 | }; 152 | 153 | // ----------------------------------------------------------------------------- 154 | // VM API 155 | // ----------------------------------------------------------------------------- 156 | 157 | bool getValueField(JStarVM* vm, ObjString* name, SymbolCache* sym); 158 | bool setValueField(JStarVM* vm, ObjString* name, SymbolCache* sym); 159 | 160 | bool getValueSubscript(JStarVM* vm); 161 | bool setValueSubscript(JStarVM* vm); 162 | 163 | bool callValue(JStarVM* vm, Value callee, uint8_t argc); 164 | bool invokeValue(JStarVM* vm, ObjString* name, uint8_t argc, SymbolCache* sym); 165 | 166 | void setGlobalName(JStarVM* vm, ObjModule* mod, ObjString* name, SymbolCache* sym); 167 | bool getGlobalName(JStarVM* vm, ObjModule* mod, ObjString* name, SymbolCache* sym); 168 | 169 | void reserveStack(JStarVM* vm, size_t needed); 170 | 171 | bool runEval(JStarVM* vm, int evalDepth); 172 | bool unwindStack(JStarVM* vm, int depth); 173 | 174 | inline void push(JStarVM* vm, Value v) { 175 | *vm->sp++ = v; 176 | } 177 | 178 | inline Value pop(JStarVM* vm) { 179 | return *--vm->sp; 180 | } 181 | 182 | inline Value peek(const JStarVM* vm) { 183 | return vm->sp[-1]; 184 | } 185 | 186 | inline Value peek2(const JStarVM* vm) { 187 | return vm->sp[-2]; 188 | } 189 | 190 | inline Value peekn(const JStarVM* vm, int n) { 191 | return vm->sp[-(n + 1)]; 192 | } 193 | 194 | inline void swapStackSlots(JStarVM* vm, int a, int b) { 195 | Value tmp = vm->sp[a]; 196 | vm->sp[a] = vm->sp[b]; 197 | vm->sp[b] = tmp; 198 | } 199 | 200 | inline ObjClass* getClass(const JStarVM* vm, Value v) { 201 | #ifdef JSTAR_NAN_TAGGING 202 | if(IS_OBJ(v)) return AS_OBJ(v)->cls; 203 | if(IS_NUM(v)) return vm->numClass; 204 | 205 | switch(BITS_TAG(v)) { 206 | case HANDLE_BITS: 207 | case NULL_BITS: 208 | return vm->nullClass; 209 | case FALSE_BITS: 210 | case TRUE_BITS: 211 | return vm->boolClass; 212 | case END_BITS: 213 | JSR_UNREACHABLE(); 214 | } 215 | #else 216 | switch(v.type) { 217 | case VAL_NUM: 218 | return vm->numClass; 219 | case VAL_BOOL: 220 | return vm->boolClass; 221 | case VAL_OBJ: 222 | return AS_OBJ(v)->cls; 223 | case VAL_HANDLE: 224 | case VAL_NULL: 225 | return vm->nullClass; 226 | } 227 | #endif 228 | 229 | JSR_UNREACHABLE(); 230 | } 231 | 232 | inline bool isSubClass(const JStarVM* vm, ObjClass* sub, ObjClass* super) { 233 | for(ObjClass* c = sub; c != NULL; c = c->superCls) { 234 | if(c == super) { 235 | return true; 236 | } 237 | } 238 | return false; 239 | } 240 | 241 | inline bool isInstance(const JStarVM* vm, Value i, ObjClass* cls) { 242 | return isSubClass(vm, getClass(vm, i), cls); 243 | } 244 | 245 | #endif 246 | --------------------------------------------------------------------------------