Passed
Push — master ( 2808aa...a99b5d )
by Jesse
01:39 queued 14s
created

Parser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 2
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
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
		for(;;) {
31 10
			$next = $lexer->next();
32 10
			if( $next->isEof() ) {
33 10
				break;
34
			}
35
36 10
			if( $next->getString() === '%' ) {
37 10
				if( $lexer->hasPrefix('%') ) {
38 2
					$this->emitter->emit(
39 2
						new Lexeme(Lexeme::T_LITERAL_STRING, '%', $lexer->pos())
40
					);
41 2
					$lexer->next();
42
43 2
					continue;
44
				}
45
46 10
				$this->lexSprintf($this->emitter, $lexer);
47
48 10
				continue;
49
			}
50
51 9
			$lexer->rewind();
52 10
			$this->lexString($this->emitter, $lexer);
53
		}
54 10
	}
55
56 9
	private function lexString( Emitter $emitter, StringLexer $lexer ) : void {
57 9
		$pos    = $lexer->pos();
58 9
		$buffer = '';
59 9
		for(;;) {
60 9
			$next = $lexer->next();
61 9
			if( $next->isEof() ) {
62 5
				break;
63
			}
64
65 9
			if( $next->getString() === '%' ) {
66 9
				$lexer->rewind();
67 9
				break;
68
			}
69
70 9
			$buffer .= $next->getString();
71
		}
72
73 9
		$emitter->emit(
74 9
			new Lexeme(Lexeme::T_LITERAL_STRING, $buffer, $pos)
75
		);
76 9
	}
77
78 10
	private function lexSprintf( Emitter $emitter, StringLexer $lexer ) : void {
79 10
		$pos  = $lexer->pos();
80 10
		$next = $lexer->next();
81
82 10
		$arg           = null;
83 10
		$showPositive  = false;
84 10
		$padChar       = null;
85 10
		$padWidth      = null;
86 10
		$leftJustified = false;
87 10
		$precision     = null;
88
89 10
		if( $next->getString() !== '0' && ctype_digit($next->getString()) ) {
90 4
			$lexer->rewind();
91 4
			$int = $this->eatInt($lexer);
92 4
			if( $lexer->hasPrefix('$') ) {
93 4
				$lexer->next();
94 4
				$next = $lexer->next();
95 4
				$arg  = $int;
96
			}
97
		}
98
99
		// flag parsing
100 10
		for(;;) {
101 10
			switch( $next->getString() ) {
102 10
				case '0':
103 1
					$padChar = '0';
104 1
					$next    = $lexer->next();
105
106 1
					continue 2;
107 10
				case ' ':
108 1
					$padChar = ' ';
109 1
					$next    = $lexer->next();
110
111 1
					continue 2;
112 10
				case "'":
113 5
					$next    = $lexer->next();
114 5
					$padChar = $next->getString();
115 5
					$next    = $lexer->next();
116
117 5
					continue 2;
118
			}
119
120 10
			if( $next->getString() === '-' ) {
121 3
				$leftJustified = true;
122 3
				$next          = $lexer->next();
123
124 3
				continue;
125
			}
126
127 10
			if( $next->getString() === '+' ) {
128 2
				$showPositive = true;
129 2
				$next         = $lexer->next();
130
131 2
				continue;
132
			}
133
134 10
			break;
135
		}
136
137 10
		if( $padChar !== null ) {
138 5
			$lexer->rewind();
139 5
			$peek = $lexer->peek();
140 5
			if( ctype_digit($peek->getString()) ) {
141 5
				$padWidth = $this->eatInt($lexer);
142
			}
143
144 5
			$next = $lexer->next();
145
		}
146
147 10
		if( $next->getString() === '.' ) {
148 1
			if( ctype_digit($lexer->peek()->getString()) ) {
149 1
				$precision = $this->eatInt($lexer);
150
			}
151
152 1
			$next = $lexer->next();
153
		}
154
155 10
		$tType = Lexeme::T_INVALID;
156 10
		if( in_array($next->getString(), ArgumentLexeme::VALID_T_TYPES, true) ) {
157 8
			$tType = $next->getString();
158
		}
159
160 10
		$content = $lexer->substr($pos, $lexer->pos() - $pos);
161
162 10
		$emitter->emit(
163 10
			new ArgumentLexeme(
164 10
				$tType,
165 10
				$content,
166 10
				$pos,
167 10
				$arg,
168 10
				$showPositive,
169 10
				$padChar,
170 10
				$padWidth,
171 10
				$leftJustified,
172 10
				$precision
173
			)
174
		);
175 10
	}
176
177 6
	private function eatInt( StringLexer $lexer ) : ?int {
178 6
		$int = '';
179 6
		for(;;) {
180 6
			$next = $lexer->next();
181 6
			if( $next->isEof() ) {
182
				break;
183
			}
184
185 6
			if( !ctype_digit($next->getString()) ) {
186 6
				$lexer->rewind();
187 6
				break;
188
			}
189
190 6
			$int .= $next->getString();
191
		}
192
193 6
		if( $int ) {
194 6
			return (int)$int;
195
		}
196
197
		return null;
198
	}
199
200
}
201