├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build.sh ├── include ├── runtime.h └── version.h ├── run_tests.sh ├── scripts ├── test.js ├── test.txt └── tests │ ├── console.test.js │ ├── process.test.js │ ├── runtime.test.js │ └── timers.test.js ├── setup.sh └── src ├── fs_api.c ├── http_api.c ├── js_bindings.c ├── jsc_engine.c ├── main.c ├── net_api.c └── uv_event_loop.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | scripts/results 3 | .vscode -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(jade) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | find_package(PkgConfig REQUIRED) 7 | pkg_check_modules(WEBKIT REQUIRED webkit2gtk-4.0) 8 | pkg_check_modules(LIBUV REQUIRED libuv) 9 | 10 | include_directories( 11 | ${WEBKIT_INCLUDE_DIRS} 12 | ${LIBUV_INCLUDE_DIRS} 13 | ${CMAKE_SOURCE_DIR}/include 14 | ) 15 | 16 | add_executable(jade 17 | src/jsc_engine.c 18 | src/uv_event_loop.c 19 | src/js_bindings.c 20 | src/fs_api.c 21 | src/net_api.c 22 | src/http_api.c 23 | src/main.c 24 | ) 25 | 26 | target_link_libraries(jade 27 | ${WEBKIT_LIBRARIES} 28 | ${LIBUV_LIBRARIES} 29 | ) 30 | 31 | # Linux specific configuration 32 | if(NOT APPLE) 33 | pkg_check_modules(WEBKIT REQUIRED webkit2gtk-4.0) 34 | endif() 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 dexter 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 | # Jade: Experimental JavaScript Runtime Foundation 2 | 3 | **Jade** is an educational JavaScript runtime implementation demonstrating core concepts used in production runtimes like Node.js and Bun. This project serves as a foundation for understanding low-level runtime construction using: 4 | 5 | - **JavaScriptCore (JSC)** from WebKit for JS execution 6 | - **libuv** for cross-platform asynchronous I/O 7 | - **C** for native bindings and system integration 8 | 9 | **Current State**: Basic prototype supporting core runtime features 10 | 11 | ## 📦 Installation 12 | 13 | ### Prerequisites 14 | 15 | #### Linux (Debian/Ubuntu) 16 | ```bash 17 | sudo apt update 18 | sudo apt install \ 19 | libwebkit2gtk-4.0-dev \ 20 | libuv1-dev \ 21 | cmake \ 22 | build-essential 23 | ``` 24 | 25 | #### macOS 26 | ```bash 27 | brew install cmake libuv 28 | xcode-select --install # For Xcode command line tools 29 | ``` 30 | 31 | ### Build from Source 32 | ```bash 33 | # Clone repository 34 | git clone https://github.com/dexter-xD/jade.git 35 | cd jade 36 | 37 | # Configure build 38 | mkdir build && cd build 39 | cmake .. -DCMAKE_BUILD_TYPE=Debug 40 | 41 | # Compile 42 | make 43 | 44 | # Verify build 45 | ./jade --version 46 | ``` 47 | 48 | ## 🏗️ Architecture Overview 49 | 50 | ### Core Components 51 | ``` 52 | ┌───────────────────────┐ 53 | │ JavaScript │ 54 | │ (User Code) │ 55 | └───────────┬───────────┘ 56 | │ 57 | ┌───────────▼───────────┐ ┌───────────────────┐ 58 | │ JavaScriptCore (JSC) │◄─▶│ System │ 59 | │ JS Execution │ │ APIs │ 60 | └───────────┬───────────┘ └───────────────────┘ 61 | │ ▲ 62 | ┌───────────▼───────────┐ │ 63 | │ libuv Event │◄────────┘ 64 | │ Loop │ 65 | └───────────────────────┘ 66 | ``` 67 | 68 | ### Component Details 69 | 70 | 1. **JSC Engine Layer** 71 | - Creates/manages JS contexts 72 | - Handles JS code parsing/execution 73 | - Manages JS/C value conversions 74 | 75 | 2. **libuv Event Loop** 76 | - Timer management (`setTimeout`) 77 | - Filesystem operations (planned) 78 | - Network I/O (planned) 79 | - Thread pool integration 80 | 81 | 3. **System API Bridge** 82 | - Console I/O implementation 83 | - Future: File system access 84 | - Future: Network interfaces 85 | 86 | ## 🛠️ Current Features 87 | 88 | ### Implemented 89 | - Core event loop infrastructure 90 | - Basic JS execution context 91 | - `console.log`/`console.error` bindings 92 | - `setTimeout` implementation 93 | - Memory-safe value passing between JS/C 94 | - Build system with CMake 95 | - HTTP Client with support for: 96 | - GET requests 97 | - POST requests with form data and JSON 98 | - PUT requests with form data and JSON 99 | - DELETE requests 100 | - Response parsing (status code, headers, body) 101 | - Error handling 102 | - Query parameters 103 | - Custom headers 104 | 105 | ### In Progress 106 | - Proper error propagation JS ↔ C 107 | - File system API stubs 108 | - Module resolution prototype 109 | 110 | ## 🚀 Usage 111 | 112 | ### Basic Execution 113 | ```bash 114 | ./build/jade path/to/script.js 115 | ``` 116 | 117 | ### Example Script 118 | ```javascript 119 | // HTTP Client Examples 120 | 121 | // GET request 122 | http.get('http://httpbin.org/get', (err, response) => { 123 | if (err) { 124 | console.error('Error:', err); 125 | return; 126 | } 127 | console.log('Status:', response.statusCode); 128 | console.log('Headers:', response.headers); 129 | console.log('Body:', response.body); 130 | }); 131 | 132 | // POST request with form data 133 | http.post('http://httpbin.org/post', 'key1=value1&key2=value2', (err, response) => { 134 | if (err) { 135 | console.error('Error:', err); 136 | return; 137 | } 138 | console.log('Response:', response.body); 139 | }); 140 | 141 | // POST request with JSON data 142 | http.post('http://httpbin.org/post', '{"name":"John","age":30}', (err, response) => { 143 | if (err) { 144 | console.error('Error:', err); 145 | return; 146 | } 147 | console.log('Response:', response.body); 148 | }); 149 | 150 | // PUT request 151 | http.put('http://httpbin.org/put', 'key1=value1&key2=value2', (err, response) => { 152 | if (err) { 153 | console.error('Error:', err); 154 | return; 155 | } 156 | console.log('Response:', response.body); 157 | }); 158 | 159 | // DELETE request 160 | http.delete('http://httpbin.org/delete', (err, response) => { 161 | if (err) { 162 | console.error('Error:', err); 163 | return; 164 | } 165 | console.log('Response:', response.body); 166 | }); 167 | ``` 168 | 169 | ### Current Limitations 170 | - No Promise support 171 | - Limited error handling 172 | - Single-file execution only 173 | - Basic memory management 174 | - No HTTPS support 175 | - No request timeout handling 176 | - No request retry mechanism 177 | - No request cancellation 178 | - No request streaming 179 | - No response streaming 180 | 181 | ## 🔬 Development Setup 182 | 183 | ### Test Suite 184 | ```bash 185 | # Run all tests 186 | cd build && ctest --output-on-failure 187 | 188 | # Specific test target 189 | ./test/runtime_tests 190 | ``` 191 | 192 | ### Test Coverage 193 | ```bash 194 | # Generate coverage report 195 | mkdir -p coverage 196 | gcovr --exclude tests/ --html-details coverage/report.html 197 | ``` 198 | 199 | ### Debug Build 200 | ```bash 201 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1 202 | make clean && make 203 | ``` 204 | 205 | ## 📝 Project Roadmap 206 | 207 | ### Phase 1: Core Foundation (Current) 208 | - [x] Basic JS execution context 209 | - [x] Event loop scaffolding 210 | - [x] Console API implementation 211 | - [x] HTTP Client implementation 212 | - [ ] File system API stubs 213 | 214 | ### Phase 2: Production Patterns 215 | - [ ] Error handling system 216 | - [ ] Memory management audits 217 | - [ ] Cross-platform testing 218 | - [ ] Benchmarking suite 219 | - [ ] HTTPS support 220 | - [ ] Request timeout handling 221 | - [ ] Request retry mechanism 222 | - [ ] Request cancellation 223 | - [ ] Request/response streaming 224 | 225 | ### Phase 3: Advanced Features 226 | - [ ] Promise integration 227 | - [ ] HTTP server prototype 228 | - [ ] WASM support exploration 229 | - [ ] Debugger protocol 230 | 231 | ## 🤝 Contribution Guide 232 | 233 | ### Ideal Contributions 234 | - Core runtime improvements 235 | - Additional system APIs 236 | - Test coverage expansion 237 | - Documentation improvements 238 | - Cross-platform fixes 239 | 240 | ### Workflow 241 | 1. Create issue for discussion 242 | 2. Fork repository 243 | 3. Use feature branch workflow: 244 | ```bash 245 | git checkout -b feat/features 246 | ``` 247 | 4. Follow coding standards: 248 | - C11 standard 249 | - 4-space indentation 250 | - Doxygen-style comments 251 | 5. Submit PR with: 252 | - Implementation details 253 | - Test cases 254 | - Documentation updates 255 | 256 | ## ⚠️ Known Issues 257 | 258 | | Issue | Workaround | Priority | 259 | |-------|------------|----------| 260 | | Memory leaks in timer callbacks | Manual cleanup in tests | High | 261 | | No Windows support | Use WSL/Linux VM | Medium | 262 | | Limited error messages | Check debug build output | Low | 263 | | No HTTPS support | Use HTTP only | Medium | 264 | | No request timeout | Implement in application | Low | 265 | 266 | ## 📚 Learning Resources 267 | 268 | ### Core Technologies 269 | - [JavaScriptCore Internals](https://webkit.org/docs/javascript/) 270 | - [libuv Design Overview](http://docs.libuv.org/en/v1.x/design.html) 271 | - [Node.js Architecture Guide](https://nodejs.org/en/docs/guides/) 272 | 273 | ### Related Projects 274 | - [Bun Runtime](https://bun.sh/docs) 275 | - [Deno Core](https://deno.land/manual/runtime) 276 | - [Node.js Bindings](https://nodejs.org/api/addons.html) 277 | 278 | 279 | --- 280 | 281 | **Jade** is maintained by [dexter](https://github.com/dexter-xD) as an educational resource for understanding low-level runtime development. Not affiliated with Node.js, Bun, or WebKit projects. 282 | 283 | --- 284 | 285 | ## Support 286 | 287 | If you find this project helpful, consider buying me a coffee! ☕ 288 | 289 | [![Buy Me a Coffee](https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png)](https://buymeacoffee.com/trish07) 290 | 291 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | build() { 4 | mkdir -p build 5 | cd build 6 | cmake .. 7 | make 8 | ./jade ../scripts/test.js 9 | cd .. 10 | } 11 | 12 | run() { 13 | if [ ! -d "build" ]; then 14 | echo "Build directory does not exist. Building project first." 15 | build 16 | fi 17 | 18 | cd build 19 | ./jade ../scripts/test.js 20 | cd .. 21 | } 22 | 23 | clean() { 24 | rm -rf build 25 | } 26 | 27 | if [ "$1" == "clean" ]; then 28 | clean 29 | echo "Build directory cleaned." 30 | elif [ "$1" == "run" ]; then 31 | run 32 | else 33 | build 34 | fi 35 | -------------------------------------------------------------------------------- /include/runtime.h: -------------------------------------------------------------------------------- 1 | /** 2 | * ===================================================================================== 3 | * 4 | * RUNTIME.H - Core Header for JavaScript Runtime (JSC + libuv Implementation) 5 | * 6 | * ===================================================================================== 7 | * 8 | * This file contains the core interface definitions for the JavaScript runtime built 9 | * with JavaScriptCore (JSC) and libuv. It serves as the central API hub connecting 10 | * the JavaScript engine, event loop, and system APIs. 11 | * 12 | * Key Components: 13 | * - JavaScript Engine: Manages JS context creation/execution using JSC 14 | * - Event Loop: libuv-based async I/O and timer handling 15 | * - System APIs: Bridge between native functionality and JS environment 16 | * 17 | * Dependencies: 18 | * - JavaScriptCore (WebKitGTK/macOS SDK) 19 | * - libuv v1.40+ 20 | * 21 | * Architecture: 22 | * ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 23 | * │ JS Engine │ ◄──►│ Event Loop │ ◄──►│ System APIs │ 24 | * └─────────────┘ └─────────────┘ └─────────────┘ 25 | * ▲ ▲ 26 | * └────── Interop ─────┘ 27 | * 28 | * Memory Management: 29 | * - Uses JSC's garbage collection with manual protection for persistent handles 30 | * - libuv handles cleanup via close callbacks 31 | * 32 | * ===================================================================================== 33 | */ 34 | 35 | #ifndef RUNTIME_H 36 | #define RUNTIME_H 37 | 38 | #include 39 | #include 40 | 41 | 42 | // ===================================================================================== 43 | // JAVASCRIPT ENGINE INTERFACE 44 | // ===================================================================================== 45 | 46 | /** 47 | * Creates a new JavaScript execution context with exposed system APIs. 48 | * @return Initialized JSC global context. 49 | */ 50 | JSGlobalContextRef create_js_context(); 51 | 52 | /** 53 | * Executes JavaScript code in the given context. 54 | * @param ctx Active JSC context. 55 | * @param script Null-terminated JS code string. 56 | */ 57 | void execute_js(JSGlobalContextRef ctx, const char* script); 58 | 59 | 60 | // ===================================================================================== 61 | // EVENT LOOP INTERFACE 62 | // ===================================================================================== 63 | 64 | /** 65 | * Global event loop instance. 66 | */ 67 | extern uv_loop_t* loop; 68 | 69 | /** 70 | * Initializes libuv's default event loop. 71 | */ 72 | void init_event_loop(); 73 | 74 | /** 75 | * Starts the event loop (blocks until all handles are closed). 76 | */ 77 | void run_event_loop(); 78 | 79 | 80 | // ===================================================================================== 81 | // TIMER API 82 | // ===================================================================================== 83 | 84 | /** 85 | * Global timer ID tracker. 86 | */ 87 | extern uint32_t next_timer_id; 88 | 89 | /** 90 | * Schedules a JS function to execute after a specified delay. 91 | * @param ctx JS context for callback execution. 92 | * @param callback JS function reference. 93 | * @param timeout Delay in milliseconds. 94 | */ 95 | void set_timeout(JSContextRef ctx, JSObjectRef callback, uint64_t timeout); 96 | 97 | /** 98 | * Cancels a scheduled timeout function. 99 | * @param ctx JS context for callback execution. 100 | * @param timer_id ID of the timeout to clear. 101 | */ 102 | void clear_timeout(JSContextRef ctx, uint32_t timer_id); 103 | 104 | /** 105 | * Schedules a JS function to execute repeatedly at a fixed interval. 106 | * @param ctx JS context for callback execution. 107 | * @param callback JS function reference. 108 | * @param interval Interval time in milliseconds. 109 | */ 110 | void set_interval(JSContextRef ctx, JSObjectRef callback, uint64_t interval); 111 | 112 | /** 113 | * Cancels a scheduled interval function. 114 | * @param ctx JS context for callback execution. 115 | * @param timer_id ID of the interval to clear. 116 | */ 117 | void clear_interval(JSContextRef ctx, uint32_t timer_id); 118 | 119 | /** 120 | * Asynchronous readFile function 121 | */ 122 | JSValueRef fs_read_file(JSContextRef ctx, JSObjectRef function, 123 | JSObjectRef thisObject, size_t argc, 124 | const JSValueRef args[], JSValueRef* exception); 125 | 126 | /** 127 | * Asynchronous writeFile function 128 | */ 129 | JSValueRef fs_write_file(JSContextRef ctx, JSObjectRef function, 130 | JSObjectRef thisObject, size_t argc, 131 | const JSValueRef args[], JSValueRef* exception); 132 | 133 | /** 134 | * Asynchronous exists function 135 | */ 136 | JSValueRef fs_exists(JSContextRef ctx, JSObjectRef function, 137 | JSObjectRef thisObject, size_t argc, 138 | const JSValueRef args[], JSValueRef* exception); 139 | 140 | 141 | 142 | /** 143 | * Creates a TCP server that listens for connections. 144 | */ 145 | JSValueRef net_create_server(JSContextRef ctx, JSObjectRef function, 146 | JSObjectRef thisObject, size_t argc, 147 | const JSValueRef args[], JSValueRef* exception); 148 | 149 | /** 150 | * Binds and starts the TCP server on a specified port. 151 | */ 152 | JSValueRef net_server_listen(JSContextRef ctx, JSObjectRef function, 153 | JSObjectRef thisObject, size_t argc, 154 | const JSValueRef args[], JSValueRef* exception); 155 | 156 | 157 | /** 158 | * Writes data to a connected client socket. 159 | */ 160 | JSValueRef client_write(JSContextRef ctx, JSObjectRef function, 161 | JSObjectRef thisObject, size_t argc, 162 | const JSValueRef args[], JSValueRef* exception); 163 | 164 | /** 165 | * Performs an HTTP GET request to the specified URL. 166 | */ 167 | JSValueRef http_get(JSContextRef ctx, JSObjectRef function, 168 | JSObjectRef thisObject, size_t argc, 169 | const JSValueRef args[], JSValueRef* exception); 170 | 171 | /** 172 | * Performs an HTTP POST request to the specified URL. 173 | */ 174 | JSValueRef http_post(JSContextRef ctx, JSObjectRef function, 175 | JSObjectRef thisObject, size_t argc, 176 | const JSValueRef args[], JSValueRef* exception); 177 | 178 | /** 179 | * Performs an HTTP PUT request to the specified URL. 180 | */ 181 | JSValueRef http_put(JSContextRef ctx, JSObjectRef function, 182 | JSObjectRef thisObject, size_t argc, 183 | const JSValueRef args[], JSValueRef* exception); 184 | 185 | /** 186 | * Performs an HTTP DELETE request to the specified URL. 187 | */ 188 | JSValueRef http_delete(JSContextRef ctx, JSObjectRef function, 189 | JSObjectRef thisObject, size_t argc, 190 | const JSValueRef args[], JSValueRef* exception); 191 | 192 | /** 193 | * Creates an HTTP server that listens on the given port. 194 | */ 195 | JSValueRef http_create_server(JSContextRef ctx, JSObjectRef function, 196 | JSObjectRef thisObject, size_t argc, 197 | const JSValueRef args[], JSValueRef* exception); 198 | 199 | /** 200 | * Starts the HTTP server on the given port. 201 | */ 202 | JSValueRef http_server_listen(JSContextRef ctx, JSObjectRef function, 203 | JSObjectRef thisObject, size_t argc, 204 | const JSValueRef args[], JSValueRef* exception); 205 | 206 | 207 | 208 | // ===================================================================================== 209 | // SYSTEM API INTERFACE 210 | // ===================================================================================== 211 | 212 | /** 213 | * Binds native system APIs to the JavaScript global scope. 214 | * @param ctx JS context to enhance with system APIs. 215 | */ 216 | void bind_js_native_apis(JSGlobalContextRef ctx); 217 | 218 | #endif // RUNTIME_H -------------------------------------------------------------------------------- /include/version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H 2 | #define VERSION_H 3 | 4 | #define RUNTIME_VERSION "0.1.0" 5 | #define RUNTIME_NAME "Jade" 6 | 7 | #endif -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RUNTIME="./build/jade" 4 | RESULTS_DIR="scripts/results" 5 | 6 | if [ ! -f "$RUNTIME" ]; then 7 | echo "Error: Runtime executable not found at $RUNTIME" 8 | echo "Please build the runtime first by running:" 9 | echo " cd build && cmake .. && make" 10 | exit 1 11 | fi 12 | 13 | if [ "$1" == "--clean" ]; then 14 | echo "Cleaning saved test results..." 15 | rm -rf "$RESULTS_DIR" 16 | echo "All test results have been deleted." 17 | exit 0 18 | fi 19 | 20 | mkdir -p "$RESULTS_DIR" 21 | 22 | strip_ansi() { 23 | sed 's/\x1B\[[0-9;]*[mK]//g' 24 | } 25 | 26 | 27 | run_test() { 28 | local test_name="$1" 29 | local test_script="$2" 30 | local output_file="$RESULTS_DIR/${test_name// /_}.txt" 31 | 32 | if [ "$MODE" == "save" ]; then 33 | echo "Saving test result: $test_name -> $output_file" 34 | echo "---------------------------------" > "$output_file" 35 | echo "Test: $test_name" >> "$output_file" 36 | echo "---------------------------------" >> "$output_file" 37 | 38 | "$RUNTIME" "$test_script" 2>&1 | strip_ansi >> "$output_file" 39 | 40 | if [ $? -eq 0 ]; then 41 | echo "TEST PASSED: $test_name" >> "$output_file" 42 | else 43 | echo "TEST FAILED: $test_name" >> "$output_file" 44 | fi 45 | echo "---------------------------------" >> "$output_file" 46 | else 47 | echo "Running test: $test_name" 48 | echo "---------------------------------" 49 | "$RUNTIME" "$test_script" 50 | if [ $? -eq 0 ]; then 51 | echo "TEST PASSED: $test_name" 52 | else 53 | echo "TEST FAILED: $test_name" 54 | fi 55 | echo "---------------------------------" 56 | echo "" 57 | fi 58 | } 59 | 60 | 61 | if [ "$1" == "--save" ]; then 62 | MODE="save" 63 | else 64 | MODE="show" 65 | fi 66 | 67 | 68 | run_test "Console API" "scripts/tests/console.test.js" 69 | run_test "Timers API" "scripts/tests/timers.test.js" 70 | run_test "Process API" "scripts/tests/process.test.js" 71 | run_test "Runtime Info" "scripts/tests/runtime.test.js" 72 | 73 | if [ "$MODE" == "save" ]; then 74 | echo "Saving process.exit test result..." 75 | "$RUNTIME" "scripts/tests/process.test.js" arg1 arg2 2>&1 | strip_ansi > "$RESULTS_DIR/process_exit_test.txt" 76 | EXIT_CODE=$? 77 | if [ $EXIT_CODE -eq 42 ]; then 78 | echo "PROCESS.EXIT TEST: PASSED" >> "$RESULTS_DIR/process_exit_test.txt" 79 | else 80 | echo "PROCESS.EXIT TEST: FAILED (Got $EXIT_CODE)" >> "$RESULTS_DIR/process_exit_test.txt" 81 | fi 82 | else 83 | echo "Testing process.exit (should exit with code 42)" 84 | "$RUNTIME" "scripts/tests/process.test.js" arg1 arg2 85 | EXIT_CODE=$? 86 | if [ $EXIT_CODE -eq 42 ]; then 87 | echo "PROCESS.EXIT TEST: PASSED" 88 | else 89 | echo "PROCESS.EXIT TEST: FAILED (Got $EXIT_CODE)" 90 | fi 91 | echo "---------------------------------" 92 | fi 93 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | const server = http.createServer((req, res) => { 2 | console.log(`Received ${req.method} request for ${req.url}`); 3 | res.end(); 4 | }); 5 | 6 | server.listen(8080); 7 | console.log("Server running on port 8080"); -------------------------------------------------------------------------------- /scripts/test.txt: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /scripts/tests/console.test.js: -------------------------------------------------------------------------------- 1 | // Test console API 2 | console.log("LOG TEST: Basic log message"); 3 | console.warn("LOG TEST: Warning message"); 4 | console.info("LOG TEST: Info message"); 5 | console.debug("LOG TEST: Debug message"); 6 | console.error("LOG TEST: Error message"); 7 | -------------------------------------------------------------------------------- /scripts/tests/process.test.js: -------------------------------------------------------------------------------- 1 | // Test process.argv 2 | console.log("PROCESS TEST: Arguments:", process.argv); 3 | 4 | // Test process.exit 5 | setTimeout(() => { 6 | console.log("PROCESS TEST: Exiting with code 42"); 7 | process.exit(42); 8 | }, 1000); -------------------------------------------------------------------------------- /scripts/tests/runtime.test.js: -------------------------------------------------------------------------------- 1 | // Test runtime version 2 | console.log(`RUNTIME TEST: ${runtime.name} v${runtime.version}`); -------------------------------------------------------------------------------- /scripts/tests/timers.test.js: -------------------------------------------------------------------------------- 1 | // Test setTimeout/clearTimeout 2 | let timeoutRan = false; 3 | const timeoutId = setTimeout(() => { 4 | timeoutRan = true; 5 | console.log("TIMER TEST: Timeout executed"); 6 | }, 500); 7 | clearTimeout(timeoutId); 8 | 9 | // Test setInterval/clearInterval 10 | let intervalCount = 0; 11 | const intervalId = setInterval(() => { 12 | intervalCount++; 13 | console.log(`TIMER TEST: Interval ${intervalCount}`); 14 | if (intervalCount >= 3) { 15 | clearInterval(intervalId); 16 | console.log("TIMER TEST: Interval cleared"); 17 | } 18 | }, 200); -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | install_linux_deps() { 4 | echo "Installing dependencies for Linux..." 5 | 6 | sudo apt update 7 | sudo apt install -y cmake pkg-config libwebkit2gtk-4.0-dev libuv1-dev 8 | 9 | echo "Linux dependencies installed." 10 | } 11 | 12 | install_mac_deps() { 13 | echo "Installing dependencies for macOS..." 14 | 15 | if ! command -v brew &>/dev/null; then 16 | echo "Homebrew not found. Installing..." 17 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 18 | fi 19 | 20 | brew install cmake pkg-config webkit2gtk libuv 21 | 22 | echo "macOS dependencies installed." 23 | } 24 | 25 | configure_for_mac() { 26 | echo "Configuring CMakeLists.txt for macOS..." 27 | 28 | echo -e "\n# macOS specific configuration" >> CMakeLists.txt 29 | echo 'if(APPLE)' >> CMakeLists.txt 30 | echo ' target_link_libraries(jade' >> CMakeLists.txt 31 | echo ' ${WEBKIT_LIBRARIES}' >> CMakeLists.txt 32 | echo ' ${LIBUV_LIBRARIES}' >> CMakeLists.txt 33 | echo ' "-framework JavaScriptCore"' >> CMakeLists.txt 34 | echo 'else()' >> CMakeLists.txt 35 | echo ' target_link_libraries(jade' >> CMakeLists.txt 36 | echo ' ${WEBKIT_LIBRARIES}' >> CMakeLists.txt 37 | echo ' ${LIBUV_LIBRARIES}' >> CMakeLists.txt 38 | echo ' )' >> CMakeLists.txt 39 | echo 'endif()' >> CMakeLists.txt 40 | 41 | echo "CMakeLists.txt configured for macOS." 42 | } 43 | 44 | configure_for_linux() { 45 | echo "Configuring CMakeLists.txt for Linux..." 46 | 47 | echo -e "\n# Linux specific configuration" >> CMakeLists.txt 48 | echo 'if(NOT APPLE)' >> CMakeLists.txt 49 | echo ' pkg_check_modules(WEBKIT REQUIRED webkit2gtk-4.0)' >> CMakeLists.txt 50 | echo 'endif()' >> CMakeLists.txt 51 | 52 | echo "CMakeLists.txt configured for Linux." 53 | } 54 | 55 | 56 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then 57 | install_linux_deps 58 | configure_for_linux 59 | elif [[ "$OSTYPE" == "darwin"* ]]; then 60 | install_mac_deps 61 | configure_for_mac 62 | else 63 | echo "Unsupported operating system: $OSTYPE" 64 | exit 1 65 | fi 66 | 67 | echo "Setup complete! You can now build the project using ./build.sh." 68 | -------------------------------------------------------------------------------- /src/fs_api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "runtime.h" 7 | 8 | // File Read Request Structure 9 | typedef struct { 10 | uv_fs_t req; 11 | uv_file file; 12 | JSContextRef ctx; 13 | JSObjectRef callback; 14 | uv_buf_t buffer; 15 | } FileReadRequest; 16 | 17 | // File Write Request Structure 18 | typedef struct { 19 | uv_fs_t req; 20 | uv_file file; 21 | JSContextRef ctx; 22 | JSObjectRef callback; 23 | uv_buf_t buffer; 24 | } FileWriteRequest; 25 | 26 | // File Existence Check Request Structure 27 | typedef struct { 28 | uv_fs_t req; 29 | JSContextRef ctx; 30 | JSObjectRef callback; 31 | } FileExistsRequest; 32 | 33 | 34 | // Read Callback Function 35 | void on_file_read(uv_fs_t* req) { 36 | FileReadRequest* fr = (FileReadRequest*)req->data; 37 | uv_fs_req_cleanup(req); 38 | 39 | // Ensure valid request 40 | if (!fr) { 41 | fprintf(stderr, "Error: FileReadRequest is NULL\n"); 42 | return; 43 | } 44 | 45 | // Handle read errors 46 | if (req->result < 0) { 47 | fprintf(stderr, "fs.readFile() error: %s\n", uv_strerror(req->result)); 48 | JSStringRef errMsg = JSStringCreateWithUTF8CString(uv_strerror(req->result)); 49 | JSValueRef args[] = { JSValueMakeString(fr->ctx, errMsg), JSValueMakeNull(fr->ctx) }; 50 | JSObjectCallAsFunction(fr->ctx, fr->callback, NULL, 2, args, NULL); 51 | JSStringRelease(errMsg); 52 | } else { 53 | // Convert buffer to JS string 54 | fr->buffer.base[req->result] = '\0'; // Ensure null termination 55 | JSStringRef fileData = JSStringCreateWithUTF8CString(fr->buffer.base); 56 | JSValueRef args[] = { JSValueMakeNull(fr->ctx), JSValueMakeString(fr->ctx, fileData) }; 57 | JSObjectCallAsFunction(fr->ctx, fr->callback, NULL, 2, args, NULL); 58 | JSStringRelease(fileData); 59 | } 60 | 61 | // Cleanup 62 | uv_fs_close(uv_default_loop(), &fr->req, fr->file, NULL); 63 | JSValueUnprotect(fr->ctx, fr->callback); 64 | free(fr->buffer.base); 65 | free(fr); 66 | } 67 | 68 | // Open File Callback 69 | void on_file_open(uv_fs_t* req) { 70 | FileReadRequest* fr = (FileReadRequest*)req->data; 71 | 72 | if (!fr) { 73 | fprintf(stderr, "Error: FileReadRequest is NULL in on_file_open()\n"); 74 | return; 75 | } 76 | 77 | if (req->result < 0) { 78 | // Print error and return error to callback 79 | fprintf(stderr, "fs.readFile() error: %s\n", uv_strerror(req->result)); 80 | JSStringRef errMsg = JSStringCreateWithUTF8CString(uv_strerror(req->result)); 81 | JSValueRef args[] = { JSValueMakeString(fr->ctx, errMsg), JSValueMakeNull(fr->ctx) }; 82 | JSObjectCallAsFunction(fr->ctx, fr->callback, NULL, 2, args, NULL); 83 | JSStringRelease(errMsg); 84 | JSValueUnprotect(fr->ctx, fr->callback); 85 | free(fr); 86 | return; 87 | } 88 | 89 | fr->file = req->result; 90 | 91 | // Allocate buffer 92 | fr->buffer = uv_buf_init((char*)malloc(1024), 1024); 93 | if (!fr->buffer.base) { 94 | fprintf(stderr, "Memory allocation failed\n"); 95 | uv_fs_close(uv_default_loop(), &fr->req, fr->file, NULL); 96 | JSValueUnprotect(fr->ctx, fr->callback); 97 | free(fr); 98 | return; 99 | } 100 | 101 | // Read file asynchronously 102 | uv_fs_read(uv_default_loop(), &fr->req, fr->file, &fr->buffer, 1, 0, on_file_read); 103 | } 104 | 105 | // `fs.readFile(path, callback)` 106 | JSValueRef fs_read_file(JSContextRef ctx, JSObjectRef function, 107 | JSObjectRef thisObject, size_t argc, 108 | const JSValueRef args[], JSValueRef* exception) { 109 | if (argc < 2) { 110 | JSStringRef errMsg = JSStringCreateWithUTF8CString("fs.readFile requires a path and callback"); 111 | *exception = JSValueMakeString(ctx, errMsg); 112 | JSStringRelease(errMsg); 113 | return JSValueMakeUndefined(ctx); 114 | } 115 | 116 | // Convert JS string (path) 117 | JSStringRef pathRef = JSValueToStringCopy(ctx, args[0], exception); 118 | size_t pathLen = JSStringGetMaximumUTF8CStringSize(pathRef); 119 | char* path = (char*)malloc(pathLen); 120 | if (!path) { 121 | JSStringRelease(pathRef); 122 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Memory allocation failed"); 123 | *exception = JSValueMakeString(ctx, errMsg); 124 | JSStringRelease(errMsg); 125 | return JSValueMakeUndefined(ctx); 126 | } 127 | JSStringGetUTF8CString(pathRef, path, pathLen); 128 | JSStringRelease(pathRef); 129 | 130 | // Debug print 131 | printf("Opening file: %s\n", path); 132 | 133 | // Ensure callback is a function 134 | if (!JSValueIsObject(ctx, args[1]) || !JSObjectIsFunction(ctx, (JSObjectRef)args[1])) { 135 | free(path); 136 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Second argument must be a function"); 137 | *exception = JSValueMakeString(ctx, errMsg); 138 | JSStringRelease(errMsg); 139 | return JSValueMakeUndefined(ctx); 140 | } 141 | 142 | // Create File Read Request 143 | FileReadRequest* fr = (FileReadRequest*)malloc(sizeof(FileReadRequest)); 144 | if (!fr) { 145 | free(path); 146 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Memory allocation failed"); 147 | *exception = JSValueMakeString(ctx, errMsg); 148 | JSStringRelease(errMsg); 149 | return JSValueMakeUndefined(ctx); 150 | } 151 | 152 | fr->ctx = ctx; 153 | fr->callback = (JSObjectRef)args[1]; 154 | JSValueProtect(ctx, fr->callback); 155 | 156 | // Open File Asynchronously 157 | uv_fs_open(uv_default_loop(), &fr->req, path, O_RDONLY, 0, on_file_open); 158 | fr->req.data = fr; 159 | free(path); 160 | 161 | return JSValueMakeUndefined(ctx); 162 | } 163 | 164 | 165 | // Write Callback Function 166 | void on_file_write(uv_fs_t* req) { 167 | FileWriteRequest* fw = (FileWriteRequest*)req->data; 168 | uv_fs_req_cleanup(req); 169 | 170 | // Ensure valid request 171 | if (!fw) { 172 | fprintf(stderr, "Error: FileWriteRequest is NULL\n"); 173 | return; 174 | } 175 | 176 | // Handle write errors 177 | if (req->result < 0) { 178 | fprintf(stderr, "fs.writeFile() error: %s\n", uv_strerror(req->result)); 179 | JSStringRef errMsg = JSStringCreateWithUTF8CString(uv_strerror(req->result)); 180 | JSValueRef args[] = { JSValueMakeString(fw->ctx, errMsg) }; 181 | JSObjectCallAsFunction(fw->ctx, fw->callback, NULL, 1, args, NULL); 182 | JSStringRelease(errMsg); 183 | } else { 184 | // Call JS callback with no error 185 | JSValueRef args[] = { JSValueMakeNull(fw->ctx) }; 186 | JSObjectCallAsFunction(fw->ctx, fw->callback, NULL, 1, args, NULL); 187 | } 188 | 189 | // Cleanup 190 | uv_fs_close(uv_default_loop(), &fw->req, fw->file, NULL); 191 | JSValueUnprotect(fw->ctx, fw->callback); 192 | free(fw->buffer.base); 193 | free(fw); 194 | } 195 | 196 | // Open File Callback 197 | void on_file_open_write(uv_fs_t* req) { 198 | FileWriteRequest* fw = (FileWriteRequest*)req->data; 199 | 200 | if (!fw) { 201 | fprintf(stderr, "Error: FileWriteRequest is NULL in on_file_open_write()\n"); 202 | return; 203 | } 204 | 205 | if (req->result < 0) { 206 | // Print error and return error to callback 207 | fprintf(stderr, "fs.writeFile() error: %s\n", uv_strerror(req->result)); 208 | JSStringRef errMsg = JSStringCreateWithUTF8CString(uv_strerror(req->result)); 209 | JSValueRef args[] = { JSValueMakeString(fw->ctx, errMsg) }; 210 | JSObjectCallAsFunction(fw->ctx, fw->callback, NULL, 1, args, NULL); 211 | JSStringRelease(errMsg); 212 | JSValueUnprotect(fw->ctx, fw->callback); 213 | free(fw); 214 | return; 215 | } 216 | 217 | fw->file = req->result; 218 | 219 | // Write file asynchronously 220 | uv_fs_write(uv_default_loop(), &fw->req, fw->file, &fw->buffer, 1, 0, on_file_write); 221 | } 222 | 223 | // `fs.writeFile(path, content, callback)` 224 | JSValueRef fs_write_file(JSContextRef ctx, JSObjectRef function, 225 | JSObjectRef thisObject, size_t argc, 226 | const JSValueRef args[], JSValueRef* exception) { 227 | if (argc < 3) { 228 | JSStringRef errMsg = JSStringCreateWithUTF8CString("fs.writeFile requires a path, content, and callback"); 229 | *exception = JSValueMakeString(ctx, errMsg); 230 | JSStringRelease(errMsg); 231 | return JSValueMakeUndefined(ctx); 232 | } 233 | 234 | // Convert JS string (path) 235 | JSStringRef pathRef = JSValueToStringCopy(ctx, args[0], exception); 236 | size_t pathLen = JSStringGetMaximumUTF8CStringSize(pathRef); 237 | char* path = (char*)malloc(pathLen); 238 | if (!path) { 239 | JSStringRelease(pathRef); 240 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Memory allocation failed"); 241 | *exception = JSValueMakeString(ctx, errMsg); 242 | JSStringRelease(errMsg); 243 | return JSValueMakeUndefined(ctx); 244 | } 245 | JSStringGetUTF8CString(pathRef, path, pathLen); 246 | JSStringRelease(pathRef); 247 | 248 | // Convert JS string (content) 249 | JSStringRef contentRef = JSValueToStringCopy(ctx, args[1], exception); 250 | size_t contentLen = JSStringGetMaximumUTF8CStringSize(contentRef); 251 | char* content = (char*)malloc(contentLen); 252 | if (!content) { 253 | free(path); 254 | JSStringRelease(contentRef); 255 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Memory allocation failed"); 256 | *exception = JSValueMakeString(ctx, errMsg); 257 | JSStringRelease(errMsg); 258 | return JSValueMakeUndefined(ctx); 259 | } 260 | JSStringGetUTF8CString(contentRef, content, contentLen); 261 | JSStringRelease(contentRef); 262 | 263 | // Ensure callback is a function 264 | if (!JSValueIsObject(ctx, args[2]) || !JSObjectIsFunction(ctx, (JSObjectRef)args[2])) { 265 | free(path); 266 | free(content); 267 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Third argument must be a function"); 268 | *exception = JSValueMakeString(ctx, errMsg); 269 | JSStringRelease(errMsg); 270 | return JSValueMakeUndefined(ctx); 271 | } 272 | 273 | // Create File Write Request 274 | FileWriteRequest* fw = (FileWriteRequest*)malloc(sizeof(FileWriteRequest)); 275 | if (!fw) { 276 | free(path); 277 | free(content); 278 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Memory allocation failed"); 279 | *exception = JSValueMakeString(ctx, errMsg); 280 | JSStringRelease(errMsg); 281 | return JSValueMakeUndefined(ctx); 282 | } 283 | 284 | fw->ctx = ctx; 285 | fw->callback = (JSObjectRef)args[2]; 286 | JSValueProtect(ctx, fw->callback); 287 | fw->buffer = uv_buf_init(content, strlen(content)); 288 | 289 | // Open File Asynchronously (Create/Truncate mode) 290 | uv_fs_open(uv_default_loop(), &fw->req, path, O_WRONLY | O_CREAT | O_TRUNC, 0644, on_file_open_write); 291 | fw->req.data = fw; 292 | free(path); 293 | 294 | return JSValueMakeUndefined(ctx); 295 | } 296 | 297 | // Stat Callback Function 298 | void on_file_stat(uv_fs_t* req) { 299 | FileExistsRequest* fe = (FileExistsRequest*)req->data; 300 | uv_fs_req_cleanup(req); 301 | 302 | if (!fe) { 303 | fprintf(stderr, "Error: FileExistsRequest is NULL\n"); 304 | return; 305 | } 306 | 307 | JSValueRef args[2]; 308 | 309 | if (req->result < 0) { 310 | // File does not exist 311 | args[0] = JSValueMakeNull(fe->ctx); 312 | args[1] = JSValueMakeBoolean(fe->ctx, false); 313 | } else { 314 | // File exists 315 | args[0] = JSValueMakeNull(fe->ctx); 316 | args[1] = JSValueMakeBoolean(fe->ctx, true); 317 | } 318 | 319 | // Call the JavaScript callback 320 | JSObjectCallAsFunction(fe->ctx, fe->callback, NULL, 2, args, NULL); 321 | 322 | // Cleanup 323 | JSValueUnprotect(fe->ctx, fe->callback); 324 | free(fe); 325 | } 326 | 327 | // `fs.exists(path, callback)` 328 | JSValueRef fs_exists(JSContextRef ctx, JSObjectRef function, 329 | JSObjectRef thisObject, size_t argc, 330 | const JSValueRef args[], JSValueRef* exception) { 331 | if (argc < 2) { 332 | JSStringRef errMsg = JSStringCreateWithUTF8CString("fs.exists requires a path and callback"); 333 | *exception = JSValueMakeString(ctx, errMsg); 334 | JSStringRelease(errMsg); 335 | return JSValueMakeUndefined(ctx); 336 | } 337 | 338 | // Convert JS string (path) 339 | JSStringRef pathRef = JSValueToStringCopy(ctx, args[0], exception); 340 | size_t pathLen = JSStringGetMaximumUTF8CStringSize(pathRef); 341 | char* path = (char*)malloc(pathLen); 342 | if (!path) { 343 | JSStringRelease(pathRef); 344 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Memory allocation failed"); 345 | *exception = JSValueMakeString(ctx, errMsg); 346 | JSStringRelease(errMsg); 347 | return JSValueMakeUndefined(ctx); 348 | } 349 | JSStringGetUTF8CString(pathRef, path, pathLen); 350 | JSStringRelease(pathRef); 351 | 352 | // Ensure callback is a function 353 | if (!JSValueIsObject(ctx, args[1]) || !JSObjectIsFunction(ctx, (JSObjectRef)args[1])) { 354 | free(path); 355 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Second argument must be a function"); 356 | *exception = JSValueMakeString(ctx, errMsg); 357 | JSStringRelease(errMsg); 358 | return JSValueMakeUndefined(ctx); 359 | } 360 | 361 | // Create File Exists Request 362 | FileExistsRequest* fe = (FileExistsRequest*)malloc(sizeof(FileExistsRequest)); 363 | if (!fe) { 364 | free(path); 365 | JSStringRef errMsg = JSStringCreateWithUTF8CString("Memory allocation failed"); 366 | *exception = JSValueMakeString(ctx, errMsg); 367 | JSStringRelease(errMsg); 368 | return JSValueMakeUndefined(ctx); 369 | } 370 | 371 | fe->ctx = ctx; 372 | fe->callback = (JSObjectRef)args[1]; 373 | JSValueProtect(ctx, fe->callback); 374 | 375 | // Check File Existence Asynchronously 376 | uv_fs_stat(uv_default_loop(), &fe->req, path, on_file_stat); 377 | fe->req.data = fe; 378 | free(path); 379 | 380 | return JSValueMakeUndefined(ctx); 381 | } 382 | -------------------------------------------------------------------------------- /src/http_api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "runtime.h" 8 | 9 | // ========================= HTTP CLIENT (http.get) ========================= // 10 | 11 | typedef struct { 12 | uv_tcp_t socket; 13 | JSContextRef ctx; 14 | JSObjectRef callback; 15 | char* host; 16 | char* path; 17 | char* response_data; 18 | size_t response_size; 19 | size_t response_len; 20 | int status_code; 21 | char* response_headers; 22 | size_t headers_size; 23 | size_t headers_len; 24 | bool headers_parsed; 25 | char* response_body; 26 | size_t body_size; 27 | size_t body_len; 28 | char* request_data; // Added for POST data 29 | size_t request_data_len; // Added for POST data length 30 | const char* method; // Added for HTTP method 31 | } HttpRequest; 32 | 33 | // Forward declarations 34 | void on_write_complete(uv_write_t* req, int status); 35 | void on_http_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf); 36 | void on_http_connect(uv_connect_t* req, int status); 37 | void on_dns_resolved(uv_getaddrinfo_t* resolver, int status, struct addrinfo* res); 38 | JSValueRef res_end(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, 39 | size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); 40 | 41 | // Write callback for uv_write 42 | void on_write_complete(uv_write_t* req, int status) { 43 | free(req->data); // Free the buffer data 44 | free(req); // Free the write request 45 | } 46 | 47 | // Allocate Buffer 48 | void on_http_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { 49 | buf->base = (char*)malloc(suggested_size); 50 | buf->len = suggested_size; 51 | } 52 | 53 | // Read Callback 54 | void on_http_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { 55 | HttpRequest* req = (HttpRequest*)stream->data; 56 | 57 | if (nread > 0) { 58 | // Append to response data 59 | req->response_data = realloc(req->response_data, req->response_size + nread + 1); 60 | memcpy(req->response_data + req->response_size, buf->base, nread); 61 | req->response_size += nread; 62 | req->response_data[req->response_size] = '\0'; 63 | 64 | // Parse response if headers haven't been parsed yet 65 | if (!req->headers_parsed) { 66 | char* header_end = strstr(req->response_data, "\r\n\r\n"); 67 | if (header_end) { 68 | // Calculate header length 69 | size_t header_len = header_end - req->response_data; 70 | 71 | // Store headers 72 | req->response_headers = malloc(header_len + 1); 73 | memcpy(req->response_headers, req->response_data, header_len); 74 | req->response_headers[header_len] = '\0'; 75 | req->headers_size = header_len; 76 | req->headers_len = header_len; 77 | 78 | // Parse status code 79 | char* status_line = strtok(req->response_headers, "\r\n"); 80 | if (status_line) { 81 | sscanf(status_line, "HTTP/1.1 %d", &req->status_code); 82 | } 83 | 84 | // Store body 85 | size_t body_start = header_len + 4; // Skip \r\n\r\n 86 | size_t body_len = req->response_size - body_start; 87 | if (body_len > 0) { // Only allocate if there's a body 88 | req->response_body = malloc(body_len + 1); 89 | memcpy(req->response_body, req->response_data + body_start, body_len); 90 | req->response_body[body_len] = '\0'; 91 | req->body_size = body_len; 92 | req->body_len = body_len; 93 | } else { 94 | req->response_body = strdup("{}"); // Empty JSON object for empty body 95 | req->body_size = 2; 96 | req->body_len = 2; 97 | } 98 | 99 | req->headers_parsed = true; 100 | } 101 | } 102 | } else { 103 | // Create response object 104 | JSObjectRef responseObj = JSObjectMake(req->ctx, NULL, NULL); 105 | 106 | // Add status code 107 | JSStringRef statusName = JSStringCreateWithUTF8CString("statusCode"); 108 | JSObjectSetProperty(req->ctx, responseObj, statusName, 109 | JSValueMakeNumber(req->ctx, req->status_code), 110 | kJSPropertyAttributeNone, NULL); 111 | JSStringRelease(statusName); 112 | 113 | // Add headers 114 | JSObjectRef headersObj = JSObjectMake(req->ctx, NULL, NULL); 115 | if (req->response_headers) { 116 | char* header_line = strtok(req->response_headers, "\r\n"); 117 | while (header_line) { 118 | char* colon = strchr(header_line, ':'); 119 | if (colon) { 120 | *colon = '\0'; 121 | char* value = colon + 1; 122 | while (*value == ' ') value++; // Skip leading spaces 123 | 124 | JSStringRef headerName = JSStringCreateWithUTF8CString(header_line); 125 | JSStringRef headerValue = JSStringCreateWithUTF8CString(value); 126 | JSObjectSetProperty(req->ctx, headersObj, headerName, 127 | JSValueMakeString(req->ctx, headerValue), 128 | kJSPropertyAttributeNone, NULL); 129 | JSStringRelease(headerName); 130 | JSStringRelease(headerValue); 131 | } 132 | header_line = strtok(NULL, "\r\n"); 133 | } 134 | } 135 | JSStringRef headersName = JSStringCreateWithUTF8CString("headers"); 136 | JSObjectSetProperty(req->ctx, responseObj, headersName, 137 | JSValueToObject(req->ctx, headersObj, NULL), 138 | kJSPropertyAttributeNone, NULL); 139 | JSStringRelease(headersName); 140 | 141 | // Add body 142 | JSStringRef bodyName = JSStringCreateWithUTF8CString("body"); 143 | JSStringRef bodyValue = JSStringCreateWithUTF8CString(req->response_body ? req->response_body : "{}"); 144 | JSObjectSetProperty(req->ctx, responseObj, bodyName, 145 | JSValueMakeString(req->ctx, bodyValue), 146 | kJSPropertyAttributeNone, NULL); 147 | JSStringRelease(bodyName); 148 | JSStringRelease(bodyValue); 149 | 150 | // Call the JS callback with response 151 | JSValueRef args[] = { JSValueMakeNull(req->ctx), JSValueToObject(req->ctx, responseObj, NULL) }; 152 | JSObjectCallAsFunction(req->ctx, req->callback, NULL, 2, args, NULL); 153 | 154 | // Cleanup 155 | uv_close((uv_handle_t*)&req->socket, NULL); 156 | free(req->host); 157 | free(req->path); 158 | free(req->response_data); 159 | free(req->response_headers); 160 | free(req->response_body); 161 | free(req); 162 | } 163 | 164 | free(buf->base); 165 | } 166 | 167 | // Helper function to check if data is JSON 168 | static bool is_json_data(const char* data) { 169 | if (!data) return false; 170 | // Simple check: starts with { and ends with } 171 | return data[0] == '{' && data[strlen(data) - 1] == '}'; 172 | } 173 | 174 | // Modify the connect callback to handle different content types 175 | void on_http_connect(uv_connect_t* req, int status) { 176 | if (status < 0) return; 177 | 178 | HttpRequest* http = (HttpRequest*)req->data; 179 | char request[1024]; 180 | 181 | // Check the HTTP method 182 | if (http->method && strcmp(http->method, "DELETE") == 0) { 183 | snprintf(request, sizeof(request), 184 | "DELETE %s HTTP/1.1\r\n" 185 | "Host: %s\r\n" 186 | "Connection: close\r\n\r\n", 187 | http->path, http->host); 188 | } else if (http->method && strcmp(http->method, "PUT") == 0) { 189 | const char* content_type = is_json_data(http->request_data) ? 190 | "application/json" : "application/x-www-form-urlencoded"; 191 | 192 | snprintf(request, sizeof(request), 193 | "PUT %s HTTP/1.1\r\n" 194 | "Host: %s\r\n" 195 | "Content-Type: %s\r\n" 196 | "Content-Length: %zu\r\n" 197 | "Connection: close\r\n\r\n" 198 | "%s", 199 | http->path, http->host, content_type, http->request_data_len, http->request_data); 200 | } else if (http->request_data) { 201 | const char* content_type = is_json_data(http->request_data) ? 202 | "application/json" : "application/x-www-form-urlencoded"; 203 | 204 | snprintf(request, sizeof(request), 205 | "POST %s HTTP/1.1\r\n" 206 | "Host: %s\r\n" 207 | "Content-Type: %s\r\n" 208 | "Content-Length: %zu\r\n" 209 | "Connection: close\r\n\r\n" 210 | "%s", 211 | http->path, http->host, content_type, http->request_data_len, http->request_data); 212 | } else { 213 | snprintf(request, sizeof(request), 214 | "GET %s HTTP/1.1\r\n" 215 | "Host: %s\r\n" 216 | "Connection: close\r\n\r\n", 217 | http->path, http->host); 218 | } 219 | 220 | uv_buf_t write_buf = uv_buf_init(strdup(request), strlen(request)); 221 | uv_write_t* write_req = malloc(sizeof(uv_write_t)); 222 | write_req->data = write_buf.base; 223 | uv_write(write_req, req->handle, &write_buf, 1, on_write_complete); 224 | uv_read_start(req->handle, on_http_alloc, on_http_read); 225 | } 226 | 227 | // DNS Resolution Callback 228 | void on_dns_resolved(uv_getaddrinfo_t* resolver, int status, struct addrinfo* res) { 229 | HttpRequest* http = (HttpRequest*)resolver->data; 230 | free(resolver); 231 | 232 | if (status < 0) { 233 | return; 234 | } 235 | 236 | uv_tcp_init(uv_default_loop(), &http->socket); 237 | http->socket.data = http; 238 | 239 | uv_connect_t* connect_req = malloc(sizeof(uv_connect_t)); 240 | connect_req->data = http; 241 | uv_tcp_connect(connect_req, &http->socket, (const struct sockaddr*)res->ai_addr, on_http_connect); 242 | uv_freeaddrinfo(res); 243 | } 244 | 245 | // `http.get(url, callback)` 246 | JSValueRef http_get(JSContextRef ctx, JSObjectRef function, 247 | JSObjectRef thisObject, size_t argc, 248 | const JSValueRef args[], JSValueRef* exception) { 249 | if (argc < 2) return JSValueMakeUndefined(ctx); 250 | 251 | // Parse URL 252 | JSStringRef urlRef = JSValueToStringCopy(ctx, args[0], exception); 253 | size_t urlLen = JSStringGetMaximumUTF8CStringSize(urlRef); 254 | char* url = malloc(urlLen); 255 | JSStringGetUTF8CString(urlRef, url, urlLen); 256 | JSStringRelease(urlRef); 257 | 258 | // Ensure HTTP scheme 259 | if (strncmp(url, "http://", 7) != 0) { 260 | free(url); 261 | return JSValueMakeUndefined(ctx); 262 | } 263 | 264 | // Parse host and path 265 | char* host_start = url + 7; 266 | char* path_start = strchr(host_start, '/'); 267 | char* host = path_start ? strndup(host_start, path_start - host_start) : strdup(host_start); 268 | char* path = path_start ? strdup(path_start) : strdup("/"); 269 | 270 | // Setup HTTP request 271 | HttpRequest* http = malloc(sizeof(HttpRequest)); 272 | http->ctx = ctx; 273 | http->callback = (JSObjectRef)args[1]; 274 | http->host = host; 275 | http->path = path; 276 | http->response_data = NULL; 277 | http->response_size = 0; 278 | http->response_len = 0; 279 | http->status_code = 0; 280 | http->response_headers = NULL; 281 | http->headers_size = 0; 282 | http->headers_len = 0; 283 | http->headers_parsed = false; 284 | http->response_body = NULL; 285 | http->body_size = 0; 286 | http->body_len = 0; 287 | http->request_data = NULL; 288 | http->request_data_len = 0; 289 | http->method = NULL; // Set the HTTP method to NULL 290 | 291 | struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }; 292 | uv_getaddrinfo_t* resolver = malloc(sizeof(uv_getaddrinfo_t)); 293 | resolver->data = http; 294 | uv_getaddrinfo(uv_default_loop(), resolver, on_dns_resolved, http->host, "80", &hints); 295 | 296 | free(url); 297 | return JSValueMakeUndefined(ctx); 298 | } 299 | 300 | // `http.post(url, data, callback)` 301 | JSValueRef http_post(JSContextRef ctx, JSObjectRef function, 302 | JSObjectRef thisObject, size_t argc, 303 | const JSValueRef args[], JSValueRef* exception) { 304 | if (argc < 3) { 305 | JSStringRef msg = JSStringCreateWithUTF8CString("http.post requires 3 arguments: url, data, and callback"); 306 | *exception = JSValueMakeString(ctx, msg); 307 | JSStringRelease(msg); 308 | return JSValueMakeUndefined(ctx); 309 | } 310 | 311 | // Parse URL 312 | JSStringRef urlRef = JSValueToStringCopy(ctx, args[0], exception); 313 | size_t urlLen = JSStringGetMaximumUTF8CStringSize(urlRef); 314 | char* url = malloc(urlLen); 315 | JSStringGetUTF8CString(urlRef, url, urlLen); 316 | JSStringRelease(urlRef); 317 | 318 | // Ensure HTTP scheme 319 | if (strncmp(url, "http://", 7) != 0) { 320 | free(url); 321 | return JSValueMakeUndefined(ctx); 322 | } 323 | 324 | // Parse host and path 325 | char* host_start = url + 7; 326 | char* path_start = strchr(host_start, '/'); 327 | char* host = path_start ? strndup(host_start, path_start - host_start) : strdup(host_start); 328 | char* path = path_start ? strdup(path_start) : strdup("/"); 329 | 330 | // Parse data 331 | JSStringRef dataRef = JSValueToStringCopy(ctx, args[1], exception); 332 | size_t dataLen = JSStringGetMaximumUTF8CStringSize(dataRef); 333 | char* data = malloc(dataLen); 334 | JSStringGetUTF8CString(dataRef, data, dataLen); 335 | JSStringRelease(dataRef); 336 | 337 | // Setup HTTP request 338 | HttpRequest* http = malloc(sizeof(HttpRequest)); 339 | http->ctx = ctx; 340 | http->callback = (JSObjectRef)args[2]; 341 | http->host = host; 342 | http->path = path; 343 | http->response_data = NULL; 344 | http->response_size = 0; 345 | http->response_len = 0; 346 | http->status_code = 0; 347 | http->response_headers = NULL; 348 | http->headers_size = 0; 349 | http->headers_len = 0; 350 | http->headers_parsed = false; 351 | http->response_body = NULL; 352 | http->body_size = 0; 353 | http->body_len = 0; 354 | http->request_data = data; 355 | http->request_data_len = strlen(data); 356 | http->method = "POST"; // Set the HTTP method 357 | 358 | struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }; 359 | uv_getaddrinfo_t* resolver = malloc(sizeof(uv_getaddrinfo_t)); 360 | resolver->data = http; 361 | uv_getaddrinfo(uv_default_loop(), resolver, on_dns_resolved, http->host, "80", &hints); 362 | 363 | free(url); 364 | return JSValueMakeUndefined(ctx); 365 | } 366 | 367 | // `http.put(url, data, callback)` 368 | JSValueRef http_put(JSContextRef ctx, JSObjectRef function, 369 | JSObjectRef thisObject, size_t argc, 370 | const JSValueRef args[], JSValueRef* exception) { 371 | if (argc < 3) { 372 | JSStringRef msg = JSStringCreateWithUTF8CString("http.put requires 3 arguments: url, data, and callback"); 373 | *exception = JSValueMakeString(ctx, msg); 374 | JSStringRelease(msg); 375 | return JSValueMakeUndefined(ctx); 376 | } 377 | 378 | // Parse URL 379 | JSStringRef urlRef = JSValueToStringCopy(ctx, args[0], exception); 380 | size_t urlLen = JSStringGetMaximumUTF8CStringSize(urlRef); 381 | char* url = malloc(urlLen); 382 | JSStringGetUTF8CString(urlRef, url, urlLen); 383 | JSStringRelease(urlRef); 384 | 385 | // Ensure HTTP scheme 386 | if (strncmp(url, "http://", 7) != 0) { 387 | free(url); 388 | return JSValueMakeUndefined(ctx); 389 | } 390 | 391 | // Parse host and path 392 | char* host_start = url + 7; 393 | char* path_start = strchr(host_start, '/'); 394 | char* host = path_start ? strndup(host_start, path_start - host_start) : strdup(host_start); 395 | char* path = path_start ? strdup(path_start) : strdup("/"); 396 | 397 | // Parse data 398 | JSStringRef dataRef = JSValueToStringCopy(ctx, args[1], exception); 399 | size_t dataLen = JSStringGetMaximumUTF8CStringSize(dataRef); 400 | char* data = malloc(dataLen); 401 | JSStringGetUTF8CString(dataRef, data, dataLen); 402 | JSStringRelease(dataRef); 403 | 404 | // Setup HTTP request 405 | HttpRequest* http = malloc(sizeof(HttpRequest)); 406 | http->ctx = ctx; 407 | http->callback = (JSObjectRef)args[2]; 408 | http->host = host; 409 | http->path = path; 410 | http->response_data = NULL; 411 | http->response_size = 0; 412 | http->response_len = 0; 413 | http->status_code = 0; 414 | http->response_headers = NULL; 415 | http->headers_size = 0; 416 | http->headers_len = 0; 417 | http->headers_parsed = false; 418 | http->response_body = NULL; 419 | http->body_size = 0; 420 | http->body_len = 0; 421 | http->request_data = data; 422 | http->request_data_len = strlen(data); 423 | http->method = "PUT"; // Set the HTTP method 424 | 425 | struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }; 426 | uv_getaddrinfo_t* resolver = malloc(sizeof(uv_getaddrinfo_t)); 427 | resolver->data = http; 428 | uv_getaddrinfo(uv_default_loop(), resolver, on_dns_resolved, http->host, "80", &hints); 429 | 430 | free(url); 431 | return JSValueMakeUndefined(ctx); 432 | } 433 | 434 | // `http.delete(url, callback)` 435 | JSValueRef http_delete(JSContextRef ctx, JSObjectRef function, 436 | JSObjectRef thisObject, size_t argc, 437 | const JSValueRef args[], JSValueRef* exception) { 438 | if (argc < 2) { 439 | JSStringRef msg = JSStringCreateWithUTF8CString("http.delete requires 2 arguments: url and callback"); 440 | *exception = JSValueMakeString(ctx, msg); 441 | JSStringRelease(msg); 442 | return JSValueMakeUndefined(ctx); 443 | } 444 | 445 | // Parse URL 446 | JSStringRef urlRef = JSValueToStringCopy(ctx, args[0], exception); 447 | size_t urlLen = JSStringGetMaximumUTF8CStringSize(urlRef); 448 | char* url = malloc(urlLen); 449 | JSStringGetUTF8CString(urlRef, url, urlLen); 450 | JSStringRelease(urlRef); 451 | 452 | // Ensure HTTP scheme 453 | if (strncmp(url, "http://", 7) != 0) { 454 | free(url); 455 | return JSValueMakeUndefined(ctx); 456 | } 457 | 458 | // Parse host and path 459 | char* host_start = url + 7; 460 | char* path_start = strchr(host_start, '/'); 461 | char* host = path_start ? strndup(host_start, path_start - host_start) : strdup(host_start); 462 | char* path = path_start ? strdup(path_start) : strdup("/"); 463 | 464 | // Setup HTTP request 465 | HttpRequest* http = malloc(sizeof(HttpRequest)); 466 | http->ctx = ctx; 467 | http->callback = (JSObjectRef)args[1]; 468 | http->host = host; 469 | http->path = path; 470 | http->response_data = NULL; 471 | http->response_size = 0; 472 | http->response_len = 0; 473 | http->status_code = 0; 474 | http->response_headers = NULL; 475 | http->headers_size = 0; 476 | http->headers_len = 0; 477 | http->headers_parsed = false; 478 | http->response_body = NULL; 479 | http->body_size = 0; 480 | http->body_len = 0; 481 | http->request_data = NULL; 482 | http->request_data_len = 0; 483 | http->method = "DELETE"; // Set the HTTP method 484 | 485 | struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }; 486 | uv_getaddrinfo_t* resolver = malloc(sizeof(uv_getaddrinfo_t)); 487 | resolver->data = http; 488 | uv_getaddrinfo(uv_default_loop(), resolver, on_dns_resolved, http->host, "80", &hints); 489 | 490 | free(url); 491 | return JSValueMakeUndefined(ctx); 492 | } 493 | 494 | // ========================= HTTP SERVER (http.createServer) ========================= // 495 | 496 | typedef struct { 497 | uv_tcp_t server; 498 | JSContextRef ctx; 499 | JSObjectRef callback; 500 | } HttpServer; 501 | 502 | // Structure to track client connections 503 | typedef struct { 504 | uv_tcp_t* handle; 505 | HttpServer* server; 506 | JSObjectRef req; 507 | JSObjectRef res; 508 | } ClientContext; 509 | 510 | // Finalize callback for the server object 511 | static void server_finalize(JSObjectRef object) { 512 | HttpServer* server = (HttpServer*)JSObjectGetPrivate(object); 513 | if (server) { 514 | uv_close((uv_handle_t*)&server->server, NULL); 515 | free(server); 516 | } 517 | } 518 | 519 | // Response "end" method implementation 520 | JSValueRef res_end(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, 521 | size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { 522 | ClientContext* clientCtx = (ClientContext*)JSObjectGetPrivate(thisObject); 523 | if (!clientCtx) return JSValueMakeUndefined(ctx); 524 | 525 | const char* body = "Hello, World!"; 526 | char response[512]; 527 | snprintf(response, sizeof(response), 528 | "HTTP/1.1 200 OK\r\n" 529 | "Content-Type: text/plain\r\n" 530 | "Content-Length: %zu\r\n" 531 | "\r\n%s", 532 | strlen(body), body); 533 | 534 | uv_buf_t buf = uv_buf_init(strdup(response), strlen(response)); 535 | uv_write_t* write_req = malloc(sizeof(uv_write_t)); 536 | write_req->data = buf.base; 537 | uv_write(write_req, (uv_stream_t*)clientCtx->handle, &buf, 1, on_write_complete); 538 | 539 | return JSValueMakeUndefined(ctx); 540 | } 541 | 542 | // Read callback for client data 543 | void on_client_read(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf) { 544 | ClientContext* clientCtx = (ClientContext*)client->data; 545 | 546 | if (nread > 0) { 547 | buf->base[nread] = '\0'; // Ensure null-terminated string 548 | 549 | char method[16], url[256]; 550 | 551 | // Extract only the first line of the request 552 | char* first_line = strtok(buf->base, "\r\n"); 553 | if (first_line && sscanf(first_line, "%15s %255s", method, url) == 2) { 554 | // Convert method & URL to JavaScript strings 555 | JSStringRef methodRef = JSStringCreateWithUTF8CString(method); 556 | JSStringRef urlRef = JSStringCreateWithUTF8CString(url); 557 | 558 | // Set req.method 559 | JSStringRef methodName = JSStringCreateWithUTF8CString("method"); 560 | JSObjectSetProperty(clientCtx->server->ctx, clientCtx->req, methodName, JSValueMakeString(clientCtx->server->ctx, methodRef), kJSPropertyAttributeNone, NULL); 561 | JSStringRelease(methodRef); 562 | JSStringRelease(methodName); 563 | 564 | // Set req.url 565 | JSStringRef urlName = JSStringCreateWithUTF8CString("url"); 566 | JSObjectSetProperty(clientCtx->server->ctx, clientCtx->req, urlName, JSValueMakeString(clientCtx->server->ctx, urlRef), kJSPropertyAttributeNone, NULL); 567 | JSStringRelease(urlRef); 568 | JSStringRelease(urlName); 569 | 570 | printf("LOG: Received %s request for %s\n", method, url); 571 | } else { 572 | fprintf(stderr, "ERROR: Failed to parse HTTP request\n"); 573 | } 574 | } 575 | 576 | free(buf->base); 577 | } 578 | 579 | // Handle new HTTP connections 580 | void on_new_http_connection(uv_stream_t* server, int status) { 581 | if (status < 0) { 582 | fprintf(stderr, "ERROR: New connection failed: %s\n", uv_strerror(status)); 583 | return; 584 | } 585 | 586 | HttpServer* httpServer = (HttpServer*)server->data; 587 | uv_tcp_t* client = malloc(sizeof(uv_tcp_t)); 588 | uv_tcp_init(uv_default_loop(), client); 589 | 590 | if (uv_accept(server, (uv_stream_t*)client) == 0) { 591 | ClientContext* clientCtx = malloc(sizeof(ClientContext)); 592 | clientCtx->handle = client; 593 | clientCtx->server = httpServer; 594 | 595 | // Create req/res objects 596 | clientCtx->req = JSObjectMake(httpServer->ctx, NULL, NULL); 597 | clientCtx->res = JSObjectMake(httpServer->ctx, NULL, NULL); 598 | 599 | // Add "end" method to res 600 | JSStringRef endName = JSStringCreateWithUTF8CString("end"); 601 | JSObjectRef endFunc = JSObjectMakeFunctionWithCallback(httpServer->ctx, endName, res_end); 602 | JSObjectSetProperty(httpServer->ctx, clientCtx->res, endName, endFunc, kJSPropertyAttributeNone, NULL); 603 | JSStringRelease(endName); 604 | 605 | // Associate client context with res object 606 | JSObjectSetPrivate(clientCtx->res, clientCtx); 607 | 608 | // Start reading client data 609 | client->data = clientCtx; 610 | uv_read_start((uv_stream_t*)client, on_http_alloc, on_client_read); 611 | 612 | // Call the JS callback with req and res 613 | JSValueRef args[] = { clientCtx->req, clientCtx->res }; 614 | JSObjectCallAsFunction(httpServer->ctx, httpServer->callback, NULL, 2, args, NULL); 615 | } else { 616 | fprintf(stderr, "ERROR: Failed to accept client connection\n"); 617 | free(client); 618 | } 619 | } 620 | 621 | // `http.createServer(callback)` 622 | JSValueRef http_create_server(JSContextRef ctx, JSObjectRef function, 623 | JSObjectRef thisObject, size_t argc, 624 | const JSValueRef args[], JSValueRef* exception) { 625 | if (argc < 1) return JSValueMakeUndefined(ctx); 626 | 627 | // Create the HttpServer structure 628 | HttpServer* server = malloc(sizeof(HttpServer)); 629 | server->ctx = ctx; 630 | server->callback = (JSObjectRef)args[0]; 631 | 632 | // Initialize the TCP server 633 | uv_tcp_init(uv_default_loop(), &server->server); 634 | server->server.data = server; 635 | 636 | // Define a JavaScript class for the server object 637 | JSClassDefinition serverClassDef = kJSClassDefinitionEmpty; 638 | serverClassDef.finalize = server_finalize; // Use the C function for finalization 639 | JSClassRef serverClass = JSClassCreate(&serverClassDef); 640 | JSObjectRef serverObject = JSObjectMake(ctx, serverClass, server); 641 | JSClassRelease(serverClass); 642 | 643 | // Add the `listen` method to the server object 644 | JSStringRef listenName = JSStringCreateWithUTF8CString("listen"); 645 | JSObjectRef listenFunc = JSObjectMakeFunctionWithCallback(ctx, listenName, http_server_listen); 646 | JSObjectSetProperty(ctx, serverObject, listenName, listenFunc, kJSPropertyAttributeNone, NULL); 647 | JSStringRelease(listenName); 648 | 649 | return serverObject; 650 | } 651 | 652 | // `server.listen(port)` 653 | JSValueRef http_server_listen(JSContextRef ctx, JSObjectRef function, 654 | JSObjectRef thisObject, size_t argc, 655 | const JSValueRef args[], JSValueRef* exception) { 656 | if (argc < 1 || !JSValueIsNumber(ctx, args[0])) { 657 | fprintf(stderr, "ERROR: server.listen() requires a valid port number\n"); 658 | return JSValueMakeUndefined(ctx); 659 | } 660 | 661 | // Retrieve the HttpServer object 662 | HttpServer* server = (HttpServer*)JSObjectGetPrivate(thisObject); 663 | if (!server) { 664 | fprintf(stderr, "ERROR: Invalid server object\n"); 665 | return JSValueMakeUndefined(ctx); 666 | } 667 | 668 | int port = (int)JSValueToNumber(ctx, args[0], exception); 669 | struct sockaddr_in addr; 670 | uv_ip4_addr("0.0.0.0", port, &addr); 671 | 672 | // Bind the server to the specified port 673 | int bind_result = uv_tcp_bind(&server->server, (const struct sockaddr*)&addr, 0); 674 | if (bind_result < 0) { 675 | fprintf(stderr, "ERROR: Failed to bind to port %d: %s\n", port, uv_strerror(bind_result)); 676 | return JSValueMakeUndefined(ctx); 677 | } 678 | 679 | // Start listening for incoming connections 680 | int listen_result = uv_listen((uv_stream_t*)&server->server, 10, on_new_http_connection); 681 | if (listen_result < 0) { 682 | fprintf(stderr, "ERROR: HTTP server failed to listen on port %d: %s\n", port, uv_strerror(listen_result)); 683 | return JSValueMakeUndefined(ctx); 684 | } 685 | 686 | printf("LOG: HTTP Server listening on port %d\n", port); 687 | return JSValueMakeUndefined(ctx); 688 | } -------------------------------------------------------------------------------- /src/js_bindings.c: -------------------------------------------------------------------------------- 1 | /** 2 | * ===================================================================================== 3 | * 4 | * JS_BINDINGS.C - Native Functionality Bindings for JavaScript 5 | * 6 | * ===================================================================================== 7 | * 8 | * This file implements the bridge between native capabilities and the JavaScript 9 | * environment. It exposes the following APIs to JavaScript: 10 | * - Console API (log, warn, info, debug, error) 11 | * - Timer API (setTimeout, clearTimeout, setInterval, clearInterval) 12 | * - Process API (argv, exit) 13 | * - Runtime Info (name, version) 14 | * 15 | * Architecture: 16 | * ┌─────────────┐ ┌─────────────┐ 17 | * │ JS Global │ ◄─────│ Console API │ 18 | * └─────────────┘ └─────────────┘ 19 | * ▲ 20 | * │ setTimeout 21 | * ┌─────────────┐ 22 | * │ Event Loop │ 23 | * └─────────────┘ 24 | * 25 | * Key Features: 26 | * - Thread-safe JS/C interop 27 | * - Proper error handling 28 | * - Memory-safe string conversions 29 | * 30 | * Security Notes: 31 | * - No input validation (demo purposes) 32 | * - Production should sanitize all inputs 33 | * 34 | * ===================================================================================== 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include "runtime.h" 43 | #include "version.h" 44 | 45 | // External variables 46 | extern int process_argc; 47 | extern char** process_argv; 48 | 49 | // ================== Helper Functions ================== // 50 | 51 | /** 52 | * Generates a timestamp string in the format "[HH:MM:SS]" 53 | * @return Timestamp string (must be freed by caller) 54 | */ 55 | static char* get_timestamp() { 56 | time_t now = time(NULL); 57 | struct tm* tm_info = localtime(&now); 58 | 59 | char* timestamp = malloc(10); // "[HH:MM:SS]" + null terminator 60 | strftime(timestamp, 10, "[%H:%M:%S]", tm_info); 61 | return timestamp; 62 | } 63 | 64 | /** 65 | * Converts multiple JS values to a concatenated string 66 | * @param ctx JS context 67 | * @param argc Number of arguments 68 | * @param args Array of JS values 69 | * @param exception Exception object (if any) 70 | * @return Concatenated string (must be freed by caller) 71 | */ 72 | static char* js_values_to_string(JSContextRef ctx, size_t argc, const JSValueRef args[], JSValueRef* exception) { 73 | if (argc == 0) return strdup(""); // Handle no arguments 74 | 75 | // Temporary array to store C strings 76 | char** c_strs = malloc(argc * sizeof(char*)); 77 | size_t total_len = 0; 78 | 79 | // First pass: convert all JS values to C strings 80 | for (size_t i = 0; i < argc; i++) { 81 | JSStringRef js_str = JSValueToStringCopy(ctx, args[i], exception); 82 | if (*exception) { 83 | // Cleanup on error 84 | for (size_t j = 0; j < i; j++) free(c_strs[j]); 85 | free(c_strs); 86 | return NULL; 87 | } 88 | 89 | size_t max_len = JSStringGetMaximumUTF8CStringSize(js_str); 90 | c_strs[i] = malloc(max_len); 91 | JSStringGetUTF8CString(js_str, c_strs[i], max_len); 92 | JSStringRelease(js_str); 93 | 94 | total_len += strlen(c_strs[i]) + 1; // +1 for space or null terminator 95 | } 96 | 97 | // Allocate final string buffer 98 | char* result = malloc(total_len); 99 | result[0] = '\0'; 100 | 101 | // Second pass: concatenate all strings 102 | for (size_t i = 0; i < argc; i++) { 103 | strcat(result, c_strs[i]); 104 | if (i < argc - 1) strcat(result, " "); 105 | free(c_strs[i]); // Free individual strings 106 | } 107 | 108 | free(c_strs); 109 | return result; 110 | } 111 | 112 | // ================== Console API ================== // 113 | 114 | /** 115 | * Generic console output function 116 | * @param prefix Prefix for the log (e.g., "LOG", "WARN") 117 | * @param color ANSI color code (e.g., "\033[34m" for blue) 118 | * @param ctx JS context 119 | * @param function JS function reference 120 | * @param thisObject JS `this` object 121 | * @param argc Number of arguments 122 | * @param args Array of JS values 123 | * @param exception Exception object (if any) 124 | */ 125 | static JSValueRef console_output(const char* prefix, const char* color, JSContextRef ctx, JSObjectRef function, 126 | JSObjectRef thisObject, size_t argc, const JSValueRef args[], JSValueRef* exception) { 127 | if (argc == 0) { 128 | printf("%s%s: \033[0m\n", color, prefix); // Handle no arguments 129 | return JSValueMakeUndefined(ctx); 130 | } 131 | 132 | // Convert all arguments to a single string 133 | char* message = js_values_to_string(ctx, argc, args, exception); 134 | printf("%s%s: %s\033[0m\n", color, prefix, message); // Print with color and prefix 135 | free(message); 136 | 137 | return JSValueMakeUndefined(ctx); 138 | } 139 | 140 | // Console methods 141 | static JSValueRef console_log(JSContextRef ctx, JSObjectRef function, 142 | JSObjectRef thisObject, size_t argc, 143 | const JSValueRef args[], JSValueRef* exception) { 144 | return console_output("LOG", "\033[0m", ctx, function, thisObject, argc, args, exception); 145 | } 146 | 147 | static JSValueRef console_warn(JSContextRef ctx, JSObjectRef function, 148 | JSObjectRef thisObject, size_t argc, 149 | const JSValueRef args[], JSValueRef* exception) { 150 | return console_output("WARN", "\033[33m", ctx, function, thisObject, argc, args, exception); 151 | } 152 | 153 | static JSValueRef console_info(JSContextRef ctx, JSObjectRef function, 154 | JSObjectRef thisObject, size_t argc, 155 | const JSValueRef args[], JSValueRef* exception) { 156 | return console_output("INFO", "\033[34m", ctx, function, thisObject, argc, args, exception); 157 | } 158 | 159 | static JSValueRef console_debug(JSContextRef ctx, JSObjectRef function, 160 | JSObjectRef thisObject, size_t argc, 161 | const JSValueRef args[], JSValueRef* exception) { 162 | return console_output("DEBUG", "\033[90m", ctx, function, thisObject, argc, args, exception); 163 | } 164 | 165 | static JSValueRef console_error(JSContextRef ctx, JSObjectRef function, 166 | JSObjectRef thisObject, size_t argc, 167 | const JSValueRef args[], JSValueRef* exception) { 168 | return console_output("ERROR", "\033[31m", ctx, function, thisObject, argc, args, exception); 169 | } 170 | 171 | // ================== Timer API ================== // 172 | 173 | /** 174 | * JS-accessible setTimeout implementation 175 | * @param args[0] Callback function 176 | * @param args[1] Delay in milliseconds 177 | */ 178 | static JSValueRef js_set_timeout(JSContextRef ctx, JSObjectRef function, 179 | JSObjectRef thisObject, size_t argc, 180 | const JSValueRef args[], JSValueRef* exception) { 181 | // Argument validation 182 | if (argc < 2) { 183 | JSStringRef msg = JSStringCreateWithUTF8CString("setTimeout requires 2 arguments"); 184 | *exception = JSValueMakeString(ctx, msg); 185 | JSStringRelease(msg); 186 | return JSValueMakeUndefined(ctx); 187 | } 188 | 189 | // Extract callback and delay 190 | JSObjectRef callback = JSValueToObject(ctx, args[0], exception); 191 | uint64_t delay = JSValueToNumber(ctx, args[1], exception); 192 | 193 | // Schedule with event loop 194 | set_timeout(ctx, callback, delay); 195 | return JSValueMakeUndefined(ctx); 196 | } 197 | 198 | /** 199 | * clearTimeout - JS-accessible function 200 | */ 201 | static JSValueRef js_clear_timeout(JSContextRef ctx, JSObjectRef function, 202 | JSObjectRef thisObject, size_t argc, 203 | const JSValueRef args[], JSValueRef* exception) { 204 | if (argc < 1) { 205 | JSStringRef msg = JSStringCreateWithUTF8CString("clearTimeout requires 1 argument"); 206 | *exception = JSValueMakeString(ctx, msg); 207 | JSStringRelease(msg); 208 | return JSValueMakeUndefined(ctx); 209 | } 210 | 211 | uint32_t timer_id = (uint32_t)JSValueToNumber(ctx, args[0], exception); 212 | if (*exception) return JSValueMakeUndefined(ctx); 213 | 214 | clear_timeout(ctx, timer_id); 215 | return JSValueMakeUndefined(ctx); 216 | } 217 | 218 | /** 219 | * setInterval - JS-accessible function 220 | */ 221 | static JSValueRef js_set_interval(JSContextRef ctx, JSObjectRef function, 222 | JSObjectRef thisObject, size_t argc, 223 | const JSValueRef args[], JSValueRef* exception) { 224 | if (argc < 2) { 225 | JSStringRef msg = JSStringCreateWithUTF8CString("setInterval requires 2 arguments"); 226 | *exception = JSValueMakeString(ctx, msg); 227 | JSStringRelease(msg); 228 | return JSValueMakeUndefined(ctx); 229 | } 230 | 231 | JSObjectRef callback = JSValueToObject(ctx, args[0], exception); 232 | uint64_t interval = (uint64_t)JSValueToNumber(ctx, args[1], exception); 233 | if (*exception) return JSValueMakeUndefined(ctx); 234 | 235 | set_interval(ctx, callback, interval); 236 | return JSValueMakeNumber(ctx, next_timer_id - 1); // Return timer ID 237 | } 238 | 239 | /** 240 | * clearInterval - JS-accessible function 241 | */ 242 | static JSValueRef js_clear_interval(JSContextRef ctx, JSObjectRef function, 243 | JSObjectRef thisObject, size_t argc, 244 | const JSValueRef args[], JSValueRef* exception) { 245 | if (argc < 1) { 246 | JSStringRef msg = JSStringCreateWithUTF8CString("clearInterval requires 1 argument"); 247 | *exception = JSValueMakeString(ctx, msg); 248 | JSStringRelease(msg); 249 | return JSValueMakeUndefined(ctx); 250 | } 251 | 252 | uint32_t timer_id = (uint32_t)JSValueToNumber(ctx, args[0], exception); 253 | if (*exception) return JSValueMakeUndefined(ctx); 254 | 255 | clear_interval(ctx, timer_id); 256 | return JSValueMakeUndefined(ctx); 257 | } 258 | 259 | // ================== Process API ================== // 260 | 261 | /** 262 | * process.exit - JS-accessible exit function 263 | */ 264 | static JSValueRef js_process_exit(JSContextRef ctx, JSObjectRef function, 265 | JSObjectRef thisObject, size_t argc, 266 | const JSValueRef args[], JSValueRef* exception) { 267 | int code = 0; 268 | if (argc > 0) { 269 | code = (int)JSValueToNumber(ctx, args[0], exception); 270 | if (*exception) return JSValueMakeUndefined(ctx); 271 | } 272 | 273 | uv_stop(loop); 274 | exit(code); 275 | return JSValueMakeUndefined(ctx); 276 | } 277 | 278 | // ================== API Exposure ================== // 279 | 280 | /** 281 | * Exposes native APIs to JS global scope 282 | * @param ctx Context to enhance 283 | */ 284 | void bind_js_native_apis(JSGlobalContextRef ctx) { 285 | // Create global object 286 | JSObjectRef global = JSContextGetGlobalObject(ctx); 287 | 288 | // ================== Console API ================== // 289 | JSObjectRef console = JSObjectMake(ctx, NULL, NULL); 290 | JSStringRef consoleName = JSStringCreateWithUTF8CString("console"); 291 | JSObjectSetProperty(ctx, global, consoleName, console, kJSPropertyAttributeNone, NULL); 292 | JSStringRelease(consoleName); 293 | 294 | // Add console methods 295 | JSStringRef logName = JSStringCreateWithUTF8CString("log"); 296 | JSObjectSetProperty(ctx, console, logName, JSObjectMakeFunctionWithCallback(ctx, logName, console_log), kJSPropertyAttributeNone, NULL); 297 | JSStringRelease(logName); 298 | 299 | JSStringRef warnName = JSStringCreateWithUTF8CString("warn"); 300 | JSObjectSetProperty(ctx, console, warnName, JSObjectMakeFunctionWithCallback(ctx, warnName, console_warn), kJSPropertyAttributeNone, NULL); 301 | JSStringRelease(warnName); 302 | 303 | JSStringRef infoName = JSStringCreateWithUTF8CString("info"); 304 | JSObjectSetProperty(ctx, console, infoName, JSObjectMakeFunctionWithCallback(ctx, infoName, console_info), kJSPropertyAttributeNone, NULL); 305 | JSStringRelease(infoName); 306 | 307 | JSStringRef debugName = JSStringCreateWithUTF8CString("debug"); 308 | JSObjectSetProperty(ctx, console, debugName, JSObjectMakeFunctionWithCallback(ctx, debugName, console_debug), kJSPropertyAttributeNone, NULL); 309 | JSStringRelease(debugName); 310 | 311 | JSStringRef errorName = JSStringCreateWithUTF8CString("error"); 312 | JSObjectSetProperty(ctx, console, errorName, JSObjectMakeFunctionWithCallback(ctx, errorName, console_error), kJSPropertyAttributeNone, NULL); 313 | JSStringRelease(errorName); 314 | 315 | // ================== Timer API ================== // 316 | JSStringRef setTimeoutName = JSStringCreateWithUTF8CString("setTimeout"); 317 | JSObjectSetProperty(ctx, global, setTimeoutName, JSObjectMakeFunctionWithCallback(ctx, setTimeoutName, js_set_timeout), kJSPropertyAttributeNone, NULL); 318 | JSStringRelease(setTimeoutName); 319 | 320 | JSStringRef clearTimeoutName = JSStringCreateWithUTF8CString("clearTimeout"); 321 | JSObjectSetProperty(ctx, global, clearTimeoutName, JSObjectMakeFunctionWithCallback(ctx, clearTimeoutName, js_clear_timeout), kJSPropertyAttributeNone, NULL); 322 | JSStringRelease(clearTimeoutName); 323 | 324 | JSStringRef setIntervalName = JSStringCreateWithUTF8CString("setInterval"); 325 | JSObjectSetProperty(ctx, global, setIntervalName, JSObjectMakeFunctionWithCallback(ctx, setIntervalName, js_set_interval), kJSPropertyAttributeNone, NULL); 326 | JSStringRelease(setIntervalName); 327 | 328 | JSStringRef clearIntervalName = JSStringCreateWithUTF8CString("clearInterval"); 329 | JSObjectSetProperty(ctx, global, clearIntervalName, JSObjectMakeFunctionWithCallback(ctx, clearIntervalName, js_clear_interval), kJSPropertyAttributeNone, NULL); 330 | JSStringRelease(clearIntervalName); 331 | 332 | // ================== Process API ================== // 333 | JSObjectRef process = JSObjectMake(ctx, NULL, NULL); 334 | JSStringRef processName = JSStringCreateWithUTF8CString("process"); 335 | JSObjectSetProperty(ctx, global, processName, process, kJSPropertyAttributeNone, NULL); 336 | JSStringRelease(processName); 337 | 338 | // Add process.argv 339 | JSObjectRef argv = JSObjectMakeArray(ctx, 0, NULL, NULL); 340 | for (int i = 0; i < process_argc; i++) { 341 | JSStringRef arg = JSStringCreateWithUTF8CString(process_argv[i]); 342 | JSObjectSetPropertyAtIndex(ctx, argv, i, JSValueMakeString(ctx, arg), NULL); 343 | JSStringRelease(arg); 344 | } 345 | JSStringRef argvName = JSStringCreateWithUTF8CString("argv"); 346 | JSObjectSetProperty(ctx, process, argvName, argv, kJSPropertyAttributeNone, NULL); 347 | JSStringRelease(argvName); 348 | 349 | // Add process.exit 350 | JSStringRef exitName = JSStringCreateWithUTF8CString("exit"); 351 | JSObjectSetProperty(ctx, process, exitName, JSObjectMakeFunctionWithCallback(ctx, exitName, js_process_exit), kJSPropertyAttributeNone, NULL); 352 | JSStringRelease(exitName); 353 | 354 | // ================== HTTP API ================== // 355 | JSObjectRef http = JSObjectMake(ctx, NULL, NULL); 356 | JSStringRef httpName = JSStringCreateWithUTF8CString("http"); 357 | JSObjectSetProperty(ctx, global, httpName, http, kJSPropertyAttributeNone, NULL); 358 | JSStringRelease(httpName); 359 | 360 | // Add http.get 361 | JSStringRef getName = JSStringCreateWithUTF8CString("get"); 362 | JSObjectSetProperty(ctx, http, getName, JSObjectMakeFunctionWithCallback(ctx, getName, http_get), kJSPropertyAttributeNone, NULL); 363 | JSStringRelease(getName); 364 | 365 | // Add http.post 366 | JSStringRef postName = JSStringCreateWithUTF8CString("post"); 367 | JSObjectSetProperty(ctx, http, postName, JSObjectMakeFunctionWithCallback(ctx, postName, http_post), kJSPropertyAttributeNone, NULL); 368 | JSStringRelease(postName); 369 | 370 | // Add http.put 371 | JSStringRef putName = JSStringCreateWithUTF8CString("put"); 372 | JSObjectSetProperty(ctx, http, putName, JSObjectMakeFunctionWithCallback(ctx, putName, http_put), kJSPropertyAttributeNone, NULL); 373 | JSStringRelease(putName); 374 | 375 | // Add http.delete 376 | JSStringRef deleteName = JSStringCreateWithUTF8CString("delete"); 377 | JSObjectSetProperty(ctx, http, deleteName, JSObjectMakeFunctionWithCallback(ctx, deleteName, http_delete), kJSPropertyAttributeNone, NULL); 378 | JSStringRelease(deleteName); 379 | 380 | // Add http.createServer 381 | JSStringRef createServerName = JSStringCreateWithUTF8CString("createServer"); 382 | JSObjectSetProperty(ctx, http, createServerName, JSObjectMakeFunctionWithCallback(ctx, createServerName, http_create_server), kJSPropertyAttributeNone, NULL); 383 | JSStringRelease(createServerName); 384 | } -------------------------------------------------------------------------------- /src/jsc_engine.c: -------------------------------------------------------------------------------- 1 | /** 2 | * ===================================================================================== 3 | * 4 | * JSC_ENGINE.C - JavaScriptCore Integration Layer 5 | * 6 | * ===================================================================================== 7 | * 8 | * Responsible for: 9 | * - JavaScript context lifecycle management 10 | * - JS code execution 11 | * - Integration with system APIs 12 | * 13 | * Key Features: 14 | * - Context isolation through JSGlobalContextRef 15 | * - Direct evaluation of JS scripts 16 | * - Automatic API exposure on context creation 17 | * 18 | * Memory Management: 19 | * - Uses JSC's automatic garbage collection 20 | * - Manual string handling for JS<->C interop 21 | * 22 | * Cross-Platform Notes: 23 | * - WebKitGTK required on Linux 24 | * - Built-in JSC framework on macOS 25 | * 26 | * ===================================================================================== 27 | */ 28 | 29 | #include 30 | #include "runtime.h" 31 | 32 | /** 33 | * Creates a fresh JS execution environment with system APIs 34 | * returned context must be released with JSGlobalContextRelease() 35 | */ 36 | JSGlobalContextRef create_js_context() { 37 | // Create isolated JS context 38 | JSGlobalContextRef ctx = JSGlobalContextCreate(NULL); 39 | 40 | // Attach system APIs to global object 41 | bind_js_native_apis(ctx); 42 | 43 | return ctx; 44 | } 45 | 46 | /** 47 | * Executes raw JS code in specified context 48 | * @param script Must be UTF-8 encoded null-terminated string 49 | */ 50 | void execute_js(JSGlobalContextRef ctx, const char* script) { 51 | // Convert C string to JSC string 52 | JSStringRef js_code = JSStringCreateWithUTF8CString(script); 53 | 54 | // Execute script with default options 55 | JSEvaluateScript(ctx, js_code, NULL, NULL, 1, NULL); 56 | 57 | // Cleanup JS string resources 58 | JSStringRelease(js_code); 59 | } -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * ===================================================================================== 3 | * 4 | * MAIN.C - CLI Entry Point for JavaScript Runtime 5 | * 6 | * ===================================================================================== 7 | * 8 | * Responsibilities: 9 | * - Argument parsing 10 | * - File I/O for JS scripts 11 | * - Runtime initialization/cleanup 12 | * 13 | * Execution Flow: 14 | * 1. Parse CLI arguments 15 | * 2. Read JS file 16 | * 3. Initialize JSC context 17 | * 4. Start event loop 18 | * 5. Execute script 19 | * 6. Cleanup resources 20 | * 21 | * Error Handling: 22 | * - Basic file read errors 23 | * - Memory allocation checks (crude) 24 | * 25 | * Future Improvements: 26 | * - REPL mode 27 | * - File watching 28 | * - Better error messages 29 | * 30 | * ===================================================================================== 31 | */ 32 | 33 | #include "runtime.h" 34 | #include "version.h" 35 | #include 36 | #include 37 | #include 38 | 39 | // Global variables for command-line arguments 40 | int process_argc; 41 | char** process_argv; 42 | 43 | // Function to print version information 44 | void print_version() { 45 | printf("Jade Runtime v%s\n", RUNTIME_VERSION); 46 | } 47 | 48 | // Function to print help/usage information 49 | void print_help() { 50 | printf("Usage: jade [options] [script.js]\n"); 51 | printf("Options:\n"); 52 | printf(" --version Print version\n"); 53 | printf(" --help Show help\n"); 54 | printf(" --eval Execute inline code\n"); 55 | } 56 | 57 | 58 | int main(int argc, char** argv) { 59 | process_argc = argc; 60 | process_argv = argv; 61 | char* eval_code = NULL; 62 | char* script_file = NULL; 63 | 64 | // Parse command-line arguments 65 | for (int i = 1; i < argc; i++) { 66 | if (strcmp(argv[i], "--version") == 0) { 67 | print_version(); 68 | return 0; 69 | } else if (strcmp(argv[i], "--help") == 0) { 70 | print_help(); 71 | return 0; 72 | } else if (strcmp(argv[i], "--eval") == 0) { 73 | if (i + 1 >= argc) { 74 | fprintf(stderr, "Error: --eval requires code argument\n"); 75 | return 1; 76 | } 77 | eval_code = argv[i + 1]; 78 | i++; // Skip code argument 79 | } else { 80 | script_file = argv[i]; 81 | break; 82 | } 83 | } 84 | 85 | // Handle --eval 86 | if (eval_code != NULL) { 87 | JSGlobalContextRef ctx = create_js_context(); 88 | init_event_loop(); 89 | execute_js(ctx, eval_code); 90 | run_event_loop(); 91 | JSGlobalContextRelease(ctx); 92 | return 0; 93 | } 94 | 95 | // Handle script file 96 | if (!script_file) { 97 | fprintf(stderr, "Error: No script or --eval provided\n"); 98 | print_help(); 99 | return 1; 100 | } 101 | 102 | // Read JS file 103 | FILE* f = fopen(argv[1], "rb"); 104 | if (!f) { 105 | fprintf(stderr, "Error: Could not open file %s\n", argv[1]); 106 | return 1; 107 | } 108 | fseek(f, 0, SEEK_END); 109 | long len = ftell(f); 110 | rewind(f); 111 | char* script = malloc(len + 1); 112 | fread(script, 1, len, f); 113 | script[len] = '\0'; 114 | fclose(f); 115 | 116 | // Initialize runtime components 117 | JSGlobalContextRef ctx = create_js_context(); 118 | init_event_loop(); 119 | 120 | // Execute script and run event loop 121 | execute_js(ctx, script); 122 | run_event_loop(); 123 | 124 | // Cleanup 125 | JSGlobalContextRelease(ctx); 126 | free(script); 127 | return 0; 128 | } -------------------------------------------------------------------------------- /src/net_api.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "runtime.h" 7 | 8 | // Define a class to store ServerRequest* 9 | static JSClassRef serverClass = NULL; 10 | 11 | // TCP Server Request Structure 12 | typedef struct { 13 | uv_tcp_t server; 14 | JSContextRef ctx; 15 | JSObjectRef callback; 16 | } ServerRequest; 17 | 18 | // Structure to hold client socket data 19 | typedef struct { 20 | uv_tcp_t* client; 21 | } ClientRequest; 22 | 23 | // Write Callback 24 | void on_client_write(uv_write_t* req, int status) { 25 | free(req->data); 26 | free(req); 27 | } 28 | 29 | // Destructor for the server object 30 | void server_finalize(JSObjectRef object) { 31 | ServerRequest* sr = (ServerRequest*)JSObjectGetPrivate(object); 32 | if (sr) { 33 | JSValueUnprotect(sr->ctx, sr->callback); 34 | free(sr); 35 | } 36 | } 37 | 38 | // Client Connection Callback 39 | void on_new_connection(uv_stream_t* server, int status) { 40 | if (status < 0) return; 41 | 42 | ServerRequest* sr = (ServerRequest*)server->data; 43 | 44 | // Accept client connection 45 | uv_tcp_t* client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t)); 46 | uv_tcp_init(uv_default_loop(), client); 47 | if (uv_accept(server, (uv_stream_t*)client) == 0) { 48 | // Create ClientRequest struct 49 | ClientRequest* cr = (ClientRequest*)malloc(sizeof(ClientRequest)); 50 | cr->client = client; 51 | 52 | // Create a JavaScript class for the client object 53 | JSClassDefinition classDef = kJSClassDefinitionEmpty; 54 | classDef.version = 0; 55 | JSClassRef clientClass = JSClassCreate(&classDef); 56 | 57 | // Create the JavaScript client object 58 | JSObjectRef clientObject = JSObjectMake(sr->ctx, clientClass, cr); 59 | JSClassRelease(clientClass); 60 | 61 | // Attach `client.write(data)` 62 | JSStringRef writeName = JSStringCreateWithUTF8CString("write"); 63 | JSObjectRef writeFunc = JSObjectMakeFunctionWithCallback(sr->ctx, writeName, client_write); 64 | JSObjectSetProperty(sr->ctx, clientObject, writeName, writeFunc, kJSPropertyAttributeNone, NULL); 65 | JSStringRelease(writeName); 66 | 67 | // **Ensure client object has useful properties** 68 | JSStringRef idKey = JSStringCreateWithUTF8CString("id"); 69 | JSValueRef idValue = JSValueMakeNumber(sr->ctx, (double)(uintptr_t)client); 70 | JSObjectSetProperty(sr->ctx, clientObject, idKey, idValue, kJSPropertyAttributeNone, NULL); 71 | JSStringRelease(idKey); 72 | 73 | // Call JavaScript callback 74 | JSValueRef args[] = { clientObject }; 75 | JSObjectCallAsFunction(sr->ctx, sr->callback, NULL, 1, args, NULL); 76 | } else { 77 | uv_close((uv_handle_t*)client, NULL); 78 | free(client); 79 | } 80 | } 81 | 82 | 83 | 84 | // `net.createServer(callback)` 85 | JSValueRef net_create_server(JSContextRef ctx, JSObjectRef function, 86 | JSObjectRef thisObject, size_t argc, 87 | const JSValueRef args[], JSValueRef* exception) { 88 | if (argc < 1 || !JSValueIsObject(ctx, args[0]) || !JSObjectIsFunction(ctx, (JSObjectRef)args[0])) { 89 | JSStringRef errMsg = JSStringCreateWithUTF8CString("net.createServer requires a callback function"); 90 | *exception = JSValueMakeString(ctx, errMsg); 91 | JSStringRelease(errMsg); 92 | return JSValueMakeUndefined(ctx); 93 | } 94 | 95 | // Create ServerRequest struct 96 | ServerRequest* sr = (ServerRequest*)malloc(sizeof(ServerRequest)); 97 | if (!sr) return JSValueMakeUndefined(ctx); 98 | 99 | sr->ctx = ctx; 100 | sr->callback = (JSObjectRef)args[0]; 101 | JSValueProtect(ctx, sr->callback); 102 | 103 | // Initialize TCP server 104 | uv_tcp_init(uv_default_loop(), &sr->server); 105 | sr->server.data = sr; 106 | 107 | // Create a class definition for the server object (only once) 108 | if (serverClass == NULL) { 109 | JSClassDefinition classDef = kJSClassDefinitionEmpty; 110 | classDef.finalize = server_finalize; 111 | serverClass = JSClassCreate(&classDef); 112 | } 113 | 114 | // Create a proper JS object for the server 115 | JSObjectRef serverObject = JSObjectMake(ctx, serverClass, sr); 116 | 117 | // Attach `server.listen(port)` 118 | JSStringRef listenName = JSStringCreateWithUTF8CString("listen"); 119 | JSObjectRef listenFunc = JSObjectMakeFunctionWithCallback(ctx, listenName, net_server_listen); 120 | JSObjectSetProperty(ctx, serverObject, listenName, listenFunc, kJSPropertyAttributeNone, NULL); 121 | JSStringRelease(listenName); 122 | 123 | return serverObject; 124 | } 125 | 126 | // `server.listen(port)` 127 | JSValueRef net_server_listen(JSContextRef ctx, JSObjectRef function, 128 | JSObjectRef thisObject, size_t argc, 129 | const JSValueRef args[], JSValueRef* exception) { 130 | if (argc < 1 || !JSValueIsNumber(ctx, args[0])) return JSValueMakeUndefined(ctx); 131 | 132 | ServerRequest* sr = (ServerRequest*)JSObjectGetPrivate(thisObject); 133 | if (!sr) return JSValueMakeUndefined(ctx); 134 | 135 | int port = (int)JSValueToNumber(ctx, args[0], exception); 136 | struct sockaddr_in addr; 137 | uv_ip4_addr("0.0.0.0", port, &addr); 138 | 139 | uv_tcp_bind(&sr->server, (const struct sockaddr*)&addr, 0); 140 | int result = uv_listen((uv_stream_t*)&sr->server, 10, on_new_connection); 141 | if (result < 0) { 142 | fprintf(stderr, "Server listen error: %s\n", uv_strerror(result)); 143 | } 144 | 145 | return JSValueMakeUndefined(ctx); 146 | } 147 | 148 | 149 | // `client.write(data)` 150 | JSValueRef client_write(JSContextRef ctx, JSObjectRef function, 151 | JSObjectRef thisObject, size_t argc, 152 | const JSValueRef args[], JSValueRef* exception) { 153 | if (argc < 1 || !JSValueIsString(ctx, args[0])) { 154 | JSStringRef errMsg = JSStringCreateWithUTF8CString("client.write requires a string argument"); 155 | *exception = JSValueMakeString(ctx, errMsg); 156 | JSStringRelease(errMsg); 157 | return JSValueMakeUndefined(ctx); 158 | } 159 | 160 | // Get the client object 161 | ClientRequest* cr = (ClientRequest*)JSObjectGetPrivate(thisObject); 162 | if (!cr || !cr->client) { 163 | return JSValueMakeUndefined(ctx); 164 | } 165 | 166 | // Convert JS string to C string 167 | JSStringRef jsData = JSValueToStringCopy(ctx, args[0], exception); 168 | size_t dataLen = JSStringGetMaximumUTF8CStringSize(jsData); 169 | char* data = (char*)malloc(dataLen); 170 | JSStringGetUTF8CString(jsData, data, dataLen); 171 | JSStringRelease(jsData); 172 | 173 | // Prepare write request 174 | uv_write_t* writeReq = (uv_write_t*)malloc(sizeof(uv_write_t)); 175 | uv_buf_t buffer = uv_buf_init(data, strlen(data)); 176 | writeReq->data = data; 177 | 178 | // Write to the client 179 | uv_write(writeReq, (uv_stream_t*)cr->client, &buffer, 1, on_client_write); 180 | 181 | return JSValueMakeUndefined(ctx); 182 | } -------------------------------------------------------------------------------- /src/uv_event_loop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "runtime.h" 6 | 7 | // Event Loop Definitions 8 | uv_loop_t* loop = NULL; 9 | 10 | void init_event_loop(void) { 11 | loop = uv_default_loop(); 12 | } 13 | 14 | void run_event_loop(void) { 15 | uv_run(loop, UV_RUN_DEFAULT); 16 | } 17 | 18 | // Timer request structure 19 | typedef struct { 20 | uv_timer_t timer; 21 | JSContextRef ctx; 22 | JSObjectRef callback; 23 | uint32_t id; 24 | } TimerRequest; 25 | 26 | // Timer registry 27 | static TimerRequest* timer_registry[1024]; // Fixed-size registry for simplicity 28 | uint32_t next_timer_id = 0; 29 | 30 | // Timer callback handler 31 | static void on_timer(uv_timer_t* handle) { 32 | TimerRequest* tr = (TimerRequest*)handle->data; 33 | JSValueProtect(tr->ctx, tr->callback); 34 | JSValueRef args[] = { JSValueMakeNumber(tr->ctx, 0) }; 35 | JSObjectCallAsFunction(tr->ctx, tr->callback, NULL, 1, args, NULL); 36 | JSValueUnprotect(tr->ctx, tr->callback); 37 | } 38 | 39 | void set_timeout(JSContextRef ctx, JSObjectRef callback, uint64_t timeout) { 40 | TimerRequest* tr = malloc(sizeof(TimerRequest)); 41 | tr->ctx = ctx; 42 | tr->callback = callback; 43 | tr->id = next_timer_id++; 44 | timer_registry[tr->id] = tr; // Add to registry 45 | 46 | uv_timer_init(loop, &tr->timer); 47 | tr->timer.data = tr; 48 | uv_timer_start(&tr->timer, on_timer, timeout, 0); // No repeat 49 | JSValueProtect(ctx, callback); 50 | } 51 | 52 | void set_interval(JSContextRef ctx, JSObjectRef callback, uint64_t interval) { 53 | TimerRequest* tr = malloc(sizeof(TimerRequest)); 54 | tr->ctx = ctx; 55 | tr->callback = callback; 56 | tr->id = next_timer_id++; 57 | timer_registry[tr->id] = tr; // Add to registry 58 | 59 | uv_timer_init(loop, &tr->timer); 60 | tr->timer.data = tr; 61 | uv_timer_start(&tr->timer, on_timer, interval, interval); // Repeat 62 | JSValueProtect(ctx, callback); 63 | } 64 | 65 | void clear_timeout(JSContextRef ctx, uint32_t timer_id) { 66 | if (timer_id >= next_timer_id || !timer_registry[timer_id]) { 67 | return; // Invalid timer ID 68 | } 69 | 70 | TimerRequest* tr = timer_registry[timer_id]; 71 | uv_timer_stop(&tr->timer); 72 | uv_close((uv_handle_t*)&tr->timer, NULL); 73 | JSValueUnprotect(tr->ctx, tr->callback); 74 | timer_registry[timer_id] = NULL; // Remove from registry 75 | free(tr); 76 | } 77 | 78 | void clear_interval(JSContextRef ctx, uint32_t timer_id) { 79 | clear_timeout(ctx, timer_id); // Reuse clear_timeout logic 80 | } --------------------------------------------------------------------------------