Implemented source code formatter

master
Marco Maccaferri 2018-12-22 17:13:30 +01:00
rodzic b0ca689ed2
commit c690fad7b7
8 zmienionych plików z 605 dodań i 12 usunięć

Wyświetl plik

@ -0,0 +1,122 @@
/*
* Copyright (c) 2018 Marco Maccaferri and others.
* All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.maccasoft.tools;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import nl.grauw.glass.Line;
import nl.grauw.glass.Parser;
import nl.grauw.glass.Scope;
import nl.grauw.glass.Source;
import nl.grauw.glass.SourceBuilder;
public class SourceFormatterTest {
int mnemonicColumn;
int argumentColumn;
int commentColumn;
@Before
public void setUp() {
mnemonicColumn = 0;
argumentColumn = 0;
commentColumn = 0;
}
@Test
public void testFormat() {
mnemonicColumn = 16;
argumentColumn = mnemonicColumn + 6;
commentColumn = mnemonicColumn + 16;
assertEquals(" exx\r\n", format(" exx"));
assertEquals(" ld a, b\r\n", format(" ld a,b"));
assertEquals("test ld a, b\r\n", format("test ld a,b"));
assertEquals(" exx ; test\r\n", format(" exx ; test"));
assertEquals("; test\r\n", format("; test"));
}
@Test
public void testDefaultFormat() {
assertEquals(" exx\r\n", format(" exx"));
assertEquals(" ld a, b\r\n", format(" ld a,b"));
assertEquals("test ld a, b\r\n", format("test ld a,b"));
assertEquals(" exx ; test\r\n", format(" exx ; test"));
assertEquals("; test\r\n", format("; test"));
}
@Test
public void testFormatNumber() {
assertEquals("test ld a, 24\r\n", format("test ld a,24"));
assertEquals("test ld a, 08H\r\n", format("test ld a,8h"));
assertEquals("test ld hl, 01F0H\r\n", format("test ld hl,1f0h"));
}
@Test
public void testFormatExpression() throws Exception {
assertEquals("a", parse("a"));
assertEquals("a + 01H", parse("a + 1H"));
assertEquals("a + (01H + 02H) * 03H", parse("a + (1H + 2H) * 3H"));
assertEquals("a ? 01H : 02H", parse("a ? 1H : 2H"));
}
@Test
public void testFormatMnemonicCase() throws Exception {
StringBuilder builder = new StringBuilder();
builder.append("test ld a,8h").append("\n");
SourceBuilder sourceBuilder = new SourceBuilder(new ArrayList<File>());
Source source = sourceBuilder.parse(new StringReader(builder.toString()), null);
SourceFormatter formatter = new SourceFormatter(source);
formatter.mnemonicCase = SourceFormatter.TO_UPPER;
assertEquals("test LD A, 08H\r\n", formatter.format());
}
@Test
public void testFormatLabelCase() throws Exception {
StringBuilder builder = new StringBuilder();
builder.append("test ld hl,test").append("\n");
SourceBuilder sourceBuilder = new SourceBuilder(new ArrayList<File>());
Source source = sourceBuilder.parse(new StringReader(builder.toString()), null);
SourceFormatter formatter = new SourceFormatter(source);
formatter.labelCase = SourceFormatter.TO_UPPER;
assertEquals("TEST ld hl, TEST\r\n", formatter.format());
}
public String format(String... sourceLines) {
StringBuilder builder = new StringBuilder();
for (String lineText : sourceLines) {
builder.append(lineText).append("\n");
}
SourceBuilder sourceBuilder = new SourceBuilder(new ArrayList<File>());
Source source = sourceBuilder.parse(new StringReader(builder.toString()), null);
SourceFormatter formatter = new SourceFormatter(source);
formatter.mnemonicColumn = mnemonicColumn;
formatter.argumentColumn = argumentColumn;
formatter.commentColumn = commentColumn;
return formatter.format();
}
public String parse(String text) {
LineNumberReader reader = new LineNumberReader(new StringReader(" test " + text));
Line line = new Parser().parse(reader, new Scope(), null);
return new SourceFormatter(null).formatExpression(line.getArguments());
}
}

Wyświetl plik

@ -17,6 +17,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
@ -86,6 +87,8 @@ import nl.grauw.glass.AssemblyException;
import nl.grauw.glass.Line;
import nl.grauw.glass.Source;
import nl.grauw.glass.SourceBuilder;
import nl.grauw.glass.directives.Directive;
import nl.grauw.glass.directives.Include;
public class Application {
@ -687,6 +690,56 @@ public class Application {
item.setText("&Tools");
item.setMenu(menu);
item = new MenuItem(menu, SWT.PUSH);
item.setText("Format source\tCtrl+Shift+F");
item.setAccelerator(SWT.MOD1 + +SWT.MOD2 + 'F');
item.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event e) {
try {
CTabItem tabItem = tabFolder.getSelection();
if (tabItem == null) {
return;
}
SourceEditorTab tab = (SourceEditorTab) tabItem.getData();
StringReader reader = new StringReader(tab.getEditor().getText());
SourceBuilder builder = new SourceBuilder(new ArrayList<File>()) {
@Override
public Directive getDirective(Line line, LineNumberReader reader, File sourceFile) {
if (line.getMnemonic() != null) {
switch (line.getMnemonic()) {
case "include":
case "INCLUDE":
case ".include":
case ".INCLUDE":
return new Include();
}
}
return super.getDirective(line, reader, sourceFile);
}
};
SourceFormatter formatter = new SourceFormatter(builder.parse(reader, new File("")));
formatter.setMnemonicColumn(preferences.getMnemonicColumn());
formatter.setArgumentColumn(preferences.getArgumentColumn());
formatter.setCommentColumn(preferences.getCommentColumn());
formatter.setLabelCase(preferences.getLabelCase());
formatter.setMnemonicCase(preferences.getMnemonicCase());
tab.getEditor().replaceText(formatter.format());
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
new MenuItem(menu, SWT.SEPARATOR);
item = new MenuItem(menu, SWT.PUSH);
item.setText("Verify / Compile\tCtrl+R");
item.setAccelerator(SWT.MOD1 + 'R');

Wyświetl plik

@ -62,6 +62,12 @@ public class Preferences {
String[] openTabs;
String selectedTab;
int mnemonicColumn;
int argumentColumn;
int commentColumn;
int labelCase;
int mnemonicCase;
boolean generateBinary;
boolean generateHex;
boolean generateListing;
@ -77,6 +83,12 @@ public class Preferences {
showLineNumbers = true;
reloadOpenTabs = true;
mnemonicColumn = 16;
argumentColumn = mnemonicColumn + 6;
commentColumn = mnemonicColumn + 40;
labelCase = SourceFormatter.NO_CHANGE;
mnemonicCase = SourceFormatter.TO_UPPER;
generateHex = true;
generateListing = true;
@ -159,6 +171,46 @@ public class Preferences {
this.selectedTab = selectedTab;
}
public int getMnemonicColumn() {
return mnemonicColumn;
}
public void setMnemonicColumn(int mnemonicColumn) {
this.mnemonicColumn = mnemonicColumn;
}
public int getArgumentColumn() {
return argumentColumn;
}
public void setArgumentColumn(int argumentColumn) {
this.argumentColumn = argumentColumn;
}
public int getCommentColumn() {
return commentColumn;
}
public void setCommentColumn(int commentColumn) {
this.commentColumn = commentColumn;
}
public int getLabelCase() {
return labelCase;
}
public void setLabelCase(int labelCase) {
this.labelCase = labelCase;
}
public int getMnemonicCase() {
return mnemonicCase;
}
public void setMnemonicCase(int mnemonicCase) {
this.mnemonicCase = mnemonicCase;
}
public boolean isGenerateBinary() {
return generateBinary;
}

Wyświetl plik

@ -22,6 +22,7 @@ import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
@ -46,6 +47,12 @@ public class PreferencesDialog extends Dialog {
Button showLineNumbers;
Button reloadOpenTabs;
Text mnemonicColumn;
Text argumentColumn;
Text commentColumn;
Combo labelCase;
Combo mnemonicCase;
Button generateBinary;
Button generateHex;
Button generateListing;
@ -132,6 +139,10 @@ public class PreferencesDialog extends Dialog {
addSeparator(composite);
createFormatterGroup(composite);
addSeparator(composite);
label = new Label(composite, SWT.NONE);
label.setText("Compiler output");
@ -155,9 +166,6 @@ public class PreferencesDialog extends Dialog {
addSeparator(composite);
label = new Label(composite, SWT.NONE);
label.setLayoutData(new GridData(SWT.DEFAULT, convertHeightInCharsToPixels(5)));
return composite;
}
@ -277,6 +285,46 @@ public class PreferencesDialog extends Dialog {
rootMoveDown.setEnabled(index != -1 && index < (roots.getItemCount() - 1));
}
void createFormatterGroup(Composite parent) {
Label label = new Label(parent, SWT.NONE);
label.setText("Mnemonic column");
mnemonicColumn = new Text(parent, SWT.BORDER);
mnemonicColumn.setLayoutData(new GridData(convertWidthInCharsToPixels(3), SWT.DEFAULT));
mnemonicColumn.setText(String.valueOf(preferences.getMnemonicColumn()));
label = new Label(parent, SWT.NONE);
label.setText("Arguments column");
argumentColumn = new Text(parent, SWT.BORDER);
argumentColumn.setLayoutData(new GridData(convertWidthInCharsToPixels(3), SWT.DEFAULT));
argumentColumn.setText(String.valueOf(preferences.getArgumentColumn()));
label = new Label(parent, SWT.NONE);
label.setText("Comment column");
commentColumn = new Text(parent, SWT.BORDER);
commentColumn.setLayoutData(new GridData(convertWidthInCharsToPixels(3), SWT.DEFAULT));
commentColumn.setText(String.valueOf(preferences.getCommentColumn()));
label = new Label(parent, SWT.NONE);
label.setText("Label case");
labelCase = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
labelCase.setItems(new String[] {
"No change",
"Upper",
"Lower",
});
labelCase.select(preferences.getLabelCase());
label = new Label(parent, SWT.NONE);
label.setText("Mnemonic case");
mnemonicCase = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
mnemonicCase.setItems(new String[] {
"No change",
"Upper",
"Lower",
});
mnemonicCase.select(preferences.getMnemonicCase());
}
void addSeparator(Composite parent) {
Label label = new Label(parent, SWT.NONE);
label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false, ((GridLayout) parent.getLayout()).numColumns, 1));
@ -291,6 +339,12 @@ public class PreferencesDialog extends Dialog {
preferences.setShowLineNumbers(showLineNumbers.getSelection());
preferences.setReloadOpenTabs(reloadOpenTabs.getSelection());
preferences.setMnemonicColumn(Integer.valueOf(mnemonicColumn.getText()));
preferences.setArgumentColumn(Integer.valueOf(argumentColumn.getText()));
preferences.setCommentColumn(Integer.valueOf(commentColumn.getText()));
preferences.setLabelCase(labelCase.getSelectionIndex());
preferences.setMnemonicCase(mnemonicCase.getSelectionIndex());
preferences.setGenerateBinary(generateBinary.getSelection());
preferences.setGenerateHex(generateHex.getSelection());
preferences.setGenerateListing(generateListing.getSelection());

Wyświetl plik

@ -0,0 +1,262 @@
/*
* Copyright (c) 2018 Marco Maccaferri and others.
* All rights reserved.
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.maccasoft.tools;
import nl.grauw.glass.AssemblyException;
import nl.grauw.glass.Line;
import nl.grauw.glass.Source;
import nl.grauw.glass.directives.If;
import nl.grauw.glass.expressions.Annotation;
import nl.grauw.glass.expressions.BinaryOperator;
import nl.grauw.glass.expressions.Expression;
import nl.grauw.glass.expressions.Flag;
import nl.grauw.glass.expressions.FlagOrRegister;
import nl.grauw.glass.expressions.Group;
import nl.grauw.glass.expressions.Identifier;
import nl.grauw.glass.expressions.IfElse;
import nl.grauw.glass.expressions.Index;
import nl.grauw.glass.expressions.Member;
import nl.grauw.glass.expressions.Register;
import nl.grauw.glass.expressions.Sequence;
import nl.grauw.glass.expressions.UnaryOperator;
public class SourceFormatter {
public static final int NO_CHANGE = 0;
public static final int TO_UPPER = 1;
public static final int TO_LOWER = 2;
Source source;
int mnemonicColumn;
int argumentColumn;
int commentColumn;
int labelCase;
int mnemonicCase;
int column;
StringBuilder sb;
public SourceFormatter(Source source) {
this.source = source;
}
public int getMnemonicColumn() {
return mnemonicColumn;
}
public void setMnemonicColumn(int mnemonicColumn) {
this.mnemonicColumn = mnemonicColumn;
}
public int getArgumentColumn() {
return argumentColumn;
}
public void setArgumentColumn(int argumentColumn) {
this.argumentColumn = argumentColumn;
}
public int getCommentColumn() {
return commentColumn;
}
public void setCommentColumn(int commentColumn) {
this.commentColumn = commentColumn;
}
public int getLabelCase() {
return labelCase;
}
public void setLabelCase(int labelCase) {
this.labelCase = labelCase;
}
public int getMnemonicCase() {
return mnemonicCase;
}
public void setMnemonicCase(int mnemonicCase) {
this.mnemonicCase = mnemonicCase;
}
public String format() {
sb = new StringBuilder();
format(source);
return sb.toString();
}
void format(Source source) {
for (Line line : source.getLines()) {
try {
column = 0;
if (line.getLabel() != null) {
if (labelCase == TO_UPPER) {
sb.append(line.getLabel().toUpperCase());
}
else if (labelCase == TO_LOWER) {
sb.append(line.getLabel().toLowerCase());
}
else {
sb.append(line.getLabel());
}
column = line.getLabel().length();
}
if (line.getMnemonic() != null) {
if (column == 0) {
sb.append(' ');
column++;
}
while (column < mnemonicColumn) {
sb.append(' ');
column++;
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
sb.append(' ');
}
if (mnemonicCase == TO_UPPER) {
sb.append(line.getMnemonic().toUpperCase());
}
else if (mnemonicCase == TO_LOWER) {
sb.append(line.getMnemonic().toLowerCase());
}
else {
sb.append(line.getMnemonic());
}
column += line.getMnemonic().length();
if (line.getArguments() != null) {
while (column < argumentColumn) {
sb.append(' ');
column++;
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
sb.append(' ');
}
String s = formatExpression(line.getArguments());
sb.append(s);
column += s.length();
}
}
if (column != 0) {
if (line.getComment() != null) {
while (column < commentColumn) {
sb.append(' ');
column++;
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
sb.append(' ');
}
sb.append(';');
if (!line.getComment().startsWith(" ")) {
sb.append(' ');
}
sb.append(line.getComment());
}
}
else {
sb.append(line.getSourceText());
}
sb.append("\r\n");
if (line.getDirective() instanceof If) {
If ins = (If) line.getDirective();
format(ins.getThenSource());
if (ins.getElseSource() != null) {
format(ins.getElseSource());
}
}
} catch (AssemblyException e) {
e.addContext(line);
throw e;
}
}
}
String formatExpression(Expression e) {
if (e instanceof Annotation) {
return "" + formatExpression(((Annotation) e).getAnnotation()) + " " + formatExpression(((Annotation) e).getAnnotee());
}
if (e instanceof Group) {
return "(" + formatExpression(((Group) e).getTerm()) + ")";
}
if (e instanceof Sequence) {
Sequence op = (Sequence) e;
return formatExpression(op.getTerm1()) + ", " + formatExpression(op.getTerm2());
}
if (e instanceof UnaryOperator) {
UnaryOperator op = (UnaryOperator) e;
return op.getLexeme() + formatExpression(op.getTerm());
}
if (e instanceof BinaryOperator) {
BinaryOperator op = (BinaryOperator) e;
return formatExpression(op.getTerm1()) + " " + op.getLexeme() + " " + formatExpression(op.getTerm2());
}
if (e instanceof IfElse) {
IfElse op = (IfElse) e;
return formatExpression(op.getCondition()) + " ? " + formatExpression(op.getTrueTerm()) + " : " + formatExpression(op.getFalseTerm());
}
if (e instanceof Index) {
Index op = (Index) e;
return formatExpression(op.getSequence()) + "[" + formatExpression(op.getIndex()) + "]";
}
if (e instanceof Member) {
Member op = (Member) e;
return formatExpression(op.getObject()) + "." + formatExpression(op.getSubject()) + "]";
}
if (e instanceof Identifier) {
if (((Identifier) e).isRegister()) {
if (mnemonicCase == TO_UPPER) {
return e.toString().toUpperCase();
}
else if (mnemonicCase == TO_LOWER) {
return e.toString().toLowerCase();
}
else {
return e.toString();
}
}
else {
if (labelCase == TO_UPPER) {
return e.toString().toUpperCase();
}
else if (labelCase == TO_LOWER) {
return e.toString().toLowerCase();
}
else {
return e.toString();
}
}
}
if ((e instanceof Register) || (e instanceof Flag) || (e instanceof FlagOrRegister)) {
if (mnemonicCase == TO_UPPER) {
return e.toString().toUpperCase();
}
else if (mnemonicCase == TO_LOWER) {
return e.toString().toLowerCase();
}
else {
return e.toString();
}
}
return e.toString();
}
}

Wyświetl plik

@ -594,6 +594,21 @@ public class SourceEditor {
this.text.addCaretListener(caretListener);
}
public void replaceText(String text) {
int offset = this.text.getCaretOffset();
int line = this.text.getLineAtOffset(offset);
int topindex = line - this.text.getTopIndex();
text = EditorUtil.replaceTabs(text, this.text.getTabs());
this.text.setText(text);
if (line > this.text.getLineCount()) {
line = this.text.getLineCount() - 1;
}
this.text.setTopIndex(line - topindex);
this.text.setCaretOffset(this.text.getOffsetAtLine(line));
}
public String getText() {
String s = text.getText();
String s2 = s.replaceAll("[ \\t]+(\r\n|\n|\r)", "$1");

Wyświetl plik

@ -429,25 +429,25 @@ public class Parser {
String string = accumulator.toString();
if (character == 'H' || character == 'h') {
int value = parseInt(string, 16);
expressionBuilder.addValueToken(new IntegerLiteral(value));
expressionBuilder.addValueToken(new IntegerLiteral(value, 16));
accumulator.setLength(0);
return argumentOperatorState;
}
else if (character == 'O' || character == 'o') {
int value = parseInt(string, 8);
expressionBuilder.addValueToken(new IntegerLiteral(value));
expressionBuilder.addValueToken(new IntegerLiteral(value, 8));
accumulator.setLength(0);
return argumentOperatorState;
}
else {
if (string.endsWith("B") || string.endsWith("b")) {
int value = parseInt(string.substring(0, string.length() - 1), 2);
expressionBuilder.addValueToken(new IntegerLiteral(value));
expressionBuilder.addValueToken(new IntegerLiteral(value, 2));
accumulator.setLength(0);
}
else {
int value = parseInt(string, 10);
expressionBuilder.addValueToken(new IntegerLiteral(value));
expressionBuilder.addValueToken(new IntegerLiteral(value, 10));
accumulator.setLength(0);
}
return argumentOperatorState.parse(character);
@ -484,7 +484,7 @@ public class Parser {
}
else {
int value = parseInt(accumulator.toString(), 16);
expressionBuilder.addValueToken(new IntegerLiteral(value));
expressionBuilder.addValueToken(new IntegerLiteral(value, 16));
accumulator.setLength(0);
return argumentOperatorState.parse(character);
}
@ -501,7 +501,7 @@ public class Parser {
}
else {
int value = parseInt(accumulator.toString(), 2);
expressionBuilder.addValueToken(new IntegerLiteral(value));
expressionBuilder.addValueToken(new IntegerLiteral(value, 2));
accumulator.setLength(0);
return argumentOperatorState.parse(character);
}

Wyświetl plik

@ -6,9 +6,16 @@ public class IntegerLiteral extends Literal {
public static final IntegerLiteral ONE = new IntegerLiteral(1);
private final int value;
private final int base;
public IntegerLiteral(int value) {
this.value = value;
this.base = 10;
}
public IntegerLiteral(int value, int base) {
this.value = value;
this.base = base;
}
@Override
@ -26,15 +33,43 @@ public class IntegerLiteral extends Literal {
return value;
}
public int getBase() {
return base;
}
@Override
public String toString() {
String string = Integer.toHexString(value).toUpperCase();
return (string.charAt(0) >= 'A' && string.charAt(0) <= 'F' ? "0" : "") + string + "H";
String string = Integer.toString(value, base).toUpperCase();
switch (base) {
case 2:
if (string.length() > 8) {
while (string.length() < 16) {
string = "0" + string;
}
}
else {
while (string.length() < 8) {
string = "0" + string;
}
}
return string + "B";
case 8:
return string + "H";
case 16:
if (string.length() == 1 || string.length() == 3) {
string = "0" + string;
}
return (string.charAt(0) >= 'A' && string.charAt(0) <= 'F' ? "0" : "") + string + "H";
}
return string;
}
@Override
public String toDebugString() {
return toString();
String string = Integer.toHexString(value).toUpperCase();
return (string.charAt(0) >= 'A' && string.charAt(0) <= 'F' ? "0" : "") + string + "H";
}
}