Passed
Push — master ( 346253...835c81 )
by Jesse
03:13
created

Parser   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Test Coverage

Coverage 99.04%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 30
eloc 97
c 9
b 0
f 0
dl 0
loc 173
ccs 103
cts 104
cp 0.9904
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A lexString() 0 14 4
A parseStr() 0 21 5
D lexSprintf() 0 95 15
A eatInt() 0 16 5
1
<?php
2
3
namespace donatj\Printf;
4
5
/**
6
 * Parser implements a PHP Printf compatible Printf string parser.
7
 *
8
 * @see https://www.php.net/manual/en/function.printf.php
9
 */
10
class Parser {
11
12
	/** @var Emitter */
13
	private $emitter;
14
15
	/**
16
	 * Parser constructor.
17
	 *
18
	 * @param \donatj\Printf\Emitter $emitter The given Emitter to emit Lexemes as parsed
19
	 */
20 10
	public function __construct( Emitter $emitter ) {
21 10
		$this->emitter = $emitter;
22 10
	}
23
24
	/**
25
	 * Parses a printf string and emit parsed lexemes to the configured Emitter
26
	 */
27 10
	public function parseStr( string $string ) : void {
28 10
		$lexer = new StringLexer($string);
29
30 10
		while( ($next = $lexer->next()) && !$next->isEof() ) {
31 10
			if( $next->getString() === '%' ) {
32 10
				if( $lexer->hasPrefix('%') ) {
33 2
					$this->emitter->emit(
34 2
						new Lexeme(Lexeme::T_LITERAL_STRING, '%', $lexer->pos())
35
					);
36 2
					$lexer->next();
37
38 2
					continue;
39
				}
40
41 10
				$this->lexSprintf($this->emitter, $lexer);
42
43 10
				continue;
44
			}
45
46 9
			$lexer->rewind();
47 9
			$this->lexString($this->emitter, $lexer);
48
		}
49 10
	}
50
51 9
	private function lexString( Emitter $emitter, StringLexer $lexer ) : void {
52 9
		$pos    = $lexer->pos();
53 9
		$buffer = '';
54 9
		while( ($next = $lexer->next()) && !$next->isEof() ) {
55 9
			if( $next->getString() === '%' ) {
56 9
				$lexer->rewind();
57 9
				break;
58
			}
59
60 9
			$buffer .= $next->getString();
61
		}
62
63 9
		$emitter->emit(
64 9
			new Lexeme(Lexeme::T_LITERAL_STRING, $buffer, $pos)
65
		);
66 9
	}
67
68 10
	private function lexSprintf( Emitter $emitter, StringLexer $lexer ) : void {
69 10
		$pos  = $lexer->pos();
70 10
		$next = $lexer->next();
71
72 10
		$arg           = null;
73 10
		$showPositive  = false;
74 10
		$padChar       = null;
75 10
		$padWidth      = null;
76 10
		$leftJustified = false;
77 10
		$precision     = null;
78
79 10
		if( $next->getString() !== '0' && ctype_digit($next->getString()) ) {
80 4
			$lexer->rewind();
81 4
			$int = $this->eatInt($lexer);
82 4
			if( $lexer->hasPrefix('$') ) {
83 4
				$lexer->next();
84 4
				$next = $lexer->next();
85 4
				$arg  = $int;
86
			}
87
		}
88
89
		// flag parsing
90 10
		for(;;) {
91 10
			switch( $next->getString() ) {
92 10
				case '0':
93 1
					$padChar = '0';
94 1
					$next    = $lexer->next();
95
96 1
					continue 2;
97 10
				case ' ':
98 1
					$padChar = ' ';
99 1
					$next    = $lexer->next();
100
101 1
					continue 2;
102 10
				case "'":
103 5
					$next    = $lexer->next();
104 5
					$padChar = $next->getString();
105 5
					$next    = $lexer->next();
106
107 5
					continue 2;
108
			}
109
110 10
			if( $next->getString() === '-' ) {
111 3
				$leftJustified = true;
112 3
				$next          = $lexer->next();
113
114 3
				continue;
115
			}
116
117 10
			if( $next->getString() === '+' ) {
118 2
				$showPositive = true;
119 2
				$next         = $lexer->next();
120
121 2
				continue;
122
			}
123
124 10
			break;
125
		}
126
127 10
		if( $padChar !== null ) {
128 5
			$lexer->rewind();
129 5
			$peek = $lexer->peek();
130 5
			if( ctype_digit($peek->getString()) ) {
131 5
				$padWidth = $this->eatInt($lexer);
132
			}
133
134 5
			$next = $lexer->next();
135
		}
136
137 10
		if( $next->getString() === '.' ) {
138 1
			if( ctype_digit($lexer->peek()->getString()) ) {
139 1
				$precision = $this->eatInt($lexer);
140
			}
141
142 1
			$next = $lexer->next();
143
		}
144
145 10
		$tType = Lexeme::T_INVALID;
146 10
		if( in_array($next->getString(), ArgumentLexeme::VALID_T_TYPES, true) ) {
147 8
			$tType = $next->getString();
148
		}
149
150 10
		$content = $lexer->substr($pos, $lexer->pos() - $pos);
151
152 10
		$emitter->emit(
153 10
			new ArgumentLexeme(
154 10
				$tType,
155 10
				$content,
156 10
				$pos,
157 10
				$arg,
158 10
				$showPositive,
159 10
				$padChar,
160 10
				$padWidth,
161 10
				$leftJustified,
162 10
				$precision
163
			)
164
		);
165 10
	}
166
167 6
	private function eatInt( StringLexer $lexer ) : ?int {
168 6
		$int = '';
169 6
		while( ($next = $lexer->next()) && !$next->isEof() ) {
170 6
			if( !ctype_digit($next->getString()) ) {
171 6
				$lexer->rewind();
172 6
				break;
173
			}
174
175 6
			$int .= $next->getString();
176
		}
177
178 6
		if( $int ) {
179 6
			return (int)$int;
180
		}
181
182
		return null;
183
	}
184
185
}
186