1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-26 16:45:21 +00:00

Compare commits

..

3 Commits

Author SHA1 Message Date
rileyJones
051be8d49e add stop on node 2017-08-02 11:04:35 -07:00
rileyJones
f464bcff6f Add stop function by ignoring result of calculations 2017-08-01 12:06:58 -07:00
rileyJones
dd915136c6 Add Stop Button 2017-07-31 14:55:20 -07:00
22 changed files with 373 additions and 721 deletions

View File

@@ -5,10 +5,10 @@ Summer project for NWAPW.
Created by Arthur Drobot, Danila Fedorin and Riley Jones. Created by Arthur Drobot, Danila Fedorin and Riley Jones.
## Project Description ## Project Description
Abacus is a calculator built with extensibility and usability in mind. It provides a plugin interface, via Java, as Lua proves too difficult to link up to the Java core. The description of the internals of the project can be found on the wiki page. Abacus is a calculator built with extensibility and usability in mind. It provides a plugin interface, via Java, as Lua provides too difficult to link up to the Java core. The description of the internals of the project can be found on the wiki page.
## Current State ## Current State
Abacus is being built for the Northwest Advanced Programming Workshop, a 3 week program in which students work in teams to complete a single project, following principles of agile development. Because of its short timeframe, Abacus is not even close to completed state. Below is a list of the current features and problems. Abacus is being built for the Northwest Advanced Programming Workshop, a 3 week program in which students work in treams to complete a single project, following principles of agile development. Because of its short timeframe, Abacus is not even close to completed state. Below is a list of the current features and problems.
- [x] Basic number class - [x] Basic number class
- [x] Implementation of basic functions - [x] Implementation of basic functions
- [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series - [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series

View File

@@ -1,7 +1,6 @@
package org.nwapw.abacus; package org.nwapw.abacus;
import org.nwapw.abacus.config.Configuration; import org.nwapw.abacus.config.ConfigurationObject;
import org.nwapw.abacus.fx.AbacusApplication;
import org.nwapw.abacus.number.NaiveNumber; import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer; import org.nwapw.abacus.parsing.LexerTokenizer;
@@ -12,7 +11,9 @@ import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin; import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.NumberReducer; import org.nwapw.abacus.tree.NumberReducer;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
import org.nwapw.abacus.window.Window;
import javax.swing.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@@ -46,21 +47,24 @@ public class Abacus {
/** /**
* The configuration loaded from a file. * The configuration loaded from a file.
*/ */
private Configuration configuration; private ConfigurationObject configuration;
/** /**
* The tree builder used to construct a tree * The tree builder used to construct a tree
* from a string. * from a string.
*/ */
private TreeBuilder treeBuilder; private TreeBuilder treeBuilder;
private Window window;
public boolean getStop(){
return window.getStop();
}
/** /**
* Creates a new instance of the Abacus calculator. * Creates a new instance of the Abacus calculator.
*/ */
public Abacus() { public Abacus() {
pluginManager = new PluginManager(); pluginManager = new PluginManager();
numberReducer = new NumberReducer(this); numberReducer = new NumberReducer(this);
configuration = new Configuration(CONFIG_FILE); configuration = new ConfigurationObject(CONFIG_FILE);
configuration.saveTo(CONFIG_FILE); configuration.save(CONFIG_FILE);
LexerTokenizer lexerTokenizer = new LexerTokenizer(); LexerTokenizer lexerTokenizer = new LexerTokenizer();
ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this); ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this);
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser); treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
@@ -78,7 +82,13 @@ public class Abacus {
} }
public static void main(String[] args) { public static void main(String[] args) {
AbacusApplication.launch(AbacusApplication.class, args); try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException e) {
e.printStackTrace();
}
new Window(new Abacus()).setVisible(true);
} }
/** /**
@@ -114,7 +124,7 @@ public class Abacus {
* *
* @return the configuration object. * @return the configuration object.
*/ */
public Configuration getConfiguration() { public ConfigurationObject getConfiguration() {
return configuration; return configuration;
} }
@@ -126,7 +136,7 @@ public class Abacus {
* @return the resulting tree, null if the tree builder or the produced tree are null. * @return the resulting tree, null if the tree builder or the produced tree are null.
*/ */
public TreeNode parseString(String input) { public TreeNode parseString(String input) {
return treeBuilder.fromString(input); return treeBuilder.fromString(input,this);
} }
/** /**
@@ -158,4 +168,8 @@ public class Abacus {
} }
return null; return null;
} }
public void setWindow(Window window) {
this.window = window;
}
} }

View File

@@ -1,83 +1,14 @@
package org.nwapw.abacus.config; package org.nwapw.abacus.config;
import com.moandjiezana.toml.Toml;
import com.moandjiezana.toml.TomlWriter;
import java.io.File;
import java.io.IOException;
/** /**
* The configuration object that stores * Serializable class that will be used to load TOML
* options that the user can change. * configurations.
*/ */
public class Configuration { public class Configuration {
/** /**
* The TOML writer used to write this configuration to a file. * The type of number this calculator should use.
*/ */
private static final TomlWriter TOML_WRITER = new TomlWriter(); public String numberType;
/**
* The TOML reader used to load this config from a file.
*/
private static final Toml TOML_READER = new Toml();
/**
* The implementation of the number that should be used.
*/
private String numberImplementation = "naive";
/**
* Creates a new configuration with the given values.
* @param numberImplementation the number implementation, like "naive" or "precise"
*/
public Configuration(String numberImplementation){
this.numberImplementation = numberImplementation;
}
/**
* Loads a configuration from a given file, keeping non-specified fields default.
* @param fromFile the file to load from.
*/
public Configuration(File fromFile){
if(!fromFile.exists()) return;
copyFrom(TOML_READER.read(fromFile).to(Configuration.class));
}
/**
* Copies the values from the given configuration into this one.
* @param otherConfiguration the configuration to copy from.
*/
public void copyFrom(Configuration otherConfiguration){
this.numberImplementation = otherConfiguration.numberImplementation;
}
/**
* Saves this configuration to the given file, creating
* any directories that do not exist.
* @param file the file to save to.
*/
public void saveTo(File file){
if(file.getParentFile() != null) file.getParentFile().mkdirs();
try {
TOML_WRITER.write(this, file);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets the number implementation from this configuration.
* @return the number implementation.
*/
public String getNumberImplementation() {
return numberImplementation;
}
/**
* Sets the number implementation for the configuration
* @param numberImplementation the number implementation.
*/
public void setNumberImplementation(String numberImplementation) {
this.numberImplementation = numberImplementation;
}
} }

View File

@@ -0,0 +1,111 @@
package org.nwapw.abacus.config;
import com.moandjiezana.toml.Toml;
import com.moandjiezana.toml.TomlWriter;
import java.io.File;
import java.io.IOException;
/**
* A configuration object, which essentially
* manages saving, loading, and getting values
* from the configuration. While Configuration is
* the data model, this is the interface with it.
*/
public class ConfigurationObject {
/**
* The writer used to store the configuration.
*/
private static final TomlWriter TOML_WRITER = new TomlWriter();
/**
* The configuration instance being modeled.
*/
private Configuration configuration;
/**
* Creates a new configuration object with the given config.
*
* @param config the config to use.
*/
public ConfigurationObject(Configuration config) {
setup(config);
}
/**
* Create a configuration object by attempting to
* load a config from the given path, using the
* default configuration otherwise.
*
* @param path the path to attempt to load.
*/
public ConfigurationObject(File path) {
Configuration config;
if (!path.exists()) {
config = getDefaultConfig();
} else {
Toml parse = new Toml();
parse.read(path);
config = parse.to(Configuration.class);
}
setup(config);
}
/**
* Creates a new configuration object with the
* default configuration.
*/
public ConfigurationObject() {
setup(getDefaultConfig());
}
/**
* Sets up the ConfigurationObject.
* different constructors do different things,
* but they all lead here.
*
* @param configuration the configuration to set up with.
*/
private void setup(Configuration configuration) {
this.configuration = configuration;
}
/**
* Creates a default configuration.
*
* @return the newly created default configuration.
*/
private Configuration getDefaultConfig() {
configuration = new Configuration();
configuration.numberType = "naive";
return configuration;
}
/**
* Returns the implementation the user has requested to
* represent their numbers.
*
* @return the implementation name.
*/
public String getNumberImplementation() {
return configuration.numberType;
}
/**
* Saves the ConfigurationObject to the given file.
*
* @param toFile the file to save ot.
* @return true if the save succeed, false if otherwise.
*/
public boolean save(File toFile) {
if (toFile.getParentFile() != null) toFile.getParentFile().mkdirs();
try {
TOML_WRITER.write(configuration, toFile);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}

View File

@@ -4,5 +4,5 @@ package org.nwapw.abacus.function;
* The type of an operator, describing how it should behave. * The type of an operator, describing how it should behave.
*/ */
public enum OperatorType { public enum OperatorType {
BINARY_INFIX, UNARY_POSTFIX, UNARY_PREFIX BINARY_INFIX, UNARY_POSTFIX
} }

View File

@@ -1,24 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
* The main application class for JavaFX responsible for loading
* and displaying the fxml file.
*/
public class AbacusApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent parent = FXMLLoader.load(getClass().getResource("/abacus.fxml"));
Scene mainScene = new Scene(parent, 320, 480);
primaryStage.setScene(mainScene);
primaryStage.setTitle("Abacus");
primaryStage.show();
}
}

View File

@@ -1,167 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.text.Text;
import javafx.util.Callback;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.tree.TreeNode;
/**
* The controller for the abacus FX UI, responsible
* for all the user interaction.
*/
public class AbacusController {
/**
* Constant string that is displayed if the text could not be lexed or parsed.
*/
private static final String ERR_SYNTAX = "Syntax Error";
/**
* Constant string that is displayed if the tree could not be reduced.
*/
private static final String ERR_EVAL = "Evaluation Error";
/**
* Constant string that is displayed if the calculations are stopped before they are done.
*/
private static final String ERR_STOP = "Stopped";
@FXML
private TableView<HistoryModel> historyTable;
@FXML
private TableColumn<HistoryModel, String> inputColumn;
@FXML
private TableColumn<HistoryModel, String> parsedColumn;
@FXML
private TableColumn<HistoryModel, String> outputColumn;
@FXML
private Text outputText;
@FXML
private TextField inputField;
@FXML
private Button inputButton;
@FXML
private ComboBox<String> numberImplementationBox;
/**
* The list of history entries, created by the users.
*/
private ObservableList<HistoryModel> historyData;
/**
* The abacus instance used for calculations and all
* other main processing code.
*/
private ObservableList<String> numberImplementationOptions;
/**
* Thread used for calculating.
*/
private Thread calcThread;
/**
* Checks whether the calculator is calculating.
*/
private boolean calculating;
/**
* Seconds delayed for timer;
*/
private double delay = 0;
private Abacus abacus;
@FXML
public void initialize(){
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
param -> new CopyableCell<>();
historyData = FXCollections.observableArrayList();
historyTable.setItems(historyData);
numberImplementationOptions = FXCollections.observableArrayList();
numberImplementationBox.setItems(numberImplementationOptions);
numberImplementationBox.valueProperty().addListener((observable, oldValue, newValue)
-> {
abacus.getConfiguration().setNumberImplementation(newValue);
abacus.getConfiguration().saveTo(Abacus.CONFIG_FILE);
});
historyTable.getSelectionModel().setCellSelectionEnabled(true);
inputColumn.setCellFactory(cellFactory);
inputColumn.setCellValueFactory(cell -> cell.getValue().inputProperty());
parsedColumn.setCellFactory(cellFactory);
parsedColumn.setCellValueFactory(cell -> cell.getValue().parsedProperty());
outputColumn.setCellFactory(cellFactory);
outputColumn.setCellValueFactory(cell -> cell.getValue().outputProperty());
abacus = new Abacus();
numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumbers());
String actualImplementation = abacus.getConfiguration().getNumberImplementation();
String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : "naive";
numberImplementationBox.getSelectionModel().select(toSelect);
}
@FXML
private void performCalculation(){
Runnable calculator = new Runnable(){
public void run() {
if(delay>0) {
Runnable timer = new Runnable() {
public void run() {
long gap = (long) (delay * 1000);
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime <= gap) {
}
stopCalculation();
}
};
Thread maxTime = new Thread(timer);
maxTime.setName("maxTime");
maxTime.start();
}
calculating = true;
Platform.runLater(() -> inputButton.setDisable(true));
TreeNode constructedTree = abacus.parseString(inputField.getText());
if (constructedTree == null) {
Platform.runLater(() ->outputText.setText(ERR_SYNTAX));
Platform.runLater(() -> inputButton.setDisable(false));
//return;
}else {
NumberInterface evaluatedNumber = abacus.evaluateTree(constructedTree);
if (evaluatedNumber == null) {
if(Thread.currentThread().isInterrupted()){
Platform.runLater(() -> outputText.setText(ERR_STOP));
Platform.runLater(() -> inputButton.setDisable(false));
}else {
Platform.runLater(() -> outputText.setText(ERR_EVAL));
Platform.runLater(() -> inputButton.setDisable(false));
//return;
}
} else {
Platform.runLater(() -> outputText.setText(evaluatedNumber.toString()));
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), evaluatedNumber.toString()));
Platform.runLater(() -> inputButton.setDisable(false));
Platform.runLater(() -> inputField.setText(""));
}
}
calculating = false;
}
};
if(!calculating) {
calcThread = new Thread(calculator);
calcThread.setName("calcThread");
calcThread.start();
}
}
@FXML
private void stopCalculation(){
calcThread.interrupt();
calculating = false;
//Platform.runLater(() ->inputButton.setDisable(false));
}
}

View File

@@ -1,35 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.scene.control.TableCell;
import javafx.scene.input.MouseEvent;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
/**
* A cell that copies its value to the clipboard
* when double clicked.
* @param <S> The type of the table view generic type.
* @param <T> The type of the value contained in the cell.
*/
public class CopyableCell<S, T> extends TableCell<S, T> {
/**
* Creates a new copyable cell.
*/
public CopyableCell(){
addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
if(event.getClickCount() == 2){
Toolkit.getDefaultToolkit().getSystemClipboard()
.setContents(new StringSelection(getText()), null);
}
});
}
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText((empty || item == null) ? null : item.toString());
setGraphic(null);
}
}

View File

@@ -1,87 +0,0 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* The data model used for storing history entries.
*/
public class HistoryModel {
/**
* The property used for displaying the column
* for the user input.
*/
private final StringProperty input;
/**
* The property used for displaying the column
* that contains the parsed input.
*/
private final StringProperty parsed;
/**
* The property used for displaying the column
* that contains the program output.
*/
private final StringProperty output;
/**
* Creates a new history model with the given variables.
* @param input the user input
* @param parsed the parsed input
* @param output the program output.
*/
public HistoryModel(String input, String parsed, String output){
this.input = new SimpleStringProperty();
this.parsed = new SimpleStringProperty();
this.output = new SimpleStringProperty();
this.input.setValue(input);
this.parsed.setValue(parsed);
this.output.setValue(output);
}
/**
* Gets the input property.
* @return the input property.
*/
public StringProperty inputProperty() {
return input;
}
/**
* Gets the input.
* @return the input.
*/
public String getInput() {
return input.get();
}
/**
* Gets the parsed input property.
* @return the parsed input property.
*/
public StringProperty parsedProperty() {
return parsed;
}
/**
* Gets the parsed input.
* @return the parsed input.
*/
public String getParsed() {
return parsed.get();
}
/**
* Gets the output property.
* @return the output property.
*/
public StringProperty outputProperty() {
return output;
}
/**
* Gets the program output.
* @return the output.
*/
public String getOutput() {
return output.get();
}
}

View File

@@ -93,11 +93,6 @@ public class NaiveNumber implements NumberInterface {
return this.compareTo(ZERO); return this.compareTo(ZERO);
} }
@Override
public int ceiling() {
return (int) Math.ceil(value);
}
@Override @Override
public NumberInterface promoteTo(Class<? extends NumberInterface> toClass) { public NumberInterface promoteTo(Class<? extends NumberInterface> toClass) {
if (toClass == this.getClass()) return this; if (toClass == this.getClass()) return this;

View File

@@ -79,12 +79,6 @@ public interface NumberInterface {
*/ */
int signum(); int signum();
/**
* Returns the least integer greater than or equal to the number.
* @return the least integer >= the number, if int can hold the value.
*/
int ceiling();
/** /**
* Promotes this class to another number class. * Promotes this class to another number class.
* *

View File

@@ -3,10 +3,6 @@ package org.nwapw.abacus.number;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
/**
* A number that uses a BigDecimal to store its value,
* leading to infinite possible precision.
*/
public class PreciseNumber implements NumberInterface { public class PreciseNumber implements NumberInterface {
/** /**
@@ -48,12 +44,12 @@ public class PreciseNumber implements NumberInterface {
@Override @Override
public int getMaxPrecision() { public int getMaxPrecision() {
return 65; return 54;
} }
@Override @Override
public NumberInterface multiply(NumberInterface multiplier) { public NumberInterface multiply(NumberInterface multiplier) {
return new PreciseNumber(this.value.multiply(((PreciseNumber) multiplier).value)); return new PreciseNumber(value.multiply(((PreciseNumber) multiplier).value));
} }
@Override @Override
@@ -98,11 +94,6 @@ public class PreciseNumber implements NumberInterface {
return value.signum(); return value.signum();
} }
@Override
public int ceiling() {
return (int) Math.ceil(value.doubleValue());
}
@Override @Override
public NumberInterface negate() { public NumberInterface negate() {
return new PreciseNumber(value.negate()); return new PreciseNumber(value.negate());
@@ -118,7 +109,7 @@ public class PreciseNumber implements NumberInterface {
@Override @Override
public String toString() { public String toString() {
BigDecimal rounded = value.setScale(getMaxPrecision() - 15, RoundingMode.HALF_UP); BigDecimal rounded = value.setScale(getMaxPrecision() - 4, RoundingMode.HALF_UP);
return rounded.stripTrailingZeros().toPlainString(); return rounded.stripTrailingZeros().toPlainString();
} }
} }

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.parsing; package org.nwapw.abacus.parsing;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
import java.util.List; import java.util.List;
@@ -18,5 +19,5 @@ public interface Parser<T> {
* @param tokens the tokens to construct a tree from. * @param tokens the tokens to construct a tree from.
* @return the constructed tree, or null on error. * @return the constructed tree, or null on error.
*/ */
public TreeNode constructTree(List<T> tokens); public TreeNode constructTree(List<T> tokens,Abacus trace);
} }

View File

@@ -34,6 +34,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
*/ */
private Map<String, OperatorType> typeMap; private Map<String, OperatorType> typeMap;
//private Abacus trace;
/** /**
* Creates a new Shunting Yard parser with the given Abacus instance. * Creates a new Shunting Yard parser with the given Abacus instance.
* *
@@ -55,12 +57,9 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
public List<Match<TokenType>> intoPostfix(List<Match<TokenType>> from) { public List<Match<TokenType>> intoPostfix(List<Match<TokenType>> from) {
ArrayList<Match<TokenType>> output = new ArrayList<>(); ArrayList<Match<TokenType>> output = new ArrayList<>();
Stack<Match<TokenType>> tokenStack = new Stack<>(); Stack<Match<TokenType>> tokenStack = new Stack<>();
TokenType previousType;
TokenType matchType = null;
while (!from.isEmpty()) { while (!from.isEmpty()) {
Match<TokenType> match = from.remove(0); Match<TokenType> match = from.remove(0);
previousType = matchType; TokenType matchType = match.getType();
matchType = match.getType();
if (matchType == TokenType.NUM) { if (matchType == TokenType.NUM) {
output.add(match); output.add(match);
} else if (matchType == TokenType.FUNCTION) { } else if (matchType == TokenType.FUNCTION) {
@@ -77,13 +76,7 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
continue; continue;
} }
if(tokenString.equals("-") && (previousType == null || previousType == TokenType.OP || while (!tokenStack.empty()) {
previousType == TokenType.OPEN_PARENTH)){
from.add(0, new Match<>("`", TokenType.OP));
continue;
}
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
Match<TokenType> otherMatch = tokenStack.peek(); Match<TokenType> otherMatch = tokenStack.peek();
TokenType otherMatchType = otherMatch.getType(); TokenType otherMatchType = otherMatch.getType();
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break; if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
@@ -112,8 +105,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
} }
while (!tokenStack.empty()) { while (!tokenStack.empty()) {
Match<TokenType> match = tokenStack.peek(); Match<TokenType> match = tokenStack.peek();
TokenType newMatchType = match.getType(); TokenType matchType = match.getType();
if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION)) return null; if (!(matchType == TokenType.OP || matchType == TokenType.FUNCTION)) return null;
output.add(tokenStack.pop()); output.add(tokenStack.pop());
} }
return output; return output;
@@ -125,7 +118,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
* @param matches the list of tokens from the source string. * @param matches the list of tokens from the source string.
* @return the construct tree expression. * @return the construct tree expression.
*/ */
public TreeNode constructRecursive(List<Match<TokenType>> matches) { public TreeNode constructRecursive(List<Match<TokenType>> matches,Abacus trace) {
//this.trace = trace;
if (matches.size() == 0) return null; if (matches.size() == 0) return null;
Match<TokenType> match = matches.remove(0); Match<TokenType> match = matches.remove(0);
TokenType matchType = match.getType(); TokenType matchType = match.getType();
@@ -133,22 +127,22 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
String operator = match.getContent(); String operator = match.getContent();
OperatorType type = typeMap.get(operator); OperatorType type = typeMap.get(operator);
if (type == OperatorType.BINARY_INFIX) { if (type == OperatorType.BINARY_INFIX) {
TreeNode right = constructRecursive(matches); TreeNode right = constructRecursive(matches,trace);
TreeNode left = constructRecursive(matches); TreeNode left = constructRecursive(matches,trace);
if (left == null || right == null) return null; if (left == null || right == null) return null;
else return new BinaryNode(operator, left, right); else return new BinaryInfixNode(operator, left, right,trace);
} else { } else {
TreeNode applyTo = constructRecursive(matches); TreeNode applyTo = constructRecursive(matches,trace);
if (applyTo == null) return null; if (applyTo == null) return null;
else return new UnaryNode(operator, applyTo); else return new UnaryPrefixNode(operator, applyTo,trace);
} }
} else if (matchType == TokenType.NUM) { } else if (matchType == TokenType.NUM) {
return new NumberNode(abacus.numberFromString(match.getContent())); return new NumberNode(abacus.numberFromString(match.getContent()));
} else if (matchType == TokenType.FUNCTION) { } else if (matchType == TokenType.FUNCTION) {
String functionName = match.getContent(); String functionName = match.getContent();
FunctionNode node = new FunctionNode(functionName); FunctionNode node = new FunctionNode(functionName,trace);
while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) { while (!matches.isEmpty() && matches.get(0).getType() != TokenType.INTERNAL_FUNCTION_END) {
TreeNode argument = constructRecursive(matches); TreeNode argument = constructRecursive(matches,trace);
if (argument == null) return null; if (argument == null) return null;
node.prependChild(argument); node.prependChild(argument);
} }
@@ -160,10 +154,10 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
} }
@Override @Override
public TreeNode constructTree(List<Match<TokenType>> tokens) { public TreeNode constructTree(List<Match<TokenType>> tokens,Abacus trace) {
tokens = intoPostfix(new ArrayList<>(tokens)); tokens = intoPostfix(new ArrayList<>(tokens));
Collections.reverse(tokens); Collections.reverse(tokens);
return constructRecursive(tokens); return constructRecursive(tokens,trace);
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.parsing; package org.nwapw.abacus.parsing;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.tree.TreeNode; import org.nwapw.abacus.tree.TreeNode;
import java.util.List; import java.util.List;
@@ -41,10 +42,10 @@ public class TreeBuilder<T> {
* @param input the string to parse into a tree. * @param input the string to parse into a tree.
* @return the resulting tree. * @return the resulting tree.
*/ */
public TreeNode fromString(String input) { public TreeNode fromString(String input,Abacus trace) {
List<T> tokens = tokenizer.tokenizeString(input); List<T> tokens = tokenizer.tokenizeString(input);
if (tokens == null) return null; if (tokens == null) return null;
return parser.constructTree(tokens); return parser.constructTree(tokens,trace);
} }
} }

View File

@@ -8,8 +8,6 @@ import org.nwapw.abacus.number.NaiveNumber;
import org.nwapw.abacus.number.NumberInterface; import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.number.PreciseNumber; import org.nwapw.abacus.number.PreciseNumber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.BiFunction; import java.util.function.BiFunction;
/** /**
@@ -18,8 +16,6 @@ import java.util.function.BiFunction;
*/ */
public class StandardPlugin extends Plugin { public class StandardPlugin extends Plugin {
private static HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> factorialLists = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
/** /**
* The addition operator, + * The addition operator, +
*/ */
@@ -31,13 +27,9 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted())
return null;
NumberInterface sum = params[0]; NumberInterface sum = params[0];
for (int i = 1; i < params.length; i++) { for (int i = 1; i < params.length; i++) {
sum = sum.add(params[i]); sum = sum.add(params[i]);
if(Thread.currentThread().isInterrupted())
return null;
} }
return sum; return sum;
} }
@@ -53,26 +45,7 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted())
return null;
return params[0].subtract(params[1]); return params[0].subtract(params[1]);
}
});
/**
* The negation operator, -
*/
public static final Operator OP_NEGATE = new Operator(OperatorAssociativity.LEFT, OperatorType.UNARY_PREFIX, 0, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted())
return null;
return params[0].negate();
} }
}); });
/** /**
@@ -86,13 +59,9 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted())
return null;
NumberInterface product = params[0]; NumberInterface product = params[0];
for (int i = 1; i < params.length; i++) { for (int i = 1; i < params.length; i++) {
product = product.multiply(params[i]); product = product.multiply(params[i]);
if(Thread.currentThread().isInterrupted())
return null;
} }
return product; return product;
} }
@@ -103,14 +72,16 @@ public class StandardPlugin extends Plugin {
public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() { public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override @Override
protected boolean matchesParams(NumberInterface[] params) { protected boolean matchesParams(NumberInterface[] params) {
return params.length == 2; return params.length >= 1;
} }
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted()) NumberInterface product = params[0];
return null; for (int i = 1; i < params.length; i++) {
return params[0].divide(params[1]); product = product.multiply(params[i]);
}
return product;
} }
}); });
/** /**
@@ -125,19 +96,15 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted())
return null;
if (params[0].signum() == 0) { if (params[0].signum() == 0) {
return (new NaiveNumber(1)).promoteTo(params[0].getClass()); return (new NaiveNumber(1)).promoteTo(params[0].getClass());
} }
NumberInterface factorial = params[0]; NumberInterface factorial = params[0];
NumberInterface multiplier = params[0]; NumberInterface multiplier = params[0];
//It is necessary to later prevent calls of factorial on anything but non-negative integers. //It is necessary to later prevent calls of factorial on anything but non-negative integers.
while (!Thread.currentThread().isInterrupted()&&(multiplier = multiplier.subtract(NaiveNumber.ONE.promoteTo(multiplier.getClass())))!=null&&multiplier.signum() == 1) { while ((multiplier = multiplier.subtract(NaiveNumber.ONE.promoteTo(multiplier.getClass()))).signum() == 1) {
factorial = factorial.multiply(multiplier); factorial = factorial.multiply(multiplier);
} }
if(Thread.currentThread().isInterrupted())
return null;
return factorial; return factorial;
/*if(!storedList.containsKey(params[0].getClass())){ /*if(!storedList.containsKey(params[0].getClass())){
storedList.put(params[0].getClass(), new ArrayList<NumberInterface>()); storedList.put(params[0].getClass(), new ArrayList<NumberInterface>());
@@ -157,12 +124,7 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted()) return FUNCTION_EXP.apply(FUNCTION_LN.apply(params[0]).multiply(params[1]));
return null;
NumberInterface check;
if((check = FUNCTION_EXP.apply(FUNCTION_LN.apply(params[0])))!=null&&(check = check.multiply(params[1]))!=null)
return check;
return null;
} }
}); });
/** /**
@@ -176,8 +138,6 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted())
return null;
return params[0].multiply((new NaiveNumber(params[0].signum())).promoteTo(params[0].getClass())); return params[0].multiply((new NaiveNumber(params[0].signum())).promoteTo(params[0].getClass()));
} }
}; };
@@ -192,53 +152,17 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted()) boolean takeReciprocal = params[0].signum() == -1;
return null; params[0] = FUNCTION_ABS.apply(params[0]);
NumberInterface maxError = getMaxError(params[0]); NumberInterface sum = sumSeries(params[0], StandardPlugin::getExpSeriesTerm, getNTermsExp(getMaxError(params[0]), params[0]));
int n = 0; if (takeReciprocal) {
if(params[0].signum() <= 0){ sum = NaiveNumber.ONE.promoteTo(sum.getClass()).divide(sum);
NumberInterface currentTerm = NaiveNumber.ONE.promoteTo(params[0].getClass()), sum = currentTerm;
NumberInterface check;
while((check = FUNCTION_ABS.apply(currentTerm))!=null && (check.compareTo(maxError) > 0)){
n++;
if(Thread.currentThread().isInterrupted()||(currentTerm = currentTerm.multiply(params[0]))==null||(currentTerm = currentTerm.divide((new NaiveNumber(n)).promoteTo(params[0].getClass())))==null||(sum = (sum.add(currentTerm)))==null)
return null;
}
return sum;
}
else{
//We need n such that x^(n+1) * 3^ceil(x) <= maxError * (n+1)!.
//right and left refer to lhs and rhs in the above inequality.
NumberInterface sum = NaiveNumber.ONE.promoteTo(params[0].getClass());
NumberInterface nextNumerator = params[0];
//NumberInterface left = params[0].multiply((new NaiveNumber(3)).promoteTo(params[0].getClass()).intPow(params[0].ceiling())), right = maxError;
NumberInterface check;
if((check =intPow(new NaiveNumber(3).promoteTo(params[0].getClass()),params[0].getClass(),(new NaiveNumber(params[0].ceiling())).promoteTo(params[0].getClass())))==null)
return null;
NumberInterface left = params[0].multiply(check), right = maxError;
do{
if((check = factorial(params[0].getClass(),n+1))==null||(check = nextNumerator.divide(check))==null||(sum = sum.add(check))==null)
return null;
n++;
if((nextNumerator = nextNumerator.multiply(params[0]))==null)
return null;
if((left = left.multiply(params[0]))==null)
return null;
NumberInterface nextN = (new NaiveNumber(n+1)).promoteTo(params[0].getClass());
if((right = right.multiply(nextN))==null)
return null;
//System.out.println(left + ", " + right);
}
while(!Thread.currentThread().isInterrupted()&&left.compareTo(right) > 0);
//System.out.println(n+1);
if(Thread.currentThread().isInterrupted())
return null;
return sum;
} }
return sum;
} }
}; };
/** /**
* The natural log function. * The natural log function, ln(exp(1)) = 1
*/ */
public static final Function FUNCTION_LN = new Function() { public static final Function FUNCTION_LN = new Function() {
@Override @Override
@@ -248,32 +172,26 @@ public class StandardPlugin extends Plugin {
@Override @Override
protected NumberInterface applyInternal(NumberInterface[] params) { protected NumberInterface applyInternal(NumberInterface[] params) {
if(Thread.currentThread().isInterrupted())
return null;
NumberInterface param = params[0]; NumberInterface param = params[0];
int powersOf2 = 0; int powersOf2 = 0;
NumberInterface check; while (FUNCTION_ABS.apply(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass()))).compareTo((new NaiveNumber(0.1)).promoteTo(param.getClass())) >= 0) {
while (!Thread.currentThread().isInterrupted()&&(check = FUNCTION_ABS.apply(param.subtract(NaiveNumber.ONE.promoteTo(param.getClass()))))!=null&&(check.compareTo((new NaiveNumber(0.1)).promoteTo(param.getClass()))) >= 0) { if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() == 1) {
if ((check = param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())))!=null&&check.signum() == 1) {
param = param.divide(new NaiveNumber(2).promoteTo(param.getClass())); param = param.divide(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2++; powersOf2++;
if ((check = param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())))==null||check.signum() != 1) { if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
break; break;
//No infinite loop for you. //No infinite loop for you.
} }
} else { } else {
param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass())); param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2--; powersOf2--;
if ((check = param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())))==null||check.signum() != 1) { if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
break; break;
//No infinite loop for you. //No infinite loop for you.
} }
} }
} }
NumberInterface check2; return getLog2(param).multiply((new NaiveNumber(powersOf2)).promoteTo(param.getClass())).add(getLogPartialSum(param));
if(!Thread.currentThread().isInterrupted()&&(check = getLog2(param))!=null&&(check = check.multiply((new NaiveNumber(powersOf2).promoteTo(param.getClass()))))!=null&&(check2 = getLogPartialSum(param))!=null&&(check = check.add(check2))!=null)
return check;
return null;
} }
/** /**
@@ -283,23 +201,16 @@ public class StandardPlugin extends Plugin {
* @return the partial sum. * @return the partial sum.
*/ */
private NumberInterface getLogPartialSum(NumberInterface x) { private NumberInterface getLogPartialSum(NumberInterface x) {
if(Thread.currentThread().isInterrupted())
return null;
NumberInterface maxError = getMaxError(x); NumberInterface maxError = getMaxError(x);
x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1). x = x.subtract(NaiveNumber.ONE.promoteTo(x.getClass())); //Terms used are for log(x+1).
NumberInterface currentNumerator = x, currentTerm = x, sum = x; NumberInterface currentNumerator = x, currentTerm = x, sum = x;
int n = 1; int n = 1;
NumberInterface check; while (FUNCTION_ABS.apply(currentTerm).compareTo(maxError) > 0) {
while (!Thread.currentThread().isInterrupted()&&(check = FUNCTION_ABS.apply(currentTerm))!=null&&check.compareTo(maxError) > 0) {
n++; n++;
if((currentNumerator = currentNumerator.multiply(x))==null||(currentNumerator = currentNumerator.negate())==null) currentNumerator = currentNumerator.multiply(x).negate();
return null;
currentTerm = currentNumerator.divide(new NaiveNumber(n).promoteTo(x.getClass())); currentTerm = currentNumerator.divide(new NaiveNumber(n).promoteTo(x.getClass()));
sum = sum.add(currentTerm); sum = sum.add(currentTerm);
} }
if(Thread.currentThread().isInterrupted())
return null;
return sum; return sum;
} }
@@ -309,8 +220,6 @@ public class StandardPlugin extends Plugin {
* @return the value of log(2) with the appropriate precision. * @return the value of log(2) with the appropriate precision.
*/ */
private NumberInterface getLog2(NumberInterface number) { private NumberInterface getLog2(NumberInterface number) {
if(Thread.currentThread().isInterrupted())
return null;
NumberInterface maxError = getMaxError(number); NumberInterface maxError = getMaxError(number);
//NumberInterface errorBound = (new NaiveNumber(1)).promoteTo(number.getClass()); //NumberInterface errorBound = (new NaiveNumber(1)).promoteTo(number.getClass());
//We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n) //We'll use the series \sigma_{n >= 1) ((1/3^n + 1/4^n) * 1/n)
@@ -319,22 +228,18 @@ public class StandardPlugin extends Plugin {
NumberInterface a = (new NaiveNumber(1)).promoteTo(number.getClass()), b = a, c = a; NumberInterface a = (new NaiveNumber(1)).promoteTo(number.getClass()), b = a, c = a;
NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass()); NumberInterface sum = NaiveNumber.ZERO.promoteTo(number.getClass());
int n = 0; int n = 0;
while (!Thread.currentThread().isInterrupted()&&a.compareTo(maxError) >= 1) { while (a.compareTo(maxError) >= 1) {
n++; n++;
a = a.divide((new NaiveNumber(3)).promoteTo(number.getClass())); a = a.divide((new NaiveNumber(3)).promoteTo(number.getClass()));
b = b.divide((new NaiveNumber(4)).promoteTo(number.getClass())); b = b.divide((new NaiveNumber(4)).promoteTo(number.getClass()));
c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass())); c = NaiveNumber.ONE.promoteTo(number.getClass()).divide((new NaiveNumber(n)).promoteTo(number.getClass()));
NumberInterface check; sum = sum.add(a.add(b).multiply(c));
if(a==null||(check = a.add(b))==null||(check = check.multiply(c))==null||(sum = sum.add(check))==null)
return null;
} }
if(Thread.currentThread().isInterrupted())
return null;
return sum; return sum;
} }
}; };
/** /**
* The square root function. * The square root function, sqrt(4) = 2
*/ */
public static final Function FUNCTION_SQRT = new Function() { public static final Function FUNCTION_SQRT = new Function() {
@Override @Override
@@ -352,6 +257,39 @@ public class StandardPlugin extends Plugin {
super(manager); super(manager);
} }
/**
* Returns the nth term of the Taylor series (centered at 0) of e^x
*
* @param n the term required (n >= 0).
* @param x the real number at which the series is evaluated.
* @return the nth term of the series.
*/
private static NumberInterface getExpSeriesTerm(int n, NumberInterface x) {
return x.intPow(n).divide(OP_FACTORIAL.getFunction().apply((new NaiveNumber(n)).promoteTo(x.getClass())));
}
/**
* Returns the number of terms needed to evaluate the exponential function (at x)
* such that the error is at most maxError.
*
* @param maxError Maximum error permissible (This should probably be positive.)
* @param x where the function is evaluated.
* @return the number of terms needed to evaluate the exponential function.
*/
private static int getNTermsExp(NumberInterface maxError, NumberInterface x) {
//We need n such that |x^(n+1)| <= (n+1)! * maxError
//The variables LHS and RHS refer to the above inequality.
int n = 0;
x = FUNCTION_ABS.apply(x);
NumberInterface LHS = x, RHS = maxError;
while (LHS.compareTo(RHS) > 0) {
n++;
LHS = LHS.multiply(x);
RHS = RHS.multiply(new NaiveNumber(n + 1).promoteTo(RHS.getClass()));
}
return n;
}
/** /**
* Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x. * Returns a partial sum of a series whose terms are given by the nthTermFunction, evaluated at x.
* *
@@ -385,7 +323,6 @@ public class StandardPlugin extends Plugin {
registerOperator("+", OP_ADD); registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT); registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY); registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE); registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET); registerOperator("^", OP_CARET);
@@ -402,44 +339,4 @@ public class StandardPlugin extends Plugin {
} }
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n){
if(Thread.currentThread().isInterrupted())
return null;
if(!factorialLists.containsKey(numberClass)){
factorialLists.put(numberClass, new ArrayList<>());
factorialLists.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
factorialLists.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
}
ArrayList<NumberInterface> list = factorialLists.get(numberClass);
if(n >= list.size()){
while(!Thread.currentThread().isInterrupted()&&list.size() < n + 16){
list.add(list.get(list.size()-1).multiply(new NaiveNumber(list.size()).promoteTo(numberClass)));
}
}
if(Thread.currentThread().isInterrupted())
return null;
return list.get(n);
}
public static NumberInterface intPow(NumberInterface number, Class<? extends NumberInterface> numberClass,NumberInterface exponent) {
if(Thread.currentThread().isInterrupted())
return null;
if (exponent.compareTo((new NaiveNumber(0)).promoteTo(numberClass))==0) {
return (new NaiveNumber(1)).promoteTo(numberClass);
}
boolean takeReciprocal = exponent.compareTo((new NaiveNumber(0)).promoteTo(numberClass))<0;
exponent = FUNCTION_ABS.apply(exponent);
NumberInterface power = number;
for(NumberInterface currentExponent =(new NaiveNumber(1)).promoteTo(numberClass);currentExponent.compareTo(exponent)<0;currentExponent = currentExponent.add((new NaiveNumber(1)).promoteTo(numberClass))){
power = power.multiply(number);
if(Thread.currentThread().isInterrupted())
return null;
}
if (takeReciprocal) {
power = (new NaiveNumber(1)).promoteTo(numberClass).divide(power);
}
if(Thread.currentThread().isInterrupted())
return null;
return power;
}
} }

View File

@@ -1,9 +1,11 @@
package org.nwapw.abacus.tree; package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus;
/** /**
* A tree node that represents an operation being applied to two operands. * A tree node that represents an operation being applied to two operands.
*/ */
public class BinaryNode extends TreeNode { public class BinaryInfixNode extends TreeNode {
/** /**
* The operation being applied. * The operation being applied.
@@ -17,8 +19,9 @@ public class BinaryNode extends TreeNode {
* The right node of the operation. * The right node of the operation.
*/ */
private TreeNode right; private TreeNode right;
private Abacus trace;
private BinaryNode() { private BinaryInfixNode() {
} }
/** /**
@@ -27,8 +30,8 @@ public class BinaryNode extends TreeNode {
* *
* @param operation the operation. * @param operation the operation.
*/ */
public BinaryNode(String operation) { public BinaryInfixNode(String operation,Abacus trace) {
this(operation, null, null); this(operation, null, null,trace);
} }
/** /**
@@ -39,10 +42,11 @@ public class BinaryNode extends TreeNode {
* @param left the left node of the expression. * @param left the left node of the expression.
* @param right the right node of the expression. * @param right the right node of the expression.
*/ */
public BinaryNode(String operation, TreeNode left, TreeNode right) { public BinaryInfixNode(String operation, TreeNode left, TreeNode right,Abacus trace) {
this.operation = operation; this.operation = operation;
this.left = left; this.left = left;
this.right = right; this.right = right;
this.trace = trace;
} }
/** /**
@@ -92,15 +96,15 @@ public class BinaryNode extends TreeNode {
@Override @Override
public <T> T reduce(Reducer<T> reducer) { public <T> T reduce(Reducer<T> reducer) {
if(Thread.currentThread().isInterrupted()) if(!trace.getStop()) {
return null; T leftReduce = left.reduce(reducer);
T leftReduce = left.reduce(reducer);
T rightReduce = right.reduce(reducer); T rightReduce = right.reduce(reducer);
if (leftReduce == null || rightReduce == null) return null; if (leftReduce == null || rightReduce == null) return null;
T a = reducer.reduceNode(this, leftReduce, rightReduce); return reducer.reduceNode(this, leftReduce, rightReduce);
if(Thread.currentThread().isInterrupted()) }
return null; return null;
return a;
} }
@Override @Override

View File

@@ -1,5 +1,7 @@
package org.nwapw.abacus.tree; package org.nwapw.abacus.tree;
import org.nwapw.abacus.Abacus;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -16,6 +18,7 @@ public class FunctionNode extends TreeNode {
* The list of arguments to the function. * The list of arguments to the function.
*/ */
private List<TreeNode> children; private List<TreeNode> children;
private Abacus trace;
/** /**
* Creates a function node with no function. * Creates a function node with no function.
@@ -28,9 +31,10 @@ public class FunctionNode extends TreeNode {
* *
* @param function the function name. * @param function the function name.
*/ */
public FunctionNode(String function) { public FunctionNode(String function,Abacus trace) {
this.function = function; this.function = function;
children = new ArrayList<>(); children = new ArrayList<>();
this.trace = trace;
} }
/** /**
@@ -62,17 +66,12 @@ public class FunctionNode extends TreeNode {
@Override @Override
public <T> T reduce(Reducer<T> reducer) { public <T> T reduce(Reducer<T> reducer) {
if(Thread.currentThread().isInterrupted())
return null;
Object[] reducedChildren = new Object[children.size()]; Object[] reducedChildren = new Object[children.size()];
for (int i = 0; i < reducedChildren.length; i++) { for (int i = 0; i < reducedChildren.length; i++) {
reducedChildren[i] = children.get(i).reduce(reducer); reducedChildren[i] = children.get(i).reduce(reducer);
if (Thread.currentThread().isInterrupted()||reducedChildren[i] == null) return null; if (reducedChildren[i] == null) return null;
} }
T a = reducer.reduceNode(this, reducedChildren); return reducer.reduceNode(this, reducedChildren);
if(Thread.currentThread().isInterrupted())
return null;
return a;
} }
@Override @Override

View File

@@ -28,15 +28,15 @@ public class NumberReducer implements Reducer<NumberInterface> {
public NumberInterface reduceNode(TreeNode node, Object... children) { public NumberInterface reduceNode(TreeNode node, Object... children) {
if (node instanceof NumberNode) { if (node instanceof NumberNode) {
return ((NumberNode) node).getNumber(); return ((NumberNode) node).getNumber();
} else if (node instanceof BinaryNode) { } else if (node instanceof BinaryInfixNode) {
NumberInterface left = (NumberInterface) children[0]; NumberInterface left = (NumberInterface) children[0];
NumberInterface right = (NumberInterface) children[1]; NumberInterface right = (NumberInterface) children[1];
Function function = abacus.getPluginManager().operatorFor(((BinaryNode) node).getOperation()).getFunction(); Function function = abacus.getPluginManager().operatorFor(((BinaryInfixNode) node).getOperation()).getFunction();
if (function == null) return null; if (function == null) return null;
return function.apply(left, right); return function.apply(left, right);
} else if (node instanceof UnaryNode) { } else if (node instanceof UnaryPrefixNode) {
NumberInterface child = (NumberInterface) children[0]; NumberInterface child = (NumberInterface) children[0];
Function functionn = abacus.getPluginManager().operatorFor(((UnaryNode) node).getOperation()).getFunction(); Function functionn = abacus.getPluginManager().operatorFor(((UnaryPrefixNode) node).getOperation()).getFunction();
if (functionn == null) return null; if (functionn == null) return null;
return functionn.apply(child); return functionn.apply(child);
} else if (node instanceof FunctionNode) { } else if (node instanceof FunctionNode) {

View File

@@ -1,6 +1,8 @@
package org.nwapw.abacus.tree; package org.nwapw.abacus.tree;
public class UnaryNode extends TreeNode { import org.nwapw.abacus.Abacus;
public class UnaryPrefixNode extends TreeNode {
/** /**
* The operation this node will apply. * The operation this node will apply.
@@ -10,14 +12,15 @@ public class UnaryNode extends TreeNode {
* The tree node to apply the operation to. * The tree node to apply the operation to.
*/ */
private TreeNode applyTo; private TreeNode applyTo;
private Abacus trace;
/** /**
* Creates a new node with the given operation and no child. * Creates a new node with the given operation and no child.
* *
* @param operation the operation for this node. * @param operation the operation for this node.
*/ */
public UnaryNode(String operation) { public UnaryPrefixNode(String operation,Abacus trace) {
this(operation, null); this(operation, null,trace);
} }
/** /**
@@ -26,21 +29,20 @@ public class UnaryNode extends TreeNode {
* @param operation the operation for this node. * @param operation the operation for this node.
* @param applyTo the node to apply the function to. * @param applyTo the node to apply the function to.
*/ */
public UnaryNode(String operation, TreeNode applyTo) { public UnaryPrefixNode(String operation, TreeNode applyTo,Abacus trace) {
this.operation = operation; this.operation = operation;
this.applyTo = applyTo; this.applyTo = applyTo;
this.trace = trace;
} }
@Override @Override
public <T> T reduce(Reducer<T> reducer) { public <T> T reduce(Reducer<T> reducer) {
if(Thread.currentThread().isInterrupted()) if(!trace.getStop()) {
return null; Object reducedChild = applyTo.reduce(reducer);
Object reducedChild = applyTo.reduce(reducer); if (reducedChild == null) return null;
if (reducedChild == null) return null; return reducer.reduceNode(this, reducedChild);
T a = reducer.reduceNode(this, reducedChild); }
if(Thread.currentThread().isInterrupted()) return null;
return null;
return a;
} }
/** /**

View File

@@ -15,13 +15,14 @@ import java.awt.event.MouseEvent;
* The main UI window for the calculator. * The main UI window for the calculator.
*/ */
public class Window extends JFrame { public class Window extends JFrame {
private static final String CALC_STRING = "Calculate"; private static final String CALC_STRING = "Calculate";
private static final String SYNTAX_ERR_STRING = "Syntax Error"; private static final String SYNTAX_ERR_STRING = "Syntax Error";
private static final String EVAL_ERR_STRING = "Evaluation Error"; private static final String EVAL_ERR_STRING = "Evaluation Error";
private static final String NUMBER_SYSTEM_LABEL = "Number Type:"; private static final String NUMBER_SYSTEM_LABEL = "Number Type:";
private static final String FUNCTION_LABEL = "Functions:"; private static final String FUNCTION_LABEL = "Functions:";
private static final String STOP_STRING = "Stop";
/** /**
* Array of Strings to which the "calculate" button's text * Array of Strings to which the "calculate" button's text
* changes. For instance, in the graph tab, the name will * changes. For instance, in the graph tab, the name will
@@ -90,7 +91,10 @@ public class Window extends JFrame {
* The "submit" button. * The "submit" button.
*/ */
private JButton inputEnterButton; private JButton inputEnterButton;
/**
* The stop calculations button.
*/
private JButton inputStopButton;
/** /**
* The side panel for separate configuration. * The side panel for separate configuration.
*/ */
@@ -113,26 +117,80 @@ public class Window extends JFrame {
* The list of functions available to the user. * The list of functions available to the user.
*/ */
private JComboBox<String> functionList; private JComboBox<String> functionList;
/**
* Thread used for calculations
*/
private Thread calculateThread;
/**
* Check if currently calculating.
*/
private boolean calculating;
/**
* Checks if thread should stop calculating.
*/
private boolean stopCalculating;
/**
* Test variable to check number of threads running in the calculator.
*/
private int count;
/**
* ActionListener that stops calculations.
*/
private ActionListener stopListener = (event) -> {
//System.out.println(count++); //Shows about how many calculation threads are running with the calculating=false command
//calculating = false; //Ignores result of calculation and allows for the start of a new calculation
stopCalculating = true; //Stops calculation at the next node check
//calculateThread.stop(); //Stops thread no matter what, Unsafe
};
/** /**
* Action listener that causes the input to be evaluated. * Node check to get whether nodes should continue calculating.
* @return boolean stop calculations if true.
*/
public boolean getStop(){
return stopCalculating;
}
/**
* ActionListener that runs all calculations.
*/ */
private ActionListener evaluateListener = (event) -> { private ActionListener evaluateListener = (event) -> {
TreeNode parsedExpression = abacus.parseString(inputField.getText()); Runnable calculate = new Runnable() {
if (parsedExpression == null) { public void run() {
lastOutputArea.setText(SYNTAX_ERR_STRING); boolean skip = false;
return; calculating = true;
TreeNode parsedExpression = null;
parsedExpression = abacus.parseString(inputField.getText());
if (parsedExpression == null) {
lastOutputArea.setText(SYNTAX_ERR_STRING);
skip = true;
}
if(!skip){
NumberInterface numberInterface = abacus.evaluateTree(parsedExpression);
if (numberInterface == null) {
lastOutputArea.setText(EVAL_ERR_STRING);
calculating = false;
stopCalculating = false;
return;
}
if(calculateThread.equals(Thread.currentThread())) {
lastOutput = numberInterface.toString();
historyModel.addEntry(new HistoryTableModel.HistoryEntry(inputField.getText(), parsedExpression, lastOutput));
historyTable.invalidate();
lastOutputArea.setText(lastOutput);
inputField.setText("");
calculating = false;
stopCalculating = false;
}
}
}
};
if(!calculating) {
calculateThread = new Thread(calculate);
calculateThread.setName("a-"+System.currentTimeMillis());
calculateThread.start();
} }
NumberInterface numberInterface = abacus.evaluateTree(parsedExpression);
if (numberInterface == null) {
lastOutputArea.setText(EVAL_ERR_STRING);
return;
}
lastOutput = numberInterface.toString();
historyModel.addEntry(new HistoryTableModel.HistoryEntry(inputField.getText(), parsedExpression, lastOutput));
historyTable.invalidate();
lastOutputArea.setText(lastOutput);
inputField.setText("");
}; };
/** /**
@@ -143,6 +201,14 @@ public class Window extends JFrame {
evaluateListener, evaluateListener,
null null
}; };
/**
* Array of listeners that tell the stop button how to behave
* at a given input tab.
*/
private ActionListener[] stopListeners = {
stopListener,
null
};
/** /**
* Creates a new window with the given manager. * Creates a new window with the given manager.
@@ -152,6 +218,7 @@ public class Window extends JFrame {
public Window(Abacus abacus) { public Window(Abacus abacus) {
this(); this();
this.abacus = abacus; this.abacus = abacus;
abacus.setWindow(this);
} }
/** /**
@@ -160,25 +227,36 @@ public class Window extends JFrame {
private Window() { private Window() {
super(); super();
//variables related to when calculations occur
stopCalculating = false;
calculating = true;
lastOutput = ""; lastOutput = "";
//default window properties
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(320, 480); setSize(320, 480);
//initiates input objects
inputField = new JTextField(); inputField = new JTextField();
inputEnterButton = new JButton(CALC_STRING); inputEnterButton = new JButton(CALC_STRING);
inputStopButton = new JButton(STOP_STRING);
//adds input objects into Panel
inputPanel = new JPanel(); inputPanel = new JPanel();
inputPanel.setLayout(new BorderLayout()); inputPanel.setLayout(new BorderLayout());
inputPanel.add(inputField, BorderLayout.CENTER); inputPanel.add(inputStopButton, BorderLayout.SOUTH);
inputPanel.add(inputEnterButton, BorderLayout.SOUTH); inputPanel.add(inputField, BorderLayout.NORTH);
inputPanel.add(inputEnterButton, BorderLayout.CENTER);
//initiates output objects in calculator tab
historyModel = new HistoryTableModel(); historyModel = new HistoryTableModel();
historyTable = new JTable(historyModel); historyTable = new JTable(historyModel);
historyScroll = new JScrollPane(historyTable); historyScroll = new JScrollPane(historyTable);
lastOutputArea = new JTextArea(lastOutput); lastOutputArea = new JTextArea(lastOutput);
lastOutputArea.setEditable(false); lastOutputArea.setEditable(false);
//adds output objects into Panel
calculationPanel = new JPanel(); calculationPanel = new JPanel();
calculationPanel.setLayout(new BorderLayout()); calculationPanel.setLayout(new BorderLayout());
calculationPanel.add(historyScroll, BorderLayout.CENTER); calculationPanel.add(historyScroll, BorderLayout.CENTER);
@@ -208,6 +286,8 @@ public class Window extends JFrame {
settingsPanel.add(numberSystemPanel); settingsPanel.add(numberSystemPanel);
settingsPanel.add(functionSelectPanel); settingsPanel.add(functionSelectPanel);
calculating = false;
pane = new JTabbedPane(); pane = new JTabbedPane();
pane.add("Calculator", calculationPanel); pane.add("Calculator", calculationPanel);
pane.add("Settings", settingsPanel); pane.add("Settings", settingsPanel);
@@ -215,17 +295,23 @@ public class Window extends JFrame {
int selectionIndex = pane.getSelectedIndex(); int selectionIndex = pane.getSelectedIndex();
boolean enabled = INPUT_ENABLED[selectionIndex]; boolean enabled = INPUT_ENABLED[selectionIndex];
ActionListener listener = listeners[selectionIndex]; ActionListener listener = listeners[selectionIndex];
ActionListener stopUsed = stopListeners[selectionIndex];
inputEnterButton.setText(BUTTON_NAMES[selectionIndex]); inputEnterButton.setText(BUTTON_NAMES[selectionIndex]);
inputField.setEnabled(enabled); inputField.setEnabled(enabled);
inputEnterButton.setEnabled(enabled); inputEnterButton.setEnabled(enabled);
inputStopButton.setEnabled(enabled);
for (ActionListener removingListener : inputEnterButton.getActionListeners()) { for (ActionListener removingListener : inputEnterButton.getActionListeners()) {
inputEnterButton.removeActionListener(removingListener); inputEnterButton.removeActionListener(removingListener);
inputField.removeActionListener(removingListener); inputField.removeActionListener(removingListener);
} }
for(ActionListener removingListener : inputStopButton.getActionListeners()){
inputStopButton.removeActionListener(removingListener);
}
if (listener != null) { if (listener != null) {
inputEnterButton.addActionListener(listener); inputEnterButton.addActionListener(listener);
inputField.addActionListener(listener); inputField.addActionListener(listener);
inputStopButton.addActionListener(stopUsed);
} }
}); });
add(pane, BorderLayout.CENTER); add(pane, BorderLayout.CENTER);
@@ -233,6 +319,7 @@ public class Window extends JFrame {
inputEnterButton.addActionListener(evaluateListener); inputEnterButton.addActionListener(evaluateListener);
inputField.addActionListener(evaluateListener); inputField.addActionListener(evaluateListener);
inputStopButton.addActionListener(stopListener);
historyTable.addMouseListener(new MouseAdapter() { historyTable.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.GridPane?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.nwapw.abacus.fx.AbacusController">
<center>
<TabPane>
<Tab text="Calculator" closable="false">
<BorderPane>
<center>
<TableView fx:id="historyTable">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="inputColumn" text="Input" sortable="false"/>
<TableColumn fx:id="parsedColumn" text="Parsed" sortable="false"/>
<TableColumn fx:id="outputColumn" text="Output" sortable="false"/>
</columns>
</TableView>
</center>
<bottom>
<VBox>
<ScrollPane prefHeight="50" vbarPolicy="NEVER">
<padding>
<Insets top="10" bottom="10" left="10" right="10"/>
</padding>
<Text fx:id="outputText"/>
</ScrollPane>
<TextField fx:id="inputField" onAction="#performCalculation"/>
<Button fx:id="inputButton" text="Calculate" maxWidth="Infinity"
onAction="#performCalculation"/>
<Button fx:id="stopButton" text="Stop" maxWidth="Infinity"
onAction="#stopCalculation"/>
</VBox>
</bottom>
</BorderPane>
</Tab>
<Tab text="Settings" closable="false">
<GridPane hgap="10" vgap="10">
<padding><Insets left="10" right="10" top="10" bottom="10"/></padding>
<Label text="Number Implementation" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<ComboBox fx:id="numberImplementationBox" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
</GridPane>
</Tab>
</TabPane>
</center>
</BorderPane>