package scotch.compiler.analyzer;
import static java.util.Arrays.asList;
import static scotch.compiler.error.ParseError.parseError;
import static scotch.compiler.syntax.value.Values.apply;
import static scotch.compiler.syntax.value.Values.unshuffled;
import static scotch.util.Either.left;
import static scotch.util.Either.right;
import static scotch.symbol.Symbol.symbol;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.function.Function;
import scotch.compiler.error.SyntaxError;
import scotch.compiler.syntax.type.Type;
import scotch.compiler.syntax.scope.Scope;
import scotch.compiler.syntax.value.Identifier;
import scotch.compiler.syntax.value.Value;
import scotch.util.Either;
public class ValueShuffler {
private final Function<Value, Value> parser;
public ValueShuffler(Function<Value, Value> parser) {
this.parser = parser;
}
public Either<SyntaxError, Value> shuffle(Scope scope, List<Value> message) {
if (message.size() == 1) {
Value value = parser.apply(message.get(0));
return right(unshuffled(value.getSourceLocation(), asList(value)));
} else {
try {
return right(parser.apply(new Shuffler(scope, message).shuffleValue()));
} catch (ShuffleException exception) {
return left(exception.syntaxError);
}
}
}
private static final class ShuffleException extends RuntimeException {
private final SyntaxError syntaxError;
private ShuffleException(SyntaxError syntaxError) {
super(syntaxError.prettyPrint());
this.syntaxError = syntaxError;
}
}
private final class Shuffler {
private final Scope scope;
private final List<Value> message;
public Shuffler(Scope scope, List<Value> message) {
this.scope = scope;
this.message = message;
}
public Value shuffleValue() {
Deque<Value> input = new ArrayDeque<>(message);
Deque<Either<OperatorPair<Identifier>, Value>> output = new ArrayDeque<>();
Deque<OperatorPair<Identifier>> stack = new ArrayDeque<>();
boolean expectsPrefix = isOperator(input.peek());
while (!input.isEmpty()) {
if (isOperator(input.peek())) {
OperatorPair<Identifier> o1 = getOperator(input.poll(), expectsPrefix);
while (!stack.isEmpty() && o1.isLessPrecedentThan(stack.peek())) {
output.push(left(stack.pop()));
}
stack.push(o1);
expectsPrefix = isOperator(input.peek());
} else {
output.push(right(shuffleNext(input)));
while (expectsArgument(input)) {
output.push(right(apply(
output.pop().orElseGet(OperatorPair::getValue),
shuffleNext(input),
reserveType()
)));
}
}
}
while (!stack.isEmpty()) {
output.push(left(stack.pop()));
}
return shuffleApply(output);
}
private boolean expectsArgument(Deque<Value> input) {
return !input.isEmpty() && expectsArgument_(input);
}
private boolean expectsArgument_(Deque<Value> input) {
return !isOperator(input.peek());
}
private OperatorPair<Identifier> getOperator(Value value, boolean expectsPrefix) {
return value.asOperator(scope)
.map(pair -> pair.into((identifier, operator) -> {
if (expectsPrefix && symbol("-").equals(identifier.getSymbol())) {
return getOperator(identifier.withSymbol(symbol("scotch.lang.(-prefix)")), true);
} else if (expectsPrefix && !operator.isPrefix()) {
throw new ShuffleException(parseError("Unexpected binary operator " + identifier.getSymbol(), identifier.getSourceLocation()));
} else {
return new OperatorPair<>(operator, identifier);
}
}))
.orElseThrow(() -> new ShuffleException(parseError("Value " + value.prettyPrint() + " is not an operator", value.getSourceLocation())));
}
private boolean isOperator(Value value) {
return value.isOperator(scope);
}
private Type reserveType() {
return scope.reserveType();
}
private Value shuffleApply(Deque<Either<OperatorPair<Identifier>, Value>> message) {
Deque<Value> stack = new ArrayDeque<>();
while (!message.isEmpty()) {
stack.push(message.pollLast().orElseGet(pair -> {
if (pair.isPrefix()) {
return apply(pair.getValue(), stack.pop(), reserveType());
} else {
Value right = stack.pop();
Value left = stack.pop();
return apply(apply(pair.getValue(), left, reserveType()), right, reserveType());
}
}));
}
while (stack.size() > 1) {
stack.push(apply(stack.pollLast(), stack.pollLast(), reserveType()));
}
return stack.pop();
}
private Value shuffleNext(Deque<Value> input) {
return input.poll().destructure()
.map(values -> shuffle(scope, values).orElseThrow(ShuffleException::new))
.orElseGet(left -> left);
}
}
}