1
|
|
|
<?php |
2
|
|
|
namespace Desmond; |
3
|
|
|
use Exception; |
4
|
|
|
use Desmond\Reader; |
5
|
|
|
use Desmond\Tokenizer; |
6
|
|
|
use Desmond\data_types\ListType; |
7
|
|
|
use Desmond\data_types\VectorType; |
8
|
|
|
use Desmond\data_types\HashType; |
9
|
|
|
use Desmond\data_types\SymbolType; |
10
|
|
|
use Desmond\data_types\NumberType; |
11
|
|
|
use Desmond\data_types\NilType; |
12
|
|
|
use Desmond\data_types\TrueType; |
13
|
|
|
use Desmond\data_types\FalseType; |
14
|
|
|
use Desmond\data_types\StringType; |
15
|
|
|
|
16
|
|
|
class Lexer |
17
|
|
|
{ |
18
|
|
|
private static $CONDITON = 0; |
19
|
|
|
private static $VALUE = 1; |
20
|
|
|
|
21
|
175 |
|
public function readString($string) |
22
|
|
|
{ |
23
|
175 |
|
$tokens = Tokenizer::tokenize($string); |
24
|
175 |
|
if (empty($tokens)) { |
25
|
1 |
|
return null; |
26
|
|
|
} |
27
|
174 |
|
return $this->readForm(new Reader($tokens)); |
28
|
|
|
} |
29
|
|
|
|
30
|
174 |
|
private function readForm(Reader $reader) |
31
|
|
|
{ |
32
|
174 |
|
switch ($reader->peek()) { |
33
|
174 |
|
case '(': |
34
|
154 |
|
$reader->next(); |
35
|
154 |
|
$collection = new ListType(); |
36
|
154 |
|
return $this->readCollection($reader, $collection, ')'); |
37
|
174 |
|
case ')': |
38
|
1 |
|
throw new Exception('Unexpected )'); |
39
|
173 |
|
case '[': |
40
|
27 |
|
$reader->next(); |
41
|
27 |
|
$collection = new VectorType(); |
42
|
27 |
|
return $this->readCollection($reader, $collection, ']'); |
43
|
173 |
|
case ']': |
44
|
1 |
|
throw new Exception('Unexpected ]'); |
45
|
172 |
|
case '{': |
46
|
22 |
|
$reader->next(); |
47
|
22 |
|
$hash = new HashType(); |
48
|
22 |
|
return $this->readHash($reader, $hash); |
49
|
172 |
|
case '}': |
50
|
2 |
|
throw new Exception('Unexpected }'); |
51
|
|
|
default: |
52
|
171 |
|
$form = $this->readAtom($reader->peek()); |
53
|
171 |
|
$reader->next(); |
54
|
171 |
|
return $form; |
55
|
|
|
} |
56
|
|
|
} |
57
|
|
|
|
58
|
157 |
|
private function readCollection(Reader $reader, $collection, $end) |
59
|
|
|
{ |
60
|
157 |
|
while (($token = $reader->peek()) !== $end) { |
61
|
157 |
|
if ($token === null) { |
62
|
2 |
|
throw new Exception('Expected "' . $end . '", found EOF.'); |
63
|
|
|
} |
64
|
157 |
|
$collection->set($this->readForm($reader)); |
65
|
|
|
} |
66
|
155 |
|
$reader->next(); |
67
|
155 |
|
return $collection; |
68
|
|
|
} |
69
|
|
|
|
70
|
22 |
|
private function readHash(Reader $reader, $hash) |
71
|
|
|
{ |
72
|
22 |
|
while (($token = $reader->peek()) !== '}') { |
73
|
22 |
|
if ($token === null) { |
74
|
1 |
|
throw new Exception('Expected "}", found EOF.'); |
75
|
|
|
} |
76
|
22 |
|
$key = $this->readForm($reader); |
77
|
|
|
try { |
78
|
22 |
|
$value = $this->readForm($reader); |
79
|
1 |
|
} catch (Exception $exeption) { |
80
|
1 |
|
throw new Exception('Unexpected end of hash. Every key must have a value.'); |
81
|
|
|
} |
82
|
22 |
|
$hash->set($value, $key->value()); |
83
|
|
|
} |
84
|
20 |
|
$reader->next(); |
85
|
20 |
|
return $hash; |
86
|
|
|
} |
87
|
|
|
|
88
|
171 |
|
private function readAtom($token) |
89
|
|
|
{ |
90
|
171 |
|
$tokenLiteral = null; |
91
|
171 |
|
foreach ($this->tokenTestList($token) as $test) { |
92
|
171 |
|
if ($test[self::$CONDITON]) { |
93
|
171 |
|
$tokenLiteral = $test[self::$VALUE]; |
94
|
171 |
|
break; |
95
|
|
|
} |
96
|
|
|
} |
97
|
171 |
|
return $tokenLiteral; |
98
|
|
|
} |
99
|
|
|
|
100
|
171 |
|
private function tokenTestList($token) |
101
|
|
|
{ |
102
|
|
|
return [ |
103
|
171 |
|
[preg_match('/^-?(\.?[0-9]+)|([0-9]+.[0-9]+)$/', $token), new NumberType($token)], |
104
|
171 |
|
[preg_match('/".*"/s', $token), new StringType($token)], |
105
|
171 |
|
[$token === 'nil', new NilType()], |
106
|
171 |
|
[$token === 'true', new TrueType()], |
107
|
171 |
|
[$token === 'false', new FalseType()], |
108
|
171 |
|
[true, new SymbolType($token)] // Basically, if it's nothing else, it's a symbol. |
109
|
|
|
]; |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|