Implemented compiler with hex and listing generators

master
Marco Maccaferri 2018-12-18 09:32:32 +01:00
rodzic 210919aa89
commit d23322ba26
3 zmienionych plików z 641 dodań i 8 usunięć

Wyświetl plik

@ -0,0 +1,120 @@
/*
* 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 java.io.OutputStream;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import junit.framework.TestCase;
public class ConsoleTest extends TestCase {
Display display;
Shell shell;
Console console;
@Override
protected void setUp() throws Exception {
display = Display.getDefault();
shell = new Shell(display);
console = new Console(shell);
}
@Override
protected void tearDown() throws Exception {
shell.dispose();
display.dispose();
}
public void testWriteToOutputStream() throws Exception {
OutputStream os = console.getOutputStream();
os.write("Line 1\r\nLine 2\r\n".getBytes());
assertEquals("Line 1\nLine 2\n", console.text.getText());
assertNull(console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(0)));
assertNull(console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(1)));
}
public void testWriteToErrorStream() throws Exception {
OutputStream os = console.getErrorStream();
os.write("Line 1\r\nLine 2\r\n".getBytes());
assertEquals("Line 1\nLine 2\n", console.text.getText());
StyleRange range0 = console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(0));
assertEquals(console.errorColor, range0.foreground);
StyleRange range1 = console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(1));
assertEquals(console.errorColor, range1.foreground);
}
public void testWriteToBothStreams() throws Exception {
console.getOutputStream().write("Line 1\r\n".getBytes());
console.getErrorStream().write("Line 2\r\n".getBytes());
assertEquals("Line 1\nLine 2\n", console.text.getText());
StyleRange range0 = console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(0));
assertNull(range0);
StyleRange range1 = console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(1));
assertEquals(console.errorColor, range1.foreground);
}
public void testWriteLineWithCRLF() throws Exception {
OutputStream os = console.getOutputStream();
os.write("Line\r\n".getBytes());
assertEquals("Line\n", console.text.getText());
}
public void testWriteLineWithCR() throws Exception {
OutputStream os = console.getOutputStream();
os.write("Line 1\r".getBytes());
assertEquals("Line 1", console.text.getText());
os.write("Line 2\r".getBytes());
assertEquals("Line 2", console.text.getText());
}
public void testWriteBlankLine() throws Exception {
OutputStream os = console.getOutputStream();
os.write("Line 1\r\n".getBytes());
assertEquals("Line 1\n", console.text.getText());
os.write("\n".getBytes());
assertEquals("Line 1\n\n", console.text.getText());
os.write("Line 2\r".getBytes());
assertEquals("Line 1\n\nLine 2", console.text.getText());
}
public void testWriteUnterminatedLine() throws Exception {
OutputStream os = console.getOutputStream();
os.write("Line 1\r\nLine 2".getBytes());
assertEquals("Line 1\nLine 2", console.text.getText());
}
public void testWriteOutputStreamErrorLine() throws Exception {
OutputStream os = console.getOutputStream();
os.write("Line 1\r\nError : Line 2\r\n".getBytes());
assertEquals("Line 1\nError : Line 2\n", console.text.getText());
StyleRange range0 = console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(0));
assertNull(range0);
StyleRange range1 = console.text.getStyleRangeAtOffset(console.text.getOffsetAtLine(1));
assertEquals(console.errorColor, range1.foreground);
}
}

Wyświetl plik

@ -11,14 +11,20 @@
package com.maccasoft.tools;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@ -36,6 +42,7 @@ import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuEvent;
@ -45,6 +52,7 @@ import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
@ -59,6 +67,11 @@ import org.eclipse.swt.widgets.Shell;
import com.maccasoft.tools.internal.ImageRegistry;
import nl.grauw.glass.AssemblyException;
import nl.grauw.glass.Line;
import nl.grauw.glass.Source;
import nl.grauw.glass.SourceBuilder;
public class Application {
public static final String APP_TITLE = "Z80 Tools";
@ -67,9 +80,11 @@ public class Application {
Display display;
Shell shell;
SashForm sashForm;
SashForm sashForm1;
FileBrowser browser;
SashForm sashForm2;
CTabFolder tabFolder;
Console console;
Preferences preferences;
@ -101,7 +116,7 @@ public class Application {
Rectangle screen = display.getClientArea();
Rectangle rect = new Rectangle(0, 0, (int) ((float) screen.width / (float) screen.height * 800), 800);
Rectangle rect = new Rectangle(0, 0, (int) ((float) screen.width / (float) screen.height * 800), 850);
rect.x = (screen.width - rect.width) / 2;
rect.y = (screen.height - rect.height) / 2;
if (rect.y < 0) {
@ -455,7 +470,11 @@ public class Application {
@Override
public void handleEvent(Event e) {
try {
handleCompile();
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
@ -507,15 +526,18 @@ public class Application {
FontMetrics fontMetrics = gc.getFontMetrics();
gc.dispose();
sashForm = new SashForm(parent, SWT.HORIZONTAL);
sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
sashForm1 = new SashForm(parent, SWT.HORIZONTAL);
sashForm1.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
browser = new FileBrowser(sashForm);
browser = new FileBrowser(sashForm1);
browser.setRoots(new File[] {
new File(System.getProperty("user.home"))
});
tabFolder = new CTabFolder(sashForm, SWT.BORDER);
sashForm2 = new SashForm(sashForm1, SWT.VERTICAL);
sashForm2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
tabFolder = new CTabFolder(sashForm2, SWT.BORDER);
tabFolder.setTabHeight((int) (fontMetrics.getHeight() * 1.5));
tabFolder.addSelectionListener(new SelectionAdapter() {
@ -533,9 +555,79 @@ public class Application {
}
});
sashForm.setWeights(new int[] {
console = new Console(sashForm2);
console.getStyledText().addListener(SWT.MouseDown, new Listener() {
@Override
public void handleEvent(Event event) {
try {
StyledText control = (StyledText) event.widget;
int offset = control.getOffsetAtLocation(new Point(event.x, event.y));
String line = control.getLine(control.getLineAtOffset(offset));
if (line.toLowerCase().contains(" error : ")) {
int s = 0;
int e = line.indexOf(')');
if (e != -1) {
line = line.substring(s, e + 1);
s = 0;
e = line.indexOf('(');
String name = line.substring(s, e);
s = e + 1;
if ((e = line.indexOf(':', s)) == -1) {
e = line.indexOf(',', s);
}
int row = Integer.parseInt(line.substring(s, e)) - 1;
if (row < 0) {
row = 0;
}
s = e + 1;
e = line.indexOf(')', s);
int column = Integer.parseInt(line.substring(s, e)) - 1;
if (column < 0) {
column = 0;
}
switchToEditor(name, row, column);
}
}
else if (line.toLowerCase().contains(" error: ") || line.toLowerCase().contains(" warning: ")) {
int e = line.indexOf(':');
String name = line.substring(0, e);
int s = e + 1;
e = line.indexOf(':', s);
int row = Integer.parseInt(line.substring(s, e)) - 1;
s = e + 1;
int column = 0;
if (line.charAt(s) != ' ') {
e = line.indexOf(':', s);
column = Integer.parseInt(line.substring(s, e)) - 1;
}
switchToEditor(name, row, column);
}
} catch (Exception e) {
e.printStackTrace();
}
}
void switchToEditor(String name, int line, int column) {
CTabItem[] tabItem = tabFolder.getItems();
for (int i = 0; i < tabItem.length; i++) {
SourceEditorTab tab = (SourceEditorTab) tabItem[i].getData();
if (name.equalsIgnoreCase(tab.getText())) {
tab.getEditor().gotToLineColumn(line, column);
tab.setFocus();
break;
}
}
}
});
sashForm1.setWeights(new int[] {
20, 80
});
sashForm2.setWeights(new int[] {
80, 20
});
String lastPath = preferences.getLastPath();
if (lastPath == null) {
@ -785,6 +877,227 @@ public class Application {
return true;
}
private void handleCompile() throws Exception {
CTabItem tabItem = tabFolder.getSelection();
if (tabItem == null) {
return;
}
SourceEditorTab tab = (SourceEditorTab) tabItem.getData();
console.clear();
console.getOutputStream().write(new String("Compiling " + tab.getText() + "...").getBytes());
List<File> includePaths = new ArrayList<File>();
if (tab.getFile() != null) {
includePaths.add(tab.getFile().getParentFile());
}
final SourceBuilder builder = new SourceBuilder(includePaths);
final StringReader reader = new StringReader(tab.getEditor().getText());
final String name = tab.getText();
final File file = tab.getFile();
new Thread(new Runnable() {
@Override
public void run() {
compile(builder, reader, name, file);
}
}).start();
}
Source compile(SourceBuilder builder, Reader reader, String name, File file) {
PrintStream out = new PrintStream(console.getOutputStream());
PrintStream err = new PrintStream(console.getErrorStream());
try {
Source source = builder.parse(reader, new File(name));
source.register();
source.expand();
source.resolve();
if (file != null) {
String baseName = name;
if (baseName.indexOf('.') != -1) {
baseName = baseName.substring(0, baseName.lastIndexOf('.'));
}
OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(new File(file.getParentFile(), baseName + ".LST")));
os.write(buildListing(new ArrayList<Line>(source.getLines())).toString());
os.close();
os = new OutputStreamWriter(new FileOutputStream(new File(file.getParentFile(), baseName + ".HEX")));
os.write(buildIntelHexString(new ArrayList<Line>(source.getLines())).toString());
os.close();
}
int lower = Integer.MAX_VALUE;
int higher = Integer.MIN_VALUE;
for (Line line : source.getLines()) {
if (line.getSize() != 0) {
lower = Math.min(lower, line.getScope().getAddress());
higher = Math.max(higher, line.getScope().getAddress());
}
}
out.println();
out.println(String.format("Compiled %d lines from %04XH to %04XH (%d bytes)", source.getLines().size(), lower, higher, higher - lower + 1));
return source;
} catch (AssemblyException ex) {
StringBuilder sb = new StringBuilder();
Iterator<AssemblyException.Context> iter = ex.contexts.iterator();
if (iter.hasNext()) {
AssemblyException.Context context = iter.next();
sb.append(context.file.getName());
sb.append(":");
sb.append(context.line + 1);
if (context.column != -1) {
sb.append(":");
sb.append(context.column);
}
sb.append(": error: ");
sb.append(ex.getPlainMessage());
}
out.println();
err.println(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
StringBuilder buildListing(List<Line> lines) throws IOException {
int column;
StringBuilder sb = new StringBuilder();
for (Line line : lines) {
try {
int address = line.getScope().getAddress();
byte[] code = line.getBytes();
sb.append(String.format("%05d %04X ", line.getLineNumber() + 1, address));
column = 0;
int codeIndex = 0;
for (int i = 0; codeIndex < code.length && (column + 3) < 24; i++, codeIndex++, address++) {
if (i != 0) {
sb.append(' ');
column++;
}
sb.append(String.format("%02X", code[codeIndex]));
column += 2;
}
while (column < 24 + 1) {
sb.append(' ');
column++;
}
sb.append(line.getSourceText());
while (codeIndex < code.length) {
sb.append("\r\n");
sb.append(String.format("%05d %04X ", line.getLineNumber() + 1, address));
column = 0;
for (int i = 0; codeIndex < code.length && (column + 3) < 24; i++, codeIndex++, address++) {
if (i != 0) {
sb.append(' ');
column++;
}
sb.append(String.format("%02X", code[codeIndex]));
column += 2;
}
}
sb.append("\r\n");
} catch (AssemblyException e) {
e.addContext(line);
throw e;
}
}
return sb;
}
StringBuilder buildIntelHexString(List<Line> lines) throws IOException {
int addr = -1;
int nextAddr = -1;
StringBuilder sb = new StringBuilder();
Collections.sort(lines, new Comparator<Line>() {
@Override
public int compare(Line o1, Line o2) {
return o1.getScope().getAddress() - o2.getScope().getAddress();
}
});
ByteArrayOutputStream os = new ByteArrayOutputStream();
for (Line line : lines) {
try {
byte[] code = line.getBytes();
if (code.length == 0) {
continue;
}
if (line.getScope().getAddress() != nextAddr) {
os.close();
byte[] data = os.toByteArray();
if (data.length != 0) {
sb.append(toHexString(addr, data));
}
nextAddr = addr = line.getScope().getAddress();
os = new ByteArrayOutputStream();
}
os.write(code);
nextAddr += code.length;
} catch (AssemblyException e) {
e.addContext(line);
throw e;
}
}
os.close();
byte[] data = os.toByteArray();
if (data.length != 0) {
sb.append(toHexString(addr, data));
}
sb.append(":00000001FF\r\n");
return sb;
}
static String toHexString(int addr, byte[] data) {
StringBuilder sb = new StringBuilder();
int i = 0;
while ((data.length - i) > 0) {
int l = data.length - i;
if (l > 24) {
l = 24;
}
sb.append(String.format(":%02X%04X%02X", l, addr, 0));
int checksum = l + (addr & 0xFF) + ((addr >> 8) & 0xFF) + 0;
for (int n = 0; n < l; n++, i++, addr++) {
sb.append(String.format("%02X", data[i]));
checksum += data[i];
}
sb.append(String.format("%02X\r\n", (-checksum) & 0xFF));
}
return sb.toString();
}
static {
System.setProperty("SWT_GTK3", "0");
}

Wyświetl plik

@ -0,0 +1,200 @@
/*
* 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 java.io.IOException;
import java.io.OutputStream;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
public class Console {
Composite container;
StyledText text;
ConsoleOutputStream outputStream;
ConsoleOutputStream errorStream;
Font font;
Color errorColor;
private class ConsoleOutputStream extends OutputStream {
boolean isError;
int moreLinesToMark;
StringBuilder lineBuilder;
public ConsoleOutputStream(boolean isError) {
this.isError = isError;
this.moreLinesToMark = 0;
lineBuilder = new StringBuilder();
}
@Override
public void write(final int b) throws IOException {
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
appendBytes(b, off, len);
}
});
}
void appendBytes(byte[] b, int off, int len) {
for (int i = 0; i < len; i++) {
char c = (char) b[off + i];
if (c == '\r') {
int ofs = getLastLineOffset();
String s = lineBuilder.toString();
text.replaceTextRange(ofs, text.getCharCount() - ofs, s);
updateStyle(ofs, s);
lineBuilder = new StringBuilder();
}
else if (c == '\n') {
if (lineBuilder.length() > 0) {
int ofs = getLastLineOffset();
String s = lineBuilder.toString();
text.replaceTextRange(ofs, text.getCharCount() - ofs, s);
updateStyle(ofs, s);
lineBuilder = new StringBuilder();
}
text.append("\n");
}
else {
lineBuilder.append(c);
}
}
if (lineBuilder.length() > 0) {
int ofs = getLastLineOffset();
String s = lineBuilder.toString();
text.replaceTextRange(ofs, text.getCharCount() - ofs, s);
updateStyle(ofs, s);
}
text.setCaretOffset(text.getCharCount());
if (text.getLineCount() > 0) {
text.setTopIndex(text.getLineCount() - 1);
}
}
int getLastLineOffset() {
int index = text.getLineCount() - 1;
return text.getOffsetAtLine(index);
}
void updateStyle(int start, String line) {
if (isError) {
text.setStyleRange(new StyleRange(start, line.length(), errorColor, null));
}
else if (line.contains(": error :") || line.contains(": error:")) {
text.setStyleRange(new StyleRange(start, line.length(), errorColor, null));
moreLinesToMark += 3;
}
else if (line.contains("Error : ")) {
text.setStyleRange(new StyleRange(start, line.length(), errorColor, null));
moreLinesToMark += 2;
}
else if (moreLinesToMark > 0) {
text.setStyleRange(new StyleRange(start, line.length(), errorColor, null));
moreLinesToMark--;
}
}
@Override
public void flush() throws IOException {
}
@Override
public void close() throws IOException {
}
}
public Console(Composite parent) {
container = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = layout.marginHeight = 0;
container.setLayout(layout);
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
Label label = new Label(container, SWT.NONE);
label.setText("Console");
GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
gridData.horizontalIndent = 5;
label.setLayoutData(gridData);
text = new StyledText(container, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
text.setMargins(5, 5, 5, 5);
text.setTabs(4);
if ("win32".equals(SWT.getPlatform())) {
font = new Font(parent.getDisplay(), "Courier New", 9, SWT.NONE);
}
else {
font = new Font(parent.getDisplay(), "mono", 9, SWT.NONE);
}
text.setFont(font);
errorColor = parent.getDisplay().getSystemColor(SWT.COLOR_RED);
text.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
font.dispose();
}
});
outputStream = new ConsoleOutputStream(false);
errorStream = new ConsoleOutputStream(true);
}
public Composite getControl() {
return container;
}
public StyledText getStyledText() {
return text;
}
public void clear() {
text.setText("\r\n");
}
public OutputStream getOutputStream() {
return outputStream;
}
public OutputStream getErrorStream() {
return errorStream;
}
}