Skip to content

koder95/Interpreter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wzorzec projektowy Interpreter

pl en

Java CI with Maven

Projekt jest implementacją wzorca projektowego Interpreter w wersji uniwersalnej.

Interfejs klienta pozwala na stworzenie nowej instancji interpretera, który będzie działał w podanym kontekście.

Języki formalne można podzielić na dwie grupy ze względu na kontekst: języki formalne bezkontekstowe i języki formalne kontekstowe. Kontekst określa ostateczne znaczenie wyrażeń wieloznacznych w perspektywie tylko jednego wyrażenia. Jeśli każde z wyrażeń jest jednoznaczne i nie potrzebuje uściśleń przez kontekst, wówczas należy używać instancji utworzonej przez new pl.koder95.interpreter.Context(){}, co oznacza gramatykę bezkontekstową.

Interfejs Interpreter dostarcza nie tylko metodę interpretującą odczytywane wartości, ale również implementacje interfejsu parsera, który wykorzystywany jest do budowania drzewa abstrakcyjnej syntaktyki za pomocą metody buildAbstractSyntaxTree(java.util.Queue).

Zależność Maven

<dependency>
    <groupId>pl.koder95</groupId>
    <artifactId>interpreter</artifactId>
    <version>1.0.0</version>
</dependency>

Przykład

package pl.koder95.interpreter;

import java.io.StringReader;
import java.util.*;

/**
 * Klasa zawierająca przykładową implementację interpretera.
 * Zaimplementowany język jest kontekstowy i określa sortowanie ciągów znaków za pomocą instrukcji
 * {@code ORDER BY ASC} lub {@code ORDER BY DSC}, albo {@code ORDER BY REV}.
 * Dla pewnego porządku: język nazywa się {@code SortExaL} od
 * ang. <i><b>Sort</b>ing <b>Exa</b>mple <b>L</b>anguage</i> (przykładowy język sortowania).
 */
public final class Example {

    private Example() {}

    /**
     * Metoda interpretująca źródło znaków i wprowadzone linie.
     * @param readable źródło znaków do zinterpretowania
     * @param lines tablica linii umieszczonych w kontekście przed procesem interpretacji
     * @return lista linii posortowanych zgodnie z instrukcjami
     */
    public static List<String> interpret(Readable readable, String... lines) {
        // Poniższy przykład pokazuje sposób korzystania z interpretera.
        // Pierwszym krokiem jest utworzenie obiektu klienta:
        ExampleClient client = new ExampleClient();
        // Należy stworzyć najpierw instancję kontekstu, który będzie wykorzystywany przez interpreter:
        ExampleContext context = new ExampleContext();
        if (lines.length > 0) {// – sprawdzanie, czy są jakieś linie we wprowadzonej tablicy
            context.lines.addAll(List.of(lines)); // – linie dodawane są do kontekstu
        }
        // Aby utworzyć nową instancję interpretera, należy to zrobić za pośrednictwem klienta:
        ExampleInterpreter interpreter = client.newInterpreter(context);
        // Interpreter dokonuje interpretacji źródła znaków i zwraca wynik w postaci listy linii:
        return interpreter.interpret(readable);
    }

    public static void main(String[] args) {
        // Przykładowe instrukcje:
        String expressions = """
                B
                D
                C
                ORDER BY DSC
                A
                ORDER BY REV
                E
                ORDER BY REV
                ORDER BY REV
                F
                """;
        StringReader readable = new StringReader(expressions);
        // Interpreter dokonuje interpretacji i zwraca wynik w postaci listy linii:
        List<String> lines = args.length > 0? interpret(readable, String.join(" ", args)) : interpret(readable);
        // Drukowanie wyników interpretacji:
        System.out.println(lines); // Spodziewany print (jeśli nie było argumentów): [A, B, C, D, E, F]
    }

/*
    IMPLEMENTACJE
    =============
    * ExampleContext – implementuje przykładowy kontekst dla języka kontekstowego
    * Line – implementuje wyrażenie nieterminalne reprezentujące linię tekstu
    * OrderByType – definiuje typy sposobów porządkowania linii tekstu
    * Sort – implementuje wyrażenie terminalne, które zwraca posortowane linie według podanego sposobu
    * Add – implementuje wyrażenie terminalne, które dodaje do kontekstu linię tekstu
    * Instructions – implementuje wyrażenie terminalne, które zawiera listę innych wyrażeń terminalnych
    * LineTokenizer – dokonuje tokenizacji dzieląc źródło znaków na linie
    * ExampleParser – implementuje parser, który tworzy AST (zwraca Instructions)
    * ExampleInterpreter – implementuje interpreter przez określenie statycznego parsera i zmiennego kontekstu
    * ExampleClient – implementuje klienta tworzącego interpreter

    LineTokenizer pobiera znaki i przekształca je w linie (Line).
    Parser (ExampleParser) analizuje linie i określa ich znaczenie.
    Linie są przekształcane przez parser na Add albo Sort, które dodawane są do listy instrukcji (Instructions).
    Interpreter (ExampleInterpreter) dokonuje interpretacji instrukcji i zwraca listę posortowanych linii.
    Klient zajmuje się utworzeniem interpretera, w zależności od kontekstu (w tym miejscu jest on ignorowany).
*/
    private static class ExampleContext implements Context {
        private final List<String> lines = new LinkedList<>();
    }

    private record Line(String content) implements NonTerminalExpression<String> {
        @Override
        public String getObject() {
            return content;
        }
    }

    private enum OrderByType {
        ASC, DSC, REV
    }

    private record Sort(OrderByType orderBy) implements TerminalExpression<ExampleContext, List<String>> {

        @Override
        public List<String> interpret(ExampleContext context) {
            if (orderBy == OrderByType.ASC) context.lines.sort(Comparator.naturalOrder());
            else if (orderBy == OrderByType.DSC) context.lines.sort(Comparator.reverseOrder());
            else if (orderBy == OrderByType.REV) Collections.reverse(context.lines);
            List<String> ordered = new ArrayList<>(context.lines);
            context.lines.clear();
            return ordered;
        }

        @Override
        public String asString() {
            return "ORDER BY " + orderBy.name();
        }
    }

    private record Add(Line line) implements TerminalExpression<ExampleContext, List<String>> {
        @Override
        public List<String> interpret(ExampleContext context) {
            if (line != null) context.lines.add(line.content);
            return context.lines;
        }

        @Override
        public String asString() {
            return line == null? "null" : line.content();
        }
    }

    private record Instructions(List<TerminalExpression<ExampleContext, List<String>>> terminalExpressionList)
            implements TerminalExpression<ExampleContext, List<String>> {
        @Override
        public List<String> interpret(ExampleContext context) {
            for (TerminalExpression<ExampleContext, List<String>> expr : terminalExpressionList) {
                List<String> interpreted = expr.interpret(context);
                if (expr instanceof Sort) {
                    context.lines.addAll(interpreted);
                }
            }
            return context.lines;
        }

        @Override
        public String asString() {
            return "INSTRUCTIONS";
        }
    }

    private static final class LinesTokenizer extends Tokenizer {
        private final Queue<Line> lineQueue = new LinkedList<>();
        private Scanner scanner = null;

        @Override
        public Line next() {
            while (scanner != null && scanner.hasNextLine()) {
                String content = scanner.nextLine();
                lineQueue.add(new Line(content));
            }
            return lineQueue.poll();
        }

        @Override
        public boolean hasNext() {
            return (scanner != null && scanner.hasNext()) || !lineQueue.isEmpty();
        }

        @Override
        public void setSource(Readable source) {
            super.setSource(source);
            scanner = new Scanner(source);
        }
    }

    private static final class ExampleParser implements Parser<ExampleContext, List<String>> {
        public static final Tokenizer TOKENIZER = new LinesTokenizer();

        @Override
        public Tokenizer getTokenizer() {
            return TOKENIZER;
        }

        @Override
        public TerminalExpression<ExampleContext, List<String>> buildAbstractSyntaxTree(Queue<NonTerminalExpression<?>> tokens) {
            List<TerminalExpression<ExampleContext, List<String>>> terminalExpressionList = new LinkedList<>();
            while (!tokens.isEmpty()) {
                NonTerminalExpression<?> token = tokens.poll();
                if (token instanceof Line line) {
                    String content = line.content();
                    String tag = "ORDER BY";
                    if (content.startsWith(tag)) {
                        Sort sorting = recognizeSort(content.substring(tag.length()).strip());
                        terminalExpressionList.add(sorting);
                    } else {
                        terminalExpressionList.add(new Add(line));
                    }
                } else {
                    terminalExpressionList.add(new Add(new Line(token.asString())));
                }
            }
            return new Instructions(terminalExpressionList);
        }

        private static Sort recognizeSort(String type) {
            Sort sorting;
            if (type.equals(OrderByType.ASC.name())) {
                sorting = new Sort(OrderByType.ASC);
            } else if (type.equals(OrderByType.DSC.name())) {
                sorting = new Sort(OrderByType.DSC);
            } else if (type.equals(OrderByType.REV.name())) {
                sorting = new Sort(OrderByType.REV);
            } else if (type.isBlank()) {
                sorting = new Sort(null);
            } else {
                throw new SyntaxException("Unknown ordering type");
            }
            return sorting;
        }
    }

    private record ExampleInterpreter(ExampleContext context) implements Interpreter<ExampleContext, List<String>> {
        public static final ExampleParser PARSER = new ExampleParser();

        @Override
        public ExampleContext getContext() {
            return context;
        }

        @Override
        public ExampleParser getParser() {
            return PARSER;
        }
    }

    private static final class ExampleClient implements Client<ExampleContext, List<String>, ExampleInterpreter> {
        @Override
        public ExampleInterpreter newInterpreter(ExampleContext context) {
            return new ExampleInterpreter(context);
        }
    }
}

About

Uniwersalna implementacja wzorca interpreter

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages