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