1 Commits

Author SHA1 Message Date
John Alanbrook
e63115e152 initial attempt 2025-06-18 18:06:27 -05:00
1074 changed files with 281262 additions and 223906 deletions

17
.cell/cell.toml Normal file
View File

@@ -0,0 +1,17 @@
sdl_video = "main"
[dependencies]
extramath = "https://gitea.pockle.world/john/extramath@master"
[system]
ar_timer = 60
actor_memory = 0
net_service = 0.1
reply_timeout = 60
actor_max = "10_000"
stack_max = 0
[actors]
[actors.prosperon/sdl_video]
main = true
[actors.prosperon/prosperon]
main = true
[actors.prosperon]
main = true

6
.cell/lock.toml Normal file
View File

@@ -0,0 +1,6 @@
[modules]
[modules.extramath]
hash = "MCLZT3JABTAENS4WVXKGWJ7JPBLZER4YQ5VN2PE7ZD2Z4WYGTIMA===="
url = "https://gitea.pockle.world/john/extramath@master"
downloaded = "Monday June 2 12:07:20.42 PM -5 2025 AD"
commit = "84d81a19a8455bcf8dc494739e9e6d545df6ff2c"

View File

@@ -1,20 +1,9 @@
BasedOnStyle: GNU
Language: C
IndentWidth: 2
TabWidth: 2
UseTab: Never
ContinuationIndentWidth: 2
ContinuationIndentWidth: 2 # Indents continuation lines by 2 spaces
AllowShortFunctionsOnASingleLine: true
AllowShortBlocksOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
BreakBeforeBraces: Attach
ColumnLimit: 0
BreakFunctionDefinitionParameters: false
BinPackParameters: false
BinPackArguments: false
# --- Fix the "static T\nname(...)" style ---
AlwaysBreakAfterDefinitionReturnType: None
BreakAfterReturnType: None

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
*.mach binary merge=ours

30
.github/docker/Dockerfile.alpine vendored Normal file
View File

@@ -0,0 +1,30 @@
# Dockerfile.alpine
FROM alpine:edge
# Enable the edge and edge/community repositories.
# If you already have those in your base image, you might not need these echo lines.
RUN echo "https://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories && \
echo "https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories
# Update indexes and install packages
RUN apk update && \
apk add --no-cache \
build-base \
binutils \
mold \
meson \
cmake \
ninja \
git \
pkgconf \
ccache \
nodejs \
npm \
zip \
alsa-lib-dev \
pulseaudio-dev \
libudev-zero-dev \
wayland-dev \
wayland-protocols \
mesa-dev \
sdl3

32
.github/docker/Dockerfile.linux vendored Normal file
View File

@@ -0,0 +1,32 @@
FROM ubuntu:plucky
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip \
libasound2-dev \
libpulse-dev \
libudev-dev \
libwayland-dev \
wayland-protocols \
libxkbcommon-dev \
libx11-dev \
libxext-dev \
libxrandr-dev \
libxcursor-dev \
libxi-dev \
libxinerama-dev \
libxss-dev \
libegl1-mesa-dev \
libgl1-mesa-dev \
cmake \
ninja-build \
git \
build-essential \
binutils \
mold \
pkg-config \
meson \
ccache \
mingw-w64 \
wine \
npm nodejs zip && \
rm -rf /var/lib/apt/lists/*

15
.github/docker/Dockerfile.mingw vendored Normal file
View File

@@ -0,0 +1,15 @@
FROM ubuntu:plucky
RUN apt-get update && \
apt-get install -y --no-install-recommends \
mingw-w64 \
cmake \
ninja-build \
git \
build-essential \
binutils \
pkg-config \
zip \
ccache \
npm nodejs && \
rm -rf /var/lib/apt/lists/*

304
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,304 @@
name: Build and Deploy
on:
push:
branches: [ "*" ]
tags: [ "v*" ]
pull_request:
jobs:
# ──────────────────────────────────────────────────────────────
# LINUX BUILD
# ──────────────────────────────────────────────────────────────
build-linux:
runs-on: ubuntu-latest
container:
image: gitea.pockle.world/john/prosperon/linux:latest
steps:
- name: Check Out Code
uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Build Prosperon (Linux)
run: |
meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true
meson compile -C build
- name: Test Prosperon (Linux)
env: { TRACY_NO_INVARIANT_CHECK: 1 }
run: |
meson test --print-errorlogs -C build
- name: Upload Test Log (Linux)
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: testlog-linux
path: build/meson-logs/testlog.txt
- name: Upload Artifact (Linux)
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v3
with:
name: prosperon-artifacts-linux
path: build/prosperon
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea Registry
uses: docker/login-action@v3
with:
registry: gitea.pockle.world
username: ${{ secrets.USER_GITEA }}
password: ${{ secrets.TOKEN_GITEA }}
- name: Determine Docker Tag
id: docker_tag
run: |
if [[ "${{ github.ref }}" =~ ^refs/tags/v.* ]]; then
TAG=$(echo "${{ github.ref }}" | sed 's#refs/tags/##')
echo "tag=$TAG" >> $GITHUB_OUTPUT
else
echo "tag=latest" >> $GITHUB_OUTPUT
fi
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: gitea.pockle.world/john/prosperon:${{ steps.docker_tag.outputs.tag }}
platforms: linux/amd64
# ──────────────────────────────────────────────────────────────
# WINDOWS BUILD (MSYS2 / CLANG64)
# ──────────────────────────────────────────────────────────────
build-windows:
runs-on: win-native
strategy:
matrix: { msystem: [ CLANG64 ] }
steps:
- name: Check Out Code
uses: actions/checkout@v4
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
update: true
cache: true
install: |
git zip gzip tar base-devel
pacboy: |
meson
cmake
toolchain
- name: Build Prosperon (Windows)
shell: msys2 {0}
run: |
meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true -Dtracy:only_localhost=true -Dtracy:no_broadcast=true
meson compile -C build
- name: Test Prosperon (Windows)
shell: msys2 {0}
env:
TRACY_NO_INVARIANT_CHECK: 1
run: |
meson test --print-errorlogs -C build
- name: Upload Test Log (Windows)
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: testlog-windows
path: build/meson-logs/testlog.txt
- name: Upload Artifact (Windows)
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v3
with:
name: prosperon-artifacts-windows
path: build/prosperon.exe
# ──────────────────────────────────────────────────────────────
# MACOS BUILD
# ──────────────────────────────────────────────────────────────
build-macos:
runs-on: macos-latest
steps:
- name: Check Out Code
uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Build Prosperon (macOS)
run: |
meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true
meson compile -C build
- name: Test Prosperon (macOS)
run: |
meson test --print-errorlogs -C build
- name: Upload Test Log (macOS)
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: testlog-macos
path: build/meson-logs/testlog.txt
- name: Upload Artifact (macOS)
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v3
with:
name: prosperon-artifacts-macos
path: build/prosperon
# ──────────────────────────────────────────────────────────────
# PACKAGE CROSS-PLATFORM DIST
# ──────────────────────────────────────────────────────────────
package-dist:
needs: [ build-linux, build-windows, build-macos ]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v3
with: { fetch-depth: 0 }
- name: Get Latest Tag
id: get_tag
run: |
TAG=$(git describe --tags --abbrev=0)
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Download Linux Artifacts
uses: actions/download-artifact@v3
with:
name: prosperon-artifacts-linux
path: linux_artifacts
- name: Download Windows Artifacts
uses: actions/download-artifact@v3
with:
name: prosperon-artifacts-windows
path: windows_artifacts
- name: Download macOS Artifacts
uses: actions/download-artifact@v3
with:
name: prosperon-artifacts-macos
path: mac_artifacts
- name: Create Dist Folder
run: |
mkdir -p dist/linux dist/win dist/mac
cp README.md dist/
cp license.txt dist/
cp -r examples dist/
cp linux_artifacts/* dist/linux/
cp windows_artifacts/* dist/win/
cp mac_artifacts/* dist/mac/
- name: Package Final Dist
run: |
TAG=${{ steps.get_tag.outputs.tag }}
zip -r "prosperon-${TAG}.zip" dist
echo "Created prosperon-${TAG}.zip"
- name: Upload Final Dist
uses: actions/upload-artifact@v3
with:
name: "prosperon-${{ steps.get_tag.outputs.tag }}"
path: "prosperon-${{ steps.get_tag.outputs.tag }}.zip"
# ──────────────────────────────────────────────────────────────
# DEPLOY TO ITCH.IO (single ZIP containing all OSes)
# ──────────────────────────────────────────────────────────────
deploy-itch:
needs: [ package-dist ]
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v3
with: { fetch-depth: 0 }
- name: Get Latest Tag
id: get_tag
run: |
TAG=$(git describe --tags --abbrev=0)
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Download Final Distribution
uses: actions/download-artifact@v3
with:
name: "prosperon-${{ steps.get_tag.outputs.tag }}"
path: dist
- name: Set up Butler
uses: jdno/setup-butler@v1
- name: Push to itch.io
run: |
butler push "dist/prosperon-${{ steps.get_tag.outputs.tag }}.zip" \
${{ secrets.ITCHIO_USERNAME }}/prosperon:universal \
--userversion ${{ steps.get_tag.outputs.tag }}
env:
BUTLER_API_KEY: ${{ secrets.ITCHIO_API_KEY }}
# ──────────────────────────────────────────────────────────────
# DEPLOY TO SELF-HOSTED GITEA
# ──────────────────────────────────────────────────────────────
deploy-gitea:
needs: [ package-dist ]
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v3
with: { fetch-depth: 0 }
- name: Get Latest Tag & Commit Message
id: get_tag
run: |
TAG=$(git describe --tags --abbrev=0)
COMMIT_MSG=$(git log -1 --pretty=%B "$TAG")
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT
- name: Download Final Distribution
uses: actions/download-artifact@v3
with:
name: "prosperon-${{ steps.get_tag.outputs.tag }}"
path: dist
- name: Create / Update Gitea Release
run: |
TAG=${{ steps.get_tag.outputs.tag }}
ZIP=dist/prosperon-${TAG}.zip
BODY=$(echo "${{ steps.get_tag.outputs.commit_msg }}" | jq -R -s '.')
RELEASE=$(curl -s -H "Authorization: token ${{ secrets.TOKEN_GITEA }}" \
"https://gitea.pockle.world/api/v1/repos/john/prosperon/releases/tags/$TAG" | jq -r '.id')
if [ "$RELEASE" = "null" ] || [ -z "$RELEASE" ]; then
RELEASE=$(curl -X POST \
-H "Authorization: token ${{ secrets.TOKEN_GITEA }}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"target_commitish\":\"${{ github.sha }}\",\"name\":\"$TAG\",\"body\":$BODY,\"draft\":false,\"prerelease\":false}" \
"https://gitea.pockle.world/api/v1/repos/john/prosperon/releases" | jq -r '.id')
fi
curl -X POST \
-H "Authorization: token ${{ secrets.TOKEN_GITEA }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$ZIP" \
"https://gitea.pockle.world/api/v1/repos/john/prosperon/releases/$RELEASE/assets?name=prosperon-${TAG}.zip"
env:
TOKEN_GITEA: ${{ secrets.TOKEN_GITEA }}

10
.gitignore vendored
View File

@@ -1,7 +1,5 @@
.git/
.obj/
website/public/
website/.hugo_build.lock
bin/
build/
*.zip
@@ -16,7 +14,6 @@ build/
source/shaders/*.h
.DS_Store
*.html
!website/themes/**/*.html
.vscode
*.icns
icon.ico
@@ -24,10 +21,3 @@ steam/
subprojects/*/
build_dbg/
modules/
sdk/
artifacts/
discord_social_sdk/
discord_partner_sdk/
steam_api64.dll
subprojects/.wraplock
.gemini

26
AGENTS.md Normal file
View File

@@ -0,0 +1,26 @@
# AGENTS.md
## Project Overview
This is a game engine developed using a QuickJS fork as its scripting language. It is an actor based system, based on Douglas Crockford's Misty. It is a Meson compiled project with a number of dependencies.
## File Structure
- `source/`: Contains the C source code
- `scripts/`: Contains script code that is loaded on executable start, and modules
- `shaders/`: Contains shaders that ship with the engine (for shader based backends)
- `benchmarks/`: Benchmark programs for testing speed
- `tests/`: Unit tests
- `examples/`: Contains full game examples
## Coding Practices
- Use K&R style C
- Javascript style prefers objects and prototypical inheritence over ES6 classes, liberal use of closures, and var everywhere
## Instructions
- When generating code, adhere to the coding practices outlined above.
- When adding new features, ensure they align with the project's goals.
- When fixing bugs, review the code carefully before making changes.
- When writing unit tests, cover all important scenarios.
## Compiling, running, and testing
- To compile the code, run "make", which generates a prosperon executable in build_dbg/, and copy it into the root folder
- Run a test by giving it as its command: so ./prosperon tests/overling.js would run the test overling.js, ./prosperon tests/nota.js runs the nota benchmark

483
CLAUDE.md
View File

@@ -1,136 +1,405 @@
# ƿit (pit) Language Project
# CLAUDE.md
## Building
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Recompile after changes: `make`
Bootstrap from scratch (first time): `make bootstrap`
Run `cell --help` to see all CLI flags.
## Build Commands
## Code Style
### Build variants
- `make` - Make and install debug version. Usually all that's needed.
- `make fast` - Build optimized version
- `make release` - Build release version with LTO and optimizations
- `make small` - Build minimal size version
- `make web` - Build for web/emscripten platform
- `make crosswin` - Cross-compile for Windows using mingw32
All code uses 2 spaces for indentation. K&R style for C and Javascript.
### Testing
After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
## ƿit Script Quick Reference
## Scripting language
This is called "cell", but it is is a variant of javascript and extremely similar.
ƿit script files: `.ce` (actors) and `.cm` (modules). The syntax is similar to JavaScript with important differences listed below.
### Common development commands
- `meson setup build_<variant>` - Configure build directory
- `meson compile -C build_<variant>` - Compile in build directory
- `./build_dbg/prosperon examples/<example>` - Run example from build directory
- Copy prosperon to game directory and run: `cp build_dbg/prosperon <game-dir>/ && cd <game-dir> && ./prosperon`
### Key Differences from JavaScript
## Architecture Overview
- `var` (mutable) and `def` (constant) — no `let` or `const`
- `==` and `!=` are strict (no `===` or `!==`)
- No `undefined` — only `null`
- No classes — only objects and prototypes (`meme()`, `proto()`, `isa()`)
- No `for...in`, `for...of`, spread (`...`), rest params, or default params
- No named function declarations — use `var fn = function() {}` or arrow functions
- Variables must be declared at function body level only (not in if/while/for/blocks)
- All variables must be initialized at declaration (`var x` alone is an error; use `var x = null`)
- No `try`/`catch`/`throw` — use `disrupt`/`disruption`
- No arraybuffers — only `blob` (works with bits; must `stone(blob)` before reading)
- Identifiers can contain `?` and `!` (e.g., `nil?`, `set!`, `is?valid`)
- Prefer backticks for string interpolation; otherwise use `text()` to convert non-strings
- Everything should be lowercase
### Intrinsic Functions (always available, no `use()` needed)
The creator functions are **polymorphic** — behavior depends on argument types:
- `array(number)` — create array of size N filled with null
- `array(number, value_or_fn)` — create array with initial values
- `array(array)` — copy array
- `array(array, fn)` — map
- `array(array, array)` — concatenate
- `array(array, from, to)` — slice
- `array(record)` — get keys as array of text
- **`array(text)` — split text into individual characters** (e.g., `array("hello")``["h","e","l","l","o"]`)
- `array(text, separator)` — split by separator
- `array(text, length)` — split into chunks of length
- `text(array, separator)` — join array into text
- `text(number)` or `text(number, radix)` — number to text
- `text(text, from, to)` — substring
- `number(text)` or `number(text, radix)` — parse text to number
- `number(logical)` — boolean to number
- `record(record)` — copy
- `record(record, another)` — merge
- `record(array_of_keys)` — create record from keys
Other key intrinsics: `length()`, `stone()`, `is_stone()`, `print()`, `filter()`, `find()`, `reduce()`, `sort()`, `reverse()`, `some()`, `every()`, `starts_with()`, `ends_with()`, `meme()`, `proto()`, `isa()`, `splat()`, `apply()`, `extract()`, `replace()`, `search()`, `format()`, `lower()`, `upper()`, `trim()`
Sensory functions: `is_array()`, `is_text()`, `is_number()`, `is_object()`, `is_function()`, `is_null()`, `is_logical()`, `is_integer()`, `is_stone()`, etc.
### Standard Library (loaded with `use()`)
- `blob` — binary data (bits, not bytes)
- `time` — time constants and conversions
- `math` — trig, logarithms, roots (`math/radians`, `math/turns`)
- `json` — JSON encoding/decoding
- `random` — random number generation
Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty system. Key architectural principles:
### Actor Model
- Each actor runs on its own thread
- Communication only through message passing (no shared JavaScript objects)
- Hierarchical actor system with spawning/killing
- Actor lifecycle: awake, update, draw, garbage collection
- `.ce` files are actors (independent execution units, don't return values)
- `.cm` files are modules (return a value, cached and frozen)
- Actors never share memory; communicate via `$send()` message passing
- Actor intrinsics start with `$`: `$me`, `$stop()`, `$send()`, `$start()`, `$delay()`, `$receiver()`, `$clock()`, `$portal()`, `$contact()`, `$couple()`, `$unneeded()`, `$connection()`, `$time_limit()`
### JavaScript Style Guide
- Use `use()` function for imports (Misty-style, not ES6 import/export)
- Prefer closures and javascript objects and prototypes over ES6 style classes
- Follow existing JavaScript patterns in the codebase
- Functions as first-class citizens
- Do not use const or let; only var
### Requestors (async composition)
### Core Systems
1. **Actor System** (scripts/core/engine.js)
- Message passing via `send()`, `$_.receive()`
- Actor spawning/management
- Register-based component system (update, draw, gui, etc.)
`sequence()`, `parallel()`, `race()`, `fallback()` — compose asynchronous operations. See docs/requestors.md.
2. **Module System**
- `use()` function for loading modules
- Module paths: `scripts/modules/`, `scripts/modules/ext/`
- Custom QuickJS build with embedded C modules
### Error Handling
3. **Build System**
- Meson build configuration (Makefile is convenience wrapper)
- Multiple platform targets (Windows, macOS, Linux, Web)
- Custom QuickJS build in `subprojects/`
- Uses SDL3 for cross-platform support
### Engine Entry Points
- `source/prosperon.c` - Main C entry point
- `scripts/core/engine.js` - JavaScript engine initialization for system
- `scripts/core/base.js` has modifications to this Javascript runtime (for example, additions to the base Array, String, etc)
### Subprojects
- C code has many subprojects, who's source and sometimes documentation can be found in subprojects. subprojects/quickjs/doc has documentation for quickjs
### Resource System
- Scripts are bundled into `core.zip` during build
- Runtime module loading via PhysFS
- Resource paths checked in order: `/`, `scripts/modules/`, `scripts/modules/ext/`
### Notable Dependencies
- QuickJS (custom build) - JavaScript runtime
- SDL3 - Platform abstraction
- Chipmunk2D - Physics
- ENet - Networking
- Soloud - Audio
- Tracy - Profiling (when enabled)
## Development Tips
### Running Games
```bash
# Build first
make debug
# Run example from build directory
./build_dbg/prosperon examples/chess
# Or copy to game directory
cp build_dbg/prosperon examples/chess/
cd examples/chess
./prosperon
```
### Documentation
- Documentation is found in docs
- Documentation for the JS modules loaded with 'use' is docs/api/modules
- .md files directly in docs gives a high level overview
- docs/dull is what this specific Javascript system is (including alterations from quickjs/es6)
### Shader Development
- Shaders are in `shaders/` directory as HLSL
- Compile script: `shaders/compile.sh`
- Outputs to platform-specific formats: `dxil/`, `msl/`, `spv/`
### Example Games
Located in `examples/` directory:
- `chess` - Chess implementation (has its own Makefile)
- `pong` - Classic pong game
- `snake` - Snake game
- `tetris` - Tetris clone
- `bunnymark` - Performance test
### Testing
```bash
# Run all tests
meson test -C build_dbg
# Run specific test
./build_dbg/prosperon tests/spawn_actor.js
```
### Debugging
- Use debug build: `make debug`
- Tracy profiler support when enabled
- Console logging available via `log.console()`, `log.error()`, etc.
- Log files written to `.prosperon/log.txt`
# Project Structure Notes
## Core JavaScript Modules
- JavaScript modules are defined using the MISTUSE macro in jsffi.c
- The `js_os_funcs`, `js_io_funcs`, etc. arrays define the available functions for each module
- New functions are added with MIST_FUNC_DEF(module, function, args_count)
## File I/O
- `io.slurp(path)` - Reads a file as text
- `io.slurpbytes(path)` - Reads a file as an ArrayBuffer
- `io.slurpwrite(path, data)` - Writes data (string or ArrayBuffer) to a file
- `io.exists(path)` - Checks if a file exists
## Script Loading
- The `use(path)` function in engine.js loads JavaScript modules
- Script loading happens in prosperon.c and the engine.js script
- jsffi.c contains the C hooks for the QuickJS JavaScript engine
- Added functionality for bytecode compilation and loading:
- `os.compile_bytecode(source, filename)` - Compiles JS to bytecode, returns ArrayBuffer
- `os.eval_bytecode(bytecode)` - Evaluates bytecode from an ArrayBuffer
- `compile(scriptPath)` - Compiles a JS file to a .jso bytecode file
- Modified `use()` to check for .jso files before loading .js files
## QuickJS Bytecode API
- `JS_Eval` with JS_EVAL_FLAG_COMPILE_ONLY - Compiles without executing
- `JS_WriteObject` with JS_WRITE_OBJ_BYTECODE - Serializes to bytecode
- `JS_ReadObject` with JS_READ_OBJ_BYTECODE - Deserializes and loads bytecode
- Bytecode files use .jso extension alongside .js files
## Available JavaScript APIs
### Core APIs
- `actor` - Base prototype for all actor objects
- `$_` - Special global for actor messaging
- `prosperon` - Global engine interface
- `console` - Logging and debugging interface
### Framework APIs
- `moth` - Higher-level game framework that simplifies Prosperon usage
- Handles window creation, game loop, and event dispatching
- Provides simple configuration via config.js
- Auto-initializes systems like rendering and input
- Manages camera, resolution, and FPS automatically
### Rendering
- `draw2d` - 2D drawing primitives
- `render` - Low-level rendering operations
- `graphics` - Higher-level graphics utilities
- `camera` - Camera controls and transformations
- `sprite` - Sprite rendering and management
### Physics and Math
- `math` - Mathematical utilities
- `geometry` - Geometric calculations and shapes
- `transform` - Object transformations
### Input and Events
- `input` - Mouse, keyboard, and touch handling
- `event` - Event management system
### Networking
- `enet` - Networking through ENet library
- `http` - HTTP client capabilities
### Audio
- `sound` - Audio playback using SoLoud
### Utility Modules
- `time` - Time management and delays
- **Must be imported with `use('time')`**
- No `time.now()` function - use:
- `time.number()` - Number representation of current time
- `time.record()` - Struct representation of current time
- `time.text()` - Text representation of current time
- `io` - File I/O operations
- `json` - JSON parsing and serialization
- `util` - General utilities
- `color` - Color manipulation
- `miniz` - Compression utilities
- `nota` - Structured data format
- `wota` - Serialization format
- `qr` - QR code generation/reading
- `tween` - Animation tweening
- `spline` - Spline calculations
- `imgui` - Immediate mode GUI
## Game Development Patterns
### Project Structure
- Game config is typically in `config.js`
- Main entry point is `main.js`
- Resource loading through `resources.js`
### Actor Pattern Usage
- Create actors with `actor.spawn(script, config)`
- Start actors with `$_.start(callback, script)` - the system automatically sends a greeting, callback receives {type: 'greet', actor: actor_ref}
- No need to manually send greetings - `$_.start` handles this automatically
- Manage actor hierarchy with overlings and underlings
- Schedule actor tasks with `$_.delay()` method
- Clean up with `kill()` and `garbage()`
### Actor Messaging with Callbacks
When sending a message with a callback, respond by sending to the message itself:
```javascript
// Sender side:
send(actor, {type: 'status'}, response => {
log.console(response); // Handle the response
});
// Receiver side:
$_.receiver(msg => {
if (msg.type === 'status') {
send(msg, {status: 'ok'}); // Send response to the message itself
}
});
```
**Critical Rules for Message Callbacks**:
- **A message can only be used ONCE as a send target** - after sending a response to a message, it cannot be used again
- If you need to send multiple updates (like progress), only the download request message should be used for the final response
- Status requests should each get their own individual response
- Actor objects and message headers are completely opaque - never try to access internal properties
- Never access `msg.__HEADER__` or similar - the actor system handles routing internally
- Use `$_.delay()` to schedule work and avoid blocking the message receiver
### Game Loop Registration
- Register functions like `update`, `draw`, `gui`, etc.
- Set function.layer property to control execution order
- Use `Register` system to manage callbacks
### Program vs Module Pattern
- Programs are actor scripts that don't return values, they execute top-to-bottom
- Modules are files that return single values (usually objects) that get frozen
- Programs can spawn other programs as underlings
- Programs have lifecycle hooks: awake, update, draw, garbage, etc.
## Technical Capabilities
### Graphics Pipeline
- Supports multiple render backends (Direct3D, Metal, Vulkan via SDL3)
- Custom shader system with cross-platform compilation
- Sprite batching for efficient 2D rendering
- Camera systems for both 2D and 3D
### Asset Support
- Images: PNG, JPG, QOI, etc.
- Audio: Various formats through SoLoud
- Models: Basic 3D model support
- Custom formats: Aseprite animations, etc.
### Developer Tools
- Built-in documentation system with `cell.DOC`
- Tracy profiler integration for performance monitoring
- Imgui debugging tools
- Console logging with various severity levels
## Misty Networking Patterns
Prosperon implements the Misty actor networking model. Understanding these patterns is critical for building distributed applications.
### Portal Reply Pattern
Portals must reply with an actor object, not application data:
```javascript
// CORRECT: Portal replies with actor
$_.portal(e => {
send(e, $_); // Reply with server actor
}, 5678);
// WRONG: Portal sends application data
$_.portal(e => {
send(e, {type: 'game_start'}); // This breaks the pattern
}, 5678);
```
### Two-Phase Connection Protocol
Proper Misty networking follows a two-phase pattern:
**Phase 1: Actor Connection**
- Client contacts portal using `$_.contact()`
- Portal replies with an actor object
- This establishes the communication channel
**Phase 2: Application Communication**
- Client sends application messages to the received actor
- Normal bidirectional messaging begins
- Application logic handles game/service initialization
### Message Handling Best Practices
Messages should be treated as opaque objects with your application data:
```javascript
var fn = function() {
disrupt // bare keyword, no value
} disruption {
// handle error; can re-raise with disrupt
}
// CORRECT: Store actor references separately
var players = {};
$_.receiver(msg => {
if (msg.type === 'join_game' && msg.player_id) {
// Store the message for later response
players[msg.player_id] = msg;
// Later, respond to the stored message
send(players[msg.player_id], {type: 'game_start'});
}
});
// WRONG: Trying to access internal message properties
$_.receiver(msg => {
var sender = msg.__HEADER__.replycc; // Never do this!
});
```
### Push/Pop Syntax
### Return ID Lifecycle
- Each reply callback gets a unique return ID
- Return IDs are consumed once and then deleted
- Reusing message objects with return headers causes "Could not find return function" errors
- Always create clean actor references for ongoing communication
### Actor Object Transparency
Actor objects must be completely opaque black boxes that work identically regardless of transport:
```javascript
var a = [1, 2]
a[] = 3 // push: [1, 2, 3]
var v = a[] // pop: v is 3, a is [1, 2]
// Actor objects work transparently for:
// - Same-process communication (fastest - uses mailbox)
// - Inter-process communication (uses mailbox)
// - Network communication (uses ENet)
// The actor shouldn't know or care about the transport mechanism
send(opponent, {type: 'move', from: [0,0], to: [1,1]});
```
## C Integration
**Key Implementation Details:**
- `actor_send()` in `scripts/core/engine.js` handles routing based on available actor data
- Actor objects sent in message data automatically get address/port populated when received over network
- Three communication pathways: `os.mailbox_exist()` check → mailbox send → network send
- Actor objects must contain all necessary routing information for transparent messaging
- Declare everything `static` that can be
- Most files don't have headers; files in a package are not shared between packages
- No undefined in C API: use `JS_IsNull` and `JS_NULL` only
- A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('<package>/png')`)
### Common Networking Bugs
1. **Portal sending application data**: Portal should only establish actor connections
2. **Return ID collision**: Reusing messages with return headers for multiple sends
3. **Mixed phases**: Trying to do application logic during connection establishment
4. **Header pollution**: Using received message objects as actor references
5. **Missing actor address info**: Actor objects in message data need network address population (fixed in engine.js:746-766)
## Project Layout
### Example: Correct Chess Networking
```javascript
// Server: Portal setup
$_.portal(e => {
send(e, $_); // Just reply with actor
}, 5678);
- `source/` — C source for the cell runtime and CLI
- `docs/` — master documentation (Markdown), reflected on the website
- `website/` — Hugo site; theme at `website/themes/knr/`
- `internal/` — internal ƿit scripts (engine.cm etc.)
- `packages/` — core packages
- `Makefile` — build system (`make` to rebuild, `make bootstrap` for first build)
// Client: Two-phase connection
$_.contact((actor, reason) => {
if (actor) {
opponent = actor;
send(opponent, {type: 'join_game'}); // Phase 2: app messaging
}
}, {address: "localhost", port: 5678});
## Testing
After any C runtime changes, run all three test suites before considering the work done:
```
make # rebuild
./cell --dev vm_suite # VM-level tests (641 tests)
./cell --dev test suite # language-level tests (493 tests)
./cell --dev fuzz # fuzzer (100 iterations)
// Server: Handle application messages
$_.receiver(e => {
if (e.type === 'join_game') {
opponent = e.__HEADER__.replycc;
send(opponent, {type: 'game_start', your_color: 'black'});
}
});
```
All three must pass with 0 failures.
## Memory Management
## Documentation
The `docs/` folder is the single source of truth. The website at `website/` mounts it via Hugo. Key files:
- `docs/language.md` — language syntax reference
- `docs/functions.md` — all built-in intrinsic functions
- `docs/actors.md` — actor model and actor intrinsics
- `docs/requestors.md` — async requestor pattern
- `docs/library/*.md` — intrinsic type reference (text, number, array, object) and standard library modules
- When working with a conversational AI system like Claude, it's important to maintain a clean and focused memory
- Regularly review and update memories to ensure they remain relevant and helpful
- Delete or modify memories that are no longer accurate or useful
- Prioritize information that can genuinely assist in future interactions

54
Dockerfile Normal file
View File

@@ -0,0 +1,54 @@
# Builder stage
FROM ubuntu:plucky AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip \
libasound2-dev \
libpulse-dev \
libudev-dev \
libwayland-dev \
wayland-protocols \
libxkbcommon-dev \
libx11-dev \
libxext-dev \
libxrandr-dev \
libxcursor-dev \
libxi-dev \
libxinerama-dev \
libxss-dev \
libegl1-mesa-dev \
libgl1-mesa-dev \
cmake \
ninja-build \
git \
build-essential \
binutils \
pkg-config \
meson \
zip && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN git clone https://gitea.pockle.world/john/prosperon.git
WORKDIR /app/prosperon
RUN git checkout jsffi_refactor
RUN meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true
RUN meson compile -C build
# Runtime stage
FROM ubuntu:latest
# Install minimal runtime dependencies (e.g., for dynamically linked libraries)
RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/*
# Copy the compiled prosperon binary from the build stage
COPY --from=builder /app/prosperon/build/prosperon /usr/local/bin/prosperon
# Create an entrypoint script
RUN echo '#!/bin/bash' > /entrypoint.sh && \
echo '/usr/local/bin/prosperon "$@" &' >> /entrypoint.sh && \
echo 'tail -f /dev/null' >> /entrypoint.sh && \
chmod +x /entrypoint.sh
WORKDIR /workdir
ENTRYPOINT ["/entrypoint.sh"]

16
Info.plist Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>Prosperon</string>
<key>CFBundleIdentifier</key>
<string>pockle.world.prosperon</string>
<key>CFBundleName</key>
<string>Prosperon</string>
<key>CFBundleVersion</key>
<string>0.5</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2024 Pockle World. All rights reserved.</string>
</dict>
</plist>

26
LICENSE Normal file
View File

@@ -0,0 +1,26 @@
Prosperon Game Engine
Copyright (c) 2019-2024 John Alanbrook
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
(1) The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
(2) Any games or other derivative software must display the "Prosperon" logo
at near the beginning of the software's startup, before the chief purpose
of the software is underway.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

123
Makefile
View File

@@ -1,98 +1,29 @@
# Development build: creates libcell_runtime.dylib + thin main wrapper
# This is the default target for working on cell itself
#
# If cell doesn't exist yet, use 'make bootstrap' first (requires meson)
# or manually build with meson once.
#
# The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core
#
# See BUILDING.md for details on the bootstrap process and .mach files.
CELL_SHOP = $(HOME)/.cell
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
# .cm sources that compile to .mach bytecode
MACH_SOURCES = tokenize.cm parse.cm fold.cm mcode.cm \
internal/bootstrap.cm internal/engine.cm
maker: install
makecell:
cell pack core -o cell
cp cell /opt/homebrew/bin/
# Install core: symlink this directory to ~/.cell/core
install: bootstrap .mach.stamp $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE_PACKAGE)"
rm -rf $(CELL_CORE_PACKAGE)
ln -s $(PWD) $(CELL_CORE_PACKAGE)
cp cell /opt/homebrew/bin/
cp libcell_runtime.dylib /opt/homebrew/lib/
@echo "Core installed."
cell: libcell_runtime.dylib cell_main
cp cell_main cell
chmod +x cell
cp cell /opt/homebrew/bin/cell
cp libcell_runtime.dylib /opt/homebrew/lib/
# Build the shared runtime library (everything except main.c)
# Uses existing cell to run build -d
libcell_runtime.dylib: $(CELL_SHOP)/build/dynamic
cell build -d
cp $(CELL_SHOP)/build/dynamic/libcell_runtime.dylib .
# Build the thin main wrapper that links to libcell_runtime
cell_main: source/main.c libcell_runtime.dylib
cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib
# Regenerate .mach bytecode when any .cm source changes
.mach.stamp: $(MACH_SOURCES)
./cell --dev regen
@touch $@
# Force-regenerate all .mach bytecode files
regen:
./cell --core . regen
@touch .mach.stamp
# Create the cell shop directories
$(CELL_SHOP):
mkdir -p $(CELL_SHOP)
mkdir -p $(CELL_SHOP)/packages
mkdir -p $(CELL_SHOP)/cache
mkdir -p $(CELL_SHOP)/build
$(CELL_CORE):
ln -s $(PWD) $(CELL_CORE)
# Static build: creates a fully static cell binary (for distribution)
static:
cell build
cp $(CELL_SHOP)/build/static/cell .
# Bootstrap: build cell from scratch using meson (only needed once)
# Also installs core scripts to ~/.cell/core
bootstrap:
meson setup build_bootstrap -Dbuildtype=debugoptimized
meson compile -C build_bootstrap
cp build_bootstrap/cell .
cp build_bootstrap/libcell_runtime.dylib .
@echo "Bootstrap complete. Cell shop initialized at $(CELL_SHOP)"
@echo "Now run 'make' to rebuild with cell itself."
# Clean build artifacts
clean:
rm -rf $(CELL_SHOP)/build build_bootstrap
rm -f cell cell_main libcell_runtime.dylib .mach.stamp
# Ensure dynamic build directory exists
$(CELL_SHOP)/build/dynamic: $(CELL_SHOP)
mkdir -p $(CELL_SHOP)/build/dynamic
# Legacy meson target
meson:
debug: FORCE
meson setup build_dbg -Dbuildtype=debugoptimized
meson install -C build_dbg
meson install --only-changed -C build_dbg
.PHONY: cell static bootstrap clean meson install regen
fast: FORCE
meson setup build_fast
meson install -C build_fast
release: FORCE
meson setup -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true build_release
meson install -C build_release
sanitize: FORCE
meson setup -Db_sanitize=address -Db_sanitize=memory -Db_sanitize=leak -Db_sanitize=undefined build_sani
meson install -C build_sani
small: FORCE
meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small
meson install -C build_small
web: FORCE
meson setup -Deditor=false -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true --cross-file emscripten.cross build_web
meson compile -C build_web
crosswin: FORCE
meson setup -Dbuildtype=debugoptimized --cross-file mingw32.cross build_win
meson compile -C build_win
FORCE:

View File

@@ -1 +1,7 @@
Read the docs to get started.
Thank you for using Prosperon!
Provided are prosperon builds for all available platforms. Simply run prosperon for your platform in a game folder to play!
To get started, take a dive into the provided example games in the examples folder. You can either copy the prosperon executable into an example directory and run it there, or run `prosperon path/to/example` from the project root.
You can take a look through the docs folder for the prosperon manual to learn all about it. The manual is available on the web at [docs.prosperon.dev](https://docs.prosperon.dev).

103
add.ce
View File

@@ -1,103 +0,0 @@
// cell add <locator> [alias] - Add a dependency to the current package
//
// Usage:
// cell add <locator> Add a dependency using default alias
// cell add <locator> <alias> Add a dependency with custom alias
//
// This adds the dependency to cell.toml and installs it to the shop.
var shop = use('internal/shop')
var pkg = use('package')
var build = use('build')
var fd = use('fd')
var locator = null
var alias = null
array(args, function(arg) {
if (arg == '--help' || arg == '-h') {
log.console("Usage: cell add <locator> [alias]")
log.console("")
log.console("Add a dependency to the current package.")
log.console("")
log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/prosperon")
log.console(" cell add gitea.pockle.world/john/cell-image image")
log.console(" cell add ../local-package")
$stop()
} else if (!starts_with(arg, '-')) {
if (!locator) {
locator = arg
} else if (!alias) {
alias = arg
}
}
})
if (!locator) {
log.console("Usage: cell add <locator> [alias]")
$stop()
}
// Resolve relative paths to absolute paths
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
// Generate default alias from locator
if (!alias) {
// Use the last component of the locator as alias
var parts = array(locator, '/')
alias = parts[length(parts) - 1]
// Remove any version suffix
if (search(alias, '@') != null) {
alias = array(alias, '@')[0]
}
}
// Check we're in a package directory
var cwd = fd.realpath('.')
if (!fd.is_file(cwd + '/cell.toml')) {
log.error("Not in a package directory (no cell.toml found)")
$stop()
}
log.console("Adding " + locator + " as '" + alias + "'...")
// Add to local project's cell.toml
try {
pkg.add_dependency(null, locator, alias)
log.console(" Added to cell.toml")
} catch (e) {
log.error("Failed to update cell.toml: " + e)
$stop()
}
// Install to shop
try {
shop.get(locator)
shop.extract(locator)
// Build scripts
shop.build_package_scripts(locator)
// Build C code if any
try {
var target = build.detect_host_target()
build.build_dynamic(locator, target, 'release')
} catch (e) {
// Not all packages have C code
}
log.console(" Installed to shop")
} catch (e) {
log.error("Failed to install: " + e)
$stop()
}
log.console("Added " + alias + " (" + locator + ")")
$stop()

591
bench.ce
View File

@@ -1,591 +0,0 @@
var shop = use('internal/shop')
var pkg = use('package')
var fd = use('fd')
var time = use('time')
var json = use('json')
var blob = use('blob')
var os = use('os')
var testlib = use('internal/testlib')
var math = use('math/radians')
var _args = args == null ? [] : args
var target_pkg = null // null = current package
var target_bench = null // null = all benchmarks, otherwise specific bench file
var all_pkgs = false
// Benchmark configuration
def WARMUP_BATCHES = 3
def SAMPLES = 11 // Number of timing samples to collect
def TARGET_SAMPLE_NS = 20000000 // 20ms per sample (fast mode)
def MIN_SAMPLE_NS = 2000000 // 2ms minimum sample duration
def MIN_BATCH_SIZE = 1
def MAX_BATCH_SIZE = 100000000 // 100M iterations max per batch
// Statistical functions
function median(arr) {
if (length(arr) == 0) return 0
var sorted = sort(arr)
var mid = floor(length(arr) / 2)
if (length(arr) % 2 == 0) {
return (sorted[mid - 1] + sorted[mid]) / 2
}
return sorted[mid]
}
function mean(arr) {
if (length(arr) == 0) return 0
var sum = 0
arrfor(arr, function(val) {
sum += val
})
return sum / length(arr)
}
function stddev(arr, mean_val) {
if (length(arr) < 2) return 0
var sum_sq_diff = 0
arrfor(arr, function(val) {
var diff = val - mean_val
sum_sq_diff += diff * diff
})
return math.sqrt(sum_sq_diff / (length(arr) - 1))
}
function percentile(arr, p) {
if (length(arr) == 0) return 0
var sorted = sort(arr)
var idx = floor(length(arr) * p / 100)
if (idx >= length(arr)) idx = length(arr) - 1
return sorted[idx]
}
// Parse arguments similar to test.ce
function parse_args() {
var name = null
var lock = null
var resolved = null
var bench_path = null
if (length(_args) == 0) {
if (!testlib.is_valid_package('.')) {
log.console('No cell.toml found in current directory')
return false
}
target_pkg = null
return true
}
if (_args[0] == 'all') {
if (!testlib.is_valid_package('.')) {
log.console('No cell.toml found in current directory')
return false
}
target_pkg = null
return true
}
if (_args[0] == 'package') {
if (length(_args) < 2) {
log.console('Usage: cell bench package <name> [bench]')
log.console(' cell bench package all')
return false
}
if (_args[1] == 'all') {
all_pkgs = true
log.console('Benchmarking all packages...')
return true
}
name = _args[1]
lock = shop.load_lock()
if (lock[name]) {
target_pkg = name
} else if (starts_with(name, '/') && testlib.is_valid_package(name)) {
target_pkg = name
} else {
if (testlib.is_valid_package('.')) {
resolved = pkg.alias_to_package(null, name)
if (resolved) {
target_pkg = resolved
} else {
log.console(`Package not found: ${name}`)
return false
}
} else {
log.console(`Package not found: ${name}`)
return false
}
}
if (length(_args) >= 3) {
target_bench = _args[2]
}
log.console(`Benchmarking package: ${target_pkg}`)
return true
}
// cell bench benches/suite or cell bench <path>
bench_path = _args[0]
// Normalize path - add benches/ prefix if not present
if (!starts_with(bench_path, 'benches/') && !starts_with(bench_path, '/')) {
if (!fd.is_file(bench_path + '.cm') && !fd.is_file(bench_path)) {
if (fd.is_file('benches/' + bench_path + '.cm') || fd.is_file('benches/' + bench_path)) {
bench_path = 'benches/' + bench_path
}
}
}
target_bench = bench_path
target_pkg = null
if (!testlib.is_valid_package('.')) {
log.console('No cell.toml found in current directory')
return false
}
return true
}
if (!parse_args()) {
$stop()
return
}
// Collect benchmark files from a package
function collect_benches(package_name, specific_bench) {
var prefix = testlib.get_pkg_dir(package_name)
var benches_dir = prefix + '/benches'
if (!fd.is_dir(benches_dir)) return []
var files = pkg.list_files(package_name)
var bench_files = []
arrfor(files, function(f) {
var bench_name = null
var match_name = null
var match_base = null
if (starts_with(f, "benches/") && ends_with(f, ".cm")) {
if (specific_bench) {
bench_name = text(f, 0, -3)
match_name = specific_bench
if (!starts_with(match_name, 'benches/')) match_name = 'benches/' + match_name
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (bench_name != match_base) return
}
push(bench_files, f)
}
})
return bench_files
}
// Calibrate batch size for a benchmark
function calibrate_batch_size(bench_fn, is_batch) {
if (!is_batch) return 1
var n = MIN_BATCH_SIZE
var dt = 0
var start = 0
var new_n = 0
var calc = 0
var target_n = 0
// Find a batch size that takes at least MIN_SAMPLE_NS
while (n < MAX_BATCH_SIZE) {
if (!is_number(n) || n < 1) {
n = 1
break
}
start = os.now()
bench_fn(n)
dt = os.now() - start
if (dt >= MIN_SAMPLE_NS) break
new_n = n * 2
if (!is_number(new_n) || new_n > MAX_BATCH_SIZE) {
n = MAX_BATCH_SIZE
break
}
n = new_n
}
// Adjust to target sample duration
if (dt > 0 && dt < TARGET_SAMPLE_NS && is_number(n) && is_number(dt)) {
calc = n * TARGET_SAMPLE_NS / dt
if (is_number(calc) && calc > 0) {
target_n = floor(calc)
if (is_number(target_n) && target_n > 0) {
if (target_n > MAX_BATCH_SIZE) target_n = MAX_BATCH_SIZE
if (target_n < MIN_BATCH_SIZE) target_n = MIN_BATCH_SIZE
n = target_n
}
}
}
if (!is_number(n) || n < 1) {
n = 1
}
return n
}
// Run a single benchmark function
function run_single_bench(bench_fn, bench_name) {
var timings_per_op = []
var is_structured = is_object(bench_fn) && bench_fn.run
var is_batch = false
var batch_size = 1
var setup_fn = null
var run_fn = null
var teardown_fn = null
var calibrate_fn = null
var _detect = null
var i = 0
var state = null
var start = 0
var duration = 0
var ns_per_op = 0
if (is_structured) {
setup_fn = bench_fn.setup || function() { return null }
run_fn = bench_fn.run
teardown_fn = bench_fn.teardown || function(s) {}
// Check if run function accepts batch size
_detect = function() {
var test_state = setup_fn()
run_fn(1, test_state)
is_batch = true
if (teardown_fn) teardown_fn(test_state)
} disruption {
is_batch = false
}
_detect()
calibrate_fn = function(n) {
var s = setup_fn()
run_fn(n, s)
if (teardown_fn) teardown_fn(s)
}
batch_size = calibrate_batch_size(calibrate_fn, is_batch)
if (!is_number(batch_size) || batch_size < 1) {
batch_size = 1
}
} else {
// Simple function format
_detect = function() {
bench_fn(1)
is_batch = true
} disruption {
is_batch = false
}
_detect()
batch_size = calibrate_batch_size(bench_fn, is_batch)
}
if (!batch_size || batch_size < 1) {
batch_size = 1
}
// Warmup phase
for (i = 0; i < WARMUP_BATCHES; i++) {
if (!is_number(batch_size) || batch_size < 1) {
batch_size = 1
}
if (is_structured) {
state = setup_fn()
if (is_batch) {
run_fn(batch_size, state)
} else {
run_fn(state)
}
if (teardown_fn) teardown_fn(state)
} else {
if (is_batch) {
bench_fn(batch_size)
} else {
bench_fn()
}
}
}
// Measurement phase - collect SAMPLES timing samples
for (i = 0; i < SAMPLES; i++) {
if (!is_number(batch_size) || batch_size < 1) {
batch_size = 1
}
if (is_structured) {
state = setup_fn()
start = os.now()
if (is_batch) {
run_fn(batch_size, state)
} else {
run_fn(state)
}
duration = os.now() - start
if (teardown_fn) teardown_fn(state)
ns_per_op = is_batch ? duration / batch_size : duration
push(timings_per_op, ns_per_op)
} else {
start = os.now()
if (is_batch) {
bench_fn(batch_size)
} else {
bench_fn()
}
duration = os.now() - start
ns_per_op = is_batch ? duration / batch_size : duration
push(timings_per_op, ns_per_op)
}
}
// Calculate statistics
var mean_ns = mean(timings_per_op)
var median_ns = median(timings_per_op)
var min_ns = reduce(timings_per_op, min)
var max_ns = reduce(timings_per_op, max)
var stddev_ns = stddev(timings_per_op, mean_ns)
var p95_ns = percentile(timings_per_op, 95)
var p99_ns = percentile(timings_per_op, 99)
var ops_per_sec = 0
if (median_ns > 0) {
ops_per_sec = floor(1000000000 / median_ns)
}
return {
name: bench_name,
batch_size: batch_size,
samples: SAMPLES,
mean_ns: round(mean_ns),
median_ns: round(median_ns),
min_ns: round(min_ns),
max_ns: round(max_ns),
stddev_ns: round(stddev_ns),
p95_ns: round(p95_ns),
p99_ns: round(p99_ns),
ops_per_sec: ops_per_sec
}
}
// Format nanoseconds for display
function format_ns(ns) {
if (ns < 1000) return `${ns}ns`
if (ns < 1000000) return `${round(ns / 1000 * 100) / 100}µs`
if (ns < 1000000000) return `${round(ns / 1000000 * 100) / 100}ms`
return `${round(ns / 1000000000 * 100) / 100}s`
}
// Format ops/sec for display
function format_ops(ops) {
if (ops < 1000) return `${ops} ops/s`
if (ops < 1000000) return `${round(ops / 1000 * 100) / 100}K ops/s`
if (ops < 1000000000) return `${round(ops / 1000000 * 100) / 100}M ops/s`
return `${round(ops / 1000000000 * 100) / 100}G ops/s`
}
// Run benchmarks for a package
function run_benchmarks(package_name, specific_bench) {
var bench_files = collect_benches(package_name, specific_bench)
var pkg_result = {
package: package_name || "local",
files: [],
total: 0
}
if (length(bench_files) == 0) return pkg_result
if (package_name) log.console(`Running benchmarks for ${package_name}`)
else log.console(`Running benchmarks for local package`)
arrfor(bench_files, function(f) {
var mod_path = text(f, 0, -3)
var load_error = false
var bench_mod = null
var use_pkg = null
var benches = []
var error_result = null
var file_result = {
name: f,
benchmarks: []
}
var _load_file = function() {
use_pkg = package_name ? package_name : fd.realpath('.')
bench_mod = shop.use(mod_path, use_pkg)
if (is_function(bench_mod)) {
push(benches, {name: 'main', fn: bench_mod})
} else if (is_object(bench_mod)) {
arrfor(array(bench_mod), function(k) {
if (is_function(bench_mod[k]))
push(benches, {name: k, fn: bench_mod[k]})
})
}
if (length(benches) > 0) {
log.console(` ${f}`)
arrfor(benches, function(b) {
var bench_error = false
var result = null
var _run_bench = function() {
result = run_single_bench(b.fn, b.name)
result.package = pkg_result.package
push(file_result.benchmarks, result)
pkg_result.total++
log.console(` ${result.name}`)
log.console(` ${format_ns(result.median_ns)}/op ${format_ops(result.ops_per_sec)}`)
log.console(` min: ${format_ns(result.min_ns)} max: ${format_ns(result.max_ns)} stddev: ${format_ns(result.stddev_ns)}`)
if (result.batch_size > 1) {
log.console(` batch: ${result.batch_size} samples: ${result.samples}`)
}
} disruption {
bench_error = true
}
_run_bench()
if (bench_error) {
log.console(` ERROR ${b.name}`)
error_result = {
package: pkg_result.package,
name: b.name,
error: "benchmark disrupted"
}
push(file_result.benchmarks, error_result)
pkg_result.total++
}
})
}
} disruption {
load_error = true
}
_load_file()
if (load_error) {
log.console(` Error loading ${f}`)
error_result = {
package: pkg_result.package,
name: "load_module",
error: "error loading module"
}
push(file_result.benchmarks, error_result)
pkg_result.total++
}
if (length(file_result.benchmarks) > 0) {
push(pkg_result.files, file_result)
}
})
return pkg_result
}
// Run all benchmarks
var all_results = []
var packages = null
if (all_pkgs) {
if (testlib.is_valid_package('.')) {
push(all_results, run_benchmarks(null, null))
}
packages = shop.list_packages()
arrfor(packages, function(p) {
push(all_results, run_benchmarks(p, null))
})
} else {
push(all_results, run_benchmarks(target_pkg, target_bench))
}
// Calculate totals
var total_benches = 0
arrfor(all_results, function(result) {
total_benches += result.total
})
log.console(`----------------------------------------`)
log.console(`Benchmarks: ${total_benches} total`)
// Generate reports
function generate_reports() {
var timestamp = text(floor(time.number()))
var report_dir = shop.get_reports_dir() + '/bench_' + timestamp
testlib.ensure_dir(report_dir)
var txt_report = `BENCHMARK REPORT
Date: ${time.text(time.number())}
Total benchmarks: ${total_benches}
=== SUMMARY ===
`
arrfor(all_results, function(pkg_res) {
if (pkg_res.total == 0) return
txt_report += `Package: ${pkg_res.package}\n`
arrfor(pkg_res.files, function(f) {
txt_report += ` ${f.name}\n`
arrfor(f.benchmarks, function(b) {
if (b.error) {
txt_report += ` ERROR ${b.name}: ${b.error}\n`
} else {
txt_report += ` ${b.name}: ${format_ns(b.median_ns)}/op (${format_ops(b.ops_per_sec)})\n`
}
})
})
})
txt_report += `\n=== DETAILED RESULTS ===\n`
arrfor(all_results, function(pkg_res) {
if (pkg_res.total == 0) return
arrfor(pkg_res.files, function(f) {
arrfor(f.benchmarks, function(b) {
if (b.error) return
txt_report += `\n${pkg_res.package}::${b.name}\n`
txt_report += ` batch_size: ${b.batch_size} samples: ${b.samples}\n`
txt_report += ` median: ${format_ns(b.median_ns)}/op\n`
txt_report += ` mean: ${format_ns(b.mean_ns)}/op\n`
txt_report += ` min: ${format_ns(b.min_ns)}\n`
txt_report += ` max: ${format_ns(b.max_ns)}\n`
txt_report += ` stddev: ${format_ns(b.stddev_ns)}\n`
txt_report += ` p95: ${format_ns(b.p95_ns)}\n`
txt_report += ` p99: ${format_ns(b.p99_ns)}\n`
txt_report += ` ops/s: ${format_ops(b.ops_per_sec)}\n`
})
})
})
testlib.ensure_dir(report_dir)
fd.slurpwrite(`${report_dir}/bench.txt`, stone(blob(txt_report)))
log.console(`Report written to ${report_dir}/bench.txt`)
// Generate JSON per package
arrfor(all_results, function(pkg_res) {
if (pkg_res.total == 0) return
var pkg_benches = []
arrfor(pkg_res.files, function(f) {
arrfor(f.benchmarks, function(benchmark) {
push(pkg_benches, benchmark)
})
})
var json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
fd.slurpwrite(json_path, stone(blob(json.encode(pkg_benches))))
})
}
generate_reports()
$stop()

View File

@@ -1,232 +0,0 @@
// actor_patterns.cm — Actor concurrency benchmarks
// Message passing, fan-out/fan-in, mailbox throughput.
// These use structured benchmarks with setup/run/teardown.
// Note: actor benchmarks are measured differently from pure compute.
// Each iteration sends messages and waits for results, so they're
// inherently slower but test real concurrency costs.
// Simple ping-pong: two actors sending messages back and forth
// Since we can't create real actors from a module, we simulate
// the message-passing patterns with function call overhead that
// mirrors what the actor dispatch does.
// Simulate message dispatch overhead
function make_mailbox() {
return {
queue: [],
delivered: 0
}
}
function send(mailbox, msg) {
push(mailbox.queue, msg)
return null
}
function receive(mailbox) {
if (length(mailbox.queue) == 0) return null
mailbox.delivered++
return pop(mailbox.queue)
}
function drain(mailbox) {
var count = 0
while (length(mailbox.queue) > 0) {
pop(mailbox.queue)
count++
}
return count
}
// Ping-pong: simulate two actors exchanging messages
function ping_pong(rounds) {
var box_a = make_mailbox()
var box_b = make_mailbox()
var i = 0
var msg = null
send(box_a, {type: "ping", val: 0})
for (i = 0; i < rounds; i++) {
// A receives and sends to B
msg = receive(box_a)
if (msg) {
send(box_b, {type: "pong", val: msg.val + 1})
}
// B receives and sends to A
msg = receive(box_b)
if (msg) {
send(box_a, {type: "ping", val: msg.val + 1})
}
}
return box_a.delivered + box_b.delivered
}
// Fan-out: one sender, N receivers
function fan_out(n_receivers, messages_per) {
var receivers = []
var i = 0
var j = 0
for (i = 0; i < n_receivers; i++) {
push(receivers, make_mailbox())
}
// Send messages to all receivers
for (j = 0; j < messages_per; j++) {
for (i = 0; i < n_receivers; i++) {
send(receivers[i], {seq: j, data: j * 17})
}
}
// All receivers drain
var total = 0
for (i = 0; i < n_receivers; i++) {
total += drain(receivers[i])
}
return total
}
// Fan-in: N senders, one receiver
function fan_in(n_senders, messages_per) {
var inbox = make_mailbox()
var i = 0
var j = 0
// Each sender sends messages
for (i = 0; i < n_senders; i++) {
for (j = 0; j < messages_per; j++) {
send(inbox, {sender: i, seq: j, data: i * 100 + j})
}
}
// Receiver processes all
var total = 0
var msg = null
msg = receive(inbox)
while (msg) {
total += msg.data
msg = receive(inbox)
}
return total
}
// Pipeline: chain of processors
function pipeline(stages, items) {
var boxes = []
var i = 0
var j = 0
var msg = null
for (i = 0; i <= stages; i++) {
push(boxes, make_mailbox())
}
// Feed input
for (i = 0; i < items; i++) {
send(boxes[0], {val: i})
}
// Process each stage
for (j = 0; j < stages; j++) {
msg = receive(boxes[j])
while (msg) {
send(boxes[j + 1], {val: msg.val * 2 + 1})
msg = receive(boxes[j])
}
}
// Drain output
var total = 0
msg = receive(boxes[stages])
while (msg) {
total += msg.val
msg = receive(boxes[stages])
}
return total
}
// Request-response pattern (simulate RPC)
function request_response(n_requests) {
var client_box = make_mailbox()
var server_box = make_mailbox()
var i = 0
var req = null
var resp = null
var total = 0
for (i = 0; i < n_requests; i++) {
// Client sends request
send(server_box, {id: i, payload: i * 3, reply_to: client_box})
// Server processes
req = receive(server_box)
if (req) {
send(req.reply_to, {id: req.id, result: req.payload * 2 + 1})
}
// Client receives response
resp = receive(client_box)
if (resp) {
total += resp.result
}
}
return total
}
return {
// Ping-pong: 10K rounds
ping_pong_10k: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += ping_pong(10000)
}
return x
},
// Fan-out: 100 receivers, 100 messages each
fan_out_100x100: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += fan_out(100, 100)
}
return x
},
// Fan-in: 100 senders, 100 messages each
fan_in_100x100: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += fan_in(100, 100)
}
return x
},
// Pipeline: 10 stages, 1000 items
pipeline_10x1k: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += pipeline(10, 1000)
}
return x
},
// Request-response: 5K requests
rpc_5k: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += request_response(5000)
}
return x
}
}

View File

@@ -1,141 +0,0 @@
// cli_tool.cm — CLI tool simulation (macro benchmark)
// Parse args + process data + transform + format output.
// Simulates a realistic small utility program.
var json = use('json')
// Generate fake records
function generate_records(n) {
var records = []
var x = 42
var i = 0
var status_vals = ["active", "inactive", "pending", "archived"]
var dept_vals = ["eng", "sales", "ops", "hr", "marketing"]
for (i = 0; i < n; i++) {
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
push(records, {
id: i + 1,
name: `user_${i}`,
score: (x % 1000) / 10,
status: status_vals[i % 4],
department: dept_vals[i % 5]
})
}
return records
}
// Filter records by field value
function filter_records(records, field, value) {
var result = []
var i = 0
for (i = 0; i < length(records); i++) {
if (records[i][field] == value) {
push(result, records[i])
}
}
return result
}
// Group by a field
function group_by(records, field) {
var groups = {}
var i = 0
var key = null
for (i = 0; i < length(records); i++) {
key = records[i][field]
if (!key) key = "unknown"
if (!groups[key]) groups[key] = []
push(groups[key], records[i])
}
return groups
}
// Aggregate: compute stats per group
function aggregate(groups) {
var keys = array(groups)
var result = []
var i = 0
var j = 0
var grp = null
var total = 0
var mn = 0
var mx = 0
for (i = 0; i < length(keys); i++) {
grp = groups[keys[i]]
total = 0
mn = 999999
mx = 0
for (j = 0; j < length(grp); j++) {
total += grp[j].score
if (grp[j].score < mn) mn = grp[j].score
if (grp[j].score > mx) mx = grp[j].score
}
push(result, {
group: keys[i],
count: length(grp),
average: total / length(grp),
low: mn,
high: mx
})
}
return result
}
// Full pipeline: load → filter → sort → group → aggregate → encode
function run_pipeline(n_records) {
// Generate data
var records = generate_records(n_records)
// Filter to active records
var filtered = filter_records(records, "status", "active")
// Sort by score
filtered = sort(filtered, "score")
// Limit to first 50
if (length(filtered) > 50) {
filtered = array(filtered, 0, 50)
}
// Group and aggregate
var groups = group_by(filtered, "department")
var stats = aggregate(groups)
stats = sort(stats, "average")
// Encode as JSON
var output = json.encode(stats)
return length(output)
}
return {
// Small dataset (100 records)
cli_pipeline_100: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += run_pipeline(100)
}
return x
},
// Medium dataset (1000 records)
cli_pipeline_1k: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += run_pipeline(1000)
}
return x
},
// Large dataset (10K records)
cli_pipeline_10k: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += run_pipeline(10000)
}
return x
}
}

View File

@@ -1,162 +0,0 @@
// deltablue.cm — Constraint solver kernel (DeltaBlue-inspired)
// Dynamic dispatch, pointer chasing, object-heavy workload.
def REQUIRED = 0
def STRONG = 1
def NORMAL = 2
def WEAK = 3
def WEAKEST = 4
function make_variable(name, value) {
return {
name: name,
value: value,
constraints: [],
determined_by: null,
stay: true,
mark: 0
}
}
function make_constraint(strength, variables, satisfy_fn) {
return {
strength: strength,
variables: variables,
satisfy: satisfy_fn,
output: null
}
}
// Constraint propagation: simple forward solver
function propagate(vars, constraints) {
var changed = true
var passes = 0
var max_passes = length(constraints) * 3
var i = 0
var c = null
var old_val = 0
while (changed && passes < max_passes) {
changed = false
passes++
for (i = 0; i < length(constraints); i++) {
c = constraints[i]
old_val = c.output ? c.output.value : null
c.satisfy(c)
if (c.output && c.output.value != old_val) {
changed = true
}
}
}
return passes
}
// Build a chain of equality constraints: v[i] = v[i-1] + 1
function build_chain(n) {
var vars = []
var constraints = []
var i = 0
for (i = 0; i < n; i++) {
push(vars, make_variable(`v${i}`, 0))
}
// Set first variable
vars[0].value = 1
var c = null
for (i = 1; i < n; i++) {
c = make_constraint(NORMAL, [vars[i - 1], vars[i]], function(self) {
self.variables[1].value = self.variables[0].value + 1
self.output = self.variables[1]
})
push(constraints, c)
push(vars[i].constraints, c)
}
return {vars: vars, constraints: constraints}
}
// Build a projection: pairs of variables with scaling constraints
function build_projection(n) {
var src = []
var dst = []
var constraints = []
var i = 0
for (i = 0; i < n; i++) {
push(src, make_variable(`src${i}`, i * 10))
push(dst, make_variable(`dst${i}`, 0))
}
var scale_c = null
for (i = 0; i < n; i++) {
scale_c = make_constraint(STRONG, [src[i], dst[i]], function(self) {
self.variables[1].value = self.variables[0].value * 2 + 1
self.output = self.variables[1]
})
push(constraints, scale_c)
push(dst[i].constraints, scale_c)
}
return {src: src, dst: dst, constraints: constraints}
}
// Edit constraint: change a source, re-propagate
function run_edits(system, edits) {
var i = 0
var total_passes = 0
for (i = 0; i < edits; i++) {
system.vars[0].value = i
total_passes += propagate(system.vars, system.constraints)
}
return total_passes
}
return {
// Chain of 100 variables, propagate
chain_100: function(n) {
var i = 0
var chain = null
var x = 0
for (i = 0; i < n; i++) {
chain = build_chain(100)
x += propagate(chain.vars, chain.constraints)
}
return x
},
// Chain of 500 variables, propagate
chain_500: function(n) {
var i = 0
var chain = null
var x = 0
for (i = 0; i < n; i++) {
chain = build_chain(500)
x += propagate(chain.vars, chain.constraints)
}
return x
},
// Projection of 100 pairs
projection_100: function(n) {
var i = 0
var proj = null
var x = 0
for (i = 0; i < n; i++) {
proj = build_projection(100)
x += propagate(proj.src, proj.constraints)
}
return x
},
// Edit and re-propagate (incremental update)
chain_edit_100: function(n) {
var chain = build_chain(100)
var i = 0
var x = 0
for (i = 0; i < n; i++) {
chain.vars[0].value = i
x += propagate(chain.vars, chain.constraints)
}
return x
}
}

View File

@@ -1,126 +0,0 @@
// fibonacci.cm — Fibonacci variants kernel
// Tests recursion overhead, memoization patterns, iteration vs recursion.
// Naive recursive (exponential) — measures call overhead
function fib_naive(n) {
if (n <= 1) return n
return fib_naive(n - 1) + fib_naive(n - 2)
}
// Iterative (linear)
function fib_iter(n) {
var a = 0
var b = 1
var i = 0
var tmp = 0
for (i = 0; i < n; i++) {
tmp = a + b
a = b
b = tmp
}
return a
}
// Memoized recursive (tests object property lookup + recursion)
function make_memo_fib() {
var cache = {}
var fib = function(n) {
var key = text(n)
if (cache[key]) return cache[key]
var result = null
if (n <= 1) {
result = n
} else {
result = fib(n - 1) + fib(n - 2)
}
cache[key] = result
return result
}
return fib
}
// CPS (continuation passing style) — tests closure creation
function fib_cps(n, cont) {
if (n <= 1) return cont(n)
return fib_cps(n - 1, function(a) {
return fib_cps(n - 2, function(b) {
return cont(a + b)
})
})
}
// Matrix exponentiation style (accumulator)
function fib_matrix(n) {
var a = 1
var b = 0
var c = 0
var d = 1
var ta = 0
var tb = 0
var m = n
while (m > 0) {
if (m % 2 == 1) {
ta = a * d + b * c // wrong but stresses numeric ops
tb = b * d + a * c
a = ta
b = tb
}
ta = c * c + d * d
tb = d * (2 * c + d)
c = ta
d = tb
m = floor(m / 2)
}
return b
}
return {
fib_naive_25: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) x += fib_naive(25)
return x
},
fib_naive_30: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) x += fib_naive(30)
return x
},
fib_iter_80: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) x += fib_iter(80)
return x
},
fib_memo_100: function(n) {
var i = 0
var x = 0
var fib = null
for (i = 0; i < n; i++) {
fib = make_memo_fib()
x += fib(100)
}
return x
},
fib_cps_20: function(n) {
var i = 0
var x = 0
var identity = function(v) { return v }
for (i = 0; i < n; i++) {
x += fib_cps(20, identity)
}
return x
},
fib_matrix_80: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) x += fib_matrix(80)
return x
}
}

View File

@@ -1,159 +0,0 @@
// hash_workload.cm — Hash-heavy / word-count / map-reduce kernel
// Stresses record (object) creation, property access, and string handling.
function make_words(count) {
// Generate a repeating word list to simulate text processing
var base_words = [
"the", "quick", "brown", "fox", "jumps", "over", "lazy", "dog",
"and", "cat", "sat", "on", "mat", "with", "hat", "bat",
"alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta",
"hello", "world", "foo", "bar", "baz", "qux", "quux", "corge"
]
var words = []
var i = 0
for (i = 0; i < count; i++) {
push(words, base_words[i % length(base_words)])
}
return words
}
// Word frequency count
function word_count(words) {
var freq = {}
var i = 0
var w = null
for (i = 0; i < length(words); i++) {
w = words[i]
if (freq[w]) {
freq[w] = freq[w] + 1
} else {
freq[w] = 1
}
}
return freq
}
// Find top-N words by frequency
function top_n(freq, n) {
var keys = array(freq)
var pairs = []
var i = 0
for (i = 0; i < length(keys); i++) {
push(pairs, {word: keys[i], count: freq[keys[i]]})
}
var sorted = sort(pairs, "count")
// Return last N (highest counts)
var result = []
var start = length(sorted) - n
if (start < 0) start = 0
for (i = start; i < length(sorted); i++) {
push(result, sorted[i])
}
return result
}
// Histogram: group words by length
function group_by_length(words) {
var groups = {}
var i = 0
var w = null
var k = null
for (i = 0; i < length(words); i++) {
w = words[i]
k = text(length(w))
if (!groups[k]) groups[k] = []
push(groups[k], w)
}
return groups
}
// Simple hash table with chaining (stress property access patterns)
function hash_table_ops(n) {
var table = {}
var i = 0
var k = null
var collisions = 0
// Insert phase
for (i = 0; i < n; i++) {
k = `key_${i % 512}`
if (table[k]) collisions++
table[k] = i
}
// Lookup phase
var found = 0
for (i = 0; i < n; i++) {
k = `key_${i % 512}`
if (table[k]) found++
}
// Delete phase
var deleted = 0
for (i = 0; i < n; i += 3) {
k = `key_${i % 512}`
if (table[k]) {
delete table[k]
deleted++
}
}
return found - deleted + collisions
}
var words_1k = make_words(1000)
var words_10k = make_words(10000)
return {
// Word count on 1K words
wordcount_1k: function(n) {
var i = 0
var freq = null
for (i = 0; i < n; i++) {
freq = word_count(words_1k)
}
return freq
},
// Word count on 10K words
wordcount_10k: function(n) {
var i = 0
var freq = null
for (i = 0; i < n; i++) {
freq = word_count(words_10k)
}
return freq
},
// Word count + top-10 extraction
wordcount_top10: function(n) {
var i = 0
var freq = null
var top = null
for (i = 0; i < n; i++) {
freq = word_count(words_10k)
top = top_n(freq, 10)
}
return top
},
// Group words by length
group_by_len: function(n) {
var i = 0
var groups = null
for (i = 0; i < n; i++) {
groups = group_by_length(words_10k)
}
return groups
},
// Hash table insert/lookup/delete
hash_table: function(n) {
var i = 0
var x = 0
for (i = 0; i < n; i++) {
x += hash_table_ops(2048)
}
return x
}
}

View File

@@ -1,167 +0,0 @@
// json_walk.cm — JSON parse + walk + serialize kernel
// Stresses strings, records, arrays, and recursive traversal.
var json = use('json')
function make_nested_object(depth, breadth) {
var obj = {}
var i = 0
var k = null
if (depth <= 0) {
for (i = 0; i < breadth; i++) {
k = `key_${i}`
obj[k] = i * 3.14
}
return obj
}
for (i = 0; i < breadth; i++) {
k = `node_${i}`
obj[k] = make_nested_object(depth - 1, breadth)
}
obj.value = depth
obj.name = `level_${depth}`
return obj
}
function make_array_data(size) {
var arr = []
var i = 0
for (i = 0; i < size; i++) {
push(arr, {
id: i,
name: `item_${i}`,
active: i % 2 == 0,
score: i * 1.5,
tags: [`tag_${i % 5}`, `tag_${(i + 1) % 5}`]
})
}
return arr
}
// Walk an object tree, counting nodes
function walk_count(obj) {
var count = 1
var keys = null
var i = 0
var v = null
if (is_object(obj)) {
keys = array(obj)
for (i = 0; i < length(keys); i++) {
v = obj[keys[i]]
if (is_object(v) || is_array(v)) {
count += walk_count(v)
}
}
} else if (is_array(obj)) {
for (i = 0; i < length(obj); i++) {
v = obj[i]
if (is_object(v) || is_array(v)) {
count += walk_count(v)
}
}
}
return count
}
// Walk and extract all numbers
function walk_sum(obj) {
var sum = 0
var keys = null
var i = 0
var v = null
if (is_object(obj)) {
keys = array(obj)
for (i = 0; i < length(keys); i++) {
v = obj[keys[i]]
if (is_number(v)) {
sum += v
} else if (is_object(v) || is_array(v)) {
sum += walk_sum(v)
}
}
} else if (is_array(obj)) {
for (i = 0; i < length(obj); i++) {
v = obj[i]
if (is_number(v)) {
sum += v
} else if (is_object(v) || is_array(v)) {
sum += walk_sum(v)
}
}
}
return sum
}
// Pre-build test data strings
var nested_obj = make_nested_object(3, 4)
var nested_json = json.encode(nested_obj)
var array_data = make_array_data(200)
var array_json = json.encode(array_data)
return {
// Parse nested JSON
json_parse_nested: function(n) {
var i = 0
var obj = null
for (i = 0; i < n; i++) {
obj = json.decode(nested_json)
}
return obj
},
// Parse array-of-records JSON
json_parse_array: function(n) {
var i = 0
var arr = null
for (i = 0; i < n; i++) {
arr = json.decode(array_json)
}
return arr
},
// Encode nested object to JSON
json_encode_nested: function(n) {
var i = 0
var s = null
for (i = 0; i < n; i++) {
s = json.encode(nested_obj)
}
return s
},
// Encode array to JSON
json_encode_array: function(n) {
var i = 0
var s = null
for (i = 0; i < n; i++) {
s = json.encode(array_data)
}
return s
},
// Parse + walk + count
json_roundtrip_walk: function(n) {
var i = 0
var obj = null
var count = 0
for (i = 0; i < n; i++) {
obj = json.decode(nested_json)
count += walk_count(obj)
}
return count
},
// Parse + sum all numbers + re-encode
json_roundtrip_full: function(n) {
var i = 0
var obj = null
var sum = 0
var out = null
for (i = 0; i < n; i++) {
obj = json.decode(array_json)
sum += walk_sum(obj)
out = json.encode(obj)
}
return sum
}
}

View File

@@ -1,494 +0,0 @@
// micro_ops.cm — microbenchmarks for core operations
function blackhole(sink, x) {
return (sink + (x | 0)) | 0
}
function make_obj_xy(x, y) {
return {x: x, y: y}
}
function make_obj_yx(x, y) {
// Different insertion order to force a different shape
return {y: y, x: x}
}
function make_shapes(n) {
var out = []
var i = 0
var o = null
for (i = 0; i < n; i++) {
o = {a: i}
o[`p${i}`] = i
push(out, o)
}
return out
}
function make_packed_array(n) {
var a = []
var i = 0
for (i = 0; i < n; i++) push(a, i)
return a
}
function make_holey_array(n) {
var a = []
var i = 0
for (i = 0; i < n; i += 2) a[i] = i
return a
}
return {
// 0) Baseline loop cost
loop_empty: function(n) {
var sink = 0
var i = 0
for (i = 0; i < n; i++) {}
return blackhole(sink, n)
},
// 1) Numeric pipelines
i32_add: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = (x + 3) | 0
return blackhole(sink, x)
},
f64_add: function(n) {
var sink = 0
var x = 1.0
var i = 0
for (i = 0; i < n; i++) x = x + 3.14159
return blackhole(sink, x | 0)
},
mixed_add: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = x + 0.25
return blackhole(sink, x | 0)
},
bit_ops: function(n) {
var sink = 0
var x = 0x12345678
var i = 0
for (i = 0; i < n; i++) x = ((x << 5) ^ (x >>> 3)) | 0
return blackhole(sink, x)
},
overflow_path: function(n) {
var sink = 0
var x = 0x70000000
var i = 0
for (i = 0; i < n; i++) x = (x + 0x10000000) | 0
return blackhole(sink, x)
},
// 2) Branching
branch_predictable: function(n) {
var sink = 0
var x = 0
var i = 0
for (i = 0; i < n; i++) {
if ((i & 7) != 0) x++
else x += 2
}
return blackhole(sink, x)
},
branch_alternating: function(n) {
var sink = 0
var x = 0
var i = 0
for (i = 0; i < n; i++) {
if ((i & 1) == 0) x++
else x += 2
}
return blackhole(sink, x)
},
// 3) Calls
call_direct: function(n) {
var sink = 0
var f = function(a) { return (a + 1) | 0 }
var x = 0
var i = 0
for (i = 0; i < n; i++) x = f(x)
return blackhole(sink, x)
},
call_indirect: function(n) {
var sink = 0
var f = function(a) { return (a + 1) | 0 }
var g = f
var x = 0
var i = 0
for (i = 0; i < n; i++) x = g(x)
return blackhole(sink, x)
},
call_closure: function(n) {
var sink = 0
var make_adder = function(k) {
return function(a) { return (a + k) | 0 }
}
var add3 = make_adder(3)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = add3(x)
return blackhole(sink, x)
},
call_multi_arity: function(n) {
var sink = 0
var f0 = function() { return 1 }
var f1 = function(a) { return a + 1 }
var f2 = function(a, b) { return a + b }
var f3 = function(a, b, c) { return a + b + c }
var f4 = function(a, b, c, d) { return a + b + c + d }
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + f0() + f1(i) + f2(i, 1) + f3(i, 1, 2) + f4(i, 1, 2, 3)) | 0
}
return blackhole(sink, x)
},
// 4) Object props (ICs / shapes)
prop_read_mono: function(n) {
var sink = 0
var o = make_obj_xy(1, 2)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = (x + o.x) | 0
return blackhole(sink, x)
},
prop_read_poly_2: function(n) {
var sink = 0
var a = make_obj_xy(1, 2)
var b = make_obj_yx(1, 2)
var x = 0
var i = 0
var o = null
for (i = 0; i < n; i++) {
o = (i & 1) == 0 ? a : b
x = (x + o.x) | 0
}
return blackhole(sink, x)
},
prop_read_poly_4: function(n) {
var sink = 0
var shapes = [
{x: 1, y: 2},
{y: 2, x: 1},
{x: 1, z: 3, y: 2},
{w: 0, x: 1, y: 2}
]
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + shapes[i & 3].x) | 0
}
return blackhole(sink, x)
},
prop_read_mega: function(n) {
var sink = 0
var objs = make_shapes(32)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + objs[i & 31].a) | 0
}
return blackhole(sink, x)
},
prop_write_mono: function(n) {
var sink = 0
var o = make_obj_xy(1, 2)
var i = 0
for (i = 0; i < n; i++) o.x = (o.x + 1) | 0
return blackhole(sink, o.x)
},
// 5) Arrays
array_read_packed: function(n) {
var sink = 0
var a = make_packed_array(1024)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = (x + a[i & 1023]) | 0
return blackhole(sink, x)
},
array_write_packed: function(n) {
var sink = 0
var a = make_packed_array(1024)
var i = 0
for (i = 0; i < n; i++) a[i & 1023] = i
return blackhole(sink, a[17] | 0)
},
array_read_holey: function(n) {
var sink = 0
var a = make_holey_array(2048)
var x = 0
var i = 0
var v = null
for (i = 0; i < n; i++) {
v = a[(i & 2047)]
if (v) x = (x + v) | 0
}
return blackhole(sink, x)
},
array_push_steady: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var a = null
for (j = 0; j < n; j++) {
a = []
for (i = 0; i < 256; i++) push(a, i)
x = (x + length(a)) | 0
}
return blackhole(sink, x)
},
array_push_pop: function(n) {
var sink = 0
var a = []
var x = 0
var i = 0
var v = 0
for (i = 0; i < n; i++) {
push(a, i)
if (length(a) > 64) {
v = pop(a)
x = (x + v) | 0
}
}
return blackhole(sink, x)
},
array_indexed_sum: function(n) {
var sink = 0
var a = make_packed_array(1024)
var x = 0
var j = 0
var i = 0
for (j = 0; j < n; j++) {
x = 0
for (i = 0; i < 1024; i++) {
x = (x + a[i]) | 0
}
}
return blackhole(sink, x)
},
// 6) Strings
string_concat_small: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var s = null
for (j = 0; j < n; j++) {
s = ""
for (i = 0; i < 16; i++) s = s + "x"
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
string_concat_medium: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var s = null
for (j = 0; j < n; j++) {
s = ""
for (i = 0; i < 100; i++) s = s + "abcdefghij"
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
string_interpolation: function(n) {
var sink = 0
var x = 0
var i = 0
var s = null
for (i = 0; i < n; i++) {
s = `item_${i}_value_${i * 2}`
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
string_slice: function(n) {
var sink = 0
var base = "the quick brown fox jumps over the lazy dog"
var x = 0
var i = 0
var s = null
for (i = 0; i < n; i++) {
s = text(base, i % 10, i % 10 + 10)
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
// 7) Allocation / GC pressure
alloc_tiny_objects: function(n) {
var sink = 0
var x = 0
var i = 0
var o = null
for (i = 0; i < n; i++) {
o = {a: i, b: i + 1, c: i + 2}
x = (x + o.b) | 0
}
return blackhole(sink, x)
},
alloc_linked_list: function(n) {
var sink = 0
var head = null
var i = 0
var x = 0
var p = null
for (i = 0; i < n; i++) head = {v: i, next: head}
x = 0
p = head
while (p) {
x = (x + p.v) | 0
p = p.next
}
return blackhole(sink, x)
},
alloc_arrays: function(n) {
var sink = 0
var x = 0
var i = 0
var a = null
for (i = 0; i < n; i++) {
a = [i, i + 1, i + 2, i + 3]
x = (x + a[2]) | 0
}
return blackhole(sink, x)
},
alloc_short_lived: function(n) {
var sink = 0
var x = 0
var i = 0
var o = null
// Allocate objects that immediately become garbage
for (i = 0; i < n; i++) {
o = {val: i, data: {inner: i + 1}}
x = (x + o.data.inner) | 0
}
return blackhole(sink, x)
},
alloc_long_lived_pressure: function(n) {
var sink = 0
var store = []
var x = 0
var i = 0
var o = null
// Keep first 1024 objects alive, churn the rest
for (i = 0; i < n; i++) {
o = {val: i, data: i * 2}
if (i < 1024) {
push(store, o)
}
x = (x + o.data) | 0
}
return blackhole(sink, x)
},
// 8) Meme (prototype clone)
meme_clone_read: function(n) {
var sink = 0
var base = {x: 1, y: 2}
var x = 0
var i = 0
var o = null
for (i = 0; i < n; i++) {
o = meme(base)
x = (x + o.x) | 0
}
return blackhole(sink, x)
},
// 9) Guard / type check paths
guard_hot_number: function(n) {
// Monomorphic number path — guards should hoist
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = x + 1
return blackhole(sink, x | 0)
},
guard_mixed_types: function(n) {
// Alternating number/text — guards must stay
var sink = 0
var vals = [1, "a", 2, "b", 3, "c", 4, "d"]
var x = 0
var i = 0
for (i = 0; i < n; i++) {
if (is_number(vals[i & 7])) x = (x + vals[i & 7]) | 0
}
return blackhole(sink, x)
},
// 10) Reduce / higher-order
reduce_sum: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + reduce(a, function(acc, v) { return acc + v }, 0)) | 0
}
return blackhole(sink, x)
},
filter_evens: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + length(filter(a, function(v) { return v % 2 == 0 }))) | 0
}
return blackhole(sink, x)
},
arrfor_sum: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
var sum = 0
for (i = 0; i < n; i++) {
sum = 0
arrfor(a, function(v) { sum += v })
x = (x + sum) | 0
}
return blackhole(sink, x)
}
}

View File

@@ -1,249 +0,0 @@
// module_load.cm — Module loading simulation (macro benchmark)
// Simulates parsing many small modules, linking, and running.
// Tests the "build scenario" pattern.
var json = use('json')
// Simulate a small module: parse token stream + build AST + evaluate
function tokenize(src) {
var tokens = []
var i = 0
var ch = null
var chars = array(src)
var buf = ""
for (i = 0; i < length(chars); i++) {
ch = chars[i]
if (ch == " " || ch == "\n" || ch == "\t") {
if (length(buf) > 0) {
push(tokens, buf)
buf = ""
}
} else if (ch == "(" || ch == ")" || ch == "+" || ch == "-"
|| ch == "*" || ch == "=" || ch == ";" || ch == ",") {
if (length(buf) > 0) {
push(tokens, buf)
buf = ""
}
push(tokens, ch)
} else {
buf = buf + ch
}
}
if (length(buf) > 0) push(tokens, buf)
return tokens
}
// Build a simple AST from tokens
function parse_tokens(tokens) {
var ast = []
var i = 0
var tok = null
var node = null
for (i = 0; i < length(tokens); i++) {
tok = tokens[i]
if (tok == "var" || tok == "def") {
node = {type: "decl", kind: tok, name: null, value: null}
i++
if (i < length(tokens)) node.name = tokens[i]
i++ // skip =
i++
if (i < length(tokens)) node.value = tokens[i]
push(ast, node)
} else if (tok == "return") {
node = {type: "return", value: null}
i++
if (i < length(tokens)) node.value = tokens[i]
push(ast, node)
} else if (tok == "function") {
node = {type: "func", name: null, body: []}
i++
if (i < length(tokens)) node.name = tokens[i]
// Skip to matching )
while (i < length(tokens) && tokens[i] != ")") i++
push(ast, node)
} else {
push(ast, {type: "expr", value: tok})
}
}
return ast
}
// Evaluate: simple symbol table + resolution
function evaluate(ast, env) {
var result = null
var i = 0
var node = null
for (i = 0; i < length(ast); i++) {
node = ast[i]
if (node.type == "decl") {
env[node.name] = node.value
} else if (node.type == "return") {
result = node.value
if (env[result]) result = env[result]
} else if (node.type == "func") {
env[node.name] = node
}
}
return result
}
// Generate fake module source code
function generate_module(id, dep_count) {
var src = ""
var i = 0
src = src + "var _id = " + text(id) + ";\n"
for (i = 0; i < dep_count; i++) {
src = src + "var dep" + text(i) + " = use(mod_" + text(i) + ");\n"
}
src = src + "var x = " + text(id * 17) + ";\n"
src = src + "var y = " + text(id * 31) + ";\n"
src = src + "function compute(a, b) { return a + b; }\n"
src = src + "var result = compute(x, y);\n"
src = src + "return result;\n"
return src
}
// Simulate loading N modules with dependency chains
function simulate_build(n_modules, deps_per_module) {
var modules = []
var loaded = {}
var i = 0
var j = 0
var src = null
var tokens = null
var ast = null
var env = null
var result = null
var total_tokens = 0
var total_nodes = 0
// Generate all module sources
for (i = 0; i < n_modules; i++) {
src = generate_module(i, deps_per_module)
push(modules, src)
}
// "Load" each module: tokenize → parse → evaluate
for (i = 0; i < n_modules; i++) {
tokens = tokenize(modules[i])
total_tokens += length(tokens)
ast = parse_tokens(tokens)
total_nodes += length(ast)
env = {}
// Resolve dependencies
for (j = 0; j < deps_per_module; j++) {
if (j < i) {
env["dep" + text(j)] = loaded["mod_" + text(j)]
}
}
result = evaluate(ast, env)
loaded["mod_" + text(i)] = result
}
return {
modules: n_modules,
total_tokens: total_tokens,
total_nodes: total_nodes,
last_result: result
}
}
// Dependency graph analysis (topological sort simulation)
function topo_sort(n_modules, deps_per_module) {
// Build adjacency list
var adj = {}
var in_degree = {}
var i = 0
var j = 0
var name = null
var dep = null
for (i = 0; i < n_modules; i++) {
name = "mod_" + text(i)
adj[name] = []
in_degree[name] = 0
}
for (i = 0; i < n_modules; i++) {
name = "mod_" + text(i)
for (j = 0; j < deps_per_module; j++) {
if (j < i) {
dep = "mod_" + text(j)
push(adj[dep], name)
in_degree[name] = in_degree[name] + 1
}
}
}
// Kahn's algorithm
var queue = []
var keys = array(in_degree)
for (i = 0; i < length(keys); i++) {
if (in_degree[keys[i]] == 0) push(queue, keys[i])
}
var order = []
var current = null
var neighbors = null
var qi = 0
while (qi < length(queue)) {
current = queue[qi]
qi++
push(order, current)
neighbors = adj[current]
if (neighbors) {
for (i = 0; i < length(neighbors); i++) {
in_degree[neighbors[i]] = in_degree[neighbors[i]] - 1
if (in_degree[neighbors[i]] == 0) push(queue, neighbors[i])
}
}
}
return order
}
return {
// Small build: 50 modules, 3 deps each
build_50: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = simulate_build(50, 3)
}
return result
},
// Medium build: 200 modules, 5 deps each
build_200: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = simulate_build(200, 5)
}
return result
},
// Large build: 500 modules, 5 deps each
build_500: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = simulate_build(500, 5)
}
return result
},
// Topo sort of 500 module dependency graph
topo_sort_500: function(n) {
var i = 0
var order = null
for (i = 0; i < n; i++) {
order = topo_sort(500, 5)
}
return order
}
}

View File

@@ -1,160 +0,0 @@
// nbody.cm — N-body gravitational simulation kernel
// Pure numeric + allocation workload. Classic VM benchmark.
var math = use('math/radians')
def PI = 3.141592653589793
def SOLAR_MASS = 4 * PI * PI
def DAYS_PER_YEAR = 365.24
function make_system() {
// Sun + 4 Jovian planets
var sun = {x: 0, y: 0, z: 0, vx: 0, vy: 0, vz: 0, mass: SOLAR_MASS}
var jupiter = {
x: 4.84143144246472090,
y: -1.16032004402742839,
z: -0.103622044471123109,
vx: 0.00166007664274403694 * DAYS_PER_YEAR,
vy: 0.00769901118419740425 * DAYS_PER_YEAR,
vz: -0.0000690460016972063023 * DAYS_PER_YEAR,
mass: 0.000954791938424326609 * SOLAR_MASS
}
var saturn = {
x: 8.34336671824457987,
y: 4.12479856412430479,
z: -0.403523417114321381,
vx: -0.00276742510726862411 * DAYS_PER_YEAR,
vy: 0.00499852801234917238 * DAYS_PER_YEAR,
vz: 0.0000230417297573763929 * DAYS_PER_YEAR,
mass: 0.000285885980666130812 * SOLAR_MASS
}
var uranus = {
x: 12.8943695621391310,
y: -15.1111514016986312,
z: -0.223307578892655734,
vx: 0.00296460137564761618 * DAYS_PER_YEAR,
vy: 0.00237847173959480950 * DAYS_PER_YEAR,
vz: -0.0000296589568540237556 * DAYS_PER_YEAR,
mass: 0.0000436624404335156298 * SOLAR_MASS
}
var neptune = {
x: 15.3796971148509165,
y: -25.9193146099879641,
z: 0.179258772950371181,
vx: 0.00268067772490389322 * DAYS_PER_YEAR,
vy: 0.00162824170038242295 * DAYS_PER_YEAR,
vz: -0.0000951592254519715870 * DAYS_PER_YEAR,
mass: 0.0000515138902046611451 * SOLAR_MASS
}
var bodies = [sun, jupiter, saturn, uranus, neptune]
// Offset momentum
var px = 0
var py = 0
var pz = 0
var i = 0
for (i = 0; i < length(bodies); i++) {
px += bodies[i].vx * bodies[i].mass
py += bodies[i].vy * bodies[i].mass
pz += bodies[i].vz * bodies[i].mass
}
sun.vx = -px / SOLAR_MASS
sun.vy = -py / SOLAR_MASS
sun.vz = -pz / SOLAR_MASS
return bodies
}
function advance(bodies, dt) {
var n = length(bodies)
var i = 0
var j = 0
var bi = null
var bj = null
var dx = 0
var dy = 0
var dz = 0
var dist_sq = 0
var dist = 0
var mag = 0
for (i = 0; i < n; i++) {
bi = bodies[i]
for (j = i + 1; j < n; j++) {
bj = bodies[j]
dx = bi.x - bj.x
dy = bi.y - bj.y
dz = bi.z - bj.z
dist_sq = dx * dx + dy * dy + dz * dz
dist = math.sqrt(dist_sq)
mag = dt / (dist_sq * dist)
bi.vx -= dx * bj.mass * mag
bi.vy -= dy * bj.mass * mag
bi.vz -= dz * bj.mass * mag
bj.vx += dx * bi.mass * mag
bj.vy += dy * bi.mass * mag
bj.vz += dz * bi.mass * mag
}
}
for (i = 0; i < n; i++) {
bi = bodies[i]
bi.x += dt * bi.vx
bi.y += dt * bi.vy
bi.z += dt * bi.vz
}
}
function energy(bodies) {
var e = 0
var n = length(bodies)
var i = 0
var j = 0
var bi = null
var bj = null
var dx = 0
var dy = 0
var dz = 0
for (i = 0; i < n; i++) {
bi = bodies[i]
e += 0.5 * bi.mass * (bi.vx * bi.vx + bi.vy * bi.vy + bi.vz * bi.vz)
for (j = i + 1; j < n; j++) {
bj = bodies[j]
dx = bi.x - bj.x
dy = bi.y - bj.y
dz = bi.z - bj.z
e -= (bi.mass * bj.mass) / math.sqrt(dx * dx + dy * dy + dz * dz)
}
}
return e
}
return {
nbody_1k: function(n) {
var i = 0
var j = 0
var bodies = null
for (i = 0; i < n; i++) {
bodies = make_system()
for (j = 0; j < 1000; j++) advance(bodies, 0.01)
energy(bodies)
}
},
nbody_10k: function(n) {
var i = 0
var j = 0
var bodies = null
for (i = 0; i < n; i++) {
bodies = make_system()
for (j = 0; j < 10000; j++) advance(bodies, 0.01)
energy(bodies)
}
}
}

View File

@@ -1,154 +0,0 @@
// ray_tracer.cm — Simple ray tracer kernel
// Control flow + numeric + allocation. Classic VM benchmark.
var math = use('math/radians')
function vec(x, y, z) {
return {x: x, y: y, z: z}
}
function vadd(a, b) {
return {x: a.x + b.x, y: a.y + b.y, z: a.z + b.z}
}
function vsub(a, b) {
return {x: a.x - b.x, y: a.y - b.y, z: a.z - b.z}
}
function vmul(v, s) {
return {x: v.x * s, y: v.y * s, z: v.z * s}
}
function vdot(a, b) {
return a.x * b.x + a.y * b.y + a.z * b.z
}
function vnorm(v) {
var len = math.sqrt(vdot(v, v))
if (len == 0) return vec(0, 0, 0)
return vmul(v, 1 / len)
}
function make_sphere(center, radius, color) {
return {
center: center,
radius: radius,
color: color
}
}
function intersect_sphere(origin, dir, sphere) {
var oc = vsub(origin, sphere.center)
var b = vdot(oc, dir)
var c = vdot(oc, oc) - sphere.radius * sphere.radius
var disc = b * b - c
if (disc < 0) return -1
var sq = math.sqrt(disc)
var t1 = -b - sq
var t2 = -b + sq
if (t1 > 0.001) return t1
if (t2 > 0.001) return t2
return -1
}
function make_scene() {
var spheres = [
make_sphere(vec(0, -1, 5), 1, vec(1, 0, 0)),
make_sphere(vec(2, 0, 6), 1, vec(0, 1, 0)),
make_sphere(vec(-2, 0, 4), 1, vec(0, 0, 1)),
make_sphere(vec(0, 1, 4.5), 0.5, vec(1, 1, 0)),
make_sphere(vec(1, -0.5, 3), 0.3, vec(1, 0, 1)),
make_sphere(vec(0, -101, 5), 100, vec(0.5, 0.5, 0.5))
]
var light = vnorm(vec(1, 1, -1))
return {spheres: spheres, light: light}
}
function trace(origin, dir, scene) {
var closest_t = 999999
var closest_sphere = null
var i = 0
var t = 0
for (i = 0; i < length(scene.spheres); i++) {
t = intersect_sphere(origin, dir, scene.spheres[i])
if (t > 0 && t < closest_t) {
closest_t = t
closest_sphere = scene.spheres[i]
}
}
if (!closest_sphere) return vec(0.2, 0.3, 0.5) // sky color
var hit = vadd(origin, vmul(dir, closest_t))
var normal = vnorm(vsub(hit, closest_sphere.center))
var diffuse = vdot(normal, scene.light)
if (diffuse < 0) diffuse = 0
// Shadow check
var shadow_origin = vadd(hit, vmul(normal, 0.001))
var in_shadow = false
for (i = 0; i < length(scene.spheres); i++) {
if (scene.spheres[i] != closest_sphere) {
t = intersect_sphere(shadow_origin, scene.light, scene.spheres[i])
if (t > 0) {
in_shadow = true
break
}
}
}
var ambient = 0.15
var intensity = in_shadow ? ambient : ambient + diffuse * 0.85
return vmul(closest_sphere.color, intensity)
}
function render(width, height, scene) {
var aspect = width / height
var fov = 1.0
var total_r = 0
var total_g = 0
var total_b = 0
var y = 0
var x = 0
var u = 0
var v = 0
var dir = null
var color = null
var origin = vec(0, 0, 0)
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
u = (2 * (x + 0.5) / width - 1) * aspect * fov
v = (1 - 2 * (y + 0.5) / height) * fov
dir = vnorm(vec(u, v, 1))
color = trace(origin, dir, scene)
total_r += color.x
total_g += color.y
total_b += color.z
}
}
return {r: total_r, g: total_g, b: total_b}
}
var scene = make_scene()
return {
raytrace_32x32: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = render(32, 32, scene)
}
return result
},
raytrace_64x64: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = render(64, 64, scene)
}
return result
}
}

View File

@@ -1,251 +0,0 @@
// richards.cm — Richards benchmark (scheduler simulation)
// Object-ish workload: dynamic dispatch, state machines, queuing.
def IDLE = 0
def WORKER = 1
def HANDLER_A = 2
def HANDLER_B = 3
def DEVICE_A = 4
def DEVICE_B = 5
def NUM_TASKS = 6
def TASK_RUNNING = 0
def TASK_WAITING = 1
def TASK_HELD = 2
def TASK_SUSPENDED = 3
function make_packet(link, id, kind) {
return {link: link, id: id, kind: kind, datum: 0, data: array(4, 0)}
}
function scheduler() {
var tasks = array(NUM_TASKS, null)
var current = null
var queue_count = 0
var hold_count = 0
var v1 = 0
var v2 = 0
var w_id = HANDLER_A
var w_datum = 0
var h_a_queue = null
var h_a_count = 0
var h_b_queue = null
var h_b_count = 0
var dev_a_pkt = null
var dev_b_pkt = null
var find_next = function() {
var best = null
var i = 0
for (i = 0; i < NUM_TASKS; i++) {
if (tasks[i] && tasks[i].state == TASK_RUNNING) {
if (!best || tasks[i].priority > best.priority) {
best = tasks[i]
}
}
}
return best
}
var hold_self = function() {
hold_count++
if (current) current.state = TASK_HELD
return find_next()
}
var release = function(id) {
var t = tasks[id]
if (!t) return find_next()
if (t.state == TASK_HELD) t.state = TASK_RUNNING
if (t.priority > (current ? current.priority : -1)) return t
return current
}
var queue_packet = function(pkt) {
var t = tasks[pkt.id]
var p = null
if (!t) return find_next()
queue_count++
pkt.link = null
pkt.id = current ? current.id : 0
if (!t.queue) {
t.queue = pkt
t.state = TASK_RUNNING
if (t.priority > (current ? current.priority : -1)) return t
} else {
p = t.queue
while (p.link) p = p.link
p.link = pkt
}
return current
}
// Idle task
tasks[IDLE] = {id: IDLE, priority: 0, queue: null, state: TASK_RUNNING,
hold_count: 0, queue_count: 0,
fn: function(pkt) {
v1--
if (v1 == 0) return hold_self()
if ((v2 & 1) == 0) {
v2 = v2 >> 1
return release(DEVICE_A)
}
v2 = (v2 >> 1) ^ 0xD008
return release(DEVICE_B)
}
}
// Worker task
tasks[WORKER] = {id: WORKER, priority: 1000, queue: null, state: TASK_SUSPENDED,
hold_count: 0, queue_count: 0,
fn: function(pkt) {
var i = 0
if (!pkt) return hold_self()
w_id = (w_id == HANDLER_A) ? HANDLER_B : HANDLER_A
pkt.id = w_id
pkt.datum = 0
for (i = 0; i < 4; i++) {
w_datum++
if (w_datum > 26) w_datum = 1
pkt.data[i] = 65 + w_datum
}
return queue_packet(pkt)
}
}
// Handler A
tasks[HANDLER_A] = {id: HANDLER_A, priority: 2000, queue: null, state: TASK_SUSPENDED,
hold_count: 0, queue_count: 0,
fn: function(pkt) {
var p = null
if (pkt) { h_a_queue = pkt; h_a_count++ }
if (h_a_queue) {
p = h_a_queue
h_a_queue = p.link
if (h_a_count < 3) return queue_packet(p)
return release(DEVICE_A)
}
return hold_self()
}
}
// Handler B
tasks[HANDLER_B] = {id: HANDLER_B, priority: 3000, queue: null, state: TASK_SUSPENDED,
hold_count: 0, queue_count: 0,
fn: function(pkt) {
var p = null
if (pkt) { h_b_queue = pkt; h_b_count++ }
if (h_b_queue) {
p = h_b_queue
h_b_queue = p.link
if (h_b_count < 3) return queue_packet(p)
return release(DEVICE_B)
}
return hold_self()
}
}
// Device A
tasks[DEVICE_A] = {id: DEVICE_A, priority: 4000, queue: null, state: TASK_SUSPENDED,
hold_count: 0, queue_count: 0,
fn: function(pkt) {
var p = null
if (pkt) { dev_a_pkt = pkt; return hold_self() }
if (dev_a_pkt) {
p = dev_a_pkt
dev_a_pkt = null
return queue_packet(p)
}
return hold_self()
}
}
// Device B
tasks[DEVICE_B] = {id: DEVICE_B, priority: 5000, queue: null, state: TASK_SUSPENDED,
hold_count: 0, queue_count: 0,
fn: function(pkt) {
var p = null
if (pkt) { dev_b_pkt = pkt; return hold_self() }
if (dev_b_pkt) {
p = dev_b_pkt
dev_b_pkt = null
return queue_packet(p)
}
return hold_self()
}
}
var run = function(iterations) {
var i = 0
var pkt1 = null
var pkt2 = null
var steps = 0
var pkt = null
var next = null
v1 = iterations
v2 = 0xBEEF
queue_count = 0
hold_count = 0
w_id = HANDLER_A
w_datum = 0
h_a_queue = null
h_a_count = 0
h_b_queue = null
h_b_count = 0
dev_a_pkt = null
dev_b_pkt = null
for (i = 0; i < NUM_TASKS; i++) {
if (tasks[i]) {
tasks[i].state = (i == IDLE) ? TASK_RUNNING : TASK_SUSPENDED
tasks[i].queue = null
}
}
pkt1 = make_packet(null, WORKER, 1)
pkt2 = make_packet(pkt1, WORKER, 1)
tasks[WORKER].queue = pkt2
tasks[WORKER].state = TASK_RUNNING
current = find_next()
while (current && steps < iterations * 10) {
pkt = current.queue
if (pkt) {
current.queue = pkt.link
current.queue_count++
}
next = current.fn(pkt)
if (next) current = next
else current = find_next()
steps++
}
return {queue_count: queue_count, hold_count: hold_count, steps: steps}
}
return {run: run}
}
return {
richards_100: function(n) {
var i = 0
var s = null
var result = null
for (i = 0; i < n; i++) {
s = scheduler()
result = s.run(100)
}
return result
},
richards_1k: function(n) {
var i = 0
var s = null
var result = null
for (i = 0; i < n; i++) {
s = scheduler()
result = s.run(1000)
}
return result
}
}

View File

@@ -1,180 +0,0 @@
// sorting.cm — Sorting and searching kernel
// Array manipulation, comparison-heavy, allocation patterns.
function make_random_array(n, seed) {
var a = []
var x = seed
var i = 0
for (i = 0; i < n; i++) {
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
push(a, x % 10000)
}
return a
}
function make_descending(n) {
var a = []
var i = 0
for (i = n - 1; i >= 0; i--) push(a, i)
return a
}
// Manual quicksort (tests recursion + array mutation)
function qsort(arr, lo, hi) {
var i = lo
var j = hi
var pivot = arr[floor((lo + hi) / 2)]
var tmp = 0
if (lo >= hi) return null
while (i <= j) {
while (arr[i] < pivot) i++
while (arr[j] > pivot) j--
if (i <= j) {
tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
i++
j--
}
}
if (lo < j) qsort(arr, lo, j)
if (i < hi) qsort(arr, i, hi)
return null
}
// Merge sort (tests allocation + array creation)
function msort(arr) {
var n = length(arr)
if (n <= 1) return arr
var mid = floor(n / 2)
var left = msort(array(arr, 0, mid))
var right = msort(array(arr, mid, n))
return merge(left, right)
}
function merge(a, b) {
var result = []
var i = 0
var j = 0
while (i < length(a) && j < length(b)) {
if (a[i] <= b[j]) {
push(result, a[i])
i++
} else {
push(result, b[j])
j++
}
}
while (i < length(a)) {
push(result, a[i])
i++
}
while (j < length(b)) {
push(result, b[j])
j++
}
return result
}
// Binary search
function bsearch(arr, target) {
var lo = 0
var hi = length(arr) - 1
var mid = 0
while (lo <= hi) {
mid = floor((lo + hi) / 2)
if (arr[mid] == target) return mid
if (arr[mid] < target) lo = mid + 1
else hi = mid - 1
}
return -1
}
// Sort objects by field
function sort_records(n) {
var records = []
var x = 42
var i = 0
for (i = 0; i < n; i++) {
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
push(records, {id: i, score: x % 10000, name: `item_${i}`})
}
return sort(records, "score")
}
return {
// Quicksort 1K random integers
qsort_1k: function(n) {
var i = 0
var a = null
for (i = 0; i < n; i++) {
a = make_random_array(1000, i)
qsort(a, 0, length(a) - 1)
}
return a
},
// Quicksort 10K random integers
qsort_10k: function(n) {
var i = 0
var a = null
for (i = 0; i < n; i++) {
a = make_random_array(10000, i)
qsort(a, 0, length(a) - 1)
}
return a
},
// Merge sort 1K (allocation heavy)
msort_1k: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = msort(make_random_array(1000, i))
}
return result
},
// Built-in sort 1K
builtin_sort_1k: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = sort(make_random_array(1000, i))
}
return result
},
// Sort worst case (descending → ascending)
sort_worst_case: function(n) {
var i = 0
var a = null
for (i = 0; i < n; i++) {
a = make_descending(1000)
qsort(a, 0, length(a) - 1)
}
return a
},
// Binary search in sorted array
bsearch_1k: function(n) {
var sorted = make_random_array(1000, 42)
sorted = sort(sorted)
var found = 0
var i = 0
for (i = 0; i < n; i++) {
if (bsearch(sorted, sorted[i % 1000]) >= 0) found++
}
return found
},
// Sort records by field
sort_records_500: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = sort_records(500)
}
return result
}
}

View File

@@ -1,82 +0,0 @@
// spectral_norm.cm — Spectral norm kernel
// Pure numeric, dense array access, mathematical computation.
var math = use('math/radians')
function eval_a(i, j) {
return 1.0 / ((i + j) * (i + j + 1) / 2 + i + 1)
}
function eval_a_times_u(n, u, au) {
var i = 0
var j = 0
var sum = 0
for (i = 0; i < n; i++) {
sum = 0
for (j = 0; j < n; j++) {
sum += eval_a(i, j) * u[j]
}
au[i] = sum
}
}
function eval_at_times_u(n, u, atu) {
var i = 0
var j = 0
var sum = 0
for (i = 0; i < n; i++) {
sum = 0
for (j = 0; j < n; j++) {
sum += eval_a(j, i) * u[j]
}
atu[i] = sum
}
}
function eval_ata_times_u(n, u, atau) {
var v = array(n, 0)
eval_a_times_u(n, u, v)
eval_at_times_u(n, v, atau)
}
function spectral_norm(n) {
var u = array(n, 1)
var v = array(n, 0)
var i = 0
var vbv = 0
var vv = 0
for (i = 0; i < 10; i++) {
eval_ata_times_u(n, u, v)
eval_ata_times_u(n, v, u)
}
vbv = 0
vv = 0
for (i = 0; i < n; i++) {
vbv += u[i] * v[i]
vv += v[i] * v[i]
}
return math.sqrt(vbv / vv)
}
return {
spectral_100: function(n) {
var i = 0
var result = 0
for (i = 0; i < n; i++) {
result = spectral_norm(100)
}
return result
},
spectral_200: function(n) {
var i = 0
var result = 0
for (i = 0; i < n; i++) {
result = spectral_norm(200)
}
return result
}
}

View File

@@ -1,188 +0,0 @@
// string_processing.cm — String-heavy kernel
// Concat, split, search, replace, interning path stress.
function make_lorem(paragraphs) {
var base = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat"
var result = ""
var i = 0
for (i = 0; i < paragraphs; i++) {
if (i > 0) result = result + " "
result = result + base
}
return result
}
// Build a lookup table from text
function build_index(txt) {
var words = array(txt, " ")
var index = {}
var i = 0
var w = null
for (i = 0; i < length(words); i++) {
w = words[i]
if (!index[w]) {
index[w] = []
}
push(index[w], i)
}
return index
}
// Levenshtein-like distance (simplified)
function edit_distance(a, b) {
var la = length(a)
var lb = length(b)
if (la == 0) return lb
if (lb == 0) return la
// Use flat array for 2 rows of DP matrix
var prev = array(lb + 1, 0)
var curr = array(lb + 1, 0)
var i = 0
var j = 0
var cost = 0
var del = 0
var ins = 0
var sub = 0
var tmp = null
var ca = array(a)
var cb = array(b)
for (j = 0; j <= lb; j++) prev[j] = j
for (i = 1; i <= la; i++) {
curr[0] = i
for (j = 1; j <= lb; j++) {
cost = ca[i - 1] == cb[j - 1] ? 0 : 1
del = prev[j] + 1
ins = curr[j - 1] + 1
sub = prev[j - 1] + cost
curr[j] = del
if (ins < curr[j]) curr[j] = ins
if (sub < curr[j]) curr[j] = sub
}
tmp = prev
prev = curr
curr = tmp
}
return prev[lb]
}
var lorem_5 = make_lorem(5)
var lorem_20 = make_lorem(20)
return {
// Split text into words and count
string_split_count: function(n) {
var i = 0
var words = null
var count = 0
for (i = 0; i < n; i++) {
words = array(lorem_5, " ")
count += length(words)
}
return count
},
// Build word index (split + hash + array ops)
string_index_build: function(n) {
var i = 0
var idx = null
for (i = 0; i < n; i++) {
idx = build_index(lorem_5)
}
return idx
},
// Search for substrings
string_search: function(n) {
var targets = ["dolor", "minim", "quis", "magna", "ipsum"]
var i = 0
var j = 0
var count = 0
for (i = 0; i < n; i++) {
for (j = 0; j < length(targets); j++) {
if (search(lorem_20, targets[j])) count++
}
}
return count
},
// Replace operations
string_replace: function(n) {
var i = 0
var result = null
for (i = 0; i < n; i++) {
result = replace(lorem_5, "dolor", "DOLOR")
result = replace(result, "ipsum", "IPSUM")
result = replace(result, "amet", "AMET")
}
return result
},
// String concatenation builder
string_builder: function(n) {
var i = 0
var j = 0
var s = null
var total = 0
for (i = 0; i < n; i++) {
s = ""
for (j = 0; j < 50; j++) {
s = s + "key=" + text(j) + "&value=" + text(j * 17) + "&"
}
total += length(s)
}
return total
},
// Edit distance (DP + array + string ops)
edit_distance: function(n) {
var words = ["kitten", "sitting", "saturday", "sunday", "intention", "execution"]
var i = 0
var j = 0
var total = 0
for (i = 0; i < n; i++) {
for (j = 0; j < length(words) - 1; j++) {
total += edit_distance(words[j], words[j + 1])
}
}
return total
},
// Upper/lower/trim chain
string_transforms: function(n) {
var src = " Hello World "
var i = 0
var x = 0
var result = null
for (i = 0; i < n; i++) {
result = trim(src)
result = upper(result)
result = lower(result)
x += length(result)
}
return x
},
// Starts_with / ends_with (interning path)
string_prefix_suffix: function(n) {
var strs = [
"application/json",
"text/html",
"image/png",
"application/xml",
"text/plain"
]
var i = 0
var j = 0
var count = 0
for (i = 0; i < n; i++) {
for (j = 0; j < length(strs); j++) {
if (starts_with(strs[j], "application/")) count++
if (ends_with(strs[j], "/json")) count++
if (starts_with(strs[j], "text/")) count++
}
}
return count
}
}

View File

@@ -1,137 +0,0 @@
// tree_ops.cm — Tree data structure operations kernel
// Pointer chasing, recursion, allocation patterns.
// Binary tree: create, walk, transform, check
function make_tree(depth) {
if (depth <= 0) return {val: 1, left: null, right: null}
return {
val: depth,
left: make_tree(depth - 1),
right: make_tree(depth - 1)
}
}
function tree_check(node) {
if (!node) return 0
if (!node.left) return node.val
return node.val + tree_check(node.left) - tree_check(node.right)
}
function tree_sum(node) {
if (!node) return 0
return node.val + tree_sum(node.left) + tree_sum(node.right)
}
function tree_depth(node) {
if (!node) return 0
var l = tree_depth(node.left)
var r = tree_depth(node.right)
return 1 + (l > r ? l : r)
}
function tree_count(node) {
if (!node) return 0
return 1 + tree_count(node.left) + tree_count(node.right)
}
// Transform tree: map values
function tree_map(node, fn) {
if (!node) return null
return {
val: fn(node.val),
left: tree_map(node.left, fn),
right: tree_map(node.right, fn)
}
}
// Flatten tree to array (in-order)
function tree_flatten(node, result) {
if (!node) return null
tree_flatten(node.left, result)
push(result, node.val)
tree_flatten(node.right, result)
return null
}
// Build sorted tree from array (balanced)
function build_balanced(arr, lo, hi) {
if (lo > hi) return null
var mid = floor((lo + hi) / 2)
return {
val: arr[mid],
left: build_balanced(arr, lo, mid - 1),
right: build_balanced(arr, mid + 1, hi)
}
}
// Find a value in BST
function bst_find(node, val) {
if (!node) return false
if (val == node.val) return true
if (val < node.val) return bst_find(node.left, val)
return bst_find(node.right, val)
}
return {
// Binary tree create + check (allocation heavy)
tree_create_check: function(n) {
var i = 0
var t = null
var x = 0
for (i = 0; i < n; i++) {
t = make_tree(10)
x += tree_check(t)
}
return x
},
// Deep tree traversals
tree_traversal: function(n) {
var t = make_tree(12)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x += tree_sum(t) + tree_depth(t) + tree_count(t)
}
return x
},
// Tree map (create new tree from old)
tree_transform: function(n) {
var t = make_tree(10)
var i = 0
var mapped = null
for (i = 0; i < n; i++) {
mapped = tree_map(t, function(v) { return v * 2 + 1 })
}
return mapped
},
// Flatten + rebuild (array <-> tree conversion)
tree_flatten_rebuild: function(n) {
var t = make_tree(10)
var i = 0
var flat = null
var rebuilt = null
for (i = 0; i < n; i++) {
flat = []
tree_flatten(t, flat)
rebuilt = build_balanced(flat, 0, length(flat) - 1)
}
return rebuilt
},
// BST search (pointer chasing)
bst_search: function(n) {
// Build a balanced BST of 1024 elements
var data = []
var i = 0
for (i = 0; i < 1024; i++) push(data, i)
var bst = build_balanced(data, 0, 1023)
var found = 0
for (i = 0; i < n; i++) {
if (bst_find(bst, i % 1024)) found++
}
return found
}
}

View File

@@ -1,5 +1,5 @@
function mainThread() {
var maxDepth = max(6, Number(arg[0] || 16));
var maxDepth = Math.max(6, Number(arg[0] || 16));
var stretchDepth = maxDepth + 1;
var check = itemCheck(bottomUpTree(stretchDepth));
@@ -7,7 +7,7 @@ function mainThread() {
var longLivedTree = bottomUpTree(maxDepth);
for (var depth = 4; depth <= maxDepth; depth += 2) {
for (let depth = 4; depth <= maxDepth; depth += 2) {
var iterations = 1 << maxDepth - depth + 4;
work(iterations, depth);
}
@@ -16,8 +16,8 @@ function mainThread() {
}
function work(iterations, depth) {
var check = 0;
for (var i = 0; i < iterations; i++)
let check = 0;
for (let i = 0; i < iterations; i++)
check += itemCheck(bottomUpTree(depth));
log.console(`${iterations}\t trees of depth ${depth}\t check: ${check}`);
}
@@ -34,10 +34,10 @@ function itemCheck(node) {
function bottomUpTree(depth) {
return depth > 0
? TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
: TreeNode(null, null);
? new TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
: new TreeNode(null, null);
}
mainThread()
$stop()
$_.stop()

View File

@@ -1,9 +1,8 @@
var blob = use('blob')
var math = use('math/radians')
function eratosthenes (n) {
var sieve = blob(n, true)
var sqrtN = whole(math.sqrt(n));
var sieve = new blob(n, true)
var sqrtN = Math.trunc(Math.sqrt(n));
for (i = 2; i <= sqrtN; i++)
if (sieve.read_logical(i))
@@ -17,9 +16,9 @@ var sieve = eratosthenes(10000000);
stone(sieve)
var c = 0
for (var i = 0; i < length(sieve); i++)
for (var i = 0; i < sieve.length; i++)
if (sieve.read_logical(i)) c++
log.console(c)
$stop()
$_.stop()

View File

@@ -1,6 +1,6 @@
function fannkuch(n) {
var perm1 = [n]
for (var i = 0; i < n; i++) perm1[i] = i
for (let i = 0; i < n; i++) perm1[i] = i
var perm = [n]
var count = [n]
var f = 0, flips = 0, nperm = 0, checksum = 0
@@ -18,7 +18,7 @@ function fannkuch(n) {
while (k != 0) {
i = 0
while (2*i < k) {
var t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
let t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
i += 1
}
k = perm[0]
@@ -34,10 +34,10 @@ function fannkuch(n) {
log.console( checksum )
return flips
}
var p0 = perm1[0]
let p0 = perm1[0]
i = 0
while (i < r) {
var j = i + 1
let j = i + 1
perm1[i] = perm1[j]
i = j
}
@@ -55,4 +55,4 @@ var n = arg[0] || 10
log.console(`Pfannkuchen(${n}) = ${fannkuch(n)}`)
$stop()
$_.stop()

View File

@@ -1,16 +0,0 @@
var time = use('time')
function fib(n) {
if (n<2) return n
return fib(n-1) + fib(n-2)
}
var now = time.number()
var arr = [1,2,3,4,5]
arrfor(arr, function(i) {
log.console(fib(28))
})
log.console(`elapsed: ${time.number()-now}`)
$stop()

View File

@@ -1,5 +1,4 @@
var time = use('time')
var math = use('math/radians')
////////////////////////////////////////////////////////////////////////////////
// JavaScript Performance Benchmark Suite
@@ -7,7 +6,7 @@ var math = use('math/radians')
////////////////////////////////////////////////////////////////////////////////
// Test configurations
def iterations = {
const iterations = {
simple: 10000000,
medium: 1000000,
complex: 100000
@@ -109,12 +108,12 @@ function benchArrayOps() {
var pushTime = measureTime(function() {
var arr = [];
for (var i = 0; i < iterations.medium; i++) {
push(arr, i);
arr.push(i);
}
});
var arr = [];
for (var i = 0; i < 10000; i++) push(arr, i);
for (var i = 0; i < 10000; i++) arr.push(i);
var accessTime = measureTime(function() {
var sum = 0;
@@ -126,7 +125,7 @@ function benchArrayOps() {
var iterateTime = measureTime(function() {
var sum = 0;
for (var j = 0; j < 1000; j++) {
for (var i = 0; i < length(arr); i++) {
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
@@ -151,12 +150,13 @@ function benchObjectCreation() {
});
function Point(x, y) {
return {x,y}
this.x = x;
this.y = y;
}
var defructorTime = measureTime(function() {
var constructorTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var p = Point(i, i * 2);
var p = new Point(i, i * 2);
}
});
@@ -171,7 +171,7 @@ function benchObjectCreation() {
var prototypeTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var obj = meme(protoObj);
var obj = Object.create(protoObj);
obj.x = i;
obj.y = i * 2;
}
@@ -179,7 +179,7 @@ function benchObjectCreation() {
return {
literalTime: literalTime,
defructorTime: defructorTime,
constructorTime: constructorTime,
prototypeTime: prototypeTime
};
}
@@ -198,19 +198,19 @@ function benchStringOps() {
var strings = [];
for (var i = 0; i < 1000; i++) {
push(strings, "string" + i);
strings.push("string" + i);
}
var joinTime = measureTime(function() {
for (var i = 0; i < iterations.complex; i++) {
var result = text(strings, ",");
var result = strings.join(",");
}
});
var splitTime = measureTime(function() {
var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p";
for (var i = 0; i < iterations.medium; i++) {
var parts = array(str, ",");
var parts = str.split(",");
}
});
@@ -237,8 +237,8 @@ function benchArithmetic() {
var floatMathTime = measureTime(function() {
var result = 1.5;
for (var i = 0; i < iterations.simple; i++) {
result = math.sine(result) + math.cosine(i * 0.01);
result = math.sqrt(abs(result)) + 0.1;
result = Math.sin(result) + Math.cos(i * 0.01);
result = Math.sqrt(Math.abs(result)) + 0.1;
}
});
@@ -269,13 +269,13 @@ function benchClosures() {
var closureCreateTime = measureTime(function() {
var funcs = [];
for (var i = 0; i < iterations.medium; i++) {
push(funcs, makeAdder(i));
funcs.push(makeAdder(i));
}
});
var adders = [];
for (var i = 0; i < 1000; i++) {
push(adders, makeAdder(i));
adders.push(makeAdder(i));
}
var closureCallTime = measureTime(function() {
@@ -342,9 +342,9 @@ var objResults = benchObjectCreation();
log.console(" Literal: " + objResults.literalTime.toFixed(3) + "s => " +
(iterations.medium / objResults.literalTime).toFixed(1) + " creates/sec [" +
(objResults.literalTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Constructor: " + objResults.defructorTime.toFixed(3) + "s => " +
(iterations.medium / objResults.defructorTime).toFixed(1) + " creates/sec [" +
(objResults.defructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Constructor: " + objResults.constructorTime.toFixed(3) + "s => " +
(iterations.medium / objResults.constructorTime).toFixed(1) + " creates/sec [" +
(objResults.constructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Prototype: " + objResults.prototypeTime.toFixed(3) + "s => " +
(iterations.medium / objResults.prototypeTime).toFixed(1) + " creates/sec [" +
(objResults.prototypeTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
@@ -392,4 +392,4 @@ log.console("");
log.console("---------------------------------------------------------");
log.console("Benchmark complete.\n");
$stop()
$_.stop()

View File

@@ -8,15 +8,15 @@ var w = h
log.console(`P4\n${w} ${h}`);
for (var y = 0; y < h; ++y) {
for (let y = 0; y < h; ++y) {
// Create a blob for the row - we need w bits
var row = blob(w);
var row = new blob(w);
for (var x = 0; x < w; ++x) {
for (let x = 0; x < w; ++x) {
zr = zi = tr = ti = 0;
cr = 2 * x / w - 1.5;
ci = 2 * y / h - 1;
for (var i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
for (let i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
zi = 2 * zr * zi + ci;
zr = tr - ti + cr;
tr = zr * zr;
@@ -37,4 +37,4 @@ for (var y = 0; y < h; ++y) {
log.console(text(row, 'b'));
}
$stop()
$_.stop()

View File

@@ -1,12 +1,11 @@
var math = use('math/radians')
var N = 1000000;
var num = 0;
for (var i = 0; i < N; i ++) {
var x = 2 * $random();
var y = $random();
if (y < math.sine(x * x))
var x = 2 * $_.random();
var y = $_.random();
if (y < Math.sin(x * x))
num++;
}
log.console(2 * num / N);
$stop()
$_.stop()

View File

@@ -1,13 +1,19 @@
var math = use('math/radians')
var SOLAR_MASS = 4 * pi * pi;
var PI = Math.PI;
var SOLAR_MASS = 4 * PI * PI;
var DAYS_PER_YEAR = 365.24;
function Body(x, y, z, vx, vy, vz, mass) {
return {x, y, z, vx, vy, vz, mass};
this.x = x;
this.y = y;
this.z = z;
this.vx = vx;
this.vy = vy;
this.vz = vz;
this.mass = mass;
}
function Jupiter() {
return Body(
return new Body(
4.84143144246472090e+00,
-1.16032004402742839e+00,
-1.03622044471123109e-01,
@@ -19,7 +25,7 @@ function Jupiter() {
}
function Saturn() {
return Body(
return new Body(
8.34336671824457987e+00,
4.12479856412430479e+00,
-4.03523417114321381e-01,
@@ -31,7 +37,7 @@ function Saturn() {
}
function Uranus() {
return Body(
return new Body(
1.28943695621391310e+01,
-1.51111514016986312e+01,
-2.23307578892655734e-01,
@@ -43,7 +49,7 @@ function Uranus() {
}
function Neptune() {
return Body(
return new Body(
1.53796971148509165e+01,
-2.59193146099879641e+01,
1.79258772950371181e-01,
@@ -55,17 +61,17 @@ function Neptune() {
}
function Sun() {
return Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
return new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
}
var bodies = Array(Sun(), Jupiter(), Saturn(), Uranus(), Neptune());
function offsetMomentum() {
var px = 0;
var py = 0;
var pz = 0;
var size = length(bodies);
for (var i = 0; i < size; i++) {
let px = 0;
let py = 0;
let pz = 0;
var size = bodies.length;
for (let i = 0; i < size; i++) {
var body = bodies[i];
var mass = body.mass;
px += body.vx * mass;
@@ -80,21 +86,21 @@ function offsetMomentum() {
}
function advance(dt) {
var size = length(bodies);
var size = bodies.length;
for (var i = 0; i < size; i++) {
for (let i = 0; i < size; i++) {
var bodyi = bodies[i];
var vxi = bodyi.vx;
var vyi = bodyi.vy;
var vzi = bodyi.vz;
for (var j = i + 1; j < size; j++) {
let vxi = bodyi.vx;
let vyi = bodyi.vy;
let vzi = bodyi.vz;
for (let j = i + 1; j < size; j++) {
var bodyj = bodies[j];
var dx = bodyi.x - bodyj.x;
var dy = bodyi.y - bodyj.y;
var dz = bodyi.z - bodyj.z;
var d2 = dx * dx + dy * dy + dz * dz;
var mag = dt / (d2 * math.sqrt(d2));
var mag = dt / (d2 * Math.sqrt(d2));
var massj = bodyj.mass;
vxi -= dx * massj * mag;
@@ -111,7 +117,7 @@ function advance(dt) {
bodyi.vz = vzi;
}
for (var i = 0; i < size; i++) {
for (let i = 0; i < size; i++) {
var body = bodies[i];
body.x += dt * body.vx;
body.y += dt * body.vy;
@@ -120,36 +126,36 @@ function advance(dt) {
}
function energy() {
var e = 0;
var size = length(bodies);
let e = 0;
var size = bodies.length;
for (var i = 0; i < size; i++) {
for (let i = 0; i < size; i++) {
var bodyi = bodies[i];
e += 0.5 * bodyi.mass * ( bodyi.vx * bodyi.vx +
bodyi.vy * bodyi.vy + bodyi.vz * bodyi.vz );
for (var j = i + 1; j < size; j++) {
for (let j = i + 1; j < size; j++) {
var bodyj = bodies[j];
var dx = bodyi.x - bodyj.x;
var dy = bodyi.y - bodyj.y;
var dz = bodyi.z - bodyj.z;
var distance = math.sqrt(dx * dx + dy * dy + dz * dz);
var distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
e -= (bodyi.mass * bodyj.mass) / distance;
}
}
return e;
}
var n = arg[0] || 100000
var n = arg[0] || 1000000
offsetMomentum();
log.console(`n = ${n}`)
log.console(energy().toFixed(9))
for (var i = 0; i < n; i++)
for (let i = 0; i < n; i++)
advance(0.01);
log.console(energy().toFixed(9))
$stop()
$_.stop()

View File

@@ -1,7 +1,6 @@
var nota = use('nota')
var os = use('os')
var io = use('fd')
var json = use('json')
var io = use('io')
var ll = io.slurp('benchmarks/nota.json')
@@ -9,7 +8,7 @@ var newarr = []
var accstr = ""
for (var i = 0; i < 10000; i++) {
accstr += i;
newarrpush(i.toString())
newarr.push(i.toString())
}
// Arrays to store timing results
var jsonDecodeTimes = [];
@@ -19,35 +18,34 @@ var notaDecodeTimes = [];
var notaSizes = [];
// Run 100 tests
for (var i = 0; i < 100; i++) {
for (let i = 0; i < 100; i++) {
// JSON Decode test
var start = os.now();
let start = os.now();
var jll = json.decode(ll);
jsonDecodeTimespush((os.now() - start) * 1000);
jsonDecodeTimes.push((os.now() - start) * 1000);
// JSON Encode test
start = os.now();
var jsonStr = JSON.stringify(jll);
jsonEncodeTimespush((os.now() - start) * 1000);
let jsonStr = JSON.stringify(jll);
jsonEncodeTimes.push((os.now() - start) * 1000);
// NOTA Encode test
start = os.now();
var nll = nota.encode(jll);
notaEncodeTimespush((os.now() - start) * 1000);
notaEncodeTimes.push((os.now() - start) * 1000);
// NOTA Decode test
start = os.now();
var oll = nota.decode(nll);
notaDecodeTimespush((os.now() - start) * 1000);
notaDecodeTimes.push((os.now() - start) * 1000);
}
// Calculate statistics
function getStats(arr) {
return {
avg: reduce(arr, (a,b) => a+b, 0) / length(arr),
min: reduce(arr, min),
max: reduce(arr, max)
};
def avg = arr.reduce((a, b) => a + b) / arr.length;
def min = Math.min(...arr);
def max = Math.max(...arr);
return { avg, min, max };
}
// Pretty print results

View File

@@ -1,13 +1,11 @@
def math = use('math/radians');
function A(i,j) {
return 1/((i+j)*(i+j+1)/2+i+1);
}
function Au(u,v) {
for (var i=0; i<length(u); ++i) {
for (var i=0; i<u.length; ++i) {
var t = 0;
for (var j=0; j<length(u); ++j)
for (var j=0; j<u.length; ++j)
t += A(i,j) * u[j];
v[i] = t;
@@ -15,9 +13,9 @@ function Au(u,v) {
}
function Atu(u,v) {
for (var i=0; i<length(u); ++i) {
for (var i=0; i<u.length; ++i) {
var t = 0;
for (var j=0; j<length(u); ++j)
for (var j=0; j<u.length; ++j)
t += A(j,i) * u[j];
v[i] = t;
@@ -44,9 +42,9 @@ function spectralnorm(n) {
vv += v[i]*v[i];
}
return math.sqrt(vBv/vv);
return Math.sqrt(vBv/vv);
}
log.console(spectralnorm(arg[0]).toFixed(9));
$stop()
$_.stop()

View File

@@ -14,18 +14,18 @@
// Helper to run a function repeatedly and measure total time in seconds.
// Returns elapsed time in seconds.
function measureTime(fn, iterations) {
var t1 = os.now();
for (var i = 0; i < iterations; i++) {
let t1 = os.now();
for (let i = 0; i < iterations; i++) {
fn();
}
var t2 = os.now();
let t2 = os.now();
return t2 - t1;
}
// We'll define a function that does `encode -> decode` for a given value:
function roundTripWota(value) {
var encoded = wota.encode(value);
var decoded = wota.decode(encoded);
let encoded = wota.encode(value);
let decoded = wota.decode(encoded);
// Not doing a deep compare here, just measuring performance.
// (We trust the test suite to verify correctness.)
}
@@ -63,9 +63,15 @@ def benchmarks = [
{
name: "Large Array (1k numbers)",
// A thousand random numbers
data: [ array(1000, i => i *0.5) ],
data: [ Array.from({length:1000}, (_, i) => i * 0.5) ],
iterations: 1000
},
{
name: "Large Binary Blob (256KB)",
// A 256KB ArrayBuffer
data: [ new Uint8Array(256 * 1024).buffer ],
iterations: 200
}
];
// Print a header
@@ -73,23 +79,28 @@ log.console("Wota Encode/Decode Benchmark");
log.console("===================\n");
// We'll run each benchmark scenario in turn.
arrfor(benchmarks, function(bench) {
var totalIterations = bench.iterations * length(bench.data);
for (let bench of benchmarks) {
// We'll measure how long it takes to do 'iterations' *for each test value*
// in bench.data. The total loop count is `bench.iterations * bench.data.length`.
// Then we compute an overall encode+decode throughput (ops/s).
let totalIterations = bench.iterations * bench.data.length;
// We'll define a function that does a roundTrip for *each* data item in bench.data
// to measure in one loop iteration. Then we multiply by bench.iterations.
function runAllData() {
arrfor(bench.data, roundTripWota)
for (let val of bench.data) {
roundTripWota(val);
}
}
var elapsedSec = measureTime(runAllData, bench.iterations);
var opsPerSec = (totalIterations / elapsedSec).toFixed(1);
let elapsedSec = measureTime(runAllData, bench.iterations);
let opsPerSec = (totalIterations / elapsedSec).toFixed(1);
log.console(`${bench.name}:`);
log.console(` Iterations: ${bench.iterations} × ${length(bench.data)} data items = ${totalIterations}`);
log.console(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
log.console(` Elapsed: ${elapsedSec.toFixed(3)} s`);
log.console(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
})
}
// All done
log.console("Benchmark completed.\n");

View File

@@ -13,9 +13,9 @@
//
// Parse command line arguments
if (length(arg) != 2) {
if (arg.length != 2) {
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');
$stop()
$_.stop()
}
var lib_name = arg[0];
@@ -32,7 +32,7 @@ def libraries = [
decode: wota.decode,
// wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
getSize(encoded) {
return length(encoded);
return encoded.length;
}
},
{
@@ -41,7 +41,7 @@ def libraries = [
decode: nota.decode,
// nota also produces an ArrayBuffer:
getSize(encoded) {
return length(encoded);
return encoded.length;
}
},
{
@@ -50,8 +50,9 @@ def libraries = [
decode: json.decode,
// json produces a JS string. We'll measure its UTF-16 code unit length
// as a rough "size". Alternatively, you could convert to UTF-8 for
// a more accurate byte size. Here we just use `string.length`.
getSize(encodedStr) {
return length(encodedStr);
return encodedStr.length;
}
}
];
@@ -97,7 +98,7 @@ def benchmarks = [
},
{
name: "large_array",
data: [ array(1000, i => i) ],
data: [ Array.from({length:1000}, (_, i) => i) ],
iterations: 1000
},
];
@@ -107,9 +108,9 @@ def benchmarks = [
////////////////////////////////////////////////////////////////////////////////
function measureTime(fn) {
var start = os.now();
let start = os.now();
fn();
var end = os.now();
let end = os.now();
return (end - start); // in seconds
}
@@ -127,19 +128,19 @@ function runBenchmarkForLibrary(lib, bench) {
// Pre-store the encoded results for all items so we can measure decode time
// in a separate pass. Also measure total size once.
var encodedList = [];
var totalSize = 0;
let encodedList = [];
let totalSize = 0;
// 1) Measure ENCODING
var encodeTime = measureTime(() => {
for (var i = 0; i < bench.iterations; i++) {
let encodeTime = measureTime(() => {
for (let i = 0; i < bench.iterations; i++) {
// For each data item, encode it
for (var j = 0; j < length(bench.data); j++) {
var e = lib.encode(bench.data[j]);
for (let j = 0; j < bench.data.length; j++) {
let e = lib.encode(bench.data[j]);
// store only in the very first iteration, so we can decode them later
// but do not store them every iteration or we blow up memory.
if (i == 0) {
push(encodedList, e);
encodedList.push(e);
totalSize += lib.getSize(e);
}
}
@@ -147,9 +148,13 @@ function runBenchmarkForLibrary(lib, bench) {
});
// 2) Measure DECODING
var decodeTime = measureTime(() => {
for (var i = 0; i < bench.iterations; i++) {
arrfor(encodedList, lib.decode)
let decodeTime = measureTime(() => {
for (let i = 0; i < bench.iterations; i++) {
// decode everything we stored during the first iteration
for (let e of encodedList) {
let decoded = lib.decode(e);
// not verifying correctness here, just measuring speed
}
}
});
@@ -161,26 +166,26 @@ function runBenchmarkForLibrary(lib, bench) {
////////////////////////////////////////////////////////////////////////////////
// Find the requested library and scenario
var lib = libraries[find(libraries, l => l.name == lib_name)];
var bench = benchmarks[find(benchmarks, b => b.name == scenario_name)];
var lib = libraries.find(l => l.name == lib_name);
var bench = benchmarks.find(b => b.name == scenario_name);
if (!lib) {
log.console('Unknown library:', lib_name);
log.console('Available libraries:', text(array(libraries, l => l.name), ', '));
$stop()
log.console('Available libraries:', libraries.map(l => l.name).join(', '));
$_.stop()
}
if (!bench) {
log.console('Unknown scenario:', scenario_name);
log.console('Available scenarios:', text(array(benchmarks, b => b.name), ', '));
$stop()
log.console('Available scenarios:', benchmarks.map(b => b.name).join(', '));
$_.stop()
}
// Run the benchmark for this library/scenario combination
var { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
// Output json for easy parsing by hyperfine or other tools
var totalOps = bench.iterations * length(bench.data);
var totalOps = bench.iterations * bench.data.length;
var result = {
lib: lib_name,
scenario: scenario_name,
@@ -194,6 +199,6 @@ var result = {
decodeNsPerOp: (decodeTime / totalOps) * 1e9
};
log.console(result);
log.console(json.encode(result));
$stop()
$_.stop()

View File

@@ -1,42 +0,0 @@
// bootstrap.ce — regenerate .mach bytecode files consumed by the mach engine
// usage: cell bootstrap.ce
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var files = [
{src: "tokenize.cm", name: "tokenize", out: "tokenize.mach"},
{src: "parse.cm", name: "parse", out: "parse.mach"},
{src: "fold.cm", name: "fold", out: "fold.mach"},
{src: "mcode.cm", name: "mcode", out: "mcode.mach"},
{src: "internal/bootstrap.cm", name: "bootstrap", out: "internal/bootstrap.mach"},
{src: "internal/engine.cm", name: "engine", out: "internal/engine.mach"}
]
var i = 0
var entry = null
var src = null
var tok_result = null
var ast = null
var folded = null
var ast_json = null
var bytecode = null
var f = null
while (i < length(files)) {
entry = files[i]
src = text(fd.slurp(entry.src))
tok_result = tokenize(src, entry.src)
ast = parse(tok_result.tokens, src, entry.src, tokenize)
folded = fold(ast)
ast_json = json.encode(folded)
bytecode = mach_compile_ast(entry.name, ast_json)
f = fd.open(entry.out, "w")
fd.write(f, bytecode)
fd.close(f)
print(`wrote ${entry.out}`)
i = i + 1
}

124
build.ce
View File

@@ -1,124 +0,0 @@
// cell build [<locator>] - Build dynamic libraries locally for the current machine
//
// Usage:
// cell build Build dynamic libraries for all packages in shop
// cell build . Build dynamic library for current directory package
// cell build <locator> Build dynamic library for specific package
// cell build -t <target> Cross-compile dynamic libraries for target platform
// cell build -b <type> Build type: release (default), debug, or minsize
var build = use('build')
var shop = use('internal/shop')
var pkg_tools = use('package')
var fd = use('fd')
var target = null
var target_package = null
var buildtype = 'release'
var force_rebuild = false
var dry_run = false
for (var i = 0; i < length(args); i++) {
if (args[i] == '-t' || args[i] == '--target') {
if (i + 1 < length(args)) {
target = args[++i]
} else {
log.error('-t requires a target')
$stop()
}
} else if (args[i] == '-p' || args[i] == '--package') {
// Legacy support for -p flag
if (i + 1 < length(args)) {
target_package = args[++i]
} else {
log.error('-p requires a package name')
$stop()
}
} else if (args[i] == '-b' || args[i] == '--buildtype') {
if (i + 1 < length(args)) {
buildtype = args[++i]
if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') {
log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize')
$stop()
}
} else {
log.error('-b requires a buildtype (release, debug, minsize)')
$stop()
}
} else if (args[i] == '--force') {
force_rebuild = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--list-targets') {
log.console('Available targets:')
var targets = build.list_targets()
for (var t = 0; t < length(targets); t++) {
log.console(' ' + targets[t])
}
$stop()
} else if (!starts_with(args[i], '-') && !target_package) {
// Positional argument - treat as package locator
target_package = args[i]
}
}
// Resolve local paths to absolute paths
if (target_package) {
if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) {
var resolved = fd.realpath(target_package)
if (resolved) {
target_package = resolved
}
}
}
// Detect target if not specified
if (!target) {
target = build.detect_host_target()
if (target) log.console('Target: ' + target)
}
if (target && !build.has_target(target)) {
log.error('Invalid target: ' + target)
log.console('Available targets: ' + text(build.list_targets(), ', '))
$stop()
}
var packages = shop.list_packages()
log.console('Preparing packages...')
arrfor(packages, function(package) {
if (package == 'core') return
shop.extract(package)
})
if (target_package) {
// Build single package
log.console('Building ' + target_package + '...')
try {
var lib = build.build_dynamic(target_package, target, buildtype)
if (lib) {
log.console('Built: ' + lib)
}
} catch (e) {
log.error('Build failed: ' + e)
$stop()
}
} else {
// Build all packages
log.console('Building all packages...')
var results = build.build_all_dynamic(target, buildtype)
var success = 0
var failed = 0
for (var i = 0; i < length(results); i++) {
if (results[i].library) {
success++
} else if (results[i].error) {
failed++
}
}
log.console(`Build complete: ${success} libraries built${failed > 0 ? `, ${failed} failed` : ''}`)
}
$stop()

455
build.cm
View File

@@ -1,455 +0,0 @@
// build.cm - Simplified build utilities for Cell
//
// Key functions:
// Build.compile_file(pkg, file, target) - Compile a C file, returns object path
// Build.build_package(pkg, target) - Build all C files for a package
// Build.build_dynamic(pkg, target) - Build dynamic library for a package
// Build.build_static(packages, target, output) - Build static binary
var fd = use('fd')
var crypto = use('crypto')
var blob = use('blob')
var os = use('os')
var toolchains = use('toolchains')
var shop = use('internal/shop')
var pkg_tools = use('package')
var Build = {}
// ============================================================================
// Sigil replacement
// ============================================================================
// Get the local directory for prebuilt libraries
function get_local_dir() {
return shop.get_local_dir()
}
// Replace sigils in a string
// Currently supports: $LOCAL -> .cell/local full path
function replace_sigils(str) {
return replace(str, '$LOCAL', get_local_dir())
}
// Replace sigils in an array of flags
function replace_sigils_array(flags) {
var result = []
arrfor(flags, function(flag) {
push(result, replace_sigils(flag))
})
return result
}
Build.get_local_dir = get_local_dir
// ============================================================================
// Toolchain helpers
// ============================================================================
Build.list_targets = function() {
return array(toolchains)
}
Build.has_target = function(target) {
return toolchains[target] != null
}
Build.detect_host_target = function() {
var platform = os.platform()
var arch = os.arch ? os.arch() : 'arm64'
if (platform == 'macOS' || platform == 'darwin') {
return arch == 'x86_64' ? 'macos_x86_64' : 'macos_arm64'
} else if (platform == 'Linux' || platform == 'linux') {
return arch == 'x86_64' ? 'linux' : 'linux_arm64'
} else if (platform == 'Windows' || platform == 'windows') {
return 'windows'
}
return null
}
// ============================================================================
// Content-addressed build cache
// ============================================================================
function content_hash(str) {
var bb = stone(blob(str))
return text(crypto.blake2(bb, 32), 'h')
}
function get_build_dir() {
return shop.get_build_dir()
}
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
for (var i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current += parts[i] + '/'
if (!fd.stat(current).isDirectory) fd.mkdir(current)
}
}
Build.ensure_dir = ensure_dir
// ============================================================================
// Compilation
// ============================================================================
// Compile a single C file for a package
// Returns the object file path (content-addressed in .cell/build)
Build.compile_file = function(pkg, file, target, buildtype = 'release') {
var pkg_dir = shop.get_package_dir(pkg)
var src_path = pkg_dir + '/' + file
if (!fd.is_file(src_path)) {
throw Error('Source file not found: ' + src_path)
}
// Get flags (with sigil replacement)
var cflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'CFLAGS', target))
var target_cflags = toolchains[target].c_args || []
var cc = toolchains[target].c
// Symbol name for this file
var sym_name = shop.c_symbol_for_file(pkg, file)
// Build command
var cmd_parts = [cc, '-c', '-fPIC']
// Add buildtype-specific flags
if (buildtype == 'release') {
cmd_parts = array(cmd_parts, ['-O3', '-DNDEBUG'])
} else if (buildtype == 'debug') {
cmd_parts = array(cmd_parts, ['-O2', '-g'])
} else if (buildtype == 'minsize') {
cmd_parts = array(cmd_parts, ['-Os', '-DNDEBUG'])
}
push(cmd_parts, '-DCELL_USE_NAME=' + sym_name)
push(cmd_parts, '-I"' + pkg_dir + '"')
// Add package CFLAGS (resolve relative -I paths)
arrfor(cflags, function(flag) {
if (starts_with(flag, '-I') && !starts_with(flag, '-I/')) {
flag = '-I"' + pkg_dir + '/' + text(flag, 2) + '"'
}
push(cmd_parts, flag)
})
// Add target CFLAGS
arrfor(target_cflags, function(flag) {
push(cmd_parts, flag)
})
push(cmd_parts, '"' + src_path + '"')
var cmd_str = text(cmd_parts, ' ')
// Content hash: command + file content
var file_content = fd.slurp(src_path)
var hash_input = cmd_str + '\n' + text(file_content)
var hash = content_hash(hash_input)
var build_dir = get_build_dir()
ensure_dir(build_dir)
var obj_path = build_dir + '/' + hash
// Check if already compiled
if (fd.is_file(obj_path)) {
return obj_path
}
// Compile
var full_cmd = cmd_str + ' -o "' + obj_path + '"'
log.console('Compiling ' + file)
var ret = os.system(full_cmd)
if (ret != 0) {
throw Error('Compilation failed: ' + file)
}
return obj_path
}
// Build all C files for a package
// Returns array of object file paths
Build.build_package = function(pkg, target = Build.detect_host_target(), exclude_main, buildtype = 'release') {
var c_files = pkg_tools.get_c_files(pkg, target, exclude_main)
var objects = []
arrfor(c_files, function(file) {
var obj = Build.compile_file(pkg, file, target, buildtype)
push(objects, obj)
})
return objects
}
// ============================================================================
// Dynamic library building
// ============================================================================
// Compute link key from all inputs that affect the dylib output
function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
// Sort objects for deterministic hash
var sorted_objects = sort(objects)
// Build a string representing all link inputs
var parts = []
push(parts, 'target:' + target)
push(parts, 'cc:' + cc)
arrfor(sorted_objects, function(obj) {
// Object paths are content-addressed, so the path itself is the hash
push(parts, 'obj:' + obj)
})
arrfor(ldflags, function(flag) {
push(parts, 'ldflag:' + flag)
})
arrfor(target_ldflags, function(flag) {
push(parts, 'target_ldflag:' + flag)
})
return content_hash(text(parts, '\n'))
}
// Build a dynamic library for a package
// Output goes to .cell/lib/<package_name>.<ext>
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time
// Uses content-addressed store + symlink for caching
Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildtype = 'release') {
var objects = Build.build_package(pkg, target, true, buildtype) // exclude main.c
if (length(objects) == 0) {
log.console('No C files in ' + pkg)
return null
}
var lib_dir = shop.get_lib_dir()
var store_dir = lib_dir + '/store'
ensure_dir(lib_dir)
ensure_dir(store_dir)
var lib_name = shop.lib_name_for_package(pkg)
var dylib_ext = toolchains[target].system == 'windows' ? '.dll' : (toolchains[target].system == 'darwin' ? '.dylib' : '.so')
var stable_path = lib_dir + '/' + lib_name + dylib_ext
// Get link flags (with sigil replacement)
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
var target_ldflags = toolchains[target].c_link_args || []
var cc = toolchains[target].cpp || toolchains[target].c
var pkg_dir = shop.get_package_dir(pkg)
var local_dir = get_local_dir()
var tc = toolchains[target]
// Resolve relative -L paths in ldflags for hash computation
var resolved_ldflags = []
arrfor(ldflags, function(flag) {
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
}
push(resolved_ldflags, flag)
})
// Compute link key
var link_key = compute_link_key(objects, resolved_ldflags, target_ldflags, target, cc)
var store_path = store_dir + '/' + lib_name + '-' + link_key + dylib_ext
// Check if already linked in store
if (fd.is_file(store_path)) {
// Ensure symlink points to the store file
if (fd.is_link(stable_path)) {
var current_target = fd.readlink(stable_path)
if (current_target == store_path) {
// Already up to date
return stable_path
}
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return stable_path
}
// Build link command
var cmd_parts = [cc, '-shared', '-fPIC']
// Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
if (tc.system == 'darwin') {
cmd_parts = array(cmd_parts, [
'-undefined', 'dynamic_lookup',
'-Wl,-dead_strip',
'-Wl,-install_name,' + stable_path,
'-Wl,-rpath,@loader_path/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'linux') {
cmd_parts = array(cmd_parts, [
'-Wl,--allow-shlib-undefined',
'-Wl,--gc-sections',
'-Wl,-rpath,$ORIGIN/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'windows') {
// Windows DLLs: use --allow-shlib-undefined for mingw
push(cmd_parts, '-Wl,--allow-shlib-undefined')
}
// Add .cell/local to library search path
push(cmd_parts, '-L"' + local_dir + '"')
arrfor(objects, function(obj) {
push(cmd_parts, '"' + obj + '"')
})
// Do NOT link against core library - symbols resolved at dlopen time
cmd_parts = array(cmd_parts, resolved_ldflags)
cmd_parts = array(cmd_parts, target_ldflags)
push(cmd_parts, '-o')
push(cmd_parts, '"' + store_path + '"')
var cmd_str = text(cmd_parts, ' ')
log.console('Linking ' + lib_name + dylib_ext)
var ret = os.system(cmd_str)
if (ret != 0) {
throw Error('Linking failed: ' + pkg)
}
// Update symlink to point to the new store file
if (fd.is_link(stable_path)) {
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return stable_path
}
// ============================================================================
// Static binary building
// ============================================================================
// Build a static binary from multiple packages
// packages: array of package names
// output: output binary path
Build.build_static = function(packages, target = Build.detect_host_target(), output, buildtype = 'release') {
var all_objects = []
var all_ldflags = []
var seen_flags = {}
// Compile all packages
arrfor(packages, function(pkg) {
var is_core = (pkg == 'core')
// For core, include main.c; for others, exclude it
var objects = Build.build_package(pkg, target, !is_core, buildtype)
arrfor(objects, function(obj) {
push(all_objects, obj)
})
// Collect LDFLAGS (with sigil replacement)
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
var pkg_dir = shop.get_package_dir(pkg)
// Deduplicate based on the entire LDFLAGS string for this package
var ldflags_key = pkg + ':' + text(ldflags, ' ')
if (!seen_flags[ldflags_key]) {
seen_flags[ldflags_key] = true
arrfor(ldflags, function(flag) {
// Resolve relative -L paths
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
}
push(all_ldflags, flag)
})
}
})
if (length(all_objects) == 0) {
throw Error('No object files to link')
}
// Link
var cc = toolchains[target].c
var target_ldflags = toolchains[target].c_link_args || []
var exe_ext = toolchains[target].system == 'windows' ? '.exe' : ''
if (!ends_with(output, exe_ext) && exe_ext) {
output = output + exe_ext
}
var cmd_parts = [cc]
arrfor(all_objects, function(obj) {
push(cmd_parts, '"' + obj + '"')
})
arrfor(all_ldflags, function(flag) {
push(cmd_parts, flag)
})
arrfor(target_ldflags, function(flag) {
push(cmd_parts, flag)
})
push(cmd_parts, '-o', '"' + output + '"')
var cmd_str = text(cmd_parts, ' ')
log.console('Linking ' + output)
var ret = os.system(cmd_str)
if (ret != 0) {
throw Error('Linking failed with command: ' + cmd_str)
}
log.console('Built ' + output)
return output
}
// ============================================================================
// Convenience functions
// ============================================================================
// Build dynamic libraries for all installed packages
Build.build_all_dynamic = function(target, buildtype = 'release') {
target = target || Build.detect_host_target()
var packages = shop.list_packages()
var results = []
// Build core first
if (find(packages, 'core') != null) {
try {
var lib = Build.build_dynamic('core', target, buildtype)
push(results, { package: 'core', library: lib })
} catch (e) {
log.error('Failed to build core: ' + text(e))
push(results, { package: 'core', error: e })
}
}
// Build other packages
arrfor(packages, function(pkg) {
if (pkg == 'core') return
try {
var lib = Build.build_dynamic(pkg, target, buildtype)
push(results, { package: pkg, library: lib })
} catch (e) {
log.error('Failed to build ' + pkg + ': ')
log.console(e.message)
log.console(e.stack)
push(results, { package: pkg, error: e })
}
})
return results
}
return Build

View File

@@ -1 +0,0 @@
// cake file for making a playdate package

122
cell.md Normal file
View File

@@ -0,0 +1,122 @@
JAVASCRIPT VISION
CELLSCRIPT
Javascript to its core. Objects. What does the language need? It can be quite small, I think. The key is, ANYTHING that we want to be fast and JIT'd, must be present. So, record lookups. These are actually quicker in a jit'd language that have them as a feature. Most things should be libraries. Blobs need to be in the runtime.
The purpose of this is to be a great language for passing messages. So it should be fast at creating records first and foremost, and finding items on them. So it needs first class, jitt'd records.
Finally, it needs to use less memory. Deleting a bunch of this stuff should make that simpler.
What is present?
Objects, prototypes, numbers, arrays, strings, true, false, null.
Things to do:
merge typeof and instanceof. Misty has array? stone? number? etc; it needs to be generic. 5 is number returns true.
No new operator. It's the same idea though: simply instead of 'var guy = new sprite({x,y})' you would say 'var guy = sprite({x,y})', and sprite would simply be a function written to return a sprite object.
One number type. Dec64. Numeric stack can be added in later: a bigint library, for example, built inside cell.
Simplify the property attributes stuff. It is simple: objects have text keys and whatever values. Objects can also have objects as values. These work like symbols. You can share them, if desired. No well known symbols exist to eliminate that much misdirection. Obejcts basically work like private keys. If you serialize an object, objects that are keys are not serialized; only textual keys are. You can do something about it with a json() method that is invoked, if you desire. You cannot retrieve
var works like let; use var instead of let
no const
Function closures and _ => all work the same and close over the 'this' variable
Totally delete modules, coroutines, generators, proxy .. this deletes a lot of the big switch statement
Add the 'go' statement for tail calls
Add the 'do' statement
Implementation detail: separate out arrays and objects. They are not the same. Objects no longer need to track if they're fast arrays or not. They're not. Arrays are. Always.
Add the functional proxy idea. Log will be implemented through that.
Remove ===; it's just == now, and !=.
Remove 'continue'; now, break handles both. For a do statement, label it, and break to that label; so
var x = 0
do loop {
x++
if (x < 5) break loop // goes back to loop
break // exits loop
}
rename instanceof to 'is'
remove undefined; all are 'null' now
remove 'delete'; to remove a field, assign it to null
remove with
Remove Object. New records have a prototype of nothing. There are no more 'type prototypes' at all.
Arrays are their own type
Remove property descriptors. Properties are always settable, unless the object as a whole is stone. Stone is an object property instead of a shape property.
Syntax stuff .. would like to invoke functions without (). This can effectively simulate a "getter". Make ? and all other characters usable for names. No reserve words, which are endlessly irritating.
----
This will all actually come about gradually. Add a few things at a time, fix up code that did not adhere. For a lot of this, no new functions will even need to be written; it's a matter of not calling certain functions that are no longer relevant, or calling different functions when required.
## Benchmarks to implement
### general speed
binarytrees
coro-prime-sieve
edigits
fannkuch-redux
fasta
http-server
json serialize/deserialize
knucleotide
lru
mandelbrot
merkletrees
nbody
nsieve
pidigits
regex-redux
secp256k1
spectral-norm
### function calling and recursion stress - test goto
naive recursive fibonacci [fib(35) or fib(40)]
tak
ackermann
### numeric
sieve of eratosthenes [10^7 bits]
spectral norm [5500 x 5500 matrix]
n-body sim [50 000 - 100 000 steps]
mandelbrot [1600x1200 image, max iter = 50]
### memory & gc torture
binary trees [depth 18 (~500 000 nodes)]
richards task scheduler
fannkuch redux [n=11 or 12]
### dynamic object & property access
deltablue constraint solver
splay tree [256k nodes]
json, wota, nota decode->encode [use 2MB example]
### string / regex kernels
regex-DNA
fasta
word-frequency
### concurrency/message passing
ping-pong [two actors exhange a small record N times, 1M messages end to end]
chameneos [mating color swap game w/ randezvous]
For all, track memory and time.

View File

@@ -1,13 +0,0 @@
[compilation]
CFLAGS = "-Isource -Wno-incompatible-pointer-types -Wno-missing-braces -Wno-strict-prototypes -Wno-unused-function -Wno-int-conversion"
LDFLAGS = "-lstdc++ -lm"
[compilation.macos_arm64]
CFLAGS = "-x objective-c"
LDFLAGS = "-framework CoreFoundation -framework CFNetwork"
[compilation.playdate]
CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$LOCAL/PlaydateSDK/C_API"
[compilation.windows]
LDFLAGS = "-lws2_32 -lwinmm -liphlpapi -lbcrypt -lwinhttp -static-libgcc -static-libstdc++"

468
cellfs.cm
View File

@@ -1,468 +0,0 @@
var cellfs = {}
// CellFS: A filesystem implementation using miniz and raw OS filesystem
// Supports mounting multiple sources (fs, zip) and named mounts (@name)
var fd = use('fd')
var miniz = use('miniz')
var qop = use('qop')
var wildstar = use('wildstar')
// Internal state
var mounts = [] // Array of {source, type, handle, name}
var writepath = "."
// Helper to normalize paths
function normalize_path(path) {
if (!path) return ""
// Remove leading/trailing slashes and normalize
return replace(path, /^\/+|\/+$/, "")
}
// Check if a file exists in a specific mount
function mount_exists(mount, path) {
if (mount.type == 'zip') {
try {
mount.handle.mod(path)
return true
} catch (e) {
return false
}
} else if (mount.type == 'qop') {
try {
return mount.handle.stat(path) != null
} catch (e) {
return false
}
} else { // fs
var full_path = fd.join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isFile || st.isDirectory
} catch (e) {
return false
}
}
}
// Check if a path refers to a directory in a specific mount
function is_directory(path) {
var res = resolve(path)
var mount = res.mount
if (mount.type == 'zip') {
try {
return mount.handle.is_directory(path);
} catch (e) {
return false;
}
} else if (mount.type == 'qop') {
try {
return mount.handle.is_directory(path);
} catch (e) {
return false;
}
} else { // fs
var full_path = fd.join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isDirectory
} catch (e) {
return false
}
}
}
// Resolve a path to a specific mount and relative path
// Returns { mount, path } or throws/returns null
function resolve(path, must_exist) {
path = normalize_path(path)
// Check for named mount
if (starts_with(path, "@")) {
var idx = search(path, "/")
var mount_name = ""
var rel_path = ""
if (idx == null) {
mount_name = text(path, 1)
rel_path = ""
} else {
mount_name = text(path, 1, idx)
rel_path = text(path, idx + 1)
}
// Find named mount
var mount = null
arrfor(mounts, function(m) {
if (m.name == mount_name) {
mount = m
return true
}
}, false, true)
if (!mount) {
throw Error("Unknown mount point: @" + mount_name)
}
return { mount: mount, path: rel_path }
}
// Search path
var found_mount = null
arrfor(mounts, function(mount) {
if (mount_exists(mount, path)) {
found_mount = { mount: mount, path: path }
return true
}
}, false, true)
if (found_mount) {
return found_mount
}
if (must_exist) {
throw Error("File not found in any mount: " + path)
}
}
// Mount a source
function mount(source, name) {
// Check if source exists
var st = fd.stat(source)
var mount_info = {
source: source,
name: name || null,
type: 'fs',
handle: null,
zip_blob: null
}
if (st.isDirectory) {
mount_info.type = 'fs'
} else if (st.isFile) {
var blob = fd.slurp(source)
// Try QOP first (it's likely faster to fail?) or Zip?
// QOP open checks magic.
var qop_archive = null
try {
qop_archive = qop.open(blob)
} catch(e) {}
if (qop_archive) {
mount_info.type = 'qop'
mount_info.handle = qop_archive
mount_info.zip_blob = blob // keep blob alive
} else {
var zip = miniz.read(blob)
if (!is_object(zip) || !is_function(zip.count)) {
throw Error("Invalid archive file (not zip or qop): " + source)
}
mount_info.type = 'zip'
mount_info.handle = zip
mount_info.zip_blob = blob // keep blob alive
}
} else {
throw Error("Unsupported mount source type: " + source)
}
push(mounts, mount_info)
}
// Unmount
function unmount(name_or_source) {
mounts = filter(mounts, function(mount) {
return mount.name != name_or_source && mount.source != name_or_source
})
}
// Read file
function slurp(path) {
var res = resolve(path, true)
if (!res) throw Error("File not found: " + path)
if (res.mount.type == 'zip') {
return res.mount.handle.slurp(res.path)
} else if (res.mount.type == 'qop') {
var data = res.mount.handle.read(res.path)
if (!data) throw Error("File not found in qop: " + path)
return data
} else {
var full_path = fd.join_paths(res.mount.source, res.path)
return fd.slurp(full_path)
}
}
// Write file
function slurpwrite(path, data) {
var full_path = writepath + "/" + path
var f = fd.open(full_path, 'w')
fd.write(f, data)
fd.close(f)
}
// Check existence
function exists(path) {
var res = resolve(path, false)
if (starts_with(path, "@")) {
return mount_exists(res.mount, res.path)
}
return res != null
}
// Stat
function stat(path) {
var res = resolve(path, true)
if (!res) throw Error("File not found: " + path)
if (res.mount.type == 'zip') {
var mod = res.mount.handle.mod(res.path)
return {
filesize: 0,
modtime: mod * 1000,
isDirectory: false
}
} else if (res.mount.type == 'qop') {
var s = res.mount.handle.stat(res.path)
if (!s) throw Error("File not found in qop: " + path)
return {
filesize: s.size,
modtime: s.modtime,
isDirectory: s.isDirectory
}
} else {
var full_path = fd.join_paths(res.mount.source, res.path)
var s = fd.stat(full_path)
return {
filesize: s.size,
modtime: s.mtime,
isDirectory: s.isDirectory
}
}
}
// Get search paths
function searchpath() {
return array(mounts)
}
// Mount a package using the shop system
function mount_package(name) {
if (name == null) {
mount('.', null)
return
}
var shop = use('internal/shop')
var dir = shop.get_package_dir(name)
if (!dir) {
throw Error("Package not found: " + name)
}
mount(dir, name)
}
// New functions for qjs_io compatibility
function match(str, pattern) {
return wildstar.match(pattern, str, wildstar.WM_PATHNAME | wildstar.WM_PERIOD | wildstar.WM_WILDSTAR)
}
function rm(path) {
var res = resolve(path, true)
if (res.mount.type != 'fs') throw Error("Cannot delete from non-fs mount")
var full_path = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full_path)
if (st.isDirectory) fd.rmdir(full_path)
else fd.unlink(full_path)
}
function mkdir(path) {
var full = fd.join_paths(writepath, path)
fd.mkdir(full)
}
function set_writepath(path) {
writepath = path
}
function basedir() {
return fd.getcwd()
}
function prefdir(org, app) {
return "./"
}
function realdir(path) {
var res = resolve(path, false)
if (!res) return null
return fd.join_paths(res.mount.source, res.path)
}
function enumerate(path, recurse) {
if (path == null) path = ""
var res = resolve(path, true)
var results = []
function visit(curr_full, rel_prefix) {
var list = fd.readdir(curr_full)
if (!list) return
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
push(results, item_rel)
if (recurse) {
var st = fd.stat(fd.join_paths(curr_full, item))
if (st.isDirectory) {
visit(fd.join_paths(curr_full, item), item_rel)
}
}
})
}
if (res.mount.type == 'fs') {
var full = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
}
} else if (res.mount.type == 'qop') {
var all = res.mount.handle.list()
var prefix = res.path ? res.path + "/" : ""
var prefix_len = length(prefix)
// Use a set to avoid duplicates if we are simulating directories
var seen = {}
arrfor(all, function(p) {
if (starts_with(p, prefix)) {
var rel = text(p, prefix_len)
if (length(rel) == 0) return
if (!recurse) {
var slash = search(rel, '/')
if (slash != null) {
rel = text(rel, 0, slash)
}
}
if (!seen[rel]) {
seen[rel] = true
push(results, rel)
}
}
})
}
return results
}
function globfs(globs, dir) {
if (dir == null) dir = ""
var res = resolve(dir, true)
var results = []
function check_neg(path) {
var result = false
arrfor(globs, function(g) {
if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) {
result = true
return true
}
}, false, true)
return result
}
function check_pos(path) {
var result = false
arrfor(globs, function(g) {
if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) {
result = true
return true
}
}, false, true)
return result
}
function visit(curr_full, rel_prefix) {
if (rel_prefix && check_neg(rel_prefix)) return
var list = fd.readdir(curr_full)
if (!list) return
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
var child_full = fd.join_paths(curr_full, item)
var st = fd.stat(child_full)
if (st.isDirectory) {
if (!check_neg(item_rel)) {
visit(child_full, item_rel)
}
} else {
if (!check_neg(item_rel) && check_pos(item_rel)) {
push(results, item_rel)
}
}
})
}
if (res.mount.type == 'fs') {
var full = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
}
} else if (res.mount.type == 'qop') {
var all = res.mount.handle.list()
var prefix = res.path ? res.path + "/" : ""
var prefix_len = length(prefix)
arrfor(all, function(p) {
if (starts_with(p, prefix)) {
var rel = text(p, prefix_len)
if (length(rel) == 0) return
if (!check_neg(rel) && check_pos(rel)) {
push(results, rel)
}
}
})
}
return results
}
// Exports
cellfs.mount = mount
cellfs.mount_package = mount_package
cellfs.unmount = unmount
cellfs.slurp = slurp
cellfs.slurpwrite = slurpwrite
cellfs.exists = exists
cellfs.is_directory = is_directory
cellfs.stat = stat
cellfs.searchpath = searchpath
cellfs.match = match
cellfs.enumerate = enumerate
cellfs.globfs = globfs
cellfs.rm = rm
cellfs.mkdir = mkdir
cellfs.writepath = set_writepath
cellfs.basedir = basedir
cellfs.prefdir = prefdir
cellfs.realdir = realdir
cellfs.mount('.')
return cellfs

218
clean.ce
View File

@@ -1,218 +0,0 @@
// cell clean [<scope>] - Remove cached material to force refetch/rebuild
//
// Usage:
// cell clean Clean build outputs for current directory package
// cell clean . Clean build outputs for current directory package
// cell clean <locator> Clean build outputs for specific package
// cell clean shop Clean entire shop
// cell clean world Clean all world packages
//
// Options:
// --build Remove build outputs only (default)
// --fetch Remove fetched sources only
// --all Remove both build outputs and fetched sources
// --deep Apply to full dependency closure
// --dry-run Show what would be deleted
var shop = use('internal/shop')
var pkg = use('package')
var fd = use('fd')
var scope = null
var clean_build = false
var clean_fetch = false
var deep = false
var dry_run = false
for (var i = 0; i < length(args); i++) {
if (args[i] == '--build') {
clean_build = true
} else if (args[i] == '--fetch') {
clean_fetch = true
} else if (args[i] == '--all') {
clean_build = true
clean_fetch = true
} else if (args[i] == '--deep') {
deep = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell clean [<scope>] [options]")
log.console("")
log.console("Remove cached material to force refetch/rebuild.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Clean specific package")
log.console(" shop Clean entire shop")
log.console(" world Clean all world packages")
log.console("")
log.console("Options:")
log.console(" --build Remove build outputs only (default)")
log.console(" --fetch Remove fetched sources only")
log.console(" --all Remove both build outputs and fetched sources")
log.console(" --deep Apply to full dependency closure")
log.console(" --dry-run Show what would be deleted")
$stop()
} else if (!starts_with(args[i], '-')) {
scope = args[i]
}
}
// Default to --build if nothing specified
if (!clean_build && !clean_fetch) {
clean_build = true
}
// Default scope to current directory
if (!scope) {
scope = '.'
}
// Resolve local paths for single package scope
var is_shop_scope = (scope == 'shop')
var is_world_scope = (scope == 'world')
if (!is_shop_scope && !is_world_scope) {
if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) {
var resolved = fd.realpath(scope)
if (resolved) {
scope = resolved
}
}
}
var files_to_delete = []
var dirs_to_delete = []
// Gather packages to clean
var packages_to_clean = []
if (is_shop_scope) {
packages_to_clean = shop.list_packages()
} else if (is_world_scope) {
// For now, world is the same as shop
packages_to_clean = shop.list_packages()
} else {
// Single package
push(packages_to_clean, scope)
if (deep) {
try {
var deps = pkg.gather_dependencies(scope)
arrfor(deps, function(dep) {
push(packages_to_clean, dep)
})
} catch (e) {
// Skip if can't read dependencies
}
}
}
// Gather files to clean
var lib_dir = shop.get_lib_dir()
var build_dir = shop.get_build_dir()
var packages_dir = replace(shop.get_package_dir(''), /\/$/, '') // Get base packages dir
if (clean_build) {
if (is_shop_scope) {
// Clean entire build and lib directories
if (fd.is_dir(build_dir)) {
push(dirs_to_delete, build_dir)
}
if (fd.is_dir(lib_dir)) {
push(dirs_to_delete, lib_dir)
}
} else {
// Clean specific package libraries
arrfor(packages_to_clean, function(p) {
if (p == 'core') return
var lib_name = shop.lib_name_for_package(p)
var dylib_ext = '.dylib'
var lib_path = lib_dir + '/' + lib_name + dylib_ext
if (fd.is_file(lib_path)) {
push(files_to_delete, lib_path)
}
// Also check for .so and .dll
var so_path = lib_dir + '/' + lib_name + '.so'
var dll_path = lib_dir + '/' + lib_name + '.dll'
if (fd.is_file(so_path)) {
push(files_to_delete, so_path)
}
if (fd.is_file(dll_path)) {
push(files_to_delete, dll_path)
}
})
}
}
if (clean_fetch) {
if (is_shop_scope) {
// Clean entire packages directory (dangerous!)
if (fd.is_dir(packages_dir)) {
push(dirs_to_delete, packages_dir)
}
} else {
// Clean specific package directories
arrfor(packages_to_clean, function(p) {
if (p == 'core') return
var pkg_dir = shop.get_package_dir(p)
if (fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)) {
push(dirs_to_delete, pkg_dir)
}
})
}
}
// Execute or report
if (dry_run) {
log.console("Would delete:")
if (length(files_to_delete) == 0 && length(dirs_to_delete) == 0) {
log.console(" (nothing to clean)")
} else {
arrfor(files_to_delete, function(f) {
log.console(" [file] " + f)
})
arrfor(dirs_to_delete, function(d) {
log.console(" [dir] " + d)
})
}
} else {
var deleted_count = 0
arrfor(files_to_delete, function(f) {
try {
fd.unlink(f)
log.console("Deleted: " + f)
deleted_count++
} catch (e) {
log.error("Failed to delete " + f + ": " + e)
}
})
arrfor(dirs_to_delete, function(d) {
try {
if (fd.is_link(d)) {
fd.unlink(d)
} else {
fd.rmdir(d, 1) // recursive
}
log.console("Deleted: " + d)
deleted_count++
} catch (e) {
log.error("Failed to delete " + d + ": " + e)
}
})
if (deleted_count == 0) {
log.console("Nothing to clean.")
} else {
log.console("")
log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.")
}
}
$stop()

121
clone.ce
View File

@@ -1,121 +0,0 @@
// cell clone <origin> <path>
// Clones a cell package <origin> to the local <path>, and links it.
var shop = use('internal/shop')
var link = use('link')
var fd = use('fd')
var http = use('http')
var miniz = use('miniz')
if (length(args) < 2) {
log.console("Usage: cell clone <origin> <path>")
log.console("Clones a cell package to a local path and links it.")
$stop()
return
}
var origin = args[0]
var target_path = args[1]
// Resolve target path to absolute
if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) {
var resolved = fd.realpath(target_path)
if (resolved) {
target_path = resolved
} else {
// Path doesn't exist yet, resolve relative to cwd
var cwd = fd.realpath('.')
if (target_path == '.') {
target_path = cwd
} else if (starts_with(target_path, './')) {
target_path = cwd + text(target_path, 1)
} else if (starts_with(target_path, '../')) {
// Go up one directory from cwd
var parent = fd.dirname(cwd)
target_path = parent + text(target_path, 2)
}
}
}
// Check if target already exists
if (fd.is_dir(target_path)) {
log.console("Error: " + target_path + " already exists")
$stop()
return
}
log.console("Cloning " + origin + " to " + target_path + "...")
// Get the latest commit
var info = shop.resolve_package_info(origin)
if (!info || info == 'local') {
log.console("Error: " + origin + " is not a remote package")
$stop()
return
}
// Update to get the commit hash
var update_result = shop.update(origin)
if (!update_result) {
log.console("Error: Could not fetch " + origin)
$stop()
return
}
// Fetch and extract to the target path
var lock = shop.load_lock()
var entry = lock[origin]
if (!entry || !entry.commit) {
log.console("Error: No commit found for " + origin)
$stop()
return
}
var download_url = shop.get_download_url(origin, entry.commit)
log.console("Downloading from " + download_url)
try {
var zip_blob = http.fetch(download_url)
// Extract zip to target path
var zip = miniz.read(zip_blob)
if (!zip) {
log.console("Error: Failed to read zip archive")
$stop()
return
}
// Create target directory
fd.mkdir(target_path)
var count = zip.count()
for (var i = 0; i < count; i++) {
if (zip.is_directory(i)) continue
var filename = zip.get_filename(i)
var first_slash = search(filename, '/')
if (first_slash == null) continue
if (first_slash + 1 >= length(filename)) continue
var rel_path = text(filename, first_slash + 1)
var full_path = target_path + '/' + rel_path
var dir_path = fd.dirname(full_path)
// Ensure directory exists
if (!fd.is_dir(dir_path)) {
fd.mkdir(dir_path)
}
fd.slurpwrite(full_path, zip.slurp(filename))
}
log.console("Extracted to " + target_path)
// Link the origin to the cloned path
link.add(origin, target_path, shop)
log.console("Linked " + origin + " -> " + target_path)
} catch (e) {
log.console("Error: " + e.message)
if (e.stack) log.console(e.stack)
}
$stop()

View File

@@ -1,102 +0,0 @@
// compile.ce — compile a .cm module to native .dylib via QBE
//
// Usage:
// cell --core . compile.ce <file.cm>
//
// Produces <file>.dylib in the current directory.
var fd = use('fd')
var os = use('os')
if (length(args) < 1) {
print('usage: cell --core . compile.ce <file.cm>')
return
}
var file = args[0]
var base = file
if (ends_with(base, '.cm')) {
base = text(base, 0, length(base) - 3)
}
var safe = replace(replace(base, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use'
var tmp = '/tmp/qbe_' + safe
var ssa_path = tmp + '.ssa'
var s_path = tmp + '.s'
var o_path = tmp + '.o'
var rt_o_path = '/tmp/qbe_rt.o'
var dylib_path = base + '.dylib'
var cwd = fd.getcwd()
var rc = 0
// Step 1: emit QBE IL
print('emit qbe...')
rc = os.system('cd ' + cwd + ' && ./cell --core . --emit-qbe ' + file + ' > ' + ssa_path)
if (rc != 0) {
print('failed to emit qbe il')
return
}
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper
// Use awk via shell to avoid blob/slurpwrite issues with long strings
print('post-process...')
var awk_cmd = `awk '
need_label && /^[[:space:]]*[^@}]/ && NF > 0 {
print "@_dead_" dead_id; dead_id++; need_label=0
}
/^@/ || /^}/ || NF==0 { need_label=0 }
/^[[:space:]]*ret / || /^[[:space:]]*jmp / { need_label=1; print; next }
{ print }
' ` + ssa_path + ` > ` + tmp + `_fixed.ssa`
rc = os.system(awk_cmd)
if (rc != 0) {
print('post-process failed')
return
}
// Append wrapper function — called as symbol(ctx) by os.dylib_symbol.
// Delegates to cell_rt_module_entry which heap-allocates a frame
// (so closures survive) and calls cell_main.
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
rc = os.system(wrapper_cmd)
if (rc != 0) {
print('wrapper append failed')
return
}
// Step 3: compile QBE IL to assembly
print('qbe compile...')
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa')
if (rc != 0) {
print('qbe compilation failed')
return
}
// Step 4: assemble
print('assemble...')
rc = os.system('cc -c ' + s_path + ' -o ' + o_path)
if (rc != 0) {
print('assembly failed')
return
}
// Step 5: compile runtime stubs (cached — skip if already built)
if (!fd.is_file(rt_o_path)) {
print('compile runtime stubs...')
rc = os.system('cc -c ' + cwd + '/qbe_rt.c -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('runtime stubs compilation failed')
return
}
}
// Step 6: link dylib
print('link...')
rc = os.system('cc -shared -fPIC -undefined dynamic_lookup ' + o_path + ' ' + rt_o_path + ' -o ' + cwd + '/' + dylib_path)
if (rc != 0) {
print('linking failed')
return
}
print('built: ' + dylib_path)

246
crypto.c
View File

@@ -1,246 +0,0 @@
#include "cell.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "monocypher.h"
/*
Crypto Module Documentation
This module provides cryptographic functions using the Monocypher library.
All inputs and outputs are Blobs.
Functions:
- keypair() -> { public: Blob(256 bits), private: Blob(256 bits) }
Generates a new random X25519 keypair.
- shared(public_key, private_key) -> Blob(256 bits)
Computes a shared secret from your private key and another's public key (X25519).
Input keys must be 256 bits (32 bytes).
- blake2(data, [hash_size_bytes=32]) -> Blob
Computes the BLAKE2b hash of the data.
Default hash size is 32 bytes (256 bits). Supports 1-64 bytes.
- sign(secret_key, message) -> Blob(512 bits)
Signs a message using EdDSA.
secret_key must be 512 bits (64 bytes).
(Note: If you have a 32-byte seed, extend it first or use appropriate key generation).
Returns a 64-byte signature.
- verify(signature, public_key, message) -> bool
Verifies an EdDSA signature.
signature: 512 bits (64 bytes).
public_key: 256 bits (32 bytes).
Returns true if valid, false otherwise.
- lock(key, nonce, message, [ad]) -> Blob
Encrypts and authenticates a message using XChaCha20-Poly1305.
key: 256 bits (32 bytes).
nonce: 192 bits (24 bytes).
ad: Optional associated data (Blob).
Returns a blob containing the ciphertext followed by the 16-byte MAC.
- unlock(key, nonce, ciphertext_with_mac, [ad]) -> Blob or null
Decrypts and verifies a message.
key: 256 bits (32 bytes).
nonce: 192 bits (24 bytes).
ciphertext_with_mac: Must include the 16-byte MAC at the end.
ad: Optional associated data (Blob).
Returns the plaintext Blob if successful, or null if verification fails.
*/
// Helper to get blob data and check exact bit length
static void *get_blob_check_bits(JSContext *js, JSValue val, size_t expected_bits, const char *name) {
size_t bits;
void* result = js_get_blob_data_bits(js, &bits, val);
if (result == -1) {
return NULL; // Exception already thrown by js_get_blob_data_bits
}
if (bits != expected_bits) {
JS_ThrowTypeError(js, "%s: expected %zu bits, got %zu", name, expected_bits, bits);
return NULL;
}
return result;
}
// Helper to get any blob data (checking it is a stoned blob)
static void *get_blob_any(JSContext *js, JSValue val, size_t *out_bits, const char *name) {
void *result = js_get_blob_data_bits(js, out_bits, val);
if (result == -1)
return NULL;
return result;
}
JSValue js_crypto_shared(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 2) {
return JS_ThrowTypeError(js, "crypto.shared: expected public_key, private_key");
}
uint8_t *pub = get_blob_check_bits(js, argv[0], 256, "crypto.shared public_key");
if (!pub) return JS_EXCEPTION;
uint8_t *priv = get_blob_check_bits(js, argv[1], 256, "crypto.shared private_key");
if (!priv) return JS_EXCEPTION;
uint8_t shared[32];
crypto_x25519(shared, priv, pub);
return js_new_blob_stoned_copy(js, shared, 32);
}
JSValue js_crypto_blake2(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 1)
return JS_ThrowTypeError(js, "crypto.blake2: expected data blob");
size_t data_bits;
uint8_t *data = get_blob_any(js, argv[0], &data_bits, "crypto.blake2 data");
if (!data) return JS_EXCEPTION;
int32_t hash_len = 32;
if (argc > 1) {
if (JS_ToInt32(js, &hash_len, argv[1]))
return JS_EXCEPTION;
if (hash_len < 1 || hash_len > 64)
return JS_ThrowRangeError(js, "crypto.blake2: hash length must be between 1 and 64 bytes");
}
uint8_t hash[64];
// Use (bits + 7) / 8 to get byte length covering all bits
crypto_blake2b(hash, hash_len, data, (data_bits + 7) / 8);
return js_new_blob_stoned_copy(js, hash, hash_len);
}
JSValue js_crypto_sign(JSContext *js, JSValue self, int argc, JSValue *argv) {
if (argc < 2) return JS_ThrowTypeError(js, "crypto.sign: expected secret_key, message");
uint8_t *sk = get_blob_check_bits(js, argv[0], 512, "crypto.sign secret_key");
if (!sk) return JS_EXCEPTION;
size_t msg_bits;
uint8_t *msg = get_blob_any(js, argv[1], &msg_bits, "crypto.sign message");
if (!msg) return JS_EXCEPTION;
uint8_t sig[64];
crypto_eddsa_sign(sig, sk, msg, (msg_bits + 7) / 8);
return js_new_blob_stoned_copy(js, sig, 64);
}
JSValue js_crypto_verify(JSContext *js, JSValue self, int argc, JSValue *argv) {
if (argc < 3) return JS_ThrowTypeError(js, "crypto.verify: expected signature, public_key, message");
uint8_t *sig = get_blob_check_bits(js, argv[0], 512, "crypto.verify signature");
if (!sig) return JS_EXCEPTION;
uint8_t *pk = get_blob_check_bits(js, argv[1], 256, "crypto.verify public_key");
if (!pk) return JS_EXCEPTION;
size_t msg_bits;
uint8_t *msg = get_blob_any(js, argv[2], &msg_bits, "crypto.verify message");
if (!msg) return JS_EXCEPTION;
int ret = crypto_eddsa_check(sig, pk, msg, (msg_bits + 7) / 8);
return JS_NewBool(js, ret == 0);
}
JSValue js_crypto_lock(JSContext *js, JSValue self, int argc, JSValue *argv) {
if (argc < 3) return JS_ThrowTypeError(js, "crypto.lock: expected key, nonce, message, [ad]");
uint8_t *key = get_blob_check_bits(js, argv[0], 256, "crypto.lock key");
if (!key) return JS_EXCEPTION;
uint8_t *nonce = get_blob_check_bits(js, argv[1], 192, "crypto.lock nonce");
if (!nonce) return JS_EXCEPTION;
size_t msg_bits;
uint8_t *msg = get_blob_any(js, argv[2], &msg_bits, "crypto.lock message");
if (!msg) return JS_EXCEPTION;
size_t msg_len = (msg_bits + 7) / 8;
size_t ad_len = 0;
uint8_t *ad = NULL;
if (argc > 3 && !JS_IsNull(argv[3])) {
size_t ad_bits;
ad = get_blob_any(js, argv[3], &ad_bits, "crypto.lock ad");
if (!ad) return JS_EXCEPTION;
ad_len = (ad_bits + 7) / 8;
}
size_t out_len = msg_len + 16;
uint8_t *out = malloc(out_len);
if (!out) return JS_ThrowOutOfMemory(js);
// Output: [Ciphertext (msg_len)] [MAC (16)]
crypto_aead_lock(out, out + msg_len, key, nonce, ad, ad_len, msg, msg_len);
JSValue ret = js_new_blob_stoned_copy(js, out, out_len);
free(out);
return ret;
}
JSValue js_crypto_unlock(JSContext *js, JSValue self, int argc, JSValue *argv) {
if (argc < 3) return JS_ThrowTypeError(js, "crypto.unlock: expected key, nonce, ciphertext, [ad]");
uint8_t *key = get_blob_check_bits(js, argv[0], 256, "crypto.unlock key");
if (!key) return JS_EXCEPTION;
uint8_t *nonce = get_blob_check_bits(js, argv[1], 192, "crypto.unlock nonce");
if (!nonce) return JS_EXCEPTION;
size_t cipher_bits;
uint8_t *cipher = get_blob_any(js, argv[2], &cipher_bits, "crypto.unlock ciphertext");
if (!cipher) return JS_EXCEPTION;
size_t cipher_len = (cipher_bits + 7) / 8;
if (cipher_len < 16) return JS_ThrowTypeError(js, "crypto.unlock: ciphertext too short (min 16 bytes)");
size_t msg_len = cipher_len - 16;
size_t ad_len = 0;
uint8_t *ad = NULL;
if (argc > 3 && !JS_IsNull(argv[3])) {
size_t ad_bits;
ad = get_blob_any(js, argv[3], &ad_bits, "crypto.unlock ad");
if (!ad) return JS_EXCEPTION;
ad_len = (ad_bits + 7) / 8;
}
uint8_t *out = malloc(msg_len > 0 ? msg_len : 1);
if (!out) return JS_ThrowOutOfMemory(js);
// MAC is at cipher + msg_len
const uint8_t *mac = cipher + msg_len;
if (crypto_aead_unlock(out, mac, key, nonce, ad, ad_len, cipher, msg_len) != 0) {
free(out);
return JS_NULL;
}
JSValue ret = js_new_blob_stoned_copy(js, out, msg_len);
free(out);
return ret;
}
static const JSCFunctionListEntry js_crypto_funcs[] = {
JS_CFUNC_DEF("shared", 2, js_crypto_shared),
JS_CFUNC_DEF("blake2", 2, js_crypto_blake2),
JS_CFUNC_DEF("sign", 2, js_crypto_sign),
JS_CFUNC_DEF("verify", 3, js_crypto_verify),
JS_CFUNC_DEF("lock", 3, js_crypto_lock),
JS_CFUNC_DEF("unlock", 3, js_crypto_unlock),
};
JSValue js_crypto_use(JSContext *js)
{
JSValue obj = JS_NewObject(js);
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
return obj;
}

View File

@@ -1,28 +0,0 @@
#include "cell.h"
// TODO: Reimplement stack depth for register VM
JSC_CCALL(debug_stack_depth, return number2js(js, 0))
// TODO: Reimplement debug introspection for register VM
JSC_CCALL(debug_build_backtrace, return JS_NewArray(js))
JSC_CCALL(debug_closure_vars, return JS_NewObject(js))
JSC_CCALL(debug_set_closure_var, return JS_NULL;)
JSC_CCALL(debug_local_vars, return JS_NewObject(js))
JSC_CCALL(debug_fn_info, return JS_NewObject(js))
JSC_CCALL(debug_backtrace_fns, return JS_NewArray(js))
static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, stack_depth, 0),
MIST_FUNC_DEF(debug, build_backtrace, 0),
MIST_FUNC_DEF(debug, closure_vars, 1),
MIST_FUNC_DEF(debug, set_closure_var, 3),
MIST_FUNC_DEF(debug, local_vars, 1),
MIST_FUNC_DEF(debug, fn_info, 1),
MIST_FUNC_DEF(debug, backtrace_fns,0),
};
JSValue js_debug_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs));
return mod;
}

View File

@@ -1,27 +0,0 @@
#include "cell.h"
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(js, js2number(js,argv[0])))
// TODO: Reimplement memory usage reporting for new allocator
JSC_CCALL(os_calc_mem,
ret = JS_NewObject(js);
)
// TODO: Reimplement for register VM
JSC_CCALL(js_disassemble, return JS_NewArray(js);)
JSC_CCALL(js_fn_info, return JS_NewObject(js);)
static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(os, calc_mem, 0),
MIST_FUNC_DEF(os, mem_limit, 1),
MIST_FUNC_DEF(os, max_stacksize, 1),
MIST_FUNC_DEF(js, disassemble, 1),
MIST_FUNC_DEF(js, fn_info, 1),
};
JSValue js_js_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs));
return mod;
}

264
diff.ce
View File

@@ -1,264 +0,0 @@
// diff.ce — differential testing: run tests optimized vs unoptimized, compare results
//
// Usage:
// cell diff - diff all test files in current package
// cell diff suite - diff a specific test file (tests/suite.cm)
// cell diff tests/foo - diff a specific test file by path
var shop = use('internal/shop')
var pkg = use('package')
var fd = use('fd')
var time = use('time')
var _args = args == null ? [] : args
var analyze = use('os').analyze
var run_ast_fn = use('os').run_ast_fn
var run_ast_noopt_fn = use('os').run_ast_noopt_fn
if (!run_ast_noopt_fn) {
log.console("error: run_ast_noopt_fn not available (rebuild bootstrap)")
$stop()
return
}
// Parse arguments: diff [test_path]
var target_test = null
if (length(_args) > 0) {
target_test = _args[0]
}
function is_valid_package(dir) {
var _dir = dir == null ? '.' : dir
return fd.is_file(_dir + '/cell.toml')
}
if (!is_valid_package('.')) {
log.console('No cell.toml found in current directory')
$stop()
return
}
// Collect test files
function collect_tests(specific_test) {
var files = pkg.list_files(null)
var test_files = []
var i = 0
var f = null
var test_name = null
var match_name = null
var match_base = null
for (i = 0; i < length(files); i++) {
f = files[i]
if (starts_with(f, "tests/") && ends_with(f, ".cm")) {
if (specific_test) {
test_name = text(f, 0, -3)
match_name = specific_test
if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (test_name != match_base) continue
}
push(test_files, f)
}
}
return test_files
}
// Deep comparison of two values
function values_equal(a, b) {
var i = 0
var ka = null
var kb = null
if (a == b) return true
if (is_null(a) && is_null(b)) return true
if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
i = 0
while (i < length(a)) {
if (!values_equal(a[i], b[i])) return false
i = i + 1
}
return true
}
if (is_object(a) && is_object(b)) {
ka = array(a)
kb = array(b)
if (length(ka) != length(kb)) return false
i = 0
while (i < length(ka)) {
if (!values_equal(a[ka[i]], b[ka[i]])) return false
i = i + 1
}
return true
}
return false
}
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
if (is_array(val)) return `[array length=${text(length(val))}]`
if (is_object(val)) return `{record keys=${text(length(array(val)))}}`
return "<unknown>"
}
// Run a single test file through both paths
function diff_test_file(file_path) {
var mod_path = text(file_path, 0, -3)
var src_path = fd.realpath('.') + '/' + file_path
var src = null
var ast = null
var mod_opt = null
var mod_noopt = null
var results = {file: file_path, tests: [], passed: 0, failed: 0, errors: []}
var use_pkg = fd.realpath('.')
var opt_error = null
var noopt_error = null
var keys = null
var i = 0
var k = null
var opt_result = null
var noopt_result = null
var opt_err = null
var noopt_err = null
var _run_one_opt = null
var _run_one_noopt = null
// Build env for module loading
var make_env = function() {
return {
use: function(path) {
return shop.use(path, use_pkg)
}
}
}
// Read and parse
var _read = function() {
src = text(fd.slurp(src_path))
ast = analyze(src, src_path)
} disruption {
push(results.errors, `failed to parse ${file_path}`)
return results
}
_read()
if (length(results.errors) > 0) return results
// Run optimized
var _run_opt = function() {
mod_opt = run_ast_fn(mod_path, ast, make_env())
} disruption {
opt_error = "disrupted"
}
_run_opt()
// Run unoptimized
var _run_noopt = function() {
mod_noopt = run_ast_noopt_fn(mod_path, ast, make_env())
} disruption {
noopt_error = "disrupted"
}
_run_noopt()
// Compare module-level behavior
if (opt_error != noopt_error) {
push(results.errors, `module load mismatch: opt=${opt_error != null ? opt_error : "ok"} noopt=${noopt_error != null ? noopt_error : "ok"}`)
results.failed = results.failed + 1
return results
}
if (opt_error != null) {
// Both disrupted during load — that's consistent
results.passed = results.passed + 1
push(results.tests, {name: "<module>", status: "passed"})
return results
}
// If module returns a record of functions, test each one
if (is_object(mod_opt) && is_object(mod_noopt)) {
keys = array(mod_opt)
while (i < length(keys)) {
k = keys[i]
if (is_function(mod_opt[k]) && is_function(mod_noopt[k])) {
opt_result = null
noopt_result = null
opt_err = null
noopt_err = null
_run_one_opt = function() {
opt_result = mod_opt[k]()
} disruption {
opt_err = "disrupted"
}
_run_one_opt()
_run_one_noopt = function() {
noopt_result = mod_noopt[k]()
} disruption {
noopt_err = "disrupted"
}
_run_one_noopt()
if (opt_err != noopt_err) {
push(results.tests, {name: k, status: "failed"})
push(results.errors, `${k}: disruption mismatch opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`)
results.failed = results.failed + 1
} else if (!values_equal(opt_result, noopt_result)) {
push(results.tests, {name: k, status: "failed"})
push(results.errors, `${k}: result mismatch opt=${describe(opt_result)} noopt=${describe(noopt_result)}`)
results.failed = results.failed + 1
} else {
push(results.tests, {name: k, status: "passed"})
results.passed = results.passed + 1
}
}
i = i + 1
}
} else {
// Compare direct return values
if (!values_equal(mod_opt, mod_noopt)) {
push(results.tests, {name: "<return>", status: "failed"})
push(results.errors, `return value mismatch: opt=${describe(mod_opt)} noopt=${describe(mod_noopt)}`)
results.failed = results.failed + 1
} else {
push(results.tests, {name: "<return>", status: "passed"})
results.passed = results.passed + 1
}
}
return results
}
// Main
var test_files = collect_tests(target_test)
log.console(`Differential testing: ${text(length(test_files))} file(s)`)
var total_passed = 0
var total_failed = 0
var i = 0
var result = null
var j = 0
while (i < length(test_files)) {
result = diff_test_file(test_files[i])
log.console(` ${result.file}: ${text(result.passed)} passed, ${text(result.failed)} failed`)
j = 0
while (j < length(result.errors)) {
log.console(` MISMATCH: ${result.errors[j]}`)
j = j + 1
}
total_passed = total_passed + result.passed
total_failed = total_failed + result.failed
i = i + 1
}
log.console(`----------------------------------------`)
log.console(`Diff: ${text(total_passed)} passed, ${text(total_failed)} failed, ${text(total_passed + total_failed)} total`)
if (total_failed > 0) {
log.console(`DIFFERENTIAL FAILURES DETECTED`)
}
$stop()

12
docs/.pages Normal file
View File

@@ -0,0 +1,12 @@
nav:
- index.md
- tutorial.md
- actors.md
- rendering.md
- resources.md
- input.md
- exporting.md
- ...
- Appendix A - dull: dull
- Appendix B - api: api

View File

@@ -1,91 +0,0 @@
---
title: "Documentation"
description: "ƿit language documentation"
type: "docs"
---
![image](/images/wizard.png)
ƿit is an actor-based scripting language for building concurrent applications. It combines a familiar C-like syntax with the actor model of computation, optimized for low memory usage and simplicity.
## Key Features
- **Actor Model** — isolated memory, message passing, no shared state
- **Immutability** — `stone()` makes values permanently frozen
- **Prototype Inheritance** — objects without classes
- **C Integration** — seamlessly extend with native code
- **Cross-Platform** — deploy to desktop, web, and embedded
## Quick Start
```javascript
// hello.ce - A simple actor
print("Hello, ƿit!")
$stop()
```
```bash
pit hello
```
## Language
- [**ƿit Language**](/docs/language/) — syntax, types, and operators
- [**Actors and Modules**](/docs/actors/) — the execution model
- [**Requestors**](/docs/requestors/) — asynchronous composition
- [**Packages**](/docs/packages/) — code organization and sharing
## Reference
- [**Built-in Functions**](/docs/functions/) — intrinsics reference
- [text](/docs/library/text/) — text conversion and manipulation
- [number](/docs/library/number/) — numeric conversion and operations
- [array](/docs/library/array/) — array creation and manipulation
- [object](/docs/library/object/) — object creation, prototypes, and serialization
## Standard Library
Modules loaded with `use()`:
- [blob](/docs/library/blob/) — binary data
- [time](/docs/library/time/) — time and dates
- [math](/docs/library/math/) — trigonometry and math
- [json](/docs/library/json/) — JSON encoding/decoding
- [random](/docs/library/random/) — random numbers
## Tools
- [**Command Line**](/docs/cli/) — the `pit` tool
- [**Testing**](/docs/testing/) — writing and running tests
- [**Writing C Modules**](/docs/c-modules/) — native extensions
## Architecture
ƿit programs are organized into **packages**. Each package contains:
- **Modules** (`.cm`) — return a value, cached and frozen
- **Actors** (`.ce`) — run independently, communicate via messages
- **C files** (`.c`) — compiled to native libraries
Actors never share memory. They communicate by sending messages, which are automatically serialized. This makes concurrent programming safe and predictable.
## Installation
```bash
# Clone and bootstrap
git clone https://gitea.pockle.world/john/cell
cd cell
make bootstrap
```
The ƿit shop is stored at `~/.pit/`.
## Development
After making changes, recompile with:
```bash
make
```
Run `cell --help` to see all available CLI flags.

View File

@@ -1,263 +1,77 @@
---
title: "Actors and Modules"
description: "The ƿit execution model"
weight: 20
type: "docs"
---
# Programs: Programs and Modules
ƿit organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
Prosperon organizes your code into two broad categories: **modules** and **programs**. Modules are used to extend programs with new functionality, while programs are used to spawn actors.
## The Actor Model
## Modules
ƿit is built on the actor model of computation. Each actor:
A **module** is any file that returns a single value. This return value is commonly an object, but it can be any data type (string, number, function, etc.). Once a module returns its value, Prosperon **freezes** that value, preventing accidental modification. The module is then cached so that subsequent imports of the same module dont re-run the file—they reuse the cached result.
- Has its own **isolated memory** — actors never share state
- Runs to completion each **turn** — no preemption
- Performs its own **garbage collection**
- Communicates only through **message passing**
### Importing a Module
This isolation makes concurrent programming safer and more predictable.
Use the built-in `use` function to import a module by file path (or by name if resolvable via Prosperons path settings). For example:
## Modules (.cm)
A module is a script that **returns a value**. The returned value is cached and frozen (made stone).
```javascript
// math_utils.cm
var math = use('math/radians')
var distance = function(x1, y1, x2, y2) {
var dx = x2 - x1
var dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
}
var midpoint = function(x1, y1, x2, y2) {
return {
x: (x1 + x2) / 2,
y: (y1 + y2) / 2
}
}
return {
distance: distance,
midpoint: midpoint
}
```
var myModule = use('scripts/modules/myModule')
```
**Key properties:**
`use('module')` returns the **exact** same object if called multiple times, since modules are cached and not re-run.
- **Must return a value** — it's an error not to
- **Executed once per actor** — subsequent `use()` calls return the cached value
- **Return value is stone** — immutable, safe to share
- Modules can import other modules with `use()`
Dull based modules are resolved by searching for them from the `prosperon.PATH` array. Engine modules are stored under `scripts/modules`, which is already added to the PATH for you.
### Using Modules
Prosperon can also load C based modules. If two modules have the same path resolution, the C based library will be imported.
```javascript
var utils = use('math_utils')
var d = utils.distance(0, 0, 3, 4) // 5
```
## Programs
## Actors (.ce)
An **program** is a file that **does not** return a value. Instead, the files contents run top to bottom as soon as the program is spawned. Programs are your games “live” scripts: each program can hold its own state and logic, spawn sub-programs, schedule timed tasks, and eventually **kill** itself (or be killed) when its done.
An actor is a script that **does not return a value**. It runs as an independent unit of execution.
### Program Intrinsic Functions
```javascript
// worker.ce
print("Worker started")
Certain functions are intrinsic to the program and cannot be overridden. Theyre assigned to each new program instance at spawn time:
$receiver(function(msg, reply) {
print("Received:", msg)
// Process message...
})
```
1. **`spawn(script, config, callback)`**
Creates (spawns) a new program from another script file.
- **`script`**: Path to the program script (a file containing statements, not returning anything).
- **`config`**: Optional object of extra properties to assign to the new program.
- **`callback(underling, info)`**: Optional function invoked right after the program is instantiated but before it fully initializes.
**Key properties:**
The newly spawned program:
- Receives a reference to its parent (the `overling`) and can store child programs (the `underlings`).
- Automatically calls `awake()` if that function is defined, after basic setup completes.
- Registers any recognized event handlers (like `update`, `draw`, etc.) if they exist.
- **Must not return a value** — it's an error to return
- Has access to **actor intrinsics** (functions starting with `$`)
- Runs until explicitly stopped or crashes
2. **`kill()`**
Destroys the program, all of its timers, and recursively kills any underling (child) programs. If the program has a parent, it is removed from the parents `underlings` set.
## Actor Intrinsics
3. **`delay(fn, seconds)`**
Runs the given function `fn` after `seconds`. This is implemented under the hood with a timer that automatically clears itself once it fires.
- **Example**:
```js
this.delay(_ => {
log.console("3 seconds later!")
}, 3)
```
Actors have access to special functions prefixed with `$`:
4. **`clear()`**
Recursively kills all child programs, clearing your immediate `underlings` set. This is not called automatically. You can use it to manually clean up all children without necessarily killing the program itself.
### $me
### The program Lifecycle
Reference to the current actor.
Specific hooks can be set on a program when it is initialized.
```javascript
print($me) // actor reference
```
- **Awake**: If the new program defines `awake()`, Prosperon calls it after the script finishes its top-level execution. This is a common place to do initialization.
- **Garbage**: When the program is killed, if it has a `garbage()` function, Prosperon calls it before final removal.
- **Then**: If the program has a `then()` function, Prosperon calls it at the very end of the kill process, allowing any final statements after your `garbage()` logic completes.
- **Registration**: In addition, if the object has **any** function named the same thing as a hook created with **prosperon.on**, that function will be registered with it after initialization.
### $stop()
### Overlings and Underlings
Stop the current actor.
Programs have access to its creator and other programs created underneath it, termed its overling and underlings.
```javascript
$stop()
```
- **`this.overling`** is the parent program that spawned the current one.
- **`this.underlings`** is a set of child programs that the current program has spawned.
### $send(actor, message, callback)
Killing a parent automatically kills all of its underlings, which in turn can kill their own underlings, and so on.
Send a message to another actor.
## Program Documentation
```javascript
$send(other_actor, {type: "ping", data: 42}, function(reply) {
print("Got reply:", reply)
})
```
Messages are automatically **splatted** — flattened to plain data without prototypes.
### $start(callback, program)
Start a new actor from a script.
```javascript
$start(function(new_actor) {
print("Started:", new_actor)
}, "worker")
```
### $delay(callback, seconds)
Schedule a callback after a delay.
```javascript
$delay(function() {
print("5 seconds later")
}, 5)
```
### $clock(callback)
Get called every frame/tick.
```javascript
$clock(function(dt) {
// Called each tick with delta time
})
```
### $receiver(callback)
Set up a message receiver.
```javascript
$receiver(function(message, reply) {
// Handle incoming message
reply({status: "ok"})
})
```
### $portal(callback, port)
Open a network port.
```javascript
$portal(function(connection) {
// Handle new connection
}, 8080)
```
### $contact(callback, record)
Connect to a remote address.
```javascript
$contact(function(connection) {
// Connected
}, {host: "example.com", port: 80})
```
### $time_limit(requestor, seconds)
Wrap a requestor with a timeout. See [Requestors](/docs/requestors/) for details.
```javascript
$time_limit(my_requestor, 10) // 10 second timeout
```
### $couple(actor)
Couple the current actor to another actor. When the coupled actor dies, the current actor also dies. Coupling is automatic between an actor and its overling (parent).
```javascript
$couple(other_actor)
```
### $unneeded(callback, seconds)
Schedule the actor for removal after a specified time.
```javascript
$unneeded(function() {
// cleanup before removal
}, 30)
```
### $connection(callback, actor, config)
Get information about the connection to another actor, such as latency, bandwidth, and activity.
```javascript
$connection(function(info) {
print(info.latency)
}, other_actor, {})
```
## Module Resolution
When you call `use('name')`, ƿit searches:
1. **Current package** — files relative to package root
2. **Dependencies** — packages declared in `pit.toml`
3. **Core** — built-in ƿit modules
```javascript
// From within package 'myapp':
use('utils') // myapp/utils.cm
use('helper/math') // myapp/helper/math.cm
use('json') // core json module
use('otherlib/foo') // dependency 'otherlib', file foo.cm
```
Files starting with underscore (`_helper.cm`) are private to the package.
## Example: Simple Actor System
```javascript
// main.ce - Entry point
var config = use('config')
print("Starting application...")
$start(function(worker) {
$send(worker, {task: "process", data: [1, 2, 3]})
}, "worker")
$delay(function() {
print("Shutting down")
$stop()
}, 10)
```
```javascript
// worker.ce - Worker actor
$receiver(function(msg, reply) {
if (msg.task == "process") {
var result = array(msg.data, x => x * 2)
reply({result: result})
}
})
```
```javascript
// config.cm - Shared configuration
return {
debug: true,
timeout: 30
}
```
Prosperon includes a module called `doc.js` which helps generate documentation for your modules and programs. Any function and value can be assigned a docstring, and prosperon will then be able to generate documentation for it via doc.js. Look under the module API for more info.

11
docs/api/actor.md Normal file
View File

@@ -0,0 +1,11 @@
# actor
### toString() <sub>function</sub>
### spawn(script, config, callback) <sub>function</sub>
### clear() <sub>function</sub>
### kill() <sub>function</sub>
### delay(fn, seconds) <sub>function</sub>

37
docs/api/console.md Normal file
View File

@@ -0,0 +1,37 @@
# console
The console object provides various logging, debugging, and output methods.
### print() <sub>function</sub>
### spam(msg) <sub>function</sub>
Output a spam-level message for very verbose logging.
### debug(msg) <sub>function</sub>
Output a debug-level message.
### info(msg) <sub>function</sub>
Output info level message.
### warn(msg) <sub>function</sub>
Output warn level message.
### log(msg) <sub>function</sub>
Output directly to in game console.
### error(e) <sub>function</sub>
Output error level message, and print stacktrace.
### panic(e) <sub>function</sub>
Output a panic-level message and exit the program.
### assert(op, str = `assertion failed [value '${op}']`) <sub>function</sub>
If the condition is false, print an error and panic.

5
docs/api/index.md Normal file
View File

@@ -0,0 +1,5 @@
# Appendix B - api
This is a complete list of accessible functions and parameters that are built into Prosperon. For the most part, developers will concern themselves with the modules, all of which can be imported with `use`.
Types document particular javascript objects with a specific object in their prototype chain, which can allow access to an underlying C data structure. A lot of these are used only internally by Prosperon, but brave developers can pick around in the module internals to see how they're used and do their own thing if they want!

87
docs/api/modules/actor.md Normal file
View File

@@ -0,0 +1,87 @@
# actor
A set of utilities for iterating over a hierarchy of actor-like objects, as well
as managing tag-based lookups. Objects are assumed to have a "objects" property,
pointing to children or sub-objects, forming a tree.
### all_objects(fn, startobj) <sub>function</sub>
Iterate over each object (and its sub-objects) in the hierarchy, calling fn for each one.
**fn**: A callback function that receives each object. If it returns a truthy value, iteration stops and that value is returned.
**startobj**: The root object at which iteration begins, default is the global "world".
**Returns**: The first truthy value returned by fn, or undefined if none.
### find_object(fn, startobj) <sub>function</sub>
Intended to find a matching object within the hierarchy.
**fn**: A callback or criteria to locate a particular object.
**startobj**: The root object at which search begins, default "world".
**Returns**: Not yet implemented.
### tag_add(tag, obj) <sub>function</sub>
Associate the given object with the specified tag. Creates a new tag set if it does not exist.
**tag**: A string tag to associate with the object.
**obj**: The object to add under this tag.
**Returns**: None
### tag_rm(tag, obj) <sub>function</sub>
Remove the given object from the specified tags set, if it exists.
**tag**: The tag to remove the object from.
**obj**: The object to remove from the tag set.
**Returns**: None
### tag_clear_guid(obj) <sub>function</sub>
Remove the object from all tag sets.
**obj**: The object whose tags should be cleared.
**Returns**: None
### objects_with_tag(tag) <sub>function</sub>
Retrieve all objects currently tagged with the specified tag.
**tag**: A string tag to look up.
**Returns**: An array of objects associated with the given tag.

View File

@@ -0,0 +1,46 @@
# camera
### list() <sub>function</sub>
Return an array of available camera device IDs.
**Returns**: An array of camera IDs, or undefined if no cameras are available.
### open(id) <sub>function</sub>
Open a camera device with the given ID.
**id**: The camera ID to open.
**Returns**: A camera object on success, or throws an error if the camera cannot be opened.
### name(id) <sub>function</sub>
Return the name of the camera with the given ID.
**id**: The camera ID to query.
**Returns**: A string with the camera's name, or throws an error if the name cannot be retrieved.
### position(id) <sub>function</sub>
Return the physical position of the camera with the given ID.
**id**: The camera ID to query.
**Returns**: A string indicating the camera position ("unknown", "front", or "back").

7
docs/api/modules/cmd.md Normal file
View File

@@ -0,0 +1,7 @@
# cmd
### length <sub>number</sub>
### name <sub>string</sub>
### prototype <sub>object</sub>

View File

@@ -0,0 +1,7 @@
# color
### Color <sub>object</sub>
### esc <sub>object</sub>
### ColorMap <sub>object</sub>

76
docs/api/modules/debug.md Normal file
View File

@@ -0,0 +1,76 @@
# debug
### stack_depth() <sub>function</sub>
Return the current stack depth.
**Returns**: A number representing the stack depth.
### build_backtrace() <sub>function</sub>
Build and return a backtrace of the current call stack.
**Returns**: An object representing the call stack backtrace.
### closure_vars(fn) <sub>function</sub>
Return the closure variables for a given function.
**fn**: The function object to inspect.
**Returns**: An object containing the closure variables.
### local_vars(depth) <sub>function</sub>
Return the local variables for a specific stack frame.
**depth**: The stack frame depth to inspect.
**Returns**: An object containing the local variables at the specified depth.
### fn_info(fn) <sub>function</sub>
Return metadata about a given function.
**fn**: The function object to inspect.
**Returns**: An object with metadata about the function.
### backtrace_fns() <sub>function</sub>
Return an array of functions in the current backtrace.
**Returns**: An array of function objects from the call stack.
### dump_obj(obj) <sub>function</sub>
Return a string representation of a given object.
**obj**: The object to dump.
**Returns**: A string describing the object's contents.

39
docs/api/modules/dmon.md Normal file
View File

@@ -0,0 +1,39 @@
# dmon
### watch() <sub>function</sub>
Start watching the root directory, recursively.
This function begins monitoring the specified directory and its subdirectories recursively for events such as file creation, deletion, modification, or movement. Events are queued and can be retrieved by calling poll.
:throws: An error if dmon is already watching.
**Returns**: None
### unwatch() <sub>function</sub>
Stop watching the currently monitored directory.
This function halts filesystem monitoring for the directory previously set by watch. It clears the watch state, allowing a new watch to be started.
:throws: An error if no directory is currently being watched.
**Returns**: None
### poll(callback) <sub>function</sub>
Retrieve and process queued filesystem events.
This function dequeues all pending filesystem events and invokes the provided callback for each one. The callback receives an event object with properties: 'action' (string: "create", "delete", "modify", or "move"), 'root' (string: watched directory), 'file' (string: affected file path), and 'old' (string: previous file path for move events, empty if not applicable).
**callback**: A function to call for each event, receiving an event object as its argument.
**Returns**: None

38
docs/api/modules/doc.md Normal file
View File

@@ -0,0 +1,38 @@
# doc
Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object".
Docstrings are set to the symbol `cell.DOC`
```js
// Suppose we have a module that returns a function
function greet(name) { log.console("Hello, " + name) }
// We can attach a docstring
greet.doc = `
Greets the user by name.
`
// A single function is a valid return!
return greet
```
```js
// Another way is to add a docstring object to an object
var greet = {
hello() { log.console('hello!') }
}
greet[cell.DOC] = {}
greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
greet[cell.DOC].hello = 'A greeter that says, "hello!"'
```
**name**: The name of the person to greet.
### writeDocFile(obj, title) <sub>function</sub>
Return a markdown string for a given obj, with an optional title.

228
docs/api/modules/draw2d.md Normal file
View File

@@ -0,0 +1,228 @@
# draw2d
A collection of 2D drawing functions that operate in screen space. Provides primitives
for lines, rectangles, text, sprite drawing, etc.
### point(pos, size, color) <sub>function</sub>
**pos**: A 2D position ([x, y]) where the point should be drawn.
**size**: The size of the point (not currently affecting rendering).
**color**: The color of the point, defaults to Color.blue.
**Returns**: None
### line(points, color, thickness, pipeline) <sub>function</sub>
**points**: An array of 2D positions representing the line vertices.
**color**: The color of the line, default Color.white.
**thickness**: The line thickness, default 1.
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: None
### cross(pos, size, color, thickness, pipe) <sub>function</sub>
**pos**: The center of the cross as a 2D position ([x, y]).
**size**: Half the size of each cross arm.
**color**: The color of the cross, default Color.red.
**thickness**: The thickness of each line, default 1.
**pipe**: (Optional) A pipeline or rendering state object.
**Returns**: None
### arrow(start, end, color, wingspan, wingangle, pipe) <sub>function</sub>
**start**: The start position of the arrow ([x, y]).
**end**: The end (tip) position of the arrow ([x, y]).
**color**: The color, default Color.red.
**wingspan**: The length of each arrowhead 'wing', default 4.
**wingangle**: Wing rotation in degrees, default 10.
**pipe**: (Optional) A pipeline or rendering state object.
**Returns**: None
### rectangle(rect, color, pipeline) <sub>function</sub>
**rect**: A rectangle object with {x, y, width, height}.
**color**: The fill color, default Color.white.
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: None
### tile(image, rect, color, tile, pipeline) <sub>function</sub>
:raises Error: If no image is provided.
**image**: An image object or string path to a texture.
**rect**: A rectangle specifying draw location/size ({x, y, width, height}).
**color**: The color tint, default Color.white.
**tile**: A tiling definition ({repeat_x, repeat_y}), default tile_def.
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: None
### slice9(image, rect, slice, color, info, pipeline) <sub>function</sub>
:raises Error: If no image is provided.
**image**: An image object or string path to a texture.
**rect**: A rectangle specifying draw location/size, default [0, 0].
**slice**: The pixel inset or spacing for the 9-slice (number or object).
**color**: The color tint, default Color.white.
**info**: A slice9 info object controlling tiling of edges/corners.
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: None
### image(image, rect, rotation, color, pipeline) <sub>function</sub>
:raises Error: If no image is provided.
**image**: An image object or string path to a texture.
**rect**: A rectangle specifying draw location/size, default [0,0]; width/height default to image size.
**rotation**: Rotation in degrees (not currently used).
**color**: The color tint, default none.
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: A sprite object that was created for this draw call.
### images(image, rects, config) <sub>function</sub>
:raises Error: If no image is provided.
**image**: An image object or string path to a texture.
**rects**: An array of rectangle objects ({x, y, width, height}) to draw.
**config**: (Unused) Additional config data if needed.
**Returns**: An array of sprite objects created and queued for rendering.
### sprites(sprites, sort, pipeline) <sub>function</sub>
**sprites**: An array of sprite objects to draw.
**sort**: Sorting mode or order, default 0.
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: None
### circle(pos, radius, color, inner_radius, pipeline) <sub>function</sub>
**pos**: Center of the circle ([x, y]).
**radius**: The circle radius.
**color**: The fill color of the circle, default none.
**inner_radius**: (Unused) Possibly ring thickness, default 1.
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: None
### text(text, rect, font, size, color, wrap, pipeline) <sub>function</sub>
**text**: The string to draw.
**rect**: A rectangle specifying draw position (and possibly wrapping area).
**font**: A font object or string path, default sysfont.
**size**: (Unused) Possibly intended for scaling the font size.
**color**: The text color, default Color.white.
**wrap**: Pixel width for text wrapping, default 0 (no wrap).
**pipeline**: (Optional) A pipeline or rendering state object.
**Returns**: None

45
docs/api/modules/enet.md Normal file
View File

@@ -0,0 +1,45 @@
# enet
### initialize() <sub>function</sub>
Initialize the ENet library. Must be called before using any ENet functionality.
Throws an error if initialization fails.
**Returns**: None
### deinitialize() <sub>function</sub>
Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
need any ENet functionality.
**Returns**: None
### create_host(address) <sub>function</sub>
Create an ENet host for either a client-like unbound host or a server bound to a specific
address and port:
- If no argument is provided, creates an unbound "client-like" host with default settings
(maximum 32 peers, 2 channels, unlimited bandwidth).
- If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
Throws an error if host creation fails for any reason.
omit to create an unbound client-like host.
**address**: (optional) A string in 'ip:port' format to bind the host (server), or
**Returns**: An ENetHost object.

25
docs/api/modules/event.md Normal file
View File

@@ -0,0 +1,25 @@
# event
### push_event(event) <sub>function</sub>
Push a custom user event into SDL's queue, passing a callback function.
**event**: A function to call when this event is consumed.
**Returns**: None
### engine_input(callback) <sub>function</sub>
Poll all system events (keyboard, mouse, etc.) and call the given function with each event object.
**callback**: A function that executes on each event consumed from the poll.
**Returns**: None

View File

@@ -0,0 +1,221 @@
# geometry
A collection of geometry-related functions for circles, spheres, boxes, polygons,
and rectangle utilities. Some functionality is implemented in C and exposed here.
### rect_intersection(a, b) <sub>function</sub>
Return the intersection of two rectangles. The result may be empty if no intersection.
**a**: The first rectangle as {x, y, w, h}.
**b**: The second rectangle as {x, y, w, h}.
**Returns**: A rectangle that is the intersection of the two. May have zero width/height if no overlap.
### rect_intersects(a, b) <sub>function</sub>
**a**: Rectangle {x,y,w,h}.
**b**: Rectangle {x,y,w,h}.
**Returns**: A boolean indicating whether the two rectangles overlap.
### rect_expand(a, b) <sub>function</sub>
Merge or combine two rectangles, returning their bounding rectangle.
**a**: Rectangle {x,y,w,h}.
**b**: Rectangle {x,y,w,h}.
**Returns**: A new rectangle that covers the bounds of both input rectangles.
### rect_inside(inner, outer) <sub>function</sub>
**inner**: A rectangle to test.
**outer**: A rectangle that may contain 'inner'.
**Returns**: True if 'inner' is completely inside 'outer', otherwise false.
### rect_random(rect) <sub>function</sub>
**rect**: A rectangle {x,y,w,h}.
**Returns**: A random point within the rectangle (uniform distribution).
### cwh2rect(center, wh) <sub>function</sub>
Helper: convert a center point and width/height vector to a rect object.
**center**: A 2D point [cx, cy].
**wh**: A 2D size [width, height].
**Returns**: A rectangle {x, y, w, h} with x,y set to center and w,h set to the given size.
### rect_point_inside(rect, point) <sub>function</sub>
**rect**: A rectangle {x,y,w,h}.
**point**: A 2D point [px, py].
**Returns**: True if the point lies inside the rectangle, otherwise false.
### rect_pos(rect) <sub>function</sub>
**rect**: A rectangle {x,y,w,h}.
**Returns**: A 2D vector [x,y] giving the rectangle's position.
### rect_move(rect, offset) <sub>function</sub>
**rect**: A rectangle {x,y,w,h}.
**offset**: A 2D vector to add to the rectangle's position.
**Returns**: A new rectangle with updated x,y offset.
### box(w, h) <sub>function</sub>
Construct a box centered at the origin with the given width and height. This overrides the box object above.
**w**: The width of the box.
**h**: The height of the box.
**Returns**: An array of four 2D points representing the corners of a rectangle centered at [0,0].
### sphere <sub>object</sub>
Sphere-related geometry functions:
- volume(r): Return the volume of a sphere with radius r.
- random(r, theta, phi): Return a random point on or inside a sphere.
### circle <sub>object</sub>
Circle-related geometry functions:
- area(r): Return the area of a circle with radius r.
- random(r, theta): Return a random 2D point on a circle; uses sphere.random internally and extracts x,z.
### ngon(radius, n) <sub>function</sub>
Generates a regular n-gon by calling geometry.arc with full 360 degrees.
**radius**: The radius of the n-gon from center to each vertex.
**n**: Number of sides/vertices.
**Returns**: An array of 2D points forming a regular n-gon.
### arc(radius, angle, n, start) <sub>function</sub>
Generate an arc (or partial circle) of n points, each angle spread equally over 'angle' degrees from 'start'.
**radius**: The distance from center to the arc points.
**angle**: The total angle (in degrees) over which points are generated, capped at 360.
**n**: Number of segments (if <=1, empty array is returned).
**start**: Starting angle (in degrees), default 0.
**Returns**: An array of 2D points along the arc.
### corners2points(ll, ur) <sub>function</sub>
Similar to box.points, but calculates differently.
**ll**: Lower-left 2D coordinate.
**ur**: Upper-right 2D coordinate (relative offset in x,y).
**Returns**: A four-point array of corners [ll, lower-right, upper-right, upper-left].
### sortpointsccw(points) <sub>function</sub>
Sort an array of points in CCW order based on their angles from the centroid.
**points**: An array of 2D points.
**Returns**: A new array of the same points, sorted counterclockwise around their centroid.
### points2cm(points) <sub>function</sub>
**points**: An array of 2D points.
**Returns**: The centroid (average x,y) of the given points.

View File

@@ -0,0 +1,278 @@
# graphics
Provides functionality for loading and managing images, fonts, textures, and sprite meshes.
Includes both JavaScript and C-implemented routines for creating geometry buffers, performing
rectangle packing, etc.
### make_sprite_mesh(sprites) <sub>function</sub>
:param oldMesh (optional): An existing mesh object to reuse/resize if possible.
Given an array of sprites, build a single geometry mesh for rendering them.
**sprites**: An array of sprite objects, each containing .rect (or transform), .src (UV region), .color, etc.
**Returns**: A GPU mesh object with pos, uv, color, and indices buffers for all sprites.
### make_sprite_queue(sprites, camera, pipeline, sort) <sub>function</sub>
Given an array of sprites, optionally sort them, then build a queue of pipeline commands.
Each group with a shared image becomes one command.
**sprites**: An array of sprite objects.
**camera**: (unused in the C code example) Typically a camera or transform for sorting?
**pipeline**: A pipeline object for rendering.
**sort**: An integer or boolean for whether to sort sprites; if truthy, sorts by layer & texture.
**Returns**: An array of pipeline commands: geometry with mesh references, grouped by image.
### make_text_buffer(text, rect, angle, color, wrap, font) <sub>function</sub>
Generate a GPU buffer mesh of text quads for rendering with a font, etc.
**text**: The string to render.
**rect**: A rectangle specifying position and possibly wrapping.
**angle**: Rotation angle (unused or optional).
**color**: A color for the text (could be a vec4).
**wrap**: The width in pixels to wrap text, or 0 for no wrap.
**font**: A font object created by graphics.make_font or graphics.get_font.
**Returns**: A geometry buffer mesh (pos, uv, color, indices) for rendering text.
### rectpack(width, height, sizes) <sub>function</sub>
Perform a rectangle packing using the stbrp library. Return positions for each rect.
**width**: The width of the area to pack into.
**height**: The height of the area to pack into.
**sizes**: An array of [w,h] pairs for the rectangles to pack.
**Returns**: An array of [x,y] coordinates placing each rect, or null if they don't fit.
### make_rtree() <sub>function</sub>
Create a new R-Tree for geometry queries.
**Returns**: An R-Tree object for quickly querying many rectangles or sprite bounds.
### make_texture(data) <sub>function</sub>
Convert raw image bytes into an SDL_Surface object.
**data**: Raw image bytes (PNG, JPG, etc.) as an ArrayBuffer.
**Returns**: An SDL_Surface object representing the decoded image in RAM, for use with GPU or software rendering.
### make_gif(data) <sub>function</sub>
Load a GIF, returning its frames. If it's a single-frame GIF, the result may have .surface only.
**data**: An ArrayBuffer containing GIF data.
**Returns**: An object with frames[], each frame having its own .surface. Some also have a .texture for GPU use.
### make_aseprite(data) <sub>function</sub>
Load an Aseprite/ASE file from an array of bytes, returning frames or animations.
**data**: An ArrayBuffer containing Aseprite (ASE) file data.
**Returns**: An object containing frames or animations, each with .surface. May also have top-level .surface for a single-layer case.
### cull_sprites(sprites, camera) <sub>function</sub>
Filter an array of sprites to only those visible in the provided cameras view.
**sprites**: An array of sprite objects (each has rect or transform).
**camera**: A camera or bounding rectangle defining the view area.
**Returns**: A new array of sprites that are visible in the camera's view.
### rects_to_sprites(rects, image) <sub>function</sub>
Convert an array of rect coords into sprite objects referencing a single image.
**rects**: An array of rect coords or objects.
**image**: An image object (with .texture).
**Returns**: An array of sprite objects referencing the 'image' and each rect for UV or position.
### make_surface(dimensions) <sub>function</sub>
Create a blank surface in RAM.
**dimensions**: The size object {width, height}, or an array [w,h].
**Returns**: A blank RGBA surface with the given dimensions, typically for software rendering or icons.
### make_cursor(opts) <sub>function</sub>
**opts**: An object with {surface, hotx, hoty} or similar.
**Returns**: An SDL_Cursor object referencing the given surface for a custom mouse cursor.
### make_font(data, size) <sub>function</sub>
Load a font from TTF/OTF data at the given size.
**data**: TTF/OTF file data as an ArrayBuffer.
**size**: Pixel size for rendering glyphs.
**Returns**: A font object with surface, texture, and glyph data, for text rendering with make_text_buffer.
### make_sprite() <sub>function</sub>
Create a new sprite object, storing default properties.
**Returns**: A new sprite object, which typically has .rect, .color, .layer, .image, etc.
### make_line_prim(points, thickness, startCap, endCap, color) <sub>function</sub>
Build a GPU mesh representing a thick polyline from an array of points, using parsl or a similar library under the hood.
**points**: An array of [x,y] points forming the line.
**thickness**: The thickness (width) of the polyline.
**startCap**: (Unused) Possibly the type of cap for the start.
**endCap**: (Unused) Possibly the type of cap for the end.
**color**: A color to apply to the line.
**Returns**: A geometry mesh object suitable for rendering the line via a pipeline command.
### is_image(obj) <sub>function</sub>
**obj**: An object to check.
**Returns**: True if 'obj' has a .texture and a .rect property, indicating it's an image object.
### texture(path) <sub>function</sub>
Load or retrieve a cached image, converting it into a GPU texture. If 'path' is already an object, its returned directly.
**path**: A string path to an image file or an already-loaded image object.
**Returns**: An image object with {surface, texture, frames?, etc.} depending on the format.
### tex_hotreload(file) <sub>function</sub>
Reload the image for the given file, updating the cached copy in memory and GPU.
**file**: The file path that was changed on disk.
**Returns**: None
### get_font(path, size) <sub>function</sub>
Load a font from file if not cached, or retrieve from cache if already loaded.
**path**: A string path to a font file, optionally with ".size" appended.
**size**: Pixel size of the font, if not included in 'path'.
**Returns**: A font object with .surface and .texture for rendering text.
### queue_sprite_mesh(queue) <sub>function</sub>
Builds a single geometry mesh for all sprite-type commands in the queue, storing first_index/num_indices
so they can be rendered in one draw call.
**queue**: An array of draw commands, some of which are {type:'sprite'} objects.
**Returns**: An array of references to GPU buffers [pos,uv,color,indices].

1244
docs/api/modules/imgui.md Normal file

File diff suppressed because it is too large Load Diff

68
docs/api/modules/input.md Normal file
View File

@@ -0,0 +1,68 @@
# input
### mouse_show(show) <sub>function</sub>
Show or hide the mouse cursor. Pass true to show, false to hide.
**show**: Boolean. True to show, false to hide.
**Returns**: None
### mouse_lock(lock) <sub>function</sub>
Capture or release the mouse, confining it within the window if locked.
**lock**: Boolean. True to lock, false to unlock.
**Returns**: None
### cursor_set(cursor) <sub>function</sub>
Set the given cursor (created by os.make_cursor) as the active mouse cursor.
**cursor**: The cursor to set.
**Returns**: None
### keyname(keycode) <sub>function</sub>
Given a numeric keycode, return the corresponding key name (e.g., from SDL).
**keycode**: A numeric SDL keycode.
**Returns**: A string with the key name.
### keymod() <sub>function</sub>
Return an object describing the current modifier keys, e.g. {shift:true, ctrl:true}.
**Returns**: An object with boolean fields for each modifier key.
### mousestate() <sub>function</sub>
Return an object describing the current mouse state, including x,y coordinates
and booleans for pressed buttons (left, middle, right, x1, x2).
**Returns**: Object { x, y, left, middle, right, x1, x2 }

243
docs/api/modules/io.md Normal file
View File

@@ -0,0 +1,243 @@
# io
### rm(path) <sub>function</sub>
Remove the file or empty directory at the given path.
**path**: The file or empty directory to remove. Must be empty if a directory.
**Returns**: None
### mkdir(path) <sub>function</sub>
Create a directory at the given path.
**path**: The directory path to create.
**Returns**: None
### stat(path) <sub>function</sub>
Return an object describing file metadata for the given path. The object includes
filesize, modtime, createtime, and accesstime. Throw an error if the path does not exist.
**path**: The file or directory to retrieve metadata for.
**Returns**: An object with metadata (filesize, modtime, createtime, accesstime).
### globfs(patterns) <sub>function</sub>
Return an array of files that do not match any of the provided glob patterns. It
recursively enumerates the filesystem within PHYSFS. Each pattern is treated as an
"ignore" rule, similar to .gitignore usage.
**patterns**: An array of glob patterns to ignore. Any file matching one of these is skipped.
**Returns**: An array of matching file paths.
### match(pattern, string) <sub>function</sub>
Return boolean indicating whether the given wildcard pattern matches the provided
string. Dots must match dots. Case is not ignored.
Patterns can incorporate:
'?' - Matches exactly one character (except leading dots or slashes).
'*' - Matches zero or more characters (excluding path separators).
'**' - Matches zero or more characters, including path separators.
'[abc]' - A bracket expression; matches any single character from the set. Ranges like [a-z], [0-9] also work.
'[[:alpha:]]' - POSIX character classes can be used inside brackets.
'\' - Backslash escapes the next character.
'!' - If placed immediately inside brackets (like [!abc]), it negates the set.
**pattern**: The wildcard pattern to compare.
**string**: The string to test against the wildcard pattern.
**Returns**: True if matched, otherwise false.
### exists(path) <sub>function</sub>
Return a boolean indicating whether the file or directory at the given path exists.
**path**: The file or directory path to check.
**Returns**: True if the path exists, otherwise false.
### mount(archiveOrDir, mountPoint) <sub>function</sub>
Mount a directory or archive at the specified mount point. An undefined mount
point mounts to '/'. Throw on error.
**archiveOrDir**: The directory or archive to mount.
**mountPoint**: The path at which to mount. If omitted or undefined, '/' is used.
**Returns**: None
### unmount(path) <sub>function</sub>
Unmount a previously mounted directory or archive. Throw on error.
**path**: The directory or archive mount point to unmount.
**Returns**: None
### slurp(path) <sub>function</sub>
Read the entire file at the given path as a string. Throw on error.
**path**: The file path to read from.
**Returns**: A string with the files contents.
### slurpbytes(path) <sub>function</sub>
Read the entire file at the given path as a raw ArrayBuffer. Throw on error.
**path**: The file path to read from.
**Returns**: An ArrayBuffer containing the files raw bytes.
### slurpwrite(data, path) <sub>function</sub>
Write data (string or ArrayBuffer) to the given file path. Overwrite if it exists.
Throw on error.
**data**: The data to write (string or ArrayBuffer).
**path**: The file path to write to.
**Returns**: None
### writepath(path) <sub>function</sub>
Set the write directory. Subsequent writes will go here by default. Throw on error.
**path**: The directory path to set as writable.
**Returns**: None
### basedir() <sub>function</sub>
Return the application's base directory (where the executable is located).
**Returns**: A string with the base directory path.
### prefdir(org, app) <sub>function</sub>
Get the user-and-app-specific path where files can be written.
**org**: The name of your organization.
**app**: The name of your application.
**Returns**: A string with the user's directory path.
### realdir(path) <sub>function</sub>
Return the actual, real directory (on the host filesystem) that contains the given
file path. Return undefined if not found.
**path**: The file path whose real directory is requested.
**Returns**: A string with the real directory path, or undefined.
### open(path) <sub>function</sub>
Open a file for writing, returning a file object that can be used for further
operations. Throw on error.
**path**: The file path to open for writing.
**Returns**: A file object for subsequent write operations.
### searchpath() <sub>function</sub>
Return an array of all directories in the current paths.
**Returns**: An array of directory paths in the search path.
### enumerate(path, recurse) <sub>function</sub>
Return an array of files within the given directory, optionally recursing into
subdirectories.
**path**: The directory to list.
**recurse**: Whether to recursively include subdirectories (true or false).
**Returns**: An array of file (and directory) paths found.
### mount_core() <sub>function</sub>
### is_directory() <sub>function</sub>

175
docs/api/modules/js.md Normal file
View File

@@ -0,0 +1,175 @@
# js
Provides functions for introspecting and configuring the QuickJS runtime engine.
Includes debug info, memory usage, GC controls, code evaluation, etc.
### cycle_hook(callback) <sub>function</sub>
or undefined to remove the callback.
Register or remove a hook function that QuickJS calls once per execution cycle. If the callback
is set, it receives a single argument (an optional object/value describing the cycle). If callback
is undefined, the hook is removed.
**callback**: A function to call each time QuickJS completes a "cycle" (internal VM loop),
**Returns**: None
### dump_shapes() <sub>function</sub>
Use this for internal debugging of object shapes.
**Returns**: A debug string describing the internal shape hierarchy used by QuickJS.
### dump_atoms() <sub>function</sub>
known by QuickJS. Helpful for diagnosing memory usage or potential key collisions.
**Returns**: A debug string listing all currently registered atoms (internal property keys/symbols)
### dump_class() <sub>function</sub>
Shows how many objects of each class exist, useful for advanced memory or performance profiling.
**Returns**: A debug string describing the distribution of JS object classes in the QuickJS runtime.
### dump_objects() <sub>function</sub>
useful for debugging memory leaks or object lifetimes.
**Returns**: A debug string listing certain internal QuickJS objects and their references,
### dump_type_overheads() <sub>function</sub>
Displays memory usage breakdown for different internal object types.
**Returns**: A debug string describing the overheads for various JS object types in QuickJS.
### stack_info() <sub>function</sub>
Internal debugging utility to examine call stack details.
**Returns**: An object or string describing the runtime's current stack usage and capacity.
### calc_mem(value) <sub>function</sub>
Compute the approximate size of a single JS value in memory. This is a best-effort estimate.
**value**: A JavaScript value to analyze.
**Returns**: Approximate memory usage (in bytes) of that single value.
### mem() <sub>function</sub>
including total allocated bytes, object counts, and more.
Retrieve an overview of the runtimes memory usage.
**Returns**: An object containing a comprehensive snapshot of memory usage for the current QuickJS runtime,
### mem_limit(bytes) <sub>function</sub>
Set the upper memory limit for the QuickJS runtime. Exceeding this limit may cause operations to
fail or throw errors.
**bytes**: The maximum memory (in bytes) QuickJS is allowed to use.
**Returns**: None
### gc_threshold(bytes) <sub>function</sub>
Set the threshold (in bytes) for QuickJS to perform an automatic GC pass when memory usage surpasses it.
**bytes**: The threshold (in bytes) at which the engine triggers automatic garbage collection.
**Returns**: None
### max_stacksize(bytes) <sub>function</sub>
Set the maximum stack size for QuickJS. If exceeded, the runtime may throw a stack overflow error.
**bytes**: The maximum allowed stack size (in bytes) for QuickJS.
**Returns**: None
### memstate() <sub>function</sub>
Gives a quick overview of the memory usage, including malloc size and other allocations.
**Returns**: A simpler memory usage object (malloc sizes, etc.) for the QuickJS runtime.
### gc() <sub>function</sub>
Force an immediate, full garbage collection pass, reclaiming unreachable memory.
**Returns**: None
### eval(src, filename) <sub>function</sub>
Execute a string of JavaScript code in the current QuickJS context.
**src**: A string of JavaScript source code to evaluate.
**filename**: (Optional) A string for the filename or label, used in debugging or stack traces.
**Returns**: The result of evaluating the given source code.

15
docs/api/modules/json.md Normal file
View File

@@ -0,0 +1,15 @@
# json
### encode(val,space,replacer,whitelist) <sub>function</sub>
Produce a JSON text from a Javascript object. If a record value, at any level, contains a json() method, it will be called, and the value it returns (usually a simpler record) will be JSONified.
If the record does not have a json() method, and if whitelist is a record, then only the keys that are associated with true in the whitelist are included.
If the space input is true, then line breaks and extra whitespace will be included in the text.
### decode(text,reviver) <sub>function</sub>
The text text is parsed, and the resulting value (usually a record or an array) is returned.
The optional reviver input is a method that will be called for every key and value at every level of the result. Each value will be replaced by the result of the reviver function. This can be used to reform data-only records into method-bearing records, or to transform date strings into seconds.

3
docs/api/modules/loop.md Normal file
View File

@@ -0,0 +1,3 @@
# loop
### step() <sub>function</sub>

115
docs/api/modules/math.md Normal file
View File

@@ -0,0 +1,115 @@
# math
### dot() <sub>function</sub>
Compute the dot product between two numeric arrays, returning a scalar. Extra elements are ignored.
### project() <sub>function</sub>
Project one vector onto another, returning a new array of the same dimension.
### rotate() <sub>function</sub>
Rotate a 2D point (or array of length 2) by the given angle (in turns) around an optional pivot.
### midpoint() <sub>function</sub>
Compute the midpoint of two arrays of numbers. Only the first two entries are used if 2D is intended.
### reflect() <sub>function</sub>
Reflect a vector across a plane normal. Both arguments must be numeric arrays.
### distance() <sub>function</sub>
Compute the Euclidean distance between two numeric arrays of matching length.
### direction() <sub>function</sub>
Compute the normalized direction vector from the first array to the second.
### angle() <sub>function</sub>
Given a 2D vector, return its angle from the X-axis in radians or some chosen units.
### norm() <sub>function</sub>
Return a normalized copy of the given numeric array. For 2D/3D/4D or arbitrary length.
### angle_between() <sub>function</sub>
Compute the angle between two vectors (2D/3D/4D).
### lerp() <sub>function</sub>
Linear interpolation between two numbers: lerp(a, b, t).
### gcd() <sub>function</sub>
Compute the greatest common divisor of two integers.
### lcm() <sub>function</sub>
Compute the least common multiple of two integers.
### clamp() <sub>function</sub>
Clamp a number between low and high. clamp(value, low, high).
### angledist() <sub>function</sub>
Compute the signed distance between two angles in 'turn' units, e.g. 0..1 range.
### jitter() <sub>function</sub>
Apply a random +/- percentage noise to a number. Example: jitter(100, 0.05) -> ~95..105.
### mean() <sub>function</sub>
Compute the arithmetic mean of an array of numbers.
### sum() <sub>function</sub>
Sum all elements of an array of numbers.
### sigma() <sub>function</sub>
Compute standard deviation of an array of numbers.
### median() <sub>function</sub>
Compute the median of an array of numbers.
### length() <sub>function</sub>
Return the length of a vector (i.e. sqrt of sum of squares).
### from_to() <sub>function</sub>
Return an array of points from a start to an end, spaced out by a certain distance.
### rand() <sub>function</sub>
Return a random float in [0,1).
### randi() <sub>function</sub>
Return a random 32-bit integer.
### srand() <sub>function</sub>
Seed the random number generator with the given integer, or with current time if none.
### TAU <sub>number</sub>
### deg2rad(deg) <sub>function</sub>
### rad2deg(rad) <sub>function</sub>
### turn2rad(x) <sub>function</sub>
### rad2turn(x) <sub>function</sub>
### turn2deg(x) <sub>function</sub>
### deg2turn(x) <sub>function</sub>

27
docs/api/modules/miniz.md Normal file
View File

@@ -0,0 +1,27 @@
# miniz
### read(data) <sub>function</sub>
Create a zip reader from the given ArrayBuffer containing an entire ZIP archive.
Return undefined if the data is invalid.
**data**: An ArrayBuffer with the entire ZIP file.
**Returns**: A 'zip reader' object with methods for reading from the archive (mod, exists, slurp).
### write(path) <sub>function</sub>
Create a zip writer that writes to the specified file path. Overwrites the file if
it already exists. Return undefined on error.
**path**: The file path where the ZIP archive will be written.
**Returns**: A 'zip writer' object with methods for adding files to the archive (add_file).

30
docs/api/modules/nota.md Normal file
View File

@@ -0,0 +1,30 @@
# nota
### encode(value) <sub>function</sub>
Convert a JavaScript value into a NOTA-encoded ArrayBuffer.
This function serializes JavaScript values (such as numbers, strings, booleans, arrays, objects, or ArrayBuffers) into the NOTA binary format. The resulting ArrayBuffer can be stored or transmitted and later decoded back into a JavaScript value.
:throws: An error if no argument is provided.
**value**: The JavaScript value to encode (e.g., number, string, boolean, array, object, or ArrayBuffer).
**Returns**: An ArrayBuffer containing the NOTA-encoded data.
### decode(buffer) <sub>function</sub>
Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns undefined.
**buffer**: An ArrayBuffer containing NOTA-encoded data to decode.
**Returns**: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.

101
docs/api/modules/os.md Normal file
View File

@@ -0,0 +1,101 @@
# os
### make_transform() <sub>function</sub>
Create a new transform object that can be used for 2D/3D positioning, scaling, and rotation.
### clean_transforms() <sub>function</sub>
Force an update on all transforms to remove dangling references or perform house-keeping.
### platform() <sub>function</sub>
Return a string with the underlying platform name, like 'Windows', 'Linux', or 'macOS'.
### arch() <sub>function</sub>
Return the CPU architecture string for this system (e.g. 'x64', 'arm64').
### totalmem() <sub>function</sub>
Return the total system RAM in bytes.
### freemem() <sub>function</sub>
Return the amount of free system RAM in bytes, if known.
### hostname() <sub>function</sub>
Return the system's hostname, or an empty string if not available.
### version() <sub>function</sub>
Return the OS or kernel version string, if the platform provides it.
### kill() <sub>function</sub>
Send a signal (e.g., 'SIGINT', 'SIGTERM', etc.) to the current process.
### exit() <sub>function</sub>
Exit the application with the specified exit code.
### now() <sub>function</sub>
Return current time (in seconds as a float) with high resolution.
### openurl() <sub>function</sub>
Open the provided URL in the default web browser, if possible.
### make_timer() <sub>function</sub>
Create a new timer object that will call a specified function after a certain delay.
### update_timers() <sub>function</sub>
Advance all timers by the provided time delta (in seconds).
### sleep() <sub>function</sub>
Block execution for the specified number of seconds.
### battery_pct() <sub>function</sub>
Return the battery level (percentage) or negative if unknown.
### battery_voltage() <sub>function</sub>
Return the current battery voltage in volts, if available.
### battery_seconds() <sub>function</sub>
Return the estimated remaining battery time in seconds, or negative if unknown.
### power_state() <sub>function</sub>
Return a string describing power status: 'on battery', 'charging', 'charged', etc.
### on() <sub>function</sub>
Register a global callback for certain engine-wide or system-level events.
### rt_info() <sub>function</sub>
Return internal QuickJS runtime info, such as object counts.
### rusage() <sub>function</sub>
Return resource usage stats for this process, if the platform supports it.
### mallinfo() <sub>function</sub>
Return detailed memory allocation info (arena size, free blocks, etc.) on some platforms.
### env() <sub>function</sub>
Fetch the value of a given environment variable, or undefined if it doesn't exist.
### system() <sub>function</sub>
Execute a shell command using the system() call. Returns the command's exit code.

View File

@@ -0,0 +1,44 @@
# packer
### getAllFiles(dir) <sub>function</sub>
Return a list of all files in the given directory that are not matched by .prosperonignore,
skipping directories.
**dir**: The directory to search.
**Returns**: An array of file paths found.
### gatherStats(filePaths) <sub>function</sub>
Analyze a list of files and categorize them as modules, programs, images, or other.
**filePaths**: An array of file paths to analyze.
**Returns**: An object { modules, programs, images, other, total } with counts.
### pack(dir, outPath) <sub>function</sub>
Create a ZIP archive of all files (skipping those matched by .prosperonignore) in the
specified directory and write it to outPath. This uses the miniz module.
**dir**: The directory to zip.
**outPath**: The path (including filename) for the resulting ZIP file.
**Returns**: None (synchronous). Throws an Error if the directory does not exist.

View File

@@ -0,0 +1,96 @@
# render
### _main <sub>object</sub>
A handle for low-level GPU operations via SDL GPU. Freed on GC.
### device <sub>object</sub>
### stencil_writer(...args) <sub>function</sub>
### fillmask(ref) <sub>function</sub>
Draw a fullscreen shape using a 'screenfill' shader to populate the stencil buffer with a given reference.
**ref**: The stencil reference value to write.
**Returns**: None
### mask(image, pos, scale, rotation, ref) <sub>function</sub>
Draw an image to the stencil buffer, marking its area with a specified reference value.
**image**: A texture or string path (which is converted to a texture).
**pos**: The translation (x, y) for the image placement.
**scale**: Optional scaling applied to the texture.
**rotation**: Optional rotation in radians (unused by default).
**ref**: The stencil reference value to write.
**Returns**: None
### viewport(rect) <sub>function</sub>
Set the GPU viewport to the specified rectangle.
**rect**: A rectangle [x, y, width, height].
**Returns**: None
### scissor(rect) <sub>function</sub>
Set the GPU scissor region to the specified rectangle (alias of render.viewport).
**rect**: A rectangle [x, y, width, height].
**Returns**: None
### queue(cmd) <sub>function</sub>
Enqueue one or more draw commands. These commands are batched until render_camera is called.
**cmd**: Either a single command object or an array of command objects.
**Returns**: None
### setup_draw() <sub>function</sub>
Switch the current queue to the primary scene render queue, then invoke 'prosperon.draw' if defined.
**Returns**: None
### setup_hud() <sub>function</sub>
Switch the current queue to the HUD render queue, then invoke 'prosperon.hud' if defined.
**Returns**: None

View File

@@ -0,0 +1,68 @@
# resources
### scripts <sub>object</sub>
### images <sub>object</sub>
### sounds <sub>object</sub>
### fonts <sub>object</sub>
### lib <sub>object</sub>
### canonical(file) <sub>function</sub>
### find_image(...args) <sub>function</sub>
### find_sound(...args) <sub>function</sub>
### find_script(...args) <sub>function</sub>
### find_font(...args) <sub>function</sub>
### getAllFiles(dir) <sub>function</sub>
Return a list of recognized files in the given directory that are not matched by
.prosperonignore, skipping directories. Recognized extensions include scripts,
images, sounds, fonts, and libs.
**dir**: The directory to search.
**Returns**: An array of recognized file paths.
### gatherStats(filePaths) <sub>function</sub>
Analyze a list of recognized files and categorize them by scripts, images, sounds,
fonts, libs, or other. Return a stats object with these counts and the total.
**filePaths**: An array of file paths to analyze.
**Returns**: { scripts, images, sounds, fonts, lib, other, total }
### pack(dir, outPath) <sub>function</sub>
Create a ZIP archive of all recognized files (skipping those matched by .prosperonignore)
in the specified directory and write it to outPath. Recognized extensions are scripts,
images, sounds, fonts, or libs.
:raises Error: If the directory does not exist.
**dir**: The directory to zip.
**outPath**: The path (including filename) for the resulting ZIP file.
**Returns**: None

11
docs/api/modules/sound.md Normal file
View File

@@ -0,0 +1,11 @@
# sound
### undefined <sub>string</sub>
### pcm(file) <sub>function</sub>
### play(file) <sub>function</sub>
### cry(file) <sub>function</sub>
### music(file, fade = 0.5) <sub>function</sub>

View File

@@ -0,0 +1,9 @@
# spline
### catmull() <sub>function</sub>
Perform Catmull-Rom spline sampling on an array of 2D points, returning an array of samples.
### bezier() <sub>function</sub>
Perform a Bezier spline (or catmull) sampling on 2D points, returning an array of sampled points.

103
docs/api/modules/time.md Normal file
View File

@@ -0,0 +1,103 @@
# time
The main time object, handling date/time utilities in earth-seconds.
### now() <sub>function</sub>
Return the current system time in seconds (implemented in C extension).
### computer_dst() <sub>function</sub>
Return true if local system time is currently in DST (implemented in C extension).
### computer_zone() <sub>function</sub>
Return local time zone offset from UTC in hours (implemented in C extension).
### second <sub>number</sub>
Number of seconds in a (real) second (always 1).
### minute <sub>number</sub>
Number of seconds in a minute (60).
### hour <sub>number</sub>
Number of seconds in an hour (3600).
### day <sub>number</sub>
Number of seconds in a day (86400).
### week <sub>number</sub>
Number of seconds in a week (604800).
### weekdays <sub>object</sub>
Names of the days of the week, Sunday through Saturday.
### monthstr <sub>object</sub>
Full names of the months of the year, January through December.
### epoch <sub>number</sub>
Base epoch year, from which day 0 is calculated (default 1970).
### hour2minute() <sub>function</sub>
Return the ratio of hour to minute in seconds, e.g. 3600 / 60 => 60.
### day2hour() <sub>function</sub>
Return the ratio of day to hour in seconds, e.g. 86400 / 3600 => 24.
### minute2second() <sub>function</sub>
Return the ratio of minute to second in seconds, e.g. 60 / 1 => 60.
### week2day() <sub>function</sub>
Return the ratio of week to day in seconds, e.g. 604800 / 86400 => 7.
### strparse <sub>object</sub>
Mapping of format tokens (yyyy, mm, dd, etc.) to time fields (year, month, day...).
### isleap(year) <sub>function</sub>
Return true if a given year is leap, based on whether it has 366 days.
### yearsize(y) <sub>function</sub>
Given a year, return 365 or 366 depending on leap-year rules.
### timecode(t, fps = 24) <sub>function</sub>
Convert seconds into a "S:frames" timecode string, with optional FPS (default 24).
### monthdays <sub>object</sub>
An array of days in each month for a non-leap year.
### zones <sub>object</sub>
Table of recognized time zone abbreviations, with offsets (e.g., "-12" -> "IDLW").
### record(num, zone = this.computer_zone() <sub>function</sub>
Convert a timestamp (in seconds) into a record with fields like day, month, year, etc.
### number(rec) <sub>function</sub>
Convert a record back into a numeric timestamp (seconds).
### fmt <sub>string</sub>
Default format string for time.text(), containing tokens like 'yyyy', 'dd', 'hh', etc.
### text(num, fmt = this.fmt, zone) <sub>function</sub>
Format a numeric or record time into a string using a format pattern, e.g. 'hh:nn:ss'.

58
docs/api/modules/tween.md Normal file
View File

@@ -0,0 +1,58 @@
# tween
### Tween <sub>object</sub>
An object providing methods to create and control tweens with additional features
like looping, custom easing, multiple stages, etc.
Properties:
- default: A template object with loop/time/ease/whole/cb properties.
Methods:
- start(obj, target, tvals, options): Create a tween over multiple target values.
- make: Alias of start.
### Ease <sub>object</sub>
This object provides multiple easing functions that remap a 0..1 input to produce
a smoothed or non-linear output. They can be used standalone or inside tweens.
Available functions:
- linear(t)
- in(t), out(t), inout(t)
- quad.in, quad.out, quad.inout
- cubic.in, cubic.out, cubic.inout
- quart.in, quart.out, quart.inout
- quint.in, quint.out, quint.inout
- expo.in, expo.out, expo.inout
- bounce.in, bounce.out, bounce.inout
- sine.in, sine.out, sine.inout
- elastic.in, elastic.out, elastic.inout
All easing functions expect t in [0..1] and return a remapped value in [0..1].
### tween(from, to, time, fn, cb) <sub>function</sub>
Creates a simple tween that linearly interpolates from "from" to "to" over "time"
and calls "fn" with each interpolated value. Once finished, "fn" is called with "to",
then "cb" is invoked if provided, and the tween is cleaned up.
**from**: The starting object or value to interpolate from.
**to**: The ending object or value to interpolate to.
**time**: The total duration of the tween in milliseconds or some time unit.
**fn**: A callback function that receives the interpolated value at each update.
**cb**: (Optional) A callback invoked once the tween completes.
**Returns**: A function that, when called, cleans up and stops the tween.

192
docs/api/modules/util.md Normal file
View File

@@ -0,0 +1,192 @@
# util
A collection of general-purpose utility functions for object manipulation, merging,
deep copying, safe property access, etc.
### guid() <sub>function</sub>
Return a random 32-character hexadecimal UUID-like string (not guaranteed RFC4122-compliant).
**Returns**: A random 32-character string (hex).
### insertion_sort(arr, cmp) <sub>function</sub>
In-place insertion sort of an array using cmp(a,b)->Number for ordering.
**arr**: The array to be sorted in-place.
**cmp**: Comparison function cmp(a,b)->Number.
**Returns**: The same array, sorted in-place.
### deepfreeze(obj) <sub>function</sub>
Recursively freeze an object and all of its nested objects so they cannot be modified.
**obj**: The object to recursively freeze.
**Returns**: None
### dainty_assign(target, source) <sub>function</sub>
Copy non-function properties from source into matching keys of target without overwriting
keys that don't exist in target. Arrays are deep-copied, and objects are recursively assigned.
**target**: The target object whose keys may be updated.
**source**: The source object containing new values.
**Returns**: None
### get(obj, path, defValue) <sub>function</sub>
Safely retrieve a nested property from obj at path (array or dot-string).
Returns defValue if the property is undefined.
**obj**: The object to traverse.
**path**: A string like "a.b.c" or an array of path segments.
**defValue**: The default value if the property is undefined.
**Returns**: The nested property or defValue.
### isEmpty(o) <sub>function</sub>
Return true if the object has no own properties, otherwise false.
**o**: The object to check.
**Returns**: Boolean indicating if the object is empty.
### dig(obj, path, def) <sub>function</sub>
Ensure a nested path of objects exists inside obj; create objects if missing, and set
the final path component to def.
**obj**: The root object to modify.
**path**: A dot-string specifying nested objects to create.
**def**: The value to store in the final path component, default {}.
**Returns**: The assigned final value.
### access(obj, name) <sub>function</sub>
Traverse obj by dot-separated path name, returning the final value or undefined
if any step is missing.
**obj**: The object to traverse.
**name**: A dot-string path (e.g. "foo.bar.baz").
**Returns**: The value at that path, or undefined if missing.
### mergekey(o1, o2, k) <sub>function</sub>
Helper for merge, updating key k from o2 into o1. Arrays are deep-copied and objects are
recursively merged.
**o1**: The target object.
**o2**: The source object.
**k**: The key to merge.
**Returns**: None
### merge(target, objs) <sub>function</sub>
Merge all passed objects into target, copying or merging each key as needed.
Arrays are deep-copied, objects are recursively merged, etc.
**target**: The target object.
**objs**: One or more objects to merge into target.
**Returns**: The updated target object.
### copy(proto, objs) <sub>function</sub>
Create a new object with proto as its prototype, then mix in additional objects properties.
**proto**: The prototype object for the new object.
**objs**: One or more objects whose properties will be mixed in.
**Returns**: The newly created object.
### obj_lerp(a, b, t) <sub>function</sub>
Linearly interpolate between two objects a and b by factor t, assuming each property
supports .lerp().
**a**: The start object (its properties must have .lerp()).
**b**: The end object (matching properties).
**t**: Interpolation factor (0..1).
**Returns**: A new object with interpolated properties.
### normalizeSpacing(spacing) <sub>function</sub>
Normalize any spacing input into a {l, r, t, b} object.
**spacing**: A number, an array of length 2 or 4, or an object with l/r/t/b.
**Returns**: An object {l, r, t, b}.

View File

@@ -0,0 +1,5 @@
# video
### make_video() <sub>function</sub>
Decode a video file (MPEG, etc.) from an ArrayBuffer, returning a datastream object.

35
docs/api/prosperon.md Normal file
View File

@@ -0,0 +1,35 @@
# prosperon
### c_types <sub>object</sub>
### argv <sub>object</sub>
### version <sub>string</sub>
### revision <sub>string</sub>
### engine_start() <sub>function</sub>
### DOC <sub>symbol</sub>
### on(type, callback) <sub>function</sub>
### dispatch(type, data) <sub>function</sub>
### PATH <sub>object</sub>
### appupdate(...args) <sub>function</sub>
### update(...args) <sub>function</sub>
### physupdate(...args) <sub>function</sub>
### gui(...args) <sub>function</sub>
### hud(...args) <sub>function</sub>
### draw(...args) <sub>function</sub>
### imgui(...args) <sub>function</sub>
### app(...args) <sub>function</sub>

View File

@@ -0,0 +1,55 @@
# PHYSFS_File
A file handle opened via PhysFS for writing or reading. Freed automatically when references go away.
### close() <sub>function</sub>
Close this file handle. Throws on error.
**Returns**: None
### write(data) <sub>function</sub>
Write data (string or ArrayBuffer) to the file. Throws on error.
**data**: The data to write (string or ArrayBuffer).
**Returns**: None
### buffer(size) <sub>function</sub>
Enable an internal write buffer of the given size on this file.
**size**: Size in bytes of the buffer.
**Returns**: None
### tell() <sub>function</sub>
Return the current position in the file.
**Returns**: A numeric offset.
### eof() <sub>function</sub>
Return whether the file pointer is at end-of-file.
**Returns**: True if at EOF, false otherwise.

View File

@@ -0,0 +1,27 @@
# SDL_Camera
A handle to a physical camera device. Freed when references drop or camera is closed.
### frame() <sub>function</sub>
Acquire the latest camera frame (as an SDL_Surface). Returns undefined if no
new frame is available yet. Throws on error.
**Returns**: SDL_Surface or undefined.
### release_frame(surface) <sub>function</sub>
Release a frame surface previously acquired via camera.frame(). Must be
done for each acquired frame.
**surface**: The surface to release.
**Returns**: None

View File

@@ -0,0 +1,4 @@
# SDL_Cursor
An SDL cursor handle. Freed automatically on GC. No direct methods.

Some files were not shown because too many files have changed in this diff Show More