diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9ad5ad..93b2cdd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: - uses: actions/checkout@v4 - uses: graalvm/setup-graalvm@v1 with: - java-version: '22' + java-version: 'dev' distribution: 'graalvm' cache: 'maven' github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/ReplaceListener.java b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/ReplaceListener.java new file mode 100644 index 0000000..c66a99d --- /dev/null +++ b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/ReplaceListener.java @@ -0,0 +1,36 @@ +package dev.thihup.jvisualg.ide; + +import org.fife.rsta.ui.search.SearchEvent; +import org.fife.rsta.ui.search.SearchListener; +import org.fife.ui.rtextarea.RTextArea; +import org.fife.ui.rtextarea.SearchContext; +import org.fife.ui.rtextarea.SearchEngine; + +import javax.swing.*; +import java.awt.*; +import java.util.function.Supplier; + +class ReplaceListener implements SearchListener { + private final RTextArea textArea; + private final Supplier searchContext; + + public ReplaceListener(RTextArea textArea, Supplier searchContext) { + this.textArea = textArea; + this.searchContext = searchContext; + } + + @Override + public void searchEvent(SearchEvent searchEvent) { + switch (searchEvent.getType()) { + case FIND -> SearchEngine.find(textArea, searchContext.get()[0]); + case REPLACE -> SearchEngine.replace(textArea, searchContext.get()[0]); + case REPLACE_ALL -> SearchEngine.replaceAll(textArea, searchContext.get()[0]); + case MARK_ALL -> SearchEngine.markAll(textArea, searchContext.get()[0]); + } + } + + @Override + public String getSelectedText() { + return textArea.getSelectedText(); + } +} 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 index 82eee45..b641a0d 100644 --- 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 @@ -2,10 +2,6 @@ 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 dev.thihup.jvisualg.lsp.CodeCompletion; import org.fife.rsta.ui.search.*; @@ -19,20 +15,15 @@ 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.event.WindowAdapter; -import java.awt.event.WindowEvent; +import java.awt.event.*; import java.awt.print.PrinterException; import java.io.IOException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.*; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -import java.util.function.Supplier; public class SwingIDE extends JFrame { @@ -65,13 +56,30 @@ public SwingIDE() { dosContent.setBackground(Color.BLACK); dosContent.setForeground(Color.WHITE); - dosContent.setFont(Font.getFont(Font.MONOSPACED)); + Font monospacedFont = new Font("Consolas", Font.PLAIN, 12); + dosContent.setFont(monospacedFont); + dosContent.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER){ + try { + callback.accept(dosContent.getText(lastPromptPosition, dosContent.getText().length())); + lastPromptPosition = dosContent.getText().length(); + } catch (BadLocationException ex) { + throw new RuntimeException(ex); + } + } + } + }); dosWindow.add(dosContent); + dosWindow.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); dosWindow.addWindowListener(new WindowAdapter() { @Override - public void windowClosed(WindowEvent e) { + public void windowClosing(WindowEvent e) { + interpreter.stop(); dosWindow.setVisible(false); } + }); interpreter = new Interpreter(new IO(this::readVariable, this::handleOutputEvent), @@ -169,7 +177,7 @@ public void actionPerformed(ActionEvent e) { SearchContext[] searchContexts = new SearchContext[1]; - ReplaceToolBar replaceToolBar = new ReplaceToolBar(new ReplaceListener(() -> searchContexts)); + ReplaceToolBar replaceToolBar = new ReplaceToolBar(new ReplaceListener(textArea, () -> searchContexts)); searchContexts[0] = replaceToolBar.getSearchContext(); @@ -202,6 +210,7 @@ public void actionPerformed(ActionEvent e) { bottomSplitPane.setLeftComponent(debugScrollPane); outputArea = new JTextArea(); + outputArea.setFont(monospacedFont); JScrollPane outputScrollPane = new JScrollPane(outputArea); bottomSplitPane.setRightComponent(outputScrollPane); @@ -588,11 +597,12 @@ private void resetExecution() { private void handleExecutionError(Throwable e) { - if (e.getCause() instanceof CancellationException) { + if (e.getCause() instanceof InterruptedException) { 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; } else if (e.getCause() instanceof TypeException) { appendOutput(e.getCause().getMessage(), ToWhere.OUTPUT, When.LATER); } else if (e.getCause() != null) { @@ -641,56 +651,4 @@ private void updateDebugArea(ProgramState programState) { .forEach(debugTable::addRow); } - 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; - } - } - - private class ReplaceListener implements SearchListener { - private final Supplier searchContext; - - public ReplaceListener(Supplier searchContext) { - this.searchContext = searchContext; - } - - @Override - public void searchEvent(SearchEvent searchEvent) { - switch (searchEvent.getType()) { - case FIND -> SearchEngine.find(textArea, searchContext.get()[0]); - case REPLACE -> SearchEngine.replace(textArea, searchContext.get()[0]); - } - } - - @Override - public String getSelectedText() { - return textArea.getSelectedText(); - } - } } diff --git a/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/TypeChecker.java b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/TypeChecker.java new file mode 100644 index 0000000..3301e28 --- /dev/null +++ b/dev.thihup.jvisualg.ide/src/main/java/dev/thihup/jvisualg/ide/TypeChecker.java @@ -0,0 +1,45 @@ +package dev.thihup.jvisualg.ide; + +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 org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +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 java.util.List; + +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.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/InputState.java b/dev.thihup.jvisualg.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/InputState.java index ee53088..f12e6b5 100644 --- a/dev.thihup.jvisualg.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/InputState.java +++ b/dev.thihup.jvisualg.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/InputState.java @@ -3,18 +3,13 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; -import java.nio.charset.StandardCharsets; import java.nio.file.Paths; -import java.util.Locale; -import java.util.Optional; import java.util.Scanner; import java.util.random.RandomGenerator; sealed interface InputState { - default Optional generateValue(InputRequestValue requestValue) { - return Optional.empty(); - } + InputValue generateValue(InputRequestValue requestValue) throws E; static InputState compose(InputState first, InputState second) { return new CompositeInputState(first, second); @@ -22,15 +17,15 @@ static InputState compose(InputState first, InputState second) { record Aleatorio(RandomGenerator random, int start, int end, int decimalPlaces) implements InputState { @Override - public Optional generateValue(InputRequestValue requestValue) { - return Optional.of(switch (requestValue.type()) { + public InputValue generateValue(InputRequestValue requestValue) { + return switch (requestValue.type()) { case CARACTER -> new InputValue.CaracterValue(random.ints(65, 91) .limit(5) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()); case LOGICO -> new InputValue.LogicoValue(random.nextBoolean()); case REAL -> new InputValue.RealValue(generateRandomDouble(this)); case INTEIRO -> new InputValue.InteiroValue(random.nextInt(start, end)); - }); + }; } @@ -49,12 +44,8 @@ private double generateRandomDouble(Aleatorio aleatorio) { record ReadInput(IO io) implements InputState { @Override - public Optional generateValue(InputRequestValue requestValue) { - try { - return io.input().apply(requestValue).join(); - } catch (Exception _) { - return Optional.empty(); - } + public InputValue generateValue(InputRequestValue requestValue) throws Exception { + return io.input().apply(requestValue).get().orElseThrow(); } } @@ -67,18 +58,14 @@ public Arquivo(String filePath) throws IOException { } @Override - public Optional generateValue(InputRequestValue requestValue) { - try { - final String value = scanner.nextLine(); - return Optional.of(switch (requestValue.type()) { - case CARACTER -> new InputValue.CaracterValue(value); - case LOGICO -> new InputValue.LogicoValue(value.equalsIgnoreCase("VERDADEIRO")); - case REAL -> new InputValue.RealValue(Double.parseDouble(value)); - case INTEIRO -> new InputValue.InteiroValue(Integer.parseInt(value)); - }); - } catch (Exception _) { - return Optional.empty(); - } + public InputValue generateValue(InputRequestValue requestValue) { + String value = scanner.nextLine(); + return switch (requestValue.type()) { + case CARACTER -> new InputValue.CaracterValue(value); + case LOGICO -> new InputValue.LogicoValue(value.equalsIgnoreCase("VERDADEIRO")); + case REAL -> new InputValue.RealValue(Double.parseDouble(value)); + case INTEIRO -> new InputValue.InteiroValue(Integer.parseInt(value)); + }; } } } @@ -93,7 +80,16 @@ public CompositeInputState(InputState first, InputState second) { } @Override - public Optional generateValue(InputRequestValue requestValue) { - return first.generateValue(requestValue).or(() -> second.generateValue(requestValue)); + public InputValue generateValue(InputRequestValue requestValue) { + try { + return first.generateValue(requestValue); + } catch (Exception e) { + try { + return second.generateValue(requestValue); + } catch (Exception ee){ + ee.addSuppressed(e); + throw e; + } + } } } diff --git a/dev.thihup.jvisualg.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/Interpreter.java b/dev.thihup.jvisualg.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/Interpreter.java index 6f37d0d..c3c6df8 100644 --- a/dev.thihup.jvisualg.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/Interpreter.java +++ b/dev.thihup.jvisualg.interpreter/src/main/java/dev/thihup/jvisualg/interpreter/Interpreter.java @@ -42,6 +42,7 @@ public class Interpreter { private InputState inputState; private boolean eco = false; private TreeMap lineToAstNode; + private Thread thread; public Interpreter(IO io, @Nullable Consumer debuggerCallback) { @@ -70,6 +71,7 @@ public void reset() { stack.clear(); breakpoints.clear(); state = InterpreterState.NotStarted.INSTANCE; + thread = null; } public InterpreterState state() { @@ -81,6 +83,11 @@ public void run(String code) { } public void runWithState(String code, InterpreterState state) { + thread = Thread.currentThread(); + startWithState(code, state); + } + + private void startWithState(String code, InterpreterState state) { try { this.state = state; ASTResult parse = VisualgParser.parse(code); @@ -89,7 +96,7 @@ public void runWithState(String code, InterpreterState state) { Node node = optionalNode.get(); lineToAstNode = node.visitChildren() .collect(Collectors.toMap(node2 -> node2.location().orElse(Location.EMPTY).startLine(), - Function.identity(), (_, b) -> b, TreeMap::new)); + Function.identity(), (a, b) -> a, TreeMap::new)); this.run(node); } else { @@ -118,7 +125,8 @@ private void run(Node node) { return; } case InterpreterState.PausedDebug(int lineNumber) - when lineToAstNode.containsKey(lineNumber) && currentLineNumber == lineNumber -> handleDebugCommand(node); + when lineToAstNode.containsKey(lineNumber) && currentLineNumber == lineNumber -> + handleDebugCommand(node); case InterpreterState.PausedDebug e -> { handleDebugCommand(node); setNextLineDebug(e); @@ -126,7 +134,7 @@ private void run(Node node) { case InterpreterState.CompletedExceptionally _, InterpreterState.NotStarted _ -> { } - case InterpreterState.Running _ when breakpoints.contains(currentLineNumber) + case InterpreterState.Running _ when breakpoints.contains(currentLineNumber) && lineToAstNode.containsKey(currentLineNumber) -> { state = new InterpreterState.PausedDebug(currentLineNumber); handleDebugCommand(node); @@ -149,7 +157,7 @@ private void run(Node node) { case Node.TypeNode _ -> throw new UnsupportedOperationException("TypeNode not implemented"); } } catch (IOException | InterruptedException | BrokenBarrierException e) { - state = new InterpreterState.CompletedExceptionally(e); + throw new RuntimeException(e); } catch (StopExecutionException _) { state = InterpreterState.CompletedSuccessfully.INSTANCE; } catch (IndexOutOfBoundsException e) { @@ -163,6 +171,7 @@ private void runCompundNode(Node.CompundNode compundNode) { public void stop() { state = InterpreterState.ForcedStop.INSTANCE; + thread.interrupt(); } private void runDeclaration(Node.DeclarationNode declarationNode) { @@ -202,8 +211,8 @@ public void step() { private void setNextLineDebug(InterpreterState.PausedDebug e) { state = Optional.ofNullable(lineToAstNode.higherKey(e.lineNumber())) - .map(InterpreterState.PausedDebug::new) - .orElse(InterpreterState.Running.INSTANCE); + .map(InterpreterState.PausedDebug::new) + .orElse(InterpreterState.Running.INSTANCE); } public void continueExecution() { @@ -517,7 +526,7 @@ private void readExpression(Node.ExpressionNode expr) { } private Object readValue(InputRequestValue inputRequest) { - InputValue inputValue = inputState.generateValue(inputRequest).orElseThrow(); + InputValue inputValue = inputState.generateValue(inputRequest); Object value = switch (inputValue) { case InputValue.InteiroValue(var value1) -> value1; case InputValue.RealValue(var value1) -> value1; diff --git a/pom.xml b/pom.xml index 5ae5820..9bb0b98 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ - 22 + 23 UTF-8