Download the PHP package nathanjel/sel-lang without Composer

On this page you can find all versions of the php package nathanjel/sel-lang. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package sel-lang

SEL — Simple Expression Language

Write a business rule once. Run it everywhere, and get the same answer.

Most applications check the same thing twice: once in the browser so the user gets a quick "that postcode looks wrong", and once on the server because the browser cannot be trusted. Two checks, two languages, two authors, two interpretations of what "empty" means. They drift, and the bug surfaces for the one customer whose order sits exactly on a rounding boundary.

SEL is a tiny language for writing that rule once:

That file is the rule. It runs unchanged on Python, PHP, JavaScript, C++23 and Common Lisp, and all five are held to the same written specification by a test suite that runs every one of them and compares the results byte for byte — including where a rule failed, not just whether it did.

It is deliberately small. There is no floating point (so money stays exact), no truthiness (so an empty string is never accidentally "false"), no loops, and no way to define your own functions. Anything the host languages cannot be made to agree on is left out rather than guessed at.

Start here

If you want to… Read
write rules in SEL Language reference — the friendly tour, with runnable examples
call SEL from your app Quick start, just below
see it in a real application examples/ — a complete order-validation rule and the host code around it
add a function or an operator Extending SEL
know what the language guarantees Error codes
port SEL to another language Test harness

New here? Quick start is a CLI you can paste into a terminal, and the language in one screen is the whole thing at a glance.


Contents


Why

Validation written twice drifts. The backend and the frontend disagree about rounding, about what \d matches, about whether an empty string is falsy — and the bug only shows up for the one customer whose postcode has an unusual character in it.

The usual fixes do not really fix it. A shared JSON schema handles shapes but not "the total must not exceed the credit limit". A rules engine drags in a runtime you now have to deploy in every one of them. Generating code from a common source means maintaining a generator per target.

SEL is one rule, one artifact, executed by five interpreters held to a shared conformance suite and a differential fuzzer. Where the host languages cannot be made to agree, SEL refuses the feature rather than picking a winner.

It has no statements. A program is one expression, and what looks like control flow is a function call — functions receive the caller's syntax tree rather than values, and decide for themselves what to evaluate:

The division never happens. That single idea, borrowed from Aster, is what lets IF, COND and the aggregates be ordinary table entries instead of syntax.

Install

From a package manager — the package is sel-lang on all of them:

Or copy the directory for your host into your project, which needs no package manager at all and is still the primary story:

Python, PHP and JS need nothing at all — no pip, no Composer, no npm, no build step; copying python/sel/, php/src/ or js/src/ into a project works. C++ is a three-file drop-in — cpp/sel.hpp, cpp/sel_ast.hpp and cpp/sel.cpp — plus the vendored and pinned cpp/third_party/srell/ (BSD-2), with the SEL→SQL layer a strict addition of cpp/sel_sql*.{hpp,cpp} beside them; it also installs as a CMake package, so find_package(sel-lang) and sel-lang::sel-lang work. Common Lisp is an ordinary ASDF system whose one dependency is cl-ppcre (BSD-2).

Publishing details, and why SRELL is vendored rather than resolved, are in PACKAGING.md.

PHP needs no mbstring, no bcmath, no gmp; C++ never touches std::regex or <locale>; and Python never touches decimal — the UTF-8 codec and the decimal arithmetic are hand-written in all five precisely so the hosts cannot drift apart. In Python's case there is a second reason: tools/decimal-oracle.py generates the decimal test cases from the decimal module, and a host built on it would be marking its own homework.

Quick start

There is a CLI for poking at rules:

Calling it from your host

Five implementations, one answer. Each host below has a complete, runnable walkthrough in examples/plain/ — evaluating, compiling once and running per row, building a context, reading results back, errors, and dependencies().

All five print byte-identical output, and tools/check-examples.sh diffs them against each other. The claim that the hosts agree is therefore checked on the code you are being invited to copy, not asserted in prose beside it.

Host Run it
Python PYTHONPATH=python python3 examples/plain/python.py
PHP php examples/plain/php.php
JS node examples/plain/js.mjs
C++ cd cpp && make && ./build/example-plain
Common Lisp see the header of the file

Three things every host does the same way, and the examples show each:

The same fragment — compile once, then run it per row — in each host:

Python

PHP

JS

C++

Common Lisp

One rule, one answer: a worked example

Here is a small program that looks harmless and is worth understanding, because it is the kind of thing where languages usually stop agreeing with each other.

Read it left to right. A[1] = … says store something under key 1 of A. But the thing being stored is (A = 2), and that expression replaces A entirely with the plain number 2 before the store ever happens. So by the time SEL comes to store, the A the sentence started talking about no longer exists.

SEL's answer is that the assignment lands where it says it lands: at the path A[1], in whatever A is by then. You get the number 2 carrying a child 1 that is also 2.

The alternative — the one most languages fall into — is to grab hold of the old A when the sentence starts and write into that. The old A has since been thrown away, so the write goes into an object nothing can reach, and the whole assignment silently evaporates: you would get 2 with no child, and no indication that half your statement did nothing.

A write that nothing can ever read is a worse answer than a visible one. SEL would rather show you the result than quietly drop it. That is the same principle as refusing truthiness and refusing floating point: prefer the loud, inspectable outcome to the convenient one.

Nobody sensible writes A[1] = (A = 2) on purpose. It matters because rules grow in layers — an index computed by a helper, a value produced by another rule — and the day two of those layers touch the same variable, all five implementations still answer identically instead of four agreeing and one being subtly special.

Two smaller consequences of the same rule, which are much more likely to come up:

The first works because A is created before the index expression runs, so COUNT(A) sees an empty A and answers 0. The second reads the target's old value (1) for the arithmetic, but still stores at the path afterwards — so you get 1 + 5.

Every line above is executed by all five implementations on every commit; that is what the => marks mean throughout this document.

Integration patterns

Runnable versions of everything below are in examples/integration-php.php, examples/integration-js.mjs and examples/integration-python.py — all three print identical output.

Compile once, run per request

Parsing is cheap but not free, and a syntax error is a deployment problem rather than a user problem. Build the table at boot so a broken rule fails there:

Give each rule its own context

Rules should not see each other's intermediate variables. Rebuilding the context per rule is cheap and keeps them independent:

Separate "tell the user" from "the rule is broken"

E_ABORT is the rule author deliberately raising a message. Every other code means the rule itself is wrong, and the user should never see it:

Re-validate only what changed

dependencies() reports every input a rule reads, statically, without running it. Invert that into a watch map and an input listener knows the minimum set of rules to re-run:

Change EMAIL, re-run only the email rule. This works because SEL has no dynamic symbol operator — dropping that feature is exactly what buys it.

Shipping rules to the browser

Send the source text, not a compiled form. It is one artifact to version, it keeps both interpreters complete and symmetric, and it is what the conformance suite tests. Serve the same strings the backend compiled, and let the frontend compile them at load.

The language in one screen

Full detail in the language reference.

No loops, no user-defined functions, no lexical scoping, no dynamic symbols, no XML, no JSON, no compression, no floating point, and no truthiness. Iteration is done by aggregates that evaluate a body per element — the natural payoff of the calling convention.

What makes the hosts agree

Cross-host agreement is the whole product, and three things threaten it. Each is handled structurally rather than hopefully. The rule throughout: never use the host's own idea of anything the language defines.

Numbers. There is no floating point. Arithmetic is exact decimal, written by hand in all five, because no host has a usable exact type that carries scale — PHP has no bigint and BCMath is optional, JS has doubles, C++ has doubles, and a Lisp ratio cannot tell 2.50 from 2.5. Python's decimal would do the job, and is still not used: it is the oracle the other cores are checked against, so a host built on it would be marking its own homework. Scale is part of the value, so 2.50 + 2.50 is 5.00 and 0.10 + 0.20 > 0.30 is false everywhere.

Text. UTF-8 is encoded and decoded by hand, so every length and offset counts code points rather than PHP's bytes, JS's UTF-16 units or C++'s chars. Text comparison is specified as UTF-8 byte order, because JS's native comparison is UTF-16 order and Lisp's is code-point order, and both disagree with it above U+FFFF. UPPER/LOWER are ASCII-only on purpose — strtoupper, toUpperCase, std::toupper, string-upcase and Python's str.upper cannot be reconciled without shipping a case table, and the last of those can even change a string's length ("ß".upper() is "SS"); SEL would rather be visibly limited than quietly wrong. Even "digit" is defined here: SBCL's DIGIT-CHAR-P accepts U+0661 ARABIC-INDIC DIGIT ONE and Python's int() accepts both that and "1_2", so every implementation tests for 0–9 explicitly.

Identity. Evaluating an expression yields a value, not a snapshot of one, so a mutation made by a later sub-expression is visible through a reference taken earlier — A[A["k"] = "k"] finds the key its own index expression just created. Assignment is the only thing that copies. Every host aliases by default and deep-copies at exactly five places, which is a rule rather than an accident of each language's object model: the C++ Value was a deep-copying type until 0.3.0 and disagreed with the other four in six different ways, one of which returned a wrong number rather than an error. It is a handle now, with an explicit clone(), like the other four (§3.4).

Regex. Patterns are checked against a PCRE ∩ ECMAScript subset at compile time, and \d, \w, \s are rewritten into explicit ASCII classes rather than passed through — PHP's u modifier enables PCRE2's UCP and JS's does not, so otherwise \d matches Arabic-Indic digits on the backend only. \b is refused outright, because a word boundary depends on the engine's idea of a word character and no rewrite fixes that. The engine underneath differs by host and each one is bent to the same shape: JS uses RegExp with us, PHP preg with usD, C++ the vendored SRELL (an ECMAScript engine, so it agrees with JS by construction), and Lisp cl-ppcre and Python re with ^/$ lowered to \A and \z/\Z, because Perl and PCRE let $ match before a trailing newline and SEL does not. Case-insensitive matching needed a correction in both directions: cl-ppcre folds neither of the two non-ASCII code points that simple-fold to an ASCII letter, and Python's re folds those two and two more (U+0130 and U+0131 both fold to i there and nowhere else), so both hosts pre-fold the subject to land on the same set.

Null safety. Missing data is represented as a distinct NULL value. SEL guarantees that NULL never silently coerces into zero, empty string, or false: operations like +, -, &, UPPER, or == immediately fail loudly with E_NULL. Explicit null coalescing (??) and vacuous / data-invariant coalescing (???) handle missing values cleanly, while GET, PATH, IS_NULL, IS_NOT_NULL, IS_BLANK, and IS_PRESENT provide complete navigation and inspection parity across in-memory evaluation and SQL translation pushdown.

Layout

When implementations disagree, spec/ and conformance/ decide which is wrong — no implementation is the reference. A future Rust or Go port is finished when it passes the same suite; tools/impls.sh is where it registers itself, and tools/README.md describes the five entry points it has to provide.

Checking it

Eleven layers, each catching what the others miss:

Two more exist and are not in tools/check.sh, deliberately, because they take minutes rather than seconds: tools/stress.sh (deep structures, the shapes a fuzzer never emits) and cd cpp && make asan (the suite under the leak and undefined-behaviour checkers).

The fuzzer is the one that earns its keep. It caught the \d UCP divergence; it caught C++ evaluating TRUE $== FALSE's operands right-to-left, because the order of function arguments is unspecified there and SEL's is not; and it caught SBCL's DIGIT-CHAR-P accepting U+0661 ARABIC-INDIC DIGIT ONE, which made "١" a number in exactly one host. When it finds a disagreement, add the minimal case to conformance/ before fixing any host — the case is the durable part.

Contributing, and the full list of invariance traps to watch for, is in docs/EXTENDING.md.

Licence

MIT — use it for anything, including commercially, as long as the copyright notice travels with it.

Two third-party components keep their own (also permissive) licences: SRELL, which is vendored into the C++ implementation, and cl-ppcre, which the Common Lisp system depends on. Both are BSD 2-Clause, and both are listed in LICENSE. The Python, PHP and JS implementations have no dependencies at all, so shipping them is just the MIT notice.


All versions of sel-lang with dependencies

PHP Build Version
Package Version
Requires php Version >=8.1
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package nathanjel/sel-lang contains the following files

Loading the files please wait ...