1
0
mirror of https://github.com/DanilaFe/abacus synced 2026-01-25 08:05:19 +00:00

Compare commits

..

52 Commits

Author SHA1 Message Date
b036b6c242 Add comments and clean some code. 2017-08-04 12:52:02 -07:00
eb3410f854 Merge branch 'sin'
# Conflicts:
#	src/main/java/org/nwapw/abacus/Abacus.java
#	src/main/java/org/nwapw/abacus/plugin/PluginManager.java
2017-08-04 12:04:37 -07:00
f967053e3d Merge branch 'master' of github.com:DanilaFe/abacus 2017-08-04 11:55:06 -07:00
75824a2a77 Merge branch 'provider-rewrite' into sin
# Conflicts:
#	src/main/java/org/nwapw/abacus/plugin/StandardPlugin.java
2017-08-04 11:54:12 -07:00
Arthur Drobot
8df468e04a Check arguments in factorial function. 2017-08-04 10:33:55 -07:00
Arthur Drobot
e029ab1fea Add argument checks in division and natural log. 2017-08-04 10:05:18 -07:00
eb51d5d3e4 Add a save and reload button. 2017-08-03 19:00:13 -07:00
8ae28f2dab Add the apply warnings when switching tabs. 2017-08-03 18:16:48 -07:00
0bade4a7df Add warnings that trigger if configuration has been changed. 2017-08-03 18:12:40 -07:00
f0e1b85dcf Make sure disk writes are only on save, and add an alert dialog. 2017-08-03 13:55:39 -07:00
37261c2f58 Fix order of operations bug. 2017-08-03 13:14:09 -07:00
Arthur Drobot
95845a1585 Add tan, sec, csc, cot in terms of what we currently have. 2017-08-03 10:30:42 -07:00
Arthur Drobot
8cf0c94947 Add cos. 2017-08-03 09:52:56 -07:00
20f6e0b0b2 Merge branch 'plugin-list' 2017-08-03 09:34:14 -07:00
4056013d1f Add defaults that actually work. 2017-08-02 21:57:53 -07:00
c7b5d4c4fc Merge branch 'unit-tests' (only typos fixed) 2017-08-02 21:30:50 -07:00
be28e26607 Stop autosaving, switch to save + reload buttons. 2017-08-02 19:40:22 -07:00
2f1ed5f0d1 Change the default implementation string to "<default>" 2017-08-02 19:26:14 -07:00
2615273d28 Refresh all settings on plugin load. 2017-08-02 19:18:33 -07:00
6e1d2ce629 Clear caches on unload and call onUnload before plugins are removed. 2017-08-02 19:14:50 -07:00
44b8efd9bc Actually disable loading the plugin functions in the PluginManager. 2017-08-02 19:06:16 -07:00
2502c90837 Write disabled / enabled plugins to the configuration. 2017-08-02 19:01:01 -07:00
e49f28a850 Add a check box list cell generator. 2017-08-02 18:48:42 -07:00
88e4a87d81 Add a data model for the plugins displayed in the enabled plugins list. 2017-08-02 18:39:00 -07:00
cda09518c3 Add the disabled plugins configuration option. 2017-08-02 18:38:37 -07:00
56510d97de Add the new UI components required for the plugin loading. 2017-08-02 18:24:20 -07:00
Arthur Drobot
86533d53c9 Fix scaling for optimization in FUNCTION_LN, in the positive direction towards unity (i.e., when the argument passed to ln is small). 2017-08-02 15:33:34 -07:00
c2ae0b4138 Merge branch 'negatives' 2017-08-02 11:33:21 -07:00
16938b4e06 Fix division to not multiply numbers. 2017-08-02 11:28:49 -07:00
d964fbfb6f Implement the negation operator. 2017-08-02 11:26:59 -07:00
9713f24ed2 Rename nodes to more general names. 2017-08-02 10:41:52 -07:00
5de9453bec Merge branch 'config-rewrite' 2017-08-01 16:42:58 -07:00
d205651332 Add a number implementation selector box. 2017-08-01 11:52:48 -07:00
6f99f07150 Add comment to PreciseNumber. 2017-08-01 10:49:50 -07:00
2cf41c1029 Add comments to the JavaFX codebase 2017-08-01 10:49:00 -07:00
76677ef494 Merge the two configuration classes into one. 2017-08-01 10:24:32 -07:00
0cd40b028a Merge branch 'javafx' 2017-08-01 09:53:47 -07:00
7cb04a1222 Switch to the new UI. 2017-08-01 09:53:38 -07:00
0a97eeb442 Resize table columns. 2017-08-01 09:25:13 -07:00
05d0755526 Implement a cell that copies input when clicked, and add it to table. 2017-07-31 22:53:42 -07:00
0b97a935bf Enable cell selection to later allow for data copying. 2017-07-31 22:34:49 -07:00
211e963db0 Populate the history table. 2017-07-31 22:29:44 -07:00
d8145acc8f Implement the history data model. 2017-07-31 17:49:57 -07:00
63b8162a9b Format the fxml. 2017-07-31 17:18:08 -07:00
c655c63233 Link up the evaluation and the UI buttons. 2017-07-31 17:17:56 -07:00
27ff1a47b5 Add the inputs to the calculator tab. 2017-07-31 17:16:26 -07:00
2941252f7d Add a tabbed pane as the main focus of the window. 2017-07-31 16:56:38 -07:00
44ed0199d4 Add initialization code to AbacusController 2017-07-31 16:52:34 -07:00
5b582a7dbe Add FXML loading application. 2017-07-31 16:50:39 -07:00
a02086e791 Create new fxml file and controller for it. 2017-07-31 16:48:04 -07:00
21d88fe256 Update README.md 2017-07-30 14:04:24 -07:00
3d61ead0f6 Update README.md 2017-07-30 14:03:58 -07:00
18 changed files with 788 additions and 170 deletions

View File

@@ -5,10 +5,10 @@ Summer project for NWAPW.
Created by Arthur Drobot, Danila Fedorin and Riley Jones.
## Project Description
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.
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.
## Current State
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.
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.
- [x] Basic number class
- [x] Implementation of basic functions
- [x] Implementation of `exp`, `ln`, `sqrt` using the basic functions and Taylor Series

View File

@@ -1,6 +1,7 @@
package org.nwapw.abacus;
import org.nwapw.abacus.config.ConfigurationObject;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.fx.AbacusApplication;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.parsing.LexerTokenizer;
import org.nwapw.abacus.parsing.ShuntingYardParser;
@@ -11,9 +12,7 @@ import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.plugin.StandardPlugin;
import org.nwapw.abacus.tree.NumberReducer;
import org.nwapw.abacus.tree.TreeNode;
import org.nwapw.abacus.window.Window;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
@@ -43,7 +42,7 @@ public class Abacus {
/**
* The configuration loaded from a file.
*/
private ConfigurationObject configuration;
private Configuration configuration;
/**
* The tree builder used to construct a tree
* from a string.
@@ -54,10 +53,10 @@ public class Abacus {
* Creates a new instance of the Abacus calculator.
*/
public Abacus() {
pluginManager = new PluginManager();
pluginManager = new PluginManager(this);
numberReducer = new NumberReducer(this);
configuration = new ConfigurationObject(CONFIG_FILE);
configuration.save(CONFIG_FILE);
configuration = new Configuration(CONFIG_FILE);
configuration.saveTo(CONFIG_FILE);
LexerTokenizer lexerTokenizer = new LexerTokenizer();
ShuntingYardParser shuntingYardParser = new ShuntingYardParser(this);
treeBuilder = new TreeBuilder<>(lexerTokenizer, shuntingYardParser);
@@ -75,13 +74,7 @@ public class Abacus {
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException e) {
e.printStackTrace();
}
new Window(new Abacus()).setVisible(true);
AbacusApplication.launch(AbacusApplication.class, args);
}
/**
@@ -117,7 +110,7 @@ public class Abacus {
*
* @return the configuration object.
*/
public ConfigurationObject getConfiguration() {
public Configuration getConfiguration() {
return configuration;
}

View File

@@ -1,14 +1,108 @@
package org.nwapw.abacus.config;
import com.moandjiezana.toml.Toml;
import com.moandjiezana.toml.TomlWriter;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Serializable class that will be used to load TOML
* configurations.
* The configuration object that stores
* options that the user can change.
*/
public class Configuration {
/**
* The type of number this calculator should use.
* The defaults TOML string.
*/
public String numberType;
private static final String DEFAULT_CONFIG =
"numberImplementation = \"naive\"\n" +
"disabledPlugins = []";
/**
* The defaults TOML object, parsed from the string.
*/
private static final Toml DEFAULT_TOML = new Toml().read(DEFAULT_CONFIG);
/**
* The TOML writer used to write this configuration to a file.
*/
private static final TomlWriter TOML_WRITER = new TomlWriter();
/**
* The implementation of the number that should be used.
*/
private String numberImplementation = "<default>";
/**
* The list of disabled plugins in this Configuration.
*/
private Set<String> disabledPlugins = new HashSet<>();
/**
* Creates a new configuration with the given values.
* @param numberImplementation the number implementation, like "naive" or "precise"
* @param disabledPlugins the list of disabled plugins.
*/
public Configuration(String numberImplementation, String[] disabledPlugins){
this.numberImplementation = numberImplementation;
this.disabledPlugins.addAll(Arrays.asList(disabledPlugins));
}
/**
* 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(new Toml(DEFAULT_TOML).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;
this.disabledPlugins.addAll(otherConfiguration.disabledPlugins);
}
/**
* 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;
}
/**
* Gets the list of disabled plugins.
* @return the list of disabled plugins.
*/
public Set<String> getDisabledPlugins() {
return disabledPlugins;
}
}

View File

@@ -1,111 +0,0 @@
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.
*/
public enum OperatorType {
BINARY_INFIX, UNARY_POSTFIX
BINARY_INFIX, UNARY_POSTFIX, UNARY_PREFIX
}

View File

@@ -0,0 +1,24 @@
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

@@ -0,0 +1,240 @@
package org.nwapw.abacus.fx;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.text.Text;
import javafx.util.Callback;
import javafx.util.StringConverter;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.config.Configuration;
import org.nwapw.abacus.number.NumberInterface;
import org.nwapw.abacus.plugin.PluginListener;
import org.nwapw.abacus.plugin.PluginManager;
import org.nwapw.abacus.tree.TreeNode;
import java.util.Set;
/**
* The controller for the abacus FX UI, responsible
* for all the user interaction.
*/
public class AbacusController implements PluginListener {
/**
* The title for the apply alert dialog.
*/
private static final String APPLY_MSG_TITLE = "\"Apply\" Needed";
/**
* The text for the header of the apply alert dialog.
*/
private static final String APPLY_MSG_HEADER = "The settings have not been applied.";
/**
* The text for the dialog that is shown if settings haven't been applied.
*/
private static final String APPLY_MSG_TEXT = "You have made changes to the configuration, however, you haven't pressed \"Apply\". " +
"The changes to the configuration will not be present in the calculator until \"Apply\" is pressed.";
/**
* 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";
@FXML
private TabPane coreTabPane;
@FXML
private Tab calculateTab;
@FXML
private Tab settingsTab;
@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;
@FXML
private ListView<ToggleablePlugin> enabledPluginView;
/**
* 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;
/**
* The list of plugin objects that can be toggled on and off,
* and, when reloaded, get added to the plugin manager's black list.
*/
private ObservableList<ToggleablePlugin> enabledPlugins;
/**
* The abacus instance used for changing the plugin configuration.
*/
private Abacus abacus;
/**
* Boolean which represents whether changes were made to the configuration.
*/
private boolean changesMade;
/**
* Whether an alert about changes to the configuration was already shown.
*/
private boolean reloadAlertShown;
/**
* The alert shown when a press to "apply" is needed.
*/
private Alert reloadAlert;
/**
* Alerts the user if the changes they made
* have not yet been applied.
*/
private void alertIfApplyNeeded(boolean ignorePrevious){
if(changesMade && (!reloadAlertShown || ignorePrevious)) {
reloadAlertShown = true;
reloadAlert.showAndWait();
}
}
@FXML
public void initialize(){
Callback<TableColumn<HistoryModel, String>, TableCell<HistoryModel, String>> cellFactory =
param -> new CopyableCell<>();
Callback<ListView<ToggleablePlugin>, ListCell<ToggleablePlugin>> pluginCellFactory =
param -> new CheckBoxListCell<>(ToggleablePlugin::enabledProperty, new StringConverter<ToggleablePlugin>() {
@Override
public String toString(ToggleablePlugin object) {
return object.getClassName().substring(object.getClassName().lastIndexOf('.') + 1);
}
@Override
public ToggleablePlugin fromString(String string) {
return new ToggleablePlugin(true, string);
}
});
historyData = FXCollections.observableArrayList();
historyTable.setItems(historyData);
numberImplementationOptions = FXCollections.observableArrayList();
numberImplementationBox.setItems(numberImplementationOptions);
numberImplementationBox.getSelectionModel().selectedIndexProperty().addListener(e -> changesMade = true);
historyTable.getSelectionModel().setCellSelectionEnabled(true);
enabledPlugins = FXCollections.observableArrayList();
enabledPluginView.setItems(enabledPlugins);
enabledPluginView.setCellFactory(pluginCellFactory);
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());
coreTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if(oldValue.equals(settingsTab)) alertIfApplyNeeded(true);
});
abacus = new Abacus();
abacus.getPluginManager().addListener(this);
abacus.getPluginManager().reload();
changesMade = false;
reloadAlertShown = false;
reloadAlert = new Alert(Alert.AlertType.WARNING);
reloadAlert.setTitle(APPLY_MSG_TITLE);
reloadAlert.setHeaderText(APPLY_MSG_HEADER);
reloadAlert.setContentText(APPLY_MSG_TEXT);
}
@FXML
private void performCalculation(){
inputButton.setDisable(true);
TreeNode constructedTree = abacus.parseString(inputField.getText());
if(constructedTree == null){
outputText.setText(ERR_SYNTAX);
inputButton.setDisable(false);
return;
}
NumberInterface evaluatedNumber = abacus.evaluateTree(constructedTree);
if(evaluatedNumber == null){
outputText.setText(ERR_EVAL);
inputButton.setDisable(false);
return;
}
outputText.setText(evaluatedNumber.toString());
historyData.add(new HistoryModel(inputField.getText(), constructedTree.toString(), evaluatedNumber.toString()));
inputButton.setDisable(false);
inputField.setText("");
}
@FXML
private void performSaveAndReload(){
performSave();
performReload();
changesMade = false;
reloadAlertShown = false;
}
@FXML
private void performReload(){
alertIfApplyNeeded(true);
abacus.getPluginManager().reload();
}
@FXML
private void performSave(){
Configuration configuration = abacus.getConfiguration();
configuration.setNumberImplementation(numberImplementationBox.getSelectionModel().getSelectedItem());
Set<String> disabledPlugins = configuration.getDisabledPlugins();
disabledPlugins.clear();
for(ToggleablePlugin pluginEntry : enabledPlugins){
if(!pluginEntry.isEnabled()) disabledPlugins.add(pluginEntry.getClassName());
}
configuration.saveTo(Abacus.CONFIG_FILE);
changesMade = false;
reloadAlertShown = false;
}
@Override
public void onLoad(PluginManager manager) {
Configuration configuration = abacus.getConfiguration();
Set<String> disabledPlugins = configuration.getDisabledPlugins();
numberImplementationOptions.addAll(abacus.getPluginManager().getAllNumberImplementations());
String actualImplementation = configuration.getNumberImplementation();
String toSelect = (numberImplementationOptions.contains(actualImplementation)) ? actualImplementation : "<default>";
numberImplementationBox.getSelectionModel().select(toSelect);
for(Class<?> pluginClass : abacus.getPluginManager().getLoadedPluginClasses()){
String fullName = pluginClass.getName();
ToggleablePlugin plugin = new ToggleablePlugin(!disabledPlugins.contains(fullName), fullName);
plugin.enabledProperty().addListener(e -> changesMade = true);
enabledPlugins.add(plugin);
}
}
@Override
public void onUnload(PluginManager manager) {
enabledPlugins.clear();
numberImplementationOptions.clear();
}
}

View File

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,87 @@
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

@@ -0,0 +1,56 @@
package org.nwapw.abacus.fx;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
* Class that represents an entry in the plugin check box list.
* The changes from this property are written to the config on application.
*/
public class ToggleablePlugin {
/**
* The property that determines whether the plugin will be enabled.
*/
private final BooleanProperty enabled;
/**
* The name of the class this entry toggles.
*/
private final String className;
/**
* Creates a new toggleable plugin with the given properties.
* @param enabled the enabled / disabled state at the beginning.
* @param className the name of the class this plugin toggles.
*/
public ToggleablePlugin(boolean enabled, String className){
this.enabled = new SimpleBooleanProperty();
this.enabled.setValue(enabled);
this.className = className;
}
/**
* Gets the enabled property of this plugin.
* @return the enabled property.
*/
public BooleanProperty enabledProperty() {
return enabled;
}
/**
* Checks if this plugin entry should be enabled.
* @return whether this plugin will be enabled.
*/
public boolean isEnabled() {
return enabled.get();
}
/**
* Gets the class name this plugin toggles.
* @return the class name that should be disabled.
*/
public String getClassName() {
return className;
}
}

View File

@@ -3,6 +3,10 @@ package org.nwapw.abacus.number;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* A number that uses a BigDecimal to store its value,
* leading to infinite possible precision.
*/
public class PreciseNumber implements NumberInterface {
/**

View File

@@ -55,9 +55,12 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
public List<Match<TokenType>> intoPostfix(List<Match<TokenType>> from) {
ArrayList<Match<TokenType>> output = new ArrayList<>();
Stack<Match<TokenType>> tokenStack = new Stack<>();
TokenType previousType;
TokenType matchType = null;
while (!from.isEmpty()) {
Match<TokenType> match = from.remove(0);
TokenType matchType = match.getType();
previousType = matchType;
matchType = match.getType();
if (matchType == TokenType.NUM) {
output.add(match);
} else if (matchType == TokenType.FUNCTION) {
@@ -74,13 +77,19 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
continue;
}
while (!tokenStack.empty()) {
if(tokenString.equals("-") && (previousType == null || previousType == TokenType.OP ||
previousType == TokenType.OPEN_PARENTH)){
from.add(0, new Match<>("`", TokenType.OP));
continue;
}
while (!tokenStack.empty() && type == OperatorType.BINARY_INFIX) {
Match<TokenType> otherMatch = tokenStack.peek();
TokenType otherMatchType = otherMatch.getType();
if (!(otherMatchType == TokenType.OP || otherMatchType == TokenType.FUNCTION)) break;
if (otherMatchType == TokenType.OP) {
int otherPrecedence = precedenceMap.get(match.getContent());
int otherPrecedence = precedenceMap.get(otherMatch.getContent());
if (otherPrecedence < precedence ||
(associativity == OperatorAssociativity.RIGHT && otherPrecedence == precedence)) {
break;
@@ -103,8 +112,8 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
}
while (!tokenStack.empty()) {
Match<TokenType> match = tokenStack.peek();
TokenType matchType = match.getType();
if (!(matchType == TokenType.OP || matchType == TokenType.FUNCTION)) return null;
TokenType newMatchType = match.getType();
if (!(newMatchType == TokenType.OP || newMatchType == TokenType.FUNCTION)) return null;
output.add(tokenStack.pop());
}
return output;
@@ -127,11 +136,11 @@ public class ShuntingYardParser implements Parser<Match<TokenType>>, PluginListe
TreeNode right = constructRecursive(matches);
TreeNode left = constructRecursive(matches);
if (left == null || right == null) return null;
else return new BinaryInfixNode(operator, left, right);
else return new BinaryNode(operator, left, right);
} else {
TreeNode applyTo = constructRecursive(matches);
if (applyTo == null) return null;
else return new UnaryPrefixNode(operator, applyTo);
else return new UnaryNode(operator, applyTo);
}
} else if (matchType == TokenType.NUM) {
return new NumberNode(abacus.numberFromString(match.getContent()));

View File

@@ -1,5 +1,6 @@
package org.nwapw.abacus.plugin;
import org.nwapw.abacus.Abacus;
import org.nwapw.abacus.function.Function;
import org.nwapw.abacus.function.Operator;
import org.nwapw.abacus.number.NumberInterface;
@@ -61,11 +62,17 @@ public class PluginManager {
* The list of plugin listeners attached to this instance.
*/
private Set<PluginListener> listeners;
/**
* The abacus instance used to access other
* components of the application.
*/
private Abacus abacus;
/**
* Creates a new plugin manager.
*/
public PluginManager() {
public PluginManager(Abacus abacus) {
this.abacus = abacus;
loadedPluginClasses = new HashSet<>();
plugins = new HashSet<>();
cachedFunctions = new HashMap<>();
@@ -210,8 +217,13 @@ public class PluginManager {
* Loads all the plugins in the PluginManager.
*/
public void load() {
for (Plugin plugin : plugins) plugin.enable();
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
for (Plugin plugin : plugins) {
if(disabledPlugins.contains(plugin.getClass().getName())) continue;
plugin.enable();
}
for (Plugin plugin : plugins) {
if(disabledPlugins.contains(plugin.getClass().getName())) continue;
allFunctions.addAll(plugin.providedFunctions());
allOperators.addAll(plugin.providedOperators());
allNumberImplementations.addAll(plugin.providedNumberImplementations());
@@ -223,7 +235,17 @@ public class PluginManager {
* Unloads all the plugins in the PluginManager.
*/
public void unload() {
for (Plugin plugin : plugins) plugin.disable();
listeners.forEach(e -> e.onUnload(this));
Set<String> disabledPlugins = abacus.getConfiguration().getDisabledPlugins();
for (Plugin plugin : plugins) {
if(disabledPlugins.contains(plugin.getClass().getName())) continue;
plugin.disable();
}
cachedFunctions.clear();
cachedOperators.clear();
cachedNumberImplementations.clear();
cachedInterfaceImplementations.clear();
cachedPi.clear();
allFunctions.clear();
allOperators.clear();
allNumberImplementations.clear();
@@ -235,7 +257,7 @@ public class PluginManager {
*/
public void reload() {
unload();
reload();
load();
}
/**
@@ -283,4 +305,12 @@ public class PluginManager {
listeners.remove(listener);
}
/**
* Gets a list of all the plugin class files that have been
* added to the plugin manager.
* @return the list of all the added plugin classes.
*/
public Set<Class<?>> getLoadedPluginClasses() {
return loadedPluginClasses;
}
}

View File

@@ -18,7 +18,7 @@ import java.util.function.BiFunction;
*/
public class StandardPlugin extends Plugin {
private static HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> factorialLists = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
private static final HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> FACTORIAL_LISTS = new HashMap<>();
/**
* The addition operator, +
@@ -52,6 +52,20 @@ public class StandardPlugin extends Plugin {
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) {
return params[0].negate();
}
});
/**
* The multiplication operator, *
*/
@@ -76,16 +90,12 @@ public class StandardPlugin extends Plugin {
public static final Operator OP_DIVIDE = new Operator(OperatorAssociativity.LEFT, OperatorType.BINARY_INFIX, 1, new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length >= 1;
return params.length == 2 && params[1].compareTo(NaiveNumber.ZERO.promoteTo(params[1].getClass())) != 0;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
NumberInterface product = params[0];
for (int i = 1; i < params.length; i++) {
product = product.multiply(params[i]);
}
return product;
return params[0].divide(params[1]);
}
});
/**
@@ -95,7 +105,9 @@ public class StandardPlugin extends Plugin {
//private HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>> storedList = new HashMap<Class<? extends NumberInterface>, ArrayList<NumberInterface>>();
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
return params.length == 1
&& params[0].fractionalPart().compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) == 0
&& params[0].signum() >= 0;
}
@Override
@@ -194,7 +206,7 @@ public class StandardPlugin extends Plugin {
public static final Function FUNCTION_LN = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
return params.length == 1 && params[0].compareTo(NaiveNumber.ZERO.promoteTo(params[0].getClass())) > 0;
}
@Override
@@ -212,7 +224,7 @@ public class StandardPlugin extends Plugin {
} else {
param = param.multiply(new NaiveNumber(2).promoteTo(param.getClass()));
powersOf2--;
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != 1) {
if (param.subtract(NaiveNumber.ONE.promoteTo(param.getClass())).signum() != -1) {
break;
//No infinite loop for you.
}
@@ -306,6 +318,82 @@ public class StandardPlugin extends Plugin {
}
};
/**
* The cosine function (the argument is in radians).
*/
public final Function functionCos = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(getPi(params[0].getClass()).divide(new NaiveNumber(2).promoteTo(params[0].getClass()))
.subtract(params[0]));
}
};
/**
* The tangent function (the argument is in radians).
*/
public final Function functionTan = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionSin.apply(params[0]).divide(functionCos.apply(params[0]));
}
};
/**
* The secant function (the argument is in radians).
*/
public final Function functionSec = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionCos.apply(params[0]));
}
};
/**
* The cosecant function (the argument is in radians).
*/
public final Function functionCsc = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return NaiveNumber.ONE.promoteTo(params[0].getClass()).divide(functionSin.apply(params[0]));
}
};
/**
* The cotangent function (the argument is in radians).
*/
public final Function functionCot = new Function() {
@Override
protected boolean matchesParams(NumberInterface[] params) {
return params.length == 1;
}
@Override
protected NumberInterface applyInternal(NumberInterface[] params) {
return functionCos.apply(params[0]).divide(functionCos.apply(params[0]));
}
};
/**
* The implementation for double-based naive numbers.
*/
@@ -395,6 +483,7 @@ public class StandardPlugin extends Plugin {
registerOperator("+", OP_ADD);
registerOperator("-", OP_SUBTRACT);
registerOperator("`", OP_NEGATE);
registerOperator("*", OP_MULTIPLY);
registerOperator("/", OP_DIVIDE);
registerOperator("^", OP_CARET);
@@ -405,6 +494,11 @@ public class StandardPlugin extends Plugin {
registerFunction("ln", FUNCTION_LN);
registerFunction("sqrt", FUNCTION_SQRT);
registerFunction("sin", functionSin);
registerFunction("cos", functionCos);
registerFunction("tan", functionTan);
registerFunction("sec", functionSec);
registerFunction("csc", functionCsc);
registerFunction("cot", functionCot);
}
@Override
@@ -420,12 +514,12 @@ public class StandardPlugin extends Plugin {
* @return a number of numClass with value n factorial.
*/
public static NumberInterface factorial(Class<? extends NumberInterface> numberClass, int n){
if(!factorialLists.containsKey(numberClass)){
factorialLists.put(numberClass, new ArrayList<NumberInterface>());
factorialLists.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
factorialLists.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
if(!FACTORIAL_LISTS.containsKey(numberClass)){
FACTORIAL_LISTS.put(numberClass, new ArrayList<>());
FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
FACTORIAL_LISTS.get(numberClass).add(NaiveNumber.ONE.promoteTo(numberClass));
}
ArrayList<NumberInterface> list = factorialLists.get(numberClass);
ArrayList<NumberInterface> list = FACTORIAL_LISTS.get(numberClass);
if(n >= list.size()){
while(list.size() < n + 16){
list.add(list.get(list.size()-1).multiply(new NaiveNumber(list.size()).promoteTo(numberClass)));

View File

@@ -3,7 +3,7 @@ package org.nwapw.abacus.tree;
/**
* A tree node that represents an operation being applied to two operands.
*/
public class BinaryInfixNode extends TreeNode {
public class BinaryNode extends TreeNode {
/**
* The operation being applied.
@@ -18,7 +18,7 @@ public class BinaryInfixNode extends TreeNode {
*/
private TreeNode right;
private BinaryInfixNode() {
private BinaryNode() {
}
/**
@@ -27,7 +27,7 @@ public class BinaryInfixNode extends TreeNode {
*
* @param operation the operation.
*/
public BinaryInfixNode(String operation) {
public BinaryNode(String operation) {
this(operation, null, null);
}
@@ -39,7 +39,7 @@ public class BinaryInfixNode extends TreeNode {
* @param left the left node of the expression.
* @param right the right node of the expression.
*/
public BinaryInfixNode(String operation, TreeNode left, TreeNode right) {
public BinaryNode(String operation, TreeNode left, TreeNode right) {
this.operation = operation;
this.left = left;
this.right = right;

View File

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

View File

@@ -1,6 +1,6 @@
package org.nwapw.abacus.tree;
public class UnaryPrefixNode extends TreeNode {
public class UnaryNode extends TreeNode {
/**
* The operation this node will apply.
@@ -16,7 +16,7 @@ public class UnaryPrefixNode extends TreeNode {
*
* @param operation the operation for this node.
*/
public UnaryPrefixNode(String operation) {
public UnaryNode(String operation) {
this(operation, null);
}
@@ -26,7 +26,7 @@ public class UnaryPrefixNode extends TreeNode {
* @param operation the operation for this node.
* @param applyTo the node to apply the function to.
*/
public UnaryPrefixNode(String operation, TreeNode applyTo) {
public UnaryNode(String operation, TreeNode applyTo) {
this.operation = operation;
this.applyTo = applyTo;
}

View File

@@ -0,0 +1,63 @@
<?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?>
<?import javafx.scene.layout.FlowPane?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.nwapw.abacus.fx.AbacusController">
<center>
<TabPane fx:id="coreTabPane">
<Tab fx:id="calculateTab" 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"/>
</VBox>
</bottom>
</BorderPane>
</Tab>
<Tab fx:id="settingsTab" 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"/>
<ListView fx:id="enabledPluginView"
GridPane.rowIndex="1" GridPane.columnIndex="0"
GridPane.columnSpan="2" maxHeight="100"/>
<FlowPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2" hgap="10" vgap="10">
<Button text="Apply" onAction="#performSave"/>
<Button text="Reload Plugins" onAction="#performReload"/>
<Button text="Apply and Reload" onAction="#performSaveAndReload"/>
</FlowPane>
</GridPane>
</Tab>
</TabPane>
</center>
</BorderPane>