49
|
1 package IMPL::Text::Parser::Player;
|
|
2 use strict;
|
|
3 use warnings;
|
|
4
|
166
|
5 use parent qw(IMPL::Object);
|
49
|
6 use IMPL::Class::Property;
|
|
7
|
|
8 use IMPL::Text::Parser::Chunk;
|
|
9
|
|
10 my %opCodesMap = (
|
|
11 IMPL::Text::Parser::Chunk::OP_REGEXP , &MatchRegexp ,
|
|
12 IMPL::Text::Parser::Chunk::OP_STRING , &MatchString ,
|
|
13 IMPL::Text::Parser::Chunk::OP_REFERENCE , &MatchReference ,
|
|
14 IMPL::Text::Parser::Chunk::OP_CHUNK , &PlayChunk ,
|
|
15 IMPL::Text::Parser::Chunk::OP_SWITCH , &MatchSwitch ,
|
|
16 IMPL::Text::Parser::Chunk::OP_REPEAT , &MatchRepeat
|
|
17 );
|
|
18
|
|
19 BEGIN {
|
|
20 private _direct property _data => prop_all;
|
|
21 private _direct property _current => prop_all;
|
|
22 private _direct property _states => prop_all;
|
|
23 private _direct property _document => prop_all;
|
|
24
|
|
25 public _direct property errorLast => prop_all;
|
|
26 public _direct property Punctuation => prop_all;
|
|
27 public _direct property Delimier => prop_all;
|
|
28 }
|
|
29
|
|
30 sub CTOR {
|
|
31 my ($this,$document) = @_;
|
|
32
|
|
33 $this->{$_document} = $document or die new IMPL::InvalidArgumentException("The first parameter must be a document");
|
|
34 }
|
|
35
|
|
36 sub LoadString {
|
|
37 my ($this,$string) = @_;
|
|
38
|
|
39 my $rxDelim = /(\s+|[.,;!-+*~$^&|%()`@\\\/])/;
|
|
40
|
|
41 my $line = 0;
|
|
42
|
|
43 $this->{$_data} = [
|
|
44 map {
|
|
45 $line++;
|
|
46 map {
|
|
47 [$line,$_]
|
|
48 } split $rxDelim, $_
|
|
49 } split /\n/, $string
|
|
50 ]
|
|
51 }
|
|
52
|
|
53 sub Play {
|
|
54 my ($this) = @_;
|
|
55 }
|
|
56
|
|
57 sub PlayChunk {
|
|
58 my ($this,$chunk) = @_;
|
|
59
|
|
60 my $end = 0;
|
|
61
|
|
62 my $name = $chunk->chunkName;
|
|
63
|
|
64 $this->enter($name) if $name;
|
|
65
|
|
66 foreach my $op ( @{$chunk->opStream} ) {
|
|
67 $this->leave(0) and return $this->error("no more data") if $end;
|
|
68
|
|
69 $opCodesMap{shift @$op}->(@$op) || return $this->leave(0) ;
|
|
70 $this->moveNext or $end = 1;
|
|
71 }
|
|
72
|
|
73 return $this->leave(1);
|
|
74 }
|
|
75
|
|
76 sub MatchRegexp {
|
|
77 my ($this,$rx) = @_;
|
|
78
|
|
79 $this->{$_current}{token} =~ $rx ? ($this->data() and return 1) : return $this->error("Expected: $rx");
|
|
80 }
|
|
81
|
|
82 sub MatchString {
|
|
83 my ($this,$string) = @_;
|
|
84
|
|
85 $this->{$_current}{token} eq $string ? ($this->data() and return 1) : return $this->error("Expected: $string");
|
|
86 }
|
|
87
|
|
88 sub MatchReference {
|
|
89 my ($this,$name) = @_;
|
|
90
|
|
91 my $chunk = $this->ResolveChunk($name) || return $this->error("Invalid reference: $name");
|
|
92 return $this->PlayChunk($chunk);
|
|
93 }
|
|
94
|
|
95 sub MatchSwitch {
|
|
96 my ($this,@chunks) = @_;
|
|
97
|
|
98 foreach my $chunk (@chunks) {
|
|
99 $this->save;
|
|
100 if ( $this->PlayChunk($chunk) ) {
|
|
101 $this->apply;
|
|
102 return 1;
|
|
103 } else {
|
|
104 $this->restore;
|
|
105 }
|
|
106 }
|
|
107
|
|
108 return 0; # passthrough last error
|
|
109 }
|
|
110
|
|
111 sub MatchRepeat {
|
|
112 my ($this,$chunk, $min, $max) = @_;
|
|
113
|
|
114 my $count = 0;
|
|
115
|
|
116 $this->save;
|
|
117 while (1) {
|
|
118 $this->save;
|
|
119 if ($this->PlayChunk($chunk)) {
|
|
120 $count ++;
|
|
121 $this->apply;
|
|
122 $this->apply and return 1 if ($count >= $max)
|
|
123 } else {
|
|
124 $this->restore;
|
|
125 $count >= $min ?
|
|
126 ($this->apply() and return 1) :
|
|
127 ($this->restore() and return $this->error("Expected at least $min occurances, got only $count"));
|
|
128 }
|
|
129 }
|
|
130
|
|
131 # we should never get here
|
|
132 die new IMPL::InvalidOperationException("unexpected error");
|
|
133 }
|
|
134
|
|
135 sub moveNext {
|
|
136 my ($this) = @_;
|
|
137
|
|
138 my $pos = $this->{$_current}{pos};
|
|
139
|
|
140 $pos ++;
|
|
141
|
|
142 if ($pos < @{$this->{$_data}}) {
|
|
143
|
|
144 $this->{$_current} = {
|
|
145 pos => $pos,
|
|
146 token => $this->{$_data}[$pos][1],
|
|
147 line => $this->{$_data}
|
|
148 };
|
|
149
|
|
150 } else {
|
|
151 $this->{$_current} = {};
|
|
152 return undef;
|
|
153 }
|
|
154 }
|
|
155
|
|
156 sub ResolveChunk {
|
|
157 my ($this,$name) = @_;
|
|
158 }
|
|
159
|
|
160 sub save {
|
|
161 my ($this) = @_;
|
|
162
|
|
163 push @{$this->{$_states}}, $this->{$_current};
|
|
164 }
|
|
165
|
|
166 sub restore {
|
|
167 my ($this) = @_;
|
|
168
|
|
169 $this->{$_current} = pop @{$this->{$_states}};
|
|
170 }
|
|
171
|
|
172 sub apply {
|
|
173 my ($this) = @_;
|
|
174
|
|
175 pop @{$this->{$_states}};
|
|
176 }
|
|
177
|
|
178 sub error {
|
|
179 my ($this,$message) = @_;
|
|
180
|
|
181 $this->{$errorLast} = {
|
|
182 message => $message,
|
|
183 line => $this->{$_current}{line},
|
|
184 token => $this->{$_current}{token}
|
|
185 };
|
|
186
|
|
187 return 0;
|
|
188 }
|
|
189
|
|
190 sub __debug {
|
|
191
|
|
192 }
|
|
193 sub enter {
|
|
194 my ($this,$name) = @_;
|
|
195
|
|
196 #always return true;
|
|
197 return 1;
|
|
198 }
|
|
199
|
|
200 sub leave {
|
|
201 my ($this,$isEmpty) = @_;
|
|
202
|
|
203 #always return true;
|
|
204 return 1;
|
|
205 }
|
|
206
|
|
207 sub data {
|
|
208 my ($this) = @_;
|
|
209
|
|
210 my $data = $this->{$_current}{token};
|
|
211
|
|
212 # always return true;
|
|
213 return 1;
|
|
214 }
|
|
215
|
|
216 1;
|