diff --git a/macros/contexts/contextNondecimalBase.pl b/macros/contexts/contextNondecimalBase.pl index 920fbb34d..228fe4d70 100644 --- a/macros/contexts/contextNondecimalBase.pl +++ b/macros/contexts/contextNondecimalBase.pl @@ -1,111 +1,82 @@ =head1 NAME -contextHex.pl - Implements a MathObject class and context for integers - in hexadecimal notation. +contextNonDecimalBase.pl - Implements a MathObject class and context for numbers in non-decimal bases =head1 DESCRIPTION -This context implements a Hex object that works like a Real, but -where you enter numbers in hexadecimal, and they display in hexadecimal. -You can perform the usual numeric operations (addition, subtraction, -etc.), but division is integer division (so 7/3 = 2). The context defines -the bitwise operators &, |, ^, >>, <<, and ~ (for bitwise and, or, -exclusive or, shift-right, shift-left, and one's complement not). You can -apply these operations within your PG code to variables that store Hex -objects. Remember that you can also obtain Perl reals via hex notation, -for example, 0x1A. +This context implements a Hex object that works like a Real, but can implement numbers +in any non-decimal base between 2 and 16. The numbers will be stored internally in +decimal, though parsed and shown in the chosen base. -To use hexadecimal MathObjects, first load the contextHex.pl file: +In addition, basic integer arithemetic (+,-,*,^) are available for these number. - loadMacros("contextHex.pl"); +The original purpose for this is simple conversion and operations in another base, however +it is not limited to that -and then select the appropriate context -- one of the following: +To use a non-decimal base MathObject, first load the contextNondecimalBase.pl file: + + loadMacros("contextNondecimalBase.pl"); - Context("Hex"); - Context("LimitedHex"); +and then select the appropriate context -- one of the following: -The latter only allows the student to enter hexadecimal literals (not -expressions), or lists of hexadecimal numbers. The former allows -expression involving numeric operations and bitwise operations. + Context("NondecimalBase"); Once one of these contexts is selected, all the nummbers parsed by MathObjects will be considered to be in hexadecimal, so - $n = Compute('10'); - -produces the hexadecimal number 10 (decimal 16). You could also obtain -a hex MathObject using - - $n = Hex(0x10); - -Once you have such a value, use - - ANS($n->cmp) - -to get an answer checker for the number. You can also perform numeric or -bitwise operations on the value, as in - - $m = $n + 0xE3; - $N = $n << 2; # shifts n to the left 2 (binary) places, - # $N = Hex(0x40) when $n = Hex(0x10) - =cut sub _contextNondecimalBase_init { - context::NondecimalBase::Init(); - sub setBase { context::NondecimalBase::setBase(@_); } + context::NondecimalBase::Init(@_); + # sub setBase { context::NondecimalBase::setBase(@_); } sub convertBase { context::NondecimalBase::convert(@_); } } ########################################################################### package context::NondecimalBase; +our @ISA = ('Parser::Context'); + +use Data::Dumper; -# defines the base for the context. -our $base = 10; # # The standard digits, pre-built so it doesn't have to be done each time the conversion is called # our $digits16 = [ '0' .. '9', 'A' .. 'F' ]; our $digit16 = { map { ($digits16->[$_], $_) } (0 .. scalar(@$digits16) - 1) }; -# # Initialize the contexts and make the creator function. -# sub Init { my $context = $main::context{NondecimalBase} = Parser::Context->getCopy("Numeric"); $context->{name} = 'NondecimalBase'; $context->{parser}{Number} = 'context::NondecimalBase::Number'; - # $context->{value}{Real} = 'context::Hex::Hex'; + $context->{value}{Real} = 'context::NondecimalBase::Real'; $context->{pattern}{number} = '[0-9A-F]+'; $context->functions->disable('All'); - $context->operators->remove('^'); + # $context->operators->remove('^'); $context->parens->remove('|'); $context->constants->clear(); - # $context->operators->add( - # '&' => {precedence => .6, associativity => 'left', type => 'bin', string => ' & ', - # class => 'context::Hex::BOP::hex', eval => sub {$_[0] & $_[1]}}, - # '|' => {precedence => .5, associativity => 'left', type => 'bin', string => ' | ', - # class => 'context::Hex::BOP::hex', eval => sub {$_[0] | $_[1]}}, - # '^' => {precedence => .5, associativity => 'left', type => 'bin', string => ' ^ ', - # class => 'context::Hex::BOP::hex', eval => sub {$_[0] ^ $_[1]}}, - # '>>' => {precedence => .4, associativity => 'left', type => 'bin', string => ' >> ', - # class => 'context::Hex::BOP::hex', eval => sub {$_[0] >> $_[1]}}, - # '<<' => {precedence => .4, associativity => 'left', type => 'bin', string => ' << ', - # class => 'context::Hex::BOP::hex', eval => sub {$_[0] << $_[1]}}, - # '~' => {precedence => 6, associativity => 'left', type => 'unary', string => '~', - # class => 'context::Hex::UOP::not'}, - # ); - # $context->{precedence}{Hex} = $context->{precedence}{special}; + $context->{precedence}{NondecimalBase} = $context->{precedence}{special}; $context->flags->set(limits => [ -1000, 1000, 1 ]); $context->update; # main::PG_restricted_eval('sub Hex {context::Hex::Hex->new(@_)}'); } +sub setBase { + my ($name, $base) = @_; + print Dumper 'in setBase'; + print Dumper $name; + my $context = $main::context{$name} = Parser::Context->getCopy($name); + print Dumper $context; + print Dumper $base; +} + sub convert { my $value = shift; + # print Dumper 'in convert'; + # print Dumper $value; # Set default options and get passed in options. my %options = ( from => 10, @@ -117,25 +88,23 @@ sub convert { my $to = $options{'to'}; my $digits = $options{'digits'}; + # print Dumper $from; + # print Dumper $to; + # print Dumper $digits; + die "The digits option must be an array of characters to use for the digits" unless ref($digits) eq 'ARRAY'; - # # The highest base the digits will support - # - my $maxBase = scalar(@$digits16); + my $maxBase = scalar(@$digits); die "The base of conversion must be between 2 and $maxBase" unless $to >= 2 && $to <= $maxBase && $from >= 2 && $from <= $maxBase; - # # Reverse map the digits to base 10 values - # my $baseBdigits = { map { ($digits->[$_], $_) } (0 .. $from - 1) }; - # # Convert to base 10 - # my $base10; if ($from == 10) { die "The number to convert must consist only of digits: 0,1,2,3,4,5,6,7,8,9" @@ -151,9 +120,7 @@ sub convert { } return $base10 if $to == 10; - # # Convert to desired base - # my @base; do { my $d = $base10 % $to; @@ -164,31 +131,15 @@ sub convert { return join('', @base); } -# set the base for the context. - -sub setBase { - my $b = shift; - return Value::Error('The base must be greater than 1 and less than or equal to $max_base') - unless $b >= 2 && $b <= scalar(@$digits16); - $base = $b; - # $digits16 = [map { $digits16->[$_] } (0..($base-1))]; - # $digit16 = { map { ($digits16->[$_], $_) } (0 .. scalar(@$digits16) - 1) }; - # $context->{pattern}{number} = '[' . join('',@$digits16) . ']+'; -} - -########################################################################### -# -# A replacement for Parser::Number that acepts numbers in -# hexadecimal and converts them to decimal for internal use -# +# A replacement for Parser::Number that acepts numbers in a NonDecimal base and converts them to decimal for internal use package context::NondecimalBase::Number; our @ISA = ('Parser::Number'); # Create a new number in the given base and convert to base 10. - +use Data::Dumper; sub new { - my $self = shift; - my ($equation, $value, $ref) = @_; + my ($self, $equation, $value, $ref) = @_; + my $base = $equation->{context}{flags}{base}; $value = context::NondecimalBase::convert($value, from => $base); return $self->SUPER::new($equation, $value, $ref); } @@ -198,68 +149,36 @@ sub new { # sub eval { $self = shift; + my $base = $self->{equation}{context}{flags}{base}; return context::NondecimalBase::convert($self->{value}, to => $base); $self->Package('Real')->make($self->context, $self->{value}); } ########################################################################### # -# A replacement for Value::Real that handles hexadecimal integers +# A replacement for Value::Real that handles non-decimal integers # -package context::Hex::Hex; +package context::NondecimalBase::Real; our @ISA = ('Value::Real'); - +use Data::Dumper; # -# Stringify and TeXify in hex notation +# Stringify and TeXify the number in the context's base # sub string { my $self = shift; - return main::spf($self->value); + my $base = $self->{context}{flags}{base}; + return context::NondecimalBase::convert($self->value, to => $base); } sub TeX { my $self = shift; - return '\text{' . $self->string . '}'; -} - -########################################################################### -# -# This is a Parser::BOP that handles the bitwise operations (all of -# them call the same class, and the operators list gives the code to -# perform the operation) -# -package context::Hex::BOP::hex; -our @ISA = ('Parser::BOP'); - -sub _check { - my $self = shift; - return if $self->checkNumbers; - $self->Error("Arguments to '%s' must be Numbers", $self->{bop}); + my $base = $self->{context}{flags}{base}; + return '\text{' . context::NondecimalBase::convert($self->string, to => $base) . '}'; } -sub _eval { - my ($self, $a, $b) = @_; - $a->inherit($b)->make(&{ $self->{def}{eval} }($a->value, $b->value)); +sub add { + my ($self, $l, $r, $other) = Value::checkOpOrderWithPromote(@_); + print Dumper 'in add'; } -########################################################################### -# -# The Parser::UOP subclass for one's complement not. -# -package context::Hex::UOP::not; -our @ISA = ('Parser::UOP'); - -sub _check { - my $self = shift; - return if $self->checkNumber; - $self->Error("Argument to '%s' must be a Number", $self->{uop}); -} - -sub _eval { - my ($self, $a) = @_; - $a->make(~($a->value)); -} - -########################################################################### - 1; diff --git a/t/contexts/nondecimal_base.t b/t/contexts/nondecimal_base.t index 3df83a822..ee0463196 100644 --- a/t/contexts/nondecimal_base.t +++ b/t/contexts/nondecimal_base.t @@ -19,25 +19,68 @@ use Value; require Parser::Legacy; import Parser::Legacy; +use Data::Dumper; + Context('NondecimalBase'); -setBase(5); +# test that convert is working -# ok my $a1 = Compute('10'); -ok my $a2 = Compute('240'); -is convertBase($a2->value, from => 5), 70, 'Base 5 stored correctly in base 10'; +# subtest 'conversion from a non-decimal base to base 10' => sub { +# is convertBase('101010', from => 2), 42, 'convert from base 2'; +# is convertBase('44011', from => 5), 3006, 'convert from base 5'; +# is convertBase('5073', from => 8), 2619, 'convert from base 8'; +# is convertBase('98A', from => 12), 1402, 'convert from base 12'; +# is convertBase('98T', from => 12, digits => [ '0' .. '9', 'T', 'E' ]), 1402, +# 'convert from base 12 with non-standard digits'; +# is convertBase('9FE8', from => 16), 40936, 'convert from base 16'; +# }; -subtest 'check that non-valid digits return errors' => sub { - like dies { Compute('456'); }, qr/^The number to convert must consist/, - 'Try to build a base-5 number will illegal digits'; -}; +# subtest 'Convert from decimal to non-decimal bases' => sub { +# is convertBase(12, to => 2), '1100', 'convert to base 2'; +# is convertBase(47, to => 2), '101111', 'convert to base 2'; + +# is convertBase(98, to => 5), '343', 'convert to base 5'; +# is convertBase(761, to => 5), '11021', 'convert to base 5'; + +# is convertBase(519, to => 8), '1007', 'convert to base 8'; +# is convertBase(2023, to => 8), '3747', 'convert to base 8'; + +# is convertBase(853, to => 12), '5B1', 'convert to base 12'; +# is convertBase(2023, to => 12), '1207', 'convert to base 12'; +# is convertBase(1678, to => 12, digits => [ '0' .. '9', 'T', 'E' ]), 'E7T', +# 'convert to base 12 using non-standard digits'; + +# is convertBase(5752, to => 16), '1678', 'convert to base 16'; +# is convertBase(41446, to => 16), 'A1E6', 'convert to base 16'; +# }; + +# subtest 'Convert between two non-decimal bases' => sub { +# is convertBase('1234', from => 5, to => 16), 'C2', 'convert from base 5 to 16'; +# }; + +# Now test the Context. + +Context()->flags->set(base => 5); +# # context::NondecimalBase->setBase(5); + +# subtest 'Check that the Context parses number correct' => sub { +# is Context()->{flags}->{base}, 5, 'Check that the base is stored.'; + +# ok my $a1 = Compute('10'), "The string '10' is parsed correctly"; +# ok my $a2 = Compute('240'), "The string '240' is parsed correctly"; +# is convertBase($a2->value, from => 5), 70, 'Base 5 stored correctly in base 10'; +# }; + +# subtest 'check that non-valid digits return errors' => sub { +# like dies { Compute('456'); }, qr/^The number to convert must consist/, +# 'Try to build a base-5 number will illegal digits'; +# }; subtest 'check arithmetic in non-decimal base' => sub { my $a3 = Compute('240+113'); ok $a3->value, '403', 'Base 5 addition is correct'; - my $a4 = Compute('240-113'); - ok $a4->value, '122', 'Base 5 subtraction is correct'; +# my $a4 = Compute('240-113'); +# ok $a4->value, '122', 'Base 5 subtraction is correct'; }; - done_testing();