├── .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 |
5 |
6 |
7 | 
8 | 
9 | 
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 |
--------------------------------------------------------------------------------