summaryrefslogtreecommitdiffstats
path: root/src/parser.yy
diff options
context:
space:
mode:
Diffstat (limited to 'src/parser.yy')
-rw-r--r--src/parser.yy386
1 files changed, 386 insertions, 0 deletions
diff --git a/src/parser.yy b/src/parser.yy
new file mode 100644
index 0000000..5a93621
--- /dev/null
+++ b/src/parser.yy
@@ -0,0 +1,386 @@
+/*
+ * parser.yy - part of abakus
+ * Copyright (C) 2004, 2005 Michael Pyne <[email protected]>
+ *
+ * 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., 59 Temple Place, Suite 330, 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 '=' {
+ QString 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 {
+ QString s(i18n("Function %1 is built-in and cannot be overridden.").arg("deriv"));
+ Result::setLastResult(s);
+
+ YYABORT;
+}
+
+S: DERIV '=' {
+ QString 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.
+ QString funcName = $2->name();
+ QString ident = $3->name();
+ FunctionManager *manager = FunctionManager::instance();
+
+ if(manager->isFunction(funcName) && !manager->isFunctionUserDefined(funcName)) {
+ QString 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, $4);
+ if(!manager->addFunction(newFn, ident)) {
+ QString 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;
+
+ QString funcName = $2->name();
+ QString 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, $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 {
+ QString 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;
+}