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>