Upload files to "java/expression/parser"
This commit is contained in:
247
java/expression/parser/ExpressionParser.java
Normal file
247
java/expression/parser/ExpressionParser.java
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
package expression.parser;
|
||||||
|
|
||||||
|
import expression.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class ExpressionParser implements ListParser {
|
||||||
|
|
||||||
|
private String src;
|
||||||
|
private int pos;
|
||||||
|
private List<String> variables;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListExpression parse(String expression, List<String> variables) {
|
||||||
|
this.src = expression;
|
||||||
|
this.pos = 0;
|
||||||
|
this.variables = variables;
|
||||||
|
AbstractExpression result = parseMinMax();
|
||||||
|
skipWhitespace();
|
||||||
|
if (pos < src.length()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unexpected character '" +
|
||||||
|
src.charAt(pos) +
|
||||||
|
"' at position " +
|
||||||
|
pos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractExpression parseMinMax() {
|
||||||
|
AbstractExpression left = parseAddSub();
|
||||||
|
while (true) {
|
||||||
|
skipWhitespace();
|
||||||
|
if (tryConsume("min")) {
|
||||||
|
left = new Min(left, parseAddSub());
|
||||||
|
} else if (tryConsume("max")) {
|
||||||
|
left = new Max(left, parseAddSub());
|
||||||
|
} else if (tryConsume("set")) {
|
||||||
|
left = new SetBit(left, parseAddSub());
|
||||||
|
} else if (tryConsume("clear")) {
|
||||||
|
left = new Clear(left, parseAddSub());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractExpression parseAddSub() {
|
||||||
|
AbstractExpression left = parseMulDiv();
|
||||||
|
while (true) {
|
||||||
|
skipWhitespace();
|
||||||
|
if (pos < src.length() && src.charAt(pos) == '+') {
|
||||||
|
pos++;
|
||||||
|
left = new Add(left, parseMulDiv());
|
||||||
|
} else if (
|
||||||
|
pos < src.length() &&
|
||||||
|
src.charAt(pos) == '-' &&
|
||||||
|
!isUnaryContext()
|
||||||
|
) {
|
||||||
|
pos++;
|
||||||
|
left = new Subtract(left, parseMulDiv());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractExpression parseMulDiv() {
|
||||||
|
AbstractExpression left = parseUnary();
|
||||||
|
while (true) {
|
||||||
|
skipWhitespace();
|
||||||
|
if (pos < src.length() && src.charAt(pos) == '*') {
|
||||||
|
pos++;
|
||||||
|
left = new Multiply(left, parseUnary());
|
||||||
|
} else if (pos < src.length() && src.charAt(pos) == '/') {
|
||||||
|
pos++;
|
||||||
|
left = new Divide(left, parseUnary());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractExpression parseUnary() {
|
||||||
|
skipWhitespace();
|
||||||
|
if (pos >= src.length()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unexpected end of expression at position " + pos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.charAt(pos) == '-') {
|
||||||
|
pos++;
|
||||||
|
if (pos < src.length() && Character.isDigit(src.charAt(pos))) {
|
||||||
|
return parseNumber(true);
|
||||||
|
}
|
||||||
|
return new Negate(parseUnary());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryConsume("reverse")) {
|
||||||
|
requireSeparatorOrParen();
|
||||||
|
return new Reverse(parseUnary());
|
||||||
|
}
|
||||||
|
if (tryConsume("digits")) {
|
||||||
|
requireSeparatorOrParen();
|
||||||
|
return new Digits(parseUnary());
|
||||||
|
}
|
||||||
|
if (tryConsume("floor")) {
|
||||||
|
requireSeparatorOrParen();
|
||||||
|
return new Floor(parseUnary());
|
||||||
|
}
|
||||||
|
if (tryConsume("ceiling")) {
|
||||||
|
requireSeparatorOrParen();
|
||||||
|
return new Ceiling(parseUnary());
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsePrimary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractExpression parsePrimary() {
|
||||||
|
skipWhitespace();
|
||||||
|
if (pos >= src.length()) {
|
||||||
|
throw new IllegalArgumentException("Unexpected end of expression");
|
||||||
|
}
|
||||||
|
char c = src.charAt(pos);
|
||||||
|
|
||||||
|
if (c == '(') {
|
||||||
|
pos++;
|
||||||
|
AbstractExpression inner = parseMinMax();
|
||||||
|
skipWhitespace();
|
||||||
|
expect();
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '$') {
|
||||||
|
pos++;
|
||||||
|
return new Variable(parseIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Character.isDigit(c)) {
|
||||||
|
return parseNumber(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Character.isLetter(c)) {
|
||||||
|
int start = pos;
|
||||||
|
while (
|
||||||
|
pos < src.length() &&
|
||||||
|
(Character.isLetterOrDigit(src.charAt(pos)) ||
|
||||||
|
src.charAt(pos) == '_')
|
||||||
|
) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
String name = src.substring(start, pos);
|
||||||
|
int idx = variables.indexOf(name);
|
||||||
|
if (idx >= 0) {
|
||||||
|
return new Variable(idx, name);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unknown identifier '" + name + "' at position " + start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unexpected character '" + c + "' at position " + pos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipWhitespace() {
|
||||||
|
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryConsume(String keyword) {
|
||||||
|
skipWhitespace();
|
||||||
|
if (!src.startsWith(keyword, pos)) return false;
|
||||||
|
int end = pos + keyword.length();
|
||||||
|
if (end < src.length()) {
|
||||||
|
char next = src.charAt(end);
|
||||||
|
if (Character.isLetterOrDigit(next) || next == '_') return false;
|
||||||
|
}
|
||||||
|
if (variables != null && variables.contains(keyword)) return false;
|
||||||
|
pos = end;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireSeparatorOrParen() {
|
||||||
|
skipWhitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expect() {
|
||||||
|
if (pos >= src.length() || src.charAt(pos) != ')') {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected '" +
|
||||||
|
')' +
|
||||||
|
"' at position " +
|
||||||
|
pos +
|
||||||
|
(pos < src.length()
|
||||||
|
? ", got '" + src.charAt(pos) + "'"
|
||||||
|
: ", got end of input")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractExpression parseNumber(boolean negative) {
|
||||||
|
int start = pos;
|
||||||
|
while (pos < src.length() && Character.isDigit(src.charAt(pos))) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (start == pos) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected digit at position " + pos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
String numStr = src.substring(start, pos);
|
||||||
|
long val = Long.parseLong(numStr);
|
||||||
|
if (negative) val = -val;
|
||||||
|
if (val < Integer.MIN_VALUE || val > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("Integer overflow: " + val);
|
||||||
|
}
|
||||||
|
return new Const((int) val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseIndex() {
|
||||||
|
int start = pos;
|
||||||
|
while (pos < src.length() && Character.isDigit(src.charAt(pos))) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (start == pos) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected digit after '$' at position " + pos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Integer.parseInt(src.substring(start, pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUnaryContext() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
java/expression/parser/ListParser.java
Normal file
12
java/expression/parser/ListParser.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package expression.parser;
|
||||||
|
|
||||||
|
import expression.ListExpression;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ListParser {
|
||||||
|
ListExpression parse(String expression, List<String> variables);
|
||||||
|
}
|
||||||
232
java/expression/parser/Operations.java
Normal file
232
java/expression/parser/Operations.java
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
package expression.parser;
|
||||||
|
|
||||||
|
import expression.ToMiniString;
|
||||||
|
import expression.common.ExpressionKind;
|
||||||
|
import expression.common.Reason;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class Operations {
|
||||||
|
|
||||||
|
// === Base
|
||||||
|
|
||||||
|
public static final Operation NEGATE = unary("-", 1, a -> -a);
|
||||||
|
|
||||||
|
@SuppressWarnings("Convert2MethodRef")
|
||||||
|
public static final Operation ADD = binary("+", 1600, (a, b) -> a + b);
|
||||||
|
|
||||||
|
public static final Operation SUBTRACT = binary("-", 1602, (a, b) -> a - b);
|
||||||
|
public static final Operation MULTIPLY = binary("*", 2001, (a, b) -> a * b);
|
||||||
|
public static final Operation DIVIDE = binary("/", 2002, (a, b) ->
|
||||||
|
b == 0 ? Reason.DBZ.error() : a / b
|
||||||
|
);
|
||||||
|
|
||||||
|
// === MinMax
|
||||||
|
public static final Operation MIN = binary("min", 401, Math::min);
|
||||||
|
public static final Operation MAX = binary("max", 401, Math::max);
|
||||||
|
|
||||||
|
// === Reverse
|
||||||
|
|
||||||
|
private static Operation digits(
|
||||||
|
final String name,
|
||||||
|
final boolean mask,
|
||||||
|
final int r,
|
||||||
|
final LongBinaryOperator q
|
||||||
|
) {
|
||||||
|
return unary(name, 1, v ->
|
||||||
|
LongStream.iterate(
|
||||||
|
mask ? v & 0xffff_ffffL : v,
|
||||||
|
n -> n != 0,
|
||||||
|
n -> n / r
|
||||||
|
)
|
||||||
|
.map(n -> n % r)
|
||||||
|
.reduce(0, q)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Operation REVERSE = digits(
|
||||||
|
"reverse",
|
||||||
|
false,
|
||||||
|
10,
|
||||||
|
(a, b) -> a * 10 + b
|
||||||
|
);
|
||||||
|
|
||||||
|
// === Digits
|
||||||
|
public static final Operation DIGITS = digits(
|
||||||
|
"digits",
|
||||||
|
false,
|
||||||
|
10,
|
||||||
|
Long::sum
|
||||||
|
);
|
||||||
|
|
||||||
|
// === Floor and Ceiling
|
||||||
|
|
||||||
|
private static long floor(final long a) {
|
||||||
|
return (
|
||||||
|
((a >= 0 ? a : a - FLOOR_CEILING_STEP + 1) / FLOOR_CEILING_STEP) *
|
||||||
|
FLOOR_CEILING_STEP
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long ceiling(final long a) {
|
||||||
|
return (
|
||||||
|
((a >= 0 ? a + FLOOR_CEILING_STEP - 1 : a) / FLOOR_CEILING_STEP) *
|
||||||
|
FLOOR_CEILING_STEP
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int FLOOR_CEILING_STEP = 1000;
|
||||||
|
public static final Operation FLOOR = unary("floor", 1, Operations::floor);
|
||||||
|
public static final Operation CEILING = unary(
|
||||||
|
"ceiling",
|
||||||
|
1,
|
||||||
|
Operations::ceiling
|
||||||
|
);
|
||||||
|
|
||||||
|
// === Set, Clear
|
||||||
|
|
||||||
|
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
|
||||||
|
public static final Operation SET = binary(
|
||||||
|
"set",
|
||||||
|
202,
|
||||||
|
(a, b) -> a | (1 << b)
|
||||||
|
);
|
||||||
|
|
||||||
|
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
|
||||||
|
public static final Operation CLEAR = binary(
|
||||||
|
"clear",
|
||||||
|
202,
|
||||||
|
(a, b) -> a & ~(1 << b)
|
||||||
|
);
|
||||||
|
|
||||||
|
// === Pow, Log
|
||||||
|
public static final Operation POW_O = binary("**", 2402, (a, b) ->
|
||||||
|
b < 0
|
||||||
|
? 1
|
||||||
|
: BigInteger.valueOf(a)
|
||||||
|
.modPow(BigInteger.valueOf(b), BigInteger.valueOf(1L << 32))
|
||||||
|
.intValue()
|
||||||
|
);
|
||||||
|
public static final Operation LOG_O = binary("//", 2402, (a, b) ->
|
||||||
|
a == 0 && b > 0
|
||||||
|
? Integer.MIN_VALUE
|
||||||
|
: a <= 0 || b <= 0 || (a == 1 && b == 1)
|
||||||
|
? 0
|
||||||
|
: a > 1 && b == 1
|
||||||
|
? Integer.MAX_VALUE
|
||||||
|
: LongStream.iterate(b, v -> v <= a, v -> v * b).count()
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Reason INVALID_POW = new Reason("Invalid power");
|
||||||
|
public static final Operation POW = binary("**", 2402, Operations::powC);
|
||||||
|
|
||||||
|
private static long powC(final long a, final long b) {
|
||||||
|
if (b < 0 || (a == 0 && b == 0)) {
|
||||||
|
return INVALID_POW.error();
|
||||||
|
}
|
||||||
|
if (Math.abs(a) > 1 && b > 32) {
|
||||||
|
return Reason.OVERFLOW.error();
|
||||||
|
}
|
||||||
|
final BigInteger result = BigInteger.valueOf(a).pow((int) b);
|
||||||
|
if (
|
||||||
|
result.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0 ||
|
||||||
|
BigInteger.valueOf(Integer.MAX_VALUE).compareTo(result) < 0
|
||||||
|
) {
|
||||||
|
return Reason.OVERFLOW.error();
|
||||||
|
}
|
||||||
|
return result.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Reason INVALID_LOG = new Reason("Invalid log");
|
||||||
|
public static final Operation LOG = binary("//", 2402, (a, b) ->
|
||||||
|
a <= 0 || b <= 1
|
||||||
|
? INVALID_LOG.error()
|
||||||
|
: (int) (Math.log(a) / Math.log(b))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pow2, Log2
|
||||||
|
|
||||||
|
private static final Reason NEG_LOG = new Reason(
|
||||||
|
"Logarithm of negative value"
|
||||||
|
);
|
||||||
|
public static final Operation LOG_2 = unary(
|
||||||
|
"log₂",
|
||||||
|
1,
|
||||||
|
NEG_LOG.less(1, a -> (long) (Math.log(a) / Math.log(2)))
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Reason NEG_POW = new Reason(
|
||||||
|
"Exponentiation to negative power"
|
||||||
|
);
|
||||||
|
public static final Operation POW_2 = unary(
|
||||||
|
"pow₂",
|
||||||
|
1,
|
||||||
|
NEG_POW.less(0, Reason.OVERFLOW.greater(31, a -> (long) Math.pow(2, a)))
|
||||||
|
);
|
||||||
|
|
||||||
|
// === High, Low
|
||||||
|
public static final Operation HIGH = unary("high", 1, v ->
|
||||||
|
Integer.highestOneBit((int) v)
|
||||||
|
);
|
||||||
|
public static final Operation LOW = unary("low", 1, v ->
|
||||||
|
Integer.lowestOneBit((int) v)
|
||||||
|
);
|
||||||
|
|
||||||
|
// === Common
|
||||||
|
|
||||||
|
private Operations() {}
|
||||||
|
|
||||||
|
public static Operation unary(
|
||||||
|
final String name,
|
||||||
|
final int priority,
|
||||||
|
final LongUnaryOperator op
|
||||||
|
) {
|
||||||
|
return unary(name, priority, (a, c) -> op.applyAsLong(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation unary(
|
||||||
|
final String left,
|
||||||
|
final String right,
|
||||||
|
final LongUnaryOperator op
|
||||||
|
) {
|
||||||
|
return unary(left, right, (a, c) -> op.applyAsLong(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation unary(
|
||||||
|
final String name,
|
||||||
|
final int priority,
|
||||||
|
final BiFunction<Long, LongToIntFunction, Long> op
|
||||||
|
) {
|
||||||
|
return tests -> tests.unary(name, priority, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation unary(
|
||||||
|
final String left,
|
||||||
|
final String right,
|
||||||
|
final BiFunction<Long, LongToIntFunction, Long> op
|
||||||
|
) {
|
||||||
|
return tests -> tests.unary(left, right, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation binary(
|
||||||
|
final String name,
|
||||||
|
final int priority,
|
||||||
|
final LongBinaryOperator op
|
||||||
|
) {
|
||||||
|
return tests -> tests.binary(name, priority, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends ToMiniString, C> Operation kind(
|
||||||
|
final ExpressionKind<E, C> kind,
|
||||||
|
final ParserTestSet.Parser<E> parser
|
||||||
|
) {
|
||||||
|
return factory -> factory.kind(kind, parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Operation extends Consumer<ParserTester> {}
|
||||||
|
}
|
||||||
39
java/expression/parser/ParserTest.java
Normal file
39
java/expression/parser/ParserTest.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package expression.parser;
|
||||||
|
|
||||||
|
import static expression.parser.Operations.*;
|
||||||
|
|
||||||
|
import base.Selector;
|
||||||
|
import expression.ListExpression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class ParserTest {
|
||||||
|
|
||||||
|
private static final ExpressionParser PARSER = new ExpressionParser();
|
||||||
|
private static final Operations.Operation LIST = kind(
|
||||||
|
ListExpression.KIND,
|
||||||
|
PARSER::parse
|
||||||
|
);
|
||||||
|
|
||||||
|
// === Common
|
||||||
|
|
||||||
|
public static final Selector SELECTOR = Selector.composite(
|
||||||
|
ParserTest.class,
|
||||||
|
ParserTester::new,
|
||||||
|
"easy",
|
||||||
|
"hard"
|
||||||
|
)
|
||||||
|
.variant("Base", LIST, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
|
||||||
|
.variant("3637", MIN, MAX, REVERSE)
|
||||||
|
.variant("3839", MIN, MAX, REVERSE, DIGITS)
|
||||||
|
.variant("3435", FLOOR, CEILING, SET, CLEAR)
|
||||||
|
.variant("3233", FLOOR, CEILING)
|
||||||
|
.selector();
|
||||||
|
|
||||||
|
private ParserTest() {}
|
||||||
|
|
||||||
|
public static void main(final String... args) {
|
||||||
|
SELECTOR.main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
java/expression/parser/package-info.java
Normal file
7
java/expression/parser/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#expressions-parsing">Expressions Parsing</a> homework
|
||||||
|
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
package expression.parser;
|
||||||
Reference in New Issue
Block a user