Completed
Push — master ( e97281...f3b478 )
by Tom
03:18
created

Value   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 4
Bugs 1 Features 1
Metric Value
wmc 34
c 4
b 1
f 1
lcom 1
cbo 5
dl 0
loc 164
rs 9.2

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A parse() 0 6 1
A parseTokens() 0 15 4
A processComparator() 0 8 3
A processDot() 0 15 3
A hasFunction() 0 3 2
A processSquareBracket() 0 11 3
A processSeparator() 0 3 1
A processScalar() 0 3 1
A processString() 0 3 1
A processBrackets() 0 9 3
A processNested() 0 6 1
A callTransphpormFunctions() 0 10 4
A processLast() 0 11 3
A processLastUnexpected() 0 9 3
1
<?php
2
/* @description     Transformation Style Sheets - Revolutionising PHP templating    *
3
 * @author          Tom Butler [email protected]                                             *
4
 * @copyright       2015 Tom Butler <[email protected]> | https://r.je/                      *
5
 * @license         http://www.opensource.org/licenses/bsd-license.php  BSD License *
6
 * @version         1.0                                                             */
7
namespace Transphporm\Parser;
8
/** Parses "string" and function(args) e.g. data(foo) or iteration(bar) */
9
class Value {
10
	private $baseData;
11
	private $autoLookup;
12
	/*
13
		Stores the last value e.g.
14
			"a" + "b"
15
		Will store "a" before reading the token for the + and perfoming the concatenate operation
16
	*/
17
	private $last;
18
	private $data;
19
	private $result;
20
	private $traversing = false;
21
22
	private $tokenFuncs = [
23
			Tokenizer::NOT => 'processComparator',
24
			Tokenizer::EQUALS => 'processComparator',
25
			Tokenizer::DOT => 'processDot',
26
			Tokenizer::OPEN_SQUARE_BRACKET => 'processSquareBracket',
27
			Tokenizer::ARG => 'processSeparator',
28
			Tokenizer::CONCAT => 'processSeparator',
29
			Tokenizer::SUBTRACT => 'processSeparator',
30
			Tokenizer::MULTIPLY => 'processSeparator',
31
			Tokenizer::DIVIDE => 'processSeparator',
32
			Tokenizer::NAME => 'processScalar',
33
			Tokenizer::NUMERIC => 'processString',
34
			Tokenizer::BOOL => 'processString',
35
			Tokenizer::STRING => 'processString',
36
			Tokenizer::OPEN_BRACKET => 'processBrackets'
37
	];
38
39
	public function __construct($data, $autoLookup = false) {
40
		$this->baseData = $data;
41
		$this->autoLookup = $autoLookup;
42
	}
43
44
	public function parse($str) {
45
		$tokenizer = new Tokenizer($str);
46
		$tokens = $tokenizer->getTokens();
47
		$this->result = $this->parseTokens($tokens, $this->baseData);
48
		return $this->result;
49
	}
50
51
	public function parseTokens($tokens, $data = null) {
52
		$this->result = new ValueResult;
53
		$this->data = new ValueData($data ? $data : $this->baseData);
54
		$this->last = null;
55
		$this->traversing = false;
56
57
		if (count($tokens) <= 0) return [$data];
58
59
		foreach (new TokenFilterIterator($tokens, [Tokenizer::WHITESPACE, Tokenizer::NEW_LINE]) as $token) {
60
			$this->{$this->tokenFuncs[$token['type']]}($token);
61
		}
62
63
		$this->processLast();
64
		return $this->result->getResult();
65
	}
66
67
	private function processComparator($token) {
68
		$this->processLast();
69
70
		if (!(in_array($this->result->getMode(), array_keys($this->tokenFuncs, 'processComparator')) && $token['type'] == Tokenizer::EQUALS)) {
71
			$this->result->setMode($token['type']);
72
			$this->last = null;
73
		}
74
	}
75
76
	//Reads the last selected value from $data regardless if it's an array or object and overrides $this->data with the new value
77
	//Dot moves $data to the next object in $data foo.bar moves the $data pointer from `foo` to `bar`
78
	private function processDot($token) {
0 ignored issues
show
Unused Code introduced by
The parameter $token is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
79
		$lastResult = $this->data->traverse($this->last, $this->result);
80
81
		//When . is not preceeded by anything, treat it as part of the string instead of an operator
82
		// foo.bar is treated as looking up `bar` in `foo` whereas .foo is treated as the string ".foo"
83
		if ($lastResult) {
84
			$this->traversing = true;
85
		}
86
		else if ($this->last === null)  {
87
			$this->processString(['value' => '.']);
88
			$this->result->setMode(Tokenizer::CONCAT);
89
		}
90
91
		$this->last = null;
92
	}
93
94
	private function hasFunction($name) {
95
		return $this->baseData instanceof \Transphporm\Functionset && $this->baseData->hasFunction($name);
96
	}
97
98
	private function processSquareBracket($token) {
99
		$parser = new Value($this->baseData, $this->autoLookup);
100
		if ($this->hasFunction($this->last)) {
101
			$this->callTransphpormFunctions($token);
102
		}
103
		else {
104
			$this->data->traverse($this->last, $this->result);
105
			$this->last = $parser->parseTokens($token['value'], null)[0];
106
			if (!is_bool($this->last)) $this->traversing = true;
107
		}
108
	}
109
110
	private function processSeparator($token) {
111
		$this->result->setMode($token['type']);
112
	}
113
114
	private function processScalar($token) {
115
		$this->last = $token['value'];
116
	}
117
118
	private function processString($token) {
119
		$this->result->processValue($token['value']);
120
	}
121
122
	private function processBrackets($token) {
123
		if ($this->hasFunction($this->last)
124
			&& !$this->data->methodExists($this->last)) {
125
			$this->callTransphpormFunctions($token);
126
		}
127
		else {
128
			$this->processNested($token);
129
		}
130
	}
131
132
	private function processNested($token) {
133
		$parser = new Value($this->baseData, $this->autoLookup);
134
		$funcResult = $this->data->parseNested($parser, $token, $this->last);
135
		$this->result->processValue($funcResult);
136
		$this->last = null;
137
	}
138
139
	private function callTransphpormFunctions($token) {
140
		$this->result->processValue($this->baseData->{$this->last}($token['value']));
141
		foreach ($this->result->getResult() as $i => $value) {
142
			if (is_scalar($value)) {
143
				$val = $this->data->read($value);
144
				if ($val) $this->result[$i] = $val;
145
			}
146
		}
147
		$this->last = null;
148
	}
149
150
	//Applies the current operation to whatever is in $last based on $mode
151
	private function processLast() {
152
		if ($this->last !== null) {
153
			try {
154
				$value = $this->data->extract($this->last, $this->autoLookup, $this->traversing);
155
				$this->result->processValue($value);
156
			}
157
			catch (\UnexpectedValueException $e) {
158
				$this->processLastUnexpected();
159
			}
160
		}
161
	}
162
163
	private function processLastUnexpected() {
164
		if (!($this->autoLookup || $this->traversing)) {
165
			$this->result->processValue($this->last);
166
		}
167
		else {
168
			$this->result->clear();
169
			$this->result[0] = false;
170
		}
171
	}
172
}
173