A Simple PHP Roman Numerals Library
This library includes a simple couple of filters to convert a string
with
Roman number to an int
that represents the input as decimal and decimal int
to a string
with Roman number as result.
use RomansFilterRomanToInt;
$filter = new RomanToInt();
$result = $filter->filter('MCMXCIX'); // 1999
use RomansFilterIntToRoman;
$filter = new IntToRoman();
$result = $filter->filter(1999); // MCMXCIX
This package uses
Composer as default
repository. You can install it adding the name of package in require
section
of composer.json
, pointing to the last stable version.
{
"require": {
"wandersonwhcr/romans": "^1.0"
}
}
Also, Romans uses Semantic Versioning. The following package versions support these PHP releases:
1.0.*
: PHP ^7.0
(Augustus)1.1.*
: PHP ^7.0
(Tiberius)1.2.*
: PHP >=7.4
(Caligula)1.3.*
: PHP >=7.4
(Claudius)1.4.*
: PHP >=7.4
(Nero)1.5.*
: PHP >=8.0
(Galba)This library can be used as dependency for projects, making integrations with libraries or frameworks easier. If you want to add more items in this list, please, open an issue or create a pull request, adding your project alphabetically.
The Romans
package uses a Lexer-Parser approach and a Deterministic Finite
Automaton (DFA) to convert Roman number to int
, using a Grammar Token library.
use RomansGrammarGrammar;
use RomansLexerLexer;
use RomansParserParser;
$grammar = new Grammar();
$lexer = new Lexer($grammar);
$parser = new Parser($grammar);
$tokens = $lexer->tokenize('MCMXCIX');
/*
$tokens = [
0 => 'M' // Grammar::T_M
1 => 'C', // Grammar::T_C
2 => 'M', // Grammar::T_M
3 => 'X', // Grammar::T_X
4 => 'C', // Grammar::T_C
5 => 'I', // Grammar::T_I
6 => 'X', // Grammar::T_X
];
*/
$result = $parser->parse($tokens); // 1999
The filter RomanToInt
uses Lexer to tokenize the input and Parser to build the
int
number. When errors are found, the Lexer or Parser throw Exceptions to
notify the problem with a specific code.
use RomansFilterRomanToInt;
use RomansLexerException as LexerException;
use RomansParserException as ParserException;
$filter = new RomanToInt();
try {
$filter->filter($input);
} catch (LexerException $e) {
// Unknown Token (LexerException::UNKNOWN_TOKEN)
} catch (ParserException $e) {
// Invalid Token Type (Not String) (ParserException::INVALID_TOKEN_TYPE)
// Unknown Token (ParserException::UNKNOWN_TOKEN)
// Invalid Roman (ParserException::INVALID_ROMAN)
}
You can use this structure to validate Roman numbers, adding a try..catch
block to check if $input
is valid. Also, you can validate if an int
can be
filtered to Roman using an IntToRoman
filter.
use RomansFilterIntToRoman;
use RomansFilterException as FilterException;
$filter = new IntToRoman();
try {
$filter->filter($input);
} catch (FilterException $e) {
// Invalid Integer (< 0) (FilterException::INVALID_INTEGER)
}
The zero value is represented as string
"N"
, the initial of nulla or
nihil, the Latin word for "nothing" (see references).
use RomansFilterRomanToInt;
use RomansFilterIntToRoman;
$filter = new RomanToInt();
$result = $filter->filter('N'); // 0 (Zero)
$filter = new IntToRoman();
$result = $filter->filter(0); // N
This package uses PSR-6 Caching Interface
to improve execution, mainly over loops (like while
or foreach
) using cache
libraries. Any PSR-6 implementation can be used and we suggest
Symfony Cache package.
use RomansFilterIntToRoman;
use RomansFilterRomanToInt;
use SymfonyComponentCacheAdapterArrayAdapter;
$cache = new ArrayAdapter();
$filter = new RomanToInt();
$filter->setCache($cache);
$result = $filter->filter('MCMXCIX'); // 1999
$result = $filter->filter('MCMXCIX'); // 1999 (from cache)
$filter = new IntToRoman();
$filter->setCache($cache);
$result = $filter->filter(1999); // MCMXCIX
$result = $filter->filter(1999); // MCMXCIX (from cache)
You can use Docker Compose to build an image and run a container to develop and test this package.
docker-compose build
docker-compose run --rm romans composer install
docker-compose run --rm romans composer test
This section describes techniques used by this package to convert Roman numbers
into int
and vice-versa.
A Lexer-Parser approach was chosen because the validating and reading of Roman
numbers are more simplified: the Lexer is responsible for reading and transform
the input into tokens, validating content at char level; and the Parser is
responsible for transform tokens into numbers, validating content at position
level and converting to int
through a DFA.
Wikipedia says that "lexical analysis is the process of converting a sequence of characters into a sequence of tokens", that "is a structure representing a lexeme that explicity indicates its categorization for the purpose of parsing". Even, Wikipedia says that "parsing or syntax analysis is the process of analysing symbols conforming to the rules of a formal grammar".
This structure makes easier the development of a structure responsible to read
an input string
, converting it to another structure according to specific
charset and its position inside input.
A DFA was developed to check if a string
with Roman number is valid. This
technique was choiced because other implementations simply convert the input
without checking rules, like four chars sequentially.
The current automaton definition is declared below.
M = (Q, Σ, δ, q0, F)
Q = { a, b, c, d, e, f, g, y, z }
Σ = { I, V, X, L, C, D, M, N }
q0 = g
F = { z }
z -> ε
y -> $z
a -> y | Iy | IIy | IIIy
b -> a | IVy | Va | IXy
c -> b | Xb | XXb | XXXb
d -> c | XLb | Lc | XCb
e -> d | Cd | CCd | CCCd
f -> e | CDd | De | CMd
g -> f | Ny | Mg
This package is opensource and available under MIT license described in LICENSE.