Forms Computed Language (FCL) is an interpreted language designed to be safe to execute when the code is arbitrary user input, while allowing users to manipulate variables, use flow control features and run functions.
FCL is based on PHP syntax and relies on @nikic/php-parser to produce an abstract syntax tree, while reimplementing an evaluator for a subset of PHP's tokens in PHP itself.
- Scalar variables (numeric, boolean and string types)
- Arrays and
foreachloops without references - Fetching constants from PHP
- Arithmetic and logical operators (
+, -, /, *, !, &&, ||) - Assignment operators (
+=, .=etc.) - Comparision operators (
<, <=, ==), string concatenation if/elseif/elseblocks- The ternary
if ? then : elseoperator - Unary plus and minus (e.g.
-1, +1are valid) - Function calls to FCL-provided functions (currently,
countSelectedItems,roundandisSelected) andFunctionStorefunctions
++,--and===operators (an easy PR :))switchandmatchblocks- User-defined functions (developers integrating FCL can use
FunctionStoreto provide custom functions) - OOP and namespaces
- References and unpacking
- Superglobals (
$_GETetc.) - Output to stdio, files etc. (you can not echo anything)
- Anonymous arrays in loops (e.g.
foreach([1, 2, 3] as $value){...})
You can install FCL using Composer by running a composer require command in your project:
composer require infobipcth/forms-computed-languageAs with any other package, you can also install it by adding it manually to composer.json or installing it manually by downloading a release.
Basic example of running FCL code:
$lr = LanguageRunner::getInstance();
$lr->setCode('$a = round($a);');
$lr->setVars(['a' => 3.14]);
$lr->evaluate();
// ['a' => 3]
var_dump($lr->getVars());IMPORTANT SECURITY NOTE: for booleans to work, and so that users can use constants such as PHP_ROUND_UP etc., you need to have some sort of access to constants (at least true and false constants). HOWEVER, if your project contains sensitive information in constants or PHP is exposing sensitive constants, this will prove to be a security risk!
To mitigate this, you can provide a list of allowed or disallowed constants to the Language Runner prior to code evaluation.
Disallowlist example:
$lr = LanguageRunner::getInstance();
$lr->setCode('$a = DB_USER;');
$lr->setVars([]);
$lr->setDisallowedConstants(['DB_USER', 'DB_HOST', 'DB_PASSWORD', 'DB_NAME']);
// IMPORTANT: IF YOU DO NOT SET CONSTANT BEHAVIOUR ALL CONSTANTS ARE ALLOWED!
$lr->setConstantBehaviour('blacklist');
// throws FormsComputedLanguage\Exceptions\UndeclaredVariableUsageException
$lr->evaluate();
var_dump($lr->getVars());Allowlist example - throws an error when a non-allowlisted constant is accessed:
$lr = LanguageRunner::getInstance();
$lr->setCode('$a = DB_USER;');
$lr->setVars([]);
$lr->setAllowedConstants(['true', 'false']);
// IMPORTANT: IF YOU DO NOT SET CONSTANT BEHAVIOUR ALL CONSTANTS ARE ALLOWED!
$lr->setConstantBehaviour('whitelist');
// throws FormsComputedLanguage\Exceptions\UndeclaredVariableUsageException
$lr->evaluate();
var_dump($lr->getVars());Misconfiguration example - DO NOT USE!:
$lr = LanguageRunner::getInstance();
$lr->setCode('$a = DB_USER;');
$lr->setVars([]);
// wrong wrong wrong
$lr->setDisallowedConstants(['true', 'false']);
// very wrong
$lr->setConstantBehaviour('blacklist');
// does not throw
$lr->evaluate();
// ['a' => 'root']
var_dump($lr->getVars());You can write FCL code similarly as you would write PHP. You can use all of the defined tokens, if for flow control and call our functions.
A notable difference is that FCL does not require an opening tag (no need to write <?php or similar).
Users can not define functions on the fly, but the calling program can define additional functions that are available to users. These are shared across all language runner instances, and aren't isolated in any way.
You can do this in your project at any time prior to evaluating an FCL program.
Rules:
- You can not redeclare functions
- You can not override standard library functions
- You always get an array of arguments
- You need to throw
FormsComputedLanguage\Exceptions\ArgumentCountExceptionif the count of args is wrong - You need to throw
FormsComputedLanguage\Exceptions\TypeExceptionif argument type is wrong
Example:
<?php
namespace MyCustomFunctions;
use FormsComputedLanguage\Functions\FunctionInterface;
use FormsComputedLanguage\Exceptions\ArgumentCountException;
use FormsComputedLanguage\Exceptions\TypeException;
$testFunction = new class implements FunctionInterface {
public const string FUNCTION_NAME = 'testFunction';
public static function getName(): string
{
return self::FUNCTION_NAME;
}
public static function getArguments(): array
{
return [
'$firstNum' => 'int|float',
'$secondNum' => 'int|float',
];
}
public static function run(array $args) {
if (count($args) !== 2) {
throw new ArgumentCountException("The function expects exactly two arguments!");
}
if (!is_numeric($args[0]) || !is_numeric($args[1])) {
throw new TypeException("The function arguments must be numeric!");
}
return $args[0] + $args[1];
}
};
FunctionStore::addFunction($testFunction::FUNCTION_NAME, $testFunction);
$this->languageRunner->setCode('$a = testFunction(1, 2);');
$this->languageRunner->evaluate();FCL includes command-line tools to help you debug and test your FCL programs:
Dumps the Abstract Syntax Tree (AST) of an FCL file for debugging purposes.
php fcldump.php <file.fcl> <variables.json>Example:
php fcldump.php scratches/min.fcl scratches/min.vars.jsonEvaluates an FCL file with provided variables and outputs the resulting variable state as JSON.
php fcleval.php <file.fcl> <variables.json>Example:
php fcleval.php scratches/a_program.fcl scratches/a_program.vars.jsonFCL uses Pest for testing with comprehensive test coverage.
# Run all tests (standards, linting, and unit tests)
composer test
# Run only unit tests
composer test:unit
# Run code standards check with PHPCS
composer test:standards
# Run syntax linting
composer test:lint
# Run tests with coverage report
composer test:coverageTests are organized in the tests/Unit/ directory and cover:
- ArrayTest.php - Array operations and manipulation
- CastingTest.php - Type casting functionality
- ControlFlowTest.php - If/elseif/else and ternary operators
- ControlStructuresTest.php - Complex control structures
- DisallowBehaviorTest.php - Constant allowlist/disallowlist security
- FunctionsTest.php - Built-in and custom functions
- LoopTest.php - Foreach loops and iteration
- OperatorsTest.php - Arithmetic, logical, and comparison operators
- ProgramTest.php - Complete program execution
- UnknownTokensTest.php - Error handling for unsupported syntax
All tests extend Pest's test case and automatically configure a LanguageRunner instance with safe defaults:
test('your test description', function () {
$this->languageRunner->setCode('$a = 2 + 2;');
$this->languageRunner->setVars([]);
$this->languageRunner->evaluate();
expect($this->languageRunner->getVars())->toBe(['a' => 4]);
});We welcome all contributions to Forms Computed Language!
Here's how you can help:
- Fork the repository
- Clone your fork:
git clone https://github.com/your-username/forms-computed-language.git - Install dependencies:
composer install - Create a feature branch:
git checkout -b feature/your-feature-name
- Write tests first - Add tests for new features or bug fixes in the appropriate test file
- Implement your changes - Follow the existing code style and patterns
- Run the test suite - Ensure all tests pass:
composer test - Check code standards - Fix any style issues:
composer test:standards - Commit your changes - Use clear, descriptive commit messages
- Push and create a PR - Submit a pull request with a description of your changes
- Follow PSR-12 coding standards, but with tabs (see phpcs.xml)
- Use meaningful variable and method names
- Add PHPDoc comments for public methods
- Keep methods focused and single-purpose
- Create a new Visitor in
src/Visitors/implementingVisitorInterface - Add the visitor to
Evaluator.phpin the appropriateenterNode()orleaveNode()section - Add comprehensive tests in
tests/Unit/
- Create a new function class in
src/Functions/implementingFunctionInterface - Register it in
FuncCallVisitor::FUNCTION_CALLBACKS - Add tests in
tests/Unit/FunctionsTest.php
- Ensure your PR has a clear title and description
- Reference any related issues
- Include tests for new functionality
- Make sure all CI checks pass
- Keep PRs focused on a single feature or fix
- Update documentation if needed
When reporting bugs, please include:
- FCL code that reproduces the issue
- Expected behavior
- Actual behavior
- PHP version and environment details
If you discover a security vulnerability which is unsuitable to be reported publicly, please email us at [email protected] or use the Infobip Coordinated Vulnerability Disclosure program.
This project is licensed under the MIT License - see the LICENSE file for details.
For detailed documentation, please see the docs directory:
- Integrating FCL into Your Project
- Understanding How FCL Evaluates a Program
- FCL Internals
- Developer Tools
- Debugging FCL
Note that the documentation is AI-assisted and might be inaccurate at times. If you have any questions, feel free to open an issue.
This project is maintained by Infobip's Creative Tech Hub - the team behind infobip.com.