Code Monkey home page Code Monkey logo

romans's Introduction

romans

A Simple PHP Roman Numerals Library

Build Status Updated Latest Stable Version codecov License StandWithUkraine

Usage

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 Romans\Filter\RomanToInt;

$filter = new RomanToInt();
$result = $filter->filter('MCMXCIX'); // 1999
use Romans\Filter\IntToRoman;

$filter = new IntToRoman();
$result = $filter->filter(1999); // MCMXCIX

Installation

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:

  • Romans 1.0.*: PHP ^7.0 (Augustus)
  • Romans 1.1.*: PHP ^7.0 (Tiberius)
  • Romans 1.2.*: PHP >=7.4 (Caligula)
  • Romans 1.3.*: PHP >=7.4 (Claudius)
  • Romans 1.4.*: PHP >=7.4 (Nero)
  • Romans 1.5.*: PHP >=8.0 (Galba)

Integrations

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.

Advanced Usage

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 Romans\Grammar\Grammar;
use Romans\Lexer\Lexer;
use Romans\Parser\Parser;

$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

Exception Handling

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 Romans\Filter\RomanToInt;
use Romans\Lexer\Exception as LexerException;
use Romans\Parser\Exception 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 Romans\Filter\IntToRoman;
use Romans\Filter\Exception as FilterException;

$filter = new IntToRoman();

try {
    $filter->filter($input);
} catch (FilterException $e) {
    // Invalid Integer (< 0) (FilterException::INVALID_INTEGER)
}

Zero

The zero value is represented as string "N", the initial of nulla or nihil, the Latin word for "nothing" (see references).

use Romans\Filter\RomanToInt;
use Romans\Filter\IntToRoman;

$filter = new RomanToInt();
$result = $filter->filter('N'); // 0 (Zero)

$filter = new IntToRoman();
$result = $filter->filter(0); // N

Cache

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 Romans\Filter\IntToRoman;
use Romans\Filter\RomanToInt;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

$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)

Development

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

Techniques

This section describes techniques used by this package to convert Roman numbers into int and vice-versa.

Lexer-Parser

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.

Deterministic Finite Automaton (DFA)

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

References

License

This package is opensource and available under MIT license described in LICENSE.

romans's People

Contributors

ianmustafa avatar moevbiz avatar pdenis avatar wandersonwhcr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

romans's Issues

Artifact

We must create an artifact with git archive on GitHub Actions.

Convert Int to Roman

Now we must convert Integer to Roman number. Create a filter class with a method that receives a int and converts it to string representing the roman number.

Update README

Describe into README.md how to use this library. Add examples of filters and, as advanced usage, lexer and parser.

Install PHPUnit

The development of this package must use unit tests.

Install PHPUnit with Composer require-dev schema atribute.

Quality of Code

Install some tools to maintain quality of source code, like PHPMD and PHPCS.

Problem with Two Tokens and Lookahead

If we have two tokens with lookahead, Parser doesn't recognize as a problem, because first value is equal to current value. Example:

use Romans\Grammar\Grammar;
use Romans\Parser\Parser;

(new Parser())->parse([Grammar::T_IX, Grammar::T_IX]); // INVALID

DFA Choice

I just found a regex (obviously) that verify if a roman number is valid. Must we move to it? I don't think so, but I think we can implement another automaton, using an regex and speed up parsing.

BTW, add some this explanation why we use an automaton: because we want to explain, exactly, which character is wrong inside roman number.

Large numbers

There may be cases where a large arabic number needs to be converted to a roman number. Should we implement that? I have read about Vinculum system as it seems to be the most widely used.

It should be viable to implement it for handling numbers between the [4000,3999999] range. I have done a quick (and ugly 😄) patch in order to filter the generated number (note that I have only implemented the arabic to roman conversion):

if (4000 <= $this->arabicNumeral && 3999999 >= $this->arabicNumeral) {
    return sprintf(
        '<span class="overline">%s</span>%s',
        str_replace('N', '', (new IntToRoman())->filter(intval(substr($this->arabicNumeral, 0, -3)))),
        str_replace('N', '', (new IntToRoman())->filter(intval(substr($this->arabicNumeral, -3))))
    );
} else {
    return (new IntToRoman())->filter($this->arabicNumeral);
}

Branch Alias

Include a Composer configuration to create a branch alias from dev-develop to 1.0.x-dev version.

Initialize README

Add some intro in README.md file with project name and description.

Exceptions with Codes

All throwed exceptions must have a code.

Currently, this package throw 3 types of exception: LexerException, ParserException and FilterException. Now, we must throw it with codes, according this table.

Type Constant Code Description
LexerException UNKNOWN_TOKEN 1 Unknown Token
ParserException UNKNOWN_TOKEN 1 Unknown Token
ParserException INVALID_TOKEN_TYPE 2 Invalid Token Type (Not String)
ParserException INVALID_ROMAN 4 Invalid Roman
FilterException INVALID_INTEGER 1 Invalid Integer

Exceptions codes must follow a x^2 convention, enabling multiple errors in the same exception with bitmask.

Include description of possible exceptions and codes in README.md.

Composer Keywords

Search in Composer for other libraries that convert integer to roman numbers and check for tags; register these parameters in composer.json.

Convert Roman to Int

Create a class that receive a string representing a roman number and output a int decimal as converting result. This class must use the Lexer and Parser created at #7 and #8.

Composer Require

This package require PHP ^7.0 and must be described in composer.json.

Describe Lexer-Parser Technique

In the section "Techniques", add a subsection called "Lexer-Parser", describing why this approach was applied. Add a reference, too.

Invalid Empty String

If we input an empty string, Parser must throw an ParserException with message "Invalid Roman Number". Currently, Parser returns zero.

Parser

According issue #6, create a parser that receives as input an array of grammar tokens and outputs an integer representing roman numerals. The tokens will be the output of lexer tokenize method, created at issue #7, but it must not have lexer as dependency.

Also, the parser will be responsible to validate tokens, checking decrescent order. If tokens are not in decrescent order, the parse method must throw an ParserException. Throw this exception if method receive an unknown token, too.

Below, there's a table with tokens and corresponding values.

Token Value
I 1
IV 4
V 5
IX 9
X 10
XL 40
L 50
XC 90
C 100
CD 400
D 500
CM 900
M 1000

Only PHP 7.4

The lastest stable PHP version is 7.4 with new improvements and features, like typed properties.

Migrate to this version and only this version.

Lexer

According issue #6, we must create an lexer that receives a string with roman numerals and convert it to an array of tokens. Valid tokens:

Token
I
IV
V
IX
X
XL
L
XC
C
CD
D
CM
M

Support PHP 5.6

To embrace more PHP versions, we must support PHP 5.6.

I think we can just remove PHP 7 type hinting and verify manually.

After that, we must reconfigure composer to require PHP ^5.6 version.

License

Include opensource license for project.

Vagrant Environment

A Vagrant development environment is needed to execute PHP Composer bin scripts, like PHPUnit.

Register Token into Exceptions

LexerException and ParserException must receive an $token attribute to store current token found on errors, like $position from issue #23.

Search for Dependents

I think we must create a GitHub Action to search for new dependents to say hello! ❤️

Validator

Create a class to validate a string with Roman Number contents.

PHP 7.3 and 7.4 Update

We must update our project to newest PHP versions and remove old versions support.

To add support, add PHP 7.3 and 7.4 into .travis.yml file.

Roman Numerals Parser

We need to create a parser that receives a string and convert it to an array of decimals.

It must to validate the string grammatically and not syntactically: it means this parser must throw an Exception only if characters are unknown. In other words, if characters are not mapped as tokens.

Valid tokens and mapped values:

Token Value
I 1
IV 4
V 5
IX 9
X 10
XL 40
L 50
XC 90
C 100
CD 400
D 500
CM 900
M 1000

Source: http://www.rapidtables.com/convert/number/how-number-to-roman-numerals.htm

Error when parse just one unknown Token

When we call Parser::parse with just one unknown token, we receive an undefined index position.

$parser = new Parser();
$parser->parse(['.']); // Undefined index: .

Prepare for Composer

How to remove some files when Composer builds the package? There's some files, like Vagrant Puppet provisioning and tests that third parties will not use.

Composer

Install Composer for this project with composer.json.

Check for Updates

We must create a GitHub Action that updates our packages via Composer and runs tests periodically.

If it finds updates, it must trigger warnings. If it finds errors from tests, it must trigger errors.

Remove Makefile

When executing a script, Composer concatenate vendor/bin in the begin of $PATH enviroment variable. So, we can use scripts from Makefile into Composer.

Transform all recipes from Makefile into Composer scripts.

Change Travis CI to use this new structure.

Travis CI

Register this repository into Travis CI, after close issue #14.

Only PHP 8.0

We must upgrade our source code to use PHP 8.0

Composer License

There is not entry for license in composer.json. Set license with value "MIT".

Disable Packages

After we configured GitHub Actions with a matrix approach to test multiple versions of PHP, GitHub is creating two Docker Packages as output from execution.

We must remove this feature.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.