diff --git a/dev.thihup.jvisualg.frontend/src/main/antlr4/dev/thihup/jvisualg/frontend/impl/antlr/VisuAlgLexer.g4 b/dev.thihup.jvisualg.frontend/src/main/antlr4/dev/thihup/jvisualg/frontend/impl/antlr/VisuAlgLexer.g4 index 915deae..bdac83b 100644 --- a/dev.thihup.jvisualg.frontend/src/main/antlr4/dev/thihup/jvisualg/frontend/impl/antlr/VisuAlgLexer.g4 +++ b/dev.thihup.jvisualg.frontend/src/main/antlr4/dev/thihup/jvisualg/frontend/impl/antlr/VisuAlgLexer.g4 @@ -106,7 +106,7 @@ DOT : '.'; ID : [a-z_][a-z0-9_]*; // Comments -COMMENT : '//' ~[\r\n]* -> skip; +COMMENT : '//' ~[\r\n]* -> channel(HIDDEN); // Whitespace -WS : [ \t\r\n]+ -> skip; +WS : [ \t\r\n]+ -> channel(HIDDEN); diff --git a/dev.thihup.jvisualg.frontend/src/main/java/module-info.java b/dev.thihup.jvisualg.frontend/src/main/java/module-info.java index 04daac8..0ee86d8 100644 --- a/dev.thihup.jvisualg.frontend/src/main/java/module-info.java +++ b/dev.thihup.jvisualg.frontend/src/main/java/module-info.java @@ -1,6 +1,3 @@ -import org.jspecify.annotations.NullMarked; - -@NullMarked module dev.thihup.jvisualg.frontend { requires org.antlr.antlr4.runtime; requires org.jspecify; @@ -11,6 +8,7 @@ dev.thihup.jvisualg.interpreter, dev.thihup.jvisualg.ide; exports dev.thihup.jvisualg.frontend to dev.thihup.jvisualg.lsp, - dev.thihup.jvisualg.interpreter; + dev.thihup.jvisualg.interpreter, dev.thihup.jvisualg.ide; + exports dev.thihup.jvisualg.frontend.impl.antlr; } diff --git a/dev.thihup.jvisualg.ide/pom.xml b/dev.thihup.jvisualg.ide/pom.xml index fce64ec..3c83d5a 100644 --- a/dev.thihup.jvisualg.ide/pom.xml +++ b/dev.thihup.jvisualg.ide/pom.xml @@ -14,10 +14,9 @@ JVisualG - dev.thihup.jvisualg.ide.Main + dev.thihup.jvisualg.ide.SwingIDE 0.9.12 JVisualG - 22 false @@ -33,52 +32,27 @@ ${project.version} - org.openjfx - javafx-controls - ${javafx.version} + com.fifesoft + rsyntaxtextarea + 3.4.0 - org.openjfx - javafx-fxml - ${javafx.version} + com.formdev + flatlaf + 3.4.1 - org.fxmisc.richtext - richtextfx - 0.11.2 + org.antlr + antlr4-runtime + 4.13.1 + + + de.tisoft.rsyntaxtextarea + rsyntaxtextarea-antlr4-extension + 0.0.3 - - - - org.openjfx - javafx-maven-plugin - 0.0.8 - - ${mainClass} - - - - - - - - - - - - - - - - - - - - - - diff --git a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/Main.java b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/Main.java deleted file mode 100644 index b2cf541..0000000 --- a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/Main.java +++ /dev/null @@ -1,716 +0,0 @@ -package dev.thihup.jvisualg.ide; - -import dev.thihup.jvisualg.interpreter.*; -import dev.thihup.jvisualg.lsp.VisualgLauncher; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.concurrent.Task; -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.geometry.Point2D; -import javafx.geometry.Pos; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseEvent; -import javafx.scene.paint.Paint; -import javafx.scene.text.Font; -import javafx.scene.text.FontPosture; -import javafx.stage.Popup; -import javafx.stage.Stage; -import org.eclipse.lsp4j.*; -import org.eclipse.lsp4j.jsonrpc.Launcher; -import org.eclipse.lsp4j.launch.LSPLauncher; -import org.eclipse.lsp4j.services.LanguageClient; -import org.eclipse.lsp4j.services.LanguageServer; -import org.fxmisc.richtext.CodeArea; -import org.fxmisc.richtext.LineNumberFactory; -import org.fxmisc.richtext.event.MouseOverTextEvent; -import org.fxmisc.richtext.model.StyleSpans; -import org.reactfx.Subscription; - -import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.Pipe; -import java.text.BreakIterator; -import java.time.Duration; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.LongAdder; -import java.util.function.Consumer; -import java.util.function.IntFunction; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Main extends Application { - - public static final String DEFAULT_CONSOLE_STYLE = """ - -fx-control-inner-background: black; - -fx-text-fill: white; - -fx-font-family: "Consolas"; - -fx-font-size: 12; - """; - private final ExecutorService fxClientExecutor = Executors.newSingleThreadExecutor(); - private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - - @FXML - private CodeArea codeArea; - - @FXML - private Button runButton; - - @FXML - private TextArea outputArea; - - @FXML - private TableView debugArea; - - - private Consumer callback; - - private TextArea dosContent; - private Stage dosWindow; - - private Subscription subscribe; - private Launcher clientLauncher; - private Future lspServer; - private Future lspClient; - private List diagnostics; - private Interpreter interpreter; - private List breakpointLines = new ArrayList<>(); - private int lastPromptPosition = 0; - - private void handleExecutionSuccessfully() { - appendOutput("\nFim da execução.", ToWhere.OUTPUT, When.LATER); - appendOutput("\n>>> Fim da execução do programa !", ToWhere.DOS, When.LATER); - } - - public record DebugState(String getEscopo, String getNome, String getTipo, String getValor) { - } - - private int previousDebugLine = -1; - - @Override - public void start(Stage stage) throws Exception { - FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("gui.fxml")); - fxmlLoader.setController(this); - Parent root = fxmlLoader.load(); - IntFunction nodeIntFunction = LineNumberFactory.get(codeArea); - codeArea.setParagraphGraphicFactory(lineNumber -> { - Label node = (Label) nodeIntFunction.apply(lineNumber); - node.setFont(Font.font("monospace", FontPosture.REGULAR, 13)); - if (breakpointLines.contains(lineNumber)) { - Label label = new Label(" ".repeat(node.getText().length() - 1) + "."); - label.setTextFill(Paint.valueOf("#ff0000")); - label.setAlignment(Pos.BASELINE_RIGHT); - label.setPadding(node.getPadding()); - label.setFont(node.getFont()); - node.setGraphic(label); - node.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); - } - node.setOnMouseClicked(_ -> handleBreakpointChange(lineNumber)); - node.setCursor(Cursor.HAND); - return node; - }); - breakpointLines.add(5); - setupSyntaxHighlighting(); - - setupDefaultText(); - - showScene(stage, root); - - setupLSP(); - - dosWindow = new Stage(); - dosContent = new TextArea(); - - dosContent.setStyle(DEFAULT_CONSOLE_STYLE); - dosWindow.setTitle("Emulação do DOS"); - dosWindow.setScene(new Scene(dosContent)); - dosContent.setEditable(false); - dosContent.addEventFilter(KeyEvent.KEY_PRESSED, this::handleKeyPress); - dosContent.addEventFilter(MouseEvent.MOUSE_CLICKED, this::handleMouseClick); - - interpreter = new Interpreter(new IO(this::readVariable, this::handleOutputEvent), programState -> { - Platform.runLater(() -> { - updateDebugArea(programState); - highlightCurrentLine(programState.lineNumber()); - }); - }); - - runButton.addEventHandler(ActionEvent.ACTION, _ -> { - handleStartOrContinue(); - }); - - - setupErrorPopup(); - stage.onCloseRequestProperty().addListener(_ -> dosWindow.close()); - if (Boolean.getBoolean("autoClose")) - Platform.runLater(stage::close); - } - - - private void startExecution(InterpreterState interpreterState) { - interpreter.runWithState(codeArea.getText(), interpreterState); - switch (interpreter.state()) { - case InterpreterState.CompletedSuccessfully _ -> this.handleExecutionSuccessfully(); - case InterpreterState.CompletedExceptionally(Throwable e) -> this.handleExecutionError(e); - default -> throw new IllegalStateException("Unexpected value: " + interpreter.state()); - } - Platform.runLater(this::removeDebugStyleFromPreviousLine); - } - - private Void handleExecutionError(Throwable e) { - if (e.getCause() instanceof CancellationException) { - appendOutput("\n*** Execução terminada pelo usuário.", ToWhere.DOS, When.LATER); - appendOutput("\n*** Feche esta janela para retornar ao Visual.", ToWhere.DOS, When.LATER); - - appendOutput("\nExecução terminada pelo usuário.", ToWhere.OUTPUT, When.LATER); - return null; - } else if (e.getCause() instanceof TypeException) { - appendOutput(e.getCause().getMessage(), ToWhere.OUTPUT, When.LATER); - } else if (e.getCause() != null) { - e.printStackTrace(); - appendOutput(e.getCause().toString(), ToWhere.OUTPUT, When.LATER); - } - - appendOutput("\nExecução terminada por erro.", ToWhere.BOTH, When.LATER); - appendOutput("\n>>> Fim da execução do programa !", ToWhere.DOS, When.LATER); - return null; - } - - private void resetExecution() { - outputArea.clear(); - debugArea.getItems().clear(); - appendOutput("Início da execução\n", ToWhere.OUTPUT, When.NOW); - interpreter.reset(); - previousDebugLine = -1; - breakpointLines.forEach(location -> interpreter.addBreakpoint(location + 1)); - - dosContent.setStyle(DEFAULT_CONSOLE_STYLE); - dosContent.clear(); - dosWindow.show(); - } - - private void updateDebugArea(ProgramState programState) { - debugArea.getItems().clear(); - programState.stack().entrySet().stream() - .mapMulti((Map.Entry> entry, Consumer consumer) -> { - entry.getValue().forEach((variableName, variableValue) -> { - String scopeName = entry.getKey().toUpperCase(); - String variableNameUpperCase = variableName.toUpperCase(); - addDebug(consumer, variableValue, scopeName, variableNameUpperCase); - }); - }) - .forEach(debugArea.getItems()::add); - } - - private static void addDebug(Consumer consumer, Object variableValue, String scopeName, String variableNameUpperCase) { - switch (variableValue) { - case Object[][] multiObjects -> - addMultiArrayDebug(scopeName, variableNameUpperCase, consumer, multiObjects); - case Object[] objects -> addArrayDebug(scopeName, variableNameUpperCase, consumer, objects); - case Object _ -> addObjectDebug(scopeName, variableNameUpperCase, consumer, variableValue); - } - } - - private void highlightCurrentLine(int lineNumber) { - if (lineNumber < 0) { - return; - } - if (previousDebugLine != -1) { - removeDebugStyleFromPreviousLine(); - } - codeArea.showParagraphAtCenter(lineNumber); - codeArea.getCaretSelectionBind().moveTo(lineNumber, 0); - - codeArea.setParagraphStyle(lineNumber, List.of("debug")); - previousDebugLine = lineNumber; - } - - private void removeDebugStyleFromPreviousLine() { - codeArea.setParagraphStyle(previousDebugLine, List.of()); - } - - private static void addObjectDebug(String scope, String variableName, Consumer consumer, Object variableValue) { - String visualgType = getVisualgType(variableValue); - switch (variableValue) { - case Boolean b -> - consumer.accept(new DebugState(scope, variableName, visualgType, b ? "VERDADEIRO" : "FALSO")); - case String s -> consumer.accept(new DebugState(scope, variableName, visualgType, "\"" + s + "\"")); - case Integer _, Double _ -> - consumer.accept(new DebugState(scope, variableName, visualgType, variableValue.toString())); - case UserDefinedValue userDefinedValue -> { - userDefinedValue.values().forEach((fieldName, fieldValue) -> addDebug(consumer, fieldValue, scope, variableName + "." + fieldName)); - } - default -> { - } - } - } - - private static String getVisualgType(Object object) { - return switch (object) { - case Boolean _ -> "LOGICO"; - case String _ -> "CARACTER"; - case Double _ -> "REAL"; - case Integer _ -> "INTEIRO"; - default -> "Desconhecido"; - }; - } - - private static void addArrayDebug(String scope, String variableName, Consumer consumer, Object[] objects) { - for (int i = 0; i < objects.length; i++) { - Object object = objects[i]; - addObjectDebug(scope, variableName + "[" + i + "]", consumer, object); - } - } - - private static void addMultiArrayDebug(String scope, String variableName, Consumer consumer, Object[][] multiObjects) { - for (int i = 0; i < multiObjects.length; i++) { - Object[] objects = multiObjects[i]; - for (int j = 0; j < objects.length; j++) { - Object object = objects[j]; - addObjectDebug(scope, variableName + "[" + i + ", " + j + "]", consumer, object); - } - } - } - - enum ToWhere { - DOS, OUTPUT, BOTH - } - - enum When { - NOW, LATER - } - - - private void appendOutput(String output, ToWhere where, When when) { - Runnable runnable = () -> { - switch (where) { - case DOS -> dosContent.appendText(output); - case OUTPUT -> outputArea.appendText(output); - case BOTH -> { - dosContent.appendText(output); - outputArea.appendText(output); - } - } - }; - - switch (when) { - case NOW -> runnable.run(); - case LATER -> Platform.runLater(runnable); - } - } - - private void handleOutputEvent(OutputEvent outputEvent) { - Platform.runLater(() -> { - switch (outputEvent) { - case OutputEvent.Text text -> appendOutput(text.text(), ToWhere.BOTH, When.NOW); - case OutputEvent.Clear _ -> dosContent.clear(); - case OutputEvent.ChangeColor( - OutputEvent.ChangeColor.Color color, OutputEvent.ChangeColor.Position position - ) -> { - switch (position) { - case FOREGROUND -> - dosContent.setStyle(dosContent.getStyle() + ";-fx-text-fill:" + color.toString().toLowerCase() + ";"); - case BACKGROUND -> - dosContent.setStyle(dosContent.getStyle() + ";-fx-control-inner-background:" + color.toString().toLowerCase() + ";"); - } - } - case null, default -> { - } - } - }); - } - - private void handleKeyPress(KeyEvent event) { - TextArea textArea = (TextArea) event.getSource(); - switch (event.getCode()) { - case ENTER -> { - int currentCaretPosition = textArea.getCaretPosition(); - String fullText = textArea.getText(); - if (lastPromptPosition < currentCaretPosition) { - String newCommand = fullText.substring(lastPromptPosition, currentCaretPosition).trim(); - if (callback != null) callback.accept(newCommand); - } - textArea.positionCaret(lastPromptPosition); - event.consume(); - } - case KeyCode.BACK_SPACE, KeyCode.DELETE, KeyCode.LEFT, KeyCode.HOME -> { - if (textArea.getCaretPosition() <= lastPromptPosition) { - event.consume(); - } - } - case RIGHT, KeyCode.END -> { - if (textArea.getCaretPosition() > lastPromptPosition) { - textArea.positionCaret(lastPromptPosition); - event.consume(); - } - } - case KeyCode.UP, KeyCode.DOWN -> event.consume(); - - case KeyCode.A -> { - if (event.isControlDown()) { - event.consume(); - } - } - case KeyCode.ESCAPE -> { - event.consume(); - dosContent.clear(); - dosWindow.hide(); - } - case KeyCode.F2 -> { - if (event.isControlDown() && interpreter != null) { - interpreter.stop(); - } - } - default -> { - } - } - } - - private void handleMouseClick(MouseEvent event) { - TextArea textArea = (TextArea) event.getSource(); - if (textArea.getCaretPosition() < lastPromptPosition) { - textArea.positionCaret(textArea.getText().length()); - event.consume(); - } - } - - private CompletableFuture> readVariable(InputRequestValue request) { - CompletableFuture> inputValueCompletableFuture = new CompletableFuture<>(); - - Platform.runLater(() -> { - if (dosWindow.isShowing()) { - dosContent.setEditable(true); - lastPromptPosition = dosContent.getText().length(); - callback = strip -> { - strip = strip.strip(); - getValue(request, strip) - .ifPresentOrElse(value -> inputValueCompletableFuture.complete(Optional.of(value)), - () -> inputValueCompletableFuture.complete(Optional.empty())); - }; - return; - } - TextInputDialog textInputDialog = new TextInputDialog(); - textInputDialog.setContentText("Digite um valor " + request.type() + " para a variável " + request.variableName()); - textInputDialog.showAndWait() - .flatMap(textValue -> getValue(request, textValue)) - .ifPresentOrElse(value -> inputValueCompletableFuture.complete(Optional.of(value)), - () -> inputValueCompletableFuture.complete(Optional.empty())); - }); - - return inputValueCompletableFuture; - } - - private static Optional getValue(InputRequestValue request, String textValue) { - try { - return Optional.of(switch (request.type()) { - case CARACTER -> new InputValue.CaracterValue(textValue); - case LOGICO -> new InputValue.LogicoValue(textValue.equalsIgnoreCase("VERDADEIRO")); - case REAL -> new InputValue.RealValue(Double.parseDouble(textValue)); - case INTEIRO -> new InputValue.InteiroValue(Integer.parseInt(textValue)); - }); - } catch (Exception _) { - return Optional.empty(); - } - } - - private void showScene(Stage stage, Parent root) { - Scene scene = new Scene(root); - scene.setOnKeyPressed(keyEvent -> { - switch (keyEvent.getCode()) { - case F2 -> { - if (keyEvent.isControlDown() && interpreter != null) { - interpreter.stop(); - } - } - case F5 -> { - int currentParagraph = codeArea.getCurrentParagraph(); - handleBreakpointChange(currentParagraph); - } - case F9 -> handleStartOrContinue(); - case F8 -> { - handleStep(); - } - case ESCAPE -> { - switch (interpreter.state()) { - case InterpreterState.ForcedStop _, InterpreterState.CompletedExceptionally _, - InterpreterState.CompletedSuccessfully _ -> dosWindow.hide(); - case InterpreterState.PausedDebug _, InterpreterState.NotStarted _, - InterpreterState.Running _ -> { - } - } - } - default -> { - } - } - }); - scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm()); - stage.setTitle("JVisualG"); - stage.setScene(scene); - stage.show(); - } - - private void handleBreakpointChange(int currentParagraph) { - if (breakpointLines.contains(currentParagraph)) { - breakpointLines.remove((Integer) currentParagraph); - interpreter.removeBreakpoint(currentParagraph + 1); - } else { - breakpointLines.add(currentParagraph); - interpreter.addBreakpoint(currentParagraph + 1); - } - codeArea.recreateParagraphGraphic(currentParagraph); - } - - private void handleStartOrContinue() { - switch (interpreter.state()) { - case InterpreterState.PausedDebug _ -> { - interpreter.continueExecution(); - } - case InterpreterState.NotStarted _, InterpreterState.ForcedStop _, - InterpreterState.CompletedExceptionally _, InterpreterState.CompletedSuccessfully _ -> { - resetExecution(); - Thread.startVirtualThread(() -> startExecution(InterpreterState.Running.INSTANCE)); - } - case InterpreterState.Running _ -> { - } - } - } - - private void handleStep() { - switch (interpreter.state()) { - case InterpreterState.PausedDebug _ -> { - interpreter.step(); - } - case InterpreterState.NotStarted _, InterpreterState.ForcedStop _, - InterpreterState.CompletedExceptionally _, InterpreterState.CompletedSuccessfully _ -> { - resetExecution(); - Thread.startVirtualThread(() -> startExecution(new InterpreterState.PausedDebug(1))); - } - case InterpreterState.Running _ -> { - } - } - } - - private void setupDefaultText() { - codeArea.replaceText(0, 0, """ - algoritmo "semnome" - // Função : - // Autor : - // Data : %s - // Seção de Declarações - var - - inicio - - fimalgoritmo - """.formatted(LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")))); - } - - private void setupSyntaxHighlighting() { - subscribe = codeArea.multiPlainChanges() - .successionEnds(Duration.ofMillis(50)) - .retainLatestUntilLater(fxClientExecutor) - .supplyTask(this::computeHighlightingAsync) - .awaitLatest(codeArea.multiPlainChanges()) - .filterMap(t -> { - if (t.isSuccess()) { - return Optional.of(t.get()); - } else { - t.getFailure().printStackTrace(); - return Optional.empty(); - } - }) - .subscribe(this::applyHighlighting); - } - - private void setupLSP() throws IOException { - Pipe clientToServerPipe = Pipe.open(); - Pipe serverToClientPipe = Pipe.open(); - - lspServer = VisualgLauncher.startServer(Channels.newInputStream(clientToServerPipe.source()), Channels.newOutputStream(serverToClientPipe.sink()), executor); - clientLauncher = LSPLauncher.createClientLauncher(new VisualgLanguageClient(), Channels.newInputStream(serverToClientPipe.source()), Channels.newOutputStream(clientToServerPipe.sink()), executor, null); - - lspClient = clientLauncher.startListening(); - } - - private void setupErrorPopup() { - Popup popup = new Popup(); - Label popupMsg = new Label(); - popupMsg.setStyle( - "-fx-background-color: black;" + - "-fx-text-fill: white;" + - "-fx-padding: 5;"); - popup.getContent().add(popupMsg); - - codeArea.setMouseOverTextDelay(Duration.ofMillis(200)); - codeArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> { - handlePopupError(e, popupMsg, popup); - }); - codeArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> { - popup.hide(); - }); - } - - private void handlePopupError(MouseOverTextEvent e, Label popupMsg, Popup popup) { - int chIdx = e.getCharacterIndex(); - Point2D pos = e.getScreenPosition(); - - if (codeArea.getText().isEmpty()) { - return; - } - if (interpreter.state() instanceof InterpreterState.PausedDebug) { - BreakIterator wordIterator = BreakIterator.getWordInstance(Locale.getDefault()); - Optional first = tryGetValue(wordIterator, chIdx).or(() -> tryGetValue(BreakIterator.getLineInstance(), chIdx)); - if (first.isEmpty()) { - return; - } - DebugState debugState = first.get(); - popupMsg.setText(debugState.getNome + " = " + debugState.getValor()); - popup.show(codeArea, pos.getX(), pos.getY() + 10); - } - if (diagnostics == null) { - diagnostics.stream() - .filter(diagnostic -> { - int start = toOffset(codeArea.getText(), diagnostic.getRange().getStart()); - int end = toOffset(codeArea.getText(), diagnostic.getRange().getEnd()); - return chIdx >= start && chIdx <= end; - }) - .findFirst().ifPresent(diagnostic -> { - popupMsg.setText(diagnostic.getMessage()); - popup.show(codeArea, pos.getX(), pos.getY() + 10); - }); - } - } - - private Optional tryGetValue(BreakIterator wordIterator, int chIdx) { - wordIterator.setText(codeArea.getText()); - - int start = wordIterator.preceding(chIdx); - int end = wordIterator.following(chIdx); - - if (start == BreakIterator.DONE || end == BreakIterator.DONE) { - return Optional.empty(); - } - - String originalText = codeArea.getText(start, wordIterator.next()); - String text = originalText.strip().replaceAll("[().,+-/%^*]", ""); - Optional first = debugArea.getItems().stream().filter(x -> x.getNome().equalsIgnoreCase(text)).findFirst(); - if (first.isEmpty()) { - Matcher matcher = ARRAY_REGEX.matcher(text); - if (matcher.find()) { - String indice = matcher.group(1); - return debugArea.getItems().stream().filter(x -> x.getNome().equalsIgnoreCase(indice)) - .findFirst() - .map(DebugState::getValor).or(() -> Optional.of(indice)) - .flatMap(index -> debugArea.getItems().stream().filter(x -> x.getNome().equalsIgnoreCase(text.replace(indice, index) )).findFirst()); - } - } - return first; - } - private static Pattern ARRAY_REGEX = Pattern.compile("\\w\\[(\\d|\\w)]"); - - class VisualgLanguageClient implements LanguageClient { - - - @Override - public void telemetryEvent(Object object) { - - } - - @Override - public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { - Main.this.diagnostics = diagnostics.getDiagnostics(); - diagnostics.getDiagnostics().forEach(diagnostic -> { - System.out.println(diagnostic.getMessage()); - Platform.runLater(() -> { - if (codeArea.getText().isEmpty()) - return; - int offsetStart = toOffset(codeArea.getText(), diagnostic.getRange().getStart()); - int offsetEnd = toOffset(codeArea.getText(), diagnostic.getRange().getEnd()) + 1; - codeArea.setStyleClass(offsetStart, offsetEnd, "error"); - }); - - }); - } - - @Override - public void showMessage(MessageParams messageParams) { - - } - - @Override - public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { - return null; - } - - @Override - public void logMessage(MessageParams message) { - System.out.println(message.getMessage()); - } - } - - @Override - public void stop() throws Exception { - if (subscribe != null) { - subscribe.unsubscribe(); - } - if (lspClient != null) { - lspClient.cancel(true); - } - if (lspServer != null) { - lspServer.cancel(true); - } - fxClientExecutor.shutdown(); - executor.shutdown(); - } - - private int toOffset(String text, Position position) { - String[] lines = text.split("\n"); - int offset = 0; - for (int i = 0; i < lines.length; i++) { - if (i == position.getLine()) { - return offset + position.getCharacter(); - } - offset += lines[i].length() + 1; - } - return 0; - } - - - public static void main(String[] args) { - launch(args); - } - - - private final LongAdder counter = new LongAdder(); - - private Task>> computeHighlightingAsync() { - String text = codeArea.getText(); - Task>> task = new Task<>() { - @Override - protected StyleSpans> call() throws Exception { - VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier("untitled://file", counter.intValue()); - DidChangeTextDocumentParams didChangeTextDocumentParams = new DidChangeTextDocumentParams(versionedTextDocumentIdentifier, Collections.singletonList(new TextDocumentContentChangeEvent(text))); - clientLauncher.getRemoteProxy().getTextDocumentService().didChange(didChangeTextDocumentParams); - return SyntaxHighlight.computeHighlighting(text); - } - }; - fxClientExecutor.execute(task); - return task; - } - - private void applyHighlighting(StyleSpans> highlighting) { - codeArea.setStyleSpans(0, highlighting); - } - - -} diff --git a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/SwingIDE.java b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/SwingIDE.java new file mode 100644 index 0000000..01b92a2 --- /dev/null +++ b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/SwingIDE.java @@ -0,0 +1,619 @@ +package dev.thihup.jvisualg.ide; + +import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.icons.FlatAbstractIcon; +import dev.thihup.jvisualg.frontend.ASTResult; +import dev.thihup.jvisualg.frontend.Error; +import dev.thihup.jvisualg.frontend.TypeCheckerResult; +import dev.thihup.jvisualg.frontend.VisualgParser; +import dev.thihup.jvisualg.interpreter.*; +import org.fife.ui.rsyntaxtextarea.*; +import org.fife.ui.rsyntaxtextarea.parser.AbstractParser; +import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult; +import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice; +import org.fife.ui.rsyntaxtextarea.parser.ParseResult; +import org.fife.ui.rtextarea.GutterIconInfo; +import org.fife.ui.rtextarea.RTextArea; +import org.fife.ui.rtextarea.RTextScrollPane; + +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.DefaultTableModel; +import javax.swing.text.BadLocationException; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.print.PrinterException; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public class SwingIDE extends JFrame { + + private final TextEditorPane textArea; + private final DefaultTableModel debugTable; + + private final RTextScrollPane scrollPane; + private final JTextArea outputArea; + private final JFrame dosWindow = new JFrame(); + private final JTextArea dosContent = new JTextArea(); + + private final Interpreter interpreter; + private final Map breakpointLines = new HashMap<>(); + + + private Consumer callback; + private int lastPromptPosition = 0; + + public SwingIDE() { + FlatLightLaf.setup(); + + dosContent.setBackground(Color.BLACK); + dosContent.setForeground(Color.WHITE); + dosContent.setFont(Font.getFont(Font.MONOSPACED)); + dosWindow.add(dosContent); + + interpreter = new Interpreter(new IO(this::readVariable, this::handleOutputEvent), programState -> { + SwingUtilities.invokeLater(() -> { + updateDebugArea(programState); + highlightCurrentLine(programState.lineNumber()); + }); + }); + + textArea = new TextEditorPane(); + resetTextArea(); + textArea.setRows(20); + textArea.setColumns(60); + AbstractTokenMakerFactory atmf = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance(); + atmf.putMapping("text/x-visualg", "dev.thihup.jvisualg.ide.VisualgTokenMaker"); + textArea.setSyntaxEditingStyle("text/x-visualg"); + textArea.setCodeFoldingEnabled(false); + textArea.setBracketMatchingEnabled(false); + textArea.addParser(new TypeChecker()); + + + InputMap inputMap = textArea.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + ActionMap actionMap = textArea.getActionMap(); + + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, KeyEvent.CTRL_DOWN_MASK), "stopInterpreter"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), "toggleBreakpoint"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0), "startOrContinue"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F7, 0), "stepDebugger"); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "handleEscape"); + + actionMap.put("stopInterpreter", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + if (interpreter != null) { + interpreter.stop(); + } + } + }); + + actionMap.put("toggleBreakpoint", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + int currentParagraph = textArea.getCaretLineNumber(); + handleBreakpointChange(currentParagraph); + } + }); + + actionMap.put("startOrContinue", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + handleStartOrContinue(); + } + }); + + actionMap.put("stepDebugger", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + handleStep(); + } + }); + + actionMap.put("handleEscape", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + switch (interpreter.state()) { + case InterpreterState.ForcedStop _, InterpreterState.CompletedExceptionally _, + InterpreterState.CompletedSuccessfully _ -> dosWindow.hide(); + default -> { + } + } + } + }); + + + scrollPane = new RTextScrollPane(textArea); + scrollPane.setIconRowHeaderEnabled(true); + scrollPane.setMinimumSize(new Dimension(100, 400)); + setSize(1024, 768); + + setJMenuBar(createMenuBar()); + + JToolBar toolBar = createToolbar(); + add(toolBar, BorderLayout.NORTH); + + JSplitPane mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + mainSplitPane.add(scrollPane); + mainSplitPane.setDividerLocation(0.8); + + JSplitPane bottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + + debugTable = new DefaultTableModel(new String[]{"Escopo", "Nome", "Tipo", "Valor"}, 0); + JTable debugArea = new JTable(debugTable); + JScrollPane debugScrollPane = new JScrollPane(debugArea); + bottomSplitPane.setLeftComponent(debugScrollPane); + + outputArea = new JTextArea(); + JScrollPane outputScrollPane = new JScrollPane(outputArea); + bottomSplitPane.setRightComponent(outputScrollPane); + + mainSplitPane.setBottomComponent(bottomSplitPane); + add(mainSplitPane, BorderLayout.CENTER); + + setVisible(true); + + } + + private void resetTextArea() { + textArea.setText(newFileText()); + textArea.discardAllEdits(); + textArea.setDirty(false); + try { + textArea.setCaretPosition(textArea.getLineStartOffset(8)); + } catch (BadLocationException e) { + throw new RuntimeException(e); + } + } + + private static String newFileText() { + return """ + algoritmo "semnome" + // Função : + // Autor : + // Data : %s + // Seção de Declarações + var + + inicio + + fimalgoritmo + """.formatted(LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))); + } + + private JToolBar createToolbar() { + JToolBar toolBar = new JToolBar(); + String[] toolBarIcons = {"🗋", "📂", "🖬", "🖶", "✂", "🗐", "📋", "▶"}; + for (String icon : toolBarIcons) { + toolBar.add(new JButton(icon)); + } + + JButton newFileButton = (JButton) toolBar.getComponent(0); + newFileButton.addActionListener(_ -> resetTextArea()); + + + JButton openFileButton = (JButton) toolBar.getComponent(1); + openFileButton.addActionListener(_ -> openFile()); + + JButton saveFileButton = (JButton) toolBar.getComponent(2); + saveFileButton.addActionListener(_ -> { + try { + textArea.save(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + JButton printButton = (JButton) toolBar.getComponent(3); + printButton.addActionListener(_ -> { + try { + textArea.print(); + } catch (PrinterException e) { + throw new RuntimeException(e); + } + }); + + JButton cutButton = (JButton) toolBar.getComponent(4); + cutButton.addActionListener(RTextArea.getAction(RTextArea.CUT_ACTION)); + + JButton copyButton = (JButton) toolBar.getComponent(5); + copyButton.addActionListener(RTextArea.getAction(RTextArea.COPY_ACTION)); + + + JButton pasteButton = (JButton) toolBar.getComponent(6); + pasteButton.addActionListener(RTextArea.getAction(RTextArea.PASTE_ACTION)); + + JButton runButton = (JButton) toolBar.getComponent(7); + runButton.addActionListener(_ -> handleStartOrContinue()); + return toolBar; + } + + private void highlightCurrentLine(int i) { + try { + if (i == 0) { + textArea.setCaretPosition(textArea.getLineEndOffsetOfCurrentLine()); + textArea.removeAllLineHighlights(); + textArea.setHighlightCurrentLine(true); + return; + } + textArea.removeAllLineHighlights(); + textArea.setHighlightCurrentLine(false); + textArea.addLineHighlight(i, Color.decode("#2a5091")); + textArea.setCaretPosition(textArea.getLineStartOffset(i)); + } catch (BadLocationException e) { + throw new RuntimeException(e); + } + } + + private void handleStep() { + switch (interpreter.state()) { + case InterpreterState.PausedDebug _ -> interpreter.step(); + case InterpreterState.NotStarted _, InterpreterState.ForcedStop _, + InterpreterState.CompletedExceptionally _, InterpreterState.CompletedSuccessfully _ -> { + resetExecution(); + Thread.startVirtualThread(() -> startExecution(new InterpreterState.PausedDebug(1))); + } + case InterpreterState.Running _ -> { + } + } + } + + private JMenuBar createMenuBar() { + + JMenuBar menuBar = new JMenuBar(); + + JMenu arquivo = new JMenu("Arquivo"); + JMenuItem abrir = new JMenuItem("Abrir"); + abrir.addActionListener(_ -> openFile()); + arquivo.add(abrir); + menuBar.add(arquivo); + + + JMenu editMenu = new JMenu("Edit"); + editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.UNDO_ACTION))); + editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.REDO_ACTION))); + editMenu.addSeparator(); + editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.CUT_ACTION))); + editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.COPY_ACTION))); + editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.PASTE_ACTION))); + editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.DELETE_ACTION))); + editMenu.addSeparator(); + editMenu.add(createMenuItem(RTextArea.getAction(RTextArea.SELECT_ALL_ACTION))); + menuBar.add(editMenu); + + JMenu themes = new JMenu("Themes"); + themes.add(createTheme("Dark", "dark")); + themes.add(createTheme("Default", "default")); + themes.add(createTheme("Druid", "druid")); + themes.add(createTheme("Eclipse", "eclipse")); + themes.add(createTheme("IDEA", "idea")); + themes.add(createTheme("Monokai", "monokai")); + themes.add(createTheme("Visual Studio Code", "vs")); + + menuBar.add(themes); + + return menuBar; + } + + private void openFile() { + JFileChooser chooser = new JFileChooser("."); + FileNameExtensionFilter filter = new FileNameExtensionFilter( + "VisualG Algoritmos", "alg"); + chooser.setFileFilter(filter); + int returnVal = chooser.showOpenDialog(SwingIDE.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + try { + textArea.load(FileLocation.create(chooser.getSelectedFile())); + textArea.addLineHighlight(4, Color.RED); + } catch (IOException | BadLocationException e) { + throw new RuntimeException(e); + } + } + } + + private JMenuItem createTheme(String title, String value) { + JMenuItem changeStyleViaThemesItem = new JMenuItem( + title); + changeStyleViaThemesItem.addActionListener(_ -> changeStyleViaThemeXml(value)); + return changeStyleViaThemesItem; + } + + private void changeStyleViaThemeXml(String name) { + try { + + Theme theme = Theme.load(RSyntaxTextArea.class.getResourceAsStream( + "/org/fife/ui/rsyntaxtextarea/themes/%s.xml".formatted(name))); + theme.apply(textArea); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private static JMenuItem createMenuItem(Action action) { + JMenuItem item = new JMenuItem(action); + item.setToolTipText(null); + return item; + } + + private CompletableFuture> readVariable(InputRequestValue request) { + CompletableFuture> inputValueCompletableFuture = new CompletableFuture<>(); + + SwingUtilities.invokeLater(() -> { + if (dosWindow.isShowing()) { + dosContent.setEditable(true); + lastPromptPosition = dosContent.getText().length(); + callback = strip -> { + strip = strip.strip(); + getValue(request, strip) + .ifPresentOrElse(value -> inputValueCompletableFuture.complete(Optional.of(value)), + () -> inputValueCompletableFuture.complete(Optional.empty())); + }; + return; + } + Optional.ofNullable(JOptionPane.showInputDialog("Digite um valor " + request.type() + " para a variável " + request.variableName())) + .flatMap(textValue -> getValue(request, textValue)) + .ifPresentOrElse(value -> inputValueCompletableFuture.complete(Optional.of(value)), + () -> inputValueCompletableFuture.complete(Optional.empty())); + + }); + + return inputValueCompletableFuture; + } + + private static Optional getValue(InputRequestValue request, String textValue) { + try { + return Optional.of(switch (request.type()) { + case CARACTER -> new InputValue.CaracterValue(textValue); + case LOGICO -> new InputValue.LogicoValue(textValue.equalsIgnoreCase("VERDADEIRO")); + case REAL -> new InputValue.RealValue(Double.parseDouble(textValue)); + case INTEIRO -> new InputValue.InteiroValue(Integer.parseInt(textValue)); + }); + } catch (Exception _) { + return Optional.empty(); + } + } + + private void handleBreakpointChange(int currentParagraph) { + if (breakpointLines.containsKey(currentParagraph)) { + scrollPane.getGutter().removeTrackingIcon(breakpointLines.remove(currentParagraph)); + interpreter.removeBreakpoint(currentParagraph + 1); + + } else { + interpreter.addBreakpoint(currentParagraph + 1); + + try { + breakpointLines.put(currentParagraph, scrollPane.getGutter().addLineTrackingIcon(currentParagraph, new FlatAbstractIcon(10, 10, Color.RED) { + @Override + protected void paintIcon(Component component, Graphics2D graphics2D) { + graphics2D.fillOval(0, 0, 10, 10); + } + })); + } catch (BadLocationException e) { + throw new RuntimeException(e); + } + } + } + + private void appendOutput(String output, ToWhere where, When when) { + Runnable runnable = () -> { + switch (where) { + case DOS -> dosContent.append(output); + case OUTPUT -> outputArea.append(output); + case BOTH -> { + dosContent.append(output); + outputArea.append(output); + } + } + }; + + switch (when) { + case NOW -> runnable.run(); + case LATER -> SwingUtilities.invokeLater(runnable); + } + } + + private void handleOutputEvent(OutputEvent outputEvent) { + SwingUtilities.invokeLater(() -> { + switch (outputEvent) { + case OutputEvent.Text text -> appendOutput(text.text(), ToWhere.BOTH, When.NOW); + case OutputEvent.Clear _ -> dosContent.setText(""); + case OutputEvent.ChangeColor( + OutputEvent.ChangeColor.Color color, OutputEvent.ChangeColor.Position position + ) -> { + switch (position) { + case FOREGROUND -> dosContent.setForeground(Color.getColor(color.toString().toLowerCase())); + case BACKGROUND -> + dosContent.setBackground(Color.getColor(color.toString().toLowerCase() + ";")); + } + } + case null, default -> { + } + } + }); + } + + private void handleExecutionSuccessfully() { + appendOutput("\nFim da execução.", ToWhere.OUTPUT, When.LATER); + appendOutput("\n>>> Fim da execução do programa !", ToWhere.DOS, When.LATER); + } + + public record DebugState(String getEscopo, String getNome, String getTipo, String getValor) { + } + + private static String getVisualgType(Object object) { + return switch (object) { + case Boolean _ -> "LOGICO"; + case String _ -> "CARACTER"; + case Double _ -> "REAL"; + case Integer _ -> "INTEIRO"; + default -> "Desconhecido"; + }; + } + + private static void addArrayDebug(String scope, String variableName, Consumer consumer, Object[] objects) { + for (int i = 0; i < objects.length; i++) { + Object object = objects[i]; + addObjectDebug(scope, variableName + "[" + i + "]", consumer, object); + } + } + + private static void addMultiArrayDebug(String scope, String variableName, Consumer consumer, Object[][] multiObjects) { + for (int i = 0; i < multiObjects.length; i++) { + Object[] objects = multiObjects[i]; + for (int j = 0; j < objects.length; j++) { + Object object = objects[j]; + addObjectDebug(scope, variableName + "[" + i + ", " + j + "]", consumer, object); + } + } + } + + enum ToWhere { + DOS, OUTPUT, BOTH + } + + enum When { + NOW, LATER + } + + private static void addObjectDebug(String scope, String variableName, Consumer consumer, Object variableValue) { + String visualgType = getVisualgType(variableValue); + switch (variableValue) { + case Boolean b -> + consumer.accept(new DebugState(scope, variableName, visualgType, b ? "VERDADEIRO" : "FALSO")); + case String s -> consumer.accept(new DebugState(scope, variableName, visualgType, "\"" + s + "\"")); + case Integer _, Double _ -> + consumer.accept(new DebugState(scope, variableName, visualgType, variableValue.toString())); + case UserDefinedValue userDefinedValue -> { + userDefinedValue.values().forEach((fieldName, fieldValue) -> addDebug(consumer, fieldValue, scope, variableName + "." + fieldName)); + } + default -> { + } + } + } + + private static void addDebug(Consumer consumer, Object variableValue, String scopeName, String variableNameUpperCase) { + switch (variableValue) { + case Object[][] multiObjects -> + addMultiArrayDebug(scopeName, variableNameUpperCase, consumer, multiObjects); + case Object[] objects -> addArrayDebug(scopeName, variableNameUpperCase, consumer, objects); + case Object _ -> addObjectDebug(scopeName, variableNameUpperCase, consumer, variableValue); + } + } + + private void resetExecution() { + outputArea.setText(""); + debugTable.setRowCount(0); + appendOutput("Início da execução\n", ToWhere.OUTPUT, When.NOW); + interpreter.reset(); + breakpointLines.forEach((location, _) -> interpreter.addBreakpoint(location + 1)); + + dosContent.setText(""); + dosWindow.setSize(320, 240); + dosWindow.show(); + } + + + private Void handleExecutionError(Throwable e) { + if (e.getCause() instanceof CancellationException) { + appendOutput("\n*** Execução terminada pelo usuário.", ToWhere.DOS, When.LATER); + appendOutput("\n*** Feche esta janela para retornar ao Visual.", ToWhere.DOS, When.LATER); + + appendOutput("\nExecução terminada pelo usuário.", ToWhere.OUTPUT, When.LATER); + return null; + } else if (e.getCause() instanceof TypeException) { + appendOutput(e.getCause().getMessage(), ToWhere.OUTPUT, When.LATER); + } else if (e.getCause() != null) { + e.printStackTrace(); + appendOutput(e.getCause().toString(), ToWhere.OUTPUT, When.LATER); + } + + appendOutput("\nExecução terminada por erro.", ToWhere.BOTH, When.LATER); + appendOutput("\n>>> Fim da execução do programa !", ToWhere.DOS, When.LATER); + return null; + } + + private void startExecution(InterpreterState interpreterState) { + interpreter.runWithState(textArea.getText(), interpreterState); + switch (interpreter.state()) { + case InterpreterState.CompletedSuccessfully _ -> this.handleExecutionSuccessfully(); + case InterpreterState.CompletedExceptionally(Throwable e) -> this.handleExecutionError(e); + default -> throw new IllegalStateException("Unexpected value: " + interpreter.state()); + } + textArea.removeAllLineHighlights(); + } + + private void handleStartOrContinue() { + switch (interpreter.state()) { + case InterpreterState.PausedDebug _ -> { + interpreter.continueExecution(); + } + case InterpreterState.NotStarted _, InterpreterState.ForcedStop _, + InterpreterState.CompletedExceptionally _, InterpreterState.CompletedSuccessfully _ -> { + resetExecution(); + Thread.startVirtualThread(() -> startExecution(InterpreterState.Running.INSTANCE)); + } + case InterpreterState.Running _ -> { + } + } + } + + private void updateDebugArea(ProgramState programState) { + debugTable.setRowCount(0); + + programState.stack().entrySet().stream() + .mapMulti((Map.Entry> entry, Consumer consumer) -> { + entry.getValue().forEach((variableName, variableValue) -> { + String scopeName = entry.getKey().toUpperCase(); + String variableNameUpperCase = variableName.toUpperCase(); + addDebug(consumer, variableValue, scopeName, variableNameUpperCase); + }); + }) + .map(x -> new Object[]{x.getEscopo(), x.getNome(), x.getTipo(), x.getValor()}) + .forEach(debugTable::addRow); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> new SwingIDE().setVisible(true)); + } + + private static class TypeChecker extends AbstractParser { + @Override + public ParseResult parse(RSyntaxDocument doc, String style) { + DefaultParseResult parseResult = new DefaultParseResult(this); + long start = System.currentTimeMillis(); + parseResult.setParsedLines(0, doc.getDefaultRootElement().getElementCount()); + + try { + String text = doc.getText(0, doc.getLength()); + ASTResult astResult = VisualgParser.parse(text); + + List errors = astResult.errors(); + + astResult.node() + .map(dev.thihup.jvisualg.frontend.TypeChecker::semanticAnalysis) + .map(TypeCheckerResult::errors) + .ifPresent(errors::addAll); + + errors.stream() + .map(x -> { + int offset = doc.getTokenListForLine(x.location().startLine() - 1).getOffset(); + return new DefaultParserNotice(this, x.message(), x.location().startLine() - 1, offset + x.location().startColumn(), x.location().endColumn() - x.location().startColumn() + 1); + }) + .forEach(parseResult::addNotice); + } catch (Exception e) { + parseResult.setError(e); + } + parseResult.setParseTime(System.currentTimeMillis() - start); + return parseResult; + } + } +} diff --git a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/SyntaxHighlight.java b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/SyntaxHighlight.java index 316d267..b58238c 100644 --- a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/SyntaxHighlight.java +++ b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/SyntaxHighlight.java @@ -1,32 +1,32 @@ package dev.thihup.jvisualg.ide; -import org.fxmisc.richtext.model.StyleSpans; -import org.fxmisc.richtext.model.StyleSpansBuilder; - import java.util.Collection; import java.util.List; -import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; class SyntaxHighlight { - private static final List KEYWORDS = List.of( - "Div", "Mod", "Se", "Entao", "Então", "Senao", "Senão", "FimSe", "Para", "De", "Ate", "Até", "Passo", "FimPara", "Faca", "Faça", "Enquanto", "FimEnquanto", "Retorne", "E", "Ou", "Nao", "Não", "Escolha", "FimEscolha", "Repita", "FimRepita", "Caso", "OutroCaso", + static final List FUNCTIONS = List.of( "abs", "arccos", "arcsen", "arctan", "asc", "carac", "caracpnum", "compr", "copia", "cos", "cotan", "exp", "grauprad", "int", "log", "logn", "maiusc", "minusc", "numpcarac", "pos", "pi", "quad", "radpgrau", "raizq", "rand", "randi", "sen", "tan" + ); + static final List KEYWORDS = List.of( + "Div", "Mod", "Se", "Entao", "Então", "Senao", "Senão", "FimSe", "Para", "De", "Ate", "Até", "Passo", "FimPara", "Faca", "Faça", "Enquanto", "FimEnquanto", "Retorne", "E", "Ou", "Nao", "Não", "Escolha", "FimEscolha", "Repita", "FimRepita", "Caso", "OutroCaso" ); - private static final List DATA_TYPES = List.of( + static final List DATA_TYPES = List.of( "Inteiro", "Real", "Logico", "Caracter", "Caractere", "Literal", "Vetor" ); - private static final List SPECIAL_KEYWORDS = List.of( + static final List SPECIAL_KEYWORDS = List.of( "Escreva", "Escreval", "Leia", "Algoritmo", "FimAlgoritmo", "Funcao", "Função", "FimFuncao", "FimFunção", "Procedimento", "FimProcedimento", "Inicio", "Var" ); private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""; - private static final String KEYWORD_PATTERN = "(?i)\\b(" + String.join("|", KEYWORDS) + ")\\b"; + private static final String KEYWORD_PATTERN = "(?i)\\b(" + Stream.of(KEYWORDS, FUNCTIONS).flatMap(Collection::stream).collect(Collectors.joining("|")) + ")\\b"; private static final String SPECIAL_PATTERN = "(?i)\\b(" + String.join("|", SPECIAL_KEYWORDS) + ")\\b"; private static final String TYPES_PATTERN = "(?i)\\b(" + String.join("|", DATA_TYPES) + ")\\b"; private static final String BOOLEAN_PATTERN = "(?i)\\b(VERDADEIRO|FALSO)\\b"; @@ -44,31 +44,4 @@ class SyntaxHighlight { + "|(?" + SPECIAL_PATTERN + ")"); - - static StyleSpans> computeHighlighting(String text) { - Matcher matcher = PATTERN.matcher(text); - int lastKwEnd = 0; - StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>(); - while (matcher.find()) { - List style = switch (matcher) { - - case Matcher _ when matcher.group("KEYWORD") != null -> List.of("keyword"); - case Matcher _ when matcher.group("STRING") != null -> List.of("string"); - case Matcher _ when matcher.group("COMMENT") != null -> List.of("comment", "italic"); - case Matcher _ when matcher.group("NUMBER") != null -> List.of("number"); - case Matcher _ when matcher.group("TYPE") != null -> List.of("dataType", "underline"); - case Matcher _ when matcher.group("SPECIAL") != null -> List.of("special", "underline"); - case Matcher _ when matcher.group("BOOLEAN") != null -> List.of("number"); - default -> throw new IllegalStateException("Unexpected value: " + matcher); - - }; - - spansBuilder.add(List.of(), matcher.start() - lastKwEnd); - spansBuilder.add(style, matcher.end() - matcher.start()); - lastKwEnd = matcher.end(); - } - spansBuilder.add(List.of(), text.length() - lastKwEnd); - return spansBuilder.create(); - } - } diff --git a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/VisualgTokenMaker.java b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/VisualgTokenMaker.java new file mode 100644 index 0000000..5fa15b0 --- /dev/null +++ b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/VisualgTokenMaker.java @@ -0,0 +1,136 @@ +package dev.thihup.jvisualg.ide; + +import de.tisoft.rsyntaxtextarea.modes.antlr.AntlrTokenMaker; +import dev.thihup.jvisualg.frontend.impl.antlr.VisuAlgLexer; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Lexer; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.TokenMap; +import org.fife.ui.rsyntaxtextarea.TokenTypes; + +import javax.swing.text.Segment; + +public class VisualgTokenMaker extends AntlrTokenMaker { + + private static final TokenMap TOKEN_MAP = new TokenMap(true); + + static { + SyntaxHighlight.FUNCTIONS.forEach(x -> TOKEN_MAP.put(x, TokenTypes.FUNCTION)); + } + + @Override + public void addToken(Segment segment, int start, int end, int tokenType, int startOffset) { + switch (tokenType) { + case Token.IDENTIFIER: + int value = TOKEN_MAP.get(segment, start, end); + if (value != -1) + tokenType = value; + break; + } + + super.addToken(segment, start, end, tokenType, startOffset); + } + + @Override + public String[] getLineCommentStartAndEnd(int languageIndex) { + return new String[]{"//", null}; + } + + @Override + protected int convertType(int i) { + return switch (i) { + case VisuAlgLexer.ALGORITMO -> Token.RESERVED_WORD; + case VisuAlgLexer.INICIO -> Token.RESERVED_WORD; + case VisuAlgLexer.FIM_ALGORITMO -> Token.RESERVED_WORD; + case VisuAlgLexer.CONST -> Token.RESERVED_WORD; + case VisuAlgLexer.VAR -> Token.RESERVED_WORD; + case VisuAlgLexer.INTEIRO -> Token.DATA_TYPE; + case VisuAlgLexer.REAL -> Token.DATA_TYPE; + case VisuAlgLexer.CARACTERE -> Token.DATA_TYPE; + case VisuAlgLexer.LOGICO -> Token.DATA_TYPE; + case VisuAlgLexer.VETOR -> Token.DATA_TYPE; + case VisuAlgLexer.ESCREVA -> Token.FUNCTION; + case VisuAlgLexer.ESCREVAL -> Token.FUNCTION; + case VisuAlgLexer.LEIA -> Token.FUNCTION; + case VisuAlgLexer.SE -> Token.RESERVED_WORD; + case VisuAlgLexer.ENTAO -> Token.RESERVED_WORD; + case VisuAlgLexer.SENAO -> Token.RESERVED_WORD; + case VisuAlgLexer.FIMSE -> Token.RESERVED_WORD; + case VisuAlgLexer.ESCOLHA -> Token.RESERVED_WORD; + case VisuAlgLexer.CASO -> Token.RESERVED_WORD; + case VisuAlgLexer.OUTROCASO -> Token.RESERVED_WORD; + case VisuAlgLexer.FIMESCOLHA -> Token.RESERVED_WORD; + case VisuAlgLexer.PARA -> Token.RESERVED_WORD; + case VisuAlgLexer.DE -> Token.RESERVED_WORD; + case VisuAlgLexer.ATE -> Token.RESERVED_WORD; + case VisuAlgLexer.PASSO -> Token.RESERVED_WORD; + case VisuAlgLexer.FACA -> Token.RESERVED_WORD; + case VisuAlgLexer.FIMPARA -> Token.RESERVED_WORD; + case VisuAlgLexer.ENQUANTO -> Token.RESERVED_WORD; + case VisuAlgLexer.FIMENQUANTO -> Token.RESERVED_WORD; + case VisuAlgLexer.REPITA -> Token.RESERVED_WORD; + case VisuAlgLexer.FIMREPITA -> Token.RESERVED_WORD; + case VisuAlgLexer.INTERROMPA -> Token.RESERVED_WORD; + case VisuAlgLexer.PROCEDIMENTO -> Token.RESERVED_WORD; + case VisuAlgLexer.FIMPROCEDIMENTO -> Token.RESERVED_WORD; + case VisuAlgLexer.FUNCAO -> Token.RESERVED_WORD; + case VisuAlgLexer.FIMFUNCAO -> Token.RESERVED_WORD; + case VisuAlgLexer.RETORNE -> Token.RESERVED_WORD; + case VisuAlgLexer.ARQUIVO -> Token.RESERVED_WORD; + case VisuAlgLexer.ALEATORIO -> Token.RESERVED_WORD; + case VisuAlgLexer.ON -> Token.RESERVED_WORD; + case VisuAlgLexer.OFF -> Token.RESERVED_WORD; + case VisuAlgLexer.TIMER -> Token.RESERVED_WORD; + case VisuAlgLexer.PAUSA -> Token.RESERVED_WORD; + case VisuAlgLexer.DEBUG -> Token.RESERVED_WORD; + case VisuAlgLexer.ECO -> Token.RESERVED_WORD; + case VisuAlgLexer.CRONOMETRO -> Token.RESERVED_WORD; + case VisuAlgLexer.LIMPATELA -> Token.RESERVED_WORD; + case VisuAlgLexer.DOS -> Token.RESERVED_WORD; + case VisuAlgLexer.TIPO -> Token.RESERVED_WORD; + case VisuAlgLexer.REGISTRO -> Token.RESERVED_WORD; + case VisuAlgLexer.FIM_REGISTRO -> Token.RESERVED_WORD; + case VisuAlgLexer.INT_LITERAL -> Token.LITERAL_NUMBER_DECIMAL_INT; + case VisuAlgLexer.RANGE -> Token.OPERATOR; + case VisuAlgLexer.REAL_LITERAL -> Token.LITERAL_NUMBER_FLOAT; + case VisuAlgLexer.STRING -> Token.LITERAL_STRING_DOUBLE_QUOTE; + case VisuAlgLexer.VERDADEIRO -> Token.LITERAL_BOOLEAN; + case VisuAlgLexer.FALSO -> Token.LITERAL_BOOLEAN; + case VisuAlgLexer.ADD -> Token.OPERATOR; + case VisuAlgLexer.SUB -> Token.OPERATOR; + case VisuAlgLexer.MUL -> Token.OPERATOR; + case VisuAlgLexer.DIV -> Token.OPERATOR; + case VisuAlgLexer.MOD -> Token.OPERATOR; + case VisuAlgLexer.DIV_INT -> Token.OPERATOR; + case VisuAlgLexer.POW -> Token.OPERATOR; + case VisuAlgLexer.EQ -> Token.OPERATOR; + case VisuAlgLexer.NEQ -> Token.OPERATOR; + case VisuAlgLexer.LT -> Token.OPERATOR; + case VisuAlgLexer.GT -> Token.OPERATOR; + case VisuAlgLexer.LE -> Token.OPERATOR; + case VisuAlgLexer.GE -> Token.OPERATOR; + case VisuAlgLexer.NOT -> Token.OPERATOR; + case VisuAlgLexer.AND -> Token.OPERATOR; + case VisuAlgLexer.OR -> Token.OPERATOR; + case VisuAlgLexer.XOR -> Token.OPERATOR; + case VisuAlgLexer.ASSIGN -> Token.OPERATOR; + case VisuAlgLexer.COLON -> Token.OPERATOR; + case VisuAlgLexer.SEMICOLON -> Token.OPERATOR; + case VisuAlgLexer.COMMA -> Token.OPERATOR; + case VisuAlgLexer.LPAREN -> Token.SEPARATOR; + case VisuAlgLexer.RPAREN -> Token.SEPARATOR; + case VisuAlgLexer.LBRACK -> Token.SEPARATOR; + case VisuAlgLexer.RBRACK -> Token.SEPARATOR; + case VisuAlgLexer.DOT -> Token.OPERATOR; + case VisuAlgLexer.ID -> Token.IDENTIFIER; + case VisuAlgLexer.COMMENT -> Token.COMMENT_EOL; + case VisuAlgLexer.WS -> Token.WHITESPACE; + default -> throw new IllegalStateException(); + }; + } + + @Override + protected Lexer createLexer(String s) { + return new VisuAlgLexer(CharStreams.fromString(s)); + } +} diff --git a/dev.thihup.jvisualg.ide/src/main/java/module-info.java b/dev.thihup.jvisualg.ide/src/main/java/module-info.java index 6561970..7b7b4f6 100644 --- a/dev.thihup.jvisualg.ide/src/main/java/module-info.java +++ b/dev.thihup.jvisualg.ide/src/main/java/module-info.java @@ -1,15 +1,12 @@ module dev.thihup.jvisualg.ide { - requires javafx.fxml; - requires javafx.controls; - requires org.fxmisc.richtext; - requires reactfx; requires dev.thihup.jvisualg.lsp; - requires org.eclipse.lsp4j; - requires org.eclipse.lsp4j.jsonrpc; requires dev.thihup.jvisualg.interpreter; - requires org.jspecify; + requires org.fife.RSyntaxTextArea; + requires com.formdev.flatlaf; + requires dev.thihup.jvisualg.frontend; + requires org.antlr.antlr4.runtime; + requires rsyntaxtextarea.antlr4.extension; + requires java.desktop; - exports dev.thihup.jvisualg.ide to javafx.graphics; - - opens dev.thihup.jvisualg.ide to javafx.fxml, javafx.base; + exports dev.thihup.jvisualg.ide to org.fife.RSyntaxTextArea; } diff --git a/dev.thihup.jvisualg.interpreter/src/main/java/module-info.java b/dev.thihup.jvisualg.interpreter/src/main/java/module-info.java index ce52999..07d3d4d 100644 --- a/dev.thihup.jvisualg.interpreter/src/main/java/module-info.java +++ b/dev.thihup.jvisualg.interpreter/src/main/java/module-info.java @@ -1,6 +1,5 @@ module dev.thihup.jvisualg.interpreter { requires dev.thihup.jvisualg.frontend; - requires jdk.unsupported; requires org.jspecify; exports dev.thihup.jvisualg.interpreter to dev.thihup.jvisualg.ide;