Java课程project(SMAC计算器)----基于JavaSE
新开了一门外教课程,Object-oriented Programming(JAVA),本章记录结课project。
This project is about making a Simple MAth Calculator (SMAC in the sequel) with some interesting features.
Author: ArthurWang
Enviroment: Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-48-generic x86_64)、openjdk 11.0.9.1
IDE: VScode
Introduction of functionalities
- Evaluate mathematical expressions made from numbers, operators (addition, subtraction, unary minus, multiplication, division, power) and parenthesis.
- Managing the precision of decimal.
- Define and use variables in mathematical expression. (Eg. let x = 3; reset x …)
- Managing errors. (Include ErrorException, SyntaxErrorException, LexicalErrorException, TokenException)
- Keeping track of the last value. (Eg. last …)
- Storing variables. (Eg. save “myfile”; load “myfile”; save “just-z-file” z …)
- Logging a session. (Eg. log “mylog”; log end …)
- More mathematical functions. (sin, cos, tan, abs)
Some ideas of design
- Singleton design idea and Multiton design idea.
- Factory design idea and Proxy design idea.
- Client (which is Smac.java) use the Interface to get service.
File structure
project: package of source file.
logFile: log file of user set log.
varFile: variables file of user set variable.
出于考虑该文章只贴出部分代码,即只给出了处理问题的逻辑方法。自定义的Exception文件、实现接口的Evaluator文件、Token、Test文件均没有贴出。
package project;
public interface IEvaluator {
// Creat Token with lexical analysis.
public void createInputToken(String input) throws LexicalErrorException, TokenException;
// Sysntax analysis
public boolean syntaxAnalysis() throws ErrorException, SyntaxErrorException, LexicalErrorException, TokenException;
// Creat Mathematical evaluator and calculate
public void mathEvaluator() throws ErrorException, SyntaxErrorException, TokenException;
// Get varFile path
public void varFilePath(String path);
// Println output
public void println(String len);
// Print output
public void print(String len);
// Log situtation
public void logUserInput(String input);
// Get varFile path
public void logFilePath(String path);
// Log session
public boolean logSession();
}
package project;
import java.util.*;
/**
* this class is to handle different keywords command.
* the SyntaxAnalyzer just need to give the factory to process.
*/
public class KeywordsCommFactory {
private static final KeywordsCommFactory command = new KeywordsCommFactory();
private KeywordsCommFactory() {
}
public static KeywordsCommFactory getKeywordsCommFactory() {
return command;
}
public void factory(Tokenizer myTokenizer, Map<String, Double> variables)throws TokenException, SyntaxErrorException, ErrorException, LexicalErrorException {
Token t = myTokenizer.readNextToken();
if (t.getIdentifier().equals("let")) {
letCommand(myTokenizer, variables);
} else if (t.getIdentifier().equals("setprecision")) {
precCommand(myTokenizer);
} else if (t.getIdentifier().equals("reset")) {
resetCommand(myTokenizer, variables);
} else if (t.getIdentifier().equals("last")) {
lastCommand(myTokenizer);
} else if (t.getIdentifier().equals("save")) {
saveCommand(myTokenizer, variables);
} else if (t.getIdentifier().equals("saved")) {
savedCommand(myTokenizer);
}else if (t.getIdentifier().equals("load")){
loadCommand(myTokenizer, variables);
}else if (t.getIdentifier().equals("log")){
logCommand(myTokenizer);
}else if (t.getIdentifier().equals("logged")){
loggerCommand(myTokenizer);
}
/*
* else if (t.getIdentifier().equals("define")){
* }
*/
}
/**
* Command Process.
* Different keywords, Factory use different method
*/
private void letCommand(Tokenizer myTokenizer, Map<String, Double> variables)throws SyntaxErrorException, TokenException {
// only one "let" situtation
if (!myTokenizer.hasNextToken()) {
letCommandOutput(variables);
return;
}
Token t = myTokenizer.readNextToken();
if (!t.isIdentifier()) {
throw new SyntaxErrorException("is not a vaild variable name");
}
String variable = t.getIdentifier();
t = myTokenizer.readNextToken();
if (!t.isEqual()) {
throw new SyntaxErrorException("is not a vaild let command");
}
MathematicalEvaluator mathEvaluer = MathematicalEvaluator.getMathematicalEvaluator();
Double value = mathEvaluer.calculate(myTokenizer, variables);
if (value == null) {
throw new SyntaxErrorException("is not a vaild let command");
}
variables.put(variable, value);
ResOutput.getResOutput().println(value);
}
private void letCommandOutput(Map<String, Double> variables) {
Iterator<String> it = variables.keySet().iterator();
if (!it.hasNext()) {
ResOutput.getResOutput().println("no variable defined");
return;
}
while (it.hasNext()) {
String var = it.next();
ResOutput.getResOutput().println(var + " = " + variables.get(var));
}
}
private void precCommand(Tokenizer myTokenizer) throws SyntaxErrorException, TokenException {
// only one "setprecision" situtation
if (!myTokenizer.hasNextToken()) {
int precision = ResOutput.getResOutput().getPrecision();
if (precision == -1) {
ResOutput.getResOutput().println("current precision is default. You can set a precision");
} else {
ResOutput.getResOutput().println("current precision is " + precision);
}
return;
}
// if token is not a number or if myTokenizer has next token after the precision
// value. Is worng.
Token t = myTokenizer.readNextToken();
if (!t.isNumber() || myTokenizer.hasNextToken()) {
throw new SyntaxErrorException("is not a vaild precision");
}
// set the precision
String temp = String.valueOf(t.getNumber());
String res = temp.substring(0, temp.indexOf("."));
int prec = Integer.parseInt(res);
ResOutput.getResOutput().setPrecision(prec);
ResOutput.getResOutput().println("current precision is " + ResOutput.getResOutput().getPrecision());
}
private void resetCommand(Tokenizer myTokenizer, Map<String, Double> variables)
throws SyntaxErrorException, TokenException {
// only one "reset" situtation.
if (!myTokenizer.hasNextToken()) {
resetCommandOutput(variables);
return;
}
while (myTokenizer.hasNextToken()) {
Token t = myTokenizer.readNextToken();
if (!t.isIdentifier()) {
throw new SyntaxErrorException("is not a vaild variable name");
}
String variable = t.getIdentifier();
if (variables.containsKey(variable)) {
variables.remove(variable);
ResOutput.getResOutput().println(variable + " has been reset");
} else {
ResOutput.getResOutput().println(variable + " not defined");
}
}
}
private void resetCommandOutput(Map<String, Double> variables) {
Iterator<String> it = variables.keySet().iterator();
if (!it.hasNext()) {
ResOutput.getResOutput().println("no variable defined");
return;
}
while (it.hasNext()) {
String var = it.next();
ResOutput.getResOutput().println(var + " has been reset");
}
variables.clear();
}
private void lastCommand(Tokenizer myTokenizer) throws SyntaxErrorException {
if (myTokenizer.hasNextToken()) {
throw new SyntaxErrorException("is not a vaild last command");
}
if (LastCaluNum.getLastCaluNum().getNum() == null) {
ResOutput.getResOutput().println("no calculate resulet");
return;
}
ResOutput.getResOutput().println(LastCaluNum.getLastCaluNum().getNum());
}
private void saveCommand(Tokenizer myTokenizer, Map<String, Double> variables) throws SyntaxErrorException, TokenException{
// only one "save" situtation
if (!myTokenizer.hasNextToken()) { throw new SyntaxErrorException("is not a vaild save command. Maybe try saved? "); }
Token t = myTokenizer.readNextToken();
if (!t.isString()){ throw new SyntaxErrorException("is not a vaild save command"); }
String variable = t.getString();
String file = variable.substring(1,variable.length()-1);
if (myTokenizer.hasNextToken()){
Token var = myTokenizer.readNextToken();
if (var.isIdentifier()){
StorVar.getStorVar().saveFileOneLetter(file, variables, var.getIdentifier());
} else{
throw new SyntaxErrorException("is not a vaild save command");
}
} else{
StorVar.getStorVar().saveFile(file, variables);
}
if (myTokenizer.hasNextToken()) { throw new SyntaxErrorException("is not a vaild save command. You only can chose one variable."); }
}
private void savedCommand(Tokenizer myTokenizer) throws SyntaxErrorException{
if (myTokenizer.hasNextToken()) { throw new SyntaxErrorException("is not a vaild saved command"); }
StorVar.getStorVar().savedFile();
}
private void loadCommand(Tokenizer myTokenizer, Map<String, Double> variables) throws SyntaxErrorException, TokenException, ErrorException, LexicalErrorException {
// only one "load" situtation
if (!myTokenizer.hasNextToken()) { throw new SyntaxErrorException("is not a vaild load command. Please input the load file"); }
Token t = myTokenizer.readNextToken();
if (!t.isString()) { throw new SyntaxErrorException("is not a vaild load file."); }
String file = t.getString().substring(1,t.getString().length()-1);
ArrayList<String> res = StorVar.getStorVar().loadVar(file);
for (int i = 0; i < res.size(); i++){
myTokenizer = new Tokenizer(res.get(i));
myTokenizer.readNextToken();
letCommand(myTokenizer, variables);
}
}
private void logCommand(Tokenizer myTokenizer) throws SyntaxErrorException, TokenException {
// only "log" situtation
if (!myTokenizer.hasNextToken()) {
if (LogSe.getLoger().getLogActiveButton()) {
ResOutput.getResOutput().println(LogSe.getLoger().getFile());
}else {
ResOutput.getResOutput().println("not in Logging session");
}
return;
}
Token t = myTokenizer.readNextToken();
if (!t.isString()) {
if (LogSe.getLoger().getLogActiveButton() && t.isIdentifier() && t.getIdentifier().equals("end")){
ResOutput.getResOutput().println("session was logged to "+LogSe.getLoger().getFile());
LogSe.getLoger().quitLoger();
return;
}else{
throw new SyntaxErrorException("is not a vaild log command.");
}
}
if (myTokenizer.hasNextToken()) { throw new SyntaxErrorException("is not a vaild log command."); }
String file = t.getString().substring(1,t.getString().length()-1);
LogSe.getLoger().setFile(file);
ResOutput.getResOutput().println("logging session to "+file);
LogSe.getLoger().activeLoger();
}
private void loggerCommand(Tokenizer myTokenizer) throws SyntaxErrorException {
if (myTokenizer.hasNextToken()) { throw new SyntaxErrorException("is not a vaild logged command."); }
Set<String> temp = LogSe.getLoger().getFileSet();
temp.forEach( (str)->ResOutput.getResOutput().println(str) );
}
}
package project;
/**
* last value trace class.
* Because many class need to use last value, and reference transmit cause the reduency
* so use this class to keep the last value.
*/
public class LastCaluNum{
private Double num;
private static final LastCaluNum last = new LastCaluNum();
private LastCaluNum(){}
public static LastCaluNum getLastCaluNum(){
return last;
}
public void setNum(Double num){
this.num = num;
}
public Double getNum(){
return this.num;
}
}
package project;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
/**
* this class for log situtation.
*/
public class LogSe {
private boolean logSesActiveButton;
private String path;
private Set<String> fileName;
private String currentFile;
public static final LogSe myLoger = new LogSe();
private LogSe() {
this.logSesActiveButton = false;
this.path = "./"; // default situation to save logFile in project path.
fileName = new HashSet<>();
}
public static LogSe getLoger(){
return myLoger;
}
public void activeLoger() {
this.logSesActiveButton = true;
}
public boolean getLogActiveButton() {
return this.logSesActiveButton;
}
public void quitLoger(){
this.logSesActiveButton = false;
}
public void setPath(String path) {
this.path = path;
}
public String getPath() {
return this.path;
}
public void setFile(String file){
this.currentFile = file;
if (!fileName.contains(currentFile)) { fileName.add(file); }
}
public String getFile(){
return this.currentFile;
}
public Set<String> getFileSet(){
return fileName;
}
public void writeLogFile(String input, boolean changeLenOrnNot) {
try {
FileWriter myFileWriter = new FileWriter(this.path + "/" + currentFile, true);
write2File(myFileWriter, input, changeLenOrnNot);
myFileWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void write2File(FileWriter myFileWriter, String text, boolean changeLenOrnNot) throws IOException {
myFileWriter.write(text);
if (changeLenOrnNot) { myFileWriter.write("\n"); }
}
}
package project;
import java.util.*;
public class MathematicalEvaluator{
private Stack<Double> valueStack;
private Stack<thisOp> operatorStack;
private static final MathematicalEvaluator mathEvaluer = new MathematicalEvaluator();
private MathematicalEvaluator(){
valueStack = new Stack<>();
operatorStack = new Stack<>();
}
public static MathematicalEvaluator getMathematicalEvaluator(){
return mathEvaluer;
}
public Double calculate(Tokenizer tokenier, Map<String, Double> variables)throws TokenException, SyntaxErrorException{
try{
// init the stack
if (!valueStack.isEmpty()) { valueStack.clear(); }
if (!operatorStack.isEmpty()) { operatorStack.clear(); }
while (tokenier.hasNextToken()){
Token t = tokenier.readNextToken();
if (t.isNumber()){
valueStack.push(t.getNumber());
}else if (t.isIdentifier()){
idenProcess(t, variables);
}else if (t.isOperator()){
operaProcess(t);
}else if (t.isDelimiter()){
parenProces(t);
}
}
while (!operatorStack.isEmpty()){
loadNumCalu();
}
}catch(Exception e){
throw new SyntaxErrorException("malformed expression");
}
if (!operatorStack.isEmpty() || valueStack.size() != 1){ throw new SyntaxErrorException("malformed expression"); }
Double res = valueStack.pop();
LastCaluNum.getLastCaluNum().setNum(res);
return res;
}
private void idenProcess(Token t, Map<String, Double> variables) throws TokenException{
if (moreMathFunAdd(t.getIdentifier())){
operaIdenProcess(t);
}else{
if (t.getIdentifier().equals("last")){
Double value = LastCaluNum.getLastCaluNum().getNum();
valueStack.push(value);
}else{
Double value = variables.get(t.getIdentifier());
valueStack.push(value);
}
}
}
private void operaIdenProcess(Token t) throws TokenException {
thisOp op = new thisOp(t.getIdentifier());
while (!operatorStack.isEmpty() && operatorStack.peek().getPriority() >= op.getPriority()){
loadNumCalu();
}
operatorStack.push(op);
}
// add the more math fun.
private boolean moreMathFunAdd(String operator){
return operator.equals("cos")||operator.equals("sin")||operator.equals("tan")||operator.equals("abs");
}
private void parenProces(Token t) throws TokenException {
if (t.getDelimiter().equals("(")){
thisOp op = new thisOp("(");
operatorStack.push(op);
}else if (t.getDelimiter().equals(")")){
while (!operatorStack.peek().getName().equals("(")){
loadNumCalu();
}
operatorStack.pop();
}
}
private void operaProcess(Token t) throws TokenException {
thisOp op = new thisOp(t.getOperator());
while (!operatorStack.isEmpty() && operatorStack.peek().getPriority() >= op.getPriority()){
loadNumCalu();
}
operatorStack.push(op);
}
private void loadNumCalu() throws TokenException {
thisOp op = operatorStack.pop();
if (op.getArity() == 2){
Double y = valueStack.pop();
Double x = valueStack.pop();
if (op.getName().equals("+")){
add(x,y);
}else if (op.getName().equals("-")){
sub(x,y);
}else if (op.getName().equals("*")){
mul(x,y);
}else if (op.getName().equals("/")){
div(x,y);
}else if (op.getName().equals("^")){
exp(x,y);
}
}else if (op.getArity() == 1){
Double x = valueStack.pop();
if (op.getName().equals("~")){
rev(x);
}else if (op.getName().equals("cos")){
cos(x);
}else if (op.getName().equals("sin")){
sin(x);
}else if (op.getName().equals("tan")){
tan(x);
}else if (op.getName().equals("abs")){
abs(x);
}
}
}
private void add(Double x, Double y){
valueStack.push(x+y);
}
private void sub(Double x, Double y){
valueStack.push(x-y);
}
private void mul(Double x, Double y){
valueStack.push(x*y);
}
private void div(Double x, Double y){
valueStack.push(x/y);
}
private void exp(Double x, Double y){
valueStack.push(Math.pow(x, y));
}
private void rev(Double x){
valueStack.push(-x);
}
private void cos(Double x){
valueStack.push(Math.cos(x));
}
private void sin(Double x){
valueStack.push(Math.sin(x));
}
private void tan(Double x){
valueStack.push(Math.tan(x));
}
private void abs(Double x){
valueStack.push(Math.abs(x));
}
}
package project;
public class ResOutput{
private int precision;
private static final ResOutput outputer = new ResOutput();
private ResOutput(){
this.precision = -1; // Default precision. just Output.
}
public static ResOutput getResOutput(){
return outputer;
}
public void setPrecision(int precision){
this.precision = precision;
}
public int getPrecision(){
return this.precision;
}
public void println(Double num){
// user not set the precision. Just ouput the num
if (this.precision == -1){
String checkInt = String.valueOf(num);
if (checkInt.indexOf('.') == checkInt.length() - 2 && checkInt.charAt(checkInt.length()-1) == '0'){
myPrintln(checkInt.substring(0, checkInt.length() - 2));
return;
}
myPrintln(checkInt);
}else{
String temp = String.valueOf(num);
int index = temp.indexOf(".");
if (this.precision == 0){
// lose the decimal of num.
String res = temp.substring(0, index);
myPrintln(res);
}else{
if (index+1+this.precision >= temp.length()){
myPrintln(temp);
}else{
String res = temp.substring(0, index+1+this.precision);
myPrintln(res);
}
}
}
}
private void myPrintln(String res){
if (LogSe.getLoger().getLogActiveButton()) {
LogSe.getLoger().writeLogFile(res, true);
}
System.out.println(res);
}
// overload
public void println(String len){
if (LogSe.getLoger().getLogActiveButton()) {
LogSe.getLoger().writeLogFile(len, true);
}
System.out.println(len);
}
public void print(String len){
if (LogSe.getLoger().getLogActiveButton()) {
LogSe.getLoger().writeLogFile(len, false);
}
System.out.print(len);
}
}
package project;
import java.util.*;
public class Smac{
// path can be changed by client user
public static final String VARPATH = "./varFile";
public static final String LOGPATH = "./logFile";
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
// creat SMAC
IEvaluator smac = new Evaluator();
smac.println("Welcome to SMAC");
smac.print("> ");
String input = console.nextLine().trim();
smac.varFilePath(VARPATH);
smac.logFilePath(LOGPATH);
while (!input.equals("exit")) {
try{
smac.createInputToken(input);
if (!smac.syntaxAnalysis()){
smac.mathEvaluator();
}
}catch(Exception e){
smac.println(e.toString());
// use for debug
// e.printStackTrace();
}
if(smac.logSession()){
smac.print(">> ");
input = console.nextLine();
smac.logUserInput(input);
}else{
smac.print("> ");
input = console.nextLine();
}
}
console.close();
smac.println("Thank you for using SMAC");
}
}
package project;
import java.util.*;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.FileReader;
import java.io.IOException;
/**
* this class use IO to save variables and load(get saved) variables.
*/
public class StorVar {
private static StorVar myStorer;
private String path;
private ArrayList<String> fileName;
private StorVar(){ fileName = new ArrayList<>(); }
public static StorVar getStorVar(){
if (myStorer == null){
myStorer = new StorVar();
}
return myStorer;
}
public void loadPath(String p){
this.path = p;
}
public void saveFile(String file, Map<String, Double> variables){
if (!fileName.contains(file)){ fileName.add(file); }
if (variables.isEmpty()) { ResOutput.getResOutput().println("no variables to save"); return; }
try{
FileWriter myFileWriter = new FileWriter(this.path+"/"+file);
writeVar2File(myFileWriter, variables);
myFileWriter.close();
ResOutput.getResOutput().println("variables saved in "+file);
}catch (IOException e){
e.toString();
}
}
private void writeVar2File(FileWriter myFileWriter, Map<String, Double> variables) throws IOException {
Iterator<String> it = variables.keySet().iterator();
while (it.hasNext()){
String var = it.next();
Double value = variables.get(var);
myFileWriter.write("let "+var+" = "+value);
myFileWriter.write("\n");
}
}
public void saveFileOneLetter(String file, Map<String, Double> variables, String variable){
if (!fileName.contains(file)){ fileName.add(file); }
if (variables.isEmpty()) { ResOutput.getResOutput().println("no variables to save"); return;}
if (!variables.containsKey(variable)) { ResOutput.getResOutput().println("variable is not defined"); return;}
try{
FileWriter myFileWriter = new FileWriter(this.path+"/"+file);
writeVar2File(myFileWriter, variable, variables);
myFileWriter.close();
ResOutput.getResOutput().println("variables saved in "+file);
}catch (IOException e){
e.toString();
}
}
// overload
private void writeVar2File(FileWriter myFileWriter, String variable, Map<String, Double> variables) throws IOException {
myFileWriter.write("let "+variable+" = "+variables.get(variable));
}
public void savedFile(){
if (fileName.isEmpty()) { ResOutput.getResOutput().println("no file to save"); return; }
for (String name : fileName) {
ResOutput.getResOutput().println(name);
}
}
public ArrayList<String> loadVar(String file) throws ErrorException {
ArrayList<String> res = new ArrayList<>();
try{
BufferedReader myFileReader = new BufferedReader(new FileReader(this.path+"/"+file));
readFile2Vars(myFileReader, res);
myFileReader.close();
ResOutput.getResOutput().println(file+" loaded");
}catch (IOException e){
throw new ErrorException("file is not exist");
}
return res;
}
private void readFile2Vars(BufferedReader myFileReader, ArrayList<String> res) throws IOException {
String line;
while ( (line = myFileReader.readLine()) != null) {
res.add(line);
}
}
}
public class SyntaxAnalyzer{
private static final SyntaxAnalyzer syntaxAnalyzer = new SyntaxAnalyzer();
private SyntaxAnalyzer(){}
public static SyntaxAnalyzer getSyntaxAnalyzer(){
return syntaxAnalyzer;
}
public boolean analyse(Tokenizer myTokenizer, Set<String> keywords, Map<String, Double>variables)throws ErrorException, SyntaxErrorException, LexicalErrorException, TokenException {
Token t = myTokenizer.peekNextToken();
if (t.isNumber()) {
return false;
}else if (t.isDelimiter()){
if (t.getDelimiter().equals("(")) { return false; }
}else if (t.isIdentifier()){
if (keywords.contains(t.getIdentifier())) {
KeywordsCommFactory command = KeywordsCommFactory.getKeywordsCommFactory();
command.factory(myTokenizer, variables);
return true;
}
if (variables.containsKey(t.getIdentifier())) {
return false;
}else if (t.getIdentifier().equals("cos")||t.getIdentifier().equals("sin")||t.getIdentifier().equals("tan")||t.getIdentifier().equals("abs")){
return false;
}else {
throw new ErrorException(t.getIdentifier() + " is not a variable");
}
}else {
throw new SyntaxErrorException("not vaild input");
}
throw new SyntaxErrorException("not vaild input");
}
}
package project;
public class thisOp{
private String name;
private int arity;
private int priority;
public thisOp(String name){
this.name = name;
this.arity = 0;
this.priority = 0;
}
public String getName(){
return this.name;
}
public int getArity() throws TokenException {
switch(this.name){
case"-":
this.arity = 2;
break;
case"~":
this.arity = 1;
break;
case"+":
this.arity = 2;
break;
case"*":
this.arity = 2;
break;
case"/":
this.arity = 2;
break;
case"^":
this.arity = 2;
break;
case"cos":
this.arity = 1;
break;
case"sin":
this.arity = 1;
break;
case"tan":
this.arity = 1;
break;
case"abs":
this.arity = 1;
break;
default:
throw new TokenException("getArity worng");
}
return this.arity;
}
public int getPriority() throws TokenException {
switch(this.name){
case"-":
this.priority = 1;
break;
case"~":
this.priority = 2;
break;
case"+":
this.priority = 1;
break;
case"*":
this.priority = 2;
break;
case"/":
this.priority = 2;
break;
case"^":
this.priority = 3;
break;
case"(":
this.priority = 0;
break;
case"cos":
this.priority = 3;
break;
case"sin":
this.priority = 3;
break;
case"tan":
this.priority = 3;
break;
case"abs":
this.priority = 3;
break;
default:
throw new TokenException("getPriority worng");
}
return this.priority;
}
}
package project;
import java.util.*;
import java.util.regex.Pattern;
public class Tokenizer {
private Deque<Token> que;
private static final char[] DELIMITER = {'=', '(', ')', ',', '^', '*', '/', '+', '-'};
public Tokenizer(String input)throws LexicalErrorException, TokenException{
que = new LinkedList<>();
String[] temp = input.trim().split("[ \u0009]+"); // [/t] will split the 't', so replace to [\u0009]
inputToToken(temp);
}
/**
* checks if there is more token to read
*/
public boolean hasNextToken() {
return !que.isEmpty();
}
/**
* returns the next token to be read
* Throws a TokenException if there is
* no more token to peek in the Tokenizer
* YOU MAY ADD THROW CLAUSES TO THIS METHOD
*/
public Token peekNextToken() throws LexicalErrorException{
if (!hasNextToken()) { throw new LexicalErrorException("no more token to peek"); }
return que.peek();
}
/**
* reads and returns the next token to be read
* (i.e. the next token is removed from the tokenizer)
* YOU MAY ADD THROW CLAUSES TO THIS METHOD
*/
public Token readNextToken() {
return que.poll();
}
// add your private methods below
private void inputToToken(String myString[]) throws LexicalErrorException, TokenException {
ArrayList<String> delier = new ArrayList<>();
// split by delimiter(except space and tab) every line.
for (int i = 0; i < myString.length; i++){
splitEveryLine(delier, myString[i]);
buildTokenList(delier);
delier.clear();
}
}
// split by delimiter(except space and tab) every line.
private void splitEveryLine(ArrayList<String> delier, String myString){
// split the string by delimiter.
String temp = "";
for (int i = 0; i < myString.length(); i++){
if (findDeliChar(myString.charAt(i))){
if (!temp.isEmpty()) { delier.add(temp); }
delier.add(String.valueOf(myString.charAt(i)));
temp = "";
} else{
temp = temp + myString.charAt(i);
}
}
if (!temp.isEmpty()) { delier.add(temp); }
}
// search delimiter.
private boolean findDeliChar(char target){
for (int i = 0; i < DELIMITER.length; i++){
if (target == DELIMITER[i]){ return true; }
}
return false;
}
// build token list.
private void buildTokenList(ArrayList<String> delier) throws LexicalErrorException, TokenException {
for (int i = 0; i < delier.size(); i++){
factoryToken(delier.get(i));
}
}
// use factory idea.
private void factoryToken(String str) throws LexicalErrorException, TokenException {
switch (str){
case "=" :
que.add(Token.makeEQUAL());
break;
case "(" :
que.add(Token.makeOPENPAR());
break;
case ")" :
que.add(Token.makeCLOSEPAR());
break;
case "," :
que.add(Token.makeCOMMA());
break;
case "-" :
if (que.isEmpty()){
que.add(Token.makeUNARYMINUS());
break;
}
if (que.getLast().isNumber() || que.getLast().isIdentifier() || (que.getLast().isDelimiter() && que.getLast().getDelimiter().equals(")")) ){
que.add(Token.makeMINUS());
break;
}
que.add(Token.makeUNARYMINUS());
break;
case "*":
que.add(Token.makeTIMES());
break;
case "/":
que.add(Token.makeDIVIDE());
break;
case "^":
que.add(Token.makePOWER());
break;
case "+":
que.add(Token.makePLUS());
break;
default:
if (isNumeric(str)){
double num = Double.parseDouble(str);
que.add(Token.makeNUMBER(num));
break;
} else if (str.length() > 3 && str.charAt(0) == '"' && str.charAt(str.length()-1) == '"'){
que.add(Token.makeSTRING(str));
break;
} else if (str.length() > 0 && isWord(str)){
que.add(Token.makeIDENTIFIER(str));
break;
} else{
throw new LexicalErrorException(str + " is not a valid character");
}
}
}
//use regex pattern to estimate is number or not.
public boolean isNumeric(String str){
Pattern pattern = Pattern.compile("[.0-9]+");
return pattern.matcher(str).matches();
}
// use regex pattern to estimate is legal variable or not.
public static boolean isWord(String str){
Pattern pattern = Pattern.compile("[a-zA-Z_0-9]+");
Pattern firstChar = Pattern.compile("[_0-9]");
return !firstChar.matcher(String.valueOf(str.charAt(0))).matches() && pattern.matcher(str).matches();
}
}