1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan; |
4: | |
5: | use PHPStan\Type\BooleanType; |
6: | use PHPStan\Type\Constant\ConstantBooleanType; |
7: | use function array_column; |
8: | use function max; |
9: | use function min; |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | class TrinaryLogic |
17: | { |
18: | |
19: | private const YES = 1; |
20: | private const MAYBE = 0; |
21: | private const NO = -1; |
22: | |
23: | |
24: | private static array $registry = []; |
25: | |
26: | private function __construct(private int $value) |
27: | { |
28: | } |
29: | |
30: | public static function createYes(): self |
31: | { |
32: | return self::$registry[self::YES] ??= new self(self::YES); |
33: | } |
34: | |
35: | public static function createNo(): self |
36: | { |
37: | return self::$registry[self::NO] ??= new self(self::NO); |
38: | } |
39: | |
40: | public static function createMaybe(): self |
41: | { |
42: | return self::$registry[self::MAYBE] ??= new self(self::MAYBE); |
43: | } |
44: | |
45: | public static function createFromBoolean(bool $value): self |
46: | { |
47: | $yesNo = $value ? self::YES : self::NO; |
48: | return self::$registry[$yesNo] ??= new self($yesNo); |
49: | } |
50: | |
51: | private static function create(int $value): self |
52: | { |
53: | self::$registry[$value] ??= new self($value); |
54: | return self::$registry[$value]; |
55: | } |
56: | |
57: | public function yes(): bool |
58: | { |
59: | return $this->value === self::YES; |
60: | } |
61: | |
62: | public function maybe(): bool |
63: | { |
64: | return $this->value === self::MAYBE; |
65: | } |
66: | |
67: | public function no(): bool |
68: | { |
69: | return $this->value === self::NO; |
70: | } |
71: | |
72: | public function toBooleanType(): BooleanType |
73: | { |
74: | if ($this->value === self::MAYBE) { |
75: | return new BooleanType(); |
76: | } |
77: | |
78: | return new ConstantBooleanType($this->value === self::YES); |
79: | } |
80: | |
81: | public function and(self ...$operands): self |
82: | { |
83: | $operandValues = array_column($operands, 'value'); |
84: | $operandValues[] = $this->value; |
85: | return self::create(min($operandValues)); |
86: | } |
87: | |
88: | |
89: | |
90: | |
91: | |
92: | |
93: | public function lazyAnd( |
94: | array $objects, |
95: | callable $callback, |
96: | ): self |
97: | { |
98: | if ($this->no()) { |
99: | return $this; |
100: | } |
101: | |
102: | $results = []; |
103: | foreach ($objects as $object) { |
104: | $result = $callback($object); |
105: | if ($result->no()) { |
106: | return $result; |
107: | } |
108: | |
109: | $results[] = $result; |
110: | } |
111: | |
112: | return $this->and(...$results); |
113: | } |
114: | |
115: | public function or(self ...$operands): self |
116: | { |
117: | $operandValues = array_column($operands, 'value'); |
118: | $operandValues[] = $this->value; |
119: | return self::create(max($operandValues)); |
120: | } |
121: | |
122: | |
123: | |
124: | |
125: | |
126: | |
127: | public function lazyOr( |
128: | array $objects, |
129: | callable $callback, |
130: | ): self |
131: | { |
132: | if ($this->yes()) { |
133: | return $this; |
134: | } |
135: | |
136: | $results = []; |
137: | foreach ($objects as $object) { |
138: | $result = $callback($object); |
139: | if ($result->yes()) { |
140: | return $result; |
141: | } |
142: | |
143: | $results[] = $result; |
144: | } |
145: | |
146: | return $this->or(...$results); |
147: | } |
148: | |
149: | public static function extremeIdentity(self ...$operands): self |
150: | { |
151: | if ($operands === []) { |
152: | throw new ShouldNotHappenException(); |
153: | } |
154: | $operandValues = array_column($operands, 'value'); |
155: | $min = min($operandValues); |
156: | $max = max($operandValues); |
157: | return self::create($min === $max ? $min : self::MAYBE); |
158: | } |
159: | |
160: | |
161: | |
162: | |
163: | |
164: | |
165: | public static function lazyExtremeIdentity( |
166: | array $objects, |
167: | callable $callback, |
168: | ): self |
169: | { |
170: | if ($objects === []) { |
171: | throw new ShouldNotHappenException(); |
172: | } |
173: | |
174: | $lastResult = null; |
175: | foreach ($objects as $object) { |
176: | $result = $callback($object); |
177: | if ($lastResult === null) { |
178: | $lastResult = $result; |
179: | continue; |
180: | } |
181: | if ($lastResult->equals($result)) { |
182: | continue; |
183: | } |
184: | |
185: | return self::createMaybe(); |
186: | } |
187: | |
188: | return $lastResult; |
189: | } |
190: | |
191: | public static function maxMin(self ...$operands): self |
192: | { |
193: | if ($operands === []) { |
194: | throw new ShouldNotHappenException(); |
195: | } |
196: | $operandValues = array_column($operands, 'value'); |
197: | return self::create(max($operandValues) > 0 ? 1 : min($operandValues)); |
198: | } |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | |
205: | public static function lazyMaxMin( |
206: | array $objects, |
207: | callable $callback, |
208: | ): self |
209: | { |
210: | $results = []; |
211: | foreach ($objects as $object) { |
212: | $result = $callback($object); |
213: | if ($result->yes()) { |
214: | return $result; |
215: | } |
216: | |
217: | $results[] = $result; |
218: | } |
219: | |
220: | return self::maxMin(...$results); |
221: | } |
222: | |
223: | public function negate(): self |
224: | { |
225: | return self::create(-$this->value); |
226: | } |
227: | |
228: | public function equals(self $other): bool |
229: | { |
230: | return $this === $other; |
231: | } |
232: | |
233: | public function compareTo(self $other): ?self |
234: | { |
235: | if ($this->value > $other->value) { |
236: | return $this; |
237: | } elseif ($other->value > $this->value) { |
238: | return $other; |
239: | } |
240: | |
241: | return null; |
242: | } |
243: | |
244: | public function describe(): string |
245: | { |
246: | static $labels = [ |
247: | self::NO => 'No', |
248: | self::MAYBE => 'Maybe', |
249: | self::YES => 'Yes', |
250: | ]; |
251: | |
252: | return $labels[$this->value]; |
253: | } |
254: | |
255: | |
256: | |
257: | |
258: | public static function __set_state(array $properties): self |
259: | { |
260: | return self::create($properties['value']); |
261: | } |
262: | |
263: | } |
264: | |