Annotation of loncom/homework/math_parser/Parser.pm, revision 1.1

1.1     ! damieng     1: # The LearningOnline Network with CAPA - LON-CAPA
        !             2: # Parser
        !             3: #
        !             4: # Copyright (C) 2014 Michigan State University Board of Trustees
        !             5: #
        !             6: # This program is free software: you can redistribute it and/or modify
        !             7: # it under the terms of the GNU General Public License as published by
        !             8: # the Free Software Foundation, either version 3 of the License, or
        !             9: # (at your option) any later version.
        !            10: #
        !            11: # This program is distributed in the hope that it will be useful,
        !            12: # but WITHOUT ANY WARRANTY; without even the implied warranty of
        !            13: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
        !            14: # GNU General Public License for more details.
        !            15: #
        !            16: # You should have received a copy of the GNU General Public License
        !            17: # along with this program. If not, see <http://www.gnu.org/licenses/>.
        !            18: #
        !            19: 
        !            20: ##
        !            21: # Equation parser
        !            22: ##
        !            23: package Apache::math_parser::Parser;
        !            24: 
        !            25: use strict;
        !            26: use warnings;
        !            27: use utf8;
        !            28: 
        !            29: use aliased 'Apache::math_parser::Definitions';
        !            30: use aliased 'Apache::math_parser::ENode';
        !            31: use aliased 'Apache::math_parser::Operator';
        !            32: use aliased 'Apache::math_parser::ParseException';
        !            33: use aliased 'Apache::math_parser::Token';
        !            34: use aliased 'Apache::math_parser::Tokenizer';
        !            35: 
        !            36: ##
        !            37: # Constructor
        !            38: # @optional {boolean} implicit_operators - assume hidden multiplication and unit operators in some cases (unlike maxima)
        !            39: # @optional {boolean} unit_mode - handle only numerical expressions with units (no variable)
        !            40: ##
        !            41: sub new {
        !            42:     my $class = shift;
        !            43:     my $self = {
        !            44:         _implicit_operators => shift // 0,
        !            45:         _unit_mode => shift // 0,
        !            46:         _defs => Definitions->new(),
        !            47:     };
        !            48:     $self->{_defs}->define();
        !            49:     bless $self, $class;
        !            50:     return $self;
        !            51: }
        !            52: 
        !            53: # Attribute helpers
        !            54: 
        !            55: ##
        !            56: # Implicit operators ?
        !            57: # @returns {boolean}
        !            58: ##
        !            59: sub implicit_operators {
        !            60:     my $self = shift;
        !            61:     return $self->{_implicit_operators};
        !            62: }
        !            63: 
        !            64: ##
        !            65: # Unit mode ?
        !            66: # @returns {boolean}
        !            67: ##
        !            68: sub unit_mode {
        !            69:     my $self = shift;
        !            70:     return $self->{_unit_mode};
        !            71: }
        !            72: 
        !            73: ##
        !            74: # Definitions
        !            75: # @returns {Definitions}
        !            76: ##
        !            77: sub defs {
        !            78:     my $self = shift;
        !            79:     return $self->{_defs};
        !            80: }
        !            81: 
        !            82: ##
        !            83: # Tokens
        !            84: # @returns {Token[]}
        !            85: ##
        !            86: sub tokens {
        !            87:     my $self = shift;
        !            88:     return $self->{_tokens};
        !            89: }
        !            90: 
        !            91: ##
        !            92: # Current token
        !            93: # @returns {Token}
        !            94: ##
        !            95: sub current_token {
        !            96:     my $self = shift;
        !            97:     return $self->{_current_token};
        !            98: }
        !            99: 
        !           100: ##
        !           101: # Current token number
        !           102: # @returns {int}
        !           103: ##
        !           104: sub token_nr {
        !           105:     my $self = shift;
        !           106:     return $self->{_token_nr};
        !           107: }
        !           108: 
        !           109: 
        !           110: ##
        !           111: # Returns the right node at the current token, based on top-down operator precedence.
        !           112: # @param {integer} rbp - Right binding power
        !           113: # @returns {ENode}
        !           114: ##
        !           115: sub expression {
        !           116:     my( $self, $rbp ) = @_;
        !           117:     my $left; # ENode
        !           118:     my $t = $self->current_token;
        !           119:     if (! defined $t) {
        !           120:         die ParseException->new("Expected something at the end.",
        !           121:             $self->tokens->[scalar(@{$self->tokens}) - 1]->to + 1);
        !           122:     }
        !           123:     $self->advance();
        !           124:     if (! defined $t->op) {
        !           125:         $left = ENode->new($t->type, undef, $t->value, undef);
        !           126:     } elsif (! defined $t->op->nud) {
        !           127:         die ParseException->new("Unexpected operator '[_1]'.", $t->from, $t->from, $t->op->id);
        !           128:     } else {
        !           129:         $left = $t->op->nud->($t->op, $self);
        !           130:     }
        !           131:     while (defined $self->current_token && defined $self->current_token->op &&
        !           132:             $rbp < $self->current_token->op->lbp) {
        !           133:         $t = $self->current_token;
        !           134:         $self->advance();
        !           135:         $left = $t->op->led->($t->op, $self, $left);
        !           136:     }
        !           137:     return $left;
        !           138: }
        !           139: 
        !           140: ##
        !           141: # Advance to the next token,
        !           142: # expecting the given operator id if it is provided.
        !           143: # Throws a ParseException if a given operator id is not found.
        !           144: # @optional {string} id - Operator id
        !           145: ##
        !           146: sub advance {
        !           147:     my ( $self, $id ) = @_;
        !           148:     if (defined $id && (!defined $self->current_token || !defined $self->current_token->op ||
        !           149:             $self->current_token->op->id ne $id)) {
        !           150:         if (!defined $self->current_token) {
        !           151:             die ParseException->new("Expected '[_1]' at the end.",
        !           152:                 $self->tokens->[scalar(@{$self->tokens}) - 1]->to + 1, undef, $id);
        !           153:         } else {
        !           154:             die ParseException->new("Expected '[_1]'.", $self->current_token->from, undef, $id);
        !           155:         }
        !           156:     }
        !           157:     if ($self->token_nr >= scalar(@{$self->tokens})) {
        !           158:         $self->{_current_token} = undef;
        !           159:         return;
        !           160:     }
        !           161:     $self->{_current_token} = $self->tokens->[$self->token_nr];
        !           162:     $self->{_token_nr} += 1;
        !           163: }
        !           164: 
        !           165: 
        !           166: ##
        !           167: # Adds hidden multiplication and unit operators to the token stream
        !           168: ##
        !           169: sub addHiddenOperators {
        !           170:     my ( $self ) = @_;
        !           171:     my $multiplication = $self->defs->findOperator("*");
        !           172:     my $unit_operator = $self->defs->findOperator("`");
        !           173:     my $in_units = 0; # we check if we are already in the units to avoid adding two ` operators inside
        !           174:     my $in_exp = 0;
        !           175:     for (my $i=0; $i<scalar(@{$self->tokens}) - 1; $i++) {
        !           176:         my $token = $self->tokens->[$i];
        !           177:         my $next_token = $self->tokens->[$i + 1];
        !           178:         if ($self->unit_mode) {
        !           179:             if ($token->value eq "`") {
        !           180:                 $in_units = 1;
        !           181:             } elsif ($in_units) {
        !           182:                 if ($token->value eq "^") {
        !           183:                     $in_exp = 1;
        !           184:                 } elsif ($in_exp && $token->type == Token->NUMBER) {
        !           185:                     $in_exp = 0;
        !           186:                 } elsif (!$in_exp && $token->type == Token->NUMBER) {
        !           187:                     $in_units = 0;
        !           188:                 } elsif (!$in_exp && $token->type == Token->OPERATOR && index("*/^()", $token->value) == -1) {
        !           189:                     $in_units = 0;
        !           190:                 } elsif ($token->type == Token->NAME && $next_token->value eq "(") {
        !           191:                     $in_units = 0;
        !           192:                 }
        !           193:             }
        !           194:         }
        !           195:         my $token_type = $token->type;
        !           196:         my $next_token_type = $next_token->type;
        !           197:         my $token_value = $token->value;
        !           198:         my $next_token_value = $next_token->value;
        !           199:         if (
        !           200:                 ($token_type == Token->NAME && $next_token_type == Token->NAME) ||
        !           201:                 ($token_type == Token->NUMBER && $next_token_type == Token->NAME) ||
        !           202:                 ($token_type == Token->NUMBER && $next_token_type == Token->NUMBER) ||
        !           203:                 ($token_type == Token->NUMBER && $next_token_value ~~ ["(","[","{"]) ||
        !           204:                 # ($token_type == Token->NAME && $next_token_value eq "(") ||
        !           205:                 # name ( could be a function call
        !           206:                 ($token_value ~~ [")","]","}"] && $next_token_type == Token->NAME) ||
        !           207:                 ($token_value ~~ [")","]","}"] && $next_token_type == Token->NUMBER) ||
        !           208:                 ($token_value ~~ [")","]","}"] && $next_token_value eq "(")
        !           209:            ) {
        !           210:             # support for things like "(1/2) (m/s)" is complex...
        !           211:             my $units = ($self->unit_mode && !$in_units &&
        !           212:                 ($token_type == Token->NUMBER || $token_value ~~ [")","]","}"]) &&
        !           213:                 ($next_token_type == Token->NAME ||
        !           214:                     ($next_token_value ~~ ["(","[","{"] && scalar(@{$self->tokens}) > $i + 2 &&
        !           215:                     $self->tokens->[$i + 2]->type == Token->NAME)));
        !           216:             if ($units) {
        !           217:                 my( $test_token, $index_test);
        !           218:                 if ($next_token_type == Token->NAME) {
        !           219:                     $test_token = $next_token;
        !           220:                     $index_test = $i + 1;
        !           221:                 } else {
        !           222:                     # for instance for "2 (m/s)"
        !           223:                     $index_test = $i + 2;
        !           224:                     $test_token = $self->tokens->[$index_test];
        !           225:                 }
        !           226:                 if (scalar(@{$self->tokens}) > $index_test + 1 && $self->tokens->[$index_test + 1]->value eq "(") {
        !           227:                     my @known_functions = ("pow", "sqrt", "abs", "exp", "factorial", "diff",
        !           228:                         "integrate", "sum", "product", "limit", "binomial", "matrix",
        !           229:                         "ln", "log", "log10", "mod", "sgn", "ceil", "floor",
        !           230:                         "sin", "cos", "tan", "asin", "acos", "atan", "atan2",
        !           231:                         "sinh", "cosh", "tanh", "asinh", "acosh", "atanh");
        !           232:                     for (my $j=0; $j<scalar(@known_functions); $j++) {
        !           233:                         if ($test_token->value eq $known_functions[$j]) {
        !           234:                             $units = 0;
        !           235:                             last;
        !           236:                         }
        !           237:                     }
        !           238:                 }
        !           239:             }
        !           240:             my $new_token;
        !           241:             if ($units) {
        !           242:                 $new_token = Token->new(Token->OPERATOR, $next_token->from,
        !           243:                     $next_token->from, $unit_operator->id, $unit_operator);
        !           244:             } else {
        !           245:                 $new_token = Token->new(Token->OPERATOR, $next_token->from,
        !           246:                     $next_token->from, $multiplication->id, $multiplication);
        !           247:             }
        !           248:             splice(@{$self->{_tokens}}, $i+1, 0, $new_token);
        !           249:         }
        !           250:     }
        !           251: }
        !           252: 
        !           253: ##
        !           254: # Parse the string, returning an ENode tree.
        !           255: # @param {string} text - The text to parse.
        !           256: # @returns {ENode}
        !           257: ##
        !           258: sub parse {
        !           259:     my ( $self, $text ) = @_;
        !           260:     
        !           261:     my $tokenizer = Tokenizer->new($self->defs, $text);
        !           262:     @{$self->{_tokens}} = $tokenizer->tokenize();
        !           263:     if (scalar(@{$self->tokens}) == 0) {
        !           264:         die ParseException->new("No information found.");
        !           265:     }
        !           266:     if ($self->implicit_operators) {
        !           267:         $self->addHiddenOperators();
        !           268:     }
        !           269:     $self->{_token_nr} = 0;
        !           270:     $self->{_current_token} = $self->tokens->[$self->token_nr];
        !           271:     $self->advance();
        !           272:     my $root = $self->expression(0);
        !           273:     if (defined $self->current_token) {
        !           274:         die ParseException->new("Expected the end.", $self->current_token->from);
        !           275:     }
        !           276:     return $root;
        !           277: }
        !           278: 
        !           279: 1;
        !           280: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>