Skip to content

Language Ports

Cookbook: This Page · Editors · Markdown · Rich-Text · Accessibility · Existing URLs · Images & Media

Native ports of the Alap expression parser for Python, PHP, Go, Rust, Ruby, and Java. These enable server-side expression resolution without a Node.js sidecar.

Live version: https://docs.alap.info/cookbook/language-ports

Source: src/other-languages/

What's included

ModulePythonPHPGoRustRubyJava
Expression parserexpression_parser.pyExpressionParser.phpalap.goalap-core crateexpression_parser.rbExpressionParser.java
Regex validatorvalidate_regex.pyBuilt-inBuilt-inBuilt-invalidate_regex.rbValidateRegex.java
URL sanitizersanitize_url.pyBuilt-inBuilt-inBuilt-insanitize_url.rbSanitizeUrl.java
Config validatorBuilt-inBuilt-inBuilt-invalidate_config.rsvalidate_config.rbValidateConfig.java
SSRF guardssrf_guard.pyBuilt-inBuilt-inssrf_guard.rsssrf_guard.rbSsrfGuard.java
Config mergerBuilt-inBuilt-inBuilt-inBuilt-inBuilt-inBuilt-in

All ports support the full expression grammar including:

  • Item IDs, tags (.coffee), macros (@favorites)
  • Operators (+, |, -) with left-to-right evaluation
  • Parenthesized grouping (up to 32 levels)
  • Regex search (/pattern/fields)
  • Protocol expressions (:time:30d:, :location:radius:args:)
  • Refiners (*sort:label*, *limit:5*)

All ports share the \w identifier constraint — item IDs, macro names, and tag names cannot contain hyphens (the - character is the WITHOUT operator).

Security parity

All ports include the same security layers as the TypeScript implementation:

FeaturePythonPHPGoRustJava
URL sanitizationyesyesyesyesyes
Prototype-pollution defenseyes (+ dunders)yesyesyesyes
Resource limits (depth/tokens)yesyesyesyesyes
ReDoS detectionsyntacticsyntactic + pcre.backtrack_limitN/A (RE2)N/A (safe engine)syntactic
validateConfigyesyesyesyesyes
SSRF guardyesyesyesyesyes

Language-specific defenses:

  • Python: Blocks dunder keys (__class__, __bases__, __mro__, __subclasses__) in validate_config — keeps configs passed downstream to Jinja2 or logging formatters from carrying handles into Python internals.
  • PHP: Rejects non-array input to validateConfig() (enforces json_decode($json, true)). Wraps regex execution with pcre.backtrack_limit as a circuit breaker.
  • Go: SSRF guard handles IPv4-mapped IPv6 addresses (::ffff:127.0.0.1) via net.IP.To4().
  • Rust: SSRF guard blocks hex/octal/integer IP obfuscation (0x7f.0.0.1, 0177.0.0.1, 2130706433).
  • Java: SSRF guard uses InetAddress for resolution with loopback/link-local/site-local checks.

See Security for the full cross-language matrix.

What's NOT included

These are server-side ports. Browser-side concerns stay in the TypeScript client:

  • DOM rendering, compass-based menu placement (flip/clamp/place strategies), event handling
  • Viewport containment and placement fallback logic
  • CSS injection, alapelem container management

Python

bash
pip install alap
# or: uv add alap
python
from expression_parser import ExpressionParser, resolve_expression, cherry_pick_links, merge_configs

config = load_config_from_db()
parser = ExpressionParser(config)
ids = parser.query('.coffee + .nyc')
links = resolve_expression(config, '.coffee')

Tests: 50+ tests covering operands, operators, macros, parentheses, regex, protocols, refiners.

PHP

bash
composer require danielsmith/alap
php
use Alap\ExpressionParser;

$config = json_decode(file_get_contents('config.json'), true);
$parser = new ExpressionParser($config);
$ids = $parser->query('.coffee + .nyc');

Tests: PHPUnit suite with 50+ tests.

Go

go
import "github.com/DanielSmith/alap-go"

config := loadConfig()
parser := alap.NewExpressionParser(config)
ids := parser.Query(".coffee + .nyc")

Tests: 50+ tests via go test.

Rust

toml
[dependencies]
alap = "0.1"
rust
use alap::ExpressionParser;

let config = load_config();
let parser = ExpressionParser::new(&config);
let ids = parser.query(".coffee + .nyc");

Tests: Full test suite via cargo test. The Rust port uses edition 2024.

Java

java
import alap.ExpressionParser;
import alap.Config;

Config config = loadConfig();
ExpressionParser parser = new ExpressionParser(config);
List<String> ids = parser.query(".coffee + .nyc");

Tests: 90+ tests via JUnit.

Used by the server examples

The language ports power expression resolution in the non-Node servers:

ServerParser
Flask, Django, FastAPIPython port
LaravelPHP port
GinGo port
AxumRust port
Spring BootJava port
Express, Hono, BunTypeScript alap/core

See Servers for the full server matrix.