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;