From 407bd389f9ef41cd24a35198cb45ef03cae2353b Mon Sep 17 00:00:00 2001 From: Wanderson Date: Thu, 13 Apr 2017 17:39:16 -0300 Subject: [PATCH 01/49] Include test with problem using lookahead tokens --- test/Parser/ParserTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/Parser/ParserTest.php b/test/Parser/ParserTest.php index 38a7afa..823f67a 100644 --- a/test/Parser/ParserTest.php +++ b/test/Parser/ParserTest.php @@ -196,4 +196,16 @@ public function testParseWithOneInvalidToken() $this->parser->parse([0]); } + + /** + * Test Parse with Two Tokens with Lookahead + */ + public function testParseWithTwoTokensWithLookahead() + { + $this->expectException(ParserException::class); + $this->expectExceptionMessage('Invalid Roman'); + $this->expectExceptionCode(ParserException::INVALID_ROMAN); + + $this->parser->parse([Grammar::T_IX, Grammar::T_IX]); + } } From 6ac6ea6aee646198a647dccddbf9b8614ac4c752 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Thu, 13 Apr 2017 17:44:08 -0300 Subject: [PATCH 02/49] Add another invalid test to fix Parser --- test/Parser/ParserTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/Parser/ParserTest.php b/test/Parser/ParserTest.php index 823f67a..68a3699 100644 --- a/test/Parser/ParserTest.php +++ b/test/Parser/ParserTest.php @@ -208,4 +208,16 @@ public function testParseWithTwoTokensWithLookahead() $this->parser->parse([Grammar::T_IX, Grammar::T_IX]); } + + /** + * Test Parse with One Lookahead Token and One Simple Token + */ + public function testParseWithOneLookaheadTokenAndOneSimpleToken() + { + $this->expectException(ParserException::class); + $this->expectExceptionMessage('Invalid Roman'); + $this->expectExceptionCode(ParserException::INVALID_ROMAN); + + $this->parser->parse([Grammar::T_IX, Grammar::T_V]); + } } From 64c693d1a93816c509927b1d51b7761ddc73c495 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Thu, 13 Apr 2017 17:52:00 -0300 Subject: [PATCH 03/49] Include more tests to avoid future breaks --- test/Parser/ParserTest.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/Parser/ParserTest.php b/test/Parser/ParserTest.php index 68a3699..d4a4cc1 100644 --- a/test/Parser/ParserTest.php +++ b/test/Parser/ParserTest.php @@ -220,4 +220,32 @@ public function testParseWithOneLookaheadTokenAndOneSimpleToken() $this->parser->parse([Grammar::T_IX, Grammar::T_V]); } + + /** + * Test Parse with Four Simple Tokens in Sequence + */ + public function testParseWithFourSimpleTokensInSequence() + { + $this->expectException(ParserException::class); + $this->expectExceptionMessage('Invalid Roman'); + $this->expectExceptionCode(ParserException::INVALID_ROMAN); + + $this->parser->parse([Grammar::T_I, Grammar::T_I, Grammar::T_I, Grammar::T_I]); + } + + /** + * Test Parse with Tokens in Sequence for Thousands + */ + public function testParseWithTokensInSequenceForThousands() + { + $this->assertSame(4000, $this->parser->parse([Grammar::T_M, Grammar::T_M, Grammar::T_M, Grammar::T_M])); + + $this->assertSame(5000, $this->parser->parse([ + Grammar::T_M, + Grammar::T_M, + Grammar::T_M, + Grammar::T_M, + Grammar::T_M, + ])); + } } From 8782e37f232f230efbe3dd6c9f7c2898e8d4f0f6 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 16:50:50 -0300 Subject: [PATCH 04/49] Reduce number of tokens --- src/Grammar/Grammar.php | 66 +++++++++++++----------------------- test/Grammar/GrammarTest.php | 44 +++++++++--------------- 2 files changed, 40 insertions(+), 70 deletions(-) diff --git a/src/Grammar/Grammar.php b/src/Grammar/Grammar.php index c8524d2..91f0e09 100644 --- a/src/Grammar/Grammar.php +++ b/src/Grammar/Grammar.php @@ -7,20 +7,14 @@ */ class Grammar { - const T_N = 'N'; - const T_I = 'I'; - const T_IV = 'IV'; - const T_V = 'V'; - const T_IX = 'IX'; - const T_X = 'X'; - const T_XL = 'XL'; - const T_L = 'L'; - const T_XC = 'XC'; - const T_C = 'C'; - const T_CD = 'CD'; - const T_D = 'D'; - const T_CM = 'CM'; - const T_M = 'M'; + const T_N = 'N'; + const T_I = 'I'; + const T_V = 'V'; + const T_X = 'X'; + const T_L = 'L'; + const T_C = 'C'; + const T_D = 'D'; + const T_M = 'M'; /** * Get Tokens @@ -30,20 +24,14 @@ class Grammar public function getTokens() : array { return [ - 'T_N' => self::T_N, - 'T_I' => self::T_I, - 'T_IV' => self::T_IV, - 'T_V' => self::T_V, - 'T_IX' => self::T_IX, - 'T_X' => self::T_X, - 'T_XL' => self::T_XL, - 'T_L' => self::T_L, - 'T_XC' => self::T_XC, - 'T_C' => self::T_C, - 'T_CD' => self::T_CD, - 'T_D' => self::T_D, - 'T_CM' => self::T_CM, - 'T_M' => self::T_M, + 'T_N' => self::T_N, + 'T_I' => self::T_I, + 'T_V' => self::T_V, + 'T_X' => self::T_X, + 'T_L' => self::T_L, + 'T_C' => self::T_C, + 'T_D' => self::T_D, + 'T_M' => self::T_M, ]; } @@ -55,20 +43,14 @@ public function getTokens() : array public function getValues() { return [ - 'T_N' => 0, - 'T_I' => 1, - 'T_IV' => 4, - 'T_V' => 5, - 'T_IX' => 9, - 'T_X' => 10, - 'T_XL' => 40, - 'T_L' => 50, - 'T_XC' => 90, - 'T_C' => 100, - 'T_CD' => 400, - 'T_D' => 500, - 'T_CM' => 900, - 'T_M' => 1000, + 'T_N' => 0, + 'T_I' => 1, + 'T_V' => 5, + 'T_X' => 10, + 'T_L' => 50, + 'T_C' => 100, + 'T_D' => 500, + 'T_M' => 1000, ]; } } diff --git a/test/Grammar/GrammarTest.php b/test/Grammar/GrammarTest.php index 9e31da0..7370cb3 100644 --- a/test/Grammar/GrammarTest.php +++ b/test/Grammar/GrammarTest.php @@ -19,37 +19,25 @@ protected function setUp() $this->grammar = new Grammar(); $this->tokens = [ - 'T_N' => 'N', - 'T_I' => 'I', - 'T_IV' => 'IV', - 'T_V' => 'V', - 'T_IX' => 'IX', - 'T_X' => 'X', - 'T_XL' => 'XL', - 'T_L' => 'L', - 'T_XC' => 'XC', - 'T_C' => 'C', - 'T_CD' => 'CD', - 'T_D' => 'D', - 'T_CM' => 'CM', - 'T_M' => 'M', + 'T_N' => 'N', + 'T_I' => 'I', + 'T_V' => 'V', + 'T_X' => 'X', + 'T_L' => 'L', + 'T_C' => 'C', + 'T_D' => 'D', + 'T_M' => 'M', ]; $this->values = [ - 'T_N' => 0, - 'T_I' => 1, - 'T_IV' => 4, - 'T_V' => 5, - 'T_IX' => 9, - 'T_X' => 10, - 'T_XL' => 40, - 'T_L' => 50, - 'T_XC' => 90, - 'T_C' => 100, - 'T_CD' => 400, - 'T_D' => 500, - 'T_CM' => 900, - 'T_M' => 1000, + 'T_N' => 0, + 'T_I' => 1, + 'T_V' => 5, + 'T_X' => 10, + 'T_L' => 50, + 'T_C' => 100, + 'T_D' => 500, + 'T_M' => 1000, ]; } From 8fcbbcc939f4eeab490c3bbfd0208c7a3b4469d6 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 16:56:01 -0300 Subject: [PATCH 05/49] Remove lookahead feature --- src/Lexer/Lexer.php | 9 --------- test/Lexer/LexerTest.php | 32 +++++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/Lexer/Lexer.php b/src/Lexer/Lexer.php index b2df45e..f00148f 100644 --- a/src/Lexer/Lexer.php +++ b/src/Lexer/Lexer.php @@ -57,15 +57,6 @@ public function tokenize(string $content) : array throw $exception; } - // Lookahead - if ($position + 1 < $length) { - $next = $content[$position + 1]; - if (isset($numerals[$current . $next])) { - $current = $current . $next; - $position = $position + 1; - } - } - $result[] = $current; $position = $position + 1; } diff --git a/test/Lexer/LexerTest.php b/test/Lexer/LexerTest.php index d899bc3..b2cb18e 100644 --- a/test/Lexer/LexerTest.php +++ b/test/Lexer/LexerTest.php @@ -92,22 +92,36 @@ public function testInvalidTokenMessage() } /** - * Test Tokenize with Lookahead + * Test Tokenize with Lookahead Removal */ - public function testTokenizeWithLookahead() + public function testTokenizeWithLookaheadRemoval() { - $this->assertSame([Grammar::T_IV], $this->lexer->tokenize('IV')); - $this->assertSame([Grammar::T_IX], $this->lexer->tokenize('IX')); - - $this->assertSame([Grammar::T_CD, Grammar::T_XC, Grammar::T_IX], $this->lexer->tokenize('CDXCIX')); + $this->assertSame([Grammar::T_I, Grammar::T_V], $this->lexer->tokenize('IV')); + $this->assertSame([Grammar::T_I, Grammar::T_X], $this->lexer->tokenize('IX')); + + $this->assertSame([ + Grammar::T_C, + Grammar::T_D, + Grammar::T_X, + Grammar::T_C, + Grammar::T_I, + Grammar::T_X, + ], $this->lexer->tokenize('CDXCIX')); } /** - * Test Tokenize With Simple And Lookahead + * Test Tokenize With Simple And Lookahead Removal */ - public function testTokenizeWithSimpleAndLookahead() + public function testTokenizeWithSimpleAndLookaheadRemoval() { - $this->assertSame([Grammar::T_CD, Grammar::T_L, Grammar::T_X, Grammar::T_IX], $this->lexer->tokenize('CDLXIX')); + $this->assertSame([ + Grammar::T_C, + Grammar::T_D, + Grammar::T_L, + Grammar::T_X, + Grammar::T_I, + Grammar::T_X, + ], $this->lexer->tokenize('CDLXIX')); } /** From 68b62c5a83b7cae8c06e95ba5e68ba0b4aa8b8da Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 16:59:09 -0300 Subject: [PATCH 06/49] Remove lookahead tokens from Parser tests --- test/Parser/ParserTest.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/Parser/ParserTest.php b/test/Parser/ParserTest.php index d4a4cc1..5f8282a 100644 --- a/test/Parser/ParserTest.php +++ b/test/Parser/ParserTest.php @@ -63,12 +63,12 @@ public function testParseWithMultipleTokens() } /** - * Test Parse with Lookahead Tokens + * Test Parse with Lookahead Removal Tokens */ - public function testParseWithLookaheadTokens() + public function testParseWithLookaheadRemovalTokens() { - $this->assertSame(9, $this->parser->parse([Grammar::T_IX])); - $this->assertSame(99, $this->parser->parse([Grammar::T_XC, Grammar::T_IX])); + $this->assertSame(9, $this->parser->parse([Grammar::T_I, Grammar::T_X])); + $this->assertSame(99, $this->parser->parse([Grammar::T_X, Grammar::T_C, Grammar::T_I, Grammar::T_X])); } /** @@ -198,19 +198,19 @@ public function testParseWithOneInvalidToken() } /** - * Test Parse with Two Tokens with Lookahead + * Test Parse with Two Tokens with Lookahead Removal */ - public function testParseWithTwoTokensWithLookahead() + public function testParseWithTwoTokensWithLookaheadRemoval() { $this->expectException(ParserException::class); $this->expectExceptionMessage('Invalid Roman'); $this->expectExceptionCode(ParserException::INVALID_ROMAN); - $this->parser->parse([Grammar::T_IX, Grammar::T_IX]); + $this->parser->parse([Grammar::T_I, Grammar::T_X, Grammar::T_I, Grammar::T_X]); } /** - * Test Parse with One Lookahead Token and One Simple Token + * Test Parse with One Lookahead Removal Token and One Simple Token */ public function testParseWithOneLookaheadTokenAndOneSimpleToken() { @@ -218,7 +218,7 @@ public function testParseWithOneLookaheadTokenAndOneSimpleToken() $this->expectExceptionMessage('Invalid Roman'); $this->expectExceptionCode(ParserException::INVALID_ROMAN); - $this->parser->parse([Grammar::T_IX, Grammar::T_V]); + $this->parser->parse([Grammar::T_I, Grammar::T_X, Grammar::T_V]); } /** From 855d53941692d28302e8748b1b9535431eb3afdb Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:23:51 -0300 Subject: [PATCH 07/49] Initialize Automaton class to parse content --- src/Parser/Automaton.php | 18 ++++++++++++++++++ test/Parser/AutomatonTest.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/Parser/Automaton.php create mode 100644 test/Parser/AutomatonTest.php diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php new file mode 100644 index 0000000..6f551e9 --- /dev/null +++ b/src/Parser/Automaton.php @@ -0,0 +1,18 @@ +automaton = new Automaton(); + } + + /** + * Test States + */ + public function testStates() + { + $this->assertSame(Automaton::STATE_Z, 'Z'); + $this->assertSame(Automaton::STATE_A, 'A'); + $this->assertSame(Automaton::STATE_B, 'B'); + $this->assertSame(Automaton::STATE_C, 'C'); + $this->assertSame(Automaton::STATE_D, 'D'); + $this->assertSame(Automaton::STATE_E, 'E'); + $this->assertSame(Automaton::STATE_F, 'F'); + $this->assertSame(Automaton::STATE_G, 'G'); + } +} From db2c6c823685f3d0db9069603f8987c0a381240a Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:27:13 -0300 Subject: [PATCH 08/49] Configure available tokens for Automaton --- src/Parser/Automaton.php | 8 ++++++++ test/Parser/AutomatonTest.php | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 6f551e9..b7cad5b 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -15,4 +15,12 @@ class Automaton const STATE_E = 'E'; const STATE_F = 'F'; const STATE_G = 'G'; + + const TOKEN_I = 'I'; + const TOKEN_V = 'V'; + const TOKEN_X = 'X'; + const TOKEN_L = 'L'; + const TOKEN_C = 'C'; + const TOKEN_D = 'D'; + const TOKEN_M = 'M'; } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index cae054b..5df6581 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -32,4 +32,18 @@ public function testStates() $this->assertSame(Automaton::STATE_F, 'F'); $this->assertSame(Automaton::STATE_G, 'G'); } + + /** + * Test Tokens + */ + public function testTokens() + { + $this->assertSame(Automaton::TOKEN_I, 'I'); + $this->assertSame(Automaton::TOKEN_V, 'V'); + $this->assertSame(Automaton::TOKEN_X, 'X'); + $this->assertSame(Automaton::TOKEN_L, 'L'); + $this->assertSame(Automaton::TOKEN_C, 'C'); + $this->assertSame(Automaton::TOKEN_D, 'D'); + $this->assertSame(Automaton::TOKEN_M, 'M'); + } } From f38de751f2101cdedf9bb1f809c92034c492aadf Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:30:52 -0300 Subject: [PATCH 09/49] Configure initial state --- src/Parser/Automaton.php | 28 ++++++++++++++++++++++++++++ test/Parser/AutomatonTest.php | 8 ++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index b7cad5b..449f52e 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -23,4 +23,32 @@ class Automaton const TOKEN_C = 'C'; const TOKEN_D = 'D'; const TOKEN_M = 'M'; + + /** + * State + * @type string + */ + private $state = self::STATE_G; + + /** + * Set State + * + * @param string $state State Value + * @return self Fluent Interface + */ + protected function setState(string $state) : self + { + $this->state = $state; + return $this; + } + + /** + * Get State + * + * @return string State Value + */ + public function getState() : string + { + return $this->state; + } } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 5df6581..648e163 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -46,4 +46,12 @@ public function testTokens() $this->assertSame(Automaton::TOKEN_D, 'D'); $this->assertSame(Automaton::TOKEN_M, 'M'); } + + /** + * Test Initial State + */ + public function testInitialState() + { + $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); + } } From 18ff3b28ac595dd2119737c0eedadc9134ae490e Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:35:29 -0300 Subject: [PATCH 10/49] Include token for zero in Automaton --- src/Parser/Automaton.php | 1 + test/Parser/AutomatonTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 449f52e..acef606 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -16,6 +16,7 @@ class Automaton const STATE_F = 'F'; const STATE_G = 'G'; + const TOKEN_N = 'N'; const TOKEN_I = 'I'; const TOKEN_V = 'V'; const TOKEN_X = 'X'; diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 648e163..34a7993 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -38,6 +38,7 @@ public function testStates() */ public function testTokens() { + $this->assertSame(Automaton::TOKEN_N, 'N'); $this->assertSame(Automaton::TOKEN_I, 'I'); $this->assertSame(Automaton::TOKEN_V, 'V'); $this->assertSame(Automaton::TOKEN_X, 'X'); From 70937f9241a3c6554af23f712357a81fda3aa1c2 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:40:25 -0300 Subject: [PATCH 11/49] Initialize read method --- src/Parser/Automaton.php | 13 +++++++++++++ test/Parser/AutomatonTest.php | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index acef606..de69ee5 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -52,4 +52,17 @@ public function getState() : string { return $this->state; } + + /** + * Read + * + * @param string[] $tokens Tokens + * @return self Fluent Interface + */ + public function read(array $tokens) : self + { + $this->setState(self::STATE_Z); + + return $this; + } } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 34a7993..4b5bcb1 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -55,4 +55,13 @@ public function testInitialState() { $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); } + + /** + * Test Zero + */ + public function testZero() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_N])); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + } } From 029b62fcf5ddf02d9c40d9ec0252b5e43d2648d3 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:43:23 -0300 Subject: [PATCH 12/49] Add value method --- src/Parser/Automaton.php | 28 ++++++++++++++++++++++++++++ test/Parser/AutomatonTest.php | 9 +++++++++ 2 files changed, 37 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index de69ee5..d0e5f0d 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -31,6 +31,12 @@ class Automaton */ private $state = self::STATE_G; + /** + * Value + * @type int + */ + private $value = 0; + /** * Set State * @@ -53,6 +59,28 @@ public function getState() : string return $this->state; } + /** + * Set Value + * + * @param int $value Value + * @return self Fluent Interface + */ + protected function setValue(int $value) : self + { + $this->value = $value; + return $this; + } + + /** + * Get Value + * + * @return int Value + */ + public function getValue() : int + { + return $this->value; + } + /** * Read * diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 4b5bcb1..80be8f1 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -56,6 +56,14 @@ public function testInitialState() $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); } + /** + * Test Initial Value + */ + public function testInitialValue() + { + $this->assertSame(0, $this->automaton->getValue()); + } + /** * Test Zero */ @@ -63,5 +71,6 @@ public function testZero() { $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_N])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + $this->assertSame(0, $this->automaton->getValue()); } } From 8471dece7c5948a3d8c02a2ebe95461ac34036a9 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:51:08 -0300 Subject: [PATCH 13/49] Read simple tokens and change current state --- src/Parser/Automaton.php | 19 ++++++++++++++++++- test/Parser/AutomatonTest.php | 10 ++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index d0e5f0d..b992ebc 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -89,7 +89,24 @@ public function getValue() : int */ public function read(array $tokens) : self { - $this->setState(self::STATE_Z); + $length = count($tokens); + $position = 0; + + while ($position < $length) { + if ($tokens[$position] === self::TOKEN_M) { + $position = $position + 1; + + $this + ->setValue($this->getValue() + 1000) + ->setState(self::STATE_G); + } elseif ($tokens[$position] === self::TOKEN_N) { + $position = $position + 1; + + $this + ->setValue($this->getValue() + 0) + ->setState(self::STATE_Z); + } + } return $this; } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 80be8f1..746b78d 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -73,4 +73,14 @@ public function testZero() $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(0, $this->automaton->getValue()); } + + /** + * Test Token M + */ + public function testTokenM() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_M])); + $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); + $this->assertSame(1000, $this->automaton->getValue()); + } } From 2e8e139ad705f84006870a124e7558c24f3074de Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 17:57:22 -0300 Subject: [PATCH 14/49] Include access of current position --- src/Parser/Automaton.php | 51 ++++++++++++++++++++++++++--------- test/Parser/AutomatonTest.php | 10 +++++++ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index b992ebc..568216f 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -31,6 +31,12 @@ class Automaton */ private $state = self::STATE_G; + /** + * Position + * @type int + */ + private $position = 0; + /** * Value * @type int @@ -59,6 +65,28 @@ public function getState() : string return $this->state; } + /** + * Set Position + * + * @param int $value Position Value + * @return self Fluent Interface + */ + protected function setPosition(int $position) : self + { + $this->position = $position; + return $this; + } + + /** + * Get Position + * + * @return int Position Value + */ + public function getPosition() : int + { + return $this->position; + } + /** * Set Value * @@ -89,22 +117,19 @@ public function getValue() : int */ public function read(array $tokens) : self { - $length = count($tokens); - $position = 0; - - while ($position < $length) { - if ($tokens[$position] === self::TOKEN_M) { - $position = $position + 1; + $length = count($tokens); + while ($this->getPosition() < $length) { + if ($tokens[$this->getPosition()] === self::TOKEN_M) { $this - ->setValue($this->getValue() + 1000) - ->setState(self::STATE_G); - } elseif ($tokens[$position] === self::TOKEN_N) { - $position = $position + 1; - + ->setState(self::STATE_G) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 1000); + } elseif ($tokens[$this->getPosition()] === self::TOKEN_N) { $this - ->setValue($this->getValue() + 0) - ->setState(self::STATE_Z); + ->setState(self::STATE_Z) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 0); } } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 746b78d..6a44adf 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -56,6 +56,14 @@ public function testInitialState() $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); } + /** + * Test Initial Position + */ + public function testInitialPosition() + { + $this->assertSame(0, $this->automaton->getPosition()); + } + /** * Test Initial Value */ @@ -71,6 +79,7 @@ public function testZero() { $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_N])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(0, $this->automaton->getValue()); } @@ -81,6 +90,7 @@ public function testTokenM() { $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_M])); $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(1000, $this->automaton->getValue()); } } From bbd83e4df6cf0aa0ab36346aa1af7864852d4bbd Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 18:04:49 -0300 Subject: [PATCH 15/49] Add another token --- src/Parser/Automaton.php | 5 +++++ test/Parser/AutomatonTest.php | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 568216f..a4d83ba 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -125,6 +125,11 @@ public function read(array $tokens) : self ->setState(self::STATE_G) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 1000); + } elseif ($tokens[$this->getPosition()] === self::TOKEN_D) { + $this + ->setState(self::STATE_E) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 500); } elseif ($tokens[$this->getPosition()] === self::TOKEN_N) { $this ->setState(self::STATE_Z) diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 6a44adf..698892e 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -92,5 +92,21 @@ public function testTokenM() $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(1000, $this->automaton->getValue()); + + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_M, Automaton::TOKEN_M])); + $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(2000, $this->automaton->getValue()); + } + + /** + * Test Token D + */ + public function testTokenD() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_D])); + $this->assertSame(Automaton::STATE_E, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); + $this->assertSame(500, $this->automaton->getValue()); } } From f577c2b276f658881415dbce6869cdb1a60de00e Mon Sep 17 00:00:00 2001 From: Wanderson Date: Sat, 15 Apr 2017 18:06:58 -0300 Subject: [PATCH 16/49] Optimize token access --- src/Parser/Automaton.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index a4d83ba..fd2b42a 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -120,17 +120,19 @@ public function read(array $tokens) : self $length = count($tokens); while ($this->getPosition() < $length) { - if ($tokens[$this->getPosition()] === self::TOKEN_M) { + $token = $tokens[$this->getPosition()]; + + if ($token === self::TOKEN_M) { $this ->setState(self::STATE_G) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 1000); - } elseif ($tokens[$this->getPosition()] === self::TOKEN_D) { + } elseif ($token === self::TOKEN_D) { $this ->setState(self::STATE_E) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 500); - } elseif ($tokens[$this->getPosition()] === self::TOKEN_N) { + } elseif ($token === self::TOKEN_N) { $this ->setState(self::STATE_Z) ->setPosition($this->getPosition() + 1) From 549538d24578f4f56cec33a7a1e043715ebd3ebc Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 12:47:09 -0300 Subject: [PATCH 17/49] Check current state in transition --- src/Parser/Automaton.php | 26 +++++++++++++++++++------- test/Parser/AutomatonTest.php | 13 +++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index fd2b42a..1895b92 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -120,23 +120,35 @@ public function read(array $tokens) : self $length = count($tokens); while ($this->getPosition() < $length) { + $state = $this->getState(); $token = $tokens[$this->getPosition()]; - if ($token === self::TOKEN_M) { + if ($state === self::STATE_G && $token === self::TOKEN_N) { + $this + ->setState(self::STATE_Z) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 0); + } elseif ($state === self::STATE_G && $token === self::TOKEN_M) { $this ->setState(self::STATE_G) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 1000); - } elseif ($token === self::TOKEN_D) { + } elseif ($state === self::STATE_G) { + $this + ->setState(self::STATE_F); + } elseif ($state === self::STATE_F && $token === self::TOKEN_D) { $this ->setState(self::STATE_E) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 500); - } elseif ($token === self::TOKEN_N) { - $this - ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 0); + } else { + $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); + + $exception + ->setToken($token) + ->setPosition($this->getPosition()); + + throw $exception; } } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 698892e..d46e165 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Romans\Parser\Automaton; +use Romans\Parser\Exception as ParserException; /** * Automaton Test @@ -109,4 +110,16 @@ public function testTokenD() $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(500, $this->automaton->getValue()); } + + /** + * Test Invalid Transition + */ + public function testInvalidTransition() + { + $this->expectException(ParserException::class); + $this->expectExceptionMessage('Invalid Roman'); + $this->expectExceptionCode(ParserException::INVALID_ROMAN); + + $this->automaton->read([Automaton::TOKEN_D, Automaton::TOKEN_M]); + } } From 6359499ac93f7d83a00c9e8406404ec1cd1fd50c Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 12:52:28 -0300 Subject: [PATCH 18/49] Parse token C --- src/Parser/Automaton.php | 8 ++++++++ test/Parser/AutomatonTest.php | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 1895b92..1eccfe0 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -141,6 +141,14 @@ public function read(array $tokens) : self ->setState(self::STATE_E) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 500); + } elseif ($state === self::STATE_F) { + $this + ->setState(self::STATE_E); + } elseif ($state === self::STATE_E && $token === self::TOKEN_C) { + $this + ->setState(self::STATE_D) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 100); } else { $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index d46e165..53991b8 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -122,4 +122,15 @@ public function testInvalidTransition() $this->automaton->read([Automaton::TOKEN_D, Automaton::TOKEN_M]); } + + /** + * Test Token C + */ + public function testTokenC() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C])); + $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); + $this->assertSame(100, $this->automaton->getValue()); + } } From 1bb0e3ce120475380518f0ac45eb37520d99282f Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 13:01:12 -0300 Subject: [PATCH 19/49] Read two token Cs sequentially --- src/Parser/Automaton.php | 31 ++++++++++++++++++++++++++++++- test/Parser/AutomatonTest.php | 5 +++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 1eccfe0..4f466ed 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -109,6 +109,21 @@ public function getValue() : int return $this->value; } + /** + * Reset Counters + * + * @return self Fluent Interface + */ + protected function reset() : self + { + $this + ->setState(self::STATE_G) + ->setPosition(0) + ->setValue(0); + + return $this; + } + /** * Read * @@ -117,6 +132,8 @@ public function getValue() : int */ public function read(array $tokens) : self { + $this->reset(); + $length = count($tokens); while ($this->getPosition() < $length) { @@ -146,9 +163,21 @@ public function read(array $tokens) : self ->setState(self::STATE_E); } elseif ($state === self::STATE_E && $token === self::TOKEN_C) { $this - ->setState(self::STATE_D) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 100); + + // lookahead +1 + if ($this->getPosition() < $length) { + $token = $tokens[$this->getPosition()]; + if ($token === self::TOKEN_C) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 100); + } + } + + $this + ->setState(self::STATE_D); } else { $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 53991b8..6b1ce13 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -132,5 +132,10 @@ public function testTokenC() $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(100, $this->automaton->getValue()); + + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_C])); + $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(200, $this->automaton->getValue()); } } From 47b608487d8adbed013285e5590ed871fc1fd3fd Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 13:03:59 -0300 Subject: [PATCH 20/49] Read three tokens C --- src/Parser/Automaton.php | 10 ++++++++++ test/Parser/AutomatonTest.php | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 4f466ed..6892b9f 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -174,6 +174,16 @@ public function read(array $tokens) : self ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 100); } + + // lookahed +2 + if ($this->getPosition() < $length) { + $token = $tokens[$this->getPosition()]; + if ($token === self::TOKEN_C) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 100); + } + } } $this diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 6b1ce13..a25c6d7 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -137,5 +137,14 @@ public function testTokenC() $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(200, $this->automaton->getValue()); + + $this->assertSame($this->automaton, $this->automaton->read([ + Automaton::TOKEN_C, + Automaton::TOKEN_C, + Automaton::TOKEN_C, + ])); + $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(3, $this->automaton->getPosition()); + $this->assertSame(300, $this->automaton->getValue()); } } From eb82852c06e1fe8c96067a597f2dba6c211ed243 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 13:05:40 -0300 Subject: [PATCH 21/49] Include test to invalidate four tokens C --- test/Parser/AutomatonTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index a25c6d7..ffbfbf1 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -147,4 +147,16 @@ public function testTokenC() $this->assertSame(3, $this->automaton->getPosition()); $this->assertSame(300, $this->automaton->getValue()); } + + /** + * Test Invalid Four Tokens C + */ + public function testInvalidFourTokensC() + { + $this->expectException(ParserException::class); + $this->expectExceptionMessage('Invalid Roman'); + $this->expectExceptionCode(ParserException::INVALID_ROMAN); + + $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_C, Automaton::TOKEN_C, Automaton::TOKEN_C]); + } } From 2a7b1b0baece19c4f1c3fef286732a26371736a9 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 13:09:34 -0300 Subject: [PATCH 22/49] Read token L --- src/Parser/Automaton.php | 8 ++++++++ test/Parser/AutomatonTest.php | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 6892b9f..c0d800b 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -188,6 +188,14 @@ public function read(array $tokens) : self $this ->setState(self::STATE_D); + } elseif ($state === self::STATE_E) { + $this + ->setState(self::STATE_D); + } elseif ($state === self::STATE_D && $token === self::TOKEN_L) { + $this + ->setState(self::STATE_C) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 50); } else { $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index ffbfbf1..e39fca6 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -159,4 +159,15 @@ public function testInvalidFourTokensC() $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_C, Automaton::TOKEN_C, Automaton::TOKEN_C]); } + + /** + * Test Token L + */ + public function testTokenL() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_L])); + $this->assertSame(Automaton::STATE_C, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); + $this->assertSame(50, $this->automaton->getValue()); + } } From 119fa748b8def6192e79fe83d4db4988a58f8c76 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 13:13:55 -0300 Subject: [PATCH 23/49] Read tokens X sequentially --- src/Parser/Automaton.php | 30 ++++++++++++++++++++++++++++ test/Parser/AutomatonTest.php | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index c0d800b..e63852c 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -196,6 +196,36 @@ public function read(array $tokens) : self ->setState(self::STATE_C) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 50); + } elseif ($state === self::STATE_D) { + $this + ->setState(self::STATE_C); + } elseif ($state === self::STATE_C && $token === self::TOKEN_X) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 10); + + // lookahead +1 + if ($this->getPosition() < $length) { + $token = $tokens[$this->getPosition()]; + if ($token === self::TOKEN_X) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 10); + } + + // lookahed +2 + if ($this->getPosition() < $length) { + $token = $tokens[$this->getPosition()]; + if ($token === self::TOKEN_X) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 10); + } + } + } + + $this + ->setState(self::STATE_B); } else { $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index e39fca6..0e3c473 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -170,4 +170,41 @@ public function testTokenL() $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(50, $this->automaton->getValue()); } + + /** + * Test Token X + */ + public function testTokenX() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X])); + $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); + $this->assertSame(10, $this->automaton->getValue()); + + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_X])); + $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(20, $this->automaton->getValue()); + + $this->assertSame($this->automaton, $this->automaton->read([ + Automaton::TOKEN_X, + Automaton::TOKEN_X, + Automaton::TOKEN_X, + ])); + $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(3, $this->automaton->getPosition()); + $this->assertSame(30, $this->automaton->getValue()); + } + + /** + * Test Invalid Four Tokens X + */ + public function testInvalidFourTokensX() + { + $this->expectException(ParserException::class); + $this->expectExceptionMessage('Invalid Roman'); + $this->expectExceptionCode(ParserException::INVALID_ROMAN); + + $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_X, Automaton::TOKEN_X, Automaton::TOKEN_X]); + } } From 63ca530d3f458d9671815fcc0e8e0ce757ebaae9 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 13:17:23 -0300 Subject: [PATCH 24/49] Read token V --- src/Parser/Automaton.php | 8 ++++++++ test/Parser/AutomatonTest.php | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index e63852c..efb6c08 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -226,6 +226,14 @@ public function read(array $tokens) : self $this ->setState(self::STATE_B); + } elseif ($state === self::STATE_C) { + $this + ->setState(self::STATE_B); + } elseif ($state === self::STATE_B && $token === self::TOKEN_V) { + $this + ->setState(self::STATE_A) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 5); } else { $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 0e3c473..02e09dc 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -207,4 +207,15 @@ public function testInvalidFourTokensX() $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_X, Automaton::TOKEN_X, Automaton::TOKEN_X]); } + + /** + * Test Token V + */ + public function testTokenV() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_V])); + $this->assertSame(Automaton::STATE_A, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); + $this->assertSame(5, $this->automaton->getValue()); + } } From 2861ff27663117a587ac9c95c72b0ffbb33b4c93 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Mon, 17 Apr 2017 13:21:29 -0300 Subject: [PATCH 25/49] Read tokens I sequentially --- src/Parser/Automaton.php | 30 ++++++++++++++++++++++++++++ test/Parser/AutomatonTest.php | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index efb6c08..daf39a3 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -234,6 +234,36 @@ public function read(array $tokens) : self ->setState(self::STATE_A) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 5); + } elseif ($state === self::STATE_B) { + $this + ->setState(self::STATE_A); + } elseif ($state === self::STATE_A && $token === self::TOKEN_I) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 1); + + // lookahead +1 + if ($this->getPosition() < $length) { + $token = $tokens[$this->getPosition()]; + if ($token === self::TOKEN_I) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 1); + } + + // lookahed +2 + if ($this->getPosition() < $length) { + $token = $tokens[$this->getPosition()]; + if ($token === self::TOKEN_I) { + $this + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 1); + } + } + } + + $this + ->setState(self::STATE_Z); } else { $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 02e09dc..32cef1f 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -218,4 +218,41 @@ public function testTokenV() $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(5, $this->automaton->getValue()); } + + /** + * Test Token I + */ + public function testTokenI() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I])); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + $this->assertSame(1, $this->automaton->getPosition()); + $this->assertSame(1, $this->automaton->getValue()); + + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_I])); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(2, $this->automaton->getValue()); + + $this->assertSame($this->automaton, $this->automaton->read([ + Automaton::TOKEN_I, + Automaton::TOKEN_I, + Automaton::TOKEN_I, + ])); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + $this->assertSame(3, $this->automaton->getPosition()); + $this->assertSame(3, $this->automaton->getValue()); + } + + /** + * Test Invalid Four Tokens I + */ + public function testInvalidFourTokensI() + { + $this->expectException(ParserException::class); + $this->expectExceptionMessage('Invalid Roman'); + $this->expectExceptionCode(ParserException::INVALID_ROMAN); + + $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_I, Automaton::TOKEN_I, Automaton::TOKEN_I]); + } } From 1791d417306799261a321282554608300b985d1f Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 13:36:09 -0300 Subject: [PATCH 26/49] Apply code refactoring to switch conditionals --- src/Parser/Automaton.php | 204 ++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 101 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index daf39a3..2b1d040 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -140,138 +140,140 @@ public function read(array $tokens) : self $state = $this->getState(); $token = $tokens[$this->getPosition()]; - if ($state === self::STATE_G && $token === self::TOKEN_N) { - $this - ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 0); - } elseif ($state === self::STATE_G && $token === self::TOKEN_M) { - $this - ->setState(self::STATE_G) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 1000); - } elseif ($state === self::STATE_G) { - $this - ->setState(self::STATE_F); - } elseif ($state === self::STATE_F && $token === self::TOKEN_D) { - $this - ->setState(self::STATE_E) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 500); - } elseif ($state === self::STATE_F) { - $this - ->setState(self::STATE_E); - } elseif ($state === self::STATE_E && $token === self::TOKEN_C) { - $this - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 100); + switch ($state) { + case self::STATE_G: + if ($token === self::TOKEN_N) { + $this + ->setState(self::STATE_Z) + ->setPosition($this->getPosition() + 1); + } elseif ($token === self::TOKEN_M) { + $this + ->setState(self::STATE_G) + ->setPosition($this->getPosition() + 1) + ->setValue($this->getValue() + 1000); + } else { + $this->setState(self::STATE_F); + } + break; - // lookahead +1 - if ($this->getPosition() < $length) { - $token = $tokens[$this->getPosition()]; - if ($token === self::TOKEN_C) { + case self::STATE_F: + if ($token === self::TOKEN_D) { $this + ->setState(self::STATE_E) ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 100); + ->setValue($this->getValue() + 500); + } else { + $this->setState(self::STATE_E); } + break; - // lookahed +2 - if ($this->getPosition() < $length) { - $token = $tokens[$this->getPosition()]; - if ($token === self::TOKEN_C) { + case self::STATE_E: + if ($token === self::TOKEN_C) { + if ($this->getPosition() + 1 < $length + && $tokens[$this->getPosition() + 1] === self::TOKEN_C) { + if ($this->getPosition() + 2 < $length + && $tokens[$this->getPosition() + 2] === self::TOKEN_C) { + $this + ->setState(self::STATE_D) + ->setPosition($this->getPosition() + 3) + ->setValue($this->getValue() + 300); + } else { + $this + ->setState(self::STATE_D) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 200); + } + } else { $this + ->setState(self::STATE_D) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 100); } + } else { + $this->setState(self::STATE_D); } - } - - $this - ->setState(self::STATE_D); - } elseif ($state === self::STATE_E) { - $this - ->setState(self::STATE_D); - } elseif ($state === self::STATE_D && $token === self::TOKEN_L) { - $this - ->setState(self::STATE_C) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 50); - } elseif ($state === self::STATE_D) { - $this - ->setState(self::STATE_C); - } elseif ($state === self::STATE_C && $token === self::TOKEN_X) { - $this - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 10); + break; - // lookahead +1 - if ($this->getPosition() < $length) { - $token = $tokens[$this->getPosition()]; - if ($token === self::TOKEN_X) { + case self::STATE_D: + if ($token === self::TOKEN_L) { $this + ->setState(self::STATE_C) ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 10); + ->setValue($this->getValue() + 50); + } else { + $this->setState(self::STATE_C); } + break; - // lookahed +2 - if ($this->getPosition() < $length) { - $token = $tokens[$this->getPosition()]; - if ($token === self::TOKEN_X) { + case self::STATE_C: + if ($token === self::TOKEN_X) { + if ($this->getPosition() + 1 < $length + && $tokens[$this->getPosition() + 1] === self::TOKEN_X) { + if ($this->getPosition() + 2 < $length + && $tokens[$this->getPosition() + 2] === self::TOKEN_X) { + $this + ->setState(self::STATE_B) + ->setPosition($this->getPosition() + 3) + ->setValue($this->getValue() + 30); + } else { + $this + ->setState(self::STATE_B) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 20); + } + } else { $this + ->setState(self::STATE_B) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 10); } + } else { + $this->setState(self::STATE_B); } - } + break; - $this - ->setState(self::STATE_B); - } elseif ($state === self::STATE_C) { - $this - ->setState(self::STATE_B); - } elseif ($state === self::STATE_B && $token === self::TOKEN_V) { - $this - ->setState(self::STATE_A) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 5); - } elseif ($state === self::STATE_B) { - $this - ->setState(self::STATE_A); - } elseif ($state === self::STATE_A && $token === self::TOKEN_I) { - $this - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 1); - - // lookahead +1 - if ($this->getPosition() < $length) { - $token = $tokens[$this->getPosition()]; - if ($token === self::TOKEN_I) { + case self::STATE_B: + if ($token === self::TOKEN_V) { $this + ->setState(self::STATE_A) ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 1); + ->setValue($this->getValue() + 5); + } else { + $this->setState(self::STATE_A); } + break; - // lookahed +2 - if ($this->getPosition() < $length) { - $token = $tokens[$this->getPosition()]; - if ($token === self::TOKEN_I) { + case self::STATE_A: + if ($token === self::TOKEN_I) { + if ($this->getPosition() + 1 < $length + && $tokens[$this->getPosition() + 1] === self::TOKEN_I) { + if ($this->getPosition() + 2 < $length + && $tokens[$this->getPosition() + 2] === self::TOKEN_I) { + $this + ->setState(self::STATE_Z) + ->setPosition($this->getPosition() + 3) + ->setValue($this->getValue() + 3); + } else { + $this + ->setState(self::STATE_Z) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 2); + } + } else { $this + ->setState(self::STATE_Z) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 1); } + } else { + $this->setState(self::STATE_Z); } - } - - $this - ->setState(self::STATE_Z); - } else { - $exception = new Exception('Invalid Roman', Exception::INVALID_ROMAN); - - $exception - ->setToken($token) - ->setPosition($this->getPosition()); + break; - throw $exception; + default: + throw (new Exception('Invalid Roman', Exception::INVALID_ROMAN)) + ->setPosition($this->getPosition()) + ->setToken($token); } } From f280fdf2c4a22f793ba3c8400c65be01596e9813 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 13:46:22 -0300 Subject: [PATCH 27/49] Read tokens CD and CM --- src/Parser/Automaton.php | 14 ++++++++++++++ test/Parser/AutomatonTest.php | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 2b1d040..72b4362 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -162,6 +162,20 @@ public function read(array $tokens) : self ->setState(self::STATE_E) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 500); + } elseif ($token === self::TOKEN_C && $this->getPosition() + 1 < $length) { + if ($tokens[$this->getPosition() + 1] === self::TOKEN_D) { + $this + ->setState(self::STATE_D) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 400); + } elseif ($tokens[$this->getPosition() + 1] === self::TOKEN_M) { + $this + ->setState(self::STATE_D) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 900); + } else { + $this->setState(self::STATE_E); + } } else { $this->setState(self::STATE_E); } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 32cef1f..919a67e 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -255,4 +255,26 @@ public function testInvalidFourTokensI() $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_I, Automaton::TOKEN_I, Automaton::TOKEN_I]); } + + /** + * Test Token C Token D + */ + public function testTokenCTokenD() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_D])); + $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(400, $this->automaton->getValue()); + } + + /** + * Test Token C Token M + */ + public function testTokenCTokenM() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_M])); + $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(900, $this->automaton->getValue()); + } } From c6f665a89435d1f75dce8b2985050ab06b4d6170 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 13:50:41 -0300 Subject: [PATCH 28/49] Read tokens XL and XC --- src/Parser/Automaton.php | 14 ++++++++++++++ test/Parser/AutomatonTest.php | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 72b4362..6a836e8 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -214,6 +214,20 @@ public function read(array $tokens) : self ->setState(self::STATE_C) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 50); + } elseif ($token === self::TOKEN_X && $this->getPosition() + 1 < $length) { + if ($tokens[$this->getPosition() + 1] === self::TOKEN_L) { + $this + ->setState(self::STATE_B) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 40); + } elseif ($tokens[$this->getPosition() + 1] === self::TOKEN_C) { + $this + ->setState(self::STATE_B) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 90); + } else { + $this->setState(self::STATE_C); + } } else { $this->setState(self::STATE_C); } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 919a67e..ca147eb 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -277,4 +277,26 @@ public function testTokenCTokenM() $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(900, $this->automaton->getValue()); } + + /** + * Test Token X Token L + */ + public function testTokenXTokenL() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_L])); + $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(40, $this->automaton->getValue()); + } + + /** + * Test Token X Token C + */ + public function testTokenXTokenC() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_C])); + $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(90, $this->automaton->getValue()); + } } From 34a66eb657d2ab4820ba2d3c56eeb188534cff3f Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 13:53:38 -0300 Subject: [PATCH 29/49] Read tokens IV and IX --- src/Parser/Automaton.php | 14 ++++++++++++++ test/Parser/AutomatonTest.php | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 6a836e8..e07f6b1 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -266,6 +266,20 @@ public function read(array $tokens) : self ->setState(self::STATE_A) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 5); + } elseif ($token === self::TOKEN_I && $this->getPosition() + 1 < $length) { + if ($tokens[$this->getPosition() + 1] === self::TOKEN_V) { + $this + ->setState(self::STATE_Z) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 4); + } elseif ($tokens[$this->getPosition() + 1] === self::TOKEN_X) { + $this + ->setState(self::STATE_Z) + ->setPosition($this->getPosition() + 2) + ->setValue($this->getValue() + 9); + } else { + $this->setState(self::STATE_A); + } } else { $this->setState(self::STATE_A); } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index ca147eb..63a8e9c 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -299,4 +299,26 @@ public function testTokenXTokenC() $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(90, $this->automaton->getValue()); } + + /** + * Test Token I Token V + */ + public function testTokenITokenV() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_V])); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(4, $this->automaton->getValue()); + } + + /** + * Test Token I Token X + */ + public function testTokenITokenX() + { + $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_X])); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); + $this->assertSame(2, $this->automaton->getPosition()); + $this->assertSame(9, $this->automaton->getValue()); + } } From a1e52704a2808f94ace2c86d156c29e8e21560f5 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 13:58:08 -0300 Subject: [PATCH 30/49] Remove token constants from Automaton --- src/Parser/Automaton.php | 57 +++++++++++-------------- test/Parser/AutomatonTest.php | 78 ++++++++++++++--------------------- 2 files changed, 57 insertions(+), 78 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index e07f6b1..0ecce4b 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -2,6 +2,8 @@ namespace Romans\Parser; +use Romans\Grammar\Grammar; + /** * Automaton */ @@ -16,15 +18,6 @@ class Automaton const STATE_F = 'F'; const STATE_G = 'G'; - const TOKEN_N = 'N'; - const TOKEN_I = 'I'; - const TOKEN_V = 'V'; - const TOKEN_X = 'X'; - const TOKEN_L = 'L'; - const TOKEN_C = 'C'; - const TOKEN_D = 'D'; - const TOKEN_M = 'M'; - /** * State * @type string @@ -142,11 +135,11 @@ public function read(array $tokens) : self switch ($state) { case self::STATE_G: - if ($token === self::TOKEN_N) { + if ($token === Grammar::T_N) { $this ->setState(self::STATE_Z) ->setPosition($this->getPosition() + 1); - } elseif ($token === self::TOKEN_M) { + } elseif ($token === Grammar::T_M) { $this ->setState(self::STATE_G) ->setPosition($this->getPosition() + 1) @@ -157,18 +150,18 @@ public function read(array $tokens) : self break; case self::STATE_F: - if ($token === self::TOKEN_D) { + if ($token === Grammar::T_D) { $this ->setState(self::STATE_E) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 500); - } elseif ($token === self::TOKEN_C && $this->getPosition() + 1 < $length) { - if ($tokens[$this->getPosition() + 1] === self::TOKEN_D) { + } elseif ($token === Grammar::T_C && $this->getPosition() + 1 < $length) { + if ($tokens[$this->getPosition() + 1] === Grammar::T_D) { $this ->setState(self::STATE_D) ->setPosition($this->getPosition() + 2) ->setValue($this->getValue() + 400); - } elseif ($tokens[$this->getPosition() + 1] === self::TOKEN_M) { + } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_M) { $this ->setState(self::STATE_D) ->setPosition($this->getPosition() + 2) @@ -182,11 +175,11 @@ public function read(array $tokens) : self break; case self::STATE_E: - if ($token === self::TOKEN_C) { + if ($token === Grammar::T_C) { if ($this->getPosition() + 1 < $length - && $tokens[$this->getPosition() + 1] === self::TOKEN_C) { + && $tokens[$this->getPosition() + 1] === Grammar::T_C) { if ($this->getPosition() + 2 < $length - && $tokens[$this->getPosition() + 2] === self::TOKEN_C) { + && $tokens[$this->getPosition() + 2] === Grammar::T_C) { $this ->setState(self::STATE_D) ->setPosition($this->getPosition() + 3) @@ -209,18 +202,18 @@ public function read(array $tokens) : self break; case self::STATE_D: - if ($token === self::TOKEN_L) { + if ($token === Grammar::T_L) { $this ->setState(self::STATE_C) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 50); - } elseif ($token === self::TOKEN_X && $this->getPosition() + 1 < $length) { - if ($tokens[$this->getPosition() + 1] === self::TOKEN_L) { + } elseif ($token === Grammar::T_X && $this->getPosition() + 1 < $length) { + if ($tokens[$this->getPosition() + 1] === Grammar::T_L) { $this ->setState(self::STATE_B) ->setPosition($this->getPosition() + 2) ->setValue($this->getValue() + 40); - } elseif ($tokens[$this->getPosition() + 1] === self::TOKEN_C) { + } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_C) { $this ->setState(self::STATE_B) ->setPosition($this->getPosition() + 2) @@ -234,11 +227,11 @@ public function read(array $tokens) : self break; case self::STATE_C: - if ($token === self::TOKEN_X) { + if ($token === Grammar::T_X) { if ($this->getPosition() + 1 < $length - && $tokens[$this->getPosition() + 1] === self::TOKEN_X) { + && $tokens[$this->getPosition() + 1] === Grammar::T_X) { if ($this->getPosition() + 2 < $length - && $tokens[$this->getPosition() + 2] === self::TOKEN_X) { + && $tokens[$this->getPosition() + 2] === Grammar::T_X) { $this ->setState(self::STATE_B) ->setPosition($this->getPosition() + 3) @@ -261,18 +254,18 @@ public function read(array $tokens) : self break; case self::STATE_B: - if ($token === self::TOKEN_V) { + if ($token === Grammar::T_V) { $this ->setState(self::STATE_A) ->setPosition($this->getPosition() + 1) ->setValue($this->getValue() + 5); - } elseif ($token === self::TOKEN_I && $this->getPosition() + 1 < $length) { - if ($tokens[$this->getPosition() + 1] === self::TOKEN_V) { + } elseif ($token === Grammar::T_I && $this->getPosition() + 1 < $length) { + if ($tokens[$this->getPosition() + 1] === Grammar::T_V) { $this ->setState(self::STATE_Z) ->setPosition($this->getPosition() + 2) ->setValue($this->getValue() + 4); - } elseif ($tokens[$this->getPosition() + 1] === self::TOKEN_X) { + } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_X) { $this ->setState(self::STATE_Z) ->setPosition($this->getPosition() + 2) @@ -286,11 +279,11 @@ public function read(array $tokens) : self break; case self::STATE_A: - if ($token === self::TOKEN_I) { + if ($token === Grammar::T_I) { if ($this->getPosition() + 1 < $length - && $tokens[$this->getPosition() + 1] === self::TOKEN_I) { + && $tokens[$this->getPosition() + 1] === Grammar::T_I) { if ($this->getPosition() + 2 < $length - && $tokens[$this->getPosition() + 2] === self::TOKEN_I) { + && $tokens[$this->getPosition() + 2] === Grammar::T_I) { $this ->setState(self::STATE_Z) ->setPosition($this->getPosition() + 3) diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index 63a8e9c..edaa8c7 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -3,6 +3,7 @@ namespace RomansTest\Parser; use PHPUnit\Framework\TestCase; +use Romans\Grammar\Grammar; use Romans\Parser\Automaton; use Romans\Parser\Exception as ParserException; @@ -34,21 +35,6 @@ public function testStates() $this->assertSame(Automaton::STATE_G, 'G'); } - /** - * Test Tokens - */ - public function testTokens() - { - $this->assertSame(Automaton::TOKEN_N, 'N'); - $this->assertSame(Automaton::TOKEN_I, 'I'); - $this->assertSame(Automaton::TOKEN_V, 'V'); - $this->assertSame(Automaton::TOKEN_X, 'X'); - $this->assertSame(Automaton::TOKEN_L, 'L'); - $this->assertSame(Automaton::TOKEN_C, 'C'); - $this->assertSame(Automaton::TOKEN_D, 'D'); - $this->assertSame(Automaton::TOKEN_M, 'M'); - } - /** * Test Initial State */ @@ -78,7 +64,7 @@ public function testInitialValue() */ public function testZero() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_N])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_N])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(0, $this->automaton->getValue()); @@ -89,12 +75,12 @@ public function testZero() */ public function testTokenM() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_M])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_M])); $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(1000, $this->automaton->getValue()); - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_M, Automaton::TOKEN_M])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_M, Grammar::T_M])); $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(2000, $this->automaton->getValue()); @@ -105,7 +91,7 @@ public function testTokenM() */ public function testTokenD() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_D])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_D])); $this->assertSame(Automaton::STATE_E, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(500, $this->automaton->getValue()); @@ -120,7 +106,7 @@ public function testInvalidTransition() $this->expectExceptionMessage('Invalid Roman'); $this->expectExceptionCode(ParserException::INVALID_ROMAN); - $this->automaton->read([Automaton::TOKEN_D, Automaton::TOKEN_M]); + $this->automaton->read([Grammar::T_D, Grammar::T_M]); } /** @@ -128,20 +114,20 @@ public function testInvalidTransition() */ public function testTokenC() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C])); $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(100, $this->automaton->getValue()); - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_C])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C, Grammar::T_C])); $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(200, $this->automaton->getValue()); $this->assertSame($this->automaton, $this->automaton->read([ - Automaton::TOKEN_C, - Automaton::TOKEN_C, - Automaton::TOKEN_C, + Grammar::T_C, + Grammar::T_C, + Grammar::T_C, ])); $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); $this->assertSame(3, $this->automaton->getPosition()); @@ -157,7 +143,7 @@ public function testInvalidFourTokensC() $this->expectExceptionMessage('Invalid Roman'); $this->expectExceptionCode(ParserException::INVALID_ROMAN); - $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_C, Automaton::TOKEN_C, Automaton::TOKEN_C]); + $this->automaton->read([Grammar::T_C, Grammar::T_C, Grammar::T_C, Grammar::T_C]); } /** @@ -165,7 +151,7 @@ public function testInvalidFourTokensC() */ public function testTokenL() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_L])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_L])); $this->assertSame(Automaton::STATE_C, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(50, $this->automaton->getValue()); @@ -176,20 +162,20 @@ public function testTokenL() */ public function testTokenX() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X])); $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(10, $this->automaton->getValue()); - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_X])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X, Grammar::T_X])); $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(20, $this->automaton->getValue()); $this->assertSame($this->automaton, $this->automaton->read([ - Automaton::TOKEN_X, - Automaton::TOKEN_X, - Automaton::TOKEN_X, + Grammar::T_X, + Grammar::T_X, + Grammar::T_X, ])); $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); $this->assertSame(3, $this->automaton->getPosition()); @@ -205,7 +191,7 @@ public function testInvalidFourTokensX() $this->expectExceptionMessage('Invalid Roman'); $this->expectExceptionCode(ParserException::INVALID_ROMAN); - $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_X, Automaton::TOKEN_X, Automaton::TOKEN_X]); + $this->automaton->read([Grammar::T_X, Grammar::T_X, Grammar::T_X, Grammar::T_X]); } /** @@ -213,7 +199,7 @@ public function testInvalidFourTokensX() */ public function testTokenV() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_V])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_V])); $this->assertSame(Automaton::STATE_A, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(5, $this->automaton->getValue()); @@ -224,20 +210,20 @@ public function testTokenV() */ public function testTokenI() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_I])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(1, $this->automaton->getValue()); - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_I])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_I, Grammar::T_I])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(2, $this->automaton->getValue()); $this->assertSame($this->automaton, $this->automaton->read([ - Automaton::TOKEN_I, - Automaton::TOKEN_I, - Automaton::TOKEN_I, + Grammar::T_I, + Grammar::T_I, + Grammar::T_I, ])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(3, $this->automaton->getPosition()); @@ -253,7 +239,7 @@ public function testInvalidFourTokensI() $this->expectExceptionMessage('Invalid Roman'); $this->expectExceptionCode(ParserException::INVALID_ROMAN); - $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_I, Automaton::TOKEN_I, Automaton::TOKEN_I]); + $this->automaton->read([Grammar::T_I, Grammar::T_I, Grammar::T_I, Grammar::T_I]); } /** @@ -261,7 +247,7 @@ public function testInvalidFourTokensI() */ public function testTokenCTokenD() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_D])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C, Grammar::T_D])); $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(400, $this->automaton->getValue()); @@ -272,7 +258,7 @@ public function testTokenCTokenD() */ public function testTokenCTokenM() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_C, Automaton::TOKEN_M])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C, Grammar::T_M])); $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(900, $this->automaton->getValue()); @@ -283,7 +269,7 @@ public function testTokenCTokenM() */ public function testTokenXTokenL() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_L])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X, Grammar::T_L])); $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(40, $this->automaton->getValue()); @@ -294,7 +280,7 @@ public function testTokenXTokenL() */ public function testTokenXTokenC() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_X, Automaton::TOKEN_C])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X, Grammar::T_C])); $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(90, $this->automaton->getValue()); @@ -305,7 +291,7 @@ public function testTokenXTokenC() */ public function testTokenITokenV() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_V])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_I, Grammar::T_V])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(4, $this->automaton->getValue()); @@ -316,7 +302,7 @@ public function testTokenITokenV() */ public function testTokenITokenX() { - $this->assertSame($this->automaton, $this->automaton->read([Automaton::TOKEN_I, Automaton::TOKEN_X])); + $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_I, Grammar::T_X])); $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(9, $this->automaton->getValue()); From a6bc70c61731827728641bbfc91fa0c81fbecb6b Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 13:59:22 -0300 Subject: [PATCH 31/49] Center reset feature into construction --- src/Parser/Automaton.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 0ecce4b..dba460d 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -22,19 +22,27 @@ class Automaton * State * @type string */ - private $state = self::STATE_G; + private $state; /** * Position * @type int */ - private $position = 0; + private $position; /** * Value * @type int */ - private $value = 0; + private $value; + + /** + * Default Constructor + */ + public function __construct() + { + $this->reset(); + } /** * Set State From e6b7a2ff86899e9eb27074b9071c438aa1733d71 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 14:07:07 -0300 Subject: [PATCH 32/49] Apply code refactor to better readability --- src/Parser/Automaton.php | 102 ++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index dba460d..b1f1e43 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -88,6 +88,18 @@ public function getPosition() : int return $this->position; } + /** + * Add Position + * + * @param int $offset Offset Value + * @return self Fluent Interface + */ + protected function addPosition(int $offset) : self + { + $this->setPosition($this->getPosition() + $offset); + return $this; + } + /** * Set Value * @@ -110,6 +122,18 @@ public function getValue() : int return $this->value; } + /** + * Add Value + * + * @param int $offset Offset Value + * @return self Fluent Interface + */ + protected function addValue(int $offset) : self + { + $this->setValue($this->getValue() + $offset); + return $this; + } + /** * Reset Counters * @@ -146,12 +170,12 @@ public function read(array $tokens) : self if ($token === Grammar::T_N) { $this ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 1); + ->addPosition(1); } elseif ($token === Grammar::T_M) { $this ->setState(self::STATE_G) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 1000); + ->addPosition(1) + ->addValue(1000); } else { $this->setState(self::STATE_F); } @@ -161,19 +185,19 @@ public function read(array $tokens) : self if ($token === Grammar::T_D) { $this ->setState(self::STATE_E) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 500); + ->addPosition(1) + ->addValue(500); } elseif ($token === Grammar::T_C && $this->getPosition() + 1 < $length) { if ($tokens[$this->getPosition() + 1] === Grammar::T_D) { $this ->setState(self::STATE_D) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 400); + ->addPosition(2) + ->addValue(400); } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_M) { $this ->setState(self::STATE_D) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 900); + ->addPosition(2) + ->addValue(900); } else { $this->setState(self::STATE_E); } @@ -190,19 +214,19 @@ public function read(array $tokens) : self && $tokens[$this->getPosition() + 2] === Grammar::T_C) { $this ->setState(self::STATE_D) - ->setPosition($this->getPosition() + 3) - ->setValue($this->getValue() + 300); + ->addPosition(3) + ->addValue(300); } else { $this ->setState(self::STATE_D) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 200); + ->addPosition(2) + ->addValue(200); } } else { $this ->setState(self::STATE_D) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 100); + ->addPosition(1) + ->addValue(100); } } else { $this->setState(self::STATE_D); @@ -213,19 +237,19 @@ public function read(array $tokens) : self if ($token === Grammar::T_L) { $this ->setState(self::STATE_C) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 50); + ->addPosition(1) + ->addValue(50); } elseif ($token === Grammar::T_X && $this->getPosition() + 1 < $length) { if ($tokens[$this->getPosition() + 1] === Grammar::T_L) { $this ->setState(self::STATE_B) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 40); + ->addPosition(2) + ->addValue(40); } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_C) { $this ->setState(self::STATE_B) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 90); + ->addPosition(2) + ->addValue(90); } else { $this->setState(self::STATE_C); } @@ -242,19 +266,19 @@ public function read(array $tokens) : self && $tokens[$this->getPosition() + 2] === Grammar::T_X) { $this ->setState(self::STATE_B) - ->setPosition($this->getPosition() + 3) - ->setValue($this->getValue() + 30); + ->addPosition(3) + ->addValue(30); } else { $this ->setState(self::STATE_B) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 20); + ->addPosition(2) + ->addValue(20); } } else { $this ->setState(self::STATE_B) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 10); + ->addPosition(1) + ->addValue(10); } } else { $this->setState(self::STATE_B); @@ -265,19 +289,19 @@ public function read(array $tokens) : self if ($token === Grammar::T_V) { $this ->setState(self::STATE_A) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 5); + ->addPosition(1) + ->addValue(5); } elseif ($token === Grammar::T_I && $this->getPosition() + 1 < $length) { if ($tokens[$this->getPosition() + 1] === Grammar::T_V) { $this ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 4); + ->addPosition(2) + ->addValue(4); } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_X) { $this ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 9); + ->addPosition(2) + ->addValue(9); } else { $this->setState(self::STATE_A); } @@ -294,19 +318,19 @@ public function read(array $tokens) : self && $tokens[$this->getPosition() + 2] === Grammar::T_I) { $this ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 3) - ->setValue($this->getValue() + 3); + ->addPosition(3) + ->addValue(3); } else { $this ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 2) - ->setValue($this->getValue() + 2); + ->addPosition(2) + ->addValue(2); } } else { $this ->setState(self::STATE_Z) - ->setPosition($this->getPosition() + 1) - ->setValue($this->getValue() + 1); + ->addPosition(1) + ->addValue(1); } } else { $this->setState(self::STATE_Z); From 591f6ad4d82d781ada425355a2617c15a6279af4 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 14:23:15 -0300 Subject: [PATCH 33/49] Create function to add values with Grammar tokens --- src/Parser/Automaton.php | 77 +++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index b1f1e43..2de83b6 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -3,12 +3,15 @@ namespace Romans\Parser; use Romans\Grammar\Grammar; +use Romans\Grammar\GrammarAwareTrait; /** * Automaton */ class Automaton { + use GrammarAwareTrait; + const STATE_Z = 'Z'; const STATE_A = 'A'; const STATE_B = 'B'; @@ -38,10 +41,18 @@ class Automaton /** * Default Constructor + * + * @param Grammar $grammar Grammar Object */ - public function __construct() + public function __construct(Grammar $grammar = null) { - $this->reset(); + if (! isset($grammar)) { + $grammar = new Grammar(); + } + + $this + ->setGrammar($grammar) + ->reset(); } /** @@ -134,6 +145,30 @@ protected function addValue(int $offset) : self return $this; } + /** + * Add Token Value + * + * @param string $token Token + * @param string $modifier Modifier Token + * @param int $quantity Quantity + * @return self Fluent Interface + */ + protected function addTokenValue(string $token, string $modifier = null, int $quantity = 1) : self + { + $values = array_combine($this->getGrammar()->getTokens(), $this->getGrammar()->getValues()); + + $tokenValue = $values[$token]; + $modifierValue = 0; + + if (isset($modifier)) { + $modifierValue = $values[$modifier]; + } + + $this->addValue(($tokenValue - $modifierValue) * $quantity); + + return $this; + } + /** * Reset Counters * @@ -175,7 +210,7 @@ public function read(array $tokens) : self $this ->setState(self::STATE_G) ->addPosition(1) - ->addValue(1000); + ->addTokenValue(Grammar::T_M); } else { $this->setState(self::STATE_F); } @@ -186,18 +221,18 @@ public function read(array $tokens) : self $this ->setState(self::STATE_E) ->addPosition(1) - ->addValue(500); + ->addTokenValue(Grammar::T_D); } elseif ($token === Grammar::T_C && $this->getPosition() + 1 < $length) { if ($tokens[$this->getPosition() + 1] === Grammar::T_D) { $this ->setState(self::STATE_D) ->addPosition(2) - ->addValue(400); + ->addTokenValue(Grammar::T_D, Grammar::T_C); } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_M) { $this ->setState(self::STATE_D) ->addPosition(2) - ->addValue(900); + ->addTokenValue(Grammar::T_M, Grammar::T_C); } else { $this->setState(self::STATE_E); } @@ -215,18 +250,18 @@ public function read(array $tokens) : self $this ->setState(self::STATE_D) ->addPosition(3) - ->addValue(300); + ->addTokenValue(Grammar::T_C, null, 3); } else { $this ->setState(self::STATE_D) ->addPosition(2) - ->addValue(200); + ->addTokenValue(Grammar::T_C, null, 2); } } else { $this ->setState(self::STATE_D) ->addPosition(1) - ->addValue(100); + ->addTokenValue(Grammar::T_C); } } else { $this->setState(self::STATE_D); @@ -238,18 +273,18 @@ public function read(array $tokens) : self $this ->setState(self::STATE_C) ->addPosition(1) - ->addValue(50); + ->addTokenValue(Grammar::T_L); } elseif ($token === Grammar::T_X && $this->getPosition() + 1 < $length) { if ($tokens[$this->getPosition() + 1] === Grammar::T_L) { $this ->setState(self::STATE_B) ->addPosition(2) - ->addValue(40); + ->addTokenValue(Grammar::T_L, Grammar::T_X); } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_C) { $this ->setState(self::STATE_B) ->addPosition(2) - ->addValue(90); + ->addTokenValue(Grammar::T_C, Grammar::T_X); } else { $this->setState(self::STATE_C); } @@ -267,18 +302,18 @@ public function read(array $tokens) : self $this ->setState(self::STATE_B) ->addPosition(3) - ->addValue(30); + ->addTokenValue(Grammar::T_X, null, 3); } else { $this ->setState(self::STATE_B) ->addPosition(2) - ->addValue(20); + ->addTokenValue(Grammar::T_X, null, 2); } } else { $this ->setState(self::STATE_B) ->addPosition(1) - ->addValue(10); + ->addTokenValue(Grammar::T_X); } } else { $this->setState(self::STATE_B); @@ -290,18 +325,18 @@ public function read(array $tokens) : self $this ->setState(self::STATE_A) ->addPosition(1) - ->addValue(5); + ->addTokenValue(Grammar::T_V); } elseif ($token === Grammar::T_I && $this->getPosition() + 1 < $length) { if ($tokens[$this->getPosition() + 1] === Grammar::T_V) { $this ->setState(self::STATE_Z) ->addPosition(2) - ->addValue(4); + ->addTokenValue(Grammar::T_V, Grammar::T_I); } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_X) { $this ->setState(self::STATE_Z) ->addPosition(2) - ->addValue(9); + ->addTokenValue(Grammar::T_X, Grammar::T_I); } else { $this->setState(self::STATE_A); } @@ -319,18 +354,18 @@ public function read(array $tokens) : self $this ->setState(self::STATE_Z) ->addPosition(3) - ->addValue(3); + ->addTokenValue(Grammar::T_I, null, 3); } else { $this ->setState(self::STATE_Z) ->addPosition(2) - ->addValue(2); + ->addTokenValue(Grammar::T_I, null, 2); } } else { $this ->setState(self::STATE_Z) ->addPosition(1) - ->addValue(1); + ->addTokenValue(Grammar::T_I); } } else { $this->setState(self::STATE_Z); From 7557363b5079de0d981252ba4fc82ca52609b855 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 14:46:06 -0300 Subject: [PATCH 34/49] Finish always with state Z --- src/Parser/Automaton.php | 36 +++++++++++++++++++++-------------- test/Parser/AutomatonTest.php | 30 ++++++++++++++--------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 2de83b6..9ab3ee0 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -20,6 +20,7 @@ class Automaton const STATE_E = 'E'; const STATE_F = 'F'; const STATE_G = 'G'; + const STATE_H = 'H'; /** * State @@ -194,17 +195,18 @@ public function read(array $tokens) : self { $this->reset(); + array_push($tokens, '$'); + $length = count($tokens); - while ($this->getPosition() < $length) { - $state = $this->getState(); + while ($this->getState() !== self::STATE_Z) { $token = $tokens[$this->getPosition()]; - switch ($state) { + switch ($this->getState()) { case self::STATE_G: if ($token === Grammar::T_N) { $this - ->setState(self::STATE_Z) + ->setState(self::STATE_H) ->addPosition(1); } elseif ($token === Grammar::T_M) { $this @@ -329,12 +331,12 @@ public function read(array $tokens) : self } elseif ($token === Grammar::T_I && $this->getPosition() + 1 < $length) { if ($tokens[$this->getPosition() + 1] === Grammar::T_V) { $this - ->setState(self::STATE_Z) + ->setState(self::STATE_H) ->addPosition(2) ->addTokenValue(Grammar::T_V, Grammar::T_I); } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_X) { $this - ->setState(self::STATE_Z) + ->setState(self::STATE_H) ->addPosition(2) ->addTokenValue(Grammar::T_X, Grammar::T_I); } else { @@ -352,30 +354,36 @@ public function read(array $tokens) : self if ($this->getPosition() + 2 < $length && $tokens[$this->getPosition() + 2] === Grammar::T_I) { $this - ->setState(self::STATE_Z) + ->setState(self::STATE_H) ->addPosition(3) ->addTokenValue(Grammar::T_I, null, 3); } else { $this - ->setState(self::STATE_Z) + ->setState(self::STATE_H) ->addPosition(2) ->addTokenValue(Grammar::T_I, null, 2); } } else { $this - ->setState(self::STATE_Z) + ->setState(self::STATE_H) ->addPosition(1) ->addTokenValue(Grammar::T_I); } } else { - $this->setState(self::STATE_Z); + $this->setState(self::STATE_H); } break; - default: - throw (new Exception('Invalid Roman', Exception::INVALID_ROMAN)) - ->setPosition($this->getPosition()) - ->setToken($token); + case self::STATE_H: + if ($token === '$') { + // done! + $this->setState(self::STATE_Z); + } else { + throw (new Exception('Invalid Roman', Exception::INVALID_ROMAN)) + ->setPosition($this->getPosition()) + ->setToken($token); + } + break; } } diff --git a/test/Parser/AutomatonTest.php b/test/Parser/AutomatonTest.php index edaa8c7..617fcd8 100644 --- a/test/Parser/AutomatonTest.php +++ b/test/Parser/AutomatonTest.php @@ -76,12 +76,12 @@ public function testZero() public function testTokenM() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_M])); - $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(1000, $this->automaton->getValue()); $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_M, Grammar::T_M])); - $this->assertSame(Automaton::STATE_G, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(2000, $this->automaton->getValue()); } @@ -92,7 +92,7 @@ public function testTokenM() public function testTokenD() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_D])); - $this->assertSame(Automaton::STATE_E, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(500, $this->automaton->getValue()); } @@ -115,12 +115,12 @@ public function testInvalidTransition() public function testTokenC() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C])); - $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(100, $this->automaton->getValue()); $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C, Grammar::T_C])); - $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(200, $this->automaton->getValue()); @@ -129,7 +129,7 @@ public function testTokenC() Grammar::T_C, Grammar::T_C, ])); - $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(3, $this->automaton->getPosition()); $this->assertSame(300, $this->automaton->getValue()); } @@ -152,7 +152,7 @@ public function testInvalidFourTokensC() public function testTokenL() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_L])); - $this->assertSame(Automaton::STATE_C, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(50, $this->automaton->getValue()); } @@ -163,12 +163,12 @@ public function testTokenL() public function testTokenX() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X])); - $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(10, $this->automaton->getValue()); $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X, Grammar::T_X])); - $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(20, $this->automaton->getValue()); @@ -177,7 +177,7 @@ public function testTokenX() Grammar::T_X, Grammar::T_X, ])); - $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(3, $this->automaton->getPosition()); $this->assertSame(30, $this->automaton->getValue()); } @@ -200,7 +200,7 @@ public function testInvalidFourTokensX() public function testTokenV() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_V])); - $this->assertSame(Automaton::STATE_A, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(1, $this->automaton->getPosition()); $this->assertSame(5, $this->automaton->getValue()); } @@ -248,7 +248,7 @@ public function testInvalidFourTokensI() public function testTokenCTokenD() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C, Grammar::T_D])); - $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(400, $this->automaton->getValue()); } @@ -259,7 +259,7 @@ public function testTokenCTokenD() public function testTokenCTokenM() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_C, Grammar::T_M])); - $this->assertSame(Automaton::STATE_D, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(900, $this->automaton->getValue()); } @@ -270,7 +270,7 @@ public function testTokenCTokenM() public function testTokenXTokenL() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X, Grammar::T_L])); - $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(40, $this->automaton->getValue()); } @@ -281,7 +281,7 @@ public function testTokenXTokenL() public function testTokenXTokenC() { $this->assertSame($this->automaton, $this->automaton->read([Grammar::T_X, Grammar::T_C])); - $this->assertSame(Automaton::STATE_B, $this->automaton->getState()); + $this->assertSame(Automaton::STATE_Z, $this->automaton->getState()); $this->assertSame(2, $this->automaton->getPosition()); $this->assertSame(90, $this->automaton->getValue()); } From 3045e092380a9997e9ed3e0878a74efa95d1d00e Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 15:05:10 -0300 Subject: [PATCH 35/49] Use Automaton to read token stream --- src/Parser/Parser.php | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index c034e63..025616a 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -37,9 +37,7 @@ public function parse(array $tokens) : int $values = $this->getGrammar()->getValues(); $tokensAvailable = array_flip($this->getGrammar()->getTokens()); - $result = 0; - $lastValue = null; - $length = count($tokens); + $length = count($tokens); if ($length === 0) { throw new Exception('Invalid Roman', Exception::INVALID_ROMAN); @@ -69,23 +67,8 @@ public function parse(array $tokens) : int throw $exception; } - - $value = $values[$tokensAvailable[$token]]; - - // $value === 0 && $length === 1? One Token with Nulla - - if ($value === 0 && $length !== 1) { - throw new Exception('Invalid Roman', Exception::INVALID_ROMAN); - } - - if (isset($lastValue) && $lastValue < $value) { - throw new Exception('Invalid Roman', Exception::INVALID_ROMAN); - } - - $result = $result + $value; - $lastValue = $value; } - return $result; + return (new Automaton($this->getGrammar()))->read($tokens)->getValue(); } } From 55019d0f5743c052bf699092c8b4064520b2ab99 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 15:21:09 -0300 Subject: [PATCH 36/49] Change responsibility of modifiers to Grammar --- src/Grammar/Grammar.php | 35 +++++++++++++++++++++++++++++++++++ test/Grammar/GrammarTest.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/Grammar/Grammar.php b/src/Grammar/Grammar.php index 91f0e09..55cade3 100644 --- a/src/Grammar/Grammar.php +++ b/src/Grammar/Grammar.php @@ -53,4 +53,39 @@ public function getValues() 'T_M' => 1000, ]; } + + /** + * Get Modifiers + * + * @return array Modifiers Available + */ + public function getModifiers() + { + return [ + 4 => ['T_I', 'T_V'], + 9 => ['T_I', 'T_X'], + 40 => ['T_X', 'T_L'], + 90 => ['T_X', 'T_C'], + 400 => ['T_C', 'T_D'], + 900 => ['T_C', 'T_M'], + ]; + } + + /** + * Get Values with Modifiers + * + * @return array Values with Modifiers Available + */ + public function getValuesWithModifiers() + { + $values = array_map(function ($value) { + return [$value]; + }, array_flip($this->getValues())); + + $valuesWithModifiers = $values + $this->getModifiers(); // merge and keep keys (append) + + ksort($valuesWithModifiers); + + return $valuesWithModifiers; + } } diff --git a/test/Grammar/GrammarTest.php b/test/Grammar/GrammarTest.php index 7370cb3..e3af0e5 100644 --- a/test/Grammar/GrammarTest.php +++ b/test/Grammar/GrammarTest.php @@ -39,6 +39,15 @@ protected function setUp() 'T_D' => 500, 'T_M' => 1000, ]; + + $this->modifiers = [ + 4 => ['T_I', 'T_V'], + 9 => ['T_I', 'T_X'], + 40 => ['T_X', 'T_L'], + 90 => ['T_X', 'T_C'], + 400 => ['T_C', 'T_D'], + 900 => ['T_C', 'T_M'], + ]; } /** @@ -62,6 +71,14 @@ public function testAvailableTokens() $this->assertSame($this->tokens, $this->grammar->getTokens()); } + /** + * Test Modifiers + */ + public function testModifiers() + { + $this->assertSame($this->modifiers, $this->grammar->getModifiers()); + } + /** * Test Values */ @@ -69,4 +86,20 @@ public function testValues() { $this->assertSame($this->values, $this->grammar->getValues()); } + + /** + * Test Values with Modifiers + */ + public function testValuesWithModifiers() + { + $values = array_map(function ($value) { + return [$value]; + }, array_flip($this->values)); + + $valuesWithModifiers = $values + $this->modifiers; // merge and keep keys + + ksort($valuesWithModifiers); + + $this->assertSame($valuesWithModifiers, $this->grammar->getValuesWithModifiers()); + } } From 15fc342fc6f3f4a212c901840fd10173748a98b5 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 15:46:18 -0300 Subject: [PATCH 37/49] Update filter to use Grammar modifiers --- src/Filter/IntToRoman.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Filter/IntToRoman.php b/src/Filter/IntToRoman.php index be4517d..2ebabea 100644 --- a/src/Filter/IntToRoman.php +++ b/src/Filter/IntToRoman.php @@ -39,20 +39,25 @@ public function filter(int $value) : string } $tokens = $this->getGrammar()->getTokens(); - $values = array_reverse($this->getGrammar()->getValues()); + $values = array_reverse($this->getGrammar()->getValuesWithModifiers(), true /* preserve keys */); + $result = ''; if ($value === 0) { - $token = array_search(0, $values); + $dataset = $values[0]; - return $tokens[$token]; - } + foreach ($dataset as $token) { + $result = $result . $tokens[$token]; + } - $result = ''; + return $result; + } - foreach ($values as $token => $current) { + foreach ($values as $current => $dataset) { while ($current > 0 && $value >= $current) { - $value = $value - $current; - $result = $result . $tokens[$token]; + $value = $value - $current; + foreach ($dataset as $token) { + $result = $result . $tokens[$token]; + } } } From 48ad5cb6f9f02468fd67ba78f7bade93df8a9870 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 15:55:06 -0300 Subject: [PATCH 38/49] Use Grammar modifiers to calculate --- src/Parser/Automaton.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 9ab3ee0..f7cfe33 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -156,16 +156,17 @@ protected function addValue(int $offset) : self */ protected function addTokenValue(string $token, string $modifier = null, int $quantity = 1) : self { - $values = array_combine($this->getGrammar()->getTokens(), $this->getGrammar()->getValues()); - - $tokenValue = $values[$token]; - $modifierValue = 0; + $tokens = array_flip($this->getGrammar()->getTokens()); + $values = $this->getGrammar()->getValuesWithModifiers(); + $elements = []; if (isset($modifier)) { - $modifierValue = $values[$modifier]; + $elements[] = $tokens[$modifier]; } - $this->addValue(($tokenValue - $modifierValue) * $quantity); + $elements[] = $tokens[$token]; + + $this->addValue((array_search($elements, $values)) * $quantity); return $this; } From 0ef795b649ed4151089bdd3c1e605f420babb767 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 15:56:41 -0300 Subject: [PATCH 39/49] FIX PHPMD --- src/Parser/Parser.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index 025616a..078990f 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -34,7 +34,6 @@ public function __construct(Grammar $grammar = null) */ public function parse(array $tokens) : int { - $values = $this->getGrammar()->getValues(); $tokensAvailable = array_flip($this->getGrammar()->getTokens()); $length = count($tokens); From d51948810324d89e1fb8bc3cacb1f009fbc861b2 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 16:26:02 -0300 Subject: [PATCH 40/49] Apply code refactor to center token capture --- src/Parser/Automaton.php | 114 +++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 35 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index f7cfe33..b0eaa93 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -40,6 +40,12 @@ class Automaton */ private $value; + /** + * Tokens + * @type string[] + */ + private $tokens; + /** * Default Constructor * @@ -171,6 +177,50 @@ protected function addTokenValue(string $token, string $modifier = null, int $qu return $this; } + /** + * Set Tokens + * + * @param string[] $tokens Token Values + * @return self Fluent Interface + */ + protected function setTokens(array $tokens) : self + { + $this->tokens = $tokens; + return $this; + } + + /** + * Get Tokens + * + * @return string[] Token Values + */ + public function getTokens() : array + { + return $this->tokens; + } + + /** + * Get Token + * + * @param int $offset Position Offset + * @return string Token + */ + protected function getToken(int $offset = 0) : string + { + return $this->getTokens()[$this->getPosition() + $offset]; + } + + /** + * Has Token? + * + * @param int $offset Position Offset + * @return bool Confirmation + */ + protected function hasToken(int $offset = 0) : bool + { + return $this->getPosition() + $offset < count($this->getTokens()); + } + /** * Reset Counters * @@ -194,22 +244,22 @@ protected function reset() : self */ public function read(array $tokens) : self { - $this->reset(); - array_push($tokens, '$'); + $this + ->setTokens($tokens) + ->reset(); + $length = count($tokens); while ($this->getState() !== self::STATE_Z) { - $token = $tokens[$this->getPosition()]; - switch ($this->getState()) { case self::STATE_G: - if ($token === Grammar::T_N) { + if ($this->getToken() === Grammar::T_N) { $this ->setState(self::STATE_H) ->addPosition(1); - } elseif ($token === Grammar::T_M) { + } elseif ($this->getToken() === Grammar::T_M) { $this ->setState(self::STATE_G) ->addPosition(1) @@ -220,18 +270,18 @@ public function read(array $tokens) : self break; case self::STATE_F: - if ($token === Grammar::T_D) { + if ($this->getToken() === Grammar::T_D) { $this ->setState(self::STATE_E) ->addPosition(1) ->addTokenValue(Grammar::T_D); - } elseif ($token === Grammar::T_C && $this->getPosition() + 1 < $length) { - if ($tokens[$this->getPosition() + 1] === Grammar::T_D) { + } elseif ($this->getToken() === Grammar::T_C && $this->hasToken(1)) { + if ($this->getToken(1) === Grammar::T_D) { $this ->setState(self::STATE_D) ->addPosition(2) ->addTokenValue(Grammar::T_D, Grammar::T_C); - } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_M) { + } elseif ($this->getToken(1) === Grammar::T_M) { $this ->setState(self::STATE_D) ->addPosition(2) @@ -245,11 +295,9 @@ public function read(array $tokens) : self break; case self::STATE_E: - if ($token === Grammar::T_C) { - if ($this->getPosition() + 1 < $length - && $tokens[$this->getPosition() + 1] === Grammar::T_C) { - if ($this->getPosition() + 2 < $length - && $tokens[$this->getPosition() + 2] === Grammar::T_C) { + if ($this->getToken() === Grammar::T_C) { + if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_C) { + if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_C) { $this ->setState(self::STATE_D) ->addPosition(3) @@ -272,18 +320,18 @@ public function read(array $tokens) : self break; case self::STATE_D: - if ($token === Grammar::T_L) { + if ($this->getToken() === Grammar::T_L) { $this ->setState(self::STATE_C) ->addPosition(1) ->addTokenValue(Grammar::T_L); - } elseif ($token === Grammar::T_X && $this->getPosition() + 1 < $length) { - if ($tokens[$this->getPosition() + 1] === Grammar::T_L) { + } elseif ($this->getToken() === Grammar::T_X && $this->hasToken(1)) { + if ($this->getToken(1) === Grammar::T_L) { $this ->setState(self::STATE_B) ->addPosition(2) ->addTokenValue(Grammar::T_L, Grammar::T_X); - } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_C) { + } elseif ($this->getToken(1) === Grammar::T_C) { $this ->setState(self::STATE_B) ->addPosition(2) @@ -297,11 +345,9 @@ public function read(array $tokens) : self break; case self::STATE_C: - if ($token === Grammar::T_X) { - if ($this->getPosition() + 1 < $length - && $tokens[$this->getPosition() + 1] === Grammar::T_X) { - if ($this->getPosition() + 2 < $length - && $tokens[$this->getPosition() + 2] === Grammar::T_X) { + if ($this->getToken() === Grammar::T_X) { + if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_X) { + if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_X) { $this ->setState(self::STATE_B) ->addPosition(3) @@ -324,18 +370,18 @@ public function read(array $tokens) : self break; case self::STATE_B: - if ($token === Grammar::T_V) { + if ($this->getToken() === Grammar::T_V) { $this ->setState(self::STATE_A) ->addPosition(1) ->addTokenValue(Grammar::T_V); - } elseif ($token === Grammar::T_I && $this->getPosition() + 1 < $length) { - if ($tokens[$this->getPosition() + 1] === Grammar::T_V) { + } elseif ($this->getToken() === Grammar::T_I && $this->hasToken(1)) { + if ($this->getToken(1) === Grammar::T_V) { $this ->setState(self::STATE_H) ->addPosition(2) ->addTokenValue(Grammar::T_V, Grammar::T_I); - } elseif ($tokens[$this->getPosition() + 1] === Grammar::T_X) { + } elseif ($this->getToken(1) === Grammar::T_X) { $this ->setState(self::STATE_H) ->addPosition(2) @@ -349,11 +395,9 @@ public function read(array $tokens) : self break; case self::STATE_A: - if ($token === Grammar::T_I) { - if ($this->getPosition() + 1 < $length - && $tokens[$this->getPosition() + 1] === Grammar::T_I) { - if ($this->getPosition() + 2 < $length - && $tokens[$this->getPosition() + 2] === Grammar::T_I) { + if ($this->getToken() === Grammar::T_I) { + if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_I) { + if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_I) { $this ->setState(self::STATE_H) ->addPosition(3) @@ -376,13 +420,13 @@ public function read(array $tokens) : self break; case self::STATE_H: - if ($token === '$') { + if ($this->getToken() === '$') { // done! $this->setState(self::STATE_Z); } else { throw (new Exception('Invalid Roman', Exception::INVALID_ROMAN)) ->setPosition($this->getPosition()) - ->setToken($token); + ->setToken($this->getToken()); } break; } From 573f2744dd5bf0540c318f24893c4d7d23b1f264 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 16:38:38 -0300 Subject: [PATCH 41/49] Divide transitions into methods --- src/Parser/Automaton.php | 374 ++++++++++++++++++++++++--------------- 1 file changed, 228 insertions(+), 146 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index b0eaa93..9a2b37f 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -236,6 +236,227 @@ protected function reset() : self return $this; } + /** + * Do Transition from G + * + * @return self Fluent Interface + */ + private function doTransitionFromG() : self + { + if ($this->getToken() === Grammar::T_N) { + $this + ->setState(self::STATE_H) + ->addPosition(1); + } elseif ($this->getToken() === Grammar::T_M) { + $this + ->setState(self::STATE_G) + ->addPosition(1) + ->addTokenValue(Grammar::T_M); + } else { + $this->setState(self::STATE_F); + } + + return $this; + } + + /** + * Do Transition from F + * + * @return self Fluent Interface + */ + private function doTransitionFromF() : self + { + if ($this->getToken() === Grammar::T_D) { + $this + ->setState(self::STATE_E) + ->addPosition(1) + ->addTokenValue(Grammar::T_D); + } elseif ($this->getToken() === Grammar::T_C && $this->hasToken(1)) { + if ($this->getToken(1) === Grammar::T_D) { + $this + ->setState(self::STATE_D) + ->addPosition(2) + ->addTokenValue(Grammar::T_D, Grammar::T_C); + } elseif ($this->getToken(1) === Grammar::T_M) { + $this + ->setState(self::STATE_D) + ->addPosition(2) + ->addTokenValue(Grammar::T_M, Grammar::T_C); + } else { + $this->setState(self::STATE_E); + } + } else { + $this->setState(self::STATE_E); + } + + return $this; + } + + /** + * Do Transition from E + * + * @return self Fluent Interface + */ + private function doTransitionFromE() : self + { + if ($this->getToken() === Grammar::T_C) { + if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_C) { + if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_C) { + $this + ->setState(self::STATE_D) + ->addPosition(3) + ->addTokenValue(Grammar::T_C, null, 3); + } else { + $this + ->setState(self::STATE_D) + ->addPosition(2) + ->addTokenValue(Grammar::T_C, null, 2); + } + } else { + $this + ->setState(self::STATE_D) + ->addPosition(1) + ->addTokenValue(Grammar::T_C); + } + } else { + $this->setState(self::STATE_D); + } + + return $this; + } + + /** + * Do Transition from D + * + * @return self Fluent Interface + */ + private function doTransitionFromD() : self + { + if ($this->getToken() === Grammar::T_L) { + $this + ->setState(self::STATE_C) + ->addPosition(1) + ->addTokenValue(Grammar::T_L); + } elseif ($this->getToken() === Grammar::T_X && $this->hasToken(1)) { + if ($this->getToken(1) === Grammar::T_L) { + $this + ->setState(self::STATE_B) + ->addPosition(2) + ->addTokenValue(Grammar::T_L, Grammar::T_X); + } elseif ($this->getToken(1) === Grammar::T_C) { + $this + ->setState(self::STATE_B) + ->addPosition(2) + ->addTokenValue(Grammar::T_C, Grammar::T_X); + } else { + $this->setState(self::STATE_C); + } + } else { + $this->setState(self::STATE_C); + } + + return $this; + } + + /** + * Do Transition from C + * + * @return self Fluent Interface + */ + private function doTransitionFromC() : self + { + if ($this->getToken() === Grammar::T_X) { + if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_X) { + if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_X) { + $this + ->setState(self::STATE_B) + ->addPosition(3) + ->addTokenValue(Grammar::T_X, null, 3); + } else { + $this + ->setState(self::STATE_B) + ->addPosition(2) + ->addTokenValue(Grammar::T_X, null, 2); + } + } else { + $this + ->setState(self::STATE_B) + ->addPosition(1) + ->addTokenValue(Grammar::T_X); + } + } else { + $this->setState(self::STATE_B); + } + + return $this; + } + + /** + * Do Transition from B + * + * @return self Fluent Interface + */ + private function doTransitionFromB() : self + { + if ($this->getToken() === Grammar::T_V) { + $this + ->setState(self::STATE_A) + ->addPosition(1) + ->addTokenValue(Grammar::T_V); + } elseif ($this->getToken() === Grammar::T_I && $this->hasToken(1)) { + if ($this->getToken(1) === Grammar::T_V) { + $this + ->setState(self::STATE_H) + ->addPosition(2) + ->addTokenValue(Grammar::T_V, Grammar::T_I); + } elseif ($this->getToken(1) === Grammar::T_X) { + $this + ->setState(self::STATE_H) + ->addPosition(2) + ->addTokenValue(Grammar::T_X, Grammar::T_I); + } else { + $this->setState(self::STATE_A); + } + } else { + $this->setState(self::STATE_A); + } + + return $this; + } + + /** + * Do Transition From A + * + * @return self Fluent Interface + */ + private function doTransitionFromA() : self + { + if ($this->getToken() === Grammar::T_I) { + if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_I) { + if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_I) { + $this + ->setState(self::STATE_H) + ->addPosition(3) + ->addTokenValue(Grammar::T_I, null, 3); + } else { + $this + ->setState(self::STATE_H) + ->addPosition(2) + ->addTokenValue(Grammar::T_I, null, 2); + } + } else { + $this + ->setState(self::STATE_H) + ->addPosition(1) + ->addTokenValue(Grammar::T_I); + } + } else { + $this->setState(self::STATE_H); + } + + return $this; + } + /** * Read * @@ -250,173 +471,34 @@ public function read(array $tokens) : self ->setTokens($tokens) ->reset(); - $length = count($tokens); - while ($this->getState() !== self::STATE_Z) { switch ($this->getState()) { case self::STATE_G: - if ($this->getToken() === Grammar::T_N) { - $this - ->setState(self::STATE_H) - ->addPosition(1); - } elseif ($this->getToken() === Grammar::T_M) { - $this - ->setState(self::STATE_G) - ->addPosition(1) - ->addTokenValue(Grammar::T_M); - } else { - $this->setState(self::STATE_F); - } + $this->doTransitionFromG(); break; case self::STATE_F: - if ($this->getToken() === Grammar::T_D) { - $this - ->setState(self::STATE_E) - ->addPosition(1) - ->addTokenValue(Grammar::T_D); - } elseif ($this->getToken() === Grammar::T_C && $this->hasToken(1)) { - if ($this->getToken(1) === Grammar::T_D) { - $this - ->setState(self::STATE_D) - ->addPosition(2) - ->addTokenValue(Grammar::T_D, Grammar::T_C); - } elseif ($this->getToken(1) === Grammar::T_M) { - $this - ->setState(self::STATE_D) - ->addPosition(2) - ->addTokenValue(Grammar::T_M, Grammar::T_C); - } else { - $this->setState(self::STATE_E); - } - } else { - $this->setState(self::STATE_E); - } + $this->doTransitionFromF(); break; case self::STATE_E: - if ($this->getToken() === Grammar::T_C) { - if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_C) { - if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_C) { - $this - ->setState(self::STATE_D) - ->addPosition(3) - ->addTokenValue(Grammar::T_C, null, 3); - } else { - $this - ->setState(self::STATE_D) - ->addPosition(2) - ->addTokenValue(Grammar::T_C, null, 2); - } - } else { - $this - ->setState(self::STATE_D) - ->addPosition(1) - ->addTokenValue(Grammar::T_C); - } - } else { - $this->setState(self::STATE_D); - } + $this->doTransitionFromE(); break; case self::STATE_D: - if ($this->getToken() === Grammar::T_L) { - $this - ->setState(self::STATE_C) - ->addPosition(1) - ->addTokenValue(Grammar::T_L); - } elseif ($this->getToken() === Grammar::T_X && $this->hasToken(1)) { - if ($this->getToken(1) === Grammar::T_L) { - $this - ->setState(self::STATE_B) - ->addPosition(2) - ->addTokenValue(Grammar::T_L, Grammar::T_X); - } elseif ($this->getToken(1) === Grammar::T_C) { - $this - ->setState(self::STATE_B) - ->addPosition(2) - ->addTokenValue(Grammar::T_C, Grammar::T_X); - } else { - $this->setState(self::STATE_C); - } - } else { - $this->setState(self::STATE_C); - } + $this->doTransitionFromD(); break; case self::STATE_C: - if ($this->getToken() === Grammar::T_X) { - if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_X) { - if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_X) { - $this - ->setState(self::STATE_B) - ->addPosition(3) - ->addTokenValue(Grammar::T_X, null, 3); - } else { - $this - ->setState(self::STATE_B) - ->addPosition(2) - ->addTokenValue(Grammar::T_X, null, 2); - } - } else { - $this - ->setState(self::STATE_B) - ->addPosition(1) - ->addTokenValue(Grammar::T_X); - } - } else { - $this->setState(self::STATE_B); - } + $this->doTransitionFromC(); break; case self::STATE_B: - if ($this->getToken() === Grammar::T_V) { - $this - ->setState(self::STATE_A) - ->addPosition(1) - ->addTokenValue(Grammar::T_V); - } elseif ($this->getToken() === Grammar::T_I && $this->hasToken(1)) { - if ($this->getToken(1) === Grammar::T_V) { - $this - ->setState(self::STATE_H) - ->addPosition(2) - ->addTokenValue(Grammar::T_V, Grammar::T_I); - } elseif ($this->getToken(1) === Grammar::T_X) { - $this - ->setState(self::STATE_H) - ->addPosition(2) - ->addTokenValue(Grammar::T_X, Grammar::T_I); - } else { - $this->setState(self::STATE_A); - } - } else { - $this->setState(self::STATE_A); - } + $this->doTransitionFromB(); break; case self::STATE_A: - if ($this->getToken() === Grammar::T_I) { - if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_I) { - if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_I) { - $this - ->setState(self::STATE_H) - ->addPosition(3) - ->addTokenValue(Grammar::T_I, null, 3); - } else { - $this - ->setState(self::STATE_H) - ->addPosition(2) - ->addTokenValue(Grammar::T_I, null, 2); - } - } else { - $this - ->setState(self::STATE_H) - ->addPosition(1) - ->addTokenValue(Grammar::T_I); - } - } else { - $this->setState(self::STATE_H); - } + $this->doTransitionFromA(); break; case self::STATE_H: From 127011e85de9f390938d533c63eee039b2b81791 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 16:47:59 -0300 Subject: [PATCH 42/49] Fix PHPMD --- src/Parser/Automaton.php | 280 ++++++++++++++++++++++----------------- 1 file changed, 159 insertions(+), 121 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 9a2b37f..7b1b399 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -236,6 +236,24 @@ protected function reset() : self return $this; } + /** + * Do Transition from H + * + * @return self Fluent Interface + */ + private function doTransitionFromH() : self + { + if ($this->getToken() !== '$') { + throw (new Exception('Invalid Roman', Exception::INVALID_ROMAN)) + ->setPosition($this->getPosition()) + ->setToken($this->getToken()); + } + + // done! + $this->setState(self::STATE_Z); + return $this; + } + /** * Do Transition from G * @@ -247,15 +265,18 @@ private function doTransitionFromG() : self $this ->setState(self::STATE_H) ->addPosition(1); - } elseif ($this->getToken() === Grammar::T_M) { + return $this; + } + + if ($this->getToken() === Grammar::T_M) { $this ->setState(self::STATE_G) ->addPosition(1) ->addTokenValue(Grammar::T_M); - } else { - $this->setState(self::STATE_F); + return $this; } + $this->setState(self::STATE_F); return $this; } @@ -271,24 +292,26 @@ private function doTransitionFromF() : self ->setState(self::STATE_E) ->addPosition(1) ->addTokenValue(Grammar::T_D); - } elseif ($this->getToken() === Grammar::T_C && $this->hasToken(1)) { - if ($this->getToken(1) === Grammar::T_D) { - $this - ->setState(self::STATE_D) - ->addPosition(2) - ->addTokenValue(Grammar::T_D, Grammar::T_C); - } elseif ($this->getToken(1) === Grammar::T_M) { - $this - ->setState(self::STATE_D) - ->addPosition(2) - ->addTokenValue(Grammar::T_M, Grammar::T_C); - } else { - $this->setState(self::STATE_E); - } - } else { - $this->setState(self::STATE_E); + return $this; + } + + if ($this->getToken() === Grammar::T_C && $this->hasToken(1) && $this->getToken(1) === Grammar::T_D) { + $this + ->setState(self::STATE_D) + ->addPosition(2) + ->addTokenValue(Grammar::T_D, Grammar::T_C); + return $this; } + if ($this->getToken() === Grammar::T_C && $this->hasToken(1) && $this->getToken(1) === Grammar::T_M) { + $this + ->setState(self::STATE_D) + ->addPosition(2) + ->addTokenValue(Grammar::T_M, Grammar::T_C); + return $this; + } + + $this->setState(self::STATE_E); return $this; } @@ -306,22 +329,24 @@ private function doTransitionFromE() : self ->setState(self::STATE_D) ->addPosition(3) ->addTokenValue(Grammar::T_C, null, 3); - } else { - $this - ->setState(self::STATE_D) - ->addPosition(2) - ->addTokenValue(Grammar::T_C, null, 2); + return $this; } - } else { + $this ->setState(self::STATE_D) - ->addPosition(1) - ->addTokenValue(Grammar::T_C); + ->addPosition(2) + ->addTokenValue(Grammar::T_C, null, 2); + return $this; } - } else { - $this->setState(self::STATE_D); + + $this + ->setState(self::STATE_D) + ->addPosition(1) + ->addTokenValue(Grammar::T_C); + return $this; } + $this->setState(self::STATE_D); return $this; } @@ -337,24 +362,26 @@ private function doTransitionFromD() : self ->setState(self::STATE_C) ->addPosition(1) ->addTokenValue(Grammar::T_L); - } elseif ($this->getToken() === Grammar::T_X && $this->hasToken(1)) { - if ($this->getToken(1) === Grammar::T_L) { - $this - ->setState(self::STATE_B) - ->addPosition(2) - ->addTokenValue(Grammar::T_L, Grammar::T_X); - } elseif ($this->getToken(1) === Grammar::T_C) { - $this - ->setState(self::STATE_B) - ->addPosition(2) - ->addTokenValue(Grammar::T_C, Grammar::T_X); - } else { - $this->setState(self::STATE_C); - } - } else { - $this->setState(self::STATE_C); + return $this; + } + + if ($this->getToken() === Grammar::T_X && $this->hasToken(1) && $this->getToken(1) === Grammar::T_L) { + $this + ->setState(self::STATE_B) + ->addPosition(2) + ->addTokenValue(Grammar::T_L, Grammar::T_X); + return $this; + } + + if ($this->getToken() === Grammar::T_X && $this->hasToken(1) && $this->getToken(1) === Grammar::T_C) { + $this + ->setState(self::STATE_B) + ->addPosition(2) + ->addTokenValue(Grammar::T_C, Grammar::T_X); + return $this; } + $this->setState(self::STATE_C); return $this; } @@ -372,22 +399,24 @@ private function doTransitionFromC() : self ->setState(self::STATE_B) ->addPosition(3) ->addTokenValue(Grammar::T_X, null, 3); - } else { - $this - ->setState(self::STATE_B) - ->addPosition(2) - ->addTokenValue(Grammar::T_X, null, 2); + return $this; } - } else { + $this ->setState(self::STATE_B) - ->addPosition(1) - ->addTokenValue(Grammar::T_X); + ->addPosition(2) + ->addTokenValue(Grammar::T_X, null, 2); + return $this; } - } else { - $this->setState(self::STATE_B); + + $this + ->setState(self::STATE_B) + ->addPosition(1) + ->addTokenValue(Grammar::T_X); + return $this; } + $this->setState(self::STATE_B); return $this; } @@ -403,24 +432,26 @@ private function doTransitionFromB() : self ->setState(self::STATE_A) ->addPosition(1) ->addTokenValue(Grammar::T_V); - } elseif ($this->getToken() === Grammar::T_I && $this->hasToken(1)) { - if ($this->getToken(1) === Grammar::T_V) { - $this - ->setState(self::STATE_H) - ->addPosition(2) - ->addTokenValue(Grammar::T_V, Grammar::T_I); - } elseif ($this->getToken(1) === Grammar::T_X) { - $this - ->setState(self::STATE_H) - ->addPosition(2) - ->addTokenValue(Grammar::T_X, Grammar::T_I); - } else { - $this->setState(self::STATE_A); - } - } else { - $this->setState(self::STATE_A); + return $this; } + if ($this->getToken() === Grammar::T_I && $this->hasToken(1) && $this->getToken(1) === Grammar::T_V) { + $this + ->setState(self::STATE_H) + ->addPosition(2) + ->addTokenValue(Grammar::T_V, Grammar::T_I); + return $this; + } + + if ($this->getToken() === Grammar::T_I && $this->hasToken(1) && $this->getToken(1) === Grammar::T_X) { + $this + ->setState(self::STATE_H) + ->addPosition(2) + ->addTokenValue(Grammar::T_X, Grammar::T_I); + return $this; + } + + $this->setState(self::STATE_A); return $this; } @@ -438,20 +469,66 @@ private function doTransitionFromA() : self ->setState(self::STATE_H) ->addPosition(3) ->addTokenValue(Grammar::T_I, null, 3); - } else { - $this - ->setState(self::STATE_H) - ->addPosition(2) - ->addTokenValue(Grammar::T_I, null, 2); + return $this; } - } else { + $this ->setState(self::STATE_H) - ->addPosition(1) - ->addTokenValue(Grammar::T_I); + ->addPosition(2) + ->addTokenValue(Grammar::T_I, null, 2); + return $this; } - } else { - $this->setState(self::STATE_H); + + $this + ->setState(self::STATE_H) + ->addPosition(1) + ->addTokenValue(Grammar::T_I); + return $this; + } + + $this->setState(self::STATE_H); + return $this; + } + + /** + * Do Transition + * + * @return self Fluent Interface + */ + private function doTransition() : self + { + switch ($this->getState()) { + case self::STATE_G: + $this->doTransitionFromG(); + break; + + case self::STATE_F: + $this->doTransitionFromF(); + break; + + case self::STATE_E: + $this->doTransitionFromE(); + break; + + case self::STATE_D: + $this->doTransitionFromD(); + break; + + case self::STATE_C: + $this->doTransitionFromC(); + break; + + case self::STATE_B: + $this->doTransitionFromB(); + break; + + case self::STATE_A: + $this->doTransitionFromA(); + break; + + case self::STATE_H: + $this->doTransitionFromH(); + break; } return $this; @@ -472,46 +549,7 @@ public function read(array $tokens) : self ->reset(); while ($this->getState() !== self::STATE_Z) { - switch ($this->getState()) { - case self::STATE_G: - $this->doTransitionFromG(); - break; - - case self::STATE_F: - $this->doTransitionFromF(); - break; - - case self::STATE_E: - $this->doTransitionFromE(); - break; - - case self::STATE_D: - $this->doTransitionFromD(); - break; - - case self::STATE_C: - $this->doTransitionFromC(); - break; - - case self::STATE_B: - $this->doTransitionFromB(); - break; - - case self::STATE_A: - $this->doTransitionFromA(); - break; - - case self::STATE_H: - if ($this->getToken() === '$') { - // done! - $this->setState(self::STATE_Z); - } else { - throw (new Exception('Invalid Roman', Exception::INVALID_ROMAN)) - ->setPosition($this->getPosition()) - ->setToken($this->getToken()); - } - break; - } + $this->doTransition(); } return $this; From 3e399f609d1e9b3b0c8e9f82eeaa976137365cfa Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 16:57:02 -0300 Subject: [PATCH 43/49] Suppress warnings for complexity This suppress warning configuration was added because PHPMD count conditionals and show a message after 50 indications. But, this class must use conditionals because it's a DFA (deterministic finite automaton), working with transitional states to check if some rules are satisfied. --- src/Parser/Automaton.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 7b1b399..92dc2eb 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -7,6 +7,8 @@ /** * Automaton + * + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class Automaton { From a6ef47293d2c81d93931aa544d1ed18309f36640 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 17:05:55 -0300 Subject: [PATCH 44/49] Remove disabled Grammar constants from docs --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2500748..9a8b8b5 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,13 @@ $tokens = $lexer->tokenize('MCMXCIX'); /* $tokens = [ - 0 => 'M', // Grammar::T_M - 1 => 'CM', // Grammar::T_CM - 2 => 'XC', // Grammar::T_XC - 3 => 'IX', // Grammar::T_IX + 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 ]; */ From 4cbd5d897a7097ab4dddc19b9bd36083078e292a Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 17:09:09 -0300 Subject: [PATCH 45/49] Include DFA into docs --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a8b8b5..595789d 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ stable version. ## Advanced Usage -The `Romans` package uses a Lexer-Parser approach to convert Roman number to -Integer, using a Grammar Token library. +The `Romans` package uses a Lexer-Parser approach and a Deterministic Finite +Automaton (DFA) to convert Roman number to Integer, using a Grammar Token +library. ```php use Romans\Grammar\Grammar; @@ -132,6 +133,7 @@ $result = $filter->filter(0); // N * Rapid Tables: [How to Convert Roman Numerals to Numbers](http://www.rapidtables.com/convert/number/how-roman-numerals-to-number.htm) * Wikipedia: [Zero in Roman Numerals](https://en.wikipedia.org/wiki/Roman_numerals#Zero) +* Wikipedia: [Deterministic Finite Automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton) ## License From a9abdef4ee1a5efa55f75594f0aba1ac2e435915 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 17:17:12 -0300 Subject: [PATCH 46/49] Include DFA definition into docs --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 595789d..e0d4bf2 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,31 @@ $filter = new IntToRoman(); $result = $filter->filter(0); // N ``` +### Deterministic Finite Automaton (DFA) + +A DFA was developed to check if a string with Roman number is valid. This +technique was choiced because some implementations simply convert the `$input` +without checking some rules, like four chars sequentially. + +The current automaton definition is declared below. + +```plain +M = (Q, Σ, δ, q0, F) +Q = { z, a, b, c, d, e, f, g, h } +Σ = { I, V, X, L, C, D, M, N } +q0 = h +F = { z } + +z -> $h +a -> z | Iz | IIz | IIIz +b -> a | IVz | Va | IXz +c -> b | Xb | XXb | XXXb +d -> c | XLb | Lc | XCb +e -> d | Cd | CCd | CCCd +f -> e | CDd | De | CMd +g -> f | Nz | Mg +``` + ## References * Rapid Tables: [How to Convert Roman Numerals to Numbers](http://www.rapidtables.com/convert/number/how-roman-numerals-to-number.htm) From 1e8979b3183fb7d8fe36af89e85edfd1958f1970 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 17:20:00 -0300 Subject: [PATCH 47/49] Change state name before final state Z --- src/Parser/Automaton.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Parser/Automaton.php b/src/Parser/Automaton.php index 92dc2eb..d798001 100644 --- a/src/Parser/Automaton.php +++ b/src/Parser/Automaton.php @@ -14,7 +14,6 @@ class Automaton { use GrammarAwareTrait; - const STATE_Z = 'Z'; const STATE_A = 'A'; const STATE_B = 'B'; const STATE_C = 'C'; @@ -22,7 +21,8 @@ class Automaton const STATE_E = 'E'; const STATE_F = 'F'; const STATE_G = 'G'; - const STATE_H = 'H'; + const STATE_Y = 'Y'; + const STATE_Z = 'Z'; /** * State @@ -265,7 +265,7 @@ private function doTransitionFromG() : self { if ($this->getToken() === Grammar::T_N) { $this - ->setState(self::STATE_H) + ->setState(self::STATE_Y) ->addPosition(1); return $this; } @@ -439,7 +439,7 @@ private function doTransitionFromB() : self if ($this->getToken() === Grammar::T_I && $this->hasToken(1) && $this->getToken(1) === Grammar::T_V) { $this - ->setState(self::STATE_H) + ->setState(self::STATE_Y) ->addPosition(2) ->addTokenValue(Grammar::T_V, Grammar::T_I); return $this; @@ -447,7 +447,7 @@ private function doTransitionFromB() : self if ($this->getToken() === Grammar::T_I && $this->hasToken(1) && $this->getToken(1) === Grammar::T_X) { $this - ->setState(self::STATE_H) + ->setState(self::STATE_Y) ->addPosition(2) ->addTokenValue(Grammar::T_X, Grammar::T_I); return $this; @@ -468,27 +468,27 @@ private function doTransitionFromA() : self if ($this->hasToken(1) && $this->getToken(1) === Grammar::T_I) { if ($this->hasToken(2) && $this->getToken(2) === Grammar::T_I) { $this - ->setState(self::STATE_H) + ->setState(self::STATE_Y) ->addPosition(3) ->addTokenValue(Grammar::T_I, null, 3); return $this; } $this - ->setState(self::STATE_H) + ->setState(self::STATE_Y) ->addPosition(2) ->addTokenValue(Grammar::T_I, null, 2); return $this; } $this - ->setState(self::STATE_H) + ->setState(self::STATE_Y) ->addPosition(1) ->addTokenValue(Grammar::T_I); return $this; } - $this->setState(self::STATE_H); + $this->setState(self::STATE_Y); return $this; } @@ -528,7 +528,7 @@ private function doTransition() : self $this->doTransitionFromA(); break; - case self::STATE_H: + case self::STATE_Y: $this->doTransitionFromH(); break; } From da90dc9e81361de65371655c33d38207235f0675 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 17:23:45 -0300 Subject: [PATCH 48/49] Update docs with empty string symbol --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e0d4bf2..767316e 100644 --- a/README.md +++ b/README.md @@ -139,14 +139,15 @@ The current automaton definition is declared below. ```plain M = (Q, Σ, δ, q0, F) -Q = { z, a, b, c, d, e, f, g, h } +Q = { a, b, c, d, e, f, g, y, z } Σ = { I, V, X, L, C, D, M, N } -q0 = h +q0 = g F = { z } -z -> $h -a -> z | Iz | IIz | IIIz -b -> a | IVz | Va | IXz +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 From d545e769f2cdde6bc3490c9482c763b7a5b59fa0 Mon Sep 17 00:00:00 2001 From: Wanderson Date: Tue, 18 Apr 2017 17:28:40 -0300 Subject: [PATCH 49/49] Include a new section to describe techniques --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 767316e..48021e5 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,11 @@ $filter = new IntToRoman(); $result = $filter->filter(0); // N ``` +## Techniques + +This section describes some techniques this package uses to convert Roman +numbers into integer and vice-versa. + ### Deterministic Finite Automaton (DFA) A DFA was developed to check if a string with Roman number is valid. This