| … | |
… | |
| 89 | $context->constants->clear(); |
89 | $context->constants->clear(); |
| 90 | $context->operators->clear(); |
90 | $context->operators->clear(); |
| 91 | $context->functions->clear(); |
91 | $context->functions->clear(); |
| 92 | $context->strings->clear(); |
92 | $context->strings->clear(); |
| 93 | $context->operators->add( |
93 | $context->operators->add( |
| 94 | '>' => {precedence => 1.5, associativity => 'left', type => 'bin', class => 'context::Ordering::BOP'}, |
94 | '>' => {precedence => 1.5, associativity => 'left', type => 'bin', class => 'context::Ordering::BOP::ordering'}, |
| 95 | '=' => {precedence => 1.7, associativity => 'left', type => 'bin', class => 'context::Ordering::BOP'}, |
95 | '=' => {precedence => 1.7, associativity => 'left', type => 'bin', class => 'context::Ordering::BOP::ordering'}, |
| 96 | ); |
96 | ); |
|
|
97 | $context->{parser}{String} = "context::Ordering::Parser::String"; |
|
|
98 | $context->{parser}{Value} = "context::Ordering::Parser::Value"; |
|
|
99 | $context->{parser}{BOP} = "context::Ordering::Parser::BOP"; |
| 97 | $context->{value}{String} = "context::Ordering::Value::String"; |
100 | $context->{value}{String} = "context::Ordering::Value::String"; |
| 98 | $context->{value}{Ordering} = "context::Ordering::Value::Ordering"; |
101 | $context->{value}{Ordering} = "context::Ordering::Value::Ordering"; |
| 99 | $context->strings->add('='=>{hidden=>1},'>'=>{hidden=>1}); |
102 | $context->strings->add('='=>{hidden=>1},'>'=>{hidden=>1}); |
| 100 | $context->{error}{msg}{"Variable '%s' is not defined in this context"} = "'%s' is not defined in this context"; |
103 | $context->{error}{msg}{"Variable '%s' is not defined in this context"} = "'%s' is not defined in this context"; |
| 101 | $context->{error}{msg}{"Unexpected character '%s'"} = "Can't use '%s' in this context"; |
104 | $context->{error}{msg}{"Unexpected character '%s'"} = "Can't use '%s' in this context"; |
| 102 | $context->{error}{msg}{"Missing operand before '%s'"} = "Missing letter before '%s'"; |
105 | $context->{error}{msg}{"Missing operand before '%s'"} = "Missing letter before '%s'"; |
| … | |
… | |
| 163 | # standard list comparisons. The operands are either the strings |
166 | # standard list comparisons. The operands are either the strings |
| 164 | # for individual letters, or another Ordering object as a |
167 | # for individual letters, or another Ordering object as a |
| 165 | # nested List. |
168 | # nested List. |
| 166 | # |
169 | # |
| 167 | |
170 | |
| 168 | package context::Ordering::BOP; |
171 | package context::Ordering::BOP::ordering; |
| 169 | our @ISA = ('Parser::BOP'); |
172 | our @ISA = ('Parser::BOP'); |
| 170 | |
173 | |
| 171 | sub class {"Ordering"} |
174 | sub class {"Ordering"} |
| 172 | |
175 | |
| 173 | sub isOrdering { |
176 | sub isOrdering { |
| 174 | my $self = shift; my $obj = shift; my $class = $obj->class; |
177 | my $self = shift; my $obj = shift; my $class = $obj->class; |
| 175 | return ($class eq 'Value' && $obj->{value}->class eq 'Ordering') || |
|
|
| 176 | ($class eq 'Ordering') || $obj->{def}{isLetter}; |
178 | return $class eq 'Ordering' || $obj->{def}{isLetter}; |
| 177 | } |
179 | } |
| 178 | |
180 | |
| 179 | sub _check { |
181 | sub _check { |
| 180 | my $self = shift; |
182 | my $self = shift; |
| 181 | return if $self->isOrdering($self->{lop}) && $self->isOrdering($self->{rop}); |
|
|
| 182 | $self->Error("Operators of %s must be letters",$self->{bop}); |
183 | $self->Error("Operators of %s must be letters",$self->{bop}) |
|
|
184 | unless $self->isOrdering($self->{lop}) && $self->isOrdering($self->{rop}); |
|
|
185 | $self->{letters} = $self->{lop}{letters}; # we modify {lop}{letters} this way, but that doesn't matter |
|
|
186 | foreach my $x (keys %{$self->{rop}{letters}}) { |
|
|
187 | if (defined($self->{letters}{$x})) { |
|
|
188 | $self->{ref} = $self->{rop}{letters}{$x}; |
|
|
189 | $self->Error("Letters can appear only once in an ordering"); |
|
|
190 | } |
|
|
191 | $self->{letters}{$x} = $self->{rop}{letters}{$x}; |
|
|
192 | } |
|
|
193 | $self->{equation}{letters} = $self->{letters}; # removed by context::Ordering::Parser::BOP; |
| 183 | } |
194 | } |
| 184 | |
195 | |
| 185 | sub _eval { |
196 | sub _eval { |
| 186 | my $self = shift; |
197 | my $self = shift; |
| 187 | return $self->Package("Ordering")->new($self->context,$self->{bop},@_); |
198 | return $self->Package("Ordering")->new($self->context,$self->{bop},@_); |
| … | |
… | |
| 194 | |
205 | |
| 195 | sub TeX { |
206 | sub TeX { |
| 196 | my $self = shift; |
207 | my $self = shift; |
| 197 | return $self->{lop}->TeX." ".$self->{bop}." ".$self->{rop}->TeX; |
208 | return $self->{lop}->TeX." ".$self->{bop}." ".$self->{rop}->TeX; |
| 198 | } |
209 | } |
|
|
210 | |
| 199 | |
211 | |
| 200 | ############################################################# |
212 | ############################################################# |
| 201 | # |
213 | # |
| 202 | # This is the Value object used to implement the list That represents |
214 | # This is the Value object used to implement the list That represents |
| 203 | # one ordering operation. It is simply a normal Value::List with the |
215 | # one ordering operation. It is simply a normal Value::List with the |
| … | |
… | |
| 246 | # Make sure we do comparison as a list of lists (rather than as the |
258 | # Make sure we do comparison as a list of lists (rather than as the |
| 247 | # individual entries in the underlying Value::List that encodes |
259 | # individual entries in the underlying Value::List that encodes |
| 248 | # the ordering) |
260 | # the ordering) |
| 249 | # |
261 | # |
| 250 | sub cmp_equal { |
262 | sub cmp_equal { |
| 251 | my $self = shift; my $ans = $_[0]; |
263 | my $self = shift; my $ans = $_[0]; |
| 252 | $ans->{typeMatch} = $ans->{firstElement} = $self; |
264 | $ans->{typeMatch} = $ans->{firstElement} = $self; |
| 253 | $self = $ans->{correct_value} = Value::List->make($self); |
265 | $self = $ans->{correct_value} = Value::List->make($self); |
| 254 | $ans->{student_value} = Value::List->make($ans->{student_value}) |
266 | $ans->{student_value} = Value::List->make($ans->{student_value}) |
| 255 | if Value::classMatch($ans->{student_value},'Ordering'); |
267 | if Value::classMatch($ans->{student_value},'Ordering'); |
| 256 | return $self->SUPER::cmp_equal(@_); |
268 | return $self->SUPER::cmp_equal(@_); |
| … | |
… | |
| 282 | sub TeX {shift->value} |
294 | sub TeX {shift->value} |
| 283 | |
295 | |
| 284 | |
296 | |
| 285 | ############################################################# |
297 | ############################################################# |
| 286 | # |
298 | # |
|
|
299 | # Override Parser classes so that we can check for repeated letters |
|
|
300 | # |
|
|
301 | |
|
|
302 | package context::Ordering::Parser::String; |
|
|
303 | our @ISA = ('Parser::String'); |
|
|
304 | |
|
|
305 | # |
|
|
306 | # Save the letters positional reference |
|
|
307 | # |
|
|
308 | sub new { |
|
|
309 | my $self = shift; |
|
|
310 | $self = $self->SUPER::new(@_); |
|
|
311 | $self->{letters}{$self->{value}} = $self->{ref} if $self->{def}{isLetter}; |
|
|
312 | return $self; |
|
|
313 | } |
|
|
314 | |
|
|
315 | ######################### |
|
|
316 | |
|
|
317 | package context::Ordering::Parser::Value; |
|
|
318 | our @ISA = ('Parser::Value'); |
|
|
319 | |
|
|
320 | # |
|
|
321 | # Move letters to Value object |
|
|
322 | # |
|
|
323 | sub new { |
|
|
324 | my $self = shift; |
|
|
325 | $self = $self->SUPER::new(@_); |
|
|
326 | if (defined($self->{value}{letters})) { |
|
|
327 | $self->{letters} = $self->{value}{letters}; |
|
|
328 | delete $self->{value}{letters}; |
|
|
329 | } |
|
|
330 | return $self; |
|
|
331 | } |
|
|
332 | |
|
|
333 | # |
|
|
334 | # Return Ordering class if the object is one |
|
|
335 | # |
|
|
336 | sub class { |
|
|
337 | my $self = shift; |
|
|
338 | return "Ordering" if $self->{value}->classMatch('Ordering'); |
|
|
339 | return $self->SUPER::class; |
|
|
340 | } |
|
|
341 | |
|
|
342 | ######################### |
|
|
343 | |
|
|
344 | package context::Ordering::Parser::BOP; |
|
|
345 | our @ISA = ('Parser::BOP'); |
|
|
346 | |
|
|
347 | # |
|
|
348 | # If a BOP is constant and so reduced automatically we will lose the |
|
|
349 | # letters hash, so it is stored temporarily in the equation by _check(), |
|
|
350 | # and replaced here. A hack, but that avoids adding letters to the |
|
|
351 | # Ordering object in eval() and having them remain there after parsing. |
|
|
352 | # |
|
|
353 | sub new { |
|
|
354 | my $self = shift; |
|
|
355 | $self = $self->SUPER::new(@_); |
|
|
356 | $self->{letters} = $self->{equation}{letters} unless defined $self->{letters} || $self->class ne 'Ordering'; |
|
|
357 | delete $self->{equation}{letters}; |
|
|
358 | return $self; |
|
|
359 | } |
|
|
360 | |
|
|
361 | |
|
|
362 | ############################################################# |
|
|
363 | # |
| 287 | # This overrides the cmp_equal method to make sure that |
364 | # This overrides the cmp_equal method to make sure that |
| 288 | # Ordering lists are put into nested lists (since the |
365 | # Ordering lists are put into nested lists (since the |
| 289 | # underlying ordering is a list, we don't want the |
366 | # underlying ordering is a list, we don't want the |
| 290 | # list checker to test the individual parts of the list, |
367 | # list checker to test the individual parts of the list, |
| 291 | # but rather the list as a whole). |
368 | # but rather the list as a whole). |
| … | |
… | |
| 298 | my $self = shift; my $ans = $_[0]; |
375 | my $self = shift; my $ans = $_[0]; |
| 299 | $ans->{student_value} = Value::List->make($ans->{student_value}) |
376 | $ans->{student_value} = Value::List->make($ans->{student_value}) |
| 300 | if Value::classMatch($ans->{student_value},'Ordering'); |
377 | if Value::classMatch($ans->{student_value},'Ordering'); |
| 301 | return $self->SUPER::cmp_equal(@_); |
378 | return $self->SUPER::cmp_equal(@_); |
| 302 | } |
379 | } |
|
|
380 | |
|
|
381 | ############################################################# |
|
|
382 | |
|
|
383 | 1; |