diff --git a/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/VisuAlgParserVisitor.java b/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/VisuAlgParserVisitor.java index b7a23ac..37fcdce 100644 --- a/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/VisuAlgParserVisitor.java +++ b/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/VisuAlgParserVisitor.java @@ -40,6 +40,9 @@ public Node visitAlgorithm(VisuAlgParser.AlgorithmContext ctx) { @Override public Node visitDeclarations(VisuAlgParser.DeclarationsContext ctx) { + if (ctx.children == null) { + return CompundNode.EMPTY; + } List list = ctx.children.stream() .map(this::visit) .filter(Objects::nonNull) @@ -364,7 +367,7 @@ public Node visitExpr(VisuAlgParser.ExprContext ctx) { if (ctx.parenExpression() instanceof VisuAlgParser.ParenExpressionContext parenExpressionContext) { return visit(parenExpressionContext.expr()); } - if (ctx.SUB() != null) { + if (ctx.SUB() != null && ctx.expr().size() == 1) { return new NegNode(visit(ctx.expr(0)), fromRuleContext(ctx)); } if (ctx.NOT() != null) { @@ -469,7 +472,7 @@ public Node visitTimerCommand(VisuAlgParser.TimerCommandContext ctx) { @Override public Node visitPausaCommand(VisuAlgParser.PausaCommandContext ctx) { - return new PausaCommandNode(fromRuleContext(ctx)); + return new PausaCommandNode(EmptyNode.INSTANCE, fromRuleContext(ctx)); } diff --git a/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/node/Node.java b/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/node/Node.java index 1a2ce0a..aa7e88f 100644 --- a/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/node/Node.java +++ b/dev.thihup.jvisualg.frontend/src/main/java/dev/thihup/jvisualg/frontend/node/Node.java @@ -443,7 +443,9 @@ record PowNode(Node left, Node right, Optional location) implements Bi sealed interface BooleanNode extends CommandNode, ExpressionNode {} - record AndNode(Node left, Node right, Optional location) implements BooleanNode, BinaryNode { + sealed interface RelationalNode extends BinaryNode, BooleanNode {} + + record AndNode(Node left, Node right, Optional location) implements RelationalNode { public AndNode { Objects.requireNonNull(left); Objects.requireNonNull(right); @@ -451,7 +453,7 @@ record AndNode(Node left, Node right, Optional location) implements Bo } } - record OrNode(Node left, Node right, Optional location) implements BooleanNode, BinaryNode { + record OrNode(Node left, Node right, Optional location) implements RelationalNode { public OrNode { Objects.requireNonNull(left); Objects.requireNonNull(right); @@ -466,7 +468,6 @@ record NotNode(Node expr, Optional location) implements BooleanNode { } } - sealed interface RelationalNode extends BinaryNode {} record EqNode(Node left, Node right, Optional location) implements RelationalNode { public EqNode { @@ -551,7 +552,7 @@ record TimerCommandNode(boolean on, int value, Optional location) impl } } - record PausaCommandNode(Optional location) implements CommandNode { + record PausaCommandNode(Node node, Optional location) implements CommandNode { public PausaCommandNode { Objects.requireNonNull(location); } diff --git a/dev.thihup.jvisualg.ide/pom.xml b/dev.thihup.jvisualg.ide/pom.xml index b4535c8..ca7536a 100644 --- a/dev.thihup.jvisualg.ide/pom.xml +++ b/dev.thihup.jvisualg.ide/pom.xml @@ -26,6 +26,11 @@ dev.thihup.jvisualg.lsp ${project.version} + + dev.thihup.jvisualg + dev.thihup.jvisualg.interpreter + ${project.version} + org.openjfx javafx-controls 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 index 9197da5..f21fe8d 100644 --- 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 @@ -1,5 +1,7 @@ package dev.thihup.jvisualg.ide; +import dev.thihup.jvisualg.interpreter.DAPServer; +import dev.thihup.jvisualg.interpreter.DebugProtocolClientExtension; import dev.thihup.jvisualg.lsp.VisualgLauncher; import javafx.application.Application; import javafx.application.Platform; @@ -9,11 +11,20 @@ import javafx.geometry.Point2D; import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.control.TextArea; import javafx.stage.Popup; import javafx.stage.Stage; import org.eclipse.lsp4j.*; +import org.eclipse.lsp4j.debug.InitializeRequestArguments; +import org.eclipse.lsp4j.debug.OutputEventArguments; +import org.eclipse.lsp4j.debug.SetBreakpointsArguments; +import org.eclipse.lsp4j.debug.SourceBreakpoint; +import org.eclipse.lsp4j.debug.launch.DSPLauncher; +import org.eclipse.lsp4j.debug.services.IDebugProtocolServer; import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.jsonrpc.debug.DebugLauncher; import org.eclipse.lsp4j.launch.LSPLauncher; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageServer; @@ -24,15 +35,13 @@ import org.fxmisc.richtext.model.StyleSpansBuilder; import org.reactfx.Subscription; +import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.time.Duration; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -43,28 +52,99 @@ public class Main extends Application { - private final ExecutorService executor = Executors.newFixedThreadPool(3); + private final ExecutorService fxClientExecutor = Executors.newSingleThreadExecutor(); + + private final ExecutorService lspClientExecutor = Executors.newSingleThreadExecutor(); + private final ExecutorService lspServerExecutor = Executors.newSingleThreadExecutor(); + + private final ExecutorService dapClientExecutor = Executors.newSingleThreadExecutor(); + private final ExecutorService dapServerExecutor = Executors.newSingleThreadExecutor(); + @FXML private CodeArea codeArea; + @FXML + private Button runButton; + + @FXML + private TextArea outputArea; + private Subscription subscribe; private Launcher clientLauncher; private Future lspServer; private Future lspClient; + private Launcher dapServerLauncher; + private Launcher dapClientLauncher; + private Future dapServerListener; + private Future dapClientListener; + private List diagnostics; @Override public void start(Stage stage) throws Exception { FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("gui.fxml")); fxmlLoader.setController(this); - Parent root = fxmlLoader.load(); codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea)); + setupSyntaxHighlighting(); + + setupDefaultText(); + + showScene(stage, root); + + setupLSP(); + + setupDAP(); + + runButton.addEventHandler(javafx.event.ActionEvent.ACTION, e -> { + Platform.runLater(() -> { + outputArea.clear(); + dapClientLauncher.getRemoteProxy().launch(Map.of("source", codeArea.getText())); + }); + }); + + + setupErrorPopup(); + + if (Boolean.getBoolean("autoClose")) + Platform.runLater(stage::close); + } + + private void showScene(Stage stage, Parent root) { + Scene scene = new Scene(root); + scene.setOnKeyPressed(x -> { + switch (x.getCode()) { + case F9 -> runButton.fire(); + default -> {} + } + }); + scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm()); + stage.setTitle("JVisualG"); + stage.setScene(scene); + stage.show(); + } + + private void setupDefaultText() { + codeArea.replaceText(0, 0, """ + algoritmo "semnome" + // Função : + // Autor : + // Data : %s + // Seção de Declarações + var + + inicio + // Seção de Comandos + fimalgoritmo + """.formatted(LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")))); + } + + private void setupSyntaxHighlighting() { subscribe = codeArea.multiPlainChanges() .successionEnds(Duration.ofMillis(50)) - .retainLatestUntilLater(executor) + .retainLatestUntilLater(fxClientExecutor) .supplyTask(this::computeHighlightingAsync) .awaitLatest(codeArea.multiPlainChanges()) .filterMap(t -> { @@ -76,42 +156,44 @@ public void start(Stage stage) throws Exception { } }) .subscribe(this::applyHighlighting); + } + private void setupLSP() throws IOException { + PipedInputStream lspInputClient = new PipedInputStream(); + PipedOutputStream lspOutputClient = new PipedOutputStream(); + PipedInputStream lspInputServer = new PipedInputStream(); + PipedOutputStream lspOutputServer = new PipedOutputStream(); - codeArea.replaceText(0, 0, """ - algoritmo "semnome" - // Função : - // Autor : - // Data : %s - // Seção de Declarações - var - - inicio - // Seção de Comandos - fimalgoritmo - """.formatted(LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")))); + lspInputClient.connect(lspOutputServer); + lspOutputClient.connect(lspInputServer); + lspServer = VisualgLauncher.startServer(lspInputServer, lspOutputServer, lspServerExecutor); + clientLauncher = LSPLauncher.createClientLauncher(new VisualgLanguageClient(), lspInputClient, lspOutputClient, lspClientExecutor, null); - Scene scene = new Scene(root); - scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm()); - stage.setTitle("JVisualG"); - stage.setScene(scene); - stage.show(); + lspClient = clientLauncher.startListening(); + } + private void setupDAP() throws IOException { + PipedInputStream dapInputClient = new PipedInputStream(); + PipedOutputStream dapOutputClient = new PipedOutputStream(); + PipedInputStream dapInputServer = new PipedInputStream(); + PipedOutputStream dapOutputServer = new PipedOutputStream(); - PipedInputStream inClient = new PipedInputStream(); - PipedOutputStream outClient = new PipedOutputStream(); - PipedInputStream inServer = new PipedInputStream(); - PipedOutputStream outServer = new PipedOutputStream(); + dapInputClient.connect(dapOutputServer); + dapOutputClient.connect(dapInputServer); - inClient.connect(outServer); - outClient.connect(inServer); + DAPServer server = new DAPServer(); + dapServerLauncher = DebugLauncher.createLauncher(server, DebugProtocolClientExtension.class, dapInputServer, dapOutputServer, dapServerExecutor, null); + server.connect(dapServerLauncher.getRemoteProxy()); + dapServerListener = dapServerLauncher.startListening(); - lspServer = VisualgLauncher.startServer(inServer, outServer, executor); - clientLauncher = LSPLauncher.createClientLauncher(new VisualgLanguageClient(), inClient, outClient, executor, null); - lspClient = clientLauncher.startListening(); + dapClientLauncher = DSPLauncher.createClientLauncher(new DAPClient(), dapInputClient, dapOutputClient, dapClientExecutor, null); + dapClientListener = dapClientLauncher.startListening(); + dapClientLauncher.getRemoteProxy().initialize(new InitializeRequestArguments()); + } + private void setupErrorPopup() { Popup popup = new Popup(); Label popupMsg = new Label(); popupMsg.setStyle( @@ -122,33 +204,30 @@ public void start(Stage stage) throws Exception { codeArea.setMouseOverTextDelay(Duration.ofMillis(200)); codeArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> { - int chIdx = e.getCharacterIndex(); - Point2D pos = e.getScreenPosition(); - if (codeArea.getText().isEmpty() || diagnostics == null) - return; - 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); - }); - + handlePopupError(e, popupMsg, popup); }); codeArea.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> { popup.hide(); }); - - - if (Boolean.getBoolean("autoClose")) - Platform.runLater(stage::close); } + private void handlePopupError(MouseOverTextEvent e, Label popupMsg, Popup popup) { + int chIdx = e.getCharacterIndex(); + Point2D pos = e.getScreenPosition(); + if (codeArea.getText().isEmpty() || diagnostics == null) + return; + 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 List diagnostics; class VisualgLanguageClient implements LanguageClient { @@ -195,13 +274,24 @@ public void stop() throws Exception { if (subscribe != null) { subscribe.unsubscribe(); } + if (lspClient != null) { + lspClient.cancel(true); + } if (lspServer != null) { lspServer.cancel(true); } - if (lspClient != null) { - lspClient.cancel(true); + if (dapClientListener != null) { + dapClientListener.cancel(true); } - executor.shutdown(); + if (dapServerListener != null) { + dapServerListener.cancel(true); + } + + fxClientExecutor.shutdown(); + lspClientExecutor.shutdown(); + dapClientExecutor.shutdown(); + lspServerExecutor.shutdown(); + dapServerExecutor.shutdown(); } private int toOffset(String text, Position position) { @@ -265,7 +355,7 @@ protected StyleSpans> call() throws Exception { return computeHighlighting(text); } }; - executor.execute(task); + fxClientExecutor.execute(task); return task; } @@ -298,4 +388,15 @@ private StyleSpans> computeHighlighting(String text) { return spansBuilder.create(); } + private class DAPClient implements DebugProtocolClientExtension { + @Override + public void output(OutputEventArguments args) { + Platform.runLater(() -> outputArea.appendText(args.getOutput())); + } + + @Override + public CompletableFuture input() { + return CompletableFuture.completedFuture("SIMPLE TEXT"); + } + } } 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 94989cb..b22161b 100644 --- a/dev.thihup.jvisualg.ide/src/main/java/module-info.java +++ b/dev.thihup.jvisualg.ide/src/main/java/module-info.java @@ -4,6 +4,11 @@ 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.eclipse.lsp4j.debug; + requires org.eclipse.lsp4j.jsonrpc.debug; exports dev.thihup.jvisualg.ide to javafx.graphics; diff --git a/dev.thihup.jvisualg.ide/src/main/resources/dev/thihup/jvisualg/ide/gui.fxml b/dev.thihup.jvisualg.ide/src/main/resources/dev/thihup/jvisualg/ide/gui.fxml index 38215ae..d97e095 100644 --- a/dev.thihup.jvisualg.ide/src/main/resources/dev/thihup/jvisualg/ide/gui.fxml +++ b/dev.thihup.jvisualg.ide/src/main/resources/dev/thihup/jvisualg/ide/gui.fxml @@ -1,9 +1,9 @@ + - @@ -13,8 +13,6 @@ - - @@ -46,7 +44,7 @@