Skip to content

Instantly share code, notes, and snippets.

@Sunjammer
Last active April 25, 2016 09:41
Show Gist options
  • Save Sunjammer/48147b558c1a2f2c6208b337841f45b4 to your computer and use it in GitHub Desktop.
Save Sunjammer/48147b558c1a2f2c6208b337841f45b4 to your computer and use it in GitHub Desktop.
package;
import haxe.ds.Vector;
using StringTools;
using Std;
enum Value
{
Literal(v:Int);
Location(v:Int);
Relative(v:Int);
AReg;
XReg;
YReg;
}
/**
* Syntax:
* INSTRUCTION ARG0 ... ARGN
* ;Comment
* alias word value
* label:
* int values only
* int value prefix with # is a memory address
* int value prefix with @ is a relative program counter offset
* A, X and Y reference registers
* A is accumulator, used for results
*/
enum Instruction
{
LDA(v:Value); //Load value into A
LDX(v:Value); //Load value into X
LDY(v:Value); //Load value into Y
STA(v:Value); //Store A at memory
STX(v:Value); //Store X at memory
STY(v:Value); //Store Y at memory
PHA; //Push A on stack
PLA; //Pull A from stack
TAX; // Copy A to X
TXA; // Copy X to A
TAY; // Copy A to Y
TYA; // Copy Y to A
TXY; // Copy X to Y
TYX; // Copy Y to X
SUB(v:Value); //Subtract value from A
ADD(v:Value); //Add value to A
BNE(v:Value, pos:Value); //Branch to pos if v != A
BEQ(v:Value, pos:Value); //Branch to pos if v == A
BLT(v:Value, pos:Value); //Branch to pos if v < A
BGT(v:Value, pos:Value); //Branch to pos if v > A
JMP(v:Value); //Set program counter for next instruction
JSR(v:Value); //Begin subroutine
RTS; //Return from subroutine
RTI; //Return from interrupt
AND(v:Value); //& v with A and put the result in A
IOR(v:Value); //| v with A and put the result in A
XOR(v:Value); //^ v with A and put the result in A
LSH(v:Value); //Leftshift A by v and put the result in A
RSH(v:Value); //Rightshift A by v and put the result in A
BRK; //End program
NOP; //No operation
/* Debug stuff */
TRC(v:Value); //Print value
MEM(a:Value, b:Value); //Print memory from A to B
}
typedef Program = Vector<Instruction>;
class Machine
{
static inline var MEMSIZE = 512;
static inline var STACK_OFFSET = MEMSIZE-128;
public var name(default, null):String;
public var cycleCount(default, null):Int;
public var program(default, null):Program;
var memory = new Vector<Int>(MEMSIZE);
var A = 0;
var X = 0;
var Y = 0;
var pc = -1;
var stack = 0;
public function new(name:String)
{
this.name = name;
reset();
}
public function setMemory(mem:Vector<Int>)
{
memory = mem;
}
public function load(prog:Program)
{
program = prog;
}
public function isRunning():Bool {
return pc != -1;
}
public inline function getValue(v:Value):Int {
return switch(v) {
case Relative(v):
pc + (v-1);
case Literal(v):
v;
case Location(l):
memory[l];
case AReg:
A;
case XReg:
X;
case YReg:
Y;
}
}
public function reset()
{
for (i in 0...MEMSIZE) {
memory[i] = 0;
}
A = X = Y = stack = cycleCount = pc = 0;
}
function printMem(a:Int = 0, b:Int = MEMSIZE)
{
trace("Memory:");
for (i in a...b)
{
trace("\t"+ i + "\t0x" + StringTools.hex(memory[i], 2));
}
}
function pushStack(v:Int)
{
if (STACK_OFFSET + stack + 1 > MEMSIZE) throw "Stack overflow";
memory[STACK_OFFSET + stack] = v;
stack++;
}
function popStack():Int
{
if (stack == 0) throw "Attempted to pop empty stack";
var v = memory[STACK_OFFSET + stack-1];
stack--;
return v;
}
public function step(instruction:Instruction):Int
{
pc++;
cycleCount++;
switch(instruction)
{
case LDA(v):
A = getValue(v);
case LDX(v):
X = getValue(v);
case LDY(v):
Y = getValue(v);
case STA(v):
memory[getValue(v)] = A;
case STX(v):
memory[getValue(v)] = X;
case STY(v):
memory[getValue(v)] = Y;
case TAX:
X = A;
case TXA:
A = X;
case TAY:
Y = A;
case TYA:
A = Y;
case TXY:
Y = X;
case TYX:
X = Y;
case SUB(v):
A -= getValue(v);
case ADD(v):
A += getValue(v);
case JMP(v):
pc = getValue(v);
case TRC(v):
trace(getValue(v));
case BNE(v, pos):
if(getValue(v) != A) pc = getValue(pos);
case BEQ(v, pos):
if(getValue(v) == A) pc = getValue(pos);
case BLT(v, pos):
if(getValue(v) < A) pc = getValue(pos);
case BGT(v, pos):
if(getValue(v) > A) pc = getValue(pos);
case AND(a):
A = A & getValue(a);
case IOR(a):
A = A | getValue(a);
case XOR(a):
A = A ^ getValue(a);
case LSH(a):
A = A << getValue(a);
case RSH(a):
A = A >> getValue(a);
case JSR(v):
pushStack(pc);
pc = getValue(v);
case RTS|RTI:
pc = popStack();
case BRK:
pc = -1;
case MEM(a, b):
printMem(getValue(a), getValue(b));
case NOP:
//Do nothing
case _:
throw "Unsupported instruction " + instruction;
}
return pc;
}
public function next(#if neko interactive:Bool = false #end)
{
var instruction:Instruction;
#if neko
if (program == null || program.length == 0)
{
Sys.stdout().writeString("? ");
instruction = Assembler.parseLine(Sys.stdin().readLine(), pc);
}else{
#end
instruction = program[pc];
#if neko
}
if (interactive) {
trace("-> " + instruction);
Sys.stdin().readLine();
}
#end
pc = step(instruction);
}
public function run(program:Program = null #if neko, interactive:Bool = false #end) {
this.program = program;
reset();
try {
while (isRunning()) {
next(interactive);
}
}catch (e:Dynamic) {
trace("Program crashed at " + pc + ": "+e);
}
}
}
class Assembler
{
public static function assemble(source:String):Program
{
var rawlines = source.trim().split("\n");
//label, comment and trim prepass
var lines:Array<String> = [];
var aliases = new Map<String, String>();
var labels = new Map<String,String>();
var count = 0;
for (i in 0...rawlines.length) {
var line = rawlines[i].trim();
var str = "";
for (i in 0...line.length)
{
var char = line.charAt(i);
if (char == ";") break;
str += char;
}
line = str.rtrim();
if (line.length == 0) continue;
if (line.indexOf(":") > -1) {
labels[line.substr(0, line.length - 1)] = count+"";
continue;
}
if (line.indexOf("alias") >-1) {
var components = line.split(" ");
components.shift();
aliases[components[0]] = components[1];
continue;
}
count++;
lines.push(line);
}
for (i in 0...lines.length) {
var line = lines[i];
for (label in labels.keys()) {
var idx = line.indexOf(label);
if (idx >-1) {
lines[i] = line.replace(label, labels[label]);
}
}
for (alias in aliases.keys()) {
var idx = line.indexOf(alias);
if (idx >-1) {
lines[i] = line.replace(alias, aliases[alias]);
}
}
}
//Generate instructions
var out = new Program(lines.length);
for (i in 0...lines.length) {
out[i] = parseLine(lines[i], i);
}
return out;
}
public static function parseLine(line:String, lineNo:Int):Instruction {
var tokens = line.split(" ");
var operator = tokens.shift();
try {
return Instruction.createByName(operator, [for(t in tokens) toValue(t)] );
}catch (e:Dynamic){
throw "Syntax error " + operator+" at line "+lineNo;
}
}
static inline function toValue(str:String):Value {
var firstChar:String = str.charAt(0).toLowerCase();
return switch(firstChar) {
case "@":
Relative(str.substr(1).parseInt());
case "#":
Location(str.substr(1).parseInt());
case "a":
AReg;
case "x":
XReg;
case "y":
YReg;
case _:
Literal(str.parseInt());
}
}
}
class Main
{
static function main()
{
var src = "
start:
LDA 0
LDX 5
LDY iteration1
JSR whileloop
LDA 0
LDX 10
LDY iteration2
JSR whileloop
JMP end
whileloop:
ADD 1
BLT X @3
JSR Y
JMP @-3
TAX
RTS
iteration1:
TRC 1
RTS
iteration2:
TRC 2
RTS
end:
TRC -1
BRK
";
//run(null);
var a = new Machine("Alpha");
var b = new Machine("Beta");
var machines = [a, b];
var memory = new Vector<Int>(512);
var prog = Assembler.assemble(src);
for (m in machines) {
m.setMemory(memory);
m.load(prog);
}
while (a.isRunning() || b.isRunning()) {
a.next();
b.next();
}
//printMem();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment