Created
March 28, 2012 14:01
-
-
Save utaal/2226428 to your computer and use it in GitHub Desktop.
m68k assembler parser and interpreter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
project/target/ | |
target/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
a sketchy a m68k assembler parser and interpreter written in Scala to learn Scala |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name := "m68k-emu" | |
scalaVersion := "2.9.1" | |
libraryDependencies ++= Seq( | |
"org.specs2" %% "specs2" % "1.8.2" % "test" | |
, "org.specs2" %% "specs2-scalaz-core" % "6.0.1" | |
) | |
// Read here for optional dependencies: | |
// http://etorreborre.github.com/specs2/guide/org.specs2.guide.Runners.html#Dependencies | |
resolvers ++= Seq( | |
"snapshots" at "http://oss.sonatype.org/content/repositories/snapshots" | |
) | |
testOptions := Seq(Tests.Filter(s => | |
Seq("Spec", "Suite", "Unit", "all").exists(s.endsWith(_)) | |
)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.m68k | |
import com.github.utaal.m68k.ast._ | |
object RegisterState { | |
def apply(): RegisterState = RegisterState() | |
} | |
case class RegisterState(value: Long) { | |
require (value >= 0L && value <= 0xffffffffL) | |
def getMask(size: Size): Long = size match { | |
case Size.B => 0xffL | |
case Size.W => 0xffffL | |
case Size.L => 0xffffffffL | |
} | |
def mask(size: Size, value: Long): Long = value & getMask(size) | |
def reverseMask(size: Size, value: Long): Long = value & (0xffffffffL ^ getMask(size)) | |
def get(size: Size): Long = mask(size, value) | |
def set(size: Size, newValue: Long) = | |
new RegisterState(reverseMask(size, value) | mask(size, newValue)) | |
} | |
object ProgramCounter { | |
def apply(): ProgramCounter = ProgramCounter(0L) | |
} | |
case class ProgramCounter(value: Long) { | |
require (value >= 0L && value <= 0xffffffL) | |
} | |
object StatusRegister { | |
def apply(): StatusRegister = StatusRegister(false, false, false, false, false) | |
} | |
case class StatusRegister(C: Boolean, V: Boolean, Z: Boolean, N: Boolean, X: Boolean) | |
object CPUState { | |
val DataRegisterNumber = 8 | |
val AddressRegisterNumber = 8 | |
val FramePointerRegister = 6 | |
val StackPointerRegister = 7 | |
private def makeRegisterArray(num: Int): Vector[RegisterState] = { | |
val emptyRegState = RegisterState(0L) | |
Vector.fill[RegisterState](num)(emptyRegState) | |
} | |
def apply(): CPUState = CPUState( | |
CPUState.makeRegisterArray(CPUState.DataRegisterNumber), | |
CPUState.makeRegisterArray(CPUState.AddressRegisterNumber), | |
ProgramCounter(), | |
StatusRegister() | |
) | |
} | |
case class CPUState( | |
D: Vector[RegisterState], | |
A: Vector[RegisterState], | |
PC: ProgramCounter, | |
SR: StatusRegister | |
) { | |
private def updatedRegVector(vec: Vector[RegisterState], num: Int, size: Size, value: Long) = | |
vec.updated(num, vec(num).set(size, value)) | |
def setD(number: Int, size: Size, value: Long) = { | |
require (number >= 0 && number < CPUState.DataRegisterNumber) | |
this.copy(D = updatedRegVector(D, number, size, value)) | |
} | |
def setA(number: Int, size: Size, value: Long) = { | |
require (number >= 0 && number < CPUState.AddressRegisterNumber) | |
this.copy(A = updatedRegVector(A, number, size, value)) | |
} | |
val FP = A(CPUState.FramePointerRegister) | |
def setFP(size: Size, value: Long) = | |
setA(CPUState.FramePointerRegister, size, value) | |
val SP = A(CPUState.StackPointerRegister) | |
def setSP(size: Size, value: Long) = | |
setA(CPUState.StackPointerRegister, size, value) | |
def setPC(value: Long) = | |
this.copy(PC = ProgramCounter(value)) | |
def setSR(sr: StatusRegister) = | |
this.copy(SR = sr) | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.github.utaal.m68k._ | |
import com.github.utaal.m68k.ast._ | |
import org.specs2._ | |
class CPUStateSpec extends mutable.Specification { | |
"The immutable Register state class" should { | |
"correctly store values which fit in the emulated register" in { | |
val r0 = RegisterState(0xffffffffL) | |
r0.value must be_>(0L) | |
RegisterState(0x100000000L) must throwAn[IllegalArgumentException] | |
RegisterState(-1L) must throwAn[IllegalArgumentException] | |
} | |
"produce a new RegisterState when a value is set" in { | |
val r0 = RegisterState(0xdeadbeefL) | |
r0.set(Size.L, 0xfefefefeL).value must_== 0xfefefefeL | |
r0.set(Size.W, 0xfefeL).value must_== 0xdeadfefeL | |
r0.set(Size.B, 0xfeL).value must_== 0xdeadbefeL | |
r0.set(Size.B, 0xfefeL).value must_== 0xdeadbefeL | |
} | |
} | |
"The CPUState immutable class" should { | |
"correctly construct" in { | |
val st = CPUState() | |
val emptyStatusRegister = StatusRegister(false, false, false, false, false) | |
st must beLike { | |
case CPUState(dvec, avec, ProgramCounter(0L), emptyStatusRegister) => { | |
forall(dvec ++ avec) (_ must beLike { case RegisterState(0L) => ok }) | |
dvec.length must_== CPUState.DataRegisterNumber | |
avec.length must_== CPUState.AddressRegisterNumber | |
} | |
} | |
} | |
"correctly produce new instances of itself on register change" in { | |
val st = CPUState() | |
val st1 = st.setD(3, Size.L, 0xdeadbeefL) | |
st1.D(3).value must_== 0xdeadbeefL | |
st1.setD(4, Size.L, 0xfefefefeL).D(3).value must_== 0xdeadbeefL | |
st.setA(CPUState.AddressRegisterNumber + 1, Size.L, 0) must throwAn[IllegalArgumentException] | |
st.setA(4, Size.W, 0xfefefefeL).A(4).value must_== 0xfefeL | |
st.setFP(Size.L, 0xafafafafL).FP.value must_== 0xafafafafL | |
st.setSP(Size.B, 0xaffa).SP.value must_== 0xfaL | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.specs2._ | |
import runner.SpecificationsFinder._ | |
class index extends Specification { def is = | |
examplesLinks("M68k emulator specifications") | |
// see the SpecificationsFinder trait for the parameters of the 'specifications' method | |
def examplesLinks(t: String) = specifications().foldLeft(t.title) { (res, cur) => res ^ see(cur) } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.m68k | |
import com.github.utaal.m68k.ast._ | |
case class Initialization( | |
val symbols: Map[String, Long], | |
val memAlloc: Map[Long, (Size, Long)]) { | |
def addSymbol(label: String, value: Long) = this.copy(symbols=symbols + (label -> value)) | |
def addMemAlloc(addr: Long, size: Size, value: Long) = | |
this.copy(memAlloc=memAlloc + (addr -> (size, value))) | |
} | |
object Initialization { | |
def apply(): Initialization = Initialization(Map[String, Long](), Map[Long, (Size, Long)]()) | |
private def withSymbol(init: Initialization, lbl: Option[String], pos: Long) = | |
(lbl map (l => init.addSymbol(l, pos))) getOrElse init | |
private def byteValue(literal: Literal, init: Initialization) = literal match { | |
case Literal.Int(litValue) => litValue.toLong | |
case Literal.Char(litChar) => litChar.toLong | |
case Literal.Label(lbl) => init.symbols get lbl getOrElse error("missing symbol") | |
} | |
private def lineFold(a: (Long, Initialization), line: Line) = { | |
val (pos, init) = a | |
import DirectiveLine._ | |
line match { | |
case Equ(lbl, value) => (pos, init.addSymbol(lbl, value)) | |
case DC(lbl, size, values) => | |
((pos, withSymbol(init, lbl, pos)) /: values) { (b, value) => | |
val (pos, init) = b | |
(pos + size.bytes, init.addMemAlloc(pos, size, byteValue(value, init))) | |
} | |
case DS(lbl, size, count) => | |
(pos + (size.bytes * count), withSymbol(init, lbl, pos)) | |
case End(_, _) => (pos, init) | |
case OpLine(_, _) => (pos, init) | |
} | |
} | |
def from(program: Program): Initialization = | |
(Initialization() /: program.sections) { case (init, Section(_, org, lines)) => | |
((org.toLong, init) /: lines)(lineFold)._2 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.log | |
import scala.annotation.elidable | |
abstract class Level(val value: Int) extends Ordered[Level] { | |
def compare(b: Level) = this.value compare b.value | |
val name = toString.toLowerCase | |
} | |
object Level { | |
val DEBUG = 100 | |
case object Debug extends Level(DEBUG) | |
case object Finest extends Level(elidable.FINEST) | |
case object Finer extends Level(elidable.FINER) | |
case object Fine extends Level(elidable.FINE) | |
case object Config extends Level(elidable.CONFIG) | |
case object Info extends Level(elidable.INFO) | |
case object Warning extends Level(elidable.WARNING) | |
case object Severe extends Level(elidable.SEVERE) | |
} | |
object Logger { | |
var level = Level.Debug | |
private def curlyFormat(fmt: String, subs: List[Any]) = | |
(fmt /: subs.view.zipWithIndex) { (res, sub) => | |
res.replaceAll("\\{%s\\}".format(sub._2), sub._1.toString) | |
} | |
def for_[Scope <: Singleton](scope: Scope) = { | |
val packageName = scope.asInstanceOf[AnyRef].getClass.getPackage match { | |
case null => null | |
case nonDefaultPackage => nonDefaultPackage.getName | |
} | |
new Logger(packageName, level) | |
} | |
} | |
class Logger(name: String, minLevel: Level) { | |
private def log(level: Level, fmt: String, subs: List[Any]) = { | |
if (level >= minLevel) | |
println("[" + level.name + "] " + name + ": " + Logger.curlyFormat(fmt, subs)) | |
} | |
@elidable(Level.DEBUG) | |
/*def DEBUG(msg: String) = log(Level.Debug, msg)*/ | |
def DEBUG(fmt: String, subs: Any*) = log(Level.Debug, fmt, subs.toList) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.m68k.ast | |
import com.github.utaal.m68k.Size | |
sealed trait Operand | |
sealed trait Register { | |
def number: Int | |
} | |
object Register { | |
case class Data(override val number: Int) extends Register | |
case class Address(override val number: Int) extends Register | |
} | |
sealed trait Literal | |
object Literal { | |
case class Int(value: scala.Int) extends Literal | |
case class Char(value: scala.Char) extends Literal | |
case class Label(name: String) extends Literal | |
} | |
sealed trait DataAddressing extends Operand | |
object DataAddressing { | |
case class Immediate(value: Literal) extends DataAddressing | |
sealed trait Direct extends DataAddressing { | |
def register: Register | |
} | |
object Direct { | |
case class Data(override val register: Register.Data) extends Direct | |
case class Address(override val register: Register.Address) extends Direct | |
} | |
case class Indirect(register: Register.Address) extends DataAddressing | |
case class IndirIncr(register: Register.Address) extends DataAddressing | |
case class IndirDecr(register: Register.Address) extends DataAddressing | |
case class IdxDispl(displ: Int, index: Register.Address) extends DataAddressing | |
case class IdxBaseDispl( | |
displ: Int, index: Register.Address, base: Register.Data, baseSize: Size.WL) | |
extends DataAddressing | |
sealed trait Absolute extends DataAddressing | |
object Absolute { | |
case class Address(addr: Int, size: Size.WL) extends Absolute | |
case class Label(label: String) extends Absolute | |
} | |
} | |
sealed trait InstructionAddressing extends Operand | |
object InstructionAddressing { | |
sealed trait Relative extends InstructionAddressing | |
object Relative { | |
case class Label(name: String) extends Relative | |
} | |
} | |
sealed trait Op { | |
def opcode: String | |
} | |
sealed trait SizedOp extends Op { | |
def size: Size | |
} | |
package DataOps { | |
class Binary( | |
val opcode: String, val size: Size, val src: DataAddressing, val dest: DataAddressing) | |
extends SizedOp { | |
override def toString = | |
"Binary(%s,%s,%s,%s)" format (opcode, size.toString, src.toString, dest.toString) | |
override def equals(other: Any): Boolean = other match { | |
case b:Binary => opcode == b.opcode && size == b.size && src == b.src && dest == b.dest | |
case _ => false | |
} | |
} | |
object Binary { | |
def apply(opcode: String, size: Size, src: DataAddressing, dest: DataAddressing) = | |
new Binary(opcode, size, src, dest) | |
} | |
case class MOVE( | |
override val size: Size, | |
override val src: DataAddressing, | |
override val dest: DataAddressing) extends Binary("MOVE", size, src, dest) | |
case class BinaryA( | |
override val opcode: String, | |
override val size: Size.WL, | |
override val src: DataAddressing, | |
override val dest: DataAddressing.Direct.Address) extends Binary(opcode, size, src, dest) | |
case class BinaryI( | |
override val opcode: String, | |
override val size: Size, | |
override val src: DataAddressing.Immediate, | |
override val dest: DataAddressing) extends Binary(opcode, size, src, dest) | |
} | |
package ControlOps { | |
case class Unary( | |
val opcode: String, val dest: InstructionAddressing) extends Op | |
} | |
sealed trait Line | |
case class Section(name: String, org: Int, lines: List[Line]) | |
sealed trait DirectiveLine extends Line | |
object DirectiveLine { | |
case class Equ(label: String, value: Int) extends DirectiveLine | |
case class DC(label: Option[String], size: Size, values: List[Literal]) extends DirectiveLine | |
case class DS(label: Option[String], size: Size, count: Int) extends DirectiveLine | |
case class End(label: Option[String], entryPoint: String) extends DirectiveLine | |
} | |
case class OpLine(label: Option[String], op: Op) extends Line | |
case class Program(sections: List[Section]) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.m68k | |
import com.github.utaal.m68k.ast._ | |
import scala.util.parsing.combinator.RegexParsers | |
object M68kParser extends RegexParsers { | |
override val skipWhitespace = false | |
object basic { | |
def number = regex("""[+-]?[0-9]+""".r) ^^ { n => n.toInt } | |
def size = "." ~> """[BWL]""".r ^^ { s => s match { | |
case "B" => Size.B | |
case "W" => Size.W | |
case "L" => Size.L | |
} } | |
def wl = "." ~> """[WL]""".r ^^ { s => s match { | |
case "W" => Size.W | |
case "L" => Size.L | |
} } | |
def label = regex("""[a-zA-Z_][0-9a-zA-Z_]+""".r) | |
def eol = ws ~ rep(elem('\n')) | |
} | |
object literal extends Parser[Literal] { | |
def int = basic.number ^^ { n => Literal.Int(n) } | |
def char = elem('\'') ~> regex("""[A-Za-z]""".r) <~ elem('\'') ^^ { | |
c => Literal.Char(c.charAt(0)) | |
} | |
def label = basic.label ^^ { lbl => Literal.Label(lbl) } | |
def apply(in: Input): ParseResult[Literal] = (int | char)(in) | |
} | |
def ws = rep(elem(' ')) | |
object register { | |
def data = "D" ~> basic.number ^^ { n => Register.Data(n) } | |
def address = "A" ~> basic.number ^^ { n => Register.Address(n) } | |
} | |
object dataAddressing { | |
import DataAddressing._ | |
def immediate = "#" ~> literal ^^ { case n => Immediate(n) } | |
def directData = register.data ^^ { s => Direct.Data(s) } | |
def directAddress = register.address ^^ { s => Direct.Address(s) } | |
def direct: Parser[Direct] = (directData | directAddress) | |
def indirect = "(" ~> register.address <~ ")" ^^ { s => Indirect(s) } | |
def indirIncr = "(" ~> register.address <~ ")+" ^^ { s => IndirIncr(s) } | |
def indirDecr = "-(" ~> register.address <~ ")" ^^ { s => IndirDecr(s) } | |
def idxDispl = basic.number ~ "(" ~ register.address ~ ")" ^^ { | |
case displ ~ _ ~ idx ~ _ => IdxDispl(displ, idx) | |
} | |
def idxBaseDispl = | |
basic.number ~ "(" ~ register.address ~ "," ~ ws ~ register.data ~ basic.wl ~ ")" ^^ { | |
case displ ~ _ ~ idx ~ _ ~ _ ~ reg ~ size ~ _ => IdxBaseDispl(displ, idx, reg, size) | |
} | |
def absoluteAddress = basic.number ~ basic.wl ^^ { | |
case addr ~ size => Absolute.Address(addr, size) | |
} | |
def absoluteLabel = basic.label ^^ { lbl => Absolute.Label(lbl) } | |
def absolute = absoluteAddress | absoluteLabel | |
} | |
object instructionAddressing { | |
import InstructionAddressing._ | |
def relativeLabel = basic.label ^^ { lbl => Relative.Label(lbl) } | |
} | |
object dataOp extends Parser[SizedOp] { | |
import DataOps._ | |
import dataAddressing._ | |
private def opcode[S <: Size](opcodes: Parser[String], size: Parser[S]) = opcodes ~ size ^^ { | |
case opcode ~ size => (opcode, size) | |
} | |
private def nonImmediateData = | |
directData | indirect | indirIncr | indirDecr | idxDispl | idxBaseDispl | absolute | |
private def any = | |
indirDecr | indirIncr | immediate | direct | indirect | idxDispl | idxBaseDispl | absolute | |
private def twoParams[P1, P2](fst: Parser[P1], snd: Parser[P2]) = | |
ws ~ fst ~ elem(',') ~ ws ~ snd ^^ { case _ ~ fst ~ _ ~ _ ~ snd => (fst, snd) } | |
def move = opcode("MOVE", basic.size) ~ twoParams(any, nonImmediateData) ^^ { | |
case ((opcode, size)) ~ ((fst, snd)) => MOVE(size, fst, snd) | |
} | |
def binaryAOpcodes = "MOVEA" | "ADDA" | "SUBA" | "CMPA" | |
def binaryA = | |
opcode(binaryAOpcodes, basic.wl) ~ twoParams(any, directAddress) ^^ { | |
case ((opcode, size)) ~ ((fst, snd)) => BinaryA(opcode, size, fst, snd) | |
} | |
def binaryAluOpcodes = "ADD" | "SUB" | "AND" | "OR" | "CMP" | |
def binaryAluSrcData = | |
opcode(binaryAluOpcodes, basic.size) ~ twoParams(directData, nonImmediateData) ^^ { | |
case ((opcode, size)) ~ ((fst, snd)) => Binary(opcode, size, fst, snd) | |
} | |
def binaryAluDestData = | |
opcode(binaryAluOpcodes, basic.size) ~ twoParams(nonImmediateData, directData) ^^ { | |
case ((opcode, size)) ~ ((fst, snd)) => Binary(opcode, size, fst, snd) | |
} | |
def binaryIOpcodes = "ADDI" | "SUBI" | "CMPI" | "ANDI" | "ORI" | |
def binaryI = | |
opcode(binaryIOpcodes, basic.size) ~ twoParams(immediate, nonImmediateData) ^^ { | |
case ((opcode, size)) ~ ((fst, snd)) => BinaryI(opcode, size, fst, snd) | |
} | |
def apply(in: Input): ParseResult[SizedOp] = | |
(move | binaryA | binaryAluSrcData | binaryAluDestData | binaryI)(in) | |
} | |
object controlOp extends Parser[Op] { | |
import ControlOps._ | |
import instructionAddressing._ | |
def jumpBraOpcodes = "JMP" | "BRA" | "BEQ" | "BNE" | "BLT" | "BGT" | "BLE" | "BGE" | |
def jumpBra = jumpBraOpcodes ~ ws ~ relativeLabel ^^ { | |
case opcode ~ _ ~ lbl => Unary(opcode, lbl) | |
} | |
def apply(in: Input): ParseResult[Op] = (jumpBra)(in) | |
} | |
def op = dataOp | controlOp | |
def lineLabel = basic.label <~ ":" | |
def opLine = (lineLabel?) ~ ws ~ op ^^ { case lbl ~ _ ~ op => OpLine(lbl, op) } | |
object directiveLine extends Parser[DirectiveLine] { | |
import DirectiveLine._ | |
def end = (lineLabel?) ~ ws ~ "END" ~ ws ~ basic.label ^^ { | |
case lineLbl ~ _ ~ _ ~ _ ~ lbl => End(lineLbl, lbl) | |
} | |
def equ = lineLabel ~ ws ~ "EQU" ~ ws ~ basic.number ^^ { | |
case lineLbl ~ _ ~ _ ~ _ ~ num => Equ(lineLbl, num) | |
} | |
private def dcValues = (literal ~ rep((ws ~ "," ~ ws) ~> literal)) ^^ { case l ~ ll => l :: ll } | |
def dc = (lineLabel?) ~ ws ~ "DC" ~ basic.size ~ ws ~ dcValues ^^ { | |
case lineLbl ~ _ ~ _ ~ size ~ _ ~ values => DC(lineLbl, size, values) | |
} | |
def ds = (lineLabel?) ~ ws ~ "DS" ~ basic.size ~ ws ~ basic.number ^^ { | |
case lineLbl ~ _ ~ _ ~ size ~ _ ~ num => DS(lineLbl, size, num) | |
} | |
def apply(in: Input): ParseResult[DirectiveLine] = (end | equ | dc | ds)(in) | |
} | |
def line = opLine | directiveLine | |
def sectionHeader = "SECTION" ~ ws ~ basic.label ~ basic.eol ^^ { | |
case _ ~ _ ~ name ~ _ => name | |
} | |
def orgHeader = "ORG" ~ ws ~ basic.number ~ basic.eol ^^ { | |
case _ ~ _ ~ num ~ _ => num | |
} | |
def section = sectionHeader ~ orgHeader ~ rep(line <~ basic.eol) ^^ { | |
case sectionName ~ org ~ lines => Section(sectionName, org, lines) | |
} | |
def program = rep(section) ^^ { s => Program(s) } | |
def parseProgram(in: String): Either[String, Program] = parseAll(program, in + "\n") match { | |
case Success(res, _) => Right(res) | |
case f:Failure => Left(f.toString()) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.github.utaal.m68k._ | |
import com.github.utaal.m68k.ast._ | |
import org.specs2._ | |
class M68kParserSpec extends mutable.Specification { | |
implicit def ParsableString(a: String) = new { | |
import com.github.utaal.m68k.{M68kParser => pp} | |
private def parseMustEqual[T](parser: pp.Parser[T])(exp: T) = | |
pp.parseAll(parser, a) must beLike { | |
case pp.Success(res, _) => res must_== exp | |
} | |
def parsesAs(opLine: OpLine) = parseMustEqual(pp.opLine)(opLine) | |
def parsesAs(dirLine: DirectiveLine) = parseMustEqual(pp.directiveLine)(dirLine) | |
def parsesAs(section: Section) = parseMustEqual(pp.section)(section) | |
} | |
"The M68kParser.opLine production" should { | |
"accept instructions with immediate values as operand" in { | |
"ADDI.W #3, D2" parsesAs OpLine(None, DataOps.BinaryI( | |
"ADDI", | |
Size.W, | |
DataAddressing.Immediate(Literal.Int(3)), | |
DataAddressing.Direct.Data(Register.Data(2)) | |
)) | |
} | |
"accept instr. with register addressing as operand(s)" in { | |
"MOVE.L (A0), D2" parsesAs OpLine(None, DataOps.MOVE( | |
Size.L, | |
DataAddressing.Indirect(Register.Address(0)), | |
DataAddressing.Direct.Data(Register.Data(2)) | |
)) | |
"MOVEA.W -(A0), A3" parsesAs OpLine(None, DataOps.BinaryA( | |
"MOVEA", | |
Size.W, | |
DataAddressing.IndirDecr(Register.Address(0)), | |
DataAddressing.Direct.Address(Register.Address(3)) | |
)) | |
"SUB.L 2(A3, D2.W), D3" parsesAs OpLine(None, DataOps.Binary( | |
"SUB", | |
Size.L, | |
DataAddressing.IdxBaseDispl(2, Register.Address(3), Register.Data(2), Size.W), | |
DataAddressing.Direct.Data(Register.Data(3)) | |
)) | |
} | |
"accept instructions with absolute addressing as operand(s)" in { | |
"MOVE.L ll1, D0" parsesAs OpLine(None, DataOps.MOVE( | |
Size.L, | |
DataAddressing.Absolute.Label("ll1"), | |
DataAddressing.Direct.Data(Register.Data(0)) | |
)) | |
} | |
"accept unary control ops" in { | |
"JMP label" parsesAs OpLine(None, ControlOps.Unary( | |
"JMP", | |
InstructionAddressing.Relative.Label("label") | |
)) | |
} | |
"accept labeled lines" in { | |
"lbl: BGE abc" parsesAs OpLine(Some("lbl"), ControlOps.Unary( | |
"BGE", | |
InstructionAddressing.Relative.Label("abc") | |
)) | |
} | |
} | |
"The M68kParser.directiveLine production" should { | |
"accept valid directives" in { | |
"end_while: END begin" parsesAs DirectiveLine.End(Some("end_while"), "begin") | |
"aa: EQU 4" parsesAs DirectiveLine.Equ("aa", 4) | |
"vec: DS.W 3" parsesAs DirectiveLine.DS(Some("vec"), Size.W, 3) | |
"hw: DC.B 'h', 'w'" parsesAs DirectiveLine.DC( | |
Some("hw"), Size.B, Literal.Char('h') :: Literal.Char('w') :: Nil | |
) | |
} | |
} | |
"The M68kParser.section production" should { | |
"accept valid program sections" in { | |
"""|SECTION CODE | |
|ORG 100 | |
|begin: END begin | |
|""".stripMargin parsesAs( | |
Section("CODE", 100, DirectiveLine.End(Some("begin"), "begin") :: Nil) | |
) | |
} | |
} | |
"The M68k parser" should { | |
"accept an entire program" in { | |
val program = | |
"""|SECTION DATA | |
|ORG 100 | |
|d1: DS.W 3 | |
|SECTION CODE | |
|ORG 200 | |
|begin: MOVE.W d1, D0 | |
|END CODE""".stripMargin | |
M68kParser.parseProgram(program) should beLike { | |
case Right(p) => p must_== Program( | |
Section("DATA", 100, | |
DirectiveLine.DS(Some("d1"), Size.W, 3) :: | |
Nil | |
) :: | |
Section("CODE", 200, | |
OpLine(Some("begin"), DataOps.MOVE(Size.W, DataAddressing.Absolute.Label("d1"), | |
DataAddressing.Direct.Data(Register.Data(0)))) :: | |
DirectiveLine.End(None, "CODE") :: | |
Nil | |
) :: | |
Nil | |
) | |
} | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.m68k | |
import com.github.utaal.m68k.ast._ | |
trait Memory { | |
def memSize: Long | |
def set(size: Size, addr: Long, value: Long): Memory | |
def get(size: Size, addr: Long): Long | |
def dump: String | |
} | |
object LinearMemory { | |
val log = com.github.utaal.log.Logger.for_(this) | |
} | |
class LinearMemory private (override val memSize: Long, memory: Vector[Char]) extends Memory { | |
import LinearMemory.log._ | |
require(memSize > 0 && memSize <= Int.MaxValue) | |
def this(memSize: Long) = | |
this(memSize, Vector.fill(memSize.toInt)(0x00)) | |
private def getNumChars(addr: Int, num: Int) = { | |
val slice = memory.slice(addr, addr + num) | |
(slice.head.toLong /: slice.tail)((c, i) => (c << 8) | i) | |
} | |
def get(size: Size, addr: Long) = { | |
require(addr > 0 && addr <= memSize) | |
getNumChars(addr.toInt, size.bytes) | |
} | |
def set(size: Size, addr: Long, value: Long) = { | |
require(addr > 0 && addr <= memSize) | |
require(value > 0 && value <= 0xffffffffL) | |
val len = size.bytes | |
val vec = Vector.tabulate[Char](len)(i => ((value >> (8 * (len - 1 - i))) & 0xffL).toChar) | |
new LinearMemory(memSize, memory.patch(addr.toInt, vec, size.bytes)) | |
} | |
def dump: String = { | |
def hexString(grp: Seq[Char]) = grp.map("%02X" format _.toInt).mkString(" ") | |
def printableString(grp: Seq[Char]) = | |
grp.map { x => if (x.isLetterOrDigit) x else "?" }.mkString("") | |
memory.view.grouped(8).zipWithIndex.filter(_._1.exists(_ != 0)).map { | |
case (x, i) => ("%8d" format (i * 8)) + " " + hexString(x) + " " + printableString(x) | |
} mkString "\n" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.github.utaal.m68k._ | |
import com.github.utaal.m68k.ast._ | |
import org.specs2._ | |
class MemorySpec extends mutable.Specification { | |
"Memory" should { | |
"create new immutable instances on set and match" in { | |
val mem: Memory = new LinearMemory(1000L) | |
val m1 = mem.set(Size.B, 100L, 0xfeL) | |
m1.get(Size.B, 100L) should_== 0xfeL | |
m1.get(Size.B, 101L) should_== 0x00L | |
val m2 = mem.set(Size.L, 100L, 0xdeadbeefL) | |
m2.get(Size.L, 100L) should_== 0xdeadbeefL | |
m2.get(Size.W, 100L) should_== 0xdeadL | |
m2.get(Size.W, 102L) should_== 0xbeefL | |
m2.get(Size.B, 101L) should_== 0xadL | |
m2.get(Size.B, 105L) should_== 0x00L | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.m68k | |
sealed trait Size { | |
def bytes: Int | |
} | |
object Size { | |
sealed trait WL extends Size | |
case object B extends Size with WL { | |
val bytes = 1 | |
} | |
case object W extends Size with WL { | |
val bytes = 2 | |
} | |
case object L extends Size with WL { | |
val bytes = 4 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.utaal.m68k | |
object util { | |
def describe[T <: AnyRef](t: T)(implicit m: scala.reflect.Manifest[T]) = | |
println("t was " + t.toString + " of class " + t.getClass.getName() + ", erased from " + m.erasure) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment