kopia lustrzana https://github.com/maccasoft/z80-tools
Added command line assembler
rodzic
53131d8dd0
commit
f6cd577b61
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.assertArrayEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import nl.grauw.glass.Source;
|
||||
import nl.grauw.glass.SourceBuilder;
|
||||
|
||||
public class AssemblerTest extends TestCase {
|
||||
|
||||
public void testGetHex() throws Exception {
|
||||
Assembler builder = new Assembler();
|
||||
|
||||
builder.ram.put(0x0000, (byte) 0x01);
|
||||
builder.ram.put(0x0001, (byte) 0x02);
|
||||
builder.ram.put(0x0002, (byte) 0x03);
|
||||
|
||||
StringBuilder sb = builder.getHex();
|
||||
assertEquals(":03000000010203F7\r\n", sb.toString());
|
||||
}
|
||||
|
||||
public void testGetHexWithGap() throws Exception {
|
||||
Assembler builder = new Assembler();
|
||||
|
||||
builder.ram.put(0x0000, (byte) 0x01);
|
||||
builder.ram.put(0x0001, (byte) 0x02);
|
||||
builder.ram.put(0x0003, (byte) 0x03);
|
||||
|
||||
StringBuilder sb = builder.getHex();
|
||||
assertEquals(":020000000102FB\r\n:0100030003F9\r\n", sb.toString());
|
||||
}
|
||||
|
||||
public void testGetHexWithOrigin() throws Exception {
|
||||
Assembler builder = new Assembler();
|
||||
|
||||
builder.ram.put(0x0100, (byte) 0x01);
|
||||
builder.ram.put(0x0101, (byte) 0x02);
|
||||
builder.ram.put(0x0102, (byte) 0x03);
|
||||
|
||||
StringBuilder sb = builder.getHex();
|
||||
assertEquals(":03010000010203F6\r\n", sb.toString());
|
||||
}
|
||||
|
||||
public void testBuildHex() throws Exception {
|
||||
Source source = assemble(
|
||||
" xor a",
|
||||
" ret");
|
||||
|
||||
Assembler builder = new Assembler();
|
||||
builder.build(source);
|
||||
|
||||
StringBuilder sb = builder.getHex();
|
||||
assertEquals(":02000000AFC986\r\n", sb.toString());
|
||||
}
|
||||
|
||||
public void testBuildHexWithOrigin() throws Exception {
|
||||
Source source = assemble(
|
||||
" .org 100H",
|
||||
" xor a",
|
||||
" ret");
|
||||
|
||||
Assembler builder = new Assembler();
|
||||
builder.build(source);
|
||||
|
||||
StringBuilder sb = builder.getHex();
|
||||
assertEquals(":02010000AFC985\r\n", sb.toString());
|
||||
}
|
||||
|
||||
public void testBuildHexWithDsGap() throws Exception {
|
||||
Source source = assemble(
|
||||
" xor a",
|
||||
" .ds 1",
|
||||
" ret");
|
||||
|
||||
Assembler builder = new Assembler();
|
||||
builder.build(source);
|
||||
|
||||
StringBuilder sb = builder.getHex();
|
||||
assertEquals(":01000000AF50\r\n:01000200C934\r\n", sb.toString());
|
||||
}
|
||||
|
||||
public void testGetBinary() throws Exception {
|
||||
Assembler builder = new Assembler();
|
||||
|
||||
builder.ram.put(0x0000, (byte) 0x01);
|
||||
builder.ram.put(0x0001, (byte) 0x02);
|
||||
builder.ram.put(0x0002, (byte) 0x03);
|
||||
|
||||
assertArrayEquals(b(0x01, 0x02, 0x03), builder.getBinary());
|
||||
}
|
||||
|
||||
public void testGetBinaryWithGap() throws Exception {
|
||||
Assembler builder = new Assembler();
|
||||
|
||||
builder.ram.put(0x0000, (byte) 0x01);
|
||||
builder.ram.put(0x0001, (byte) 0x02);
|
||||
builder.ram.put(0x0003, (byte) 0x03);
|
||||
|
||||
assertArrayEquals(b(0x01, 0x02, 0x00, 0x03), builder.getBinary());
|
||||
}
|
||||
|
||||
public void testGetBinaryWithOrigin() throws Exception {
|
||||
Assembler builder = new Assembler();
|
||||
|
||||
builder.ram.put(0x0100, (byte) 0x01);
|
||||
builder.ram.put(0x0101, (byte) 0x02);
|
||||
builder.ram.put(0x0102, (byte) 0x03);
|
||||
|
||||
assertArrayEquals(b(0x01, 0x02, 0x03), builder.getBinary());
|
||||
}
|
||||
|
||||
public void testBuildBinary() throws Exception {
|
||||
Source source = assemble(
|
||||
" xor a",
|
||||
" ret");
|
||||
|
||||
Assembler builder = new Assembler();
|
||||
builder.build(source);
|
||||
|
||||
assertArrayEquals(b(0xAF, 0xC9), builder.getBinary());
|
||||
}
|
||||
|
||||
public void testBuildBinaryWithOrigin() throws Exception {
|
||||
Source source = assemble(
|
||||
" .org 100H",
|
||||
" xor a",
|
||||
" ret");
|
||||
|
||||
Assembler builder = new Assembler();
|
||||
builder.build(source);
|
||||
|
||||
assertArrayEquals(b(0xAF, 0xC9), builder.getBinary());
|
||||
}
|
||||
|
||||
public void testBuildBinaryWithDsGap() throws Exception {
|
||||
Source source = assemble(
|
||||
" xor a",
|
||||
" .ds 1",
|
||||
" ret");
|
||||
|
||||
Assembler builder = new Assembler();
|
||||
builder.build(source);
|
||||
|
||||
assertArrayEquals(b(0xAF, 0x00, 0xC9), builder.getBinary());
|
||||
}
|
||||
|
||||
private Source assemble(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);
|
||||
source.register();
|
||||
source.expand();
|
||||
source.resolve();
|
||||
return source;
|
||||
}
|
||||
|
||||
private byte[] b(int... values) {
|
||||
byte[] bytes = new byte[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
bytes[i] = (byte) values[i];
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
package com.maccasoft.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nl.grauw.glass.AssemblyException;
|
||||
import nl.grauw.glass.Line;
|
||||
import nl.grauw.glass.Scope;
|
||||
import nl.grauw.glass.Source;
|
||||
import nl.grauw.glass.SourceBuilder;
|
||||
import nl.grauw.glass.directives.If;
|
||||
import nl.grauw.glass.directives.Section;
|
||||
|
||||
public class Assembler {
|
||||
|
||||
public static final int RAM_SIZE = 65536;
|
||||
|
||||
private static Assembler instance;
|
||||
|
||||
Source source;
|
||||
|
||||
Map<Integer, Byte> ram;
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
System.out.println("Usage: java -jar glass.jar [OPTION] SOURCE [OBJECT]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File sourcePath = null;
|
||||
File objectPath = null;
|
||||
List<File> includePaths = new ArrayList<File>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-I") && i + 1 < args.length) {
|
||||
includePaths.add(new File(args[++i]));
|
||||
}
|
||||
else if (sourcePath == null) {
|
||||
sourcePath = new File(args[i]);
|
||||
}
|
||||
else if (objectPath == null) {
|
||||
objectPath = new File(args[i]);
|
||||
}
|
||||
else {
|
||||
throw new AssemblyException("Too many arguments.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
instance = new Assembler();
|
||||
instance.assemble(sourcePath, includePaths, objectPath);
|
||||
} 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());
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.err.println(sb.toString());
|
||||
System.exit(1);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
public Assembler() {
|
||||
ram = new HashMap<Integer, Byte>(RAM_SIZE);
|
||||
}
|
||||
|
||||
void assemble(File sourcePath, List<File> includePaths, File objectPath) {
|
||||
System.out.print("Compiling " + sourcePath.getName() + "...");
|
||||
System.out.flush();
|
||||
|
||||
source = new SourceBuilder(includePaths) {
|
||||
|
||||
@Override
|
||||
public Source parse(File sourceFile) {
|
||||
if (!sourceFile.equals(sourcePath)) {
|
||||
System.out.print("\r\nCompiling " + sourceFile.getName() + "...");
|
||||
System.out.flush();
|
||||
}
|
||||
return super.parse(sourceFile);
|
||||
}
|
||||
|
||||
}.parse(sourcePath);
|
||||
|
||||
source.register();
|
||||
source.expand();
|
||||
source.resolve();
|
||||
|
||||
build(source);
|
||||
writeObject(objectPath);
|
||||
|
||||
int lower = Integer.MAX_VALUE;
|
||||
int higher = Integer.MIN_VALUE;
|
||||
for (Line line : source.getLines()) {
|
||||
try {
|
||||
if (line.getSize() != 0) {
|
||||
lower = Math.min(lower, line.getScope().getAddress());
|
||||
higher = Math.max(higher, line.getScope().getAddress() + line.getSize() - 1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore, not important
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println(String.format("Compiled %d lines from %04XH to %04XH (%d bytes)", source.getLines().size(), lower, higher, higher - lower + 1));
|
||||
}
|
||||
|
||||
void build(Source source) {
|
||||
for (Line line : source.getLines()) {
|
||||
try {
|
||||
if (line.getDirective() instanceof If) {
|
||||
if (line.getInstructionObject() != null) {
|
||||
nl.grauw.glass.instructions.If ins = (nl.grauw.glass.instructions.If) line.getInstruction();
|
||||
build(ins.getThenSource());
|
||||
if (ins.getElseSource() != null) {
|
||||
build(ins.getElseSource());
|
||||
}
|
||||
}
|
||||
else {
|
||||
If ins = (If) line.getDirective();
|
||||
build(ins.getThenSource());
|
||||
if (ins.getElseSource() != null) {
|
||||
build(ins.getElseSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (line.getDirective() instanceof Section) {
|
||||
if (line.getInstructionObject() != null) {
|
||||
nl.grauw.glass.instructions.Section ins = (nl.grauw.glass.instructions.Section) line.getInstruction();
|
||||
build(ins.getSource());
|
||||
}
|
||||
else {
|
||||
Section ins = (Section) line.getDirective();
|
||||
build(ins.getSource());
|
||||
}
|
||||
}
|
||||
else {
|
||||
Scope scope = line.getScope();
|
||||
if (scope.isAddressSet()) {
|
||||
byte[] code = line.getBytes();
|
||||
for (int i = 0; i < code.length; i++) {
|
||||
ram.put(scope.getAddress() + i, code[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (AssemblyException e) {
|
||||
e.addContext(line);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeObject(File objectPath) {
|
||||
try {
|
||||
if (objectPath.getName().toLowerCase().endsWith(".hex")) {
|
||||
OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(objectPath));
|
||||
os.write(getHex().toString());
|
||||
os.write(":00000001FF\r\n");
|
||||
os.close();
|
||||
}
|
||||
else {
|
||||
OutputStream output = new FileOutputStream(objectPath);
|
||||
output.write(getBinary());
|
||||
output.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] getBinary() {
|
||||
int from, to;
|
||||
|
||||
from = 0;
|
||||
while (from < RAM_SIZE && !ram.containsKey(from)) {
|
||||
from++;
|
||||
}
|
||||
to = RAM_SIZE - 1;
|
||||
while (to > from && !ram.containsKey(to)) {
|
||||
to--;
|
||||
}
|
||||
to++;
|
||||
|
||||
byte[] data = new byte[to - from];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (ram.containsKey(from + i)) {
|
||||
data[i] = ram.get(from + i);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
StringBuilder getHex() {
|
||||
int from, to;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
from = 0;
|
||||
while (from < RAM_SIZE) {
|
||||
if (ram.containsKey(from)) {
|
||||
to = from + 1;
|
||||
while (to < RAM_SIZE && ram.containsKey(to)) {
|
||||
to++;
|
||||
}
|
||||
byte[] data = new byte[to - from];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = ram.get(from + i);
|
||||
}
|
||||
sb.append(toHexString(from, data));
|
||||
from = to - 1;
|
||||
}
|
||||
from++;
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
Ładowanie…
Reference in New Issue