|
|
|
/*
|
|
|
|
* parser.yy - part of abakus
|
|
|
|
* Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
%{
|
|
|
|
|
|
|
|
/* Add necessary includes here. */
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kglobal.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include "result.h"
|
|
|
|
#include "node.h"
|
|
|
|
#include "function.h"
|
|
|
|
#include "valuemanager.h"
|
|
|
|
|
|
|
|
extern char *yytext;
|
|
|
|
|
|
|
|
extern int gCheckIdents;
|
|
|
|
|
|
|
|
int yylex(void);
|
|
|
|
int yyerror(const char *);
|
|
|
|
|
|
|
|
%}
|
|
|
|
|
|
|
|
%union {
|
|
|
|
Node *node;
|
|
|
|
NumericValue *value;
|
|
|
|
UnaryFunction *fn;
|
|
|
|
Identifier *ident;
|
|
|
|
}
|
|
|
|
|
|
|
|
%token <value> NUM
|
|
|
|
%type <node> EXP FACTOR TERM S EXPONENT NUMBER VALUE FINAL
|
|
|
|
%type <fn> FUNC
|
|
|
|
%token <fn> FN
|
|
|
|
%token <ident> ID
|
|
|
|
%type <ident> IDENT ASSIGN
|
|
|
|
%token POWER "**"
|
|
|
|
%token SET "set"
|
|
|
|
%token REMOVE "remove"
|
|
|
|
%token DERIV "deriv"
|
|
|
|
|
|
|
|
%%
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parser design:
|
|
|
|
*
|
|
|
|
* This is pretty standard stuff for the calculator part (read in tokens from
|
|
|
|
* the lexer, and form a syntax tree using the Node* objects). The unusual
|
|
|
|
* part is that due to the design of bison, we don't actually return a value
|
|
|
|
* normally to the calling function.
|
|
|
|
*
|
|
|
|
* Instead, we make use of the static Result::setLastResult() call in order
|
|
|
|
* to notify the calling function of the result of the parse. There are
|
|
|
|
* different statuses you can set, including Error (with a message), Null
|
|
|
|
* (which indicates that some action happened that doesn't generate a result),
|
|
|
|
* and Value (with a Node* that holds the result).
|
|
|
|
*
|
|
|
|
* If you are done parsing before reaching the FINAL token, you can call:
|
|
|
|
* YYACCEPT: Done, parsed successfully.
|
|
|
|
* YYERROR : Done, there was an error.
|
|
|
|
*
|
|
|
|
* Note that if you let the parse bubble back up to FINAL, then the result
|
|
|
|
* will always be a Value.
|
|
|
|
*/
|
|
|
|
|
|
|
|
FINAL: { gCheckIdents = 1; } S {
|
|
|
|
Result::setLastResult(NodePtr($2));
|
|
|
|
$$ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
S: EXP { $$ = $1; }
|
|
|
|
|
|
|
|
// Rudimentary error handling
|
|
|
|
S: error '=' {
|
|
|
|
Result::setLastResult(i18n("This is an invalid assignment."));
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can't assign to a function.
|
|
|
|
S: FUNC '=' {
|
|
|
|
TQString s(i18n("You can't assign to function %1").arg($1->name()));
|
|
|
|
Result::setLastResult(s);
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a function prototype. abakus currently only supports one-argument
|
|
|
|
// functions.
|
|
|
|
ASSIGN: '(' { --gCheckIdents; } IDENT ')' '=' {
|
|
|
|
$$ = $3;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Blocking a variable with the name deriv is a slight feature regression
|
|
|
|
// since normally functions and variables with the same name can coexist, but
|
|
|
|
// I don't want to duplicate code all over the place.
|
|
|
|
S: SET DERIV {
|
|
|
|
TQString s(i18n("Function %1 is built-in and cannot be overridden.").arg("deriv"));
|
|
|
|
Result::setLastResult(s);
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
S: DERIV '=' {
|
|
|
|
TQString s(i18n("Function %1 is built-in and cannot be overridden.").arg("deriv"));
|
|
|
|
Result::setLastResult(s);
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
S: SET FUNC ASSIGN EXP {
|
|
|
|
++gCheckIdents;
|
|
|
|
|
|
|
|
// We're trying to reassign an already defined function, make sure it's
|
|
|
|
// not a built-in.
|
|
|
|
TQString funcName = $2->name();
|
|
|
|
TQString ident = $3->name();
|
|
|
|
FunctionManager *manager = FunctionManager::instance();
|
|
|
|
|
|
|
|
if(manager->isFunction(funcName) && !manager->isFunctionUserDefined(funcName)) {
|
|
|
|
TQString s(i18n("Function %1 is built-in and cannot be overridden.").arg(funcName));
|
|
|
|
Result::setLastResult(s);
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(manager->isFunction(funcName))
|
|
|
|
manager->removeFunction(funcName);
|
|
|
|
|
|
|
|
BaseFunction *newFn = new UserDefinedFunction(funcName.ascii(), $4);
|
|
|
|
if(!manager->addFunction(newFn, ident)) {
|
|
|
|
TQString s(i18n("Unable to define function %1 because it is recursive.").arg(funcName));
|
|
|
|
Result::setLastResult(s);
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result::setLastResult(Result::Null);
|
|
|
|
YYACCEPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// IDENT is the same as FUNC, except that the lexer has determined that IDENT
|
|
|
|
// is not already a FUNC.
|
|
|
|
S: SET IDENT ASSIGN EXP {
|
|
|
|
++gCheckIdents;
|
|
|
|
|
|
|
|
TQString funcName = $2->name();
|
|
|
|
TQString ident = $3->name();
|
|
|
|
|
|
|
|
// No need to check if the function is already defined, because the
|
|
|
|
// lexer checked for us before returning the IDENT token.
|
|
|
|
BaseFunction *newFn = new UserDefinedFunction(funcName.ascii(), $4);
|
|
|
|
FunctionManager::instance()->addFunction(newFn, ident);
|
|
|
|
|
|
|
|
Result::setLastResult(Result::Null);
|
|
|
|
YYACCEPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove a defined function.
|
|
|
|
S: REMOVE FUNC '(' ')' {
|
|
|
|
FunctionManager::instance()->removeFunction($2->name());
|
|
|
|
|
|
|
|
Result::setLastResult(Result::Null);
|
|
|
|
YYACCEPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can't remove an ident using remove-func syntax.
|
|
|
|
S: REMOVE IDENT '(' ')' {
|
|
|
|
// This is an error
|
|
|
|
Result::setLastResult(Result(i18n("Function %1 is not defined.").arg($2->name())));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This happens when the user tries to remove a function that's not defined.
|
|
|
|
S: REMOVE IDENT '(' IDENT ')' {
|
|
|
|
// This is an error
|
|
|
|
Result::setLastResult(Result(i18n("Function %1 is not defined.").arg($2->name())));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
S: REMOVE IDENT {
|
|
|
|
ValueManager *manager = ValueManager::instance();
|
|
|
|
|
|
|
|
if(manager->isValueSet($2->name()) && !manager->isValueReadOnly($2->name())) {
|
|
|
|
manager->removeValue($2->name());
|
|
|
|
|
|
|
|
Result::setLastResult(Result::Null);
|
|
|
|
YYACCEPT;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
TQString s;
|
|
|
|
if(manager->isValueSet($2->name()))
|
|
|
|
s = i18n("Can't remove predefined variable %1.").arg($2->name());
|
|
|
|
else
|
|
|
|
s = i18n("Can't remove undefined variable %1.").arg($2->name());
|
|
|
|
|
|
|
|
Result::setLastResult(s);
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
S: SET IDENT '=' EXP {
|
|
|
|
ValueManager *vm = ValueManager::instance();
|
|
|
|
|
|
|
|
if(vm->isValueReadOnly($2->name())) {
|
|
|
|
if($2->name() == "pi" && $4->value() == Abakus::number_t("3.0"))
|
|
|
|
Result::setLastResult(i18n("This isn't Indiana, you can't just change pi"));
|
|
|
|
else
|
|
|
|
Result::setLastResult(i18n("%1 is a constant").arg($2->name()));
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
ValueManager::instance()->setValue($2->name(), $4->value());
|
|
|
|
|
|
|
|
Result::setLastResult(Result::Null);
|
|
|
|
YYACCEPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a variable.
|
|
|
|
S: IDENT '=' EXP {
|
|
|
|
ValueManager *vm = ValueManager::instance();
|
|
|
|
|
|
|
|
if(vm->isValueReadOnly($1->name())) {
|
|
|
|
if($1->name() == "pi" && $3->value() == Abakus::number_t("3.0"))
|
|
|
|
Result::setLastResult(i18n("This isn't Indiana, you can't just change pi"));
|
|
|
|
else
|
|
|
|
Result::setLastResult(i18n("%1 is a constant").arg($1->name()));
|
|
|
|
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
ValueManager::instance()->setValue($1->name(), $3->value());
|
|
|
|
|
|
|
|
Result::setLastResult(Result::Null);
|
|
|
|
YYACCEPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
S: NUMBER '=' {
|
|
|
|
Result::setLastResult(i18n("Can't assign to %1").arg($1->value().toString()));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can't call this as a function.
|
|
|
|
TERM: IDENT '(' {
|
|
|
|
Result::setLastResult(i18n("%1 isn't a function (or operator expected)").arg($1->name()));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can't do this either.
|
|
|
|
TERM: IDENT IDENT {
|
|
|
|
Result::setLastResult(i18n("Missing operator"));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
TERM: IDENT NUMBER {
|
|
|
|
Result::setLastResult(i18n("Missing operator"));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
TERM: NUMBER NUMBER {
|
|
|
|
Result::setLastResult(i18n("Missing operator"));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
S: error {
|
|
|
|
Result::setLastResult(i18n("Sorry, I can't figure it out."));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Here be the standard calculator-parsing part. Nothing here should be too
|
|
|
|
* fancy.
|
|
|
|
*/
|
|
|
|
EXP: EXP '+' FACTOR { $$ = new BinaryOperator(BinaryOperator::Addition, $1, $3); }
|
|
|
|
EXP: EXP '-' FACTOR { $$ = new BinaryOperator(BinaryOperator::Subtraction, $1, $3); }
|
|
|
|
EXP: FACTOR { $$ = $1; }
|
|
|
|
|
|
|
|
FACTOR: FACTOR '*' EXPONENT { $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $3); }
|
|
|
|
FACTOR: FACTOR '/' EXPONENT { $$ = new BinaryOperator(BinaryOperator::Division, $1, $3); }
|
|
|
|
FACTOR: EXPONENT { $$ = $1; }
|
|
|
|
|
|
|
|
EXPONENT: TERM POWER EXPONENT { $$ = new BinaryOperator(BinaryOperator::Exponentiation, $1, $3); }
|
|
|
|
EXPONENT: TERM { $$ = $1; }
|
|
|
|
|
|
|
|
TERM: '+' VALUE { $$ = $2; }
|
|
|
|
TERM: '-' VALUE { $$ = new UnaryOperator(UnaryOperator::Negation, $2); }
|
|
|
|
TERM: '(' EXP ')' { $$ = $2; }
|
|
|
|
TERM: '-' '(' EXP ')' { $$ = new UnaryOperator(UnaryOperator::Negation, $3); }
|
|
|
|
|
|
|
|
TERM: VALUE { $$ = $1; }
|
|
|
|
|
|
|
|
VALUE: NUMBER { $$ = $1; }
|
|
|
|
|
|
|
|
NUMBER: NUM {
|
|
|
|
KLocale *locale = KGlobal::locale();
|
|
|
|
QChar decimal = locale->decimalSymbol()[0];
|
|
|
|
|
|
|
|
// Replace current decimal separator with US Decimal separator to be
|
|
|
|
// evil.
|
|
|
|
unsigned len = strlen(yytext);
|
|
|
|
for(unsigned i = 0; i < len; ++i)
|
|
|
|
if(yytext[i] == decimal)
|
|
|
|
yytext[i] = '.';
|
|
|
|
|
|
|
|
Abakus::number_t value(yytext);
|
|
|
|
|
|
|
|
$$ = new NumericValue(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
TERM: DERIV { --gCheckIdents; } '(' EXP ',' { ++gCheckIdents; } EXP ')' {
|
|
|
|
$$ = new DerivativeFunction($4, $7);
|
|
|
|
}
|
|
|
|
|
|
|
|
TERM: FUNC TERM {
|
|
|
|
$1->setOperand($2);
|
|
|
|
$$ = $1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle implicit multiplication */
|
|
|
|
TERM: NUMBER FUNC TERM {
|
|
|
|
$2->setOperand($3);
|
|
|
|
$$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $2);
|
|
|
|
}
|
|
|
|
|
|
|
|
TERM: NUMBER '(' EXP ')' {
|
|
|
|
$$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $3);
|
|
|
|
}
|
|
|
|
|
|
|
|
TERM: NUMBER IDENT {
|
|
|
|
if(gCheckIdents > 0 && !ValueManager::instance()->isValueSet($2->name())) {
|
|
|
|
Result::setLastResult(i18n("Unknown variable %1").arg($2->name()));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
|
|
|
|
$$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $2);
|
|
|
|
}
|
|
|
|
|
|
|
|
VALUE: IDENT {
|
|
|
|
if(gCheckIdents <= 0 || ValueManager::instance()->isValueSet($1->name()))
|
|
|
|
$$ = $1;
|
|
|
|
else {
|
|
|
|
Result::setLastResult(i18n("Unknown variable %1").arg($1->name()));
|
|
|
|
YYABORT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
IDENT: ID {
|
|
|
|
$$ = new Identifier(yytext);
|
|
|
|
}
|
|
|
|
|
|
|
|
FUNC: FN {
|
|
|
|
/* No check necessary, the lexer has already checked for us. */
|
|
|
|
$$ = new BuiltinFunction(yytext, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
%%
|
|
|
|
|
|
|
|
int gCheckIdents = 0;
|
|
|
|
|
|
|
|
int yyerror(const char *)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|