Skip to content

shakfu/pyjoy-lang

Repository files navigation

pyjoy-lang

A Python implementation of Manfred von Thun's Joy programming language with dual-mode architecture.

PyPI package: pyjoy-lang | Command: pyjoy

The primary aim of this project is to implement the Joy language in Python 3. This means the implementation should run Joy programs without issue. A secondary aim is to have the Python implementation generate C code which can then be compiled into machine code. This is consistent with the late Manfred von Thun's wish:

Several other people have published other more or less complete Joy interpreters, written in ML and in Scheme, in the "concatenative" mailing group. At this point in time I have no plans to write a full compiler. A first version of such a compiler would presumably use C as an intermediate language and leave the generation of machine code to the C compiler. I would very much welcome if somebody were to take up the task." A Conversation with Manfred von Thun

The implementation provides a dual-mode architecture: strict mode (strict=True) for Joy compliance, and pythonic mode (strict=False) for Python interoperability.

Installation

From PyPI

pip install pyjoy-lang

From Source

Requires Python 3.13+ and uv.

# Clone the repository
git clone https://github.com/shakfu/pyjoy-lang.git
cd pyjoy-lang

# Install dependencies
uv sync

# Run Python tests
uv run pytest

# Run Joy test suite
uv run pyjoy test tests/joy

# Run Joy tests with C compilation
uv run pyjoy test tests/joy --compile

For C compilation, you'll also need gcc or clang.

Usage

Interactive REPL

# Start the Joy REPL
uv run pyjoy

Example session:

Joy> 2 3 + .
5
Joy> [1 2 3] [dup *] map .
[1 4 9]
Joy> DEFINE square == dup *.
Joy> 5 square .
25
Joy> quit

Execute Joy files

# Run a Joy source file
uv run pyjoy examples/factorial.joy

# Or using the run subcommand
uv run pyjoy run examples/factorial.joy

# Evaluate an expression
uv run pyjoy -e "5 [1] [*] primrec ."
120

Compile to C

# Compile Joy source to executable
uv run pyjoy compile program.joy -o build -n myprogram

# Run the compiled program
./build/myprogram

# Or compile and run in one step
uv run pyjoy compile program.joy --run

# Generate C code only (no compilation)
uv run pyjoy compile program.joy --no-compile

Run Test Suite

# Run all Joy tests
uv run pyjoy test tests/joy

# Run with verbose output
uv run pyjoy test tests/joy -v

# Run specific pattern
uv run pyjoy test tests/joy --pattern "fact*.joy"

# Also test C compilation
uv run pyjoy test tests/joy --compile

Status

Test Results

Backend Passing Total Coverage
Python Interpreter 194 215 90.2%
C Backend 199 215 92.6%
pytest (unit tests) 712 712 100%

Primitives

  • 200+ primitives implemented in both Python and C backends
  • Full support for Joy's core operations, combinators, and I/O
  • Mode-aware primitives work with both strict (JoyValue) and pythonic (raw Python) values
  • Some interpreter-specific primitives (get, include) have limited C support

See TODO.md for detailed status and remaining work.

Features

Core Language

  • Stack operations: dup, pop, swap, rollup, rolldown, rotate, etc.
  • Arithmetic: +, -, *, /, rem, div, abs, neg, sign, etc.
  • Comparison: <, >, <=, >=, =, !=, equal, compare
  • Logic: and, or, not, xor
  • Aggregates: lists [...], sets {...}, strings "..."
  • Quotations and combinators

Combinators

  • Basic: i, x, dip, dipd, dipdd
  • Conditionals: ifte, cond, branch, iflist, ifinteger, etc.
  • Recursion: linrec, binrec, genrec, primrec, tailrec
  • Tree recursion: treerec, treegenrec, treestep
  • Conditional recursion: condlinrec, condnestrec
  • Application: app1, app2, app3, app4, map, filter, fold, step
  • Arity: nullary, unary, binary, ternary, unary2, unary3, unary4
  • Control: cleave, construct, some, all, split

I/O and System

  • Console: put, putch, putchars, . (print with newline)
  • File I/O: fopen, fclose, fread, fwrite, fgets, fput, etc.
  • System: system, getenv, argc, argv
  • Time: time, localtime, gmtime, mktime, strftime

Python Interop (Pythonic Mode)

When running with strict=False, Joy gains Python interoperability:

from pyjoy import Evaluator

ev = Evaluator(strict=False)  # Enable pythonic mode

# Backtick expressions - evaluate Python and push result
ev.run("`2 + 3`")           # Pushes 5
ev.run("`math.sqrt(16)`")   # Pushes 4.0 (math is pre-imported)

# Dollar expressions - same as backticks, better for nested parens
ev.run("$(len([1,2,3]))")   # Pushes 3

# Bang statements - execute Python without pushing
ev.run("!x = 42")           # Sets x in Python namespace
ev.run("`x * 2`")           # Pushes 84

# Access stack from Python
ev.run("1 2 3")
ev.run("`sum(stack)`")      # Pushes 6

# Define Python functions
ev.run("!def square(n): return n * n")
ev.run("`square(7)`")       # Pushes 49

Pre-imported modules: math, json, os, sys, re, itertools, functools, collections

Available in namespace: stack (alias S), ctx, evaluator

See docs/pythonic-mode.md for the complete guide.

Extending Joy from Python

Define new Joy words using Python decorators:

from pyjoy.evaluator import joy_word, python_word, ExecutionContext
from pyjoy.types import JoyValue

# Simple functions: use @python_word (auto-pops args, pushes result)
@python_word(name="hypot", doc="X Y -> Z")
def hypot(x, y):
    return (x**2 + y**2) ** 0.5

# Full stack control: use @joy_word
@joy_word(name="rot3", params=3, doc="X Y Z -> Y Z X")
def rot3(ctx: ExecutionContext):
    c, b, a = ctx.stack.pop_n(3)
    ctx.stack.push_value(b)
    ctx.stack.push_value(c)
    ctx.stack.push_value(a)

# Wrap existing Python functions
import math

@python_word(name="deg2rad")
def deg2rad(degrees):
    return math.radians(degrees)

@python_word(name="factorial")
def factorial(n):
    return math.factorial(int(n))

When to use each decorator:

Decorator Use When
@python_word Pure functions, fixed arity, single return value
@joy_word Need stack access, multiple results, control flow, or ExecutionContext

Example: HTTP fetch word

@joy_word(name="http-get", params=1, doc="URL -> RESPONSE")
def http_get(ctx: ExecutionContext):
    import requests
    url = ctx.stack.pop()
    response = requests.get(url.value if hasattr(url, 'value') else url)
    ctx.stack.push(response.text)

C Backend Features

  • Compiles Joy to standalone C executables
  • Compile-time include preprocessing
  • Recursive include with circular dependency detection
  • Full runtime with garbage collection

Project Structure

pyjoy-lang/
  src/pyjoy/
    __init__.py         # Public API
    __main__.py         # CLI entry point
    types.py            # Joy type system
    stack.py            # Stack implementation
    scanner.py          # Lexical analysis
    parser.py           # Parser
    evaluator/          # Execution engine
    backends/c/         # C code generator
    stdlib/             # Joy standard library
  tests/
    joy/                # Joy language tests
    test_*.py           # pytest unit tests
  docs/
    pyjoy.md            # Implementation spec
    pythonic-mode.md    # Python integration guide
    comparison-with-joy.md  # Word comparison tables
    tutorial.md         # Getting started

Documentation

License

MIT License - see LICENSE file for details.

References

About

A Python implementation of the Joy Language

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors

Languages