-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
452 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>net.iryndin</groupId> | ||
<artifactId>expreval</artifactId> | ||
<version>1.0</version> | ||
|
||
<name>Simple arithmetic expression evaluator</name> | ||
|
||
<properties> | ||
<project.build.source>UTF-8</project.build.source> | ||
|
||
<maven.compiler.source>1.8</maven.compiler.source> | ||
<maven.compiler.target>1.8</maven.compiler.target> | ||
<maven.compiler.fork>true</maven.compiler.fork> | ||
|
||
<junit.version>4.12</junit.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>${junit.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
16 changes: 16 additions & 0 deletions
16
expreval/src/main/java/net/iryndin/expreval/EvalException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package net.iryndin.expreval; | ||
|
||
/** | ||
* @author iryndin | ||
* @since 06/04/17 | ||
*/ | ||
public class EvalException extends Exception{ | ||
|
||
public EvalException(String s) { | ||
super(s); | ||
} | ||
|
||
public EvalException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
} |
97 changes: 97 additions & 0 deletions
97
expreval/src/main/java/net/iryndin/expreval/ExpressionEvaluator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package net.iryndin.expreval; | ||
|
||
import java.util.EmptyStackException; | ||
import java.util.Stack; | ||
|
||
/** | ||
* @author iryndin | ||
* @since 06/04/17 | ||
*/ | ||
public class ExpressionEvaluator { | ||
|
||
private final ITokenEmitter tokenEmitter; | ||
private Stack<Token> stack = new Stack<>(); | ||
|
||
public ExpressionEvaluator(ITokenEmitter tokenEmitter) { | ||
this.tokenEmitter = tokenEmitter; | ||
} | ||
|
||
public long eval() throws EvalException { | ||
Token nextToken; | ||
|
||
while ((nextToken = tokenEmitter.emit()) != null) { | ||
if (nextToken.isOperation()) { | ||
if (stack.isEmpty()) { | ||
throw new EvalException("No first operand for operation: '" + nextToken + "'"); | ||
} | ||
if (stack.peek().isOperation()) { | ||
throw new EvalException("Two operations in a row: '" | ||
+ stack.peek() + "' and '" + nextToken + "'"); | ||
} | ||
} else { | ||
if (!stack.isEmpty() && !stack.peek().isOperation()) { | ||
throw new EvalException("Two numbers in a row: '" | ||
+ stack.peek() + "' and '" + nextToken + "'"); | ||
} | ||
} | ||
if (nextToken.getType() == Token.TYPE_ADD) { | ||
evalStack(); | ||
} | ||
stack.push(nextToken); | ||
} | ||
evalStack(); | ||
Token result = stack.pop(); | ||
if (result.isOperation()) { | ||
throw new EvalException("Smth goes wrong!"); | ||
} | ||
return result.getValue(); | ||
} | ||
|
||
private void evalStack() throws EvalException { | ||
while (!stack.isEmpty()) { | ||
Token operand2 = stack.pop(); | ||
if (stack.isEmpty()) { | ||
stack.push(operand2); | ||
break; | ||
} else { | ||
try { | ||
Token operation = stack.pop(); | ||
Token operand1 = stack.pop(); | ||
if (!operation.isOperation() || operand1.isOperation() || operand2.isOperation()) { | ||
throw new EvalException("Wrong!"); | ||
} | ||
switch (operation.getType()) { | ||
case Token.TYPE_ADD: { | ||
long res = operand1.getValue() + operand2.getValue(); | ||
stack.push(Token.createNumber(res)); | ||
break; | ||
} | ||
case Token.TYPE_MULTIPLY: { | ||
long res = operand1.getValue() * operand2.getValue(); | ||
stack.push(Token.createNumber(res)); | ||
break; | ||
} | ||
} | ||
} catch (EmptyStackException ese) { | ||
throw new EvalException("No operation or operand1", ese); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public static void main(String[] args) throws EvalException { | ||
StringTokenEmitter ste = new StringTokenEmitter("1+2*4*6+9"); | ||
ExpressionEvaluator ee = new ExpressionEvaluator(ste); | ||
System.out.println(ee.eval()); | ||
} | ||
|
||
} | ||
|
||
/* | ||
1+2*5*6*7*8*9+5*6*7*8= | ||
5*4 +3 +3 +5 +6*8*7*9*90 | ||
number - no tokens ? | ||
1 2 5 6 7 8 9 5 6 7 8 | ||
+ * * * * * + * * * | ||
*/ |
20 changes: 20 additions & 0 deletions
20
expreval/src/main/java/net/iryndin/expreval/ITokenEmitter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package net.iryndin.expreval; | ||
|
||
/** | ||
* Emit tokens. Can emit tokens from string, or from infinite incoming stream. | ||
* | ||
* @author iryndin | ||
* @since 06/04/17 | ||
*/ | ||
public interface ITokenEmitter { | ||
/** | ||
* Emit tokens in the order they income to this emitter. | ||
* Token may be an integer number, or "+" or "*". | ||
* Return null when tokens are ended. | ||
* The call to this method may block current thread until new token comes or end of list/stream of tokens is reached. | ||
* | ||
* @return next emitted token or null when no tokens anymore | ||
* @throws EvalException when evaluation error occurs | ||
*/ | ||
Token emit() throws EvalException; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package net.iryndin.expreval; | ||
|
||
/** | ||
* @author iryndin | ||
* @since 06/04/17 | ||
*/ | ||
public class Main { | ||
public static void main(String[] args) throws EvalException { | ||
ExpressionEvaluator ee = new ExpressionEvaluator(new StringTokenEmitter("1 +2*3+7")); | ||
System.out.println(ee.eval()); | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
expreval/src/main/java/net/iryndin/expreval/StringTokenEmitter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package net.iryndin.expreval; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
/** | ||
* Token emitter which takes tokens from a string expression | ||
* | ||
* @author iryndin | ||
* @since 06/04/17 | ||
*/ | ||
public class StringTokenEmitter implements ITokenEmitter { | ||
|
||
private final List<String> list; | ||
private Iterator<String> iter; | ||
|
||
public StringTokenEmitter(String expression) { | ||
this.list = parseExpression(expression); | ||
this.iter = list.iterator(); | ||
} | ||
|
||
static List<String> parseExpression(String expression) { | ||
List<String> result = new ArrayList<>(); | ||
StringBuilder sb = new StringBuilder(16); | ||
for (char c : expression.toCharArray()) { | ||
if (Character.isWhitespace(c)) { | ||
if (sb.length() > 0) { | ||
result.add(sb.toString()); | ||
sb.setLength(0); | ||
} | ||
} else if (c == '+') { | ||
if (sb.length() > 0) { | ||
result.add(sb.toString()); | ||
sb.setLength(0); | ||
} | ||
result.add("+"); | ||
} else if (c == '*') { | ||
if (sb.length() > 0) { | ||
result.add(sb.toString()); | ||
sb.setLength(0); | ||
} | ||
result.add("*"); | ||
} else if (Character.isDigit(c)) { | ||
sb.append(c); | ||
} | ||
} | ||
if (sb.length() > 0) { | ||
result.add(sb.toString()); | ||
sb.setLength(0); | ||
} | ||
return result; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "StringTokenEmitter{" + | ||
"list=" + list + | ||
'}'; | ||
} | ||
|
||
@Override | ||
public Token emit() throws EvalException { | ||
if (iter.hasNext()) { | ||
return Token.fromString(iter.next()); | ||
} | ||
return null; | ||
} | ||
|
||
public static void main(String[] args) { | ||
StringTokenEmitter e = new StringTokenEmitter(" 1 + 2* 5* 6* 7*8 *9+ \t5*6*\t7*8+6+7+8*7"); | ||
System.out.println(e); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package net.iryndin.expreval; | ||
|
||
import static java.lang.String.format; | ||
|
||
/** | ||
* Token class. | ||
* Token can be either integer positive number, or "+" operation, or "*" operation. | ||
* | ||
* @author iryndin | ||
* @since 06/04/17 | ||
*/ | ||
public class Token { | ||
|
||
public static final int TYPE_NUMBER = 1; | ||
public static final int TYPE_ADD = 2; | ||
public static final int TYPE_MULTIPLY = 3; | ||
|
||
private final long value; | ||
private final int type; | ||
|
||
private Token(long value, int type) { | ||
this.value = value; | ||
this.type = type; | ||
} | ||
|
||
public long getValue() { | ||
return value; | ||
} | ||
|
||
public int getType() { | ||
return type; | ||
} | ||
|
||
public boolean isOperation() { | ||
return type == TYPE_ADD || type == TYPE_MULTIPLY; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
switch (type) { | ||
case TYPE_ADD: | ||
return "+"; | ||
case TYPE_MULTIPLY: | ||
return "*"; | ||
default: | ||
return Long.toString(value); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
|
||
Token token = (Token) o; | ||
|
||
if (value != token.value) return false; | ||
return type == token.type; | ||
|
||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int result = (int) (value ^ (value >>> 32)); | ||
result = 31 * result + type; | ||
return result; | ||
} | ||
|
||
public static Token createNumber(long n) { | ||
return new Token(n, TYPE_NUMBER); | ||
} | ||
|
||
public static Token createAddOperation() { | ||
return new Token(0, TYPE_ADD); | ||
} | ||
|
||
public static Token createMultiplyOperation() { | ||
return new Token(0, TYPE_MULTIPLY); | ||
} | ||
|
||
public static Token fromString(String s) throws EvalException { | ||
s = s.trim(); | ||
if (s.equals("*")) { | ||
return createMultiplyOperation(); | ||
} else if (s.equals("+")) { | ||
return createAddOperation(); | ||
} else { | ||
try { | ||
return createNumber(Long.parseLong(s)); | ||
} catch (NumberFormatException nfe) { | ||
throw new EvalException(format("Cannot evaluate token '%s'", s), nfe); | ||
} | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
expreval/src/test/java/net/iryndin/expreval/ExpressionEvaluatorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package net.iryndin.expreval; | ||
|
||
import org.junit.Test; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
|
||
/** | ||
* @author iryndin | ||
* @since 06/04/17 | ||
*/ | ||
public class ExpressionEvaluatorTest { | ||
|
||
@Test | ||
public void testEval() throws EvalException { | ||
ExpressionEvaluator ee = new ExpressionEvaluator(new StringTokenEmitter("1+2*3+7*8+1+2+3")); | ||
assertEquals(1+2*3+7*8+1+2+3, ee.eval()); | ||
} | ||
|
||
@Test(expected = EvalException.class) | ||
public void testEvalFail1() throws EvalException { | ||
new ExpressionEvaluator(new StringTokenEmitter("+1")).eval(); | ||
} | ||
|
||
@Test(expected = EvalException.class) | ||
public void testEvalFail2() throws EvalException { | ||
new ExpressionEvaluator(new StringTokenEmitter("1 * ")).eval(); | ||
} | ||
|
||
@Test(expected = EvalException.class) | ||
public void testEvalFail3() throws EvalException { | ||
new ExpressionEvaluator(new StringTokenEmitter("1 2")).eval(); | ||
} | ||
|
||
@Test(expected = EvalException.class) | ||
public void testEvalFail4() throws EvalException { | ||
new ExpressionEvaluator(new StringTokenEmitter("1+2 3*")).eval(); | ||
} | ||
} |
Oops, something went wrong.