Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions src/rars/util/Indenter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package rars.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* Basic indenter for RISC-V assembly code.
* Aligns labels and instructions using relative tab-based indentation.
* Formatting rules:
* - 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 {
// 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.
* @param lines List of raw assembly code lines
* @return List of properly indented lines
*/
public List<String> indent(List<String> lines) {
List<String> 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("#") || trimmedLine.startsWith("//")) {
indentedLines.add(line);
continue;
}

// 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();

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 (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+");
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
? (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);

// 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 {
// No label, this is just an instruction
// Use currentLabelTabLevel + 1 for instructions under a label
if (!instruction.isEmpty()) {
formattedLine.append("\t".repeat(currentLabelTabLevel + 1));
}
}

// Handle instruction and operands
if (!instruction.isEmpty()) {
formattedLine.append(instruction);
if (!operands.isEmpty()) {
formattedLine.append(" ").append(operands);
}
}

// Add comment (aligned consistently)
if (!commentPart.isEmpty()) {
alignComment(formattedLine, commentPart);
}

indentedLines.add(formattedLine.toString());
}
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
* @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"))));
}
}
23 changes: 22 additions & 1 deletion src/rars/venus/VenusUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -56,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;
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,
Expand Down Expand Up @@ -91,6 +92,8 @@ public class VenusUI extends JFrame {
settingsDisplayRegisterNumbersAction;
private Action helpHelpAction, helpAboutAction;

// Nueva acción para el indentador
private Action editIndentAction;

/**
* Constructor for the Class. Sets up a window object for the UI
Expand Down Expand Up @@ -466,6 +469,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
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();
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();
Expand Down Expand Up @@ -540,6 +557,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();
Expand All @@ -549,6 +568,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"));
Expand Down
Loading