Parser::lexSprintf()   D
last analyzed

Complexity

Conditions 15
Paths 216

Size

Total Lines 95
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 64
CRAP Score 15

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 64
c 5
b 0
f 0
dl 0
loc 95
ccs 64
cts 64
cp 1
rs 4.8833
cc 15
nc 216
nop 2
crap 15

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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