package scotch.compiler.analyzer; import static java.util.Collections.reverse; import static scotch.compiler.error.ParseError.parseError; import static scotch.util.Either.left; import static scotch.util.Either.right; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import com.google.common.collect.ImmutableList; import scotch.compiler.error.SyntaxError; import scotch.symbol.Symbol; import scotch.compiler.syntax.definition.UnshuffledDefinition; import scotch.compiler.syntax.scope.Scope; import scotch.compiler.syntax.pattern.CaptureMatch; import scotch.compiler.syntax.pattern.PatternMatch; import scotch.util.Either; public class PatternShuffler { private static Either<SyntaxError, ShuffledPattern> success(Symbol symbol, List<PatternMatch> matches) { return right(new ShuffledPattern(symbol, matches)); } public Either<SyntaxError, ShuffledPattern> shuffle(Scope scope, List<String> memberNames, UnshuffledDefinition pattern) { return new Shuffler(scope, memberNames, pattern).splitPattern(); } 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<String> memberNames; private final UnshuffledDefinition pattern; private Shuffler(Scope scope, List<String> memberNames, UnshuffledDefinition pattern) { this.scope = scope; this.memberNames = ImmutableList.copyOf(memberNames); this.pattern = pattern; } public Either<SyntaxError, ShuffledPattern> splitPattern() { try { List<PatternMatch> matches = shufflePattern(pattern.getMatches()); return matches.remove(0).asCapture() .map(match -> success(scope.qualifyCurrent(match.getSymbol()).nest(memberNames), matches)) .orElseGet(match -> left(parseError("Illegal start of pattern", match.getSourceLocation()))); } catch (ShuffleException exception) { return left(exception.syntaxError); } } private boolean expectsArgument(Deque<PatternMatch> input) { return !input.isEmpty() && !isOperator(input.peek()); } private OperatorPair<CaptureMatch> getOperator(PatternMatch match, boolean expectsPrefix) { return match.asCaptureOperator(scope) .map(pair -> pair.into((capture, operator) -> { if (expectsPrefix && !operator.isPrefix()) { throw new ShuffleException(parseError("Unexpected binary operator " + capture.getSymbol(), capture.getSourceLocation())); } else { return new OperatorPair<>(operator, capture); } })) .orElseThrow(() -> new ShuffleException(parseError("Match " + match.prettyPrint() + " is not an operator", match.getSourceLocation()))); } private boolean isOperator(PatternMatch match) { return match.isOperator(scope); } private List<PatternMatch> shufflePattern(List<PatternMatch> matches) { Deque<PatternMatch> input = new ArrayDeque<>(matches); Deque<Either<OperatorPair<CaptureMatch>, PatternMatch>> output = new ArrayDeque<>(); Deque<OperatorPair<CaptureMatch>> stack = new ArrayDeque<>(); boolean expectsPrefix = isOperator(input.peek()); while (!input.isEmpty()) { if (isOperator(input.peek())) { OperatorPair<CaptureMatch> 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(input.poll())); while (expectsArgument(input)) { output.push(right(input.poll())); } } } while (!stack.isEmpty()) { output.push(left(stack.pop())); } return shufflePatternApply(output); } private List<PatternMatch> shufflePatternApply(Deque<Either<OperatorPair<CaptureMatch>, PatternMatch>> input) { Deque<PatternMatch> output = new ArrayDeque<>(); while (!input.isEmpty()) { input.pollLast() .map(right -> { output.push(right); return null; }) .orElseGet(left -> { if (left.isPrefix()) { PatternMatch head = output.pop(); output.push(left.getValue()); output.push(head); } else { PatternMatch l = output.pop(); PatternMatch r = output.pop(); output.push(left.getValue()); output.push(r); output.push(l); } return null; }); } List<PatternMatch> matches = new ArrayList<>(output); reverse(matches); return matches; } } }