diff --git a/LICENSE.glass b/LICENSE.glass
new file mode 100644
index 0000000..4b0034e
--- /dev/null
+++ b/LICENSE.glass
@@ -0,0 +1,22 @@
+Copyright (c) 2014, Laurens Holst
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/NOTICE.glass b/NOTICE.glass
new file mode 100644
index 0000000..59a9f43
--- /dev/null
+++ b/NOTICE.glass
@@ -0,0 +1,5 @@
+Glass Z80 assembler
+Copyright 2013 Laurens Holst
+
+This product includes software developed by
+Laurens Holst .
diff --git a/build.xml b/build.xml
index 69672f0..4857049 100644
--- a/build.xml
+++ b/build.xml
@@ -224,6 +224,8 @@
+
+
diff --git a/src-tests/nl/grauw/glass/ParserTest.java b/src-tests/nl/grauw/glass/ParserTest.java
new file mode 100644
index 0000000..b73aa07
--- /dev/null
+++ b/src-tests/nl/grauw/glass/ParserTest.java
@@ -0,0 +1,180 @@
+package nl.grauw.glass;
+
+import static org.junit.Assert.*;
+
+import java.io.LineNumberReader;
+import java.io.StringReader;
+
+import nl.grauw.glass.Parser.SyntaxError;
+import nl.grauw.glass.expressions.CharacterLiteral;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.ExpressionBuilder.ExpressionError;
+import nl.grauw.glass.expressions.Flag;
+import nl.grauw.glass.expressions.IntegerLiteral;
+
+import org.junit.Test;
+
+public class ParserTest {
+
+ @Test
+ public void testLabel() {
+ assertEquals("test_label1", parse("test_label1:").getLabel());
+ }
+
+ @Test
+ public void testLabelNoColon() {
+ assertEquals("test_label1", parse("test_label1").getLabel());
+ }
+
+ @Test
+ public void testLabelIndented() {
+ assertEquals("test_label", parse(" test_label:").getLabel());
+ }
+
+ @Test
+ public void testLabelIndentedWithMnemonic() {
+ assertEquals("test_label", parse(" test_label:exx").getLabel());
+ assertEquals("exx", parse(" test_label:exx").getMnemonic());
+ }
+
+ @Test
+ public void testMnemonic() {
+ assertEquals("exx", parse(" exx").getMnemonic());
+ }
+
+ @Test
+ public void testArguments() {
+ assertTrue(parse("\tcp 0H").getArguments() instanceof IntegerLiteral);
+ }
+
+ @Test
+ public void testComment() {
+ assertEquals("test comment", parse(";test comment").getComment());
+ }
+
+ @Test
+ public void testParser1() {
+ assertEquals(";test comment", parse(" ;test comment").toString());
+ }
+
+ @Test
+ public void testParser2() {
+ assertEquals("test_label1: ;test", parse("test_label1:;test").toString());
+ }
+
+ @Test
+ public void testParser3() {
+ assertEquals("test_label1: ;test", parse("test_label1;test").toString());
+ }
+
+ @Test
+ public void testParser4() {
+ assertEquals("test_label1: exx ;test", parse("test_label1:exx;test").toString());
+ }
+
+ @Test
+ public void testParser5() {
+ assertEquals("test_label1: push af ;test", parse("test_label1: push af ;test").toString());
+ }
+
+ @Test
+ public void testParser6() {
+ assertEquals("test_label1: ex af, af' ;test", parse("test_label1: ex af,af';test").toString());
+ }
+
+ @Test
+ public void testCharacterLiteral() {
+ assertEquals('x', ((CharacterLiteral)parseExpression("'x'")).getCharacter());
+ }
+
+ @Test
+ public void testCharacterLiteralEscape() {
+ assertEquals('"', ((CharacterLiteral)parseExpression("'\\\"'")).getCharacter());
+ }
+
+ @Test(expected=SyntaxError.class)
+ public void testCharacterLiteralTooLong() {
+ parse("'xx'");
+ }
+
+ @Test(expected=SyntaxError.class)
+ public void testCharacterLiteralTooShort() {
+ parse("''");
+ }
+
+ @Test(expected=SyntaxError.class)
+ public void testCharacterLiteralUnclosed() {
+ parse("'");
+ }
+
+ @Test(expected=SyntaxError.class)
+ public void testCharacterLiteralUnclosedEscape() {
+ parse("'\\");
+ }
+
+ @Test(expected=SyntaxError.class)
+ public void testHexNumberTooShort() {
+ parseExpression("0x");
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testHexNumberWrong() {
+ parseExpression("003x0");
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testHexNumberWrong2() {
+ parseExpression("0x0x0");
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testHexNumberWrong3() {
+ parseExpression("3x0");
+ }
+
+ @Test
+ public void testNumber() {
+ assertEquals(127, parseExpression("127").getInteger());
+ assertEquals(4095, parseExpression("0FFFH").getInteger());
+ assertEquals(4095, parseExpression("#0FFF").getInteger());
+ assertEquals(4095, parseExpression("$0FFF").getInteger());
+ assertEquals(171, parseExpression("10101011B").getInteger());
+ assertEquals(171, parseExpression("%10101011").getInteger());
+ assertEquals(255, parseExpression("0xFF").getInteger());
+ assertEquals(50, parseExpression("0X032").getInteger());
+ }
+
+ @Test
+ public void testFlag() {
+ assertEquals(Flag.NZ, parseExpression("nz").getFlag());
+ assertEquals(Flag.Z, parseExpression("z").getFlag());
+ assertEquals(Flag.NC, parseExpression("nc").getFlag());
+ assertEquals(Flag.C, parseExpression("c").getFlag());
+ assertEquals(Flag.PO, parseExpression("po").getFlag());
+ assertEquals(Flag.PE, parseExpression("pe").getFlag());
+ assertEquals(Flag.P, parseExpression("p").getFlag());
+ assertEquals(Flag.M, parseExpression("m").getFlag());
+ }
+
+ @Test
+ public void testFlagNegate() {
+ assertEquals(Flag.Z, parseExpression("!nz").getFlag());
+ assertEquals(Flag.NZ, parseExpression("!z").getFlag());
+ assertEquals(Flag.C, parseExpression("!nc").getFlag());
+ assertEquals(Flag.NC, parseExpression("!c").getFlag());
+ assertEquals(Flag.PE, parseExpression("!po").getFlag());
+ assertEquals(Flag.PO, parseExpression("!pe").getFlag());
+ assertEquals(Flag.M, parseExpression("!p").getFlag());
+ assertEquals(Flag.P, parseExpression("!m").getFlag());
+ }
+
+ public Line parse(String text) {
+ LineNumberReader reader = new LineNumberReader(new StringReader(text));
+ return new Parser().parse(reader, new Scope(), null);
+ }
+
+ public Expression parseExpression(String text) {
+ return parse(" test " + text).getArguments();
+ }
+
+}
diff --git a/src-tests/nl/grauw/glass/SourceTest.java b/src-tests/nl/grauw/glass/SourceTest.java
new file mode 100644
index 0000000..b7f6993
--- /dev/null
+++ b/src-tests/nl/grauw/glass/SourceTest.java
@@ -0,0 +1,890 @@
+package nl.grauw.glass;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+
+import nl.grauw.glass.Scope.SymbolNotFoundException;
+import nl.grauw.glass.expressions.EvaluationException;
+import nl.grauw.glass.instructions.ArgumentException;
+import nl.grauw.glass.instructions.Error.ErrorDirectiveException;
+
+import org.junit.Test;
+
+public class SourceTest {
+
+ @Test
+ public void testNops() {
+ assertArrayEquals(b(0x00, 0x00), assemble(
+ " nop",
+ " nop"
+ ));
+ }
+
+ @Test
+ public void testJumpSelf() {
+ assertArrayEquals(b(0x00, 0xC3, 0x01, 0x00), assemble(
+ " nop",
+ " jp $"
+ ));
+ }
+
+ @Test
+ public void testJumpLabelSelf() {
+ assertArrayEquals(b(0x00, 0x00, 0xC3, 0x01, 0x00), assemble(
+ " nop",
+ "label: nop",
+ " jp label"
+ ));
+ }
+
+ @Test
+ public void testJumpLabelBackward() {
+ assertArrayEquals(b(0x00, 0x00, 0xC3, 0x01, 0x00), assemble(
+ " nop",
+ "label: nop",
+ " jp label"
+ ));
+ }
+
+ @Test
+ public void testJumpLabelForward() {
+ assertArrayEquals(b(0xC3, 0x03, 0x00), assemble(
+ " jp label",
+ "label:"
+ ));
+ }
+
+ @Test
+ public void testEqu() {
+ assertArrayEquals(b(0x3E, 0x10), assemble(
+ " ld a,label",
+ "label: equ 10H"
+ ));
+ }
+
+ @Test
+ public void testEquRegister() {
+ assertArrayEquals(b(0x78), assemble(
+ " ld a,label",
+ "label: equ b"
+ ));
+ }
+
+ @Test
+ public void testEquIndirectRegister() {
+ assertArrayEquals(b(0x7E), assemble(
+ " ld a,label",
+ "label: equ (hl)"
+ ));
+ }
+
+ @Test
+ public void testOrg() {
+ assertArrayEquals(b(0xC3, 0x32, 0x40, 0x00, 0x18, 0xFD), assemble(
+ " jp label",
+ " org 4032H",
+ "label:",
+ " nop",
+ " jr label"
+ ));
+ }
+
+ @Test
+ public void testOrgLabel() {
+ assertArrayEquals(b(0xC3, 0x03, 0x00, 0x00), assemble(
+ " jp label",
+ "label: org 4032H",
+ " nop"
+ ));
+ }
+
+ @Test
+ public void testOrgLabelBefore() {
+ assertArrayEquals(b(0xC3, 0x03, 0x00, 0x00), assemble(
+ " jp label",
+ "label:",
+ " org 4032H",
+ " nop"
+ ));
+ }
+
+ @Test
+ public void testOrgSelfAddress() {
+ assertArrayEquals(b(0xC3, 0x04, 0x01, 0x00), assemble(
+ " jp label",
+ " org $ + 101H",
+ "label:",
+ " nop"
+ ));
+ }
+
+ @Test
+ public void testRelativeJumpAssembly() {
+ assertArrayEquals(b(0x18, 0x05, 0x28, 0x03, 0x10, 0x01, 0x00), assemble(
+ " org 100H",
+ " jr label", // Because uninitialised labels use value 0, these
+ " jr z,label", // would be out of range if they would generate
+ " djnz label", // actual object code in the first pass.
+ " nop",
+ "label:"
+ ));
+ }
+
+ @Test
+ public void testIndexDoubleAdd() {
+ assertArrayEquals(b(
+ 0xDD, 0xA6, 0x03,
+ 0xDD, 0xA6, 0x05,
+ 0xDD, 0xA6, 0xFD,
+ 0xDD, 0xA6, 0xFB,
+ 0xDD, 0xA6, 0x06
+ ), assemble(
+ " and (ix + 1 + 2)",
+ " and (ix + 7 - 2)",
+ " and (ix - 1 - 2)",
+ " and (ix - 7 + 2)",
+ " and (ix + 3 * 2)"
+ )
+ );
+ }
+
+ @Test
+ public void testMacro() {
+ assertArrayEquals(b(0x00, 0x00), assemble(
+ "test: MACRO",
+ " nop",
+ " ENDM",
+ " test",
+ " test"
+ ));
+ }
+
+ @Test
+ public void testMacroAddress() {
+ assertArrayEquals(b(0x00, 0xC3, 0x01, 0x00, 0xC3, 0x04, 0x00), assemble(
+ " nop",
+ "test: MACRO",
+ " jp $",
+ " ENDM",
+ " test",
+ " test"
+ ));
+ }
+
+ @Test
+ public void testMacroArguments() {
+ assertArrayEquals(b(0x3E, 0x10, 0x3E, 0x20), assemble(
+ "test: MACRO arg",
+ " ld a,arg",
+ " ENDM",
+ " test 10H",
+ " test 20H"
+ ));
+ }
+
+ @Test
+ public void testMacroArgumentsTwo() {
+ assertArrayEquals(b(0x3E, 0x30, 0x3E, 0x77), assemble(
+ "test: MACRO arg1, arg2",
+ " ld a,arg1 + arg2",
+ " ENDM",
+ " test 10H, 20H",
+ " test 33H, 44H"
+ ));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testMacroTooManyArguments() {
+ assemble(
+ "test: MACRO",
+ " ENDM",
+ " test 10H"
+ );
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testMacroTooFewArguments() {
+ assemble(
+ "test: MACRO arg",
+ " ENDM",
+ " test"
+ );
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testMacroNonIdentifierArguments() {
+ assemble(
+ "test: MACRO (arg)",
+ " ENDM"
+ );
+ }
+
+ @Test
+ public void testMacroDefaultArgument() {
+ assertArrayEquals(b(0x3E, 0x10, 0x3E, 0x20), assemble(
+ "test: MACRO arg = 10H",
+ " ld a,arg",
+ " ENDM",
+ " test",
+ " test 20H"
+ ));
+ }
+
+ @Test
+ public void testMacroDefaultFlagArgument() {
+ assertArrayEquals(b(0xC8, 0xC0), assemble(
+ "test: MACRO arg = z",
+ " ret arg",
+ " ENDM",
+ " test",
+ " test nz"
+ ));
+ }
+
+ @Test(expected=AssemblyException.class)
+ public void testMacroNoEnd() {
+ assemble(
+ "test: MACRO"
+ );
+ }
+
+ @Test
+ public void testMacroLabels() {
+ assertArrayEquals(b(0x00, 0x21, 0x04, 0x00, 0x21, 0x07, 0x00), assemble(
+ " nop",
+ "test: MACRO",
+ " ld hl,test2",
+ "test2:",
+ " ENDM",
+ " test",
+ " test"
+ ));
+ }
+
+ @Test
+ public void testMacroOuterScope() {
+ assertArrayEquals(b(0x3E, 0x11), assemble(
+ "test: MACRO arg",
+ " ld a,value + arg",
+ " ENDM",
+ " test 1H",
+ "value: equ 10H"
+ ));
+ }
+
+ @Test
+ public void testMacroNesting() {
+ assertArrayEquals(b(0x3E, 0x13, 0x3E, 0x23), assemble(
+ "test: MACRO arg",
+ "test: MACRO arg",
+ " ld a,20H + arg + value",
+ " ENDM",
+ " ld a,10H + arg + value",
+ " test arg",
+ "value: equ 2",
+ " ENDM",
+ " test 1H"
+ ));
+ }
+
+ @Test
+ public void testMacroTwiceWithLocalReferences() {
+ assertArrayEquals(b(0x3E, 0x14, 0x3E, 0x23), assemble(
+ "test: MACRO arg",
+ " ld a,10H + arg + value",
+ " test2 arg",
+ "value: equ 3",
+ " ENDM",
+ "test2: MACRO arg",
+ " ld a,20H + arg + value",
+ "value: equ 2",
+ " ENDM",
+ " test 1H"
+ ));
+ }
+
+ @Test
+ public void testMacroClosure() {
+ assertArrayEquals(b(0x3E, 0x14, 0x3E, 0x25), assemble(
+ "test: MACRO arg",
+ " ld a,10H + arg + value",
+ " test2 arg",
+ "value: equ 3",
+ " ENDM",
+ "test2: MACRO arg",
+ " ld a,20H + arg + value",
+ " ENDM",
+ "value: equ 4",
+ " test 1H"
+ ));
+ }
+
+ @Test(expected=SymbolNotFoundException.class)
+ public void testMacroUnboundReference() {
+ assertArrayEquals(b(0x3E, 0x14, 0x3E, 0x24), assemble(
+ "test: MACRO arg",
+ " ld a,10H + arg + value",
+ " test2 arg",
+ "value: equ 3",
+ " ENDM",
+ "test2: MACRO arg",
+ " ld a,20H + arg + value",
+ " ENDM",
+ " test 1H"
+ ));
+ }
+
+ @Test
+ public void testMacroLabelsDereference() {
+ assertArrayEquals(b(0x11, 0x09, 0x00, 0x21, 0x06, 0x00, 0x21, 0x09, 0x00), assemble(
+ " ld de,test.z.test2",
+ "test: MACRO",
+ " ld hl,test2",
+ "test2:",
+ " ENDM",
+ " test",
+ "test.z: test"
+ ));
+ }
+
+ @Test
+ public void testMacroDefinitionDereference() {
+ assertArrayEquals(b(0x11, 0x03, 0x00), assemble(
+ " ld de,test.test2",
+ "test: MACRO",
+ " ld hl,test2",
+ "test2:",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testMacroDefinitionWithArgumentsDereference() {
+ assertArrayEquals(b(0x11, 0x03, 0x00), assemble(
+ " ld de,test.test2",
+ "test: MACRO arg",
+ " ld hl,arg",
+ "test2:",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testMacroDefinitionWithDefaultArgumentsDereference() {
+ assertArrayEquals(b(0x11, 0x03, 0x00), assemble(
+ " ld de,test.test2",
+ "test: MACRO arg = 0",
+ " ld hl,arg",
+ "test2:",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testMacroDefinitionWithNonIntegerArgumentDereference() {
+ assertArrayEquals(b(0x11, 0x03, 0x00), assemble(
+ " ld de,test.test2",
+ "test: MACRO arg1, arg2",
+ " ld hl,arg1",
+ "test2:",
+ " ret arg2",
+ " ENDM"
+ ));
+ }
+
+ @Test(expected=EvaluationException.class)
+ public void testMacroDefinitionWithNonIntegerArgumentBeforeDereference() {
+ assertArrayEquals(b(0x11, 0x03, 0x00), assemble(
+ " ld de,test.test2",
+ "test: MACRO arg1, arg2",
+ " ld hl,arg1",
+ " ret arg2",
+ "test2:",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testMacroDefinitionWithDefaultFlagArgumentBeforeDereference() {
+ assertArrayEquals(b(0x11, 0x01, 0x00), assemble(
+ " ld de,test.test2",
+ "test: MACRO arg = z",
+ " ret arg",
+ "test2:",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testMacroContextArgumentDereference() {
+ assertArrayEquals(b(0x03), assemble(
+ "macro1: MACRO",
+ " ld hl,0",
+ "test:",
+ " ENDM",
+ "macro2: MACRO ?arg",
+ " db ?arg.test",
+ " ENDM",
+ " macro2 macro1"
+ ));
+ }
+
+ @Test
+ public void testMacroContextArgumentDereference2() {
+ assertArrayEquals(b(0x3E, 0x05, 0x21, 0x00, 0x00), assemble(
+ "macro1: MACRO",
+ " ld hl,0",
+ "test:",
+ " ENDM",
+ "macro2: MACRO ?arg",
+ " ld a,?arg.test",
+ " ENDM",
+ " macro2 (m1)",
+ "m1: macro1"
+ ));
+ }
+
+ @Test
+ public void testMacroInstructionArgument() {
+ assertArrayEquals(b(0x3E, 0x10, 0xC9), assemble(
+ "test: MACRO arg",
+ " ld a,10H",
+ " arg",
+ " ENDM",
+ " test ret"
+ ));
+ }
+
+ @Test
+ public void testRept() {
+ assertArrayEquals(b(0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF), assemble(
+ " REPT 3",
+ " nop",
+ " rst 38H",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testReptIndirect() {
+ assertArrayEquals(b(0x00, 0x00, 0x00, 0x00), assemble(
+ " REPT count",
+ " nop",
+ " ENDM",
+ "count: equ 4"
+ ));
+ }
+
+ @Test
+ public void testReptParameter() {
+ assertArrayEquals(b(0x3E, 0x00, 0x3E, 0x01, 0x3E, 0x02), assemble(
+ " REPT 3, ?value",
+ " ld a,?value",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testReptParameterStart() {
+ assertArrayEquals(b(0x3E, 0x10, 0x3E, 0x11, 0x3E, 0x12), assemble(
+ " REPT 3, ?value, 10H",
+ " ld a,?value",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testReptParameterStartStep() {
+ assertArrayEquals(b(0x3E, 0x10, 0x3E, 0x13, 0x3E, 0x16), assemble(
+ " REPT 3, ?value, 10H, 3",
+ " ld a,?value",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testReptWithIndex() {
+ assertArrayEquals(b(0x21, 0x05, 0x00, 0x00, 0x00, 0x00), assemble(
+ " ld hl,test.2",
+ "test: REPT 3",
+ " nop",
+ "test: ENDM"
+ ));
+ }
+
+ @Test
+ public void testReptWithLabel() {
+ assertArrayEquals(b(0x21, 0x06, 0x00, 0x00, 0x00, 0x00), assemble(
+ " ld hl,test.2.test",
+ "test: REPT 3",
+ " nop",
+ "test: ENDM"
+ ));
+ }
+
+ @Test(expected=SymbolNotFoundException.class)
+ public void testReptWithLabelNoIndex() {
+ assemble(
+ " nop",
+ "test: REPT 2",
+ " nop",
+ "test: nop",
+ " ENDM",
+ " ld hl,test.test"
+ );
+ }
+
+ @Test
+ public void testReptNested() {
+ assertArrayEquals(b(0x00, 0x01, 0x02, 0x10, 0x11, 0x12), assemble(
+ " REPT 2, ?value, 0, 10H",
+ " REPT 3, ?value2",
+ " db ?value + ?value2",
+ " ENDM",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testReptNoBody() {
+ assertArrayEquals(b(), assemble(
+ " REPT 3",
+ " ENDM"
+ ));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testReptNoCount() {
+ assemble(
+ " REPT",
+ " ENDM"
+ );
+ }
+
+ @Test
+ public void testIrp() {
+ assertArrayEquals(b(0x3E, 0x10, 0xFF, 0x3E, 0x20, 0xFF, 0x3E, 0x30, 0xFF), assemble(
+ " IRP ?value, 10H, 20H, 30H",
+ " ld a,?value",
+ " rst 38H",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testIrpNoValues() {
+ assertArrayEquals(b(), assemble(
+ " IRP ?value",
+ " ld a,?value",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testIrpNoBody() {
+ assertArrayEquals(b(), assemble(
+ " IRP ?value, 1, 2, 3",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testIrpRegisters() {
+ assertArrayEquals(b(0xC5, 0xD5, 0xE5, 0xF5), assemble(
+ " IRP ?register, bc, de, hl, af",
+ " push ?register",
+ " ENDM"
+ ));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testIrpNoIdentifier() {
+ assertArrayEquals(b(), assemble(
+ " IRP",
+ " nop",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testIrpWithIndex() {
+ assertArrayEquals(b(0x21, 0x05, 0x00, 0x10, 0x20, 0x30), assemble(
+ " ld hl,test.2",
+ "test: IRP ?value, 10H, 20H, 30H",
+ "test: db ?value",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testIrpWithLabel() {
+ assertArrayEquals(b(0x21, 0x05, 0x00, 0x10, 0x20, 0x30), assemble(
+ " ld hl,test.2.test",
+ "test: IRP ?value, 10H, 20H, 30H",
+ "test: db ?value",
+ " ENDM"
+ ));
+ }
+
+ @Test(expected=SymbolNotFoundException.class)
+ public void testIrpWithLabelNoIndex() {
+ assemble(
+ " nop",
+ "test: IRP ?value, 10H, 20H, 30H",
+ " nop",
+ "test: nop",
+ " ENDM",
+ " ld hl,test.test"
+ );
+ }
+
+ @Test
+ public void testProc() {
+ assertArrayEquals(b(0x21, 0x0A, 0x00, 0xC3, 0x06, 0x00, 0xFF, 0xC3, 0x0A, 0x00, 0xFF), assemble(
+ " ld hl,test2.test",
+ "test1: PROC",
+ " jp test",
+ "test: rst 38H",
+ " ENDP",
+ "test2: PROC",
+ " jp test",
+ "test: rst 38H",
+ " ENDP"
+ ));
+ }
+
+ @Test
+ public void testIf() {
+ assertArrayEquals(b(0x00), assemble(
+ " IF 1",
+ " nop",
+ " ENDIF"
+ ));
+ }
+
+ @Test
+ public void testIfThen() {
+ assertArrayEquals(b(0x00), assemble(
+ " IF 1 = 1",
+ " nop",
+ " ELSE",
+ " rst 38H",
+ " ENDIF"
+ ));
+ }
+
+ @Test
+ public void testIfThenElse() {
+ assertArrayEquals(b(0xFF), assemble(
+ " IF 0 > 1",
+ " nop",
+ " ELSE",
+ " rst 38H",
+ " ENDIF"
+ ));
+ }
+
+ @Test
+ public void testIfInsideRept() {
+ assertArrayEquals(b(0xFF, 0x00, 0xFF), assemble(
+ " IRP ?test, 00H, 10H, 11H",
+ " IF ?test = 10H",
+ " nop",
+ " ELSE",
+ " rst 38H",
+ " ENDIF",
+ " ENDM"
+ ));
+ }
+
+ @Test
+ public void testIfWithEqu() {
+ assertArrayEquals(b(0xC3, 0x10, 0x00), assemble(
+ " IF 1",
+ "test: equ 10H",
+ " ELSE",
+ "test: equ 20H",
+ " ENDIF",
+ " jp test"
+ ));
+ }
+
+ @Test
+ public void testIfWithLabel() {
+ assertArrayEquals(b(0x22, 0x22, 0xC3, 0x02, 0x00), assemble(
+ " IF 0",
+ " db 11H",
+ "test: ELSE",
+ " dw 2222H",
+ "test: ENDIF",
+ " jp test"
+ ));
+ }
+
+ @Test(expected=SymbolNotFoundException.class)
+ public void testIfWithEquForward() {
+ assemble(
+ " jp test",
+ " IF 1",
+ "test: equ 10H",
+ " ENDIF"
+ );
+ }
+
+ @Test(expected=ErrorDirectiveException.class)
+ public void testError() {
+ assemble(
+ " ERROR"
+ );
+ }
+
+ @Test
+ public void testErrorWithMessage() {
+ try {
+ assemble(
+ " ERROR \"Test\""
+ );
+ } catch (ErrorDirectiveException e) {
+ assertEquals("Test", e.getPlainMessage());
+ }
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testAnnotationNotSupported() {
+ assemble(
+ " or A 0"
+ );
+ }
+
+ @Test
+ public void testDsVirtual() {
+ assertArrayEquals(b(0x3E, 0x86, 0x21, 0x12, 0x00), assemble(
+ " ld a,86H",
+ " ds VIRTUAL 10H",
+ " ld hl,$"
+ ));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testDsVirtualWithFill() {
+ assemble(
+ " ds VIRTUAL 10H, 0"
+ );
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testDsUnknownAnnotation() {
+ assemble(
+ " ds UNKNOWN 10H"
+ );
+ }
+
+ @Test
+ public void testSection() {
+ assertArrayEquals(b(0x00, 0x21, 0x07, 0x00, 0x21, 0x04, 0x00, 0x86, 0x86, 0x00, 0x11, 0x07, 0x00), assemble(
+ " nop",
+ "ROM: ds 8H, 86H",
+ " nop",
+ " SECTION ROM",
+ " ld hl,label",
+ " ld hl,$",
+ "label: ENDS",
+ " ld de,label"
+ ));
+ }
+
+ @Test
+ public void testSectionVirtual() {
+ assertArrayEquals(b(0x00, 0x00, 0x11, 0x07, 0x00), assemble(
+ " nop",
+ "RAM: ds VIRTUAL 8H",
+ " nop",
+ " SECTION RAM",
+ " ld hl,label",
+ " ld hl,$",
+ "label: ENDS",
+ " ld de,label"
+ ));
+ }
+
+ @Test
+ public void testSectionFitsSpace() {
+ assemble(
+ "ROM: ds 3H",
+ " SECTION ROM",
+ " ld hl,$",
+ " ENDS"
+ );
+ }
+
+ @Test(expected=AssemblyException.class)
+ public void testSectionExceedsSpace() {
+ assemble(
+ "ROM: ds 2H",
+ " SECTION ROM",
+ " ld hl,$",
+ " ENDS"
+ );
+ }
+
+ @Test
+ public void testSectionIndirect() {
+ assemble(
+ "ROM2: ds 3H",
+ "ROM: equ ROM2",
+ " SECTION ROM",
+ " ld hl,$",
+ " ENDS"
+ );
+ }
+
+ @Test(expected=AssemblyException.class)
+ public void testEndm() {
+ assemble(
+ " ENDM"
+ );
+ }
+
+ @Test(expected=AssemblyException.class)
+ public void testEndp() {
+ assemble(
+ " ENDP"
+ );
+ }
+
+ @Test(expected=AssemblyException.class)
+ public void testEnds() {
+ assemble(
+ " ENDS"
+ );
+ }
+
+ public byte[] assemble(String... sourceLines) {
+ StringBuilder builder = new StringBuilder();
+ for (String lineText : sourceLines)
+ builder.append(lineText).append("\n");
+ SourceBuilder sourceBuilder = new SourceBuilder(new ArrayList());
+ Source source = sourceBuilder.parse(new StringReader(builder.toString()), null);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ try {
+ source.assemble(output);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return output.toByteArray();
+ }
+
+ public 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;
+ }
+
+
+}
diff --git a/src-tests/nl/grauw/glass/expressions/ExpressionBuilderTest.java b/src-tests/nl/grauw/glass/expressions/ExpressionBuilderTest.java
new file mode 100644
index 0000000..7a950fa
--- /dev/null
+++ b/src-tests/nl/grauw/glass/expressions/ExpressionBuilderTest.java
@@ -0,0 +1,255 @@
+package nl.grauw.glass.expressions;
+
+import static org.junit.Assert.*;
+
+import java.io.LineNumberReader;
+import java.io.StringReader;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Parser;
+import nl.grauw.glass.Parser.SyntaxError;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.ExpressionBuilder.ExpressionError;
+
+import org.junit.Test;
+
+public class ExpressionBuilderTest {
+
+ @Test
+ public void testSingleValue() {
+ assertEquals("a", parse("a"));
+ }
+
+ @Test
+ public void testAddition() {
+ assertEquals("{a + 1H}", parse("a + 1H"));
+ }
+
+ @Test
+ public void testAddition2() {
+ assertEquals("{{a + 1H} + 2H}", parse("a + 1H + 2H"));
+ }
+
+ @Test
+ public void testPrecedence() {
+ assertEquals("{a + {1H * 2H}}", parse("a + 1H * 2H"));
+ }
+
+ @Test
+ public void testPrecedence2() {
+ assertEquals("{{a + {1H * 2H}} + b}", parse("a + 1H * 2H + b"));
+ }
+
+ @Test
+ public void testGrouping() {
+ assertEquals("{a + {({1H + 2H}) * 3H}}", parse("a + (1H + 2H) * 3H"));
+ }
+
+ @Test
+ public void testGrouping2() {
+ assertEquals("{{10H + ({15H * ({5H - 2H})})} + 4H}", parse("10H + (15H * (5H - 2H)) + 4H"));
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testGrouping3() {
+ parse("10H + (15H * (5H - 2H) + 4H");
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testGrouping4() {
+ parse("10H + 15H * (5H - 2H)) + 4H");
+ }
+
+ @Test
+ public void testMember() {
+ assertEquals("{($).member}", parse("($).member"));
+ }
+
+ @Test
+ public void testMemberCombinedIdentifier() {
+ assertEquals("object.member", parse("object.member"));
+ }
+
+ @Test
+ public void testIndex() {
+ assertEquals("{({4H, 5H})[0H]}", parse("(4H, 5H)[0H]"));
+ }
+
+ @Test
+ public void testIndexPrecedence() {
+ assertEquals("{{$.member}[0H]}", parse("$.member[0]"));
+ }
+
+ @Test
+ public void testTernaryIfElse() {
+ assertEquals("{a ? 1H : 2H}", parse("a ? 1H : 2H"));
+ }
+
+ @Test
+ public void testTernaryIfElseNested1() {
+ assertEquals("{a ? 1H : {b ? 2H : 3H}}", parse("a ? 1H : b ? 2H : 3H"));
+ }
+
+ @Test
+ public void testTernaryIfElseNested2() {
+ assertEquals("{a ? {b ? 1H : 2H} : 3H}", parse("a ? b ? 1H : 2H : 3H"));
+ }
+
+ @Test
+ public void testTernaryIfElseNested3() {
+ assertEquals("{a ? {b ? 1H : 2H} : 3H}", parse("a ? b ? 1H : 2H : 3H"));
+ }
+
+ @Test
+ public void testTernaryIfElseNested4() {
+ assertEquals("{{ANN 1H}, {{ANN {a ? {b ? {2H + 3H} : {{c < d} ? 4H : {5H - 6H}}} : 7H}}, {e ? 8H : 9H}}}",
+ parse("ANN 1H, ANN a ? b ? 2H + 3H : c < d ? 4H : 5H - 6H : 7H, e ? 8H : 9H"));
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testTernaryIfWithoutElse() {
+ parse("a ? 1H");
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testTernaryElseWithoutIf() {
+ parse("a : b");
+ }
+
+ @Test
+ public void testTernaryIfElseHigherPrecedence() {
+ assertEquals("{{a < 1H} ? {x + 1H} : {y + 2H}}", parse("a < 1H ? x + 1H : y + 2H"));
+ }
+
+ @Test
+ public void testTernaryIfElseLowerPrecedence() {
+ assertEquals("{0H, {{a ? 1H : 2H}, 3H}}", parse("0H, a ? 1H : 2H, 3H"));
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testTernaryIfElseLowerPrecedenceNegative() {
+ parse("a ? 1H, 2H : 3H");
+ }
+
+ @Test
+ public void testTernaryIfElseLowerPrecedenceGroup() {
+ assertEquals("{a ? ({1H, 2H}) : 3H}", parse("a ? (1H, 2H) : 3H"));
+ }
+
+ @Test
+ public void testSequence() {
+ assertEquals("{a, 1H}", parse("a, 1H"));
+ }
+
+ @Test
+ public void testSequence2() {
+ assertEquals("{{a + 1H}, {b + 2H}}", parse("a + 1H, b + 2H"));
+ }
+
+ @Test
+ public void testSequence3() {
+ assertEquals("{a, {{1H + 2H}, {3H + 4H}}}", parse("a, 1H + 2H, 3H + 4H"));
+ }
+
+ @Test
+ public void testSequenceInGroup() {
+ assertEquals("{1H + {({a, {2H, 3H}}) * b}}", parse("1H + (a, 2H, 3H) * b"));
+ }
+
+ @Test
+ public void testSequenceWithDoubleGroup() {
+ assertEquals("{a, {{({10H + 15H}) * ({5H - 2H})} + 4H}}", parse("a, (10H + 15H) * (5H - 2H) + 4H"));
+ }
+
+ @Test
+ public void testAnnotation() {
+ assertEquals("{a 1H}", parse("a 1H"));
+ }
+
+ @Test
+ public void testAnnotationTwice() {
+ assertEquals("{a {b 1H}}", parse("a b 1H"));
+ }
+
+ @Test
+ public void testAnnotationGroup() {
+ assertEquals("{a (1H)}", parse("a (1H)"));
+ }
+
+ @Test
+ public void testAnnotationNot() {
+ assertEquals("{a !1H}", parse("a !1H"));
+ }
+
+ @Test
+ public void testAnnotationComplement() {
+ assertEquals("{a ~1H}", parse("a ~1H"));
+ }
+
+ @Test
+ public void testAnnotationSubtract() {
+ assertEquals("{a {1H - 2H}}", parse("a 1H - 2H"));
+ }
+
+ @Test
+ public void testAnnotationLogicalOr() {
+ assertEquals("{a {1H || 2H}}", parse("a 1H || 2H"));
+ }
+
+ @Test
+ public void testAnnotationSequence() {
+ assertEquals("{{a 1H}, {b 2H}}", parse("a 1H, b 2H"));
+ }
+
+ @Test
+ public void testAnnotationInGroup() {
+ assertEquals("{a {1H || ({b 2H})}}", parse("a 1H || (b 2H)"));
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testAnnotationInTheMiddle() {
+ parse("a 1H || b 2H");
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testAnnotationNotAnIdentifier() {
+ parse("0 1H");
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testAnnotationNotAnIdentifier2() {
+ parse("a 0 1H");
+ }
+
+ @Test
+ public void testMultiline() {
+ assertEquals("{a + 1H}", parse("a +\n1H"));
+ }
+
+ @Test
+ public void testMultiline2() {
+ assertEquals("{a, 1H}", parse("a, ;\n 1H"));
+ }
+
+ @Test
+ public void testMultiline3() {
+ assertEquals("a", parse("a \n + 1H"));
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testMultilineLabel() {
+ assertEquals(null, parse("a +\ntest: 1H"));
+ }
+
+ @Test(expected=SyntaxError.class)
+ public void testIncomplete() {
+ assertEquals(null, parse("a,"));
+ }
+
+ public String parse(String text) {
+ LineNumberReader reader = new LineNumberReader(new StringReader(" test " + text));
+ Line line = new Parser().parse(reader, new Scope(), null);
+ return line.getArguments().toDebugString();
+ }
+
+}
diff --git a/src-tests/nl/grauw/glass/expressions/ExpressionTest.java b/src-tests/nl/grauw/glass/expressions/ExpressionTest.java
new file mode 100644
index 0000000..adba583
--- /dev/null
+++ b/src-tests/nl/grauw/glass/expressions/ExpressionTest.java
@@ -0,0 +1,277 @@
+package nl.grauw.glass.expressions;
+
+import static org.junit.Assert.*;
+
+import java.io.LineNumberReader;
+import java.io.StringReader;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Parser;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.ExpressionBuilder.ExpressionError;
+
+import org.junit.Test;
+
+public class ExpressionTest {
+
+ @Test
+ public void testPositive() {
+ assertEquals(100, parse("+100").getInteger());
+ }
+
+ @Test
+ public void testPositiveTwice() {
+ assertEquals(100, parse("++100").getInteger());
+ }
+
+ @Test
+ public void testNegative() {
+ assertEquals(-100, parse("-100").getInteger());
+ }
+
+ @Test
+ public void testNegativeTwice() {
+ assertEquals(100, parse("--100").getInteger());
+ }
+
+ @Test
+ public void testComplement() {
+ assertEquals(-5, parse("~4").getInteger());
+ }
+
+ @Test
+ public void testComplementTwice() {
+ assertEquals(4, parse("~~4").getInteger());
+ }
+
+ @Test
+ public void testNot() {
+ assertEquals(0, parse("!100").getInteger());
+ }
+
+ @Test
+ public void testNotTwice() {
+ assertEquals(-1, parse("!!100").getInteger());
+ }
+
+ @Test
+ public void testMultiply() {
+ assertEquals(99, parse("9 * 11").getInteger());
+ }
+
+ @Test
+ public void testDivide() {
+ assertEquals(3, parse("11 / 3").getInteger());
+ }
+
+ @Test(expected=EvaluationException.class)
+ public void testDivideByZero() {
+ parse("1 / 0").getInteger();
+ }
+
+ @Test
+ public void testModulo() {
+ assertEquals(2, parse("11 % 3").getInteger());
+ }
+
+ @Test(expected=EvaluationException.class)
+ public void testModuloByZero() {
+ parse("1 % 0").getInteger();
+ }
+
+ @Test
+ public void testAdd() {
+ assertEquals(9, parse("4 + 5").getInteger());
+ }
+
+ @Test
+ public void testSubtract() {
+ assertEquals(-4, parse("5 - 9").getInteger());
+ }
+
+ @Test
+ public void testShiftLeft() {
+ assertEquals(192, parse("3 << 6").getInteger());
+ }
+
+ @Test
+ public void testShiftRight() {
+ assertEquals(3, parse("193 >> 6").getInteger());
+ }
+
+ @Test
+ public void testShiftRightSign() {
+ assertEquals(-1, parse("-1 >> 16").getInteger());
+ }
+
+ @Test
+ public void testLessThan() {
+ assertEquals(-1, parse("3 < 4").getInteger());
+ assertEquals(0, parse("4 < 4").getInteger());
+ assertEquals(0, parse("5 < 4").getInteger());
+ }
+
+ @Test
+ public void testLessOrEquals() {
+ assertEquals(-1, parse("3 <= 4").getInteger());
+ assertEquals(-1, parse("4 <= 4").getInteger());
+ assertEquals(0, parse("5 <= 4").getInteger());
+ }
+
+ @Test
+ public void testGreaterThan() {
+ assertEquals(0, parse("3 > 4").getInteger());
+ assertEquals(0, parse("4 > 4").getInteger());
+ assertEquals(-1, parse("5 > 4").getInteger());
+ }
+
+ @Test
+ public void testGreaterOrEquals() {
+ assertEquals(0, parse("3 >= 4").getInteger());
+ assertEquals(-1, parse("4 >= 4").getInteger());
+ assertEquals(-1, parse("5 >= 4").getInteger());
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(0, parse("3 = 4").getInteger());
+ assertEquals(-1, parse("4 = 4").getInteger());
+ assertEquals(0, parse("5 = 4").getInteger());
+ }
+
+ @Test
+ public void testNotEquals() {
+ assertEquals(-1, parse("3 != 4").getInteger());
+ assertEquals(0, parse("4 != 4").getInteger());
+ assertEquals(-1, parse("5 != 4").getInteger());
+ }
+
+ @Test
+ public void testAnd() {
+ assertEquals(2, parse("6 & 3").getInteger());
+ }
+
+ @Test
+ public void testXor() {
+ assertEquals(5, parse("6 ^ 3").getInteger());
+ }
+
+ @Test
+ public void testOr() {
+ assertEquals(7, parse("6 | 3").getInteger());
+ }
+
+ @Test
+ public void testLogicalAnd() {
+ assertEquals(0, parse("0 && 0").getInteger());
+ assertEquals(0, parse("0 && 8").getInteger());
+ assertEquals(0, parse("-1 && 0").getInteger());
+ assertEquals(3, parse("1 && 3").getInteger());
+ }
+
+ @Test
+ public void testLogicalOr() {
+ assertEquals(0, parse("0 || 0").getInteger());
+ assertEquals(8, parse("0 || 8").getInteger());
+ assertEquals(-1, parse("-1 || 0").getInteger());
+ assertEquals(1, parse("1 || 3").getInteger());
+ }
+
+ @Test
+ public void testAnnotation() {
+ assertEquals("VIRTUAL", parse("VIRTUAL 15").getAnnotation().getName());
+ assertEquals(15, parse("VIRTUAL 15").getAnnotee().getInteger());
+ }
+
+ @Test
+ public void testSequence() {
+ assertEquals(3, parse("4, 5, 6").getList().size());
+ assertEquals(4, parse("4, 5, 6").getElement(0).getInteger());
+ assertEquals(5, parse("4, 5, 6").getElement(1).getInteger());
+ assertEquals(6, parse("4, 5, 6").getElement(2).getInteger());
+ assertEquals(null, parse("4, 5, 6").getElement(3));
+ }
+
+ @Test
+ public void testTernaryIfElse() {
+ assertEquals(2, parse("1 ? 2 : 3").getInteger());
+ assertEquals(3, parse("0 ? 2 : 3").getInteger());
+ }
+
+ @Test
+ public void testGroup() {
+ assertEquals(9, parse("(1 + 2) * 3").getInteger());
+ }
+
+ @Test
+ public void testIdentifier() {
+ Scope scope = new Scope();
+ scope.addSymbol("symbol", new IntegerLiteral(11));
+ assertEquals(11, parse("symbol", scope).getInteger());
+ }
+
+ @Test
+ public void testMember() {
+ Scope objectScope = new Scope();
+ objectScope.addSymbol("symbol", new IntegerLiteral(11));
+ Scope scope = new Scope();
+ scope.addSymbol("object", new ContextLiteral(objectScope));
+ assertEquals(11, parse("object.symbol", scope).getInteger());
+ }
+
+ @Test
+ public void testThisMember() {
+ Scope scope = new Scope();
+ scope.addSymbol("symbol", new IntegerLiteral(11));
+ assertEquals(11, parse("$.symbol", scope).getInteger());
+ }
+
+ @Test
+ public void testMemberOfExpression() {
+ Scope scope = new Scope();
+ scope.addSymbol("symbol", new IntegerLiteral(11));
+ assertEquals(11, parse("($).symbol", scope).getInteger());
+ }
+
+ @Test(expected=EvaluationException.class)
+ public void testMemberNoContext() {
+ parse("1.symbol").getInteger();
+ }
+
+ @Test(expected=ExpressionError.class)
+ public void testMemberNoIdentifier() {
+ parse("($).1").getInteger();
+ }
+
+ @Test
+ public void testIndex() {
+ assertEquals(4, parse("(4H, 5H)[0]").getInteger());
+ assertEquals(5, parse("(4H, 5H)[1]").getInteger());
+ }
+
+ @Test
+ public void testIndexNoSequence() {
+ assertEquals(4, parse("4H[0]").getInteger());
+ }
+
+ @Test(expected=EvaluationException.class)
+ public void testIndexOutOfBounds() {
+ parse("(4H, 5H)[2]").getInteger();
+ }
+
+ @Test(expected=EvaluationException.class)
+ public void testIndexNoSequenceOutOfBounds() {
+ parse("4H[1]").getInteger();
+ }
+
+ public Expression parse(String text) {
+ return parse(text, new Scope());
+ }
+
+ public Expression parse(String text, Scope scope) {
+ LineNumberReader reader = new LineNumberReader(new StringReader(" test " + text));
+ Line line = new Parser().parse(reader, scope, null);
+ return line.getArguments();
+ }
+
+}
diff --git a/src-tests/nl/grauw/glass/instructions/InstructionTest.java b/src-tests/nl/grauw/glass/instructions/InstructionTest.java
new file mode 100644
index 0000000..7d7913b
--- /dev/null
+++ b/src-tests/nl/grauw/glass/instructions/InstructionTest.java
@@ -0,0 +1,972 @@
+package nl.grauw.glass.instructions;
+
+import static org.junit.Assert.*;
+
+import java.io.LineNumberReader;
+import java.io.StringReader;
+
+import nl.grauw.glass.GlobalScope;
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Parser;
+import nl.grauw.glass.Scope;
+
+import org.junit.Test;
+
+public class InstructionTest {
+
+ @Test
+ public void testAdcA() {
+ assertArrayEquals(b(0x88), parse("adc a,b"));
+ assertArrayEquals(b(0x89), parse("adc a,c"));
+ assertArrayEquals(b(0x8A), parse("adc a,d"));
+ assertArrayEquals(b(0x8B), parse("adc a,e"));
+ assertArrayEquals(b(0x8C), parse("adc a,h"));
+ assertArrayEquals(b(0x8D), parse("adc a,l"));
+ assertArrayEquals(b(0x8E), parse("adc a,(hl)"));
+ assertArrayEquals(b(0x8F), parse("adc a,a"));
+ assertArrayEquals(b(0xDD, 0x8C), parse("adc a,ixh"));
+ assertArrayEquals(b(0xFD, 0x8D), parse("adc a,iyl"));
+ assertArrayEquals(b(0xDD, 0x8E, 0x47), parse("adc a,(ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0x8E, 0x86), parse("adc a,(iy - 7AH)"));
+ }
+
+ @Test
+ public void testAdcAN() {
+ assertArrayEquals(b(0xCE, 0x86), parse("adc a,86H"));
+ }
+
+ @Test
+ public void testAdcHL() {
+ assertArrayEquals(b(0xED, 0x4A), parse("adc hl,bc"));
+ assertArrayEquals(b(0xED, 0x5A), parse("adc hl,de"));
+ assertArrayEquals(b(0xED, 0x6A), parse("adc hl,hl"));
+ assertArrayEquals(b(0xED, 0x7A), parse("adc hl,sp"));
+ }
+
+ @Test
+ public void testAddA() {
+ assertArrayEquals(b(0x80), parse("add a,b"));
+ assertArrayEquals(b(0x81), parse("add a,c"));
+ assertArrayEquals(b(0x82), parse("add a,d"));
+ assertArrayEquals(b(0x83), parse("add a,e"));
+ assertArrayEquals(b(0x84), parse("add a,h"));
+ assertArrayEquals(b(0x85), parse("add a,l"));
+ assertArrayEquals(b(0x86), parse("add a,(hl)"));
+ assertArrayEquals(b(0x87), parse("add a,a"));
+ assertArrayEquals(b(0xDD, 0x84), parse("add a,ixh"));
+ assertArrayEquals(b(0xFD, 0x85), parse("add a,iyl"));
+ assertArrayEquals(b(0xDD, 0x86, 0x47), parse("add a,(ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0x86, 0x86), parse("add a,(iy - 7AH)"));
+ }
+
+ @Test
+ public void testAddAN() {
+ assertArrayEquals(b(0xC6, 0x86), parse("add a,86H"));
+ }
+
+ @Test
+ public void testAddHL() {
+ assertArrayEquals(b(0x09), parse("add hl,bc"));
+ assertArrayEquals(b(0x19), parse("add hl,de"));
+ assertArrayEquals(b(0x29), parse("add hl,hl"));
+ assertArrayEquals(b(0x39), parse("add hl,sp"));
+ assertArrayEquals(b(0xDD, 0x09), parse("add ix,bc"));
+ assertArrayEquals(b(0xDD, 0x19), parse("add ix,de"));
+ assertArrayEquals(b(0xDD, 0x29), parse("add ix,ix"));
+ assertArrayEquals(b(0xDD, 0x39), parse("add ix,sp"));
+ assertArrayEquals(b(0xFD, 0x09), parse("add iy,bc"));
+ assertArrayEquals(b(0xFD, 0x19), parse("add iy,de"));
+ assertArrayEquals(b(0xFD, 0x29), parse("add iy,iy"));
+ assertArrayEquals(b(0xFD, 0x39), parse("add iy,sp"));
+ }
+
+ @Test
+ public void testAnd() {
+ assertArrayEquals(b(0xA0), parse("and b"));
+ assertArrayEquals(b(0xA1), parse("and c"));
+ assertArrayEquals(b(0xA2), parse("and d"));
+ assertArrayEquals(b(0xA3), parse("and e"));
+ assertArrayEquals(b(0xA4), parse("and h"));
+ assertArrayEquals(b(0xA5), parse("and l"));
+ assertArrayEquals(b(0xA6), parse("and (hl)"));
+ assertArrayEquals(b(0xA7), parse("and a"));
+ assertArrayEquals(b(0xDD, 0xA4), parse("and ixh"));
+ assertArrayEquals(b(0xFD, 0xA5), parse("and iyl"));
+ assertArrayEquals(b(0xDD, 0xA6, 0x47), parse("and (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xA6, 0x86), parse("and (iy - 7AH)"));
+ }
+
+ @Test
+ public void testAndN() {
+ assertArrayEquals(b(0xE6, 0x86), parse("and 86H"));
+ }
+
+ @Test
+ public void testBit() {
+ assertArrayEquals(b(0xCB, 0x78), parse("bit 7,b"));
+ assertArrayEquals(b(0xCB, 0x71), parse("bit 6,c"));
+ assertArrayEquals(b(0xCB, 0x6A), parse("bit 5,d"));
+ assertArrayEquals(b(0xCB, 0x63), parse("bit 4,e"));
+ assertArrayEquals(b(0xCB, 0x5C), parse("bit 3,h"));
+ assertArrayEquals(b(0xCB, 0x55), parse("bit 2,l"));
+ assertArrayEquals(b(0xCB, 0x4E), parse("bit 1,(hl)"));
+ assertArrayEquals(b(0xCB, 0x47), parse("bit 0,a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x5E), parse("bit 3,(ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x66), parse("bit 4,(iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testBit_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x5C), parse("bit 3,ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x55), parse("bit 2,iyl"));
+ }
+
+ @Test
+ public void testCall() {
+ assertArrayEquals(b(0xCD, 0x86, 0x47), parse("call 4786H"));
+ }
+
+ @Test
+ public void testCall_F() {
+ assertArrayEquals(b(0xC4, 0x86, 0x47), parse("call nz,4786H"));
+ assertArrayEquals(b(0xCC, 0x86, 0x47), parse("call z,4786H"));
+ assertArrayEquals(b(0xD4, 0x86, 0x47), parse("call nc,4786H"));
+ assertArrayEquals(b(0xDC, 0x86, 0x47), parse("call c,4786H"));
+ assertArrayEquals(b(0xE4, 0x86, 0x47), parse("call po,4786H"));
+ assertArrayEquals(b(0xEC, 0x86, 0x47), parse("call pe,4786H"));
+ assertArrayEquals(b(0xF4, 0x86, 0x47), parse("call p,4786H"));
+ assertArrayEquals(b(0xFC, 0x86, 0x47), parse("call m,4786H"));
+ }
+
+ @Test
+ public void testCcf() {
+ assertArrayEquals(b(0x3F), parse("ccf"));
+ }
+
+ @Test
+ public void testCp() {
+ assertArrayEquals(b(0xB8), parse("cp b"));
+ assertArrayEquals(b(0xB9), parse("cp c"));
+ assertArrayEquals(b(0xBA), parse("cp d"));
+ assertArrayEquals(b(0xBB), parse("cp e"));
+ assertArrayEquals(b(0xBC), parse("cp h"));
+ assertArrayEquals(b(0xBD), parse("cp l"));
+ assertArrayEquals(b(0xBE), parse("cp (hl)"));
+ assertArrayEquals(b(0xBF), parse("cp a"));
+ assertArrayEquals(b(0xDD, 0xBC), parse("cp ixh"));
+ assertArrayEquals(b(0xFD, 0xBD), parse("cp iyl"));
+ assertArrayEquals(b(0xDD, 0xBE, 0x47), parse("cp (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xBE, 0x86), parse("cp (iy - 7AH)"));
+ }
+
+ @Test
+ public void testCpN() {
+ assertArrayEquals(b(0xFE, 0x86), parse("cp 86H"));
+ }
+
+ @Test
+ public void testCpd() {
+ assertArrayEquals(b(0xED, 0xA9), parse("cpd"));
+ }
+
+ @Test
+ public void testCpdr() {
+ assertArrayEquals(b(0xED, 0xB9), parse("cpdr"));
+ }
+
+ @Test
+ public void testCpi() {
+ assertArrayEquals(b(0xED, 0xA1), parse("cpi"));
+ }
+
+ @Test
+ public void testCpir() {
+ assertArrayEquals(b(0xED, 0xB1), parse("cpir"));
+ }
+
+ @Test
+ public void testCpl() {
+ assertArrayEquals(b(0x2F), parse("cpl"));
+ }
+
+ @Test
+ public void testDaa() {
+ assertArrayEquals(b(0x27), parse("daa"));
+ }
+
+ @Test
+ public void testDb() {
+ assertArrayEquals(b(0x86), parse("db 86H"));
+ assertArrayEquals(b(0x11, 0x22, 0x33, 0x44), parse("db 11H, 22H, 33H, 44H"));
+ assertArrayEquals(b(0x11, 0x61, 0x62, 0x63, 0x22), parse("db 11H, \"abc\", 22H"));
+ }
+
+ @Test
+ public void testDd() {
+ assertArrayEquals(b(0xCC, 0xDD, 0xEE, 0xFF), parse("dd 0FFEEDDCCH"));
+ assertArrayEquals(b(0x00, 0x00, 0x00, 0x80), parse("dd -80000000H"));
+ assertArrayEquals(b(0x44, 0x33, 0x22, 0x11, 0x88, 0x77, 0x66, 0x55), parse("dd 11223344H, 55667788H"));
+ }
+
+ @Test
+ public void testDec() {
+ assertArrayEquals(b(0x05), parse("dec b"));
+ assertArrayEquals(b(0x0D), parse("dec c"));
+ assertArrayEquals(b(0x15), parse("dec d"));
+ assertArrayEquals(b(0x1D), parse("dec e"));
+ assertArrayEquals(b(0x25), parse("dec h"));
+ assertArrayEquals(b(0x2D), parse("dec l"));
+ assertArrayEquals(b(0x35), parse("dec (hl)"));
+ assertArrayEquals(b(0x3D), parse("dec a"));
+ assertArrayEquals(b(0xDD, 0x25), parse("dec ixh"));
+ assertArrayEquals(b(0xFD, 0x2D), parse("dec iyl"));
+ assertArrayEquals(b(0xDD, 0x35, 0x47), parse("dec (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0x35, 0x86), parse("dec (iy - 7AH)"));
+ }
+
+ @Test
+ public void testDec_RR() {
+ assertArrayEquals(b(0x0B), parse("dec bc"));
+ assertArrayEquals(b(0x1B), parse("dec de"));
+ assertArrayEquals(b(0x2B), parse("dec hl"));
+ assertArrayEquals(b(0x3B), parse("dec sp"));
+ assertArrayEquals(b(0xDD, 0x2B), parse("dec ix"));
+ assertArrayEquals(b(0xFD, 0x2B), parse("dec iy"));
+ }
+
+ @Test
+ public void testDi() {
+ assertArrayEquals(b(0xF3), parse("di"));
+ }
+
+ @Test
+ public void testDjnz() {
+ assertArrayEquals(b(0x10, 0xFE), parse("djnz $"));
+ }
+
+ @Test
+ public void testDs() {
+ assertArrayEquals(b(0x00, 0x00, 0x00, 0x00, 0x00), parse("ds 5H"));
+ assertArrayEquals(b(0x47, 0x47, 0x47, 0x47, 0x47), parse("ds 5H,47H"));
+ }
+
+ @Test
+ public void testDw() {
+ assertArrayEquals(b(0x86, 0x47), parse("dw 4786H"));
+ assertArrayEquals(b(0x22, 0x11, 0x44, 0x33, 0x66, 0x55), parse("dw 1122H, 3344H, 5566H"));
+ assertArrayEquals(b(0x22, 0x11, 0xAC, 0x20, 0x31, 0x00, 0x30, 0x00), parse("dw 1122H, \"€10\""));
+ }
+
+ @Test
+ public void testEi() {
+ assertArrayEquals(b(0xFB), parse("ei"));
+ }
+
+ @Test
+ public void testExAF() {
+ assertArrayEquals(b(0x08), parse("ex af,af'"));
+ }
+
+ @Test
+ public void testExDEHL() {
+ assertArrayEquals(b(0xEB), parse("ex de,hl"));
+ }
+
+ @Test
+ public void testExSP() {
+ assertArrayEquals(b(0xE3), parse("ex (sp),hl"));
+ assertArrayEquals(b(0xDD, 0xE3), parse("ex (sp),ix"));
+ assertArrayEquals(b(0xFD, 0xE3), parse("ex (sp),iy"));
+ }
+
+ @Test
+ public void testExx() {
+ assertArrayEquals(b(0xD9), parse("exx"));
+ }
+
+ @Test
+ public void testHalt() {
+ assertArrayEquals(b(0x76), parse("halt"));
+ }
+
+ @Test
+ public void testIm() {
+ assertArrayEquals(b(0xED, 0x46), parse("im 0"));
+ assertArrayEquals(b(0xED, 0x56), parse("im 1"));
+ assertArrayEquals(b(0xED, 0x5E), parse("im 2"));
+ }
+
+ @Test
+ public void testIn_C() {
+ assertArrayEquals(b(0xED, 0x40), parse("in b,(c)"));
+ assertArrayEquals(b(0xED, 0x48), parse("in c,(c)"));
+ assertArrayEquals(b(0xED, 0x50), parse("in d,(c)"));
+ assertArrayEquals(b(0xED, 0x58), parse("in e,(c)"));
+ assertArrayEquals(b(0xED, 0x60), parse("in h,(c)"));
+ assertArrayEquals(b(0xED, 0x68), parse("in l,(c)"));
+ assertArrayEquals(b(0xED, 0x70), parse("in (c)"));
+ assertArrayEquals(b(0xED, 0x78), parse("in a,(c)"));
+ }
+
+ @Test
+ public void testIn_N() {
+ assertArrayEquals(b(0xDB, 0x86), parse("in a,(86H)"));
+ }
+
+ @Test
+ public void testInc() {
+ assertArrayEquals(b(0x04), parse("inc b"));
+ assertArrayEquals(b(0x0C), parse("inc c"));
+ assertArrayEquals(b(0x14), parse("inc d"));
+ assertArrayEquals(b(0x1C), parse("inc e"));
+ assertArrayEquals(b(0x24), parse("inc h"));
+ assertArrayEquals(b(0x2C), parse("inc l"));
+ assertArrayEquals(b(0x34), parse("inc (hl)"));
+ assertArrayEquals(b(0x3C), parse("inc a"));
+ assertArrayEquals(b(0xDD, 0x24), parse("inc ixh"));
+ assertArrayEquals(b(0xFD, 0x2C), parse("inc iyl"));
+ assertArrayEquals(b(0xDD, 0x34, 0x47), parse("inc (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0x34, 0x86), parse("inc (iy - 7AH)"));
+ }
+
+ @Test
+ public void testInc_RR() {
+ assertArrayEquals(b(0x03), parse("inc bc"));
+ assertArrayEquals(b(0x13), parse("inc de"));
+ assertArrayEquals(b(0x23), parse("inc hl"));
+ assertArrayEquals(b(0x33), parse("inc sp"));
+ assertArrayEquals(b(0xDD, 0x23), parse("inc ix"));
+ assertArrayEquals(b(0xFD, 0x23), parse("inc iy"));
+ }
+
+ @Test
+ public void testInd() {
+ assertArrayEquals(b(0xED, 0xAA), parse("ind"));
+ }
+
+ @Test
+ public void testIndr() {
+ assertArrayEquals(b(0xED, 0xBA), parse("indr"));
+ }
+
+ @Test
+ public void testIni() {
+ assertArrayEquals(b(0xED, 0xA2), parse("ini"));
+ }
+
+ @Test
+ public void testInir() {
+ assertArrayEquals(b(0xED, 0xB2), parse("inir"));
+ }
+
+ @Test
+ public void testJp() {
+ assertArrayEquals(b(0xC3, 0x86, 0x47), parse("jp 4786H"));
+ }
+
+ @Test
+ public void testJp_F() {
+ assertArrayEquals(b(0xC2, 0x86, 0x47), parse("jp nz,4786H"));
+ assertArrayEquals(b(0xCA, 0x86, 0x47), parse("jp z,4786H"));
+ assertArrayEquals(b(0xD2, 0x86, 0x47), parse("jp nc,4786H"));
+ assertArrayEquals(b(0xDA, 0x86, 0x47), parse("jp c,4786H"));
+ assertArrayEquals(b(0xE2, 0x86, 0x47), parse("jp po,4786H"));
+ assertArrayEquals(b(0xEA, 0x86, 0x47), parse("jp pe,4786H"));
+ assertArrayEquals(b(0xF2, 0x86, 0x47), parse("jp p,4786H"));
+ assertArrayEquals(b(0xFA, 0x86, 0x47), parse("jp m,4786H"));
+ }
+
+ @Test
+ public void testJp_HL() {
+ assertArrayEquals(b(0xE9), parse("jp (hl)"));
+ assertArrayEquals(b(0xDD, 0xE9), parse("jp (ix)"));
+ assertArrayEquals(b(0xFD, 0xE9), parse("jp (iy)"));
+ }
+
+ @Test
+ public void testJr() {
+ assertArrayEquals(b(0x18, 0xFE), parse("jr $"));
+ }
+
+ @Test
+ public void testJr_F() {
+ assertArrayEquals(b(0x20, 0xFE), parse("jr nz,$"));
+ assertArrayEquals(b(0x28, 0xFE), parse("jr z,$"));
+ assertArrayEquals(b(0x30, 0xFE), parse("jr nc,$"));
+ assertArrayEquals(b(0x38, 0xFE), parse("jr c,$"));
+ }
+
+ @Test
+ public void testLd_R_R() {
+ assertArrayEquals(b(0x78), parse("ld a,b"));
+ assertArrayEquals(b(0x71), parse("ld (hl),c"));
+ assertArrayEquals(b(0x6A), parse("ld l,d"));
+ assertArrayEquals(b(0x63), parse("ld h,e"));
+ assertArrayEquals(b(0x5C), parse("ld e,h"));
+ assertArrayEquals(b(0x55), parse("ld d,l"));
+ assertArrayEquals(b(0x4E), parse("ld c,(hl)"));
+ assertArrayEquals(b(0x47), parse("ld b,a"));
+ assertArrayEquals(b(0xDD, 0x6C), parse("ld ixl,ixh"));
+ assertArrayEquals(b(0xFD, 0x55), parse("ld d,iyl"));
+ assertArrayEquals(b(0xDD, 0x6E, 0x47), parse("ld l,(ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0x74, 0x86), parse("ld (iy - 7AH),h"));
+ }
+
+ @Test
+ public void testLd_R_N() {
+ assertArrayEquals(b(0x06, 0x86), parse("ld b,86H"));
+ assertArrayEquals(b(0x0E, 0x86), parse("ld c,86H"));
+ assertArrayEquals(b(0x16, 0x86), parse("ld d,86H"));
+ assertArrayEquals(b(0x1E, 0x86), parse("ld e,86H"));
+ assertArrayEquals(b(0x26, 0x86), parse("ld h,86H"));
+ assertArrayEquals(b(0x2E, 0x86), parse("ld l,86H"));
+ assertArrayEquals(b(0x36, 0x86), parse("ld (hl),86H"));
+ assertArrayEquals(b(0x3E, 0x86), parse("ld a,86H"));
+ assertArrayEquals(b(0xDD, 0x26, 0x86), parse("ld ixh,86H"));
+ assertArrayEquals(b(0xFD, 0x2E, 0x86), parse("ld iyl,86H"));
+ assertArrayEquals(b(0xDD, 0x36, 0x47, 0x86), parse("ld (ix + 47H),86H"));
+ assertArrayEquals(b(0xFD, 0x36, 0x86, 0x47), parse("ld (iy - 7AH),47H"));
+ }
+
+ @Test
+ public void testLd_A_IR() {
+ assertArrayEquals(b(0xED, 0x57), parse("ld a,i"));
+ assertArrayEquals(b(0xED, 0x5F), parse("ld a,r"));
+ }
+
+ @Test
+ public void testLd_IR_A() {
+ assertArrayEquals(b(0xED, 0x47), parse("ld i,a"));
+ assertArrayEquals(b(0xED, 0x4F), parse("ld r,a"));
+ }
+
+ @Test
+ public void testLd_A_BCDE() {
+ assertArrayEquals(b(0x0A), parse("ld a,(bc)"));
+ assertArrayEquals(b(0x1A), parse("ld a,(de)"));
+ }
+
+ @Test
+ public void testLd_A_NN() {
+ assertArrayEquals(b(0x3A, 0x86, 0x47), parse("ld a,(4786H)"));
+ }
+
+ @Test
+ public void testLd_HL_NN() {
+ assertArrayEquals(b(0x2A, 0x86, 0x47), parse("ld hl,(4786H)"));
+ assertArrayEquals(b(0xDD, 0x2A, 0x86, 0x47), parse("ld ix,(4786H)"));
+ assertArrayEquals(b(0xFD, 0x2A, 0x86, 0x47), parse("ld iy,(4786H)"));
+ }
+
+ @Test
+ public void testLd_RR_NN() {
+ assertArrayEquals(b(0xED, 0x4B, 0x86, 0x47), parse("ld bc,(4786H)"));
+ assertArrayEquals(b(0xED, 0x5B, 0x86, 0x47), parse("ld de,(4786H)"));
+ assertArrayEquals(b(0xED, 0x7B, 0x86, 0x47), parse("ld sp,(4786H)"));
+ }
+
+ @Test
+ public void testLd_BCDE_A() {
+ assertArrayEquals(b(0x02), parse("ld (bc),a"));
+ assertArrayEquals(b(0x12), parse("ld (de),a"));
+ }
+
+ @Test
+ public void testLd_NN_A() {
+ assertArrayEquals(b(0x32, 0x86, 0x47), parse("ld (4786H),a"));
+ }
+
+ @Test
+ public void testLd_NN_HL() {
+ assertArrayEquals(b(0x22, 0x86, 0x47), parse("ld (4786H),hl"));
+ assertArrayEquals(b(0xDD, 0x22, 0x86, 0x47), parse("ld (4786H),ix"));
+ assertArrayEquals(b(0xFD, 0x22, 0x86, 0x47), parse("ld (4786H),iy"));
+ }
+
+ @Test
+ public void testLd_NN_RR() {
+ assertArrayEquals(b(0xED, 0x43, 0x86, 0x47), parse("ld (4786H),bc"));
+ assertArrayEquals(b(0xED, 0x53, 0x86, 0x47), parse("ld (4786H),de"));
+ assertArrayEquals(b(0xED, 0x73, 0x86, 0x47), parse("ld (4786H),sp"));
+ }
+
+ @Test
+ public void testLd_RR_N() {
+ assertArrayEquals(b(0x01, 0x86, 0x47), parse("ld bc,4786H"));
+ assertArrayEquals(b(0x11, 0x86, 0x47), parse("ld de,4786H"));
+ assertArrayEquals(b(0x21, 0x86, 0x47), parse("ld hl,4786H"));
+ assertArrayEquals(b(0x31, 0x86, 0x47), parse("ld sp,4786H"));
+ assertArrayEquals(b(0xDD, 0x21, 0x86, 0x47), parse("ld ix,4786H"));
+ assertArrayEquals(b(0xFD, 0x21, 0x86, 0x47), parse("ld iy,4786H"));
+ }
+
+ @Test
+ public void testLd_SP_HL() {
+ assertArrayEquals(b(0xF9), parse("ld sp,hl"));
+ assertArrayEquals(b(0xDD, 0xF9), parse("ld sp,ix"));
+ assertArrayEquals(b(0xFD, 0xF9), parse("ld sp,iy"));
+ }
+
+ @Test
+ public void testLdd() {
+ assertArrayEquals(b(0xED, 0xA8), parse("ldd"));
+ }
+
+ @Test
+ public void testLddr() {
+ assertArrayEquals(b(0xED, 0xB8), parse("lddr"));
+ }
+
+ @Test
+ public void testLdi() {
+ assertArrayEquals(b(0xED, 0xA0), parse("ldi"));
+ }
+
+ @Test
+ public void testLdir() {
+ assertArrayEquals(b(0xED, 0xB0), parse("ldir"));
+ }
+
+ @Test
+ public void testMulub() {
+ assertArrayEquals(b(0xED, 0xC1), parse("mulub a,b"));
+ assertArrayEquals(b(0xED, 0xC9), parse("mulub a,c"));
+ assertArrayEquals(b(0xED, 0xD1), parse("mulub a,d"));
+ assertArrayEquals(b(0xED, 0xD9), parse("mulub a,e"));
+ }
+
+ @Test
+ public void testMuluw() {
+ assertArrayEquals(b(0xED, 0xC3), parse("muluw hl,bc"));
+ assertArrayEquals(b(0xED, 0xF3), parse("muluw hl,sp"));
+ }
+
+ @Test
+ public void testNeg() {
+ assertArrayEquals(b(0xED, 0x44), parse("neg"));
+ }
+
+ @Test
+ public void testNop() {
+ assertArrayEquals(b(0x00), parse("nop"));
+ }
+
+ @Test
+ public void testOr() {
+ assertArrayEquals(b(0xB0), parse("or b"));
+ assertArrayEquals(b(0xB1), parse("or c"));
+ assertArrayEquals(b(0xB2), parse("or d"));
+ assertArrayEquals(b(0xB3), parse("or e"));
+ assertArrayEquals(b(0xB4), parse("or h"));
+ assertArrayEquals(b(0xB5), parse("or l"));
+ assertArrayEquals(b(0xB6), parse("or (hl)"));
+ assertArrayEquals(b(0xB7), parse("or a"));
+ assertArrayEquals(b(0xDD, 0xB4), parse("or ixh"));
+ assertArrayEquals(b(0xFD, 0xB5), parse("or iyl"));
+ assertArrayEquals(b(0xDD, 0xB6, 0x47), parse("or (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xB6, 0x86), parse("or (iy - 7AH)"));
+ }
+
+ @Test
+ public void testOrN() {
+ assertArrayEquals(b(0xF6, 0x86), parse("or 86H"));
+ }
+
+ @Test
+ public void testOtdr() {
+ assertArrayEquals(b(0xED, 0xBB), parse("otdr"));
+ }
+
+ @Test
+ public void testOtir() {
+ assertArrayEquals(b(0xED, 0xB3), parse("otir"));
+ }
+
+ @Test
+ public void testOut_C() {
+ assertArrayEquals(b(0xED, 0x41), parse("out (c),b"));
+ assertArrayEquals(b(0xED, 0x49), parse("out (c),c"));
+ assertArrayEquals(b(0xED, 0x51), parse("out (c),d"));
+ assertArrayEquals(b(0xED, 0x59), parse("out (c),e"));
+ assertArrayEquals(b(0xED, 0x61), parse("out (c),h"));
+ assertArrayEquals(b(0xED, 0x69), parse("out (c),l"));
+ assertArrayEquals(b(0xED, 0x79), parse("out (c),a"));
+ }
+
+ @Test
+ public void testOut_N() {
+ assertArrayEquals(b(0xD3, 0x86), parse("out (86H),a"));
+ }
+
+ @Test
+ public void testOutd() {
+ assertArrayEquals(b(0xED, 0xAB), parse("outd"));
+ }
+
+ @Test
+ public void testOuti() {
+ assertArrayEquals(b(0xED, 0xA3), parse("outi"));
+ }
+
+ @Test
+ public void testPop() {
+ assertArrayEquals(b(0xC1), parse("pop bc"));
+ assertArrayEquals(b(0xD1), parse("pop de"));
+ assertArrayEquals(b(0xE1), parse("pop hl"));
+ assertArrayEquals(b(0xF1), parse("pop af"));
+ assertArrayEquals(b(0xDD, 0xE1), parse("pop ix"));
+ assertArrayEquals(b(0xFD, 0xE1), parse("pop iy"));
+ }
+
+ @Test
+ public void testPush() {
+ assertArrayEquals(b(0xC5), parse("push bc"));
+ assertArrayEquals(b(0xD5), parse("push de"));
+ assertArrayEquals(b(0xE5), parse("push hl"));
+ assertArrayEquals(b(0xF5), parse("push af"));
+ assertArrayEquals(b(0xDD, 0xE5), parse("push ix"));
+ assertArrayEquals(b(0xFD, 0xE5), parse("push iy"));
+ }
+
+ @Test
+ public void testRes() {
+ assertArrayEquals(b(0xCB, 0xB8), parse("res 7,b"));
+ assertArrayEquals(b(0xCB, 0xB1), parse("res 6,c"));
+ assertArrayEquals(b(0xCB, 0xAA), parse("res 5,d"));
+ assertArrayEquals(b(0xCB, 0xA3), parse("res 4,e"));
+ assertArrayEquals(b(0xCB, 0x9C), parse("res 3,h"));
+ assertArrayEquals(b(0xCB, 0x95), parse("res 2,l"));
+ assertArrayEquals(b(0xCB, 0x8E), parse("res 1,(hl)"));
+ assertArrayEquals(b(0xCB, 0x87), parse("res 0,a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x9E), parse("res 3,(ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0xA6), parse("res 4,(iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testRes_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x9C), parse("res 3,ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x95), parse("res 2,iyl"));
+ }
+
+ @Test
+ public void testRet() {
+ assertArrayEquals(b(0xC9), parse("ret"));
+ }
+
+ @Test
+ public void testRetF() {
+ assertArrayEquals(b(0xC0), parse("ret nz"));
+ assertArrayEquals(b(0xC8), parse("ret z"));
+ assertArrayEquals(b(0xD0), parse("ret nc"));
+ assertArrayEquals(b(0xD8), parse("ret c"));
+ assertArrayEquals(b(0xE0), parse("ret po"));
+ assertArrayEquals(b(0xE8), parse("ret pe"));
+ assertArrayEquals(b(0xF0), parse("ret p"));
+ assertArrayEquals(b(0xF8), parse("ret m"));
+ }
+
+ @Test
+ public void testReti() {
+ assertArrayEquals(b(0xED, 0x4D), parse("reti"));
+ }
+
+ @Test
+ public void testRetn() {
+ assertArrayEquals(b(0xED, 0x45), parse("retn"));
+ }
+
+ @Test
+ public void testRl() {
+ assertArrayEquals(b(0xCB, 0x10), parse("rl b"));
+ assertArrayEquals(b(0xCB, 0x11), parse("rl c"));
+ assertArrayEquals(b(0xCB, 0x12), parse("rl d"));
+ assertArrayEquals(b(0xCB, 0x13), parse("rl e"));
+ assertArrayEquals(b(0xCB, 0x14), parse("rl h"));
+ assertArrayEquals(b(0xCB, 0x15), parse("rl l"));
+ assertArrayEquals(b(0xCB, 0x16), parse("rl (hl)"));
+ assertArrayEquals(b(0xCB, 0x17), parse("rl a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x16), parse("rl (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x16), parse("rl (iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testRl_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x14), parse("rl ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x15), parse("rl iyl"));
+ }
+
+ @Test
+ public void testRla() {
+ assertArrayEquals(b(0x17), parse("rla"));
+ }
+
+ @Test
+ public void testRlc() {
+ assertArrayEquals(b(0xCB, 0x00), parse("rlc b"));
+ assertArrayEquals(b(0xCB, 0x01), parse("rlc c"));
+ assertArrayEquals(b(0xCB, 0x02), parse("rlc d"));
+ assertArrayEquals(b(0xCB, 0x03), parse("rlc e"));
+ assertArrayEquals(b(0xCB, 0x04), parse("rlc h"));
+ assertArrayEquals(b(0xCB, 0x05), parse("rlc l"));
+ assertArrayEquals(b(0xCB, 0x06), parse("rlc (hl)"));
+ assertArrayEquals(b(0xCB, 0x07), parse("rlc a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x06), parse("rlc (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x06), parse("rlc (iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testRlc_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x04), parse("rlc ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x05), parse("rlc iyl"));
+ }
+
+ @Test
+ public void testRlca() {
+ assertArrayEquals(b(0x07), parse("rlca"));
+ }
+
+ @Test
+ public void testRld() {
+ assertArrayEquals(b(0xED, 0x6F), parse("rld"));
+ }
+
+ @Test
+ public void testRr() {
+ assertArrayEquals(b(0xCB, 0x18), parse("rr b"));
+ assertArrayEquals(b(0xCB, 0x19), parse("rr c"));
+ assertArrayEquals(b(0xCB, 0x1A), parse("rr d"));
+ assertArrayEquals(b(0xCB, 0x1B), parse("rr e"));
+ assertArrayEquals(b(0xCB, 0x1C), parse("rr h"));
+ assertArrayEquals(b(0xCB, 0x1D), parse("rr l"));
+ assertArrayEquals(b(0xCB, 0x1E), parse("rr (hl)"));
+ assertArrayEquals(b(0xCB, 0x1F), parse("rr a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x1E), parse("rr (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x1E), parse("rr (iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testRr_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x1C), parse("rr ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x1D), parse("rr iyl"));
+ }
+
+ @Test
+ public void testRra() {
+ assertArrayEquals(b(0x1F), parse("rra"));
+ }
+
+ @Test
+ public void testRrc() {
+ assertArrayEquals(b(0xCB, 0x08), parse("rrc b"));
+ assertArrayEquals(b(0xCB, 0x09), parse("rrc c"));
+ assertArrayEquals(b(0xCB, 0x0A), parse("rrc d"));
+ assertArrayEquals(b(0xCB, 0x0B), parse("rrc e"));
+ assertArrayEquals(b(0xCB, 0x0C), parse("rrc h"));
+ assertArrayEquals(b(0xCB, 0x0D), parse("rrc l"));
+ assertArrayEquals(b(0xCB, 0x0E), parse("rrc (hl)"));
+ assertArrayEquals(b(0xCB, 0x0F), parse("rrc a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x0E), parse("rrc (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x0E), parse("rrc (iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testRrc_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x0C), parse("rrc ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x0D), parse("rrc iyl"));
+ }
+
+ @Test
+ public void testRrca() {
+ assertArrayEquals(b(0x0F), parse("rrca"));
+ }
+
+ @Test
+ public void testRrd() {
+ assertArrayEquals(b(0xED, 0x67), parse("rrd"));
+ }
+
+ @Test
+ public void testRst() {
+ assertArrayEquals(b(0xC7), parse("rst 00H"));
+ assertArrayEquals(b(0xCF), parse("rst 08H"));
+ assertArrayEquals(b(0xD7), parse("rst 10H"));
+ assertArrayEquals(b(0xDF), parse("rst 18H"));
+ assertArrayEquals(b(0xE7), parse("rst 20H"));
+ assertArrayEquals(b(0xEF), parse("rst 28H"));
+ assertArrayEquals(b(0xF7), parse("rst 30H"));
+ assertArrayEquals(b(0xFF), parse("rst 38H"));
+ }
+
+ @Test
+ public void testSbcA() {
+ assertArrayEquals(b(0x98), parse("sbc a,b"));
+ assertArrayEquals(b(0x99), parse("sbc a,c"));
+ assertArrayEquals(b(0x9A), parse("sbc a,d"));
+ assertArrayEquals(b(0x9B), parse("sbc a,e"));
+ assertArrayEquals(b(0x9C), parse("sbc a,h"));
+ assertArrayEquals(b(0x9D), parse("sbc a,l"));
+ assertArrayEquals(b(0x9E), parse("sbc a,(hl)"));
+ assertArrayEquals(b(0x9F), parse("sbc a,a"));
+ assertArrayEquals(b(0xDD, 0x9C), parse("sbc a,ixh"));
+ assertArrayEquals(b(0xFD, 0x9D), parse("sbc a,iyl"));
+ assertArrayEquals(b(0xDD, 0x9E, 0x47), parse("sbc a,(ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0x9E, 0x86), parse("sbc a,(iy - 7AH)"));
+ }
+
+ @Test
+ public void testSbcAN() {
+ assertArrayEquals(b(0xDE, 0x86), parse("sbc a,86H"));
+ }
+
+ @Test
+ public void testSbcHL() {
+ assertArrayEquals(b(0xED, 0x42), parse("sbc hl,bc"));
+ assertArrayEquals(b(0xED, 0x52), parse("sbc hl,de"));
+ assertArrayEquals(b(0xED, 0x62), parse("sbc hl,hl"));
+ assertArrayEquals(b(0xED, 0x72), parse("sbc hl,sp"));
+ }
+
+ @Test
+ public void testScf() {
+ assertArrayEquals(b(0x37), parse("scf"));
+ }
+
+ @Test
+ public void testSet() {
+ assertArrayEquals(b(0xCB, 0xF8), parse("set 7,b"));
+ assertArrayEquals(b(0xCB, 0xF1), parse("set 6,c"));
+ assertArrayEquals(b(0xCB, 0xEA), parse("set 5,d"));
+ assertArrayEquals(b(0xCB, 0xE3), parse("set 4,e"));
+ assertArrayEquals(b(0xCB, 0xDC), parse("set 3,h"));
+ assertArrayEquals(b(0xCB, 0xD5), parse("set 2,l"));
+ assertArrayEquals(b(0xCB, 0xCE), parse("set 1,(hl)"));
+ assertArrayEquals(b(0xCB, 0xC7), parse("set 0,a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0xDE), parse("set 3,(ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0xE6), parse("set 4,(iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testSet_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0xDC), parse("set 3,ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0xD5), parse("set 2,iyl"));
+ }
+
+ @Test
+ public void testSla() {
+ assertArrayEquals(b(0xCB, 0x20), parse("sla b"));
+ assertArrayEquals(b(0xCB, 0x21), parse("sla c"));
+ assertArrayEquals(b(0xCB, 0x22), parse("sla d"));
+ assertArrayEquals(b(0xCB, 0x23), parse("sla e"));
+ assertArrayEquals(b(0xCB, 0x24), parse("sla h"));
+ assertArrayEquals(b(0xCB, 0x25), parse("sla l"));
+ assertArrayEquals(b(0xCB, 0x26), parse("sla (hl)"));
+ assertArrayEquals(b(0xCB, 0x27), parse("sla a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x26), parse("sla (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x26), parse("sla (iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testSla_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x24), parse("sla ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x25), parse("sla iyl"));
+ }
+
+ @Test
+ public void testSra() {
+ assertArrayEquals(b(0xCB, 0x28), parse("sra b"));
+ assertArrayEquals(b(0xCB, 0x29), parse("sra c"));
+ assertArrayEquals(b(0xCB, 0x2A), parse("sra d"));
+ assertArrayEquals(b(0xCB, 0x2B), parse("sra e"));
+ assertArrayEquals(b(0xCB, 0x2C), parse("sra h"));
+ assertArrayEquals(b(0xCB, 0x2D), parse("sra l"));
+ assertArrayEquals(b(0xCB, 0x2E), parse("sra (hl)"));
+ assertArrayEquals(b(0xCB, 0x2F), parse("sra a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x2E), parse("sra (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x2E), parse("sra (iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testSra_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x2C), parse("sra ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x2D), parse("sra iyl"));
+ }
+
+ @Test
+ public void testSrl() {
+ assertArrayEquals(b(0xCB, 0x38), parse("srl b"));
+ assertArrayEquals(b(0xCB, 0x39), parse("srl c"));
+ assertArrayEquals(b(0xCB, 0x3A), parse("srl d"));
+ assertArrayEquals(b(0xCB, 0x3B), parse("srl e"));
+ assertArrayEquals(b(0xCB, 0x3C), parse("srl h"));
+ assertArrayEquals(b(0xCB, 0x3D), parse("srl l"));
+ assertArrayEquals(b(0xCB, 0x3E), parse("srl (hl)"));
+ assertArrayEquals(b(0xCB, 0x3F), parse("srl a"));
+ assertArrayEquals(b(0xDD, 0xCB, 0x47, 0x3E), parse("srl (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x86, 0x3E), parse("srl (iy - 7AH)"));
+ }
+
+ @Test(expected=ArgumentException.class)
+ public void testSrl_Invalid() {
+ assertArrayEquals(b(0xDD, 0xCB, 0x3C), parse("srl ixh"));
+ assertArrayEquals(b(0xFD, 0xCB, 0x3D), parse("srl iyl"));
+ }
+
+ @Test
+ public void testSub() {
+ assertArrayEquals(b(0x90), parse("sub b"));
+ assertArrayEquals(b(0x91), parse("sub c"));
+ assertArrayEquals(b(0x92), parse("sub d"));
+ assertArrayEquals(b(0x93), parse("sub e"));
+ assertArrayEquals(b(0x94), parse("sub h"));
+ assertArrayEquals(b(0x95), parse("sub l"));
+ assertArrayEquals(b(0x96), parse("sub (hl)"));
+ assertArrayEquals(b(0x97), parse("sub a"));
+ assertArrayEquals(b(0xDD, 0x94), parse("sub ixh"));
+ assertArrayEquals(b(0xFD, 0x95), parse("sub iyl"));
+ assertArrayEquals(b(0xDD, 0x96, 0x47), parse("sub (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0x96, 0x86), parse("sub (iy - 7AH)"));
+ }
+
+ @Test
+ public void testSubN() {
+ assertArrayEquals(b(0xD6, 0x86), parse("sub 86H"));
+ }
+
+ @Test
+ public void testXor() {
+ assertArrayEquals(b(0xA8), parse("xor b"));
+ assertArrayEquals(b(0xA9), parse("xor c"));
+ assertArrayEquals(b(0xAA), parse("xor d"));
+ assertArrayEquals(b(0xAB), parse("xor e"));
+ assertArrayEquals(b(0xAC), parse("xor h"));
+ assertArrayEquals(b(0xAD), parse("xor l"));
+ assertArrayEquals(b(0xAE), parse("xor (hl)"));
+ assertArrayEquals(b(0xAF), parse("xor a"));
+ assertArrayEquals(b(0xDD, 0xAC), parse("xor ixh"));
+ assertArrayEquals(b(0xFD, 0xAD), parse("xor iyl"));
+ assertArrayEquals(b(0xDD, 0xAE, 0x47), parse("xor (ix + 47H)"));
+ assertArrayEquals(b(0xFD, 0xAE, 0x86), parse("xor (iy - 7AH)"));
+ }
+
+ @Test
+ public void testXorN() {
+ assertArrayEquals(b(0xEE, 0x86), parse("xor 86H"));
+ }
+
+ public byte[] parse(String string) {
+ LineNumberReader reader = new LineNumberReader(new StringReader(" " + string));
+ Line line = new Parser().parse(reader, new Scope(new GlobalScope()), null);
+ line.resolve(0x4321);
+ byte[] bytes = line.getBytes();
+ assertEquals(bytes.length, line.getSize());
+ return bytes;
+ }
+
+ public 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;
+ }
+
+}
diff --git a/src/nl/grauw/glass/Assembler.java b/src/nl/grauw/glass/Assembler.java
new file mode 100644
index 0000000..e89d2e7
--- /dev/null
+++ b/src/nl/grauw/glass/Assembler.java
@@ -0,0 +1,75 @@
+package nl.grauw.glass;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Assembler {
+
+ private static Assembler instance;
+ private final Source source;
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ System.out.println("Usage: java -jar glass.jar [OPTION] SOURCE [OBJECT] [SYMBOL]");
+ System.exit(1);
+ }
+
+ File sourcePath = null;
+ File objectPath = null;
+ File symbolPath = null;
+ List includePaths = new ArrayList();
+ 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 if (symbolPath == null) {
+ symbolPath = new File(args[i]);
+ } else {
+ throw new AssemblyException("Too many arguments.");
+ }
+ }
+
+ instance = new Assembler(sourcePath, includePaths);
+ instance.writeObject(objectPath);
+ if (symbolPath != null)
+ instance.writeSymbols(symbolPath);
+ }
+
+ public Assembler(File sourcePath, List includePaths) {
+ source = new SourceBuilder(includePaths).parse(sourcePath);
+ }
+
+ public void writeObject(File objectPath) {
+ try (OutputStream output = objectPath != null ? new FileOutputStream(objectPath) : new NullOutputStream()) {
+ source.assemble(output);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void writeSymbols(File symbolPath) {
+ try (PrintStream symbolOutput = new PrintStream(symbolPath)) {
+ symbolOutput.print(source.getScope().serializeSymbols());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static class NullOutputStream extends OutputStream {
+ public void write(int b) throws IOException {}
+ public void write(byte[] b) throws IOException {}
+ public void write(byte[] b, int off, int len) throws IOException {}
+ }
+
+}
diff --git a/src/nl/grauw/glass/AssemblyException.java b/src/nl/grauw/glass/AssemblyException.java
new file mode 100644
index 0000000..ef69588
--- /dev/null
+++ b/src/nl/grauw/glass/AssemblyException.java
@@ -0,0 +1,80 @@
+package nl.grauw.glass;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AssemblyException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ private final List contexts = new ArrayList<>();
+
+ public AssemblyException() {
+ this((Throwable)null);
+ }
+
+ public AssemblyException(String message) {
+ this(message, null);
+ }
+
+ public AssemblyException(Throwable cause) {
+ this("Error during assembly.", null);
+ }
+
+ public AssemblyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public void addContext(Line line) {
+ addContext(line.getSourceFile(), line.getLineNumber(), -1, line.getSourceText());
+ }
+
+ public void addContext(File file, int line, int column, String text) {
+ contexts.add(new Context(file, line, column, text));
+ }
+
+ @Override
+ public String getMessage() {
+ String message = super.getMessage();
+
+ for (Context context : contexts)
+ message += "\n" + context;
+
+ return message;
+ }
+
+ public String getPlainMessage() {
+ return super.getMessage();
+ }
+
+ private static class Context {
+
+ private final File file;
+ private final int line;
+ private final int column;
+ private final String text;
+
+ public Context(File file, int line, int column, String text) {
+ this.file = file;
+ this.line = line;
+ this.column = column;
+ this.text = text;
+ }
+
+ @Override
+ public String toString() {
+ String prefix = "[at " + file + ":" + line + (column != -1 ? "," + column : "") + "]\n";
+ String context = prefix + text;
+
+ if (column >= 0) {
+ int start = Math.min(context.lastIndexOf('\n') + 1, context.length());
+ int end = Math.min(start + column, context.length());
+ context += "\n" + context.substring(start, end).replaceAll("[^\t]", " ") + "^";
+ }
+
+ return context;
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/GlobalScope.java b/src/nl/grauw/glass/GlobalScope.java
new file mode 100644
index 0000000..9173a4a
--- /dev/null
+++ b/src/nl/grauw/glass/GlobalScope.java
@@ -0,0 +1,106 @@
+package nl.grauw.glass;
+
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Instruction;
+import nl.grauw.glass.instructions.*;
+import nl.grauw.glass.instructions.Error;
+
+public class GlobalScope extends Scope {
+
+ public GlobalScope() {
+ super();
+ setAddress(0);
+
+ addBuiltInSymbol("adc", new Instruction(new Adc()));
+ addBuiltInSymbol("add", new Instruction(new Add()));
+ addBuiltInSymbol("and", new Instruction(new And()));
+ addBuiltInSymbol("bit", new Instruction(new Bit()));
+ addBuiltInSymbol("call", new Instruction(new Call()));
+ addBuiltInSymbol("ccf", new Instruction(new Ccf()));
+ addBuiltInSymbol("cp", new Instruction(new Cp()));
+ addBuiltInSymbol("cpd", new Instruction(new Cpd()));
+ addBuiltInSymbol("cpdr", new Instruction(new Cpdr()));
+ addBuiltInSymbol("cpi", new Instruction(new Cpi()));
+ addBuiltInSymbol("cpir", new Instruction(new Cpir()));
+ addBuiltInSymbol("cpl", new Instruction(new Cpl()));
+ addBuiltInSymbol("daa", new Instruction(new Daa()));
+ addBuiltInSymbol("db", new Instruction(new Db()));
+ addBuiltInSymbol("dd", new Instruction(new Dd()));
+ addBuiltInSymbol("dec", new Instruction(new Dec()));
+ addBuiltInSymbol("di", new Instruction(new Di()));
+ addBuiltInSymbol("djnz", new Instruction(new Djnz()));
+ addBuiltInSymbol("ds", new Instruction(new Ds()));
+ addBuiltInSymbol("dw", new Instruction(new Dw()));
+ addBuiltInSymbol("ei", new Instruction(new Ei()));
+ addBuiltInSymbol("ex", new Instruction(new Ex()));
+ addBuiltInSymbol("exx", new Instruction(new Exx()));
+ addBuiltInSymbol("halt", new Instruction(new Halt()));
+ addBuiltInSymbol("im", new Instruction(new Im()));
+ addBuiltInSymbol("in", new Instruction(new In()));
+ addBuiltInSymbol("inc", new Instruction(new Inc()));
+ addBuiltInSymbol("ind", new Instruction(new Ind()));
+ addBuiltInSymbol("indr", new Instruction(new Indr()));
+ addBuiltInSymbol("ini", new Instruction(new Ini()));
+ addBuiltInSymbol("inir", new Instruction(new Inir()));
+ addBuiltInSymbol("jp", new Instruction(new Jp()));
+ addBuiltInSymbol("jr", new Instruction(new Jr()));
+ addBuiltInSymbol("ld", new Instruction(new Ld()));
+ addBuiltInSymbol("ldd", new Instruction(new Ldd()));
+ addBuiltInSymbol("lddr", new Instruction(new Lddr()));
+ addBuiltInSymbol("ldi", new Instruction(new Ldi()));
+ addBuiltInSymbol("ldir", new Instruction(new Ldir()));
+ addBuiltInSymbol("mulub", new Instruction(new Mulub()));
+ addBuiltInSymbol("muluw", new Instruction(new Muluw()));
+ addBuiltInSymbol("neg", new Instruction(new Neg()));
+ addBuiltInSymbol("nop", new Instruction(new Nop()));
+ addBuiltInSymbol("or", new Instruction(new Or()));
+ addBuiltInSymbol("otdr", new Instruction(new Otdr()));
+ addBuiltInSymbol("otir", new Instruction(new Otir()));
+ addBuiltInSymbol("out", new Instruction(new Out()));
+ addBuiltInSymbol("outi", new Instruction(new Outi()));
+ addBuiltInSymbol("outd", new Instruction(new Outd()));
+ addBuiltInSymbol("pop", new Instruction(new Pop()));
+ addBuiltInSymbol("push", new Instruction(new Push()));
+ addBuiltInSymbol("res", new Instruction(new Res()));
+ addBuiltInSymbol("ret", new Instruction(new Ret()));
+ addBuiltInSymbol("reti", new Instruction(new Reti()));
+ addBuiltInSymbol("retn", new Instruction(new Retn()));
+ addBuiltInSymbol("rl", new Instruction(new Rl()));
+ addBuiltInSymbol("rla", new Instruction(new Rla()));
+ addBuiltInSymbol("rlc", new Instruction(new Rlc()));
+ addBuiltInSymbol("rlca", new Instruction(new Rlca()));
+ addBuiltInSymbol("rld", new Instruction(new Rld()));
+ addBuiltInSymbol("rr", new Instruction(new Rr()));
+ addBuiltInSymbol("rra", new Instruction(new Rra()));
+ addBuiltInSymbol("rrc", new Instruction(new Rrc()));
+ addBuiltInSymbol("rrca", new Instruction(new Rrca()));
+ addBuiltInSymbol("rrd", new Instruction(new Rrd()));
+ addBuiltInSymbol("rst", new Instruction(new Rst()));
+ addBuiltInSymbol("sbc", new Instruction(new Sbc()));
+ addBuiltInSymbol("scf", new Instruction(new Scf()));
+ addBuiltInSymbol("set", new Instruction(new Set()));
+ addBuiltInSymbol("sla", new Instruction(new Sla()));
+ addBuiltInSymbol("sra", new Instruction(new Sra()));
+ addBuiltInSymbol("srl", new Instruction(new Srl()));
+ addBuiltInSymbol("sub", new Instruction(new Sub()));
+ addBuiltInSymbol("xor", new Instruction(new Xor()));
+
+ addBuiltInSymbol("include", new Instruction(new Include()));
+ addBuiltInSymbol("equ", new Instruction(new Equ()));
+ addBuiltInSymbol("org", new Instruction(new Org()));
+ addBuiltInSymbol("endm", new Instruction(new Endm()));
+ addBuiltInSymbol("endp", new Instruction(new Endp()));
+ addBuiltInSymbol("ends", new Instruction(new Ends()));
+ addBuiltInSymbol("end", new Instruction(new End()));
+ addBuiltInSymbol("endif", new Instruction(new Endif()));
+ addBuiltInSymbol("else", new Instruction(new Else()));
+ addBuiltInSymbol("error", new Instruction(new Error()));
+ addBuiltInSymbol("warning", new Instruction(new Warning()));
+ }
+
+ private void addBuiltInSymbol(String symbol, Expression value) {
+ addSymbol(symbol, value);
+ addSymbol(symbol.toUpperCase(), value);
+ }
+
+}
diff --git a/src/nl/grauw/glass/Line.java b/src/nl/grauw/glass/Line.java
new file mode 100644
index 0000000..5309a1b
--- /dev/null
+++ b/src/nl/grauw/glass/Line.java
@@ -0,0 +1,143 @@
+package nl.grauw.glass;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import nl.grauw.glass.directives.Directive;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.instructions.Empty;
+import nl.grauw.glass.instructions.InstructionFactory;
+import nl.grauw.glass.instructions.InstructionObject;
+
+public class Line {
+
+ private final Scope scope;
+ private final String label;
+ private final String mnemonic;
+ private final Expression arguments;
+ private final String comment;
+ private final File sourceFile;
+ private final int lineNumber;
+ private final String sourceText;
+
+ private InstructionFactory instruction;
+ private InstructionObject instructionObject;
+ private Directive directive;
+
+ public Line(Scope scope, String label, String mnemonic, Expression arguments, String comment, File sourceFile, int lineNumber, String sourceText) {
+ this.scope = scope;
+ this.label = label;
+ this.mnemonic = mnemonic;
+ this.arguments = arguments;
+ this.comment = comment;
+ this.sourceFile = sourceFile;
+ this.lineNumber = lineNumber;
+ this.sourceText = sourceText;
+ }
+
+ public Line(Scope scope, Line other) {
+ this(scope, other.label, other.mnemonic, other.arguments != null ? other.arguments.copy(scope) : null,
+ other.comment, other.sourceFile, other.lineNumber, other.sourceText);
+ directive = other.directive;
+ }
+
+ public Scope getScope() {
+ return scope;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public String getMnemonic() {
+ return mnemonic;
+ }
+
+ public Expression getArguments() {
+ return arguments;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public File getSourceFile() {
+ return sourceFile;
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ public String getSourceText() {
+ return sourceText;
+ }
+
+ public void setDirective(Directive directive) {
+ this.directive = directive;
+ }
+
+ public void setInstruction(InstructionFactory instruction) {
+ this.instruction = instruction;
+ }
+
+ public InstructionFactory getInstruction() {
+ if (instruction == null)
+ instruction = mnemonic != null ? scope.getSymbol(mnemonic).getInstruction() : Empty.INSTANCE;
+ return instruction;
+ }
+
+ public void register(Scope sourceScope) {
+ try {
+ directive.register(sourceScope, this);
+ } catch (AssemblyException e) {
+ e.addContext(this);
+ throw e;
+ }
+ }
+
+ public List expand() {
+ try {
+ return getInstruction().expand(this);
+ } catch (AssemblyException e) {
+ e.addContext(this);
+ throw e;
+ }
+ }
+
+ public int resolve(int address) {
+ try {
+ instructionObject = getInstruction().createObject(scope, arguments);
+ return instructionObject.resolve(address);
+ } catch (AssemblyException e) {
+ e.addContext(this);
+ throw e;
+ }
+ }
+
+ public void generateObjectCode(OutputStream output) throws IOException {
+ try {
+ instructionObject.generateObjectCode(output);
+ } catch (AssemblyException e) {
+ e.addContext(this);
+ throw e;
+ }
+ }
+
+ public int getSize() {
+ return instructionObject.getSize();
+ }
+
+ public byte[] getBytes() {
+ return instructionObject.getBytes();
+ }
+
+ public String toString() {
+ return (label != null ? label + ":" : "") +
+ (mnemonic != null ? (label != null ? " " : "\t") + mnemonic + (arguments != null ? " " + arguments : "") : "") +
+ (comment != null ? (label != null || mnemonic != null ? " ;" : ";") + comment : "");
+ }
+
+}
diff --git a/src/nl/grauw/glass/LineBuilder.java b/src/nl/grauw/glass/LineBuilder.java
new file mode 100644
index 0000000..ac54251
--- /dev/null
+++ b/src/nl/grauw/glass/LineBuilder.java
@@ -0,0 +1,55 @@
+package nl.grauw.glass;
+
+import java.io.File;
+
+import nl.grauw.glass.expressions.Expression;
+
+public class LineBuilder {
+
+ private String label;
+ private String mnemonic;
+ private Expression arguments;
+ private String comment;
+ private String sourceText;
+
+ public void setLabel(String label) {
+ if (this.label != null)
+ throw new AssemblyException("Label already set.");
+ this.label = label;
+ }
+
+ public void setMnemonic(String mnemonic) {
+ if (this.mnemonic != null)
+ throw new AssemblyException("Mnemonic already set.");
+ this.mnemonic = mnemonic;
+ }
+
+ public void setArguments(Expression arguments) {
+ if (this.arguments != null)
+ throw new AssemblyException("Arguments already set.");
+ this.arguments = arguments;
+ }
+
+ public void setComment(String comment) {
+ this.comment = this.comment == null ? comment : this.comment + "\n" + comment;
+ }
+
+ public void setSourceText(String sourceText) {
+ if (this.sourceText != null)
+ throw new AssemblyException("Source text already set.");
+ this.sourceText = sourceText;
+ }
+
+ public Line getLine(Scope scope, File sourceFile, int lineNumber) {
+ Line line = new Line(scope, label, mnemonic, arguments, comment, sourceFile, lineNumber, sourceText);
+
+ label = null;
+ mnemonic = null;
+ arguments = null;
+ comment = null;
+ sourceText = null;
+
+ return line;
+ }
+
+}
diff --git a/src/nl/grauw/glass/ParameterScope.java b/src/nl/grauw/glass/ParameterScope.java
new file mode 100644
index 0000000..2fa3d3e
--- /dev/null
+++ b/src/nl/grauw/glass/ParameterScope.java
@@ -0,0 +1,39 @@
+package nl.grauw.glass;
+
+import nl.grauw.glass.expressions.Equals;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Identifier;
+import nl.grauw.glass.instructions.ArgumentException;
+
+public class ParameterScope extends Scope {
+
+ public ParameterScope(Scope parent, Expression parameters, Expression arguments) {
+ super(parent);
+
+ while (parameters != null) {
+ Expression parameter = parameters.getElement();
+ Expression argument;
+
+ if (parameter instanceof Equals) {
+ argument = arguments != null ? arguments.getElement() : ((Equals)parameter).getTerm2();
+ parameter = ((Equals)parameter).getTerm1();
+ } else {
+ if (arguments == null)
+ throw new ArgumentException("Not enough arguments.");
+ argument = arguments.getElement();
+ }
+
+ if (!(parameter instanceof Identifier))
+ throw new ArgumentException("Parameter must be an identifier.");
+
+ addSymbol(((Identifier)parameter).getName(), argument);
+
+ parameters = parameters.getNext();
+ if (arguments != null)
+ arguments = arguments.getNext();
+ }
+ if (arguments != null)
+ throw new ArgumentException("Too many arguments.");
+ }
+
+}
diff --git a/src/nl/grauw/glass/Parser.java b/src/nl/grauw/glass/Parser.java
new file mode 100644
index 0000000..6852e02
--- /dev/null
+++ b/src/nl/grauw/glass/Parser.java
@@ -0,0 +1,666 @@
+package nl.grauw.glass;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.ArrayList;
+
+import nl.grauw.glass.expressions.CharacterLiteral;
+import nl.grauw.glass.expressions.ExpressionBuilder;
+import nl.grauw.glass.expressions.Identifier;
+import nl.grauw.glass.expressions.IntegerLiteral;
+import nl.grauw.glass.expressions.StringLiteral;
+
+public class Parser {
+
+ private Scope scope;
+ private LineBuilder lineBuilder = new LineBuilder();
+
+ private State state;
+ private StringBuilder accumulator = new StringBuilder();
+ private ExpressionBuilder expressionBuilder = new ExpressionBuilder();
+
+ public Line parse(LineNumberReader reader, Scope scope, File sourceFile) {
+ this.scope = scope;
+ state = labelStartState;
+
+ final int firstLineNumber = reader.getLineNumber();
+ int lineNumber = firstLineNumber;
+ int column = 0;
+ ArrayList sourceLines = new ArrayList();
+ try {
+ while (state != endState)
+ {
+ String sourceLine = reader.readLine();
+ if (sourceLine == null) {
+ state = state.parse('\0');
+ if (state != endState)
+ throw new AssemblyException("Unexpected end of file.");
+ if (sourceLines.size() > 0)
+ break; // return null (parsing end) the next time
+ return null;
+ }
+ sourceLines.add(sourceLine);
+ lineNumber = reader.getLineNumber();
+ column = 0;
+
+ for (int i = 0, length = sourceLine.length(); i < length; i++) {
+ column = i;
+ state = state.parse(sourceLine.charAt(i));
+ }
+ column = sourceLine.length();
+ state = state.parse('\n');
+ }
+
+ if (accumulator.length() > 0)
+ throw new AssemblyException("Accumulator not consumed. Value: " + accumulator.toString());
+ } catch(AssemblyException e) {
+ e.addContext(sourceFile, lineNumber, column, String.join("\n", sourceLines));
+ throw e;
+ } catch (IOException e) {
+ throw new AssemblyException(e);
+ }
+
+ lineBuilder.setSourceText(String.join("\n", sourceLines));
+
+ return lineBuilder.getLine(scope, sourceFile, firstLineNumber);
+ }
+
+ private abstract class State {
+ public abstract State parse(char character);
+
+ public boolean isWhitespace(char character) {
+ return character == ' ' || character == '\t';
+ }
+
+ public boolean isIdentifier(char character) {
+ return isIdentifierStart(character) || character >= '0' && character <= '9' ||
+ character == '\'' || character == '$';
+ }
+
+ public boolean isIdentifierStart(char character) {
+ return character >= 'a' && character <= 'z' || character >= 'A' && character <= 'Z' ||
+ character == '_' || character == '.' || character == '?' || character == '@';
+ }
+
+ }
+
+ private LabelStartState labelStartState = new LabelStartState();
+ private class LabelStartState extends State {
+ public State parse(char character) {
+ if (isIdentifierStart(character)) {
+ accumulator.append(character);
+ return labelReadState;
+ } else if (isWhitespace(character)) {
+ return statementStartState;
+ } else if (character == ';') {
+ return commentReadState;
+ } else if (character == '\n' || character == '\0') {
+ return endState;
+ }
+ throw new SyntaxError();
+ }
+ }
+
+ private LabelReadState labelReadState = new LabelReadState();
+ private class LabelReadState extends State {
+ public State parse(char character) {
+ if (isIdentifier(character)) {
+ accumulator.append(character);
+ return labelReadState;
+ } else {
+ lineBuilder.setLabel(accumulator.toString());
+ accumulator.setLength(0);
+ if (character == ':' || isWhitespace(character)) {
+ return statementStartState;
+ } else if (character == ';') {
+ return commentReadState;
+ } else if (character == '\n' || character == '\0') {
+ return endState;
+ }
+ }
+ throw new SyntaxError();
+ }
+ }
+
+ private StatementStartState statementStartState = new StatementStartState();
+ private class StatementStartState extends State {
+ public State parse(char character) {
+ if (isIdentifierStart(character)) {
+ accumulator.append(character);
+ return statementReadState;
+ } else if (isWhitespace(character)) {
+ return statementStartState;
+ } else if (character == ';') {
+ return commentReadState;
+ } else if (character == '\n' || character == '\0') {
+ return endState;
+ }
+ throw new SyntaxError();
+ }
+ }
+
+ private StatementReadState statementReadState = new StatementReadState();
+ private class StatementReadState extends State {
+ public State parse(char character) {
+ if (isIdentifier(character)) {
+ accumulator.append(character);
+ return statementReadState;
+ } if (character == ':') {
+ lineBuilder.setLabel(accumulator.toString());
+ accumulator.setLength(0);
+ return statementStartState;
+ } else {
+ lineBuilder.setMnemonic(accumulator.toString());
+ accumulator.setLength(0);
+ if (isWhitespace(character)) {
+ return argumentStartState;
+ } else if (character == ';') {
+ return commentReadState;
+ } else if (character == '\n' || character == '\0') {
+ return endState;
+ }
+ }
+ throw new SyntaxError();
+ }
+ }
+
+ private ArgumentStartState argumentStartState = new ArgumentStartState();
+ private class ArgumentStartState extends State {
+ public State parse(char character) {
+ if (character == ';') {
+ return commentReadState;
+ } else if (character == '\n' || character == '\0') {
+ return endState;
+ } else if (isWhitespace(character)) {
+ return argumentStartState;
+ } else {
+ return argumentValueState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentValueState argumentValueState = new ArgumentValueState();
+ private class ArgumentValueState extends State {
+ public State parse(char character) {
+ if (isIdentifierStart(character)) {
+ accumulator.append(character);
+ return argumentIdentifierState;
+ } else if (character == '0') {
+ accumulator.append(character);
+ return argumentZeroState;
+ } else if (character >= '1' && character <= '9') {
+ accumulator.append(character);
+ return argumentNumberState;
+ } else if (character == '#') {
+ return argumentHexadecimalState;
+ } else if (character == '$') {
+ return argumentDollarState;
+ } else if (character == '%') {
+ return argumentBinaryState;
+ } else if (character == '"') {
+ return argumentStringState;
+ } else if (character == '\'') {
+ return argumentCharacterState;
+ } else if (character == '+') {
+ expressionBuilder.addOperatorToken(expressionBuilder.POSITIVE);
+ return argumentValueState;
+ } else if (character == '-') {
+ expressionBuilder.addOperatorToken(expressionBuilder.NEGATIVE);
+ return argumentValueState;
+ } else if (character == '~') {
+ expressionBuilder.addOperatorToken(expressionBuilder.COMPLEMENT);
+ return argumentValueState;
+ } else if (character == '!') {
+ expressionBuilder.addOperatorToken(expressionBuilder.NOT);
+ return argumentValueState;
+ } else if (character == '(') {
+ expressionBuilder.addOperatorToken(expressionBuilder.GROUP_OPEN);
+ return argumentValueState;
+ } else if (isWhitespace(character)) {
+ return argumentValueState;
+ } else if (character == ';') {
+ return commentReadThenArgumentState;
+ } else if (character == '\n') {
+ return argumentValueState;
+ }
+ throw new SyntaxError();
+ }
+ }
+
+ private ArgumentIdentifierState argumentIdentifierState = new ArgumentIdentifierState();
+ private class ArgumentIdentifierState extends State {
+ public State parse(char character) {
+ if (isIdentifier(character)) {
+ accumulator.append(character);
+ return argumentIdentifierState;
+ } else {
+ expressionBuilder.addValueToken(new Identifier(accumulator.toString(), scope));
+ accumulator.setLength(0);
+ return argumentOperatorState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentStringState argumentStringState = new ArgumentStringState();
+ private class ArgumentStringState extends State {
+ public State parse(char character) {
+ if (character == '"') {
+ expressionBuilder.addValueToken(new StringLiteral(accumulator.toString()));
+ accumulator.setLength(0);
+ return argumentOperatorState;
+ } else if (character == '\\') {
+ return argumentStringEscapeState;
+ } else if (character == '\n' || character == '\0') {
+ throw new SyntaxError();
+ } else {
+ accumulator.append(character);
+ return argumentStringState;
+ }
+ }
+ }
+
+ private ArgumentStringEscapeState argumentStringEscapeState = new ArgumentStringEscapeState();
+ private class ArgumentStringEscapeState extends State {
+ public State parse(char character) {
+ if (character == '0') {
+ accumulator.append('\0');
+ return argumentStringState;
+ } else if (character == 'a') {
+ accumulator.append('\7');
+ return argumentStringState;
+ } else if (character == 't') {
+ accumulator.append('\t');
+ return argumentStringState;
+ } else if (character == 'n') {
+ accumulator.append('\n');
+ return argumentStringState;
+ } else if (character == 'f') {
+ accumulator.append('\f');
+ return argumentStringState;
+ } else if (character == 'r') {
+ accumulator.append('\r');
+ return argumentStringState;
+ } else if (character == 'e') {
+ accumulator.append('\33');
+ return argumentStringState;
+ } else if (character == '"') {
+ accumulator.append('"');
+ return argumentStringState;
+ } else if (character == '\'') {
+ accumulator.append('\'');
+ return argumentStringState;
+ } else if (character == '\\') {
+ accumulator.append('\\');
+ return argumentStringState;
+ } else if (character == '\n' || character == '\0') {
+ throw new SyntaxError();
+ } else {
+ throw new SyntaxError();
+ }
+ }
+ }
+
+ private ArgumentCharacterState argumentCharacterState = new ArgumentCharacterState();
+ private class ArgumentCharacterState extends State {
+ public State parse(char character) {
+ if (character == '\\') {
+ return argumentCharacterEscapeState;
+ } else if (character == '\'' || character == '\n' || character == '\0') {
+ throw new SyntaxError();
+ } else {
+ accumulator.append(character);
+ return argumentCharacterEndState;
+ }
+ }
+ }
+
+ private ArgumentCharacterEscapeState argumentCharacterEscapeState = new ArgumentCharacterEscapeState();
+ private class ArgumentCharacterEscapeState extends State {
+ public State parse(char character) {
+ State state = argumentStringEscapeState.parse(character);
+ if (state == argumentStringState)
+ return argumentCharacterEndState;
+ throw new AssemblyException("Unexpected state.");
+ }
+ }
+
+ private ArgumentCharacterEndState argumentCharacterEndState = new ArgumentCharacterEndState();
+ private class ArgumentCharacterEndState extends State {
+ public State parse(char character) {
+ if (character == '\'') {
+ expressionBuilder.addValueToken(new CharacterLiteral(accumulator.charAt(0)));
+ accumulator.setLength(0);
+ return argumentOperatorState;
+ } else {
+ throw new SyntaxError();
+ }
+ }
+ }
+
+ private ArgumentZeroState argumentZeroState = new ArgumentZeroState();
+ private class ArgumentZeroState extends State {
+ public State parse(char character) {
+ if (character == 'x' || character == 'X') {
+ accumulator.setLength(0);
+ return argumentHexadecimalState;
+ } else {
+ return argumentNumberState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentNumberState argumentNumberState = new ArgumentNumberState();
+ private class ArgumentNumberState extends State {
+ public State parse(char character) {
+ if (character >= '0' && character <= '9' || character >= 'A' && character <= 'F' ||
+ character >= 'a' && character <= 'f') {
+ accumulator.append(character);
+ return argumentNumberState;
+ } else {
+ String string = accumulator.toString();
+ if (character == 'H' || character == 'h') {
+ int value = parseInt(string, 16);
+ expressionBuilder.addValueToken(new IntegerLiteral(value));
+ accumulator.setLength(0);
+ return argumentOperatorState;
+ } else if (character == 'O' || character == 'o') {
+ int value = parseInt(string, 8);
+ expressionBuilder.addValueToken(new IntegerLiteral(value));
+ 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));
+ accumulator.setLength(0);
+ } else {
+ int value = parseInt(string, 10);
+ expressionBuilder.addValueToken(new IntegerLiteral(value));
+ accumulator.setLength(0);
+ }
+ return argumentOperatorState.parse(character);
+ }
+ }
+ }
+ }
+
+ private ArgumentDollarState argumentDollarState = new ArgumentDollarState();
+ private class ArgumentDollarState extends State {
+ public State parse(char character) {
+ if (character >= '0' && character <= '9' || character >= 'A' && character <= 'F' ||
+ character >= 'a' && character <= 'f') {
+ accumulator.append(character);
+ return argumentHexadecimalState;
+ } else {
+ expressionBuilder.addValueToken(new Identifier("$", scope));
+ accumulator.setLength(0);
+ return argumentOperatorState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentHexadecimalState argumentHexadecimalState = new ArgumentHexadecimalState();
+ private class ArgumentHexadecimalState extends State {
+ public State parse(char character) {
+ if (character >= '0' && character <= '9' || character >= 'A' && character <= 'F' ||
+ character >= 'a' && character <= 'f') {
+ accumulator.append(character);
+ return argumentHexadecimalState;
+ } else {
+ int value = parseInt(accumulator.toString(), 16);
+ expressionBuilder.addValueToken(new IntegerLiteral(value));
+ accumulator.setLength(0);
+ return argumentOperatorState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentBinaryState argumentBinaryState = new ArgumentBinaryState();
+ private class ArgumentBinaryState extends State {
+ public State parse(char character) {
+ if (character >= '0' && character <= '1') {
+ accumulator.append(character);
+ return argumentBinaryState;
+ } else {
+ int value = parseInt(accumulator.toString(), 2);
+ expressionBuilder.addValueToken(new IntegerLiteral(value));
+ accumulator.setLength(0);
+ return argumentOperatorState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentOperatorState argumentOperatorState = new ArgumentOperatorState();
+ private class ArgumentOperatorState extends State {
+ public State parse(char character) {
+ if (character == ')') {
+ expressionBuilder.addOperatorToken(expressionBuilder.GROUP_CLOSE);
+ return argumentOperatorState;
+ } else if (character == '[') {
+ expressionBuilder.addOperatorToken(expressionBuilder.INDEX_OPEN);
+ return argumentValueState;
+ } else if (character == ']') {
+ expressionBuilder.addOperatorToken(expressionBuilder.INDEX_CLOSE);
+ return argumentOperatorState;
+ } else if (character == '.') {
+ expressionBuilder.addOperatorToken(expressionBuilder.MEMBER);
+ return argumentValueState;
+ } else if (character == '*') {
+ expressionBuilder.addOperatorToken(expressionBuilder.MULTIPLY);
+ return argumentValueState;
+ } else if (character == '/') {
+ expressionBuilder.addOperatorToken(expressionBuilder.DIVIDE);
+ return argumentValueState;
+ } else if (character == '%') {
+ expressionBuilder.addOperatorToken(expressionBuilder.MODULO);
+ return argumentValueState;
+ } else if (character == '+') {
+ expressionBuilder.addOperatorToken(expressionBuilder.ADD);
+ return argumentValueState;
+ } else if (character == '-') {
+ expressionBuilder.addOperatorToken(expressionBuilder.SUBTRACT);
+ return argumentValueState;
+ } else if (character == '<') {
+ return argumentLessThanState;
+ } else if (character == '>') {
+ return argumentGreaterThanState;
+ } else if (character == '=') {
+ expressionBuilder.addOperatorToken(expressionBuilder.EQUALS);
+ return argumentValueState;
+ } else if (character == '!') {
+ return argumentNotEqualsState;
+ } else if (character == '&') {
+ return argumentAndState;
+ } else if (character == '^') {
+ expressionBuilder.addOperatorToken(expressionBuilder.XOR);
+ return argumentValueState;
+ } else if (character == '|') {
+ return argumentOrState;
+ } else if (character == '?') {
+ expressionBuilder.addOperatorToken(expressionBuilder.TERNARYIF);
+ return argumentValueState;
+ } else if (character == ':') {
+ expressionBuilder.addOperatorToken(expressionBuilder.TERNARYELSE);
+ return argumentValueState;
+ } else if (character == ',') {
+ expressionBuilder.addOperatorToken(expressionBuilder.SEQUENCE);
+ return argumentValueState;
+ } else if (isWhitespace(character)) {
+ return argumentOperatorState;
+ } else if (character == ';') {
+ if (!expressionBuilder.hasOpenGroup()) {
+ lineBuilder.setArguments(expressionBuilder.getExpression());
+ return commentReadState;
+ } else {
+ return commentReadThenOperatorState;
+ }
+ } else if (character == '\n' || character == '\0') {
+ if (!expressionBuilder.hasOpenGroup() || character == '\0') {
+ lineBuilder.setArguments(expressionBuilder.getExpression());
+ return endState;
+ } else {
+ return argumentOperatorState;
+ }
+ } else {
+ expressionBuilder.addOperatorToken(expressionBuilder.ANNOTATION);
+ return argumentValueState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentLessThanState argumentLessThanState = new ArgumentLessThanState();
+ private class ArgumentLessThanState extends State {
+ public State parse(char character) {
+ if (character == '<') {
+ expressionBuilder.addOperatorToken(expressionBuilder.SHIFT_LEFT);
+ return argumentValueState;
+ } else if (character == '=') {
+ expressionBuilder.addOperatorToken(expressionBuilder.LESS_OR_EQUALS);
+ return argumentValueState;
+ } else {
+ expressionBuilder.addOperatorToken(expressionBuilder.LESS_THAN);
+ return argumentValueState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentGreaterThanState argumentGreaterThanState = new ArgumentGreaterThanState();
+ private class ArgumentGreaterThanState extends State {
+ public State parse(char character) {
+ if (character == '>') {
+ expressionBuilder.addOperatorToken(expressionBuilder.SHIFT_RIGHT);
+ return argumentValueState;
+ } else if (character == '=') {
+ expressionBuilder.addOperatorToken(expressionBuilder.GREATER_OR_EQUALS);
+ return argumentValueState;
+ } else {
+ expressionBuilder.addOperatorToken(expressionBuilder.GREATER_THAN);
+ return argumentValueState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentNotEqualsState argumentNotEqualsState = new ArgumentNotEqualsState();
+ private class ArgumentNotEqualsState extends State {
+ public State parse(char character) {
+ if (character == '=') {
+ expressionBuilder.addOperatorToken(expressionBuilder.NOT_EQUALS);
+ return argumentValueState;
+ } else {
+ expressionBuilder.addOperatorToken(expressionBuilder.ANNOTATION);
+ expressionBuilder.addOperatorToken(expressionBuilder.NOT);
+ return argumentValueState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentAndState argumentAndState = new ArgumentAndState();
+ private class ArgumentAndState extends State {
+ public State parse(char character) {
+ if (character == '&') {
+ expressionBuilder.addOperatorToken(expressionBuilder.LOGICAL_AND);
+ return argumentValueState;
+ } else {
+ expressionBuilder.addOperatorToken(expressionBuilder.AND);
+ return argumentValueState.parse(character);
+ }
+ }
+ }
+
+ private ArgumentOrState argumentOrState = new ArgumentOrState();
+ private class ArgumentOrState extends State {
+ public State parse(char character) {
+ if (character == '|') {
+ expressionBuilder.addOperatorToken(expressionBuilder.LOGICAL_OR);
+ return argumentValueState;
+ } else {
+ expressionBuilder.addOperatorToken(expressionBuilder.OR);
+ return argumentValueState.parse(character);
+ }
+ }
+ }
+
+ private CommentReadState commentReadState = new CommentReadState();
+ private class CommentReadState extends State {
+ public State parse(char character) {
+ if (character == '\n' || character == '\0') {
+ lineBuilder.setComment(accumulator.toString());
+ accumulator.setLength(0);
+ return endState;
+ } else {
+ accumulator.append(character);
+ return commentReadState;
+ }
+ }
+ }
+
+ private CommentReadThenArgumentState commentReadThenArgumentState = new CommentReadThenArgumentState();
+ private class CommentReadThenArgumentState extends State {
+ public State parse(char character) {
+ if (character == '\n' || character == '\0') {
+ lineBuilder.setComment(accumulator.toString());
+ accumulator.setLength(0);
+ if (character == '\0') {
+ throw new SyntaxError();
+ } else {
+ return argumentValueState;
+ }
+ } else {
+ accumulator.append(character);
+ return commentReadState;
+ }
+ }
+ }
+
+ private CommentReadThenOperatorState commentReadThenOperatorState = new CommentReadThenOperatorState();
+ private class CommentReadThenOperatorState extends State {
+ public State parse(char character) {
+ if (character == '\n' || character == '\0') {
+ lineBuilder.setComment(accumulator.toString());
+ accumulator.setLength(0);
+ if (character == '\0') {
+ lineBuilder.setArguments(expressionBuilder.getExpression());
+ return endState;
+ } else {
+ return argumentOperatorState;
+ }
+ } else {
+ accumulator.append(character);
+ return commentReadState;
+ }
+ }
+ }
+
+ private EndState endState = new EndState();
+ private class EndState extends State {
+ public State parse(char character) {
+ throw new AssemblyException("End state reached but not all characters consumed.");
+ }
+ }
+
+ private static int parseInt(String string, int radix) {
+ try {
+ long value = Long.parseLong(string, radix);
+ if (value > 0xFFFFFFFFL)
+ throw new SyntaxError();
+ return (int)value;
+ } catch (NumberFormatException e) {
+ throw new SyntaxError();
+ }
+ }
+
+ public static class SyntaxError extends AssemblyException {
+ private static final long serialVersionUID = 1L;
+
+ public SyntaxError() {
+ this(null);
+ }
+
+ public SyntaxError(Throwable cause) {
+ super("Syntax error.", cause);
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/Scope.java b/src/nl/grauw/glass/Scope.java
new file mode 100644
index 0000000..c833f35
--- /dev/null
+++ b/src/nl/grauw/glass/Scope.java
@@ -0,0 +1,119 @@
+package nl.grauw.glass;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import nl.grauw.glass.expressions.Context;
+import nl.grauw.glass.expressions.ContextLiteral;
+import nl.grauw.glass.expressions.EvaluationException;
+import nl.grauw.glass.expressions.Expression;
+
+public class Scope implements Context {
+
+ private boolean set = false;
+ private final Scope parent;
+ private final Map symbols = new HashMap<>();
+ private int address = 0;
+
+ public Scope() {
+ this(null);
+ }
+
+ public Scope(Scope parent) {
+ this.parent = parent;
+ addSymbol("$", this);
+ }
+
+ public Scope getParent() {
+ return parent;
+ }
+
+ @Override
+ public int getAddress() {
+ if (!set)
+ throw new EvaluationException("Address not initialized.");
+ return address;
+ }
+
+ public void setAddress(int address) {
+ if (set)
+ throw new AssemblyException("Address was already set.");
+ this.address = address;
+ this.set = true;
+ }
+
+ public void addSymbol(String name, Expression value) {
+ if (name == null || value == null)
+ throw new AssemblyException("Symbol name and value must not be null.");
+ if (symbols.containsKey(name))
+ throw new AssemblyException("Can not redefine symbol: " + name);
+ symbols.put(name, value);
+ }
+
+ public void addSymbol(String name, Scope context) {
+ addSymbol(name, new ContextLiteral(context));
+ }
+
+ public boolean hasSymbol(String name) {
+ return getLocalSymbol(name) != null || parent != null && parent.hasSymbol(name);
+ }
+
+ public Expression getSymbol(String name) {
+ Expression value = getLocalSymbol(name);
+ if (value != null)
+ return value;
+ if (parent != null)
+ return parent.getSymbol(name);
+ throw new SymbolNotFoundException(name);
+ }
+
+ private Expression getLocalSymbol(String name) {
+ Expression value = symbols.get(name);
+ if (value != null)
+ return value;
+
+ int index = name.length();
+ while ((index = name.lastIndexOf('.', index - 1)) != -1) {
+ Expression result = symbols.get(name.substring(0, index));
+ if (result != null && result.isContext())
+ return ((Scope)result.getContext()).getLocalSymbol(name.substring(index + 1));
+ }
+ return null;
+ }
+
+ public static class SymbolNotFoundException extends AssemblyException {
+ private static final long serialVersionUID = 1L;
+
+ public SymbolNotFoundException(String name) {
+ super("Symbol not found: " + name);
+ }
+ }
+
+ public String serializeSymbols() {
+ return serializeSymbols("");
+ }
+
+ public String serializeSymbols(String namePrefix) {
+ StringBuilder builder = new StringBuilder();
+ TreeMap sortedMap = new TreeMap<>(symbols);
+ for (Map.Entry entry : sortedMap.entrySet()) {
+ if (entry.getValue() instanceof ContextLiteral && !"$".equals(entry.getKey())) {
+ String name = namePrefix + entry.getKey();
+ try {
+ builder.append(name + ": equ " + entry.getValue().getHexValue() + "\n");
+ } catch (EvaluationException e) {
+ // ignore
+ }
+ Scope context = (Scope)((ContextLiteral)entry.getValue()).getContext();
+ builder.append(context.serializeSymbols(name + "."));
+ }
+ }
+ return builder.toString();
+ }
+
+ public String toString() {
+ return serializeSymbols();
+ }
+
+}
diff --git a/src/nl/grauw/glass/Source.java b/src/nl/grauw/glass/Source.java
new file mode 100644
index 0000000..f330ec8
--- /dev/null
+++ b/src/nl/grauw/glass/Source.java
@@ -0,0 +1,110 @@
+package nl.grauw.glass;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Source {
+
+ private final Scope scope;
+ private List lines = new ArrayList();
+
+ public Source() {
+ scope = new GlobalScope();
+ }
+
+ public Source(Scope scope) {
+ this.scope = scope;
+ }
+
+ public Source(Scope scope, Source other) {
+ this(scope);
+ for (Line line : other.lines)
+ addLine(new Line(new Scope(scope), line));
+ }
+
+ public Scope getScope() {
+ return scope;
+ }
+
+ public List getLines() {
+ return lines;
+ }
+
+ public Line getLastLine() {
+ return lines.size() > 0 ? lines.get(lines.size() - 1) : null;
+ }
+
+ public List getLineCopies(Scope newParent) {
+ List lineCopies = new ArrayList<>();
+ for (Line line : lines)
+ lineCopies.add(new Line(new Scope(newParent), line));
+ return lineCopies;
+ }
+
+ public Line addLine(Line line) {
+ lines.add(line);
+ return line;
+ }
+
+ public List addLines(List lines) {
+ this.lines.addAll(lines);
+ return lines;
+ }
+
+ public void assemble(OutputStream output) throws IOException {
+ register();
+ expand();
+ resolve();
+ generateObjectCode(output);
+ }
+
+ public void register() {
+ for (Line line : lines)
+ line.register(scope);
+ }
+
+ public void expand() {
+ List newLines = new ArrayList<>();
+ for (Line line : lines)
+ newLines.addAll(line.expand());
+ lines = newLines;
+ }
+
+ public int resolve() {
+ return resolve(0);
+ }
+
+ public int resolve(int address) {
+ for (Line line : lines)
+ address = line.resolve(address);
+ return address;
+ }
+
+ public void generateObjectCode(OutputStream output) throws IOException {
+ for (Line line : lines)
+ line.generateObjectCode(output);
+ }
+
+ public byte[] generateObjectCode() {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try {
+ generateObjectCode(bytes);
+ } catch (IOException e) {
+ throw new AssemblyException(e);
+ }
+ return bytes.toByteArray();
+ }
+
+ public String toString() {
+ StringBuilder string = new StringBuilder();
+ for (Line line : lines) {
+ string.append(line);
+ string.append('\n');
+ }
+ return string.toString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/SourceBuilder.java b/src/nl/grauw/glass/SourceBuilder.java
new file mode 100644
index 0000000..61f0d8e
--- /dev/null
+++ b/src/nl/grauw/glass/SourceBuilder.java
@@ -0,0 +1,206 @@
+package nl.grauw.glass;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nl.grauw.glass.directives.Directive;
+import nl.grauw.glass.directives.Ds;
+import nl.grauw.glass.directives.Equ;
+import nl.grauw.glass.directives.If;
+import nl.grauw.glass.directives.Incbin;
+import nl.grauw.glass.directives.Include;
+import nl.grauw.glass.directives.Instruction;
+import nl.grauw.glass.directives.Irp;
+import nl.grauw.glass.directives.Macro;
+import nl.grauw.glass.directives.Proc;
+import nl.grauw.glass.directives.Rept;
+import nl.grauw.glass.directives.Section;
+import nl.grauw.glass.expressions.Annotation;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Sequence;
+
+public class SourceBuilder {
+
+ public static final List END_TERMINATORS = Arrays.asList(new String[] { "end", "END" });
+ public static final List ENDM_TERMINATORS = Arrays.asList(new String[] { "endm", "ENDM" });
+ public static final List ENDP_TERMINATORS = Arrays.asList(new String[] { "endp", "ENDP" });
+ public static final List ENDS_TERMINATORS = Arrays.asList(new String[] { "ends", "ENDS" });
+ public static final List ELSE_TERMINATORS = Arrays.asList(new String[] { "else", "ELSE", "endif", "ENDIF" });
+ public static final List ENDIF_TERMINATORS = Arrays.asList(new String[] { "endif", "ENDIF" });
+
+ private final Source source;
+ private final List terminators;
+ private final List includePaths;
+ private final Parser parser = new Parser();
+
+ private static final List sourceFiles = new ArrayList();
+
+ public SourceBuilder(List includePaths) {
+ this.source = new Source();
+ this.terminators = END_TERMINATORS;
+ this.includePaths = includePaths;
+ }
+
+ public SourceBuilder(Source source, List terminators, List includePaths) {
+ this.source = source;
+ this.terminators = terminators;
+ this.includePaths = includePaths;
+ }
+
+ public boolean hasLoadedSourceFile(File file) {
+ try {
+ for (int i = 0; i < sourceFiles.size(); i++)
+ if (file.getCanonicalPath().equals(sourceFiles.get(i).getCanonicalPath()))
+ return true;
+ } catch (IOException e) {
+ }
+ return false;
+ }
+
+ public Source parse(File sourceFile) {
+ try {
+ parse(new FileInputStream(sourceFile), sourceFile);
+ } catch (FileNotFoundException e) {
+ throw new AssemblyException(e);
+ }
+ return source;
+ }
+
+ private Source parseInclude(File sourceFile, File basePath, boolean once) {
+ File fullPath = new File(basePath.getParent(), sourceFile.getPath());
+ if (fullPath.exists()) {
+ if (once && hasLoadedSourceFile(fullPath))
+ return null;
+ return parse(fullPath);
+ }
+ return parseInclude(sourceFile, once);
+ }
+
+ private Source parseInclude(File sourceFile, boolean once) {
+ for (File includePath : includePaths) {
+ File fullPath = new File(includePath, sourceFile.getPath());
+ if (fullPath.exists()) {
+ if (once && hasLoadedSourceFile(fullPath))
+ return null;
+ return parse(fullPath);
+ }
+ }
+ throw new AssemblyException("Include file not found: " + sourceFile);
+ }
+
+ public Source parse(InputStream reader, File sourceFile) {
+ return parse(new InputStreamReader(reader, Charset.forName("ISO-8859-1")), sourceFile);
+ }
+
+ public Source parse(Reader reader, File sourceFile) {
+ return parse(new LineNumberReader(reader), sourceFile);
+ }
+
+ private Source parse(LineNumberReader reader, File sourceFile) {
+ sourceFiles.add(sourceFile);
+ while (true) {
+ Line line = parser.parse(reader, new Scope(source.getScope()), sourceFile);
+ if (line == null)
+ break;
+
+ try {
+ line.setDirective(getDirective(line, reader, sourceFile));
+ source.addLine(line);
+ if (line.getMnemonic() != null && terminators.contains(line.getMnemonic()))
+ return source;
+ } catch (AssemblyException e) {
+ e.addContext(line);
+ throw e;
+ }
+ }
+ if (terminators != END_TERMINATORS)
+ throw new AssemblyException("Unexpected end of file. Expecting: " + terminators.toString());
+ return source;
+ }
+
+ public Directive getDirective(Line line, LineNumberReader reader, File sourceFile) {
+ if (line.getMnemonic() == null)
+ return new Instruction();
+
+ switch (line.getMnemonic()) {
+ case "equ":
+ case "EQU":
+ return new Equ();
+ case "include":
+ case "INCLUDE":
+ return getIncludeDirective(line, sourceFile);
+ case "incbin":
+ case "INCBIN":
+ return new Incbin(sourceFile, includePaths);
+ case "macro":
+ case "MACRO":
+ return new Macro(parseBlock(line.getScope(), ENDM_TERMINATORS, reader, sourceFile));
+ case "rept":
+ case "REPT":
+ return new Rept(parseBlock(line.getScope(), ENDM_TERMINATORS, reader, sourceFile));
+ case "irp":
+ case "IRP":
+ return new Irp(parseBlock(line.getScope(), ENDM_TERMINATORS, reader, sourceFile));
+ case "proc":
+ case "PROC":
+ return new Proc(parseBlock(line.getScope(), ENDP_TERMINATORS, reader, sourceFile));
+ case "if":
+ case "IF":
+ Source thenBlock = parseBlock(source.getScope(), ELSE_TERMINATORS, reader, sourceFile);
+ Source elseBlock = !ENDIF_TERMINATORS.contains(thenBlock.getLastLine().getMnemonic()) ?
+ parseBlock(source.getScope(), ENDIF_TERMINATORS, reader, sourceFile) : null;
+ return new If(thenBlock, elseBlock);
+ case "section":
+ case "SECTION":
+ return new Section(parseBlock(source.getScope(), ENDS_TERMINATORS, reader, sourceFile));
+ case "ds":
+ case "DS":
+ return new Ds();
+ case "endm":
+ case "ENDM":
+ case "endp":
+ case "ENDP":
+ case "ends":
+ case "ENDS":
+ if (!terminators.contains(line.getMnemonic()))
+ throw new AssemblyException("Unexpected " + line.getMnemonic() + ".");
+ return new Instruction();
+ default:
+ return new Instruction();
+ }
+ }
+
+ private Directive getIncludeDirective(Line line, File sourceFile) {
+ boolean once = false;
+ Expression argument = line.getArguments();
+ if (argument instanceof Annotation) {
+ String annotation = argument.getAnnotation().getName();
+ if ("once".equals(annotation) || "ONCE".equals(annotation)) {
+ argument = argument.getAnnotee();
+ once = true;
+ }
+ }
+ if (line.getArguments() instanceof Sequence)
+ throw new AssemblyException("Include only accepts 1 argument.");
+ if (!argument.isString())
+ throw new AssemblyException("A string literal is expected.");
+ SourceBuilder sourceBuilder = new SourceBuilder(source, END_TERMINATORS, includePaths);
+ sourceBuilder.parseInclude(new File(argument.getString()), sourceFile, once);
+ return new Include();
+ }
+
+ private Source parseBlock(Scope scope, List terminators, LineNumberReader reader, File sourceFile) {
+ return new SourceBuilder(new Source(scope), terminators, includePaths).parse(reader, sourceFile);
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Directive.java b/src/nl/grauw/glass/directives/Directive.java
new file mode 100644
index 0000000..cd91ee6
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Directive.java
@@ -0,0 +1,13 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+
+public abstract class Directive {
+
+ public void register(Scope scope, Line line) {
+ if (line.getLabel() != null)
+ scope.addSymbol(line.getLabel(), line.getScope());
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Ds.java b/src/nl/grauw/glass/directives/Ds.java
new file mode 100644
index 0000000..2da9e21
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Ds.java
@@ -0,0 +1,17 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.SectionContextLiteral;
+
+public class Ds extends Directive {
+
+ @Override
+ public void register(Scope scope, Line line) {
+ nl.grauw.glass.instructions.Ds ds = new nl.grauw.glass.instructions.Ds();
+ line.setInstruction(ds);
+ if (line.getLabel() != null)
+ scope.addSymbol(line.getLabel(), new SectionContextLiteral(line.getScope(), ds));
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Equ.java b/src/nl/grauw/glass/directives/Equ.java
new file mode 100644
index 0000000..4d49927
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Equ.java
@@ -0,0 +1,16 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.AssemblyException;
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+
+public class Equ extends Directive {
+
+ @Override
+ public void register(Scope scope, Line line) {
+ if (line.getLabel() == null)
+ throw new AssemblyException("Equ without label.");
+ scope.addSymbol(line.getLabel(), line.getArguments());
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/If.java b/src/nl/grauw/glass/directives/If.java
new file mode 100644
index 0000000..0be7f92
--- /dev/null
+++ b/src/nl/grauw/glass/directives/If.java
@@ -0,0 +1,26 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+
+public class If extends Directive {
+
+ private final Source thenSource;
+ private final Source elseSource;
+
+ public If(Source thenSource, Source elseSource) {
+ this.thenSource = thenSource;
+ this.elseSource = elseSource;
+ }
+
+ @Override
+ public void register(Scope scope, Line line) {
+ line.setInstruction(new nl.grauw.glass.instructions.If(
+ new Source(scope, thenSource),
+ elseSource != null ? new Source(scope, elseSource) : null
+ ));
+ super.register(scope, line);
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Incbin.java b/src/nl/grauw/glass/directives/Incbin.java
new file mode 100644
index 0000000..1ee9f56
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Incbin.java
@@ -0,0 +1,25 @@
+package nl.grauw.glass.directives;
+
+import java.io.File;
+import java.util.List;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+
+public class Incbin extends Directive {
+
+ private final File sourceFile;
+ private final List includePaths;
+
+ public Incbin(File sourceFile, List includePaths) {
+ this.sourceFile = sourceFile;
+ this.includePaths = includePaths;
+ }
+
+ @Override
+ public void register(Scope scope, Line line) {
+ line.setInstruction(new nl.grauw.glass.instructions.Incbin(sourceFile, includePaths));
+ super.register(scope, line);
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Include.java b/src/nl/grauw/glass/directives/Include.java
new file mode 100644
index 0000000..d4040bb
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Include.java
@@ -0,0 +1,5 @@
+package nl.grauw.glass.directives;
+
+public class Include extends Directive {
+
+}
diff --git a/src/nl/grauw/glass/directives/Instruction.java b/src/nl/grauw/glass/directives/Instruction.java
new file mode 100644
index 0000000..cade15b
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Instruction.java
@@ -0,0 +1,5 @@
+package nl.grauw.glass.directives;
+
+public class Instruction extends Directive {
+
+}
diff --git a/src/nl/grauw/glass/directives/Irp.java b/src/nl/grauw/glass/directives/Irp.java
new file mode 100644
index 0000000..8c687ba
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Irp.java
@@ -0,0 +1,21 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+
+public class Irp extends Directive {
+
+ private final Source source;
+
+ public Irp(Source source) {
+ this.source = source;
+ }
+
+ @Override
+ public void register(Scope scope, Line line) {
+ line.setInstruction(new nl.grauw.glass.instructions.Irp(source));
+ super.register(scope, line);
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Macro.java b/src/nl/grauw/glass/directives/Macro.java
new file mode 100644
index 0000000..bd7da78
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Macro.java
@@ -0,0 +1,31 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.AssemblyException;
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Instruction;
+import nl.grauw.glass.instructions.MacroInstruction;
+
+public class Macro extends Directive {
+
+ private final Source source;
+
+ public Macro(Source source) {
+ this.source = source;
+ }
+
+ @Override
+ public void register(Scope scope, Line line) {
+ if (line.getLabel() == null)
+ throw new AssemblyException("Macro without label.");
+ scope.addSymbol(line.getLabel(),
+ new Instruction(
+ new MacroInstruction(line.getArguments(), source),
+ source.getScope()
+ )
+ );
+ line.setInstruction(new nl.grauw.glass.instructions.Macro(source));
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Proc.java b/src/nl/grauw/glass/directives/Proc.java
new file mode 100644
index 0000000..0791f2a
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Proc.java
@@ -0,0 +1,21 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+
+public class Proc extends Directive {
+
+ private final Source source;
+
+ public Proc(Source source) {
+ this.source = source;
+ }
+
+ @Override
+ public void register(Scope scope, Line line) {
+ line.setInstruction(new nl.grauw.glass.instructions.Proc(source));
+ super.register(scope, line);
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Rept.java b/src/nl/grauw/glass/directives/Rept.java
new file mode 100644
index 0000000..1ff521d
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Rept.java
@@ -0,0 +1,21 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+
+public class Rept extends Directive {
+
+ private final Source source;
+
+ public Rept(Source source) {
+ this.source = source;
+ }
+
+ @Override
+ public void register(Scope scope, Line line) {
+ line.setInstruction(new nl.grauw.glass.instructions.Rept(source));
+ super.register(scope, line);
+ }
+
+}
diff --git a/src/nl/grauw/glass/directives/Section.java b/src/nl/grauw/glass/directives/Section.java
new file mode 100644
index 0000000..a2b9501
--- /dev/null
+++ b/src/nl/grauw/glass/directives/Section.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.directives;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+
+public class Section extends Directive {
+
+ private final Source source;
+
+ public Section(Source source) {
+ this.source = source;
+ }
+
+ @Override
+ public void register(Scope scope, Line line) {
+ nl.grauw.glass.instructions.Section section = new nl.grauw.glass.instructions.Section(source);
+ line.setInstruction(section);
+
+ source.register();
+ super.register(scope, line);
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Add.java b/src/nl/grauw/glass/expressions/Add.java
new file mode 100644
index 0000000..825e102
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Add.java
@@ -0,0 +1,51 @@
+package nl.grauw.glass.expressions;
+
+public class Add extends BinaryOperator {
+
+ public Add(Expression augend, Expression addend) {
+ super(augend, addend);
+ }
+
+ @Override
+ public Add copy(Context context) {
+ return new Add(term1.copy(context), term2.copy(context));
+ }
+
+ public Expression getAugend() {
+ return term1;
+ }
+
+ public Expression getAddend() {
+ return term2;
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() + term2.getInteger();
+ }
+
+ @Override
+ public boolean isRegister() {
+ if (term1.isRegister()) {
+ Register register = term1.getRegister();
+ return register.isIndex() && register.isPair();
+ }
+ return false;
+ }
+
+ @Override
+ public Register getRegister() {
+ if (term1.isRegister()) {
+ Register register = term1.getRegister();
+ if (register.isIndex() && register.isPair())
+ return new Register(register, new Add(register.getIndexOffset(), term2));
+ }
+ throw new EvaluationException("Not a register.");
+ }
+
+ @Override
+ public String getLexeme() {
+ return "+";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/And.java b/src/nl/grauw/glass/expressions/And.java
new file mode 100644
index 0000000..92d64ec
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/And.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class And extends BinaryOperator {
+
+ public And(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public And copy(Context context) {
+ return new And(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() & term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "&";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Annotation.java b/src/nl/grauw/glass/expressions/Annotation.java
new file mode 100644
index 0000000..c394b68
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Annotation.java
@@ -0,0 +1,34 @@
+package nl.grauw.glass.expressions;
+
+public class Annotation extends Expression {
+
+ private final Identifier annotation;
+ private final Expression annotee;
+
+ public Annotation(Identifier annotation, Expression annotee) {
+ this.annotation = annotation;
+ this.annotee = annotee;
+ }
+
+ @Override
+ public Annotation copy(Context context) {
+ return new Annotation(annotation.copy(context), annotee.copy(context));
+ }
+
+ public Identifier getAnnotation() {
+ return annotation;
+ }
+
+ public Expression getAnnotee() {
+ return annotee;
+ }
+
+ public String toString() {
+ return "" + annotation + " " + annotee;
+ }
+
+ public String toDebugString() {
+ return "{" + annotation.toDebugString() + " " + annotee.toDebugString() + "}";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/BinaryOperator.java b/src/nl/grauw/glass/expressions/BinaryOperator.java
new file mode 100644
index 0000000..66b67e3
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/BinaryOperator.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.expressions;
+
+public abstract class BinaryOperator extends Expression {
+
+ protected final Expression term1;
+ protected final Expression term2;
+
+ public abstract String getLexeme();
+
+ public BinaryOperator(Expression term1, Expression term2) {
+ this.term1 = term1;
+ this.term2 = term2;
+ }
+
+ public Expression getTerm1() {
+ return term1;
+ }
+
+ public Expression getTerm2() {
+ return term2;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return term1.isInteger() && term2.isInteger();
+ }
+
+ public String toString() {
+ return "" + term1 + " " + getLexeme() + " " + term2;
+ }
+
+ public String toDebugString() {
+ return "{" + term1.toDebugString() + " " + getLexeme() + " " + term2.toDebugString() + "}";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/CharacterLiteral.java b/src/nl/grauw/glass/expressions/CharacterLiteral.java
new file mode 100644
index 0000000..97afc2e
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/CharacterLiteral.java
@@ -0,0 +1,48 @@
+package nl.grauw.glass.expressions;
+
+public class CharacterLiteral extends Literal {
+
+ private final char character;
+
+ public CharacterLiteral(char character) {
+ this.character = character;
+ }
+
+ @Override
+ public CharacterLiteral copy(Context context) {
+ return this;
+ }
+
+ public char getCharacter() {
+ return character;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return true;
+ }
+
+ @Override
+ public int getInteger() {
+ return character;
+ }
+
+ public String toString() {
+ String escaped = Character.toString(character);
+ escaped = escaped.replace("\\", "\\\\");
+ escaped = escaped.replace("\'", "\\\'");
+ escaped = escaped.replace("\0", "\\0");
+ escaped = escaped.replace("\7", "\\a");
+ escaped = escaped.replace("\t", "\\t");
+ escaped = escaped.replace("\n", "\\n");
+ escaped = escaped.replace("\f", "\\f");
+ escaped = escaped.replace("\r", "\\r");
+ escaped = escaped.replace("\33", "\\e");
+ return "'" + escaped + "'";
+ }
+
+ public String toDebugString() {
+ return toString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Complement.java b/src/nl/grauw/glass/expressions/Complement.java
new file mode 100644
index 0000000..b5cdfa9
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Complement.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class Complement extends UnaryOperator {
+
+ public Complement(Expression term) {
+ super(term);
+ }
+
+ @Override
+ public Complement copy(Context context) {
+ return new Complement(term.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return ~term.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "~";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Context.java b/src/nl/grauw/glass/expressions/Context.java
new file mode 100644
index 0000000..34b4cb7
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Context.java
@@ -0,0 +1,11 @@
+package nl.grauw.glass.expressions;
+
+public interface Context {
+
+ public Expression getSymbol(String name);
+
+ public boolean hasSymbol(String name);
+
+ public int getAddress();
+
+}
diff --git a/src/nl/grauw/glass/expressions/ContextLiteral.java b/src/nl/grauw/glass/expressions/ContextLiteral.java
new file mode 100644
index 0000000..629c7f8
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/ContextLiteral.java
@@ -0,0 +1,48 @@
+package nl.grauw.glass.expressions;
+
+public class ContextLiteral extends Literal {
+
+ private final Context context;
+
+ public ContextLiteral(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public ContextLiteral copy(Context context) {
+ return new ContextLiteral(context);
+ }
+
+ @Override
+ public boolean isContext() {
+ return true;
+ }
+
+ @Override
+ public Context getContext() {
+ return context;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return true;
+ }
+
+ @Override
+ public int getInteger() {
+ return context.getAddress();
+ }
+
+ public String toString() {
+ try {
+ return getHexValue();
+ } catch (EvaluationException e) {
+ return "<" + e.getMessage() + ">";
+ }
+ }
+
+ public String toDebugString() {
+ return toString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Divide.java b/src/nl/grauw/glass/expressions/Divide.java
new file mode 100644
index 0000000..a4e9bf4
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Divide.java
@@ -0,0 +1,35 @@
+package nl.grauw.glass.expressions;
+
+public class Divide extends BinaryOperator {
+
+ public Divide(Expression dividend, Expression divisor) {
+ super(dividend, divisor);
+ }
+
+ @Override
+ public Divide copy(Context context) {
+ return new Divide(term1.copy(context), term2.copy(context));
+ }
+
+ public Expression getDividend() {
+ return term1;
+ }
+
+ public Expression getDivisor() {
+ return term2;
+ }
+
+ @Override
+ public int getInteger() {
+ int divisor = term2.getInteger();
+ if (divisor == 0)
+ throw new EvaluationException("Division by zero.");
+ return term1.getInteger() / divisor;
+ }
+
+ @Override
+ public String getLexeme() {
+ return "/";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Equals.java b/src/nl/grauw/glass/expressions/Equals.java
new file mode 100644
index 0000000..973cbaa
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Equals.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class Equals extends BinaryOperator {
+
+ public Equals(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public Equals copy(Context context) {
+ return new Equals(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() == term2.getInteger() ? -1 : 0;
+ }
+
+ @Override
+ public String getLexeme() {
+ return "=";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/EvaluationException.java b/src/nl/grauw/glass/expressions/EvaluationException.java
new file mode 100644
index 0000000..ef59fe4
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/EvaluationException.java
@@ -0,0 +1,16 @@
+package nl.grauw.glass.expressions;
+
+import nl.grauw.glass.AssemblyException;
+
+public class EvaluationException extends AssemblyException {
+ private static final long serialVersionUID = 1L;
+
+ public EvaluationException() {
+ this(null);
+ }
+
+ public EvaluationException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Expression.java b/src/nl/grauw/glass/expressions/Expression.java
new file mode 100644
index 0000000..1e32d7b
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Expression.java
@@ -0,0 +1,117 @@
+package nl.grauw.glass.expressions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nl.grauw.glass.instructions.InstructionFactory;
+
+public abstract class Expression {
+
+ public abstract String toDebugString();
+
+ public abstract Expression copy(Context context);
+
+ public Expression resolve() {
+ return this;
+ }
+
+ public boolean isInteger() {
+ return false;
+ }
+
+ public int getInteger() {
+ throw new EvaluationException("Not an integer.");
+ }
+
+ public boolean isString() {
+ return false;
+ }
+
+ public String getString() {
+ throw new EvaluationException("Not a string.");
+ }
+
+ public boolean isRegister() {
+ return false;
+ }
+
+ public Register getRegister() {
+ throw new EvaluationException("Not a register.");
+ }
+
+ public boolean isFlag() {
+ return false;
+ }
+
+ public Flag getFlag() {
+ throw new EvaluationException("Not a flag.");
+ }
+
+ public boolean isGroup() {
+ return false;
+ }
+
+ public Identifier getAnnotation() {
+ return null;
+ }
+
+ public Expression getAnnotee() {
+ return this;
+ }
+
+ public boolean isInstruction() {
+ return false;
+ }
+
+ public InstructionFactory getInstruction() {
+ throw new EvaluationException("Not an instruction.");
+ }
+
+ public boolean isContext() {
+ return false;
+ }
+
+ public Context getContext() {
+ throw new EvaluationException("Not a context.");
+ }
+
+ public boolean isSectionContext() {
+ return false;
+ }
+
+ public SectionContext getSectionContext() {
+ throw new EvaluationException("Not a context.");
+ }
+
+ public int getAddress() {
+ return getInteger();
+ }
+
+ public List getList() {
+ List list = new ArrayList<>();
+ addToList(list);
+ return list;
+ }
+
+ protected void addToList(List list) {
+ list.add(this);
+ }
+
+ public Expression getElement() {
+ return getElement(0);
+ }
+
+ public Expression getElement(int index) {
+ return index == 0 ? this : null;
+ }
+
+ public Expression getNext() {
+ return null;
+ }
+
+ public String getHexValue() {
+ String string = Integer.toHexString(getInteger()).toUpperCase();
+ return (string.charAt(0) >= 'A' && string.charAt(0) <= 'F' ? "0" : "") + string + "H";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/ExpressionBuilder.java b/src/nl/grauw/glass/expressions/ExpressionBuilder.java
new file mode 100644
index 0000000..b6e9535
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/ExpressionBuilder.java
@@ -0,0 +1,390 @@
+package nl.grauw.glass.expressions;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+import nl.grauw.glass.AssemblyException;
+
+
+/**
+ * Constructs an AST from the given expression tokens.
+ *
+ * It uses a shunting yard algorithm.
+ */
+public class ExpressionBuilder {
+
+ private Deque operands = new ArrayDeque();
+ private Deque operators = new ArrayDeque();
+ private int groupCount = 0;
+
+ public ExpressionBuilder() {
+ operators.push(SENTINEL);
+ }
+
+ public void addValueToken(Expression value) {
+ operands.push(value);
+ }
+
+ public void addOperatorToken(Operator operator) {
+ evaluateNotYieldingTo(operator);
+
+ if (operator == GROUP_OPEN || operator == INDEX_OPEN) {
+ groupCount++;
+ operators.push(operator);
+ operators.push(SENTINEL);
+ } else if (operator == GROUP_CLOSE || operator == INDEX_CLOSE) {
+ groupCount--;
+ if (operators.pop() != SENTINEL)
+ throw new AssemblyException("Sentinel expected.");
+ if (operator == GROUP_CLOSE && operators.peek() != GROUP_OPEN)
+ throw new ExpressionError("Group open expected.");
+ if (operator == INDEX_CLOSE && operators.peek() != INDEX_OPEN)
+ throw new ExpressionError("Index open expected.");
+ } else {
+ operators.push(operator);
+ }
+ }
+
+ public Expression getExpression() {
+ if (operands.isEmpty() || operators.isEmpty())
+ throw new AssemblyException("Operands / operators is empty: " + this);
+
+ // process remainder
+ evaluateNotYieldingTo(SENTINEL);
+
+ if (operators.size() > 1 && operators.peek() == SENTINEL)
+ throw new ExpressionError("Group close expected.");
+ if (operands.size() > 1 || operators.size() != 1)
+ throw new AssemblyException("Not all operands / operators were processed: " + this);
+
+ return operands.pop();
+ }
+
+ private void evaluateNotYieldingTo(Operator operator) {
+ while (!operators.peek().yieldsTo(operator))
+ operands.push(operators.pop().evaluate());
+ }
+
+ public boolean hasOpenGroup()
+ {
+ return groupCount > 0;
+ }
+
+ public String toString() {
+ return "" + operands + " / " + operators;
+ }
+
+ private abstract class Operator {
+
+ private Precedence precedence;
+ private Associativity associativity;
+
+ private Operator(Precedence precedence, Associativity associativity) {
+ this.precedence = precedence;
+ this.associativity = associativity;
+ }
+
+ public boolean yieldsTo(Operator other) {
+ if (associativity == Associativity.LEFT_TO_RIGHT)
+ return precedence.ordinal() > other.precedence.ordinal();
+ else
+ return precedence.ordinal() >= other.precedence.ordinal();
+ }
+
+ public abstract Expression evaluate();
+ }
+
+ public final Operator POSITIVE = new Operator(Precedence.UNARY, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ return new Positive(operands.pop());
+ };
+ };
+
+ public final Operator NEGATIVE = new Operator(Precedence.UNARY, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ return new Negative(operands.pop());
+ };
+ };
+
+ public final Operator COMPLEMENT = new Operator(Precedence.UNARY, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ return new Complement(operands.pop());
+ };
+ };
+
+ public final Operator NOT = new Operator(Precedence.UNARY, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ return new Not(operands.pop());
+ };
+ };
+
+ public final Operator MEMBER = new Operator(Precedence.MEMBER, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ if (!(operandRight instanceof Identifier))
+ throw new ExpressionError("Member operator right hand side must be an identifier.");
+ return new Member(operands.pop(), (Identifier)operandRight);
+ };
+ };
+
+ public final Operator MULTIPLY = new Operator(Precedence.MULTIPLICATION, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Multiply(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator DIVIDE = new Operator(Precedence.MULTIPLICATION, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Divide(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator MODULO = new Operator(Precedence.MULTIPLICATION, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Modulo(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator ADD = new Operator(Precedence.ADDITION, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Add(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator SUBTRACT = new Operator(Precedence.ADDITION, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Subtract(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator SHIFT_LEFT = new Operator(Precedence.SHIFT, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new ShiftLeft(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator SHIFT_RIGHT = new Operator(Precedence.SHIFT, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new ShiftRight(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator LESS_THAN = new Operator(Precedence.COMPARISON, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new LessThan(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator LESS_OR_EQUALS = new Operator(Precedence.COMPARISON, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new LessOrEquals(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator GREATER_THAN = new Operator(Precedence.COMPARISON, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new GreaterThan(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator GREATER_OR_EQUALS = new Operator(Precedence.COMPARISON, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new GreaterOrEquals(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator EQUALS = new Operator(Precedence.EQUALITY, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Equals(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator NOT_EQUALS = new Operator(Precedence.EQUALITY, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new NotEquals(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator AND = new Operator(Precedence.AND, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new And(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator XOR = new Operator(Precedence.XOR, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Xor(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator OR = new Operator(Precedence.OR, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Or(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator LOGICAL_AND = new Operator(Precedence.LOGICAL_AND, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new LogicalAnd(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator LOGICAL_OR = new Operator(Precedence.LOGICAL_OR, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new LogicalOr(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator ANNOTATION = new Operator(Precedence.ANNOTATION, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ Expression operandLeft = operands.pop();
+ if (!(operandLeft instanceof Identifier))
+ throw new ExpressionError("Annotation left hand side must be an identifier.");
+ return new Annotation((Identifier)operandLeft, operandRight);
+ };
+ };
+
+ public final Operator SEQUENCE = new Operator(Precedence.SEQUENCE, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Sequence(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator TERNARYIF = new Operator(Precedence.TERNARYIFELSE, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ throw new ExpressionError("Ternary if (?) without else (:).");
+ };
+ };
+
+ public final Operator TERNARYELSE = new Operator(Precedence.TERNARYIFELSE, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ while (operators.peek() == TERNARYELSE) {
+ operands.push(operators.pop().evaluate());
+ }
+ if (operators.peek() == TERNARYIF) {
+ operators.pop();
+ Expression operandMiddle = operands.pop();
+ return new IfElse(operands.pop(), operandMiddle, operandRight);
+ } else {
+ throw new ExpressionError("Ternary else (:) without if (?).");
+ }
+ };
+ };
+
+ public final Operator GROUP_OPEN = new Operator(Precedence.GROUPING, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ return new Group(operands.pop());
+ };
+ };
+
+ public final Operator GROUP_CLOSE = new Operator(Precedence.NONE, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ throw new AssemblyException("Can not evaluate group close.");
+ };
+ };
+
+ public final Operator INDEX_OPEN = new Operator(Precedence.MEMBER, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ Expression operandRight = operands.pop();
+ return new Index(operands.pop(), operandRight);
+ };
+ };
+
+ public final Operator INDEX_CLOSE = new Operator(Precedence.NONE, Associativity.LEFT_TO_RIGHT) {
+ @Override
+ public Expression evaluate() {
+ throw new AssemblyException("Can not evaluate group close.");
+ };
+ };
+
+ public final Operator SENTINEL = new Operator(Precedence.NONE, Associativity.RIGHT_TO_LEFT) {
+ @Override
+ public Expression evaluate() {
+ throw new AssemblyException("Can not evaluate sentinel.");
+ };
+ };
+
+ private enum Precedence {
+ GROUPING,
+ MEMBER,
+ UNARY,
+ MULTIPLICATION,
+ ADDITION,
+ SHIFT,
+ COMPARISON,
+ EQUALITY,
+ AND,
+ XOR,
+ OR,
+ LOGICAL_AND,
+ LOGICAL_OR,
+ TERNARYIFELSE,
+ ANNOTATION,
+ SEQUENCE,
+ NONE
+ }
+
+ private enum Associativity {
+ LEFT_TO_RIGHT,
+ RIGHT_TO_LEFT
+ }
+
+ public static class ExpressionError extends AssemblyException {
+ private static final long serialVersionUID = 1L;
+ public ExpressionError(String message) {
+ super("Expression error: " + message);
+ }
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Flag.java b/src/nl/grauw/glass/expressions/Flag.java
new file mode 100644
index 0000000..e13e53c
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Flag.java
@@ -0,0 +1,81 @@
+package nl.grauw.glass.expressions;
+
+public class Flag extends Literal {
+
+ public static Flag NZ = new Flag("nz", 0);
+ public static Flag Z = new Flag("z", 1);
+ public static Flag NC = new Flag("nc", 2);
+ public static Flag C = new Flag("c", 3);
+ public static Flag PO = new Flag("po", 4);
+ public static Flag PE = new Flag("pe", 5);
+ public static Flag P = new Flag("p", 6);
+ public static Flag M = new Flag("m", 7);
+
+ private final String name;
+ private final int code;
+
+ public Flag(String name, int code) {
+ this.name = name;
+ this.code = code;
+ }
+
+ @Override
+ public Flag copy(Context context) {
+ return this;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ @Override
+ public boolean isFlag() {
+ return true;
+ }
+
+ @Override
+ public Flag getFlag() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public String toDebugString() {
+ return toString();
+ }
+
+ public static Flag getByName(String name) {
+ switch (name) {
+ case "nz":
+ case "NZ":
+ return Flag.NZ;
+ case "z":
+ case "Z":
+ return Flag.Z;
+ case "nc":
+ case "NC":
+ return Flag.NC;
+ case "c":
+ case "C":
+ return Flag.C;
+ case "po":
+ case "PO":
+ return Flag.PO;
+ case "pe":
+ case "PE":
+ return Flag.PE;
+ case "p":
+ case "P":
+ return Flag.P;
+ case "m":
+ case "M":
+ return Flag.M;
+ }
+ return null;
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/FlagOrRegister.java b/src/nl/grauw/glass/expressions/FlagOrRegister.java
new file mode 100644
index 0000000..9101f06
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/FlagOrRegister.java
@@ -0,0 +1,62 @@
+package nl.grauw.glass.expressions;
+
+public class FlagOrRegister extends Literal {
+
+ public static FlagOrRegister C = new FlagOrRegister(Flag.C, Register.C);
+
+ private final Flag flag;
+ private final Register register;
+
+ public FlagOrRegister(Flag flag, Register register) {
+ this.flag = flag;
+ this.register = register;
+ }
+
+ @Override
+ public FlagOrRegister copy(Context context) {
+ return this;
+ }
+
+ @Override
+ public boolean isFlag() {
+ return true;
+ }
+
+ @Override
+ public Flag getFlag() {
+ return flag;
+ }
+
+ @Override
+ public boolean isRegister() {
+ return true;
+ }
+
+ @Override
+ public Register getRegister() {
+ return register;
+ }
+
+ @Override
+ public String toString() {
+ return flag.toString();
+ }
+
+ @Override
+ public String toDebugString() {
+ return flag.toString();
+ }
+
+ public static Literal getByName(String name) {
+ Flag flag = Flag.getByName(name);
+ Register register = Register.getByName(name);
+ if (flag != null && register == null)
+ return flag;
+ if (flag == null && register != null)
+ return register;
+ if (flag != null && register != null)
+ return new FlagOrRegister(flag, register);
+ return null;
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/GreaterOrEquals.java b/src/nl/grauw/glass/expressions/GreaterOrEquals.java
new file mode 100644
index 0000000..11ae3db
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/GreaterOrEquals.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class GreaterOrEquals extends BinaryOperator {
+
+ public GreaterOrEquals(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public GreaterOrEquals copy(Context context) {
+ return new GreaterOrEquals(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() >= term2.getInteger() ? -1 : 0;
+ }
+
+ @Override
+ public String getLexeme() {
+ return ">=";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/GreaterThan.java b/src/nl/grauw/glass/expressions/GreaterThan.java
new file mode 100644
index 0000000..606f24b
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/GreaterThan.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class GreaterThan extends BinaryOperator {
+
+ public GreaterThan(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public GreaterThan copy(Context context) {
+ return new GreaterThan(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() > term2.getInteger() ? -1 : 0;
+ }
+
+ @Override
+ public String getLexeme() {
+ return ">";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Group.java b/src/nl/grauw/glass/expressions/Group.java
new file mode 100644
index 0000000..78c5ae3
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Group.java
@@ -0,0 +1,38 @@
+package nl.grauw.glass.expressions;
+
+public class Group extends Passthrough {
+
+ private final Expression term;
+
+ public Group(Expression term) {
+ this.term = term;
+ }
+
+ @Override
+ public Group copy(Context context) {
+ return new Group(term.copy(context));
+ }
+
+ public Expression getTerm() {
+ return term;
+ }
+
+ @Override
+ public Expression resolve() {
+ return term;
+ }
+
+ @Override
+ public boolean isGroup() {
+ return true;
+ }
+
+ public String toString() {
+ return "(" + term + ")";
+ }
+
+ public String toDebugString() {
+ return "(" + term.toDebugString() + ")";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Identifier.java b/src/nl/grauw/glass/expressions/Identifier.java
new file mode 100644
index 0000000..3401a4e
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Identifier.java
@@ -0,0 +1,55 @@
+package nl.grauw.glass.expressions;
+
+public class Identifier extends Passthrough {
+
+ private final String name;
+ private final Context context;
+
+ public Identifier(String name, Context context) {
+ this.name = name;
+ this.context = context;
+ }
+
+ @Override
+ public Identifier copy(Context context) {
+ return new Identifier(name, context);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean exists() {
+ return FlagOrRegister.getByName(name) != null || context.hasSymbol(name);
+ }
+
+ @Override
+ public Expression resolve() {
+ Literal flagOrRegister = FlagOrRegister.getByName(name);
+ return flagOrRegister != null ? flagOrRegister : context.getSymbol(name);
+ }
+
+ @Override
+ public boolean isRegister() {
+ return exists() && super.isRegister();
+ }
+
+ @Override
+ public boolean isFlag() {
+ return exists() && super.isFlag();
+ }
+
+ @Override
+ public boolean isGroup() {
+ return exists() && super.isGroup();
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ public String toDebugString() {
+ return toString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/IfElse.java b/src/nl/grauw/glass/expressions/IfElse.java
new file mode 100644
index 0000000..a8199c4
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/IfElse.java
@@ -0,0 +1,76 @@
+package nl.grauw.glass.expressions;
+
+public class IfElse extends Passthrough {
+
+ private final Expression condition;
+ private final Expression trueTerm;
+ private final Expression falseTerm;
+
+ public IfElse(Expression condition, Expression trueTerm, Expression falseTerm) {
+ this.condition = condition;
+ this.trueTerm = trueTerm;
+ this.falseTerm = falseTerm;
+ }
+
+ @Override
+ public IfElse copy(Context context) {
+ return new IfElse(condition.copy(context), trueTerm.copy(context), falseTerm.copy(context));
+ }
+
+ public Expression getCondition() {
+ return condition;
+ }
+
+ public Expression getTrueTerm() {
+ return trueTerm;
+ }
+
+ public Expression getFalseTerm() {
+ return falseTerm;
+ }
+
+ public boolean isTrue() {
+ return condition.getInteger() != 0;
+ }
+
+ @Override
+ public Expression resolve() {
+ return isTrue() ? trueTerm : falseTerm;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return (trueTerm.isInteger() && falseTerm.isInteger()) || super.isInteger();
+ }
+
+ @Override
+ public boolean isString() {
+ return (trueTerm.isString() && falseTerm.isString()) || super.isString();
+ }
+
+ @Override
+ public boolean isRegister() {
+ return (trueTerm.isRegister() && falseTerm.isRegister()) || super.isRegister();
+ }
+
+ @Override
+ public boolean isFlag() {
+ return (trueTerm.isFlag() && falseTerm.isFlag()) || super.isFlag();
+ }
+
+ @Override
+ public boolean isContext() {
+ return (trueTerm.isContext() && falseTerm.isContext()) || super.isContext();
+ }
+
+ @Override
+ public String toString() {
+ return "" + condition + " ? " + trueTerm + " : " + falseTerm;
+ }
+
+ @Override
+ public String toDebugString() {
+ return "{" + condition.toDebugString() + " ? " + trueTerm.toDebugString() + " : " + falseTerm.toDebugString() + "}";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Index.java b/src/nl/grauw/glass/expressions/Index.java
new file mode 100644
index 0000000..e76afbb
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Index.java
@@ -0,0 +1,42 @@
+package nl.grauw.glass.expressions;
+
+public class Index extends Passthrough {
+
+ private final Expression sequence;
+ private final Expression index;
+
+ public Index(Expression sequence, Expression index) {
+ this.sequence = sequence;
+ this.index = index;
+ }
+
+ @Override
+ public Index copy(Context context) {
+ return new Index(sequence.copy(context), index.copy(context));
+ }
+
+ public Expression getSequence() {
+ return sequence;
+ }
+
+ public Expression getIndex() {
+ return index;
+ }
+
+ @Override
+ public Expression resolve() {
+ Expression element = sequence.resolve().getElement(index.getInteger());
+ if (element == null)
+ throw new EvaluationException("Index out of bounds.");
+ return element;
+ }
+
+ public String toString() {
+ return "" + sequence + "[" + index + "]";
+ }
+
+ public String toDebugString() {
+ return "{" + sequence.toDebugString() + "[" + index.toDebugString() + "]}";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Instruction.java b/src/nl/grauw/glass/expressions/Instruction.java
new file mode 100644
index 0000000..3111cf7
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Instruction.java
@@ -0,0 +1,54 @@
+package nl.grauw.glass.expressions;
+
+import nl.grauw.glass.instructions.InstructionFactory;
+
+public class Instruction extends Expression {
+
+ private final InstructionFactory instruction;
+ private final Context context;
+
+ public Instruction(InstructionFactory instruction) {
+ this(instruction, null);
+ }
+
+ public Instruction(InstructionFactory instruction, Context context) {
+ this.instruction = instruction;
+ this.context = context;
+ }
+
+ @Override
+ public Instruction copy(Context context) {
+ return new Instruction(instruction, context);
+ }
+
+ @Override
+ public boolean isInstruction() {
+ return true;
+ }
+
+ @Override
+ public InstructionFactory getInstruction() {
+ return instruction;
+ }
+
+ @Override
+ public boolean isContext() {
+ return context != null;
+ }
+
+ @Override
+ public Context getContext() {
+ if (!isContext())
+ super.getContext();
+ return context;
+ }
+
+ public String toString() {
+ return "instruction";
+ }
+
+ public String toDebugString() {
+ return toString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/IntegerLiteral.java b/src/nl/grauw/glass/expressions/IntegerLiteral.java
new file mode 100644
index 0000000..7060faf
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/IntegerLiteral.java
@@ -0,0 +1,38 @@
+package nl.grauw.glass.expressions;
+
+public class IntegerLiteral extends Literal {
+
+ public static final IntegerLiteral ZERO = new IntegerLiteral(0);
+ public static final IntegerLiteral ONE = new IntegerLiteral(1);
+
+ private final int value;
+
+ public IntegerLiteral(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public IntegerLiteral copy(Context context) {
+ return this;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return true;
+ }
+
+ @Override
+ public int getInteger() {
+ return value;
+ }
+
+ public String toString() {
+ String string = Integer.toHexString(value).toUpperCase();
+ return (string.charAt(0) >= 'A' && string.charAt(0) <= 'F' ? "0" : "") + string + "H";
+ }
+
+ public String toDebugString() {
+ return toString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/LessOrEquals.java b/src/nl/grauw/glass/expressions/LessOrEquals.java
new file mode 100644
index 0000000..cc579fc
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/LessOrEquals.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class LessOrEquals extends BinaryOperator {
+
+ public LessOrEquals(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public LessOrEquals copy(Context context) {
+ return new LessOrEquals(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() <= term2.getInteger() ? -1 : 0;
+ }
+
+ @Override
+ public String getLexeme() {
+ return "<=";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/LessThan.java b/src/nl/grauw/glass/expressions/LessThan.java
new file mode 100644
index 0000000..dc02fb2
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/LessThan.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class LessThan extends BinaryOperator {
+
+ public LessThan(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public LessThan copy(Context context) {
+ return new LessThan(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() < term2.getInteger() ? -1 : 0;
+ }
+
+ @Override
+ public String getLexeme() {
+ return "<";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Literal.java b/src/nl/grauw/glass/expressions/Literal.java
new file mode 100644
index 0000000..e6ec014
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Literal.java
@@ -0,0 +1,5 @@
+package nl.grauw.glass.expressions;
+
+public abstract class Literal extends Expression {
+
+}
diff --git a/src/nl/grauw/glass/expressions/LogicalAnd.java b/src/nl/grauw/glass/expressions/LogicalAnd.java
new file mode 100644
index 0000000..d096899
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/LogicalAnd.java
@@ -0,0 +1,25 @@
+package nl.grauw.glass.expressions;
+
+public class LogicalAnd extends BinaryOperator {
+
+ public LogicalAnd(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public LogicalAnd copy(Context context) {
+ return new LogicalAnd(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ int value1 = term1.getInteger();
+ return value1 == 0 ? value1 : term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "&&";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/LogicalOr.java b/src/nl/grauw/glass/expressions/LogicalOr.java
new file mode 100644
index 0000000..5e18128
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/LogicalOr.java
@@ -0,0 +1,25 @@
+package nl.grauw.glass.expressions;
+
+public class LogicalOr extends BinaryOperator {
+
+ public LogicalOr(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public LogicalOr copy(Context context) {
+ return new LogicalOr(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ int value1 = term1.getInteger();
+ return value1 != 0 ? value1 : term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "||";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Member.java b/src/nl/grauw/glass/expressions/Member.java
new file mode 100644
index 0000000..7ee918b
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Member.java
@@ -0,0 +1,42 @@
+package nl.grauw.glass.expressions;
+
+public class Member extends Passthrough {
+
+ private final Expression object;
+ private final Identifier subject;
+
+ public Member(Expression object, Identifier subject) {
+ this.object = object;
+ this.subject = subject;
+ }
+
+ @Override
+ public Member copy(Context context) {
+ return new Member(object.copy(context), subject.copy(context));
+ }
+
+ public Expression getObject() {
+ return object;
+ }
+
+ public Expression getSubject() {
+ return subject;
+ }
+
+ @Override
+ public Expression resolve() {
+ if (!object.isContext())
+ throw new EvaluationException("Object not found.");
+ return object.getContext().getSymbol(subject.getName());
+ }
+
+ @Override
+ public String toString() {
+ return "" + object + "." + subject;
+ }
+
+ public String toDebugString() {
+ return "{" + object.toDebugString() + "." + subject.toDebugString() + "}";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Modulo.java b/src/nl/grauw/glass/expressions/Modulo.java
new file mode 100644
index 0000000..baaaa54
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Modulo.java
@@ -0,0 +1,35 @@
+package nl.grauw.glass.expressions;
+
+public class Modulo extends BinaryOperator {
+
+ public Modulo(Expression dividend, Expression divisor) {
+ super(dividend, divisor);
+ }
+
+ @Override
+ public Modulo copy(Context context) {
+ return new Modulo(term1.copy(context), term2.copy(context));
+ }
+
+ public Expression getDividend() {
+ return term1;
+ }
+
+ public Expression getDivisor() {
+ return term2;
+ }
+
+ @Override
+ public int getInteger() {
+ int divisor = term2.getInteger();
+ if (divisor == 0)
+ throw new EvaluationException("Division by zero.");
+ return term1.getInteger() % divisor;
+ }
+
+ @Override
+ public String getLexeme() {
+ return "%";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Multiply.java b/src/nl/grauw/glass/expressions/Multiply.java
new file mode 100644
index 0000000..2b6eef5
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Multiply.java
@@ -0,0 +1,32 @@
+package nl.grauw.glass.expressions;
+
+public class Multiply extends BinaryOperator {
+
+ public Multiply(Expression multiplicand, Expression multiplier) {
+ super(multiplicand, multiplier);
+ }
+
+ @Override
+ public Multiply copy(Context context) {
+ return new Multiply(term1.copy(context), term2.copy(context));
+ }
+
+ public Expression getMultiplicand() {
+ return term1;
+ }
+
+ public Expression getMultiplier() {
+ return term2;
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() * term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "*";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Negative.java b/src/nl/grauw/glass/expressions/Negative.java
new file mode 100644
index 0000000..80c77e1
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Negative.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class Negative extends UnaryOperator {
+
+ public Negative(Expression term) {
+ super(term);
+ }
+
+ @Override
+ public Negative copy(Context context) {
+ return new Negative(term.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return -term.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "-";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Not.java b/src/nl/grauw/glass/expressions/Not.java
new file mode 100644
index 0000000..1ce315c
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Not.java
@@ -0,0 +1,53 @@
+package nl.grauw.glass.expressions;
+
+import nl.grauw.glass.AssemblyException;
+
+public class Not extends UnaryOperator {
+
+ public Not(Expression term) {
+ super(term);
+ }
+
+ @Override
+ public Not copy(Context context) {
+ return new Not(term.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term.getInteger() == 0 ? -1 : 0;
+ }
+
+ @Override
+ public boolean isFlag() {
+ return term.isFlag();
+ }
+
+ @Override
+ public Flag getFlag() {
+ Flag flag = term.getFlag();
+ if (flag == Flag.NZ)
+ return Flag.Z;
+ if (flag == Flag.Z)
+ return Flag.NZ;
+ if (flag == Flag.NC)
+ return Flag.C;
+ if (flag == Flag.C)
+ return Flag.NC;
+ if (flag == Flag.PO)
+ return Flag.PE;
+ if (flag == Flag.PE)
+ return Flag.PO;
+ if (flag == Flag.P)
+ return Flag.M;
+ if (flag == Flag.M)
+ return Flag.P;
+ throw new AssemblyException("Unrecognised flag.");
+ }
+
+ @Override
+ public String getLexeme() {
+ return "!";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/NotEquals.java b/src/nl/grauw/glass/expressions/NotEquals.java
new file mode 100644
index 0000000..bd9d71b
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/NotEquals.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class NotEquals extends BinaryOperator {
+
+ public NotEquals(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public NotEquals copy(Context context) {
+ return new NotEquals(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() != term2.getInteger() ? -1 : 0;
+ }
+
+ @Override
+ public String getLexeme() {
+ return "!=";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Or.java b/src/nl/grauw/glass/expressions/Or.java
new file mode 100644
index 0000000..4ecbf60
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Or.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class Or extends BinaryOperator {
+
+ public Or(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public Or copy(Context context) {
+ return new Or(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() | term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "|";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Passthrough.java b/src/nl/grauw/glass/expressions/Passthrough.java
new file mode 100644
index 0000000..9e86841
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Passthrough.java
@@ -0,0 +1,82 @@
+package nl.grauw.glass.expressions;
+
+import nl.grauw.glass.instructions.InstructionFactory;
+
+public abstract class Passthrough extends Expression {
+
+ @Override
+ public boolean isInteger() {
+ return resolve().isInteger();
+ }
+
+ @Override
+ public int getInteger() {
+ return resolve().getInteger();
+ }
+
+ @Override
+ public boolean isString() {
+ return resolve().isString();
+ }
+
+ @Override
+ public String getString() {
+ return resolve().getString();
+ }
+
+ @Override
+ public boolean isRegister() {
+ return resolve().isRegister();
+ }
+
+ @Override
+ public Register getRegister() {
+ return resolve().getRegister();
+ }
+
+ @Override
+ public boolean isFlag() {
+ return resolve().isFlag();
+ }
+
+ @Override
+ public Flag getFlag() {
+ return resolve().getFlag();
+ }
+
+ @Override
+ public boolean isGroup() {
+ return resolve().isGroup();
+ }
+
+ @Override
+ public boolean isInstruction() {
+ return resolve().isInstruction();
+ }
+
+ @Override
+ public InstructionFactory getInstruction() {
+ return resolve().getInstruction();
+ }
+
+ @Override
+ public boolean isContext() {
+ return resolve().isContext();
+ }
+
+ @Override
+ public Context getContext() {
+ return resolve().getContext();
+ }
+
+ @Override
+ public boolean isSectionContext() {
+ return resolve().isSectionContext();
+ }
+
+ @Override
+ public SectionContext getSectionContext() {
+ return resolve().getSectionContext();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Positive.java b/src/nl/grauw/glass/expressions/Positive.java
new file mode 100644
index 0000000..2e35961
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Positive.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class Positive extends UnaryOperator {
+
+ public Positive(Expression term) {
+ super(term);
+ }
+
+ @Override
+ public Positive copy(Context context) {
+ return new Positive(term.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return +term.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "+";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Register.java b/src/nl/grauw/glass/expressions/Register.java
new file mode 100644
index 0000000..8934915
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Register.java
@@ -0,0 +1,186 @@
+package nl.grauw.glass.expressions;
+
+import nl.grauw.glass.AssemblyException;
+
+public class Register extends Literal {
+
+ public static final int NONE = -1;
+ public static final int IX_CODE = 0xDD;
+ public static final int IY_CODE = 0xFD;
+
+ public static Register B = new Register("b", false, 0, NONE, NONE);
+ public static Register C = new Register("c", false, 1, NONE, NONE);
+ public static Register D = new Register("d", false, 2, NONE, NONE);
+ public static Register E = new Register("e", false, 3, NONE, NONE);
+ public static Register H = new Register("h", false, 4, NONE, NONE);
+ public static Register L = new Register("l", false, 5, NONE, NONE);
+ public static Register A = new Register("a", false, 7, NONE, NONE);
+ public static Register IXH = new Register("ixh", false, 4, NONE, IX_CODE);
+ public static Register IXL = new Register("ixl", false, 5, NONE, IX_CODE);
+ public static Register IYH = new Register("iyh", false, 4, NONE, IY_CODE);
+ public static Register IYL = new Register("iyl", false, 5, NONE, IY_CODE);
+ public static Register BC = new Register("bc", true, NONE, 0, NONE);
+ public static Register DE = new Register("de", true, NONE, 1, NONE);
+ public static Register HL = new Register("hl", true, 6, 2, NONE);
+ public static Register SP = new Register("sp", true, NONE, 3, NONE);
+ public static Register AF = new Register("af", true, NONE, 3, NONE);
+ public static Register AF_ = new Register("af'", true, NONE, NONE, NONE);
+ public static Register IX = new Register("ix", true, 6, 2, IX_CODE, IntegerLiteral.ZERO);
+ public static Register IY = new Register("iy", true, 6, 2, IY_CODE, IntegerLiteral.ZERO);
+ public static Register I = new Register("i", false, NONE, NONE, NONE);
+ public static Register R = new Register("r", false, NONE, NONE, NONE);
+
+ private final String name;
+ private final boolean pair;
+ private final int code8;
+ private final int code16;
+ private final int indexCode;
+ private final Expression indexOffset;
+
+ public Register(String name, boolean pair, int code8, int code16, int indexCode) {
+ this(name, pair, code8, code16, indexCode, null);
+ }
+
+ public Register(String name, boolean pair, int code8, int code16, int indexCode, Expression offset) {
+ if (offset != null && (!pair || indexCode == NONE))
+ throw new AssemblyException("Can only specify offset for 16-bit index registers.");
+
+ this.name = name;
+ this.pair = pair;
+ this.code8 = code8;
+ this.code16 = code16;
+ this.indexCode = indexCode;
+ this.indexOffset = offset;
+ }
+
+ public Register(Register register, Expression offset) {
+ this(register.name, register.pair, register.code8, register.code16, register.indexCode, offset);
+ }
+
+ @Override
+ public Register copy(Context context) {
+ return this;
+ }
+
+ public boolean isPair() {
+ return pair;
+ }
+
+ public int get8BitCode() {
+ if (code8 == NONE)
+ throw new EvaluationException("Register does not have an 8-bit code.");
+ return code8;
+ }
+
+ public int get16BitCode() {
+ if (code16 == NONE)
+ throw new EvaluationException("Register does not have a 16-bit code.");
+ return code16;
+ }
+
+ public boolean isIndex() {
+ return indexCode != NONE;
+ }
+
+ public byte getIndexCode() {
+ if (indexCode == NONE)
+ throw new EvaluationException("Not an index register.");
+ return (byte)indexCode;
+ }
+
+ public Expression getIndexOffset() {
+ if (indexCode == NONE || !pair)
+ throw new EvaluationException("Not an index register pair.");
+ return indexOffset;
+ }
+
+ @Override
+ public boolean isRegister() {
+ return true;
+ }
+
+ @Override
+ public Register getRegister() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public String toDebugString() {
+ return toString();
+ }
+
+ public static Register getByName(String name) {
+ switch (name) {
+ case "b":
+ case "B":
+ return Register.B;
+ case "c":
+ case "C":
+ return Register.C;
+ case "d":
+ case "D":
+ return Register.D;
+ case "e":
+ case "E":
+ return Register.E;
+ case "h":
+ case "H":
+ return Register.H;
+ case "l":
+ case "L":
+ return Register.L;
+ case "a":
+ case "A":
+ return Register.A;
+ case "ixh":
+ case "IXH":
+ return Register.IXH;
+ case "ixl":
+ case "IXL":
+ return Register.IXL;
+ case "iyh":
+ case "IYH":
+ return Register.IYH;
+ case "iyl":
+ case "IYL":
+ return Register.IYL;
+ case "bc":
+ case "BC":
+ return Register.BC;
+ case "de":
+ case "DE":
+ return Register.DE;
+ case "hl":
+ case "HL":
+ return Register.HL;
+ case "sp":
+ case "SP":
+ return Register.SP;
+ case "af":
+ case "AF":
+ return Register.AF;
+ case "af'":
+ case "AF'":
+ return Register.AF_;
+ case "ix":
+ case "IX":
+ return Register.IX;
+ case "iy":
+ case "IY":
+ return Register.IY;
+ case "i":
+ case "I":
+ return Register.I;
+ case "r":
+ case "R":
+ return Register.R;
+ }
+ return null;
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Schema.java b/src/nl/grauw/glass/expressions/Schema.java
new file mode 100644
index 0000000..46e7c69
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Schema.java
@@ -0,0 +1,154 @@
+package nl.grauw.glass.expressions;
+
+public class Schema implements SchemaType {
+
+ private SchemaType[] types;
+
+ public Schema(SchemaType... types) {
+ this.types = types;
+ }
+
+ public boolean check(Expression arguments) {
+ for (int i = 0; i < types.length; i++) {
+ if (arguments == null || !types[i].check(arguments.getElement()))
+ return false;
+ arguments = arguments.getNext();
+ }
+ return arguments == null;
+ }
+
+ public static SchemaType ANY = new IsAny();
+ public static SchemaType DIRECT = new IsDirect();
+ public static SchemaType INDIRECT = new IsIndirect();
+ public static SchemaType INTEGER = new IsInteger();
+ public static SchemaType STRING = new IsString();
+ public static SchemaType IDENTIFIER = new IsIdentifier();
+ public static SchemaType DIRECT_N = new And(DIRECT, INTEGER);
+ public static SchemaType DIRECT_R = new And(DIRECT, new IsRegister8Bit());
+ public static SchemaType DIRECT_A = new And(DIRECT, new IsRegister(Register.A));
+ public static SchemaType DIRECT_IR = new And(DIRECT, new IsRegister(Register.I, Register.R));
+ public static SchemaType DIRECT_RR = new And(DIRECT, new IsRegister(Register.BC, Register.DE, Register.HL, Register.SP));
+ public static SchemaType DIRECT_RR_INDEX = new And(DIRECT, new IsRegister(Register.BC, Register.DE, Register.HL, Register.SP, Register.IX, Register.IY));
+ public static SchemaType DIRECT_RR_AF_INDEX = new And(DIRECT, new IsRegister(Register.BC, Register.DE, Register.HL, Register.AF, Register.IX, Register.IY));
+ public static SchemaType DIRECT_DE = new And(DIRECT, new IsRegister(Register.DE));
+ public static SchemaType DIRECT_HL = new And(DIRECT, new IsRegister(Register.HL));
+ public static SchemaType DIRECT_HL_IX_IY = new And(DIRECT, new IsRegister(Register.HL, Register.IX, Register.IY));
+ public static SchemaType DIRECT_SP = new And(DIRECT, new IsRegister(Register.SP));
+ public static SchemaType DIRECT_AF = new And(DIRECT, new IsRegister(Register.AF));
+ public static SchemaType DIRECT_AF_ = new And(DIRECT, new IsRegister(Register.AF_));
+ public static SchemaType INDIRECT_N = new And(INDIRECT, INTEGER);
+ public static SchemaType INDIRECT_C = new And(INDIRECT, new IsRegister(Register.C));
+ public static SchemaType INDIRECT_BC_DE = new And(INDIRECT, new IsRegister(Register.BC, Register.DE));
+ public static SchemaType INDIRECT_HL_IX_IY = new And(INDIRECT, new IsRegister(Register.HL, Register.IX, Register.IY));
+ public static SchemaType INDIRECT_SP = new And(INDIRECT, new IsRegister(Register.SP));
+ public static SchemaType DIRECT_R_INDIRECT_HL_IX_IY = new IsDirectRIndirectHLIXIY();
+
+ public static class IsAny implements SchemaType {
+ public boolean check(Expression argument) {
+ return true;
+ }
+ }
+
+ public static class And implements SchemaType {
+ private SchemaType[] types;
+ public And(SchemaType... types) {
+ this.types = types;
+ }
+ public boolean check(Expression argument) {
+ for (SchemaType type : types)
+ if (!type.check(argument))
+ return false;
+ return true;
+ }
+ }
+
+ public static class IsDirect implements SchemaType {
+ public boolean check(Expression argument) {
+ return !argument.isGroup();
+ }
+ }
+
+ public static class IsIndirect implements SchemaType {
+ public boolean check(Expression argument) {
+ return argument.isGroup();
+ }
+ }
+
+ public static class IsAnnotation implements SchemaType {
+ private final SchemaType rhsType;
+ public IsAnnotation(SchemaType rhsType) {
+ this.rhsType = rhsType;
+ }
+ public boolean check(Expression argument) {
+ return rhsType.check(argument.getAnnotee());
+ }
+ }
+
+ public static class IsInteger implements SchemaType {
+ public boolean check(Expression argument) {
+ return argument.isInteger();
+ }
+ }
+
+ public static class IsString implements SchemaType {
+ public boolean check(Expression argument) {
+ return argument.isString();
+ }
+ }
+
+ public static class IsIdentifier implements SchemaType {
+ public boolean check(Expression argument) {
+ return argument instanceof Identifier;
+ }
+ }
+
+ public static class IsRegister implements SchemaType {
+ private Register[] registers;
+ public IsRegister(Register... registers) {
+ this.registers = registers;
+ }
+ public boolean check(Expression argument) {
+ if (argument.isRegister()) {
+ Register register = argument.getRegister();
+ for (Register expected : registers)
+ if (register == expected)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public static class IsRegister8Bit implements SchemaType {
+ public boolean check(Expression argument) {
+ if (argument.isRegister()) {
+ Register register = argument.getRegister();
+ return !register.isPair() && register != Register.I && register != Register.R;
+ }
+ return false;
+ }
+ }
+
+ public static class IsDirectRIndirectHLIXIY implements SchemaType {
+ public boolean check(Expression argument) {
+ if (argument.isRegister()) {
+ Register register = argument.getRegister();
+ return DIRECT.check(argument) && !register.isPair() && register != Register.I && register != Register.R ||
+ INDIRECT.check(argument) && (register == Register.HL || register.isIndex());
+ }
+ return false;
+ }
+ }
+
+ public static class IsFlag implements SchemaType {
+ public boolean check(Expression argument) {
+ return argument.isFlag();
+ }
+ }
+
+ public static class IsFlagZC implements SchemaType {
+ public boolean check(Expression argument) {
+ return argument.isFlag() && argument.getFlag().getCode() < 4;
+ }
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/SchemaType.java b/src/nl/grauw/glass/expressions/SchemaType.java
new file mode 100644
index 0000000..821f34f
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/SchemaType.java
@@ -0,0 +1,5 @@
+package nl.grauw.glass.expressions;
+
+public interface SchemaType {
+ public boolean check(Expression argument);
+}
diff --git a/src/nl/grauw/glass/expressions/SectionContext.java b/src/nl/grauw/glass/expressions/SectionContext.java
new file mode 100644
index 0000000..2241e72
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/SectionContext.java
@@ -0,0 +1,9 @@
+package nl.grauw.glass.expressions;
+
+import nl.grauw.glass.instructions.Section;
+
+public interface SectionContext {
+
+ public void addSection(Section section);
+
+}
diff --git a/src/nl/grauw/glass/expressions/SectionContextLiteral.java b/src/nl/grauw/glass/expressions/SectionContextLiteral.java
new file mode 100644
index 0000000..18ad7f8
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/SectionContextLiteral.java
@@ -0,0 +1,22 @@
+package nl.grauw.glass.expressions;
+
+public class SectionContextLiteral extends ContextLiteral {
+
+ private final SectionContext sectionContext;
+
+ public SectionContextLiteral(Context context, SectionContext sectionContext) {
+ super(context);
+ this.sectionContext = sectionContext;
+ }
+
+ @Override
+ public boolean isSectionContext() {
+ return true;
+ }
+
+ @Override
+ public SectionContext getSectionContext() {
+ return sectionContext;
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Sequence.java b/src/nl/grauw/glass/expressions/Sequence.java
new file mode 100644
index 0000000..cd8b66e
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Sequence.java
@@ -0,0 +1,57 @@
+package nl.grauw.glass.expressions;
+
+import java.util.List;
+
+public class Sequence extends BinaryOperator {
+
+ public Sequence(Expression value, Expression tail) {
+ super(value, tail);
+ }
+
+ @Override
+ public Sequence copy(Context context) {
+ return new Sequence(term1.copy(context), term2.copy(context));
+ }
+
+ public Expression getValue() {
+ return term1;
+ }
+
+ public Expression getTail() {
+ return term2;
+ }
+
+ @Override
+ protected void addToList(List list) {
+ term1.addToList(list);
+ Expression tail = term2;
+ while (tail != null) {
+ tail.getElement().addToList(list);
+ tail = tail.getNext();
+ }
+ }
+
+ @Override
+ public Expression getElement(int index) {
+ return index == 0 ? term1 : term2.getElement(index - 1);
+ }
+
+ @Override
+ public Expression getNext() {
+ return term2;
+ }
+
+ @Override
+ public String getLexeme() {
+ return ",";
+ }
+
+ public String toString() {
+ return "" + term1 + ", " + term2;
+ }
+
+ public String toDebugString() {
+ return "{" + term1.toDebugString() + ", " + term2.toDebugString() + "}";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/ShiftLeft.java b/src/nl/grauw/glass/expressions/ShiftLeft.java
new file mode 100644
index 0000000..ccfe89f
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/ShiftLeft.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class ShiftLeft extends BinaryOperator {
+
+ public ShiftLeft(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public ShiftLeft copy(Context context) {
+ return new ShiftLeft(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() << term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "<<";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/ShiftRight.java b/src/nl/grauw/glass/expressions/ShiftRight.java
new file mode 100644
index 0000000..88505b7
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/ShiftRight.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class ShiftRight extends BinaryOperator {
+
+ public ShiftRight(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public ShiftRight copy(Context context) {
+ return new ShiftRight(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() >> term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return ">>";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/StringLiteral.java b/src/nl/grauw/glass/expressions/StringLiteral.java
new file mode 100644
index 0000000..8f5547c
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/StringLiteral.java
@@ -0,0 +1,63 @@
+package nl.grauw.glass.expressions;
+
+import java.util.List;
+
+public class StringLiteral extends Literal {
+
+ private final String string;
+
+ public StringLiteral(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public StringLiteral copy(Context context) {
+ return this;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return string.length() == 1;
+ }
+
+ @Override
+ public int getInteger() {
+ if (string.length() != 1)
+ throw new EvaluationException("Can not evaluate strings of more than 1 character to integer.");
+ return string.codePointAt(0);
+ }
+
+ @Override
+ public boolean isString() {
+ return true;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ @Override
+ protected void addToList(List list) {
+ for (int i = 0, length = string.length(); i < length; i++)
+ list.add(new CharacterLiteral(string.charAt(i)));
+ }
+
+ public String toString() {
+ String escaped = string;
+ escaped = escaped.replace("\\", "\\\\");
+ escaped = escaped.replace("\"", "\\\"");
+ escaped = escaped.replace("\0", "\\0");
+ escaped = escaped.replace("\7", "\\a");
+ escaped = escaped.replace("\t", "\\t");
+ escaped = escaped.replace("\n", "\\n");
+ escaped = escaped.replace("\f", "\\f");
+ escaped = escaped.replace("\r", "\\r");
+ escaped = escaped.replace("\33", "\\e");
+ return "\"" + escaped + "\"";
+ }
+
+ public String toDebugString() {
+ return toString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Subtract.java b/src/nl/grauw/glass/expressions/Subtract.java
new file mode 100644
index 0000000..e9fee72
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Subtract.java
@@ -0,0 +1,51 @@
+package nl.grauw.glass.expressions;
+
+public class Subtract extends BinaryOperator {
+
+ public Subtract(Expression minuend, Expression subtrahend) {
+ super(minuend, subtrahend);
+ }
+
+ @Override
+ public Subtract copy(Context context) {
+ return new Subtract(term1.copy(context), term2.copy(context));
+ }
+
+ public Expression getMinuend() {
+ return term1;
+ }
+
+ public Expression getSubtrahend() {
+ return term2;
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() - term2.getInteger();
+ }
+
+ @Override
+ public boolean isRegister() {
+ if (term1.isRegister() && term2.isInteger()) {
+ Register register = term1.getRegister();
+ return register.isIndex() && register.isPair();
+ }
+ return false;
+ }
+
+ @Override
+ public Register getRegister() {
+ if (term1.isRegister() && term2.isInteger()) {
+ Register register = term1.getRegister();
+ if (register.isIndex() && register.isPair())
+ return new Register(register, new Subtract(register.getIndexOffset(), term2));
+ }
+ throw new EvaluationException("Not a register.");
+ }
+
+ @Override
+ public String getLexeme() {
+ return "-";
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/UnaryOperator.java b/src/nl/grauw/glass/expressions/UnaryOperator.java
new file mode 100644
index 0000000..02c59df
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/UnaryOperator.java
@@ -0,0 +1,30 @@
+package nl.grauw.glass.expressions;
+
+public abstract class UnaryOperator extends Expression {
+
+ protected final Expression term;
+
+ public abstract String getLexeme();
+
+ public UnaryOperator(Expression term) {
+ this.term = term;
+ }
+
+ public Expression getTerm() {
+ return term;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return term.isInteger();
+ }
+
+ public String toString() {
+ return getLexeme() + term;
+ }
+
+ public String toDebugString() {
+ return getLexeme() + term.toDebugString();
+ }
+
+}
diff --git a/src/nl/grauw/glass/expressions/Xor.java b/src/nl/grauw/glass/expressions/Xor.java
new file mode 100644
index 0000000..5558083
--- /dev/null
+++ b/src/nl/grauw/glass/expressions/Xor.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.expressions;
+
+public class Xor extends BinaryOperator {
+
+ public Xor(Expression term1, Expression term2) {
+ super(term1, term2);
+ }
+
+ @Override
+ public Xor copy(Context context) {
+ return new Xor(term1.copy(context), term2.copy(context));
+ }
+
+ @Override
+ public int getInteger() {
+ return term1.getInteger() ^ term2.getInteger();
+ }
+
+ @Override
+ public String getLexeme() {
+ return "^";
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Adc.java b/src/nl/grauw/glass/instructions/Adc.java
new file mode 100644
index 0000000..fd3d7ed
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Adc.java
@@ -0,0 +1,91 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Adc extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Adc_A_R.ARGUMENTS.check(arguments))
+ return new Adc_A_R(context, arguments.getElement(1));
+ if (Adc_A_N.ARGUMENTS.check(arguments))
+ return new Adc_A_N(context, arguments.getElement(1));
+ if (Adc_HL_RR.ARGUMENTS.check(arguments))
+ return new Adc_HL_RR(context, arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Adc_A_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Adc_A_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0x88 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class Adc_A_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Adc_A_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xCE, (byte)argument.getInteger() };
+ }
+
+ }
+
+ public static class Adc_HL_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_HL, Schema.DIRECT_RR);
+
+ private Expression argument;
+
+ public Adc_HL_RR(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)(0x4A | argument.getRegister().get16BitCode() << 4) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Add.java b/src/nl/grauw/glass/instructions/Add.java
new file mode 100644
index 0000000..4a13358
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Add.java
@@ -0,0 +1,96 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Add extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Add_A_R.ARGUMENTS.check(arguments))
+ return new Add_A_R(context, arguments.getElement(1));
+ if (Add_A_N.ARGUMENTS.check(arguments))
+ return new Add_A_N(context, arguments.getElement(1));
+ if (Add_HL_RR.ARGUMENTS.check(arguments))
+ return new Add_HL_RR(context, arguments.getElement(0).getRegister(), arguments.getElement(1).getRegister());
+ throw new ArgumentException();
+ }
+
+ public static class Add_A_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Add_A_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0x80 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class Add_A_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Add_A_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xC6, (byte)argument.getInteger() };
+ }
+
+ }
+
+ public static class Add_HL_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_HL_IX_IY, Schema.DIRECT_RR_INDEX);
+
+ private Register register1;
+ private Register register2;
+
+ public Add_HL_RR(Scope context, Register register1, Register register2) {
+ super(context);
+ this.register1 = register1;
+ this.register2 = register2;
+
+ if (register1.get16BitCode() == register2.get16BitCode() && register1 != register2)
+ throw new ArgumentException();
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(register1.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return indexifyDirect(register1.getRegister(), (byte)(0x09 | register2.getRegister().get16BitCode() << 4));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/And.java b/src/nl/grauw/glass/instructions/And.java
new file mode 100644
index 0000000..610494d
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/And.java
@@ -0,0 +1,66 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class And extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (And_R.ARGUMENTS.check(arguments))
+ return new And_R(context, arguments);
+ if (And_N.ARGUMENTS.check(arguments))
+ return new And_N(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class And_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public And_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0xA0 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class And_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public And_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xE6, (byte)argument.getInteger() };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/ArgumentException.java b/src/nl/grauw/glass/instructions/ArgumentException.java
new file mode 100644
index 0000000..72f5dd5
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/ArgumentException.java
@@ -0,0 +1,16 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.AssemblyException;
+
+public class ArgumentException extends AssemblyException {
+ private static final long serialVersionUID = 1L;
+
+ public ArgumentException() {
+ this("Invalid arguments.");
+ }
+
+ public ArgumentException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Bit.java b/src/nl/grauw/glass/instructions/Bit.java
new file mode 100644
index 0000000..61d5f1a
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Bit.java
@@ -0,0 +1,46 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Bit extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Bit_N_R.ARGUMENTS.check(arguments))
+ return new Bit_N_R(context, arguments.getElement(0), arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Bit_N_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N, Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Bit_N_R(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument2.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int value = argument1.getInteger();
+ if (value < 0 || value > 7)
+ throw new ArgumentException();
+ Register register = argument2.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x40 | value << 3 | register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Call.java b/src/nl/grauw/glass/instructions/Call.java
new file mode 100644
index 0000000..66578dd
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Call.java
@@ -0,0 +1,68 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Call extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Call_F_N.ARGUMENTS.check(arguments))
+ return new Call_F_N(context, arguments.getElement(0), arguments.getElement(1));
+ if (Call_N.ARGUMENTS.check(arguments))
+ return new Call_N(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Call_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Call_N(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 3;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument.getAddress();
+ return new byte[] { (byte)0xCD, (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+ public static class Call_F_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(new Schema.IsFlag(), Schema.DIRECT_N);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Call_F_N(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return 3;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument2.getAddress();
+ return new byte[] { (byte)(0xC4 | argument1.getFlag().getCode() << 3), (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ccf.java b/src/nl/grauw/glass/instructions/Ccf.java
new file mode 100644
index 0000000..25d68d5
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ccf.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ccf extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ccf_.ARGUMENTS.check(arguments))
+ return new Ccf_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Ccf_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ccf_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x3F };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Cp.java b/src/nl/grauw/glass/instructions/Cp.java
new file mode 100644
index 0000000..029672d
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Cp.java
@@ -0,0 +1,66 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Cp extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Cp_R.ARGUMENTS.check(arguments))
+ return new Cp_R(context, arguments);
+ if (Cp_N.ARGUMENTS.check(arguments))
+ return new Cp_N(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class Cp_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Cp_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0xB8 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class Cp_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Cp_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xFE, (byte)argument.getInteger() };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Cpd.java b/src/nl/grauw/glass/instructions/Cpd.java
new file mode 100644
index 0000000..f4b7c54
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Cpd.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Cpd extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Cpd_.ARGUMENTS.check(arguments))
+ return new Cpd_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Cpd_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Cpd_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xA9 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Cpdr.java b/src/nl/grauw/glass/instructions/Cpdr.java
new file mode 100644
index 0000000..784676c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Cpdr.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Cpdr extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Cpdr_.ARGUMENTS.check(arguments))
+ return new Cpdr_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Cpdr_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Cpdr_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xB9 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Cpi.java b/src/nl/grauw/glass/instructions/Cpi.java
new file mode 100644
index 0000000..a91e072
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Cpi.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Cpi extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Cpi_.ARGUMENTS.check(arguments))
+ return new Cpi_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Cpi_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Cpi_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xA1 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Cpir.java b/src/nl/grauw/glass/instructions/Cpir.java
new file mode 100644
index 0000000..de88118
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Cpir.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Cpir extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Cpir_.ARGUMENTS.check(arguments))
+ return new Cpir_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Cpir_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Cpir_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xB1 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Cpl.java b/src/nl/grauw/glass/instructions/Cpl.java
new file mode 100644
index 0000000..cdc5fda
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Cpl.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Cpl extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Cpl_.ARGUMENTS.check(arguments))
+ return new Cpl_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Cpl_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Cpl_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x2F };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Daa.java b/src/nl/grauw/glass/instructions/Daa.java
new file mode 100644
index 0000000..6fce034
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Daa.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Daa extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Daa_.ARGUMENTS.check(arguments))
+ return new Daa_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Daa_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Daa_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x27 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Db.java b/src/nl/grauw/glass/instructions/Db.java
new file mode 100644
index 0000000..c7be5cc
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Db.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+
+public class Db extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (arguments != null)
+ return new Db_N(context, arguments.getList());
+ throw new ArgumentException();
+ }
+
+ public static class Db_N extends InstructionObject {
+
+ private List arguments;
+
+ public Db_N(Scope context, List arguments) {
+ super(context);
+ this.arguments = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return arguments.size();
+ }
+
+ @Override
+ public byte[] getBytes() {
+ byte[] bytes = new byte[arguments.size()];
+ for (int i = 0, length = arguments.size(); i < length; i++)
+ bytes[i] = (byte)arguments.get(i).getInteger();
+ return bytes;
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Dd.java b/src/nl/grauw/glass/instructions/Dd.java
new file mode 100644
index 0000000..dd0198a
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Dd.java
@@ -0,0 +1,45 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+
+public class Dd extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (arguments != null)
+ return new Dd_N(context, arguments.getList());
+ throw new ArgumentException();
+ }
+
+ public static class Dd_N extends InstructionObject {
+
+ private List arguments;
+
+ public Dd_N(Scope context, List arguments) {
+ super(context);
+ this.arguments = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return arguments.size() * 4;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ byte[] bytes = new byte[arguments.size() * 4];
+ for (int i = 0, length = arguments.size(); i < length; i++) {
+ bytes[i * 4] = (byte)arguments.get(i).getInteger();
+ bytes[i * 4 + 1] = (byte)(arguments.get(i).getInteger() >> 8);
+ bytes[i * 4 + 2] = (byte)(arguments.get(i).getInteger() >> 16);
+ bytes[i * 4 + 3] = (byte)(arguments.get(i).getInteger() >> 24);
+ }
+ return bytes;
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Dec.java b/src/nl/grauw/glass/instructions/Dec.java
new file mode 100644
index 0000000..68b773b
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Dec.java
@@ -0,0 +1,67 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Dec extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Dec_R.ARGUMENTS.check(arguments))
+ return new Dec_R(context, arguments.getElement(0));
+ if (Dec_RR.ARGUMENTS.check(arguments))
+ return new Dec_RR(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Dec_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Dec_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0x05 | register.get8BitCode() << 3));
+ }
+
+ }
+
+ public static class Dec_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_RR_INDEX);
+
+ private Expression argument;
+
+ public Dec_RR(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyDirect(register, (byte)(0x0B | register.get16BitCode() << 4));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Di.java b/src/nl/grauw/glass/instructions/Di.java
new file mode 100644
index 0000000..1549af8
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Di.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Di extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Di_.ARGUMENTS.check(arguments))
+ return new Di_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Di_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Di_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xF3 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Djnz.java b/src/nl/grauw/glass/instructions/Djnz.java
new file mode 100644
index 0000000..428f6f6
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Djnz.java
@@ -0,0 +1,42 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Djnz extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Djnz_N.ARGUMENTS.check(arguments))
+ return new Djnz_N(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class Djnz_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Djnz_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int offset = argument.getAddress() - (context.getAddress() + 2);
+ if (offset < -128 || offset > 127)
+ throw new ArgumentException("Jump offset out of range: " + offset);
+ return new byte[] { (byte)0x10, (byte)offset };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ds.java b/src/nl/grauw/glass/instructions/Ds.java
new file mode 100644
index 0000000..0b670a4
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ds.java
@@ -0,0 +1,105 @@
+package nl.grauw.glass.instructions;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nl.grauw.glass.AssemblyException;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Identifier;
+import nl.grauw.glass.expressions.IntegerLiteral;
+import nl.grauw.glass.expressions.Schema;
+import nl.grauw.glass.expressions.SectionContext;
+
+public class Ds extends InstructionFactory implements SectionContext {
+
+ public static Schema ARGUMENTS_N = new Schema(new Schema.IsAnnotation(Schema.INTEGER));
+ public static Schema ARGUMENTS_N_N = new Schema(Schema.INTEGER, Schema.INTEGER);
+
+ private final List sections = new ArrayList<>();
+
+ @Override
+ public void addSection(Section section) {
+ sections.add(section);
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS_N.check(arguments))
+ return new Ds_N_N(context, arguments.getAnnotation(),
+ arguments.getAnnotee(), IntegerLiteral.ZERO);
+ if (ARGUMENTS_N_N.check(arguments))
+ return new Ds_N_N(context, null, arguments.getElement(0), arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public class Ds_N_N extends InstructionObject {
+
+ private final boolean virtual;
+ private final Expression size;
+ private final Expression value;
+
+ public Ds_N_N(Scope context, Identifier annotation, Expression size, Expression value) {
+ super(context);
+ this.virtual = annotation != null && ("virtual".equals(annotation.getName()) || "VIRTUAL".equals(annotation.getName()));
+ this.size = size;
+ this.value = value;
+
+ if (annotation != null && !virtual)
+ throw new ArgumentException("Unsupported annotation: " + annotation.getName());
+ }
+
+ @Override
+ public int resolve(int address) {
+ int innerAddress = address;
+ for (Section section : sections)
+ innerAddress = section.getSource().resolve(innerAddress);
+ return super.resolve(address);
+ }
+
+ @Override
+ public int getSize() {
+ return size.getInteger();
+ }
+
+ @Override
+ public void generateObjectCode(OutputStream output) throws IOException {
+ byte[] bytes = getSectionBytes();
+ if (bytes.length > size.getInteger())
+ throw new AssemblyException("Section size exceeds space (required: " +
+ bytes.length + " bytes, available: " + size.getInteger() + " bytes).");
+
+ if (virtual)
+ return;
+
+ output.write(bytes);
+
+ byte[] padding = new byte[size.getInteger() - bytes.length];
+ Arrays.fill(padding, (byte)value.getInteger());
+
+ output.write(padding);
+ }
+
+ public byte[] getSectionBytes() throws IOException {
+ ByteArrayOutputStream sourceByteStream = new ByteArrayOutputStream(size.getInteger());
+ for (Section section : sections)
+ section.getSource().generateObjectCode(sourceByteStream);
+ return sourceByteStream.toByteArray();
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (virtual)
+ return new byte[] {};
+ byte[] bytes = new byte[size.getInteger()];
+ Arrays.fill(bytes, (byte)value.getInteger());
+ return bytes;
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Dw.java b/src/nl/grauw/glass/instructions/Dw.java
new file mode 100644
index 0000000..07c64ab
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Dw.java
@@ -0,0 +1,43 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+
+public class Dw extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (arguments != null)
+ return new Dw_N(context, arguments.getList());
+ throw new ArgumentException();
+ }
+
+ public static class Dw_N extends InstructionObject {
+
+ private List arguments;
+
+ public Dw_N(Scope context, List arguments) {
+ super(context);
+ this.arguments = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return arguments.size() * 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ byte[] bytes = new byte[arguments.size() * 2];
+ for (int i = 0, length = arguments.size(); i < length; i++) {
+ bytes[i * 2] = (byte)arguments.get(i).getInteger();
+ bytes[i * 2 + 1] = (byte)(arguments.get(i).getInteger() >> 8);
+ }
+ return bytes;
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ei.java b/src/nl/grauw/glass/instructions/Ei.java
new file mode 100644
index 0000000..cbe458c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ei.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ei extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ei_.ARGUMENTS.check(arguments))
+ return new Ei_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Ei_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ei_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xFB };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Else.java b/src/nl/grauw/glass/instructions/Else.java
new file mode 100644
index 0000000..e47a698
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Else.java
@@ -0,0 +1,18 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Else extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Empty.java b/src/nl/grauw/glass/instructions/Empty.java
new file mode 100644
index 0000000..d08730b
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Empty.java
@@ -0,0 +1,35 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+
+public class Empty extends InstructionFactory {
+
+ public static final Empty INSTANCE = new Empty();
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ return new EmptyObject(context);
+ }
+
+ public static class EmptyObject extends InstructionObject {
+
+ private static final byte[] NO_BYTES = new byte[] {};
+
+ public EmptyObject(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 0;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return NO_BYTES;
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/End.java b/src/nl/grauw/glass/instructions/End.java
new file mode 100644
index 0000000..6055e70
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/End.java
@@ -0,0 +1,18 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class End extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Endif.java b/src/nl/grauw/glass/instructions/Endif.java
new file mode 100644
index 0000000..736f241
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Endif.java
@@ -0,0 +1,18 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Endif extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Endm.java b/src/nl/grauw/glass/instructions/Endm.java
new file mode 100644
index 0000000..a7af6d2
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Endm.java
@@ -0,0 +1,18 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Endm extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Endp.java b/src/nl/grauw/glass/instructions/Endp.java
new file mode 100644
index 0000000..28f1628
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Endp.java
@@ -0,0 +1,18 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Endp extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ends.java b/src/nl/grauw/glass/instructions/Ends.java
new file mode 100644
index 0000000..d213ac3
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ends.java
@@ -0,0 +1,18 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ends extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Equ.java b/src/nl/grauw/glass/instructions/Equ.java
new file mode 100644
index 0000000..dfc28f3
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Equ.java
@@ -0,0 +1,18 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Equ extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema(Schema.ANY);
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Error.java b/src/nl/grauw/glass/instructions/Error.java
new file mode 100644
index 0000000..49c0ae5
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Error.java
@@ -0,0 +1,51 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.AssemblyException;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Error extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+ public static Schema ARGUMENTS_S = new Schema(Schema.STRING);
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments) || ARGUMENTS_S.check(arguments))
+ return new Error_(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class Error_ extends Empty.EmptyObject {
+
+ private final Expression argument;
+
+ public Error_(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (argument == null)
+ throw new ErrorDirectiveException();
+ throw new ErrorDirectiveException(argument.getString());
+ }
+
+ }
+
+ public static class ErrorDirectiveException extends AssemblyException {
+ private static final long serialVersionUID = 1L;
+
+ public ErrorDirectiveException() {
+ this("Error directive was encountered.");
+ }
+
+ public ErrorDirectiveException(String message) {
+ super(message);
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ex.java b/src/nl/grauw/glass/instructions/Ex.java
new file mode 100644
index 0000000..2e53c83
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ex.java
@@ -0,0 +1,83 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ex extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ex_AF.ARGUMENTS.check(arguments))
+ return new Ex_AF(context);
+ if (Ex_DE_HL.ARGUMENTS.check(arguments))
+ return new Ex_DE_HL(context);
+ if (Ex_SP.ARGUMENTS.check(arguments))
+ return new Ex_SP(context, arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Ex_AF extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_AF, Schema.DIRECT_AF_);
+
+ public Ex_AF(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x08 };
+ }
+
+ }
+
+ public static class Ex_DE_HL extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_DE, Schema.DIRECT_HL);
+
+ public Ex_DE_HL(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xEB };
+ }
+
+ }
+
+ public static class Ex_SP extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_SP, Schema.DIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Ex_SP(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return indexifyDirect(argument.getRegister(), (byte)0xE3);
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Exx.java b/src/nl/grauw/glass/instructions/Exx.java
new file mode 100644
index 0000000..efb750c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Exx.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Exx extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Exx_.ARGUMENTS.check(arguments))
+ return new Exx_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Exx_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Exx_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xD9 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Halt.java b/src/nl/grauw/glass/instructions/Halt.java
new file mode 100644
index 0000000..040e22b
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Halt.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Halt extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Halt_.ARGUMENTS.check(arguments))
+ return new Halt_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Halt_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Halt_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x76 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/If.java b/src/nl/grauw/glass/instructions/If.java
new file mode 100644
index 0000000..ff5e2cd
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/If.java
@@ -0,0 +1,77 @@
+package nl.grauw.glass.instructions;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import nl.grauw.glass.AssemblyException;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class If extends InstructionFactory {
+
+ private static Schema ARGUMENTS = new Schema(Schema.INTEGER);
+
+ private final Source thenSource;
+ private final Source elseSource;
+
+ public If(Source thenSource, Source elseSource) {
+ this.thenSource = thenSource;
+ this.elseSource = elseSource;
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new IfObject(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public class IfObject extends InstructionObject {
+
+ private final Expression argument;
+
+ public IfObject(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int resolve(int address) {
+ context.setAddress(address);
+ if (argument.getInteger() != 0) {
+ thenSource.register();
+ thenSource.expand();
+ return thenSource.resolve(address);
+ } else if (elseSource != null) {
+ elseSource.register();
+ elseSource.expand();
+ return elseSource.resolve(address);
+ } else {
+ return address;
+ }
+ }
+
+ @Override
+ public int getSize() {
+ throw new AssemblyException("Not implemented.");
+ }
+
+ @Override
+ public void generateObjectCode(OutputStream output) throws IOException {
+ if (argument.getInteger() != 0) {
+ thenSource.generateObjectCode(output);
+ } else if (elseSource != null) {
+ elseSource.generateObjectCode(output);
+ }
+ }
+
+ @Override
+ public byte[] getBytes() {
+ throw new AssemblyException("Not implemented.");
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Im.java b/src/nl/grauw/glass/instructions/Im.java
new file mode 100644
index 0000000..5bf6539
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Im.java
@@ -0,0 +1,47 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Im extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Im_N.ARGUMENTS.check(arguments))
+ return new Im_N(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Im_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Im_N(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int value = argument.getInteger();
+ if (value == 0) {
+ return new byte[] { (byte)0xED, (byte)0x46 };
+ } else if (value == 1) {
+ return new byte[] { (byte)0xED, (byte)0x56 };
+ } else if (value == 2) {
+ return new byte[] { (byte)0xED, (byte)0x5E };
+ }
+ throw new ArgumentException();
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/In.java b/src/nl/grauw/glass/instructions/In.java
new file mode 100644
index 0000000..1d1ea60
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/In.java
@@ -0,0 +1,68 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class In extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (In_N_C.ARGUMENTS.check(arguments))
+ return new In_N_C(context, arguments.getElement(0));
+ if (In_N_C.ARGUMENTS_NO_R.check(arguments))
+ return new In_N_C(context, Register.HL);
+ if (In_N_N.ARGUMENTS.check(arguments))
+ return new In_N_N(context, arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class In_N_C extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R, Schema.INDIRECT_C);
+ public static Schema ARGUMENTS_NO_R = new Schema(Schema.INDIRECT_C);
+
+ private Expression argument;
+
+ public In_N_C(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)(0x40 | argument.getRegister().get8BitCode() << 3) };
+ }
+
+ }
+
+ public static class In_N_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.INDIRECT_N);
+
+ private Expression argument;
+
+ public In_N_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xDB, (byte)argument.getInteger() };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Inc.java b/src/nl/grauw/glass/instructions/Inc.java
new file mode 100644
index 0000000..20b78fb
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Inc.java
@@ -0,0 +1,67 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Inc extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Inc_R.ARGUMENTS.check(arguments))
+ return new Inc_R(context, arguments.getElement(0));
+ if (Inc_RR.ARGUMENTS.check(arguments))
+ return new Inc_RR(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Inc_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Inc_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0x04 | register.get8BitCode() << 3));
+ }
+
+ }
+
+ public static class Inc_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_RR_INDEX);
+
+ private Expression argument;
+
+ public Inc_RR(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyDirect(register, (byte)(0x03 | register.get16BitCode() << 4));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Incbin.java b/src/nl/grauw/glass/instructions/Incbin.java
new file mode 100644
index 0000000..6ba667e
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Incbin.java
@@ -0,0 +1,98 @@
+package nl.grauw.glass.instructions;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nl.grauw.glass.AssemblyException;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.IntegerLiteral;
+import nl.grauw.glass.expressions.Schema;
+
+public class Incbin extends InstructionFactory {
+
+ private final List basePaths = new ArrayList();
+
+ public Incbin(File basePath, List includePaths) {
+ this.basePaths.add(basePath);
+ this.basePaths.addAll(includePaths);
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Incbin_.ARGUMENTS_S.check(arguments))
+ return new Incbin_(context, arguments.getElement(0),
+ IntegerLiteral.ZERO, null, basePaths);
+ if (Incbin_.ARGUMENTS_S_N.check(arguments))
+ return new Incbin_(context, arguments.getElement(0),
+ arguments.getElement(1), null, basePaths);
+ if (Incbin_.ARGUMENTS_S_N_N.check(arguments))
+ return new Incbin_(context, arguments.getElement(0),
+ arguments.getElement(1), arguments.getElement(2), basePaths);
+ throw new ArgumentException();
+ }
+
+ public static class Incbin_ extends InstructionObject {
+
+ public static Schema ARGUMENTS_S = new Schema(Schema.STRING);
+ public static Schema ARGUMENTS_S_N = new Schema(Schema.STRING, Schema.INTEGER);
+ public static Schema ARGUMENTS_S_N_N = new Schema(Schema.STRING, Schema.INTEGER, Schema.INTEGER);
+
+ private final Expression path;
+ private final Expression start;
+ private final Expression length;
+ private final List basePaths;
+ private byte[] bytes;
+
+ public Incbin_(Scope context, Expression path, Expression start, Expression length, List basePaths) {
+ super(context);
+ this.path = path;
+ this.start = start;
+ this.length = length;
+ this.basePaths = basePaths;
+ }
+
+ @Override
+ public int getSize() {
+ return length != null ? length.getInteger() : getBytes().length;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (bytes == null) {
+ byte[] allBytes = loadFile();
+
+ int from = this.start.getInteger();
+ int to = this.length != null ? from + this.length.getInteger() : allBytes.length;
+ if (from < 0 || from > allBytes.length)
+ throw new AssemblyException("Incbin start exceeds file size.");
+ if (to < from || to > allBytes.length)
+ throw new AssemblyException("Incbin length exceeds file size.");
+
+ bytes = Arrays.copyOfRange(allBytes, from, to);
+ }
+ return bytes;
+ }
+
+ private byte[] loadFile()
+ {
+ for (File basePath : basePaths) {
+ File fullPath = new File(basePath.getParent(), path.getString());
+ if (fullPath.exists()) {
+ try {
+ return Files.readAllBytes(fullPath.toPath());
+ } catch (IOException e) {
+ throw new AssemblyException(e);
+ }
+ }
+ }
+ throw new AssemblyException("Incbin file not found: " + path.getString());
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Include.java b/src/nl/grauw/glass/instructions/Include.java
new file mode 100644
index 0000000..62b5ee1
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Include.java
@@ -0,0 +1,24 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Include extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema(Schema.STRING);
+ public static Schema ARGUMENTS_ONCE = new Schema(new Schema.IsAnnotation(Schema.STRING));
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ if (ARGUMENTS_ONCE.check(arguments)) {
+ String annotation = arguments.getAnnotation().getName();
+ if ("once".equals(annotation) || "ONCE".equals(annotation))
+ return new Empty.EmptyObject(context);
+ }
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ind.java b/src/nl/grauw/glass/instructions/Ind.java
new file mode 100644
index 0000000..74fde4c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ind.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ind extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ind_.ARGUMENTS.check(arguments))
+ return new Ind_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Ind_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ind_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xAA };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Indr.java b/src/nl/grauw/glass/instructions/Indr.java
new file mode 100644
index 0000000..779bdba
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Indr.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Indr extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Indr_.ARGUMENTS.check(arguments))
+ return new Indr_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Indr_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Indr_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xBA };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ini.java b/src/nl/grauw/glass/instructions/Ini.java
new file mode 100644
index 0000000..f018ded
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ini.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ini extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ini_.ARGUMENTS.check(arguments))
+ return new Ini_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Ini_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ini_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xA2 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Inir.java b/src/nl/grauw/glass/instructions/Inir.java
new file mode 100644
index 0000000..dc11813
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Inir.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Inir extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Inir_.ARGUMENTS.check(arguments))
+ return new Inir_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Inir_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Inir_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xB2 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/InstructionFactory.java b/src/nl/grauw/glass/instructions/InstructionFactory.java
new file mode 100644
index 0000000..0c129e2
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/InstructionFactory.java
@@ -0,0 +1,20 @@
+package nl.grauw.glass.instructions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+
+public abstract class InstructionFactory {
+
+ public List expand(Line line) {
+ List lines = new ArrayList();
+ lines.add(line);
+ return lines;
+ }
+
+ public abstract InstructionObject createObject(Scope context, Expression arguments);
+
+}
diff --git a/src/nl/grauw/glass/instructions/InstructionObject.java b/src/nl/grauw/glass/instructions/InstructionObject.java
new file mode 100644
index 0000000..7b7c2eb
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/InstructionObject.java
@@ -0,0 +1,102 @@
+package nl.grauw.glass.instructions;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Register;
+
+public abstract class InstructionObject {
+
+ protected final Scope context;
+
+ public InstructionObject(Scope context) {
+ this.context = context;
+ }
+
+ public int resolve(int address) {
+ context.setAddress(address);
+ return address + getSize();
+ }
+
+ public abstract int getSize();
+
+ public void generateObjectCode(OutputStream output) throws IOException {
+ byte[] object = getBytes();
+ output.write(object, 0, object.length);
+ }
+
+ public abstract byte[] getBytes();
+
+ public int indexifyDirect(Register register, int size) {
+ return register.isIndex() ? size + 1 : size;
+ }
+
+ public int indexifyIndirect(Register register, int size) {
+ return register.isIndex() ? register.isPair() ? size + 2 : size + 1 : size;
+ }
+
+ /**
+ * Inserts index register prefix in the object code if needed.
+ */
+ public byte[] indexifyDirect(Register register, byte byte1) {
+ if (!register.isIndex())
+ return new byte[] { byte1 };
+ if (register.isPair() && register.getIndexOffset().getInteger() != 0)
+ throw new ArgumentException("Can not have index offset for direct addressing.");
+ return new byte[] { register.getIndexCode(), byte1 };
+ }
+
+ public byte[] indexifyDirect(Register register, byte byte1, byte byte2) {
+ if (!register.isIndex())
+ return new byte[] { byte1, byte2 };
+ if (register.isPair() && register.getIndexOffset().getInteger() != 0)
+ throw new ArgumentException("Can not have index offset for direct addressing.");
+ return new byte[] { register.getIndexCode(), byte1, byte2 };
+ }
+
+ public byte[] indexifyDirect(Register register, byte byte1, byte byte2, byte byte3) {
+ if (!register.isIndex())
+ return new byte[] { byte1, byte2, byte3 };
+ if (register.isPair() && register.getIndexOffset().getInteger() != 0)
+ throw new ArgumentException("Can not have index offset for direct addressing.");
+ return new byte[] { register.getIndexCode(), byte1, byte2, byte3 };
+ }
+
+ /**
+ * Inserts index register prefix + offset in the object code if needed.
+ */
+ public byte[] indexifyIndirect(Register register, byte byte1) {
+ if (!register.isIndex())
+ return new byte[] { byte1 };
+ if (!register.isPair())
+ return indexifyDirect(register, byte1);
+ int offset = register.getIndexOffset().getInteger();
+ if (offset < -128 || offset > 127)
+ throw new ArgumentException("Index offset out of range: " + offset);
+ return new byte[] { register.getIndexCode(), byte1, (byte)offset };
+ }
+
+ public byte[] indexifyIndirect(Register register, byte byte1, byte byte2) {
+ if (!register.isIndex())
+ return new byte[] { byte1, byte2 };
+ if (!register.isPair())
+ return indexifyDirect(register, byte1, byte2);
+ int offset = register.getIndexOffset().getInteger();
+ if (offset < -128 || offset > 127)
+ throw new ArgumentException("Index offset out of range: " + offset);
+ return new byte[] { register.getIndexCode(), byte1, (byte)offset, byte2 };
+ }
+
+ public byte[] indexifyOnlyIndirect(Register register, byte byte1, byte byte2) {
+ if (!register.isIndex())
+ return new byte[] { byte1, byte2 };
+ if (!register.isPair())
+ throw new ArgumentException();
+ int offset = register.getIndexOffset().getInteger();
+ if (offset < -128 || offset > 127)
+ throw new ArgumentException("Index offset out of range: " + offset);
+ return new byte[] { register.getIndexCode(), byte1, (byte)offset, byte2 };
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Irp.java b/src/nl/grauw/glass/instructions/Irp.java
new file mode 100644
index 0000000..5030da4
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Irp.java
@@ -0,0 +1,51 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.ParameterScope;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Irp extends InstructionFactory {
+
+ private final Source source;
+
+ public Irp(Source source) {
+ this.source = source;
+ }
+
+ public List expand(Line line) {
+ Expression arguments = line.getArguments();
+ if (arguments == null || !Schema.IDENTIFIER.check(arguments.getElement()))
+ throw new ArgumentException();
+
+ List lines = super.expand(line);
+ Expression parameter = arguments.getElement();
+ for (int i = 0; (arguments = arguments.getNext()) != null; i++) {
+ Scope parameterScope = new ParameterScope(line.getScope(), parameter, arguments.getElement());
+
+ // set up the number symbol
+ line.getScope().addSymbol(Integer.toString(i), parameterScope);
+ Line rept = new Line(parameterScope, line);
+ rept.setInstruction(Empty.INSTANCE);
+ lines.add(rept); // so that the parameterScope address gets initialised
+
+ // copy & expand content
+ List lineCopies = source.getLineCopies(parameterScope);
+ for (Line lineCopy : lineCopies)
+ lineCopy.register(parameterScope);
+ for (Line lineCopy : lineCopies)
+ lines.addAll(lineCopy.expand());
+ }
+ return lines;
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ return new Empty.EmptyObject(context);
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Jp.java b/src/nl/grauw/glass/instructions/Jp.java
new file mode 100644
index 0000000..2feab6c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Jp.java
@@ -0,0 +1,94 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Jp extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Jp_F_N.ARGUMENTS.check(arguments))
+ return new Jp_F_N(context, arguments.getElement(0), arguments.getElement(1));
+ if (Jp_HL.ARGUMENTS.check(arguments) || Jp_HL.ARGUMENTS_ALT.check(arguments))
+ return new Jp_HL(context, arguments.getElement(0));
+ if (Jp_N.ARGUMENTS.check(arguments))
+ return new Jp_N(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Jp_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Jp_N(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 3;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument.getAddress();
+ return new byte[] { (byte)0xC3, (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+ public static class Jp_F_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(new Schema.IsFlag(), Schema.DIRECT_N);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Jp_F_N(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return 3;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument2.getAddress();
+ return new byte[] { (byte)(0xC2 | argument1.getFlag().getCode() << 3), (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+ public static class Jp_HL extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_HL_IX_IY);
+ public static Schema ARGUMENTS_ALT = new Schema(Schema.DIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Jp_HL(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return indexifyDirect(argument.getRegister(), (byte)0xE9);
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Jr.java b/src/nl/grauw/glass/instructions/Jr.java
new file mode 100644
index 0000000..2c2eca0
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Jr.java
@@ -0,0 +1,74 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Jr extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Jr_F_N.ARGUMENTS.check(arguments))
+ return new Jr_F_N(context, arguments.getElement(0), arguments.getElement(1));
+ if (Jr_N.ARGUMENTS.check(arguments))
+ return new Jr_N(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Jr_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Jr_N(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int offset = argument.getAddress() - (context.getAddress() + 2);
+ if (offset < -128 || offset > 127)
+ throw new ArgumentException("Jump offset out of range: " + offset);
+ return new byte[] { (byte)0x18, (byte)offset };
+ }
+
+ }
+
+ public static class Jr_F_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(new Schema.IsFlagZC(), Schema.DIRECT_N);
+
+ private final Scope context;
+ private Expression argument1;
+ private Expression argument2;
+
+ public Jr_F_N(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.context = context;
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int offset = argument2.getAddress() - (context.getAddress() + 2);
+ if (offset < -128 || offset > 127)
+ throw new ArgumentException("Jump offset out of range: " + offset);
+ return new byte[] { (byte)(0x20 | argument1.getFlag().getCode() << 3), (byte)offset };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ld.java b/src/nl/grauw/glass/instructions/Ld.java
new file mode 100644
index 0000000..6d7e097
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ld.java
@@ -0,0 +1,406 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ld extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ld_R_R.ARGUMENTS.check(arguments))
+ return new Ld_R_R(context, arguments.getElement(0).getRegister(), arguments.getElement(1).getRegister());
+ if (Ld_A_BCDE.ARGUMENTS.check(arguments))
+ return new Ld_A_BCDE(context, arguments.getElement(1));
+ if (Ld_BCDE_A.ARGUMENTS.check(arguments))
+ return new Ld_BCDE_A(context, arguments.getElement(0));
+ if (Ld_SP_HL.ARGUMENTS.check(arguments))
+ return new Ld_SP_HL(context, arguments.getElement(1));
+ if (Ld_A_IR.ARGUMENTS.check(arguments))
+ return new Ld_A_IR(context, arguments.getElement(1));
+ if (Ld_IR_A.ARGUMENTS.check(arguments))
+ return new Ld_IR_A(context, arguments.getElement(0));
+ if (Ld_R_N.ARGUMENTS.check(arguments))
+ return new Ld_R_N(context, arguments.getElement(0), arguments.getElement(1));
+ if (Ld_RR_N.ARGUMENTS.check(arguments))
+ return new Ld_RR_N(context, arguments.getElement(0), arguments.getElement(1));
+ if (Ld_A_NN.ARGUMENTS.check(arguments))
+ return new Ld_A_NN(context, arguments.getElement(0));
+ if (Ld_HL_NN.ARGUMENTS.check(arguments))
+ return new Ld_HL_NN(context, arguments.getElement(0), arguments.getElement(1));
+ if (Ld_RR_NN.ARGUMENTS.check(arguments))
+ return new Ld_RR_NN(context, arguments.getElement(0), arguments.getElement(1));
+ if (Ld_NN_A.ARGUMENTS.check(arguments))
+ return new Ld_NN_A(context, arguments.getElement(1));
+ if (Ld_NN_HL.ARGUMENTS.check(arguments))
+ return new Ld_NN_HL(context, arguments.getElement(0), arguments.getElement(1));
+ if (Ld_NN_RR.ARGUMENTS.check(arguments))
+ return new Ld_NN_RR(context, arguments.getElement(0), arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Ld_R_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY, Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Register register1;
+ private Register register2;
+
+ public Ld_R_R(Scope context, Register register1, Register register2) {
+ super(context);
+ this.register1 = register1;
+ this.register2 = register2;
+
+ if (register1.isPair() && register2.isPair())
+ throw new ArgumentException(); // forbid (hl),(hl), (ix+0),(ix-0), etc.
+ if (register1.isIndex() && register2.isIndex() && register1.getIndexCode() != register2.getIndexCode())
+ throw new ArgumentException(); // forbid ixh,iyl, etc.
+ if (register1.isIndex() && register2.isPair() || register1.isPair() && register2.isIndex())
+ throw new ArgumentException(); // forbid (hl),ixh, ixh,(ix+0)
+ if (register1.isIndex() && !register1.isPair() && (register2 == Register.H || register2 == Register.L))
+ throw new ArgumentException(); // forbid iyl,h
+ if (register2.isIndex() && !register2.isPair() && (register1 == Register.H || register1 == Register.L))
+ throw new ArgumentException(); // forbid h,iyl
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(register1.isIndex() ? register1 : register2, 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return indexifyIndirect(register1.isIndex() ? register1 : register2,
+ (byte)(0x40 | register1.get8BitCode() << 3 | register2.get8BitCode()));
+ }
+
+ }
+
+ public static class Ld_A_BCDE extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.INDIRECT_BC_DE);
+
+ private Expression argument;
+
+ public Ld_A_BCDE(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)(0x0A | argument.getRegister().get16BitCode() << 4) };
+ }
+
+ }
+
+ public static class Ld_BCDE_A extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_BC_DE, Schema.DIRECT_A);
+
+ private Expression argument;
+
+ public Ld_BCDE_A(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)(0x02 | argument.getRegister().get16BitCode() << 4) };
+ }
+
+ }
+
+ public static class Ld_SP_HL extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_SP, Schema.DIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Ld_SP_HL(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return indexifyDirect(argument.getRegister(), (byte)0xF9);
+ }
+
+ }
+
+ public static class Ld_A_IR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_IR);
+
+ private Expression argument;
+
+ public Ld_A_IR(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (argument.getRegister() == Register.I)
+ return new byte[] { (byte)0xED, (byte)0x57 };
+ return new byte[] { (byte)0xED, (byte)0x5F };
+ }
+
+ }
+
+ public static class Ld_IR_A extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_IR, Schema.DIRECT_A);
+
+ private Expression argument;
+
+ public Ld_IR_A(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (argument.getRegister() == Register.I)
+ return new byte[] { (byte)0xED, (byte)0x47 };
+ return new byte[] { (byte)0xED, (byte)0x4F };
+ }
+
+ }
+
+ public static class Ld_R_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY, Schema.DIRECT_N);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Ld_R_N(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument1.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument1.getRegister();
+ return indexifyIndirect(register, (byte)(0x06 | register.get8BitCode() << 3), (byte)argument2.getInteger());
+ }
+
+ }
+
+ public static class Ld_RR_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_RR_INDEX, Schema.DIRECT_N);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Ld_RR_N(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument1.getRegister(), 3);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument1.getRegister();
+ return indexifyDirect(register, (byte)(0x01 | register.get16BitCode() << 4),
+ (byte)argument2.getInteger(), (byte)(argument2.getInteger() >> 8));
+ }
+
+ }
+
+ public static class Ld_A_NN extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_N, Schema.DIRECT_A);
+
+ private Expression argument;
+
+ public Ld_A_NN(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 3;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument.getAddress();
+ return new byte[] { (byte)0x32, (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+ public static class Ld_HL_NN extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_HL_IX_IY, Schema.INDIRECT_N);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Ld_HL_NN(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument1.getRegister(), 3);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument2.getAddress();
+ return indexifyDirect(argument1.getRegister(), (byte)0x2A, (byte)address, (byte)(address >> 8));
+ }
+
+ }
+
+ public static class Ld_RR_NN extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_RR, Schema.INDIRECT_N);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Ld_RR_NN(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return 4;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument2.getAddress();
+ return new byte[] { (byte)0xED, (byte)(0x4B | argument1.getRegister().get16BitCode() << 4),
+ (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+ public static class Ld_NN_A extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.INDIRECT_N);
+
+ private Expression argument;
+
+ public Ld_NN_A(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 3;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument.getAddress();
+ return new byte[] { (byte)0x3A, (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+ public static class Ld_NN_HL extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_N, Schema.DIRECT_HL_IX_IY);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Ld_NN_HL(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument2.getRegister(), 3);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument1.getAddress();
+ return indexifyDirect(argument2.getRegister(), (byte)0x22, (byte)address, (byte)(address >> 8));
+ }
+
+ }
+
+ public static class Ld_NN_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_N, Schema.DIRECT_RR);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Ld_NN_RR(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return 4;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int address = argument1.getAddress();
+ return new byte[] { (byte)0xED, (byte)(0x43 | argument2.getRegister().get16BitCode() << 4),
+ (byte)address, (byte)(address >> 8) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ldd.java b/src/nl/grauw/glass/instructions/Ldd.java
new file mode 100644
index 0000000..c1198f6
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ldd.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ldd extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ldd_.ARGUMENTS.check(arguments))
+ return new Ldd_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Ldd_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ldd_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xA8 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Lddr.java b/src/nl/grauw/glass/instructions/Lddr.java
new file mode 100644
index 0000000..f9c4c59
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Lddr.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Lddr extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Lddr_.ARGUMENTS.check(arguments))
+ return new Lddr_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Lddr_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Lddr_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xB8 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ldi.java b/src/nl/grauw/glass/instructions/Ldi.java
new file mode 100644
index 0000000..149ce07
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ldi.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ldi extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ldi_.ARGUMENTS.check(arguments))
+ return new Ldi_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Ldi_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ldi_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xA0 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ldir.java b/src/nl/grauw/glass/instructions/Ldir.java
new file mode 100644
index 0000000..d481e4b
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ldir.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ldir extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ldir_.ARGUMENTS.check(arguments))
+ return new Ldir_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Ldir_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ldir_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xB0 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Macro.java b/src/nl/grauw/glass/instructions/Macro.java
new file mode 100644
index 0000000..b290c01
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Macro.java
@@ -0,0 +1,75 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.AssemblyException;
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Equals;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Identifier;
+import nl.grauw.glass.expressions.IntegerLiteral;
+
+public class Macro extends InstructionFactory {
+
+ private final Source source;
+ private final Scope parameterScope;
+
+ public Macro(Source source) {
+ this.source = new Source(source.getScope());
+ this.parameterScope = new Scope(source.getScope());
+ this.source.addLines(source.getLineCopies(parameterScope));
+ this.source.register();
+ }
+
+ @Override
+ public List expand(Line line) {
+ Expression parameters = line.getArguments();
+ while (parameters != null) {
+ Expression parameter = parameters.getElement();
+ if (!(parameter instanceof Identifier) &&
+ !(parameter instanceof Equals && ((Equals)parameter).getTerm1() instanceof Identifier))
+ throw new ArgumentException("Parameter must be an identifier.");
+
+ if (parameter instanceof Equals) {
+ Equals equals = (Equals)parameter;
+ parameterScope.addSymbol(((Identifier)equals.getTerm1()).getName(), equals.getTerm2());
+ } else {
+ parameterScope.addSymbol(((Identifier)parameter).getName(), IntegerLiteral.ZERO);
+ }
+ parameters = parameters.getNext();
+ }
+
+ try {
+ source.expand();
+ } catch (AssemblyException e) {
+ // ignore
+ }
+ return super.expand(line);
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ return new MacroObject(context);
+ }
+
+ public class MacroObject extends Empty.EmptyObject {
+
+ public MacroObject(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int resolve(int address) {
+ try {
+ source.resolve(0);
+ } catch (AssemblyException e) {
+ // ignore
+ }
+ return super.resolve(address);
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/MacroInstruction.java b/src/nl/grauw/glass/instructions/MacroInstruction.java
new file mode 100644
index 0000000..bb3e344
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/MacroInstruction.java
@@ -0,0 +1,50 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.ParameterScope;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Equals;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Identifier;
+
+public class MacroInstruction extends InstructionFactory {
+
+ private final Expression parameters;
+ private final Source source;
+
+ public MacroInstruction(Expression parameters, Source source) {
+ this.parameters = parameters;
+ this.source = source;
+
+ Expression parameter = parameters != null ? parameters.getElement(0) : null;
+ for (int i = 0; parameter != null; i++) {
+ if (!(parameter instanceof Identifier) &&
+ !(parameter instanceof Equals && ((Equals)parameter).getTerm1() instanceof Identifier))
+ throw new ArgumentException("Parameter must be an identifier.");
+ parameter = parameters.getElement(i);
+ }
+ }
+
+ @Override
+ public List expand(Line line) {
+ Scope parameterScope = new ParameterScope(source.getScope(), parameters, line.getArguments());
+ List lines = super.expand(line);
+ List lineCopies = source.getLineCopies(parameterScope);
+ for (Line lineCopy : lineCopies) {
+ lineCopy.register(parameterScope);
+ lineCopy.register(line.getScope());
+ }
+ for (Line lineCopy : lineCopies)
+ lines.addAll(lineCopy.expand());
+ return lines;
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ return new Empty.EmptyObject(context);
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Mulub.java b/src/nl/grauw/glass/instructions/Mulub.java
new file mode 100644
index 0000000..6013f76
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Mulub.java
@@ -0,0 +1,39 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Mulub extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Mulub_R_R.ARGUMENTS.check(arguments))
+ return new Mulub_R_R(context, arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Mulub_R_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_R);
+
+ private Expression argument;
+
+ public Mulub_R_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)(0xC1 | argument.getRegister().get8BitCode() << 3) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Muluw.java b/src/nl/grauw/glass/instructions/Muluw.java
new file mode 100644
index 0000000..43103c6
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Muluw.java
@@ -0,0 +1,39 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Muluw extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Muluw_RR_RR.ARGUMENTS.check(arguments))
+ return new Muluw_RR_RR(context, arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Muluw_RR_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_HL, Schema.DIRECT_RR);
+
+ private Expression argument;
+
+ public Muluw_RR_RR(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)(0xC3 | argument.getRegister().get16BitCode() << 4) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Neg.java b/src/nl/grauw/glass/instructions/Neg.java
new file mode 100644
index 0000000..b2b3446
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Neg.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Neg extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Neg_.ARGUMENTS.check(arguments))
+ return new Neg_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Neg_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Neg_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0x44 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Nop.java b/src/nl/grauw/glass/instructions/Nop.java
new file mode 100644
index 0000000..5732f06
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Nop.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Nop extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Nop_.ARGUMENTS.check(arguments))
+ return new Nop_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Nop_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Nop_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x00 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Or.java b/src/nl/grauw/glass/instructions/Or.java
new file mode 100644
index 0000000..9f19f5f
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Or.java
@@ -0,0 +1,66 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Or extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Or_R.ARGUMENTS.check(arguments))
+ return new Or_R(context, arguments);
+ if (Or_N.ARGUMENTS.check(arguments))
+ return new Or_N(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class Or_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Or_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0xB0 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class Or_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Or_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xF6, (byte)argument.getInteger() };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Org.java b/src/nl/grauw/glass/instructions/Org.java
new file mode 100644
index 0000000..bdaa5d5
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Org.java
@@ -0,0 +1,46 @@
+package nl.grauw.glass.instructions;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Org extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Org_N.ARGUMENTS.check(arguments))
+ return new Org_N(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Org_N extends Empty.EmptyObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INTEGER);
+
+ private Expression argument;
+
+ public Org_N(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ public int getAddress() {
+ return argument.getAddress();
+ }
+
+ @Override
+ public int resolve(int address) {
+ super.resolve(address);
+ return getAddress();
+ }
+
+ @Override
+ public void generateObjectCode(OutputStream output) throws IOException {
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Otdr.java b/src/nl/grauw/glass/instructions/Otdr.java
new file mode 100644
index 0000000..ddeab1e
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Otdr.java
@@ -0,0 +1,37 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Otdr extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Otdr_.ARGUMENTS.check(arguments))
+ return new Otdr_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Otdr_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Otdr_(Scope context) {
+ super(context);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xBB };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Otir.java b/src/nl/grauw/glass/instructions/Otir.java
new file mode 100644
index 0000000..dbb405d
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Otir.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Otir extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Otir_.ARGUMENTS.check(arguments))
+ return new Otir_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Otir_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Otir_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xB3 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Out.java b/src/nl/grauw/glass/instructions/Out.java
new file mode 100644
index 0000000..dc88f0c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Out.java
@@ -0,0 +1,64 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Out extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Out_C_N.ARGUMENTS.check(arguments))
+ return new Out_C_N(context, arguments.getElement(1));
+ if (Out_N_N.ARGUMENTS.check(arguments))
+ return new Out_N_N(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Out_C_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_C, Schema.DIRECT_R);
+
+ private Expression argument;
+
+ public Out_C_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)(0x41 | argument.getRegister().get8BitCode() << 3) };
+ }
+
+ }
+
+ public static class Out_N_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.INDIRECT_N, Schema.DIRECT_A);
+
+ private Expression argument;
+
+ public Out_N_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xD3, (byte)argument.getInteger() };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Outd.java b/src/nl/grauw/glass/instructions/Outd.java
new file mode 100644
index 0000000..146cb3a
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Outd.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Outd extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Outd_.ARGUMENTS.check(arguments))
+ return new Outd_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Outd_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Outd_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xAB };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Outi.java b/src/nl/grauw/glass/instructions/Outi.java
new file mode 100644
index 0000000..05e1dc7
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Outi.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Outi extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Outi_.ARGUMENTS.check(arguments))
+ return new Outi_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Outi_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Outi_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0xA3 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Pop.java b/src/nl/grauw/glass/instructions/Pop.java
new file mode 100644
index 0000000..047bb43
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Pop.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Pop extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Pop_RR.ARGUMENTS.check(arguments))
+ return new Pop_RR(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Pop_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_RR_AF_INDEX);
+
+ Expression argument;
+
+ public Pop_RR(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyDirect(register, (byte)(0xC1 | register.get16BitCode() << 4));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Proc.java b/src/nl/grauw/glass/instructions/Proc.java
new file mode 100644
index 0000000..2a746b5
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Proc.java
@@ -0,0 +1,40 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Proc extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ private final Source source;
+
+ public Proc(Source source) {
+ this.source = source;
+ }
+
+ public List expand(Line line) {
+ Expression arguments = line.getArguments();
+ if (!ARGUMENTS.check(arguments))
+ throw new ArgumentException();
+
+ List lines = super.expand(line);
+ List lineCopies = source.getLineCopies(line.getScope());
+ for (Line lineCopy : lineCopies)
+ lineCopy.register(line.getScope());
+ for (Line lineCopy : lineCopies)
+ lines.addAll(lineCopy.expand());
+ return lines;
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ return new Empty.EmptyObject(context);
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Push.java b/src/nl/grauw/glass/instructions/Push.java
new file mode 100644
index 0000000..7286bbf
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Push.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Push extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Push_RR.ARGUMENTS.check(arguments))
+ return new Push_RR(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Push_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_RR_AF_INDEX);
+
+ Expression argument;
+
+ public Push_RR(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyDirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyDirect(register, (byte)(0xC5 | register.get16BitCode() << 4));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rept.java b/src/nl/grauw/glass/instructions/Rept.java
new file mode 100644
index 0000000..43de359
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rept.java
@@ -0,0 +1,71 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.ParameterScope;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.IntegerLiteral;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rept extends InstructionFactory {
+
+ public static Schema ARGUMENTS_N = new Schema(Schema.INTEGER);
+ public static Schema ARGUMENTS_N_ID = new Schema(Schema.INTEGER, Schema.IDENTIFIER);
+ public static Schema ARGUMENTS_N_ID_N = new Schema(Schema.INTEGER, Schema.IDENTIFIER, Schema.INTEGER);
+ public static Schema ARGUMENTS_N_ID_N_N = new Schema(Schema.INTEGER, Schema.IDENTIFIER, Schema.INTEGER, Schema.INTEGER);
+
+ private final Source source;
+
+ public Rept(Source source) {
+ this.source = source;
+ }
+
+ public List expand(Line line) {
+ Expression arguments = line.getArguments();
+ if (ARGUMENTS_N.check(arguments))
+ return expand(line, arguments.getElement(0).getInteger(), null, 0, 1);
+ if (ARGUMENTS_N_ID.check(arguments))
+ return expand(line, arguments.getElement(0).getInteger(), arguments.getElement(1), 0, 1);
+ if (ARGUMENTS_N_ID_N.check(arguments))
+ return expand(line, arguments.getElement(0).getInteger(), arguments.getElement(1),
+ arguments.getElement(2).getInteger(), 1);
+ if (ARGUMENTS_N_ID_N_N.check(arguments))
+ return expand(line, arguments.getElement(0).getInteger(), arguments.getElement(1),
+ arguments.getElement(2).getInteger(), arguments.getElement(3).getInteger());
+ throw new ArgumentException();
+ }
+
+ public List expand(Line line, int count, Expression parameter, int start, int step) {
+ if (count < 0)
+ throw new ArgumentException("Repetition count must be 0 or greater.");
+
+ List lines = super.expand(line);
+ for (int i = 0, counter = start; i < count; i++, counter += step) {
+ Scope parameterScope = new ParameterScope(line.getScope(), parameter,
+ parameter != null ? new IntegerLiteral(counter) : null);
+
+ // set up the number symbol
+ line.getScope().addSymbol(Integer.toString(i), parameterScope);
+ Line rept = new Line(parameterScope, line);
+ rept.setInstruction(Empty.INSTANCE);
+ lines.add(rept); // so that the parameterScope address gets initialised
+
+ // copy & expand content
+ List lineCopies = source.getLineCopies(parameterScope);
+ for (Line lineCopy : lineCopies)
+ lineCopy.register(parameterScope);
+ for (Line lineCopy : lineCopies)
+ lines.addAll(lineCopy.expand());
+ }
+ return lines;
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ return new Empty.EmptyObject(context);
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Res.java b/src/nl/grauw/glass/instructions/Res.java
new file mode 100644
index 0000000..0f05ce8
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Res.java
@@ -0,0 +1,46 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Res extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Res_N_R.ARGUMENTS.check(arguments))
+ return new Res_N_R(context, arguments.getElement(0), arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Res_N_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N, Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Res_N_R(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument2.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int value = argument1.getInteger();
+ if (value < 0 || value > 7)
+ throw new ArgumentException();
+ Register register = argument2.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x80 | value << 3 | register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Ret.java b/src/nl/grauw/glass/instructions/Ret.java
new file mode 100644
index 0000000..2509cf7
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Ret.java
@@ -0,0 +1,61 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Ret extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Ret_.ARGUMENTS.check(arguments))
+ return new Ret_(context);
+ if (Ret_F.ARGUMENTS.check(arguments))
+ return new Ret_F(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Ret_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Ret_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xC9 };
+ }
+
+ }
+
+ public static class Ret_F extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(new Schema.IsFlag());
+
+ private Expression argument;
+
+ public Ret_F(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)(0xC0 | argument.getFlag().getCode() << 3) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Reti.java b/src/nl/grauw/glass/instructions/Reti.java
new file mode 100644
index 0000000..614322e
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Reti.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Reti extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Reti_.ARGUMENTS.check(arguments))
+ return new Reti_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Reti_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Reti_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0x4D };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Retn.java b/src/nl/grauw/glass/instructions/Retn.java
new file mode 100644
index 0000000..fa52864
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Retn.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Retn extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Retn_.ARGUMENTS.check(arguments))
+ return new Retn_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Retn_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Retn_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0x45 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rl.java b/src/nl/grauw/glass/instructions/Rl.java
new file mode 100644
index 0000000..f3b002d
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rl.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rl extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rl_R.ARGUMENTS.check(arguments))
+ return new Rl_R(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Rl_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Rl_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x10 + register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rla.java b/src/nl/grauw/glass/instructions/Rla.java
new file mode 100644
index 0000000..e987fea
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rla.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rla extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rla_.ARGUMENTS.check(arguments))
+ return new Rla_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Rla_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Rla_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x17 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rlc.java b/src/nl/grauw/glass/instructions/Rlc.java
new file mode 100644
index 0000000..56ffee6
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rlc.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rlc extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rlc_R.ARGUMENTS.check(arguments))
+ return new Rlc_R(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Rlc_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Rlc_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x00 + register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rlca.java b/src/nl/grauw/glass/instructions/Rlca.java
new file mode 100644
index 0000000..c048294
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rlca.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rlca extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rlca_.ARGUMENTS.check(arguments))
+ return new Rlca_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Rlca_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Rlca_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x07 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rld.java b/src/nl/grauw/glass/instructions/Rld.java
new file mode 100644
index 0000000..d7e4ac1
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rld.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rld extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rld_.ARGUMENTS.check(arguments))
+ return new Rld_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Rld_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Rld_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0x6F };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rr.java b/src/nl/grauw/glass/instructions/Rr.java
new file mode 100644
index 0000000..e5f259e
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rr.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rr extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rr_R.ARGUMENTS.check(arguments))
+ return new Rr_R(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Rr_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Rr_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x18 + register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rra.java b/src/nl/grauw/glass/instructions/Rra.java
new file mode 100644
index 0000000..2a4ab00
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rra.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rra extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rra_.ARGUMENTS.check(arguments))
+ return new Rra_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Rra_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Rra_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x1F };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rrc.java b/src/nl/grauw/glass/instructions/Rrc.java
new file mode 100644
index 0000000..6c010c9
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rrc.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rrc extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rrc_R.ARGUMENTS.check(arguments))
+ return new Rrc_R(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Rrc_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Rrc_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x08 + register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rrca.java b/src/nl/grauw/glass/instructions/Rrca.java
new file mode 100644
index 0000000..4c48a8e
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rrca.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rrca extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rrca_.ARGUMENTS.check(arguments))
+ return new Rrca_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Rrca_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Rrca_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x0F };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rrd.java b/src/nl/grauw/glass/instructions/Rrd.java
new file mode 100644
index 0000000..8053e6b
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rrd.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rrd extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rrd_.ARGUMENTS.check(arguments))
+ return new Rrd_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Rrd_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Rrd_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)0x67 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Rst.java b/src/nl/grauw/glass/instructions/Rst.java
new file mode 100644
index 0000000..4f44e34
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Rst.java
@@ -0,0 +1,42 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Rst extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Rst_N.ARGUMENTS.check(arguments))
+ return new Rst_N(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Rst_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Rst_N(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int value = argument.getInteger();
+ if (value < 0 || value > 0x38 || (value & 7) != 0)
+ throw new ArgumentException();
+ return new byte[] { (byte)(0xC7 + value) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Sbc.java b/src/nl/grauw/glass/instructions/Sbc.java
new file mode 100644
index 0000000..452604c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Sbc.java
@@ -0,0 +1,91 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Sbc extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Sbc_A_R.ARGUMENTS.check(arguments))
+ return new Sbc_A_R(context, arguments.getElement(1));
+ if (Sbc_A_N.ARGUMENTS.check(arguments))
+ return new Sbc_A_N(context, arguments.getElement(1));
+ if (Sbc_HL_RR.ARGUMENTS.check(arguments))
+ return new Sbc_HL_RR(context, arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Sbc_A_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Sbc_A_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0x98 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class Sbc_A_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_A, Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Sbc_A_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xDE, (byte)argument.getInteger() };
+ }
+
+ }
+
+ public static class Sbc_HL_RR extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_HL, Schema.DIRECT_RR);
+
+ private Expression argument;
+
+ public Sbc_HL_RR(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xED, (byte)(0x42 | argument.getRegister().get16BitCode() << 4) };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Scf.java b/src/nl/grauw/glass/instructions/Scf.java
new file mode 100644
index 0000000..b961404
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Scf.java
@@ -0,0 +1,36 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Scf extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Scf_.ARGUMENTS.check(arguments))
+ return new Scf_(context);
+ throw new ArgumentException();
+ }
+
+ public static class Scf_ extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema();
+
+ public Scf_(Scope context) {
+ super(context);
+ }
+
+ @Override
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0x37 };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Section.java b/src/nl/grauw/glass/instructions/Section.java
new file mode 100644
index 0000000..bf5c6ab
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Section.java
@@ -0,0 +1,46 @@
+package nl.grauw.glass.instructions;
+
+import java.util.List;
+
+import nl.grauw.glass.Line;
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.Source;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Section extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema(Schema.IDENTIFIER);
+
+ private final Source source;
+
+ public Section(Source source) {
+ this.source = source;
+ }
+
+ public Source getSource() {
+ return source;
+ }
+
+ @Override
+ public List expand(Line line) {
+ if (!ARGUMENTS.check(line.getArguments()))
+ throw new ArgumentException();
+
+ if (!line.getArguments().isSectionContext())
+ throw new ArgumentException("Argument does not reference a section context.");
+
+ line.getArguments().getSectionContext().addSection(this);
+
+ source.expand();
+ return super.expand(line);
+ }
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments))
+ return new Empty.EmptyObject(context);
+ throw new ArgumentException();
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Set.java b/src/nl/grauw/glass/instructions/Set.java
new file mode 100644
index 0000000..ce907e7
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Set.java
@@ -0,0 +1,46 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Set extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Set_N_R.ARGUMENTS.check(arguments))
+ return new Set_N_R(context, arguments.getElement(0), arguments.getElement(1));
+ throw new ArgumentException();
+ }
+
+ public static class Set_N_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N, Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument1;
+ private Expression argument2;
+
+ public Set_N_R(Scope context, Expression argument1, Expression argument2) {
+ super(context);
+ this.argument1 = argument1;
+ this.argument2 = argument2;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument2.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ int value = argument1.getInteger();
+ if (value < 0 || value > 7)
+ throw new ArgumentException();
+ Register register = argument2.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0xC0 | value << 3 | register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Sla.java b/src/nl/grauw/glass/instructions/Sla.java
new file mode 100644
index 0000000..0a2821c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Sla.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Sla extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Sla_R.ARGUMENTS.check(arguments))
+ return new Sla_R(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Sla_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Sla_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x20 + register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Sra.java b/src/nl/grauw/glass/instructions/Sra.java
new file mode 100644
index 0000000..94a652c
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Sra.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Sra extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Sra_R.ARGUMENTS.check(arguments))
+ return new Sra_R(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Sra_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Sra_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x28 + register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Srl.java b/src/nl/grauw/glass/instructions/Srl.java
new file mode 100644
index 0000000..78abc3e
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Srl.java
@@ -0,0 +1,41 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Srl extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Srl_R.ARGUMENTS.check(arguments))
+ return new Srl_R(context, arguments.getElement(0));
+ throw new ArgumentException();
+ }
+
+ public static class Srl_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Srl_R(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 2);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyOnlyIndirect(register, (byte)0xCB, (byte)(0x38 + register.get8BitCode()));
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Sub.java b/src/nl/grauw/glass/instructions/Sub.java
new file mode 100644
index 0000000..d1dc183
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Sub.java
@@ -0,0 +1,66 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Sub extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Sub_R.ARGUMENTS.check(arguments))
+ return new Sub_R(context, arguments);
+ if (Sub_N.ARGUMENTS.check(arguments))
+ return new Sub_N(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class Sub_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Sub_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0x90 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class Sub_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Sub_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xD6, (byte)argument.getInteger() };
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Warning.java b/src/nl/grauw/glass/instructions/Warning.java
new file mode 100644
index 0000000..27a99fe
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Warning.java
@@ -0,0 +1,39 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Schema;
+
+public class Warning extends InstructionFactory {
+
+ public static Schema ARGUMENTS = new Schema();
+ public static Schema ARGUMENTS_S = new Schema(Schema.STRING);
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (ARGUMENTS.check(arguments) || ARGUMENTS_S.check(arguments))
+ return new Warning_(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class Warning_ extends Empty.EmptyObject {
+
+ private final Expression argument;
+
+ public Warning_(Scope context, Expression argument) {
+ super(context);
+ this.argument = argument;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (argument == null)
+ System.out.println("Warning: A warning directive was encountered.");
+ else
+ System.out.println("Warning: " + argument.getString());
+ return super.getBytes();
+ }
+
+ }
+
+}
diff --git a/src/nl/grauw/glass/instructions/Xor.java b/src/nl/grauw/glass/instructions/Xor.java
new file mode 100644
index 0000000..d3751c7
--- /dev/null
+++ b/src/nl/grauw/glass/instructions/Xor.java
@@ -0,0 +1,66 @@
+package nl.grauw.glass.instructions;
+
+import nl.grauw.glass.Scope;
+import nl.grauw.glass.expressions.Expression;
+import nl.grauw.glass.expressions.Register;
+import nl.grauw.glass.expressions.Schema;
+
+public class Xor extends InstructionFactory {
+
+ @Override
+ public InstructionObject createObject(Scope context, Expression arguments) {
+ if (Xor_R.ARGUMENTS.check(arguments))
+ return new Xor_R(context, arguments);
+ if (Xor_N.ARGUMENTS.check(arguments))
+ return new Xor_N(context, arguments);
+ throw new ArgumentException();
+ }
+
+ public static class Xor_R extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_R_INDIRECT_HL_IX_IY);
+
+ private Expression argument;
+
+ public Xor_R(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return indexifyIndirect(argument.getRegister(), 1);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Register register = argument.getRegister();
+ return indexifyIndirect(register, (byte)(0xA8 | register.get8BitCode()));
+ }
+
+ }
+
+ public static class Xor_N extends InstructionObject {
+
+ public static Schema ARGUMENTS = new Schema(Schema.DIRECT_N);
+
+ private Expression argument;
+
+ public Xor_N(Scope context, Expression arguments) {
+ super(context);
+ this.argument = arguments;
+ }
+
+ @Override
+ public int getSize() {
+ return 2;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return new byte[] { (byte)0xEE, (byte)argument.getInteger() };
+ }
+
+ }
+
+}