From af19fc056238b8ac70d7b4d91e305164f355737a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Hinojosa=20Gil?= Date: Sun, 18 May 2025 13:17:29 +0200 Subject: [PATCH 1/2] Basic indenter --- src/rars/util/Indenter.java | 109 ++++++++++++++++++++++++++++++++++++ src/rars/venus/VenusUI.java | 78 +++++++++++++++++++++++--- 2 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 src/rars/util/Indenter.java diff --git a/src/rars/util/Indenter.java b/src/rars/util/Indenter.java new file mode 100644 index 000000000..a587d2d1b --- /dev/null +++ b/src/rars/util/Indenter.java @@ -0,0 +1,109 @@ +package rars.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Basic indenter for RISC-V assembly code. + * Aligns labels, instructions/directives, and comments in fixed columns. + * Formatting rules: + * - Labels start at column 0 + * - Instructions start at column 20 + * - Comments start at column 40 + */ +public class Indenter { + // Column positions for alignment + private static final int LABEL_COLUMN = 0; // Column for labels + private static final int INSTRUCTION_COLUMN = 20; // Column for instructions + private static final int COMMENT_COLUMN = 40; // Column for comments + + /** + * Indents a list of assembly code lines according to RISC-V conventions. + * @param lines List of raw assembly code lines + * @return List of properly indented lines + */ + public List indent(List lines) { + List indentedLines = new ArrayList<>(); + + for (String line : lines) { + String trimmedLine = line.trim(); + + // Handle empty lines and comments/directives + if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) { + indentedLines.add(line); + continue; + } + + // Handle directives + if (trimmedLine.startsWith(".")) { + StringBuilder formattedLine = new StringBuilder(); + formattedLine.append(trimmedLine); + indentedLines.add(formattedLine.toString()); + continue; + } + + // Split line into components + String[] parts = line.split("#", 2); + String codePart = parts[0].trim(); + String commentPart = parts.length > 1 ? "#" + parts[1].trim() : ""; + + // Process code part + String[] tokens = codePart.split("\\s+"); + boolean hasLabel = tokens.length > 0 && tokens[0].endsWith(":"); + String label = hasLabel ? tokens[0] : ""; + String instruction = hasLabel ? (tokens.length > 1 ? tokens[1] : "") : (tokens.length > 0 ? tokens[0] : ""); + String operands = hasLabel + ? String.join(" ", Arrays.copyOfRange(tokens, 2, tokens.length)) + : String.join(" ", Arrays.copyOfRange(tokens, 1, tokens.length)); + + // Build indented line + StringBuilder formattedLine = new StringBuilder(); + + // Handle label + if (!label.isEmpty()) { + formattedLine.append(label); + int spacesNeeded = INSTRUCTION_COLUMN - label.length() - 1; + if (spacesNeeded > 0) { + formattedLine.append(" ".repeat(spacesNeeded)); + } + } else { + // Only add spaces if there's an instruction to align + if (!instruction.isEmpty()) { + formattedLine.append(" ".repeat(INSTRUCTION_COLUMN - 1)); + } + } + + // Handle instruction and operands + if (!instruction.isEmpty()) { + formattedLine.append(instruction); + if (!operands.isEmpty()) { + formattedLine.append(" ").append(operands); + } + } + + // Add comment + if (!commentPart.isEmpty()) { + int currentLength = formattedLine.length(); + if (currentLength < COMMENT_COLUMN) { + formattedLine.append(" ".repeat(COMMENT_COLUMN - currentLength)); + } else { + formattedLine.append(" "); + } + formattedLine.append(commentPart); + } + + indentedLines.add(formattedLine.toString()); + } + return indentedLines; + } + + /** + * Convenience method to indent a complete assembly program string. + * @param assemblyCode The raw assembly code as a single string with newlines + * @return Formatted assembly code with consistent indentation + */ + public static String indentAssembly(String assemblyCode) { + return String.join("\n", new Indenter().indent(Arrays.asList(assemblyCode.split("\n")))); + } +} diff --git a/src/rars/venus/VenusUI.java b/src/rars/venus/VenusUI.java index 4833e0f51..6a7dc2c6e 100644 --- a/src/rars/venus/VenusUI.java +++ b/src/rars/venus/VenusUI.java @@ -1,5 +1,37 @@ package rars.venus; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.net.URL; +import java.util.ArrayList; + +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.JToolBar; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.WindowConstants; + import rars.Globals; import rars.Settings; import rars.SimulationException; @@ -8,18 +40,23 @@ import rars.simulator.Simulator; import rars.simulator.SimulatorNotice; import rars.tools.ConversionTool; +import rars.util.Indenter; import rars.venus.registers.ControlAndStatusWindow; import rars.venus.registers.FloatingPointWindow; import rars.venus.registers.RegistersPane; import rars.venus.registers.RegistersWindow; -import rars.venus.run.*; -import rars.venus.settings.*; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.net.URL; -import java.util.ArrayList; +import rars.venus.run.RunAssembleAction; +import rars.venus.run.RunBackstepAction; +import rars.venus.run.RunClearBreakpointsAction; +import rars.venus.run.RunGoAction; +import rars.venus.run.RunResetAction; +import rars.venus.run.RunSpeedPanel; +import rars.venus.run.RunStepAction; +import rars.venus.settings.SettingsAction; +import rars.venus.settings.SettingsEditorAction; +import rars.venus.settings.SettingsExceptionHandlerAction; +import rars.venus.settings.SettingsHighlightingAction; +import rars.venus.settings.SettingsMemoryConfigurationAction; /** * Top level container for Venus GUI. @@ -56,7 +93,7 @@ public class VenusUI extends JFrame { // components of the menubar private JMenu file, run, window, help, edit, settings; private JMenuItem fileNew, fileOpen, fileClose, fileCloseAll, fileSave, fileSaveAs, fileSaveAll, fileDumpMemory, fileExit; - private JMenuItem editUndo, editRedo, editCut, editCopy, editPaste, editFindReplace, editSelectAll; + private JMenuItem editUndo, editRedo, editCut, editCopy, editPaste, editFindReplace, editSelectAll, fileIndent; private JMenuItem runGo, runStep, runBackstep, runReset, runAssemble, runStop, runPause, runClearBreakpoints, runToggleBreakpoints; private JCheckBoxMenuItem settingsLabel, settingsValueDisplayBase, settingsAddressDisplayBase, settingsExtended, settingsAssembleOnOpen, settingsAssembleAll, settingsAssembleOpen, settingsWarningsAreErrors, @@ -89,6 +126,8 @@ public class VenusUI extends JFrame { settingsSelfModifyingCodeAction, settingsRV64Action, settingsDeriveCurrentWorkingDirectoryAction, settingsDarkModeAction; private Action helpHelpAction, helpAboutAction; + // Nueva acción para el indentador + private Action fileIndentAction; /** * Constructor for the Class. Sets up a window object for the UI @@ -461,6 +500,20 @@ public void handler(boolean value) { "Help", KeyEvent.VK_H, KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), mainUI); helpAboutAction = new HelpAboutAction("About ...", null, "Information about Rars", null, null, mainUI); + + // Nueva acción para el indentador + fileIndentAction = new GuiAction("Indent Code", null, + "Apply indentation to the current file", KeyEvent.VK_I, makeShortcut(KeyEvent.VK_I)) { + public void actionPerformed(ActionEvent e) { + EditPane editPane = mainPane.getEditPane(); + if (editPane != null) { + String currentContent = editPane.getSource(); + String indentedContent = Indenter.indentAssembly(currentContent); + editPane.setSourceCode(indentedContent, true); + editPane.setFileStatus(FileStatus.EDITED); // Marcar como editado + } + } + }; } catch (NullPointerException e) { System.out.println("Internal Error: images folder not found, or other null pointer exception while creating Action objects"); e.printStackTrace(); @@ -505,6 +558,7 @@ private JMenuBar setUpMenuBar() { fileSaveAll.setIcon(loadIcon("MyBlank16.gif")); fileDumpMemory = new JMenuItem(fileDumpMemoryAction); fileDumpMemory.setIcon(loadIcon("Dump16.png")); + fileIndent = new JMenuItem(fileIndentAction); // Add new item for indenting fileExit = new JMenuItem(fileExitAction); fileExit.setIcon(loadIcon("MyBlank16.gif")); file.add(fileNew); @@ -519,8 +573,14 @@ private JMenuBar setUpMenuBar() { file.add(fileDumpMemory); } file.addSeparator(); + file.add(fileIndent); + file.addSeparator(); file.add(fileExit); + + + + editUndo = new JMenuItem(editUndoAction); editUndo.setIcon(loadIcon("Undo16.png"));//"Undo16.gif")); editRedo = new JMenuItem(editRedoAction); From c2b41d656d7c905cd247340a6a38632e95f141da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Hinojosa=20Gil?= Date: Wed, 9 Jul 2025 19:45:07 +0200 Subject: [PATCH 2/2] pull request fixes --- src/rars/util/Indenter.java | 190 ++++++++++++++++++++++++++++++------ src/rars/venus/VenusUI.java | 69 +++---------- 2 files changed, 175 insertions(+), 84 deletions(-) diff --git a/src/rars/util/Indenter.java b/src/rars/util/Indenter.java index a587d2d1b..748c67fdc 100644 --- a/src/rars/util/Indenter.java +++ b/src/rars/util/Indenter.java @@ -6,17 +6,18 @@ /** * Basic indenter for RISC-V assembly code. - * Aligns labels, instructions/directives, and comments in fixed columns. + * Aligns labels and instructions using relative tab-based indentation. * Formatting rules: - * - Labels start at column 0 - * - Instructions start at column 20 - * - Comments start at column 40 + * - Instructions have n+1 tabs where n is the label's tab level + * - If label has 0 tabs, instructions get 1 tab + * - If label has 1 tab, instructions get 2 tabs, etc. + * - Tab size is 8 spaces (multiple of 8) + * - Comments align at column 40 */ public class Indenter { - // Column positions for alignment - private static final int LABEL_COLUMN = 0; // Column for labels - private static final int INSTRUCTION_COLUMN = 20; // Column for instructions - private static final int COMMENT_COLUMN = 40; // Column for comments + // Tab configuration + private static final int TAB_SIZE = 8; // Tab size in spaces (multiple of 8) + private static final int COMMENT_COLUMN = 40; // Column where comments should align /** * Indents a list of assembly code lines according to RISC-V conventions. @@ -25,28 +26,116 @@ public class Indenter { */ public List indent(List lines) { List indentedLines = new ArrayList<>(); + int currentLabelTabLevel = 0; // Track the tab level of the most recent label for (String line : lines) { String trimmedLine = line.trim(); // Handle empty lines and comments/directives - if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) { + if (trimmedLine.isEmpty() || trimmedLine.startsWith("#") || trimmedLine.startsWith("//")) { indentedLines.add(line); continue; } - // Handle directives + // Handle directives (align like instructions, except section directives) if (trimmedLine.startsWith(".")) { + // Check if it's a section directive that should always be at the start + boolean isSectionDirective = trimmedLine.startsWith(".data") || + trimmedLine.startsWith(".text") || + trimmedLine.startsWith(".bss") || + trimmedLine.startsWith(".rodata") || + trimmedLine.startsWith(".section"); + + // Find the first # that's not inside quotes (same logic as instructions) + String codePart; + String commentPart = ""; + + int commentIndex = -1; + boolean inQuotes = false; + char quoteChar = 0; + + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + + if (!inQuotes && (c == '"' || c == '\'')) { + inQuotes = true; + quoteChar = c; + } else if (inQuotes && c == quoteChar) { + // Check if it's escaped + if (i == 0 || line.charAt(i - 1) != '\\') { + inQuotes = false; + } + } else if (!inQuotes && c == '#') { + commentIndex = i; + break; + } + } + + if (commentIndex != -1) { + codePart = line.substring(0, commentIndex).trim(); + commentPart = line.substring(commentIndex); + } else { + codePart = line.trim(); + } + StringBuilder formattedLine = new StringBuilder(); - formattedLine.append(trimmedLine); + + if (isSectionDirective) { + // Section directives always start at column 0 + formattedLine.append(codePart); + } else { + // Other directives use currentLabelTabLevel + 1 for directives under a label + formattedLine.append("\t".repeat(currentLabelTabLevel + 1)); + formattedLine.append(codePart); + } + + // Add comment if present (aligned consistently) + if (!commentPart.isEmpty()) { + alignComment(formattedLine, commentPart); + } + indentedLines.add(formattedLine.toString()); continue; } - // Split line into components - String[] parts = line.split("#", 2); - String codePart = parts[0].trim(); - String commentPart = parts.length > 1 ? "#" + parts[1].trim() : ""; + // Split line into components (handle # in string literals) + String codePart; + String commentPart = ""; + + // Find the first # that's not inside quotes + int commentIndex = -1; + boolean inQuotes = false; + char quoteChar = 0; + + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + + if (!inQuotes && (c == '"' || c == '\'')) { + inQuotes = true; + quoteChar = c; + } else if (inQuotes && c == quoteChar) { + // Check if it's escaped + if (i == 0 || line.charAt(i - 1) != '\\') { + inQuotes = false; + } + } else if (!inQuotes && c == '#') { + commentIndex = i; + break; + } + } + + if (commentIndex != -1) { + codePart = line.substring(0, commentIndex).trim(); + commentPart = line.substring(commentIndex); // Preserve original comment spacing + } else { + codePart = line.trim(); + } + + // If it's just a comment line, keep it as is + if (codePart.isEmpty() && !commentPart.isEmpty()) { + indentedLines.add(line); + continue; + } // Process code part String[] tokens = codePart.split("\\s+"); @@ -54,23 +143,50 @@ public List indent(List lines) { String label = hasLabel ? tokens[0] : ""; String instruction = hasLabel ? (tokens.length > 1 ? tokens[1] : "") : (tokens.length > 0 ? tokens[0] : ""); String operands = hasLabel - ? String.join(" ", Arrays.copyOfRange(tokens, 2, tokens.length)) - : String.join(" ", Arrays.copyOfRange(tokens, 1, tokens.length)); + ? (tokens.length > 2 ? String.join(" ", Arrays.copyOfRange(tokens, 2, tokens.length)) : "") + : (tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens, 1, tokens.length)) : ""); // Build indented line StringBuilder formattedLine = new StringBuilder(); // Handle label if (!label.isEmpty()) { + // Determine label indentation level from the original line + int labelTabLevel = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) == '\t') { + labelTabLevel++; + } else if (line.charAt(i) == ' ') { + // Count spaces as tabs (8 spaces = 1 tab) + int spaces = 0; + while (i < line.length() && line.charAt(i) == ' ') { + spaces++; + i++; + } + labelTabLevel += spaces / TAB_SIZE; + break; + } else { + break; + } + } + + // Update the current label tab level for subsequent instructions + currentLabelTabLevel = labelTabLevel; + + // Add label indentation + formattedLine.append("\t".repeat(labelTabLevel)); formattedLine.append(label); - int spacesNeeded = INSTRUCTION_COLUMN - label.length() - 1; - if (spacesNeeded > 0) { - formattedLine.append(" ".repeat(spacesNeeded)); + + // If there's an instruction on the same line, it gets labelTabLevel + 1 tabs + if (!instruction.isEmpty()) { + formattedLine.append("\n"); + formattedLine.append("\t".repeat(labelTabLevel + 1)); } } else { - // Only add spaces if there's an instruction to align + // No label, this is just an instruction + // Use currentLabelTabLevel + 1 for instructions under a label if (!instruction.isEmpty()) { - formattedLine.append(" ".repeat(INSTRUCTION_COLUMN - 1)); + formattedLine.append("\t".repeat(currentLabelTabLevel + 1)); } } @@ -82,15 +198,9 @@ public List indent(List lines) { } } - // Add comment + // Add comment (aligned consistently) if (!commentPart.isEmpty()) { - int currentLength = formattedLine.length(); - if (currentLength < COMMENT_COLUMN) { - formattedLine.append(" ".repeat(COMMENT_COLUMN - currentLength)); - } else { - formattedLine.append(" "); - } - formattedLine.append(commentPart); + alignComment(formattedLine, commentPart); } indentedLines.add(formattedLine.toString()); @@ -98,6 +208,26 @@ public List indent(List lines) { return indentedLines; } + /** + * Aligns a comment at the specified column position. + * @param line The StringBuilder containing the current line + * @param comment The comment text to append (including the # symbol) + */ + private void alignComment(StringBuilder line, String comment) { + int currentLength = line.length(); + + if (currentLength >= COMMENT_COLUMN) { + // If line is already at or past comment column, add just one space + line.append(" "); + } else { + // Add spaces to reach the comment column + int spacesToAdd = COMMENT_COLUMN - currentLength; + line.append(" ".repeat(spacesToAdd)); + } + + line.append(comment); + } + /** * Convenience method to indent a complete assembly program string. * @param assemblyCode The raw assembly code as a single string with newlines diff --git a/src/rars/venus/VenusUI.java b/src/rars/venus/VenusUI.java index 6a7dc2c6e..2254c3645 100644 --- a/src/rars/venus/VenusUI.java +++ b/src/rars/venus/VenusUI.java @@ -1,37 +1,5 @@ package rars.venus; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.GraphicsEnvironment; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.net.URL; -import java.util.ArrayList; - -import javax.swing.Action; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSplitPane; -import javax.swing.JToolBar; -import javax.swing.KeyStroke; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.WindowConstants; - import rars.Globals; import rars.Settings; import rars.SimulationException; @@ -45,18 +13,14 @@ import rars.venus.registers.FloatingPointWindow; import rars.venus.registers.RegistersPane; import rars.venus.registers.RegistersWindow; -import rars.venus.run.RunAssembleAction; -import rars.venus.run.RunBackstepAction; -import rars.venus.run.RunClearBreakpointsAction; -import rars.venus.run.RunGoAction; -import rars.venus.run.RunResetAction; -import rars.venus.run.RunSpeedPanel; -import rars.venus.run.RunStepAction; -import rars.venus.settings.SettingsAction; -import rars.venus.settings.SettingsEditorAction; -import rars.venus.settings.SettingsExceptionHandlerAction; -import rars.venus.settings.SettingsHighlightingAction; -import rars.venus.settings.SettingsMemoryConfigurationAction; +import rars.venus.run.*; +import rars.venus.settings.*; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.net.URL; +import java.util.ArrayList; /** * Top level container for Venus GUI. @@ -93,7 +57,7 @@ public class VenusUI extends JFrame { // components of the menubar private JMenu file, run, window, help, edit, settings; private JMenuItem fileNew, fileOpen, fileClose, fileCloseAll, fileSave, fileSaveAs, fileSaveAll, fileDumpMemory, fileExit; - private JMenuItem editUndo, editRedo, editCut, editCopy, editPaste, editFindReplace, editSelectAll, fileIndent; + private JMenuItem editUndo, editRedo, editCut, editCopy, editPaste, editFindReplace, editSelectAll, editIndent; private JMenuItem runGo, runStep, runBackstep, runReset, runAssemble, runStop, runPause, runClearBreakpoints, runToggleBreakpoints; private JCheckBoxMenuItem settingsLabel, settingsValueDisplayBase, settingsAddressDisplayBase, settingsExtended, settingsAssembleOnOpen, settingsAssembleAll, settingsAssembleOpen, settingsWarningsAreErrors, @@ -127,7 +91,7 @@ public class VenusUI extends JFrame { private Action helpHelpAction, helpAboutAction; // Nueva acción para el indentador - private Action fileIndentAction; + private Action editIndentAction; /** * Constructor for the Class. Sets up a window object for the UI @@ -502,7 +466,7 @@ public void handler(boolean value) { "Information about Rars", null, null, mainUI); // Nueva acción para el indentador - fileIndentAction = new GuiAction("Indent Code", null, + editIndentAction = new GuiAction("Indent Code", null, "Apply indentation to the current file", KeyEvent.VK_I, makeShortcut(KeyEvent.VK_I)) { public void actionPerformed(ActionEvent e) { EditPane editPane = mainPane.getEditPane(); @@ -558,7 +522,6 @@ private JMenuBar setUpMenuBar() { fileSaveAll.setIcon(loadIcon("MyBlank16.gif")); fileDumpMemory = new JMenuItem(fileDumpMemoryAction); fileDumpMemory.setIcon(loadIcon("Dump16.png")); - fileIndent = new JMenuItem(fileIndentAction); // Add new item for indenting fileExit = new JMenuItem(fileExitAction); fileExit.setIcon(loadIcon("MyBlank16.gif")); file.add(fileNew); @@ -573,14 +536,8 @@ private JMenuBar setUpMenuBar() { file.add(fileDumpMemory); } file.addSeparator(); - file.add(fileIndent); - file.addSeparator(); file.add(fileExit); - - - - editUndo = new JMenuItem(editUndoAction); editUndo.setIcon(loadIcon("Undo16.png"));//"Undo16.gif")); editRedo = new JMenuItem(editRedoAction); @@ -595,6 +552,8 @@ private JMenuBar setUpMenuBar() { editFindReplace.setIcon(loadIcon("Find16.png"));//"Paste16.gif")); editSelectAll = new JMenuItem(editSelectAllAction); editSelectAll.setIcon(loadIcon("MyBlank16.gif")); + editIndent = new JMenuItem(editIndentAction); + editIndent.setIcon(loadIcon("MyBlank16.gif")); edit.add(editUndo); edit.add(editRedo); edit.addSeparator(); @@ -604,6 +563,8 @@ private JMenuBar setUpMenuBar() { edit.addSeparator(); edit.add(editFindReplace); edit.add(editSelectAll); + edit.addSeparator(); + edit.add(editIndent); runAssemble = new JMenuItem(runAssembleAction); runAssemble.setIcon(loadIcon("Assemble16.png"));//"MyAssemble16.gif"));