Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add a Notch-style assembler

  • Loading branch information...
commit 2597c9edc7a118b9b3c05f48551c5de2ba18d2e7 1 parent 3baf66b
@judofyr authored
View
23 bin/rcpu
@@ -13,12 +13,17 @@ DCPU-16 assembler, emulator, debugger
Usage:
bin/rcpu examples/hello.rcpu
bin/rcpu examples/hello.bin
- bin/rcpu -f bin examples/hello
+ bin/rcpu examples/hello.s
+ bin/rcpu -b examples/hello
+
+Valid extensions:
+ .rcpu - Ruby DSL assembler
+ .s - Notch-style assembler
+ .bin - Binary file
Available options:
EOS
- opt :format, "File format (bin or rcpu). Guessed from filename if not present.",
- :type => String
+ opt :binary, "Always treat the file as a binary"
end
formats = %w[bin rcpu]
@@ -27,19 +32,15 @@ opts = nil
T.with_standard_exception_handling(p) do
opts = p.parse(ARGV)
raise T::HelpNeeded unless opts[:filename] = ARGV.shift
- opts[:format] ||= File.extname(opts[:filename]).sub('.','')
- p.die :format, "can only be 'bin' or 'rcpu'" unless formats.include?(opts[:format])
+ opts[:binary] ||= File.extname(opts[:filename]) == ".bin"
end
linker = RCPU::Linker.new
-
-case opts[:format]
-when 'rcpu'
- lib = linker.find(opts[:filename], Dir.pwd)
- linker.compile_library(lib)
-when 'bin'
+if opts[:binary]
linker.extensions << [0x8000, RCPU::ScreenExtension, []]
linker.compile_binary(File.binread(opts[:filename]))
+else
+ linker.compile(opts[:filename], Dir.pwd)
end
linker.debugger.start
View
14 examples/hello.s
@@ -0,0 +1,14 @@
+; Assembler test for DCPU
+; by Markus Persson
+.library screen
+
+ set i, 0 ; Init loop counter, for clarity
+:nextchar ife [data+i], 0 ; If the character is 0 ..
+ set PC, end ; .. jump to the end
+ set [0x8000+i], [data+i] ; Video ram starts at 0x8000, copy char there
+ add i, 1 ; Increase loop counter
+ set PC, nextchar ; Loop
+
+:data dat "Hello world!", 0 ; Zero terminated string
+
+:end sub PC, 1 ; Freeze the CPU forever
View
50 examples/maximinus-thrax/01_notch.s
@@ -0,0 +1,50 @@
+;Notch's examples
+;Should compile fine, by default
+
+;Try some basic stuff
+ SET A, 0x30 ; 7c01 0030
+ SET [0x1000], 0x20 ; 7de1 1000 0020
+ SUB A, [0x1000] ; 7803 1000
+ IFN A, 0x10 ; c00d
+ SET PC, crash ; 7dc1 001a [*]
+
+;Do a loopy thing
+ SET I, 10 ; a861
+ SET A, 0x2000 ; 7c01 2000
+:loop SET [0x2000+I], [A] ; 2161 2000
+ SUB I, 1 ; 8463
+ IFN I, 0 ; 806d
+ SET PC, loop ; 7dc1 000d [*]
+
+;Call a subroutine
+ SET X, 0x4 ; 9031
+ JSR testsub ; 7c10 0018 [*]
+ SET PC, crash ; 7dc1 001a [*]
+
+:testsub
+ SHL X, 4 ; 9037
+ SET PC, POP ; 61c1
+
+
+;Hang forever. X should now be 0x40 if everything went right.
+:crash SET PC, crash ; 7dc1 001a [*]
+
+; Assembler test for DCPU
+; by Markus Persson
+
+ set a, 0xbeef ; Assign 0xbeef to register a
+ set [0x1000], a ; Assign memory at 0x1000 to value of register a
+ ifn a, [0x1000] ; Compare value of register a to memory at 0x1000 ..
+ set PC, end ; .. and jump to end if they don't match
+
+ set i, 0; Init loop counter, for clarity
+:nextchar
+ ife [data+i] , 0; If the character is 0 ..
+ set PC, end ; .. jump to the end
+ set [0x8000+i], [data+i] ; Video ram starts at 0x8000, copy char there
+ add i, 1 ; Increase loop counter
+ set PC, nextchar ; Loop
+
+:data dat "Hello world!", 0; Zero terminated string
+
+:end sub PC, 1; Freeze the CPU forever
View
18 examples/maximinus-thrax/02_test_code.s
@@ -0,0 +1,18 @@
+;Some examples of compiler code that should compile fine.
+;I had added my reasoning for each one
+
+:start set a, 5 ;uses tabs
+ set b, 5 ;uses spaces
+
+ set a, 5 ;case is not important
+ SET A, 5
+
+ set a, start ;labels should be fine
+ set a, end ;as should forward labels
+
+:data dat "test" ;should be able to handle data constructs
+ dat "test", 0 ;and mixed data
+ dat 3, 5, 6 ;of both types
+
+ set a, 5 ;number of spaces should be no issue
+:end set a,5
View
18 examples/maximinus-thrax/03_instructions.s
@@ -0,0 +1,18 @@
+;Some examples of compiler code that should compile fine.
+;I had added my reasoning for each one
+
+:start set a, 5 ;uses tabs
+ set b, 5 ;uses spaces
+
+ set a, 5 ;case is not important
+ SET A, 5
+
+ set a, start ;labels should be fine
+ set a, end ;as should forward labels
+
+:data dat "test" ;should be able to handle data constructs
+ dat "test", 0 ;and mixed data
+ dat 3, 5, 6 ;of both types
+
+ set a, 5 ;number of spaces should be no issue
+:end set a,5
View
34 examples/maximinus-thrax/04_operands.s
@@ -0,0 +1,34 @@
+;All operand types, presented in various ways
+
+:start set a, 5
+ set a, b
+ set a, [b]
+ set a, [b + 3]
+ set a, [3 + b] ;both ways should be acceptable
+ set a, [b+3] ;spaces should not be forced
+ set a, POP
+ set a, PEEK
+ set PUSH, a
+ set a, start
+ set a, [start]
+ set a, PC
+ set a, SP
+ set a, O
+
+ ;reverse these
+
+ ;set 5, a ;allowable in the spec though
+
+ set b, a
+ set [b], a
+ set [b + 3], a
+ set [3 + b], a
+ set [b+3], a
+ set POP, a
+ set PEEK, a
+ set a, PUSH
+ set start, a
+ set [start], a
+ set PC, a
+ set SP, a
+ set O, a
View
19 examples/maximinus-thrax/05_errors.s
@@ -0,0 +1,19 @@
+;list of errors that all compilers should check
+;all lines up to :start will be skipped by the test program
+
+:start
+ sit a, 3 ;instruction name incorrect
+ set d, 4 ;wrong register name
+ set a, 70000 ;integer too big
+ set a, 0xfffff ;hex value too large
+ set a a ;no comma to seperate
+ set a ;missing operand
+ set ;no operands
+label set a, 0 ;label has no colon
+loop: set a, 0 ;label is at wrong end
+ set a, [x ;missing right square bracket
+ set a, x] ;missing left square bracket
+ jsr a, a ;too many operands on a jsr
+ set a, aaa ;no label exists
+ set a, [aaa] ;no indexed label exists
+ div a, 0 ;catch compiler divide by zero errors
View
1  lib/rcpu.rb
@@ -264,6 +264,7 @@ def to_machine(mem = [])
end
require 'rcpu/assembler'
+require 'rcpu/asm'
require 'rcpu/emulator'
require 'rcpu/libraries'
require 'rcpu/debugger'
View
155 lib/rcpu/asm.rb
@@ -0,0 +1,155 @@
+require 'strscan'
+
+module RCPU
+ class ASMParser
+ def self.u(arr)
+ Regexp.new('('+arr.map(&:to_s)*'|'+')\b', 'i')
+ end
+
+ BASIC = u(BasicInstruction::ALL)
+ NONBASIC = u(NonBasicInstruction::ALL)
+ REGS = u(Register::ALL)
+ SPACE = /[ \t]*/
+ NUMBER = /(0x[0-9a-fA-F]{1,4})|\d+/
+
+ class SyntaxError < StandardError
+ def initialize(msg, line, lineno, col)
+ space = ""
+ line.each_char do |char|
+ break if space.size >= col
+ space << (char == "\t" ? "\t" : " ")
+ end
+ super("#{msg}\nLine number: #{lineno}\n|#{line}\n|#{space}^")
+ end
+ end
+
+ def initialize(lib, str)
+ @lib = lib
+ @str = StringScanner.new(str)
+ end
+
+ def parse
+ parse_instruction until @str.empty?
+ end
+
+ def block(name)
+ @block = @lib.blocks[name] = Block.new
+ end
+
+ def push(thing)
+ block(:main) unless @block
+ @block.ins << thing
+ end
+
+ def skip_crap!
+ begin
+ crap = false
+ crap |= @str.skip(/\s+/)
+ crap |= @str.skip(/;[^\n]+/)
+ end while crap
+ end
+
+ def parse_instruction
+ skip_crap!
+
+ if @str.skip(/:/)
+ name = @str.scan(/\w+/)
+ push name.to_sym
+ return
+ end
+
+ if name = @str.scan(BASIC)
+ name = name.upcase.to_sym
+ a = parse_value
+ @str.skip(SPACE)
+ @str.skip(/,/) or error("#{name} requires two arguments")
+ b = parse_value
+ push BasicInstruction.new(name, a, b)
+ return
+ end
+
+ if name = @str.scan(NONBASIC)
+ name = name.upcase.to_sym
+ a = parse_value
+ push NonBasicInstruction.new(name, a)
+ return
+ end
+
+ if @str.scan(/dat\b/i)
+ data = ""
+ while true
+ data << parse_data
+ @str.skip(SPACE)
+ @str.skip(/,/) or break
+ end
+
+ push StringData.new(data)
+ return
+ end
+
+ if @str.scan(/\.library\b/)
+ @str.skip(SPACE)
+ name = @str.scan(/\w+/) or error("Unknown library")
+ @lib.library(name.to_sym)
+ return
+ end
+
+ skip_crap!
+ error("Unknown instruction") unless @str.empty?
+ end
+
+ def parse_value(indirect = false)
+ @str.skip(/ */)
+ res = if reg = @str.scan(REGS)
+ Register.new(reg.upcase.to_sym)
+ elsif num = @str.scan(NUMBER)
+ Literal.new(Integer(num))
+ elsif @str.scan(/\[/)
+ val = parse_value(true)
+ @str.scan(/\]/) or error('Missing ]')
+ val.is_a?(PlusRegister) ? val : Indirection.new(val)
+ elsif label = @str.scan(/\w+/)
+ Label.new(label.to_sym)
+ else
+ error("Unknown value")
+ end
+ ensure
+ if indirect && @str.scan(/\+/)
+ reg = parse_value
+ case res
+ when Literal
+ res = res.value
+ when Label
+ else
+ error("Left side must be a number or a label")
+ end
+
+ error("Right side must be registerer") unless reg.is_a?(Register)
+ return PlusRegister.new(reg, res)
+ end
+ end
+
+ def parse_data
+ @str.skip(SPACE)
+
+ if @str.scan(/"/)
+ str = @str.scan_until(/"/) or error("Missing \"")
+ str.chop
+ elsif num = @str.scan(NUMBER)
+ Integer(num).chr
+ else
+ error("Unknown data")
+ end
+ end
+
+ def error(msg)
+ parsed = @str.string[0, @str.pos]
+ lineno = parsed.count("\n")
+ line = parsed[(parsed.rindex("\n")+1)..-1]
+ col = line.size
+ line << @str.check(/[^\n]*/)
+ raise SyntaxError.new(msg, line, lineno, col)
+ end
+ end
+end
+
View
20 lib/rcpu/assembler.rb
@@ -3,12 +3,13 @@
module RCPU
class Block
include StandardMacros
+ attr_reader :ins
def initialize(&blk)
@ins = []
@data = {}
@next_newlabel = 0
- instance_eval(&blk)
+ instance_eval(&blk) if blk
end
def data(*args)
@@ -113,6 +114,10 @@ def extension(location, klass, *args, &blk)
def library(name)
@libraries << name
end
+
+ def parse(str)
+ ASMParser.new(self, str).parse
+ end
end
def self.define(name, &blk)
@@ -164,10 +169,21 @@ def find(name, scope)
def load_file(file)
l = Library.new
l.scope = File.dirname(file)
- l.instance_eval(File.read(file), file)
+ case ext = File.extname(file)
+ when ".rcpu"
+ l.instance_eval(File.read(file), file)
+ when ".s"
+ l.parse(File.binread(file))
+ else
+ raise AssemblerError, "Unknown file format: #{ext}"
+ end
@libraries[file] = l
end
+ def compile(file, scope)
+ compile_library(find(file, scope))
+ end
+
def compile_binary(str)
@memory.concat(str.unpack('v*'))
end
View
1  lib/rcpu/emulator.rb
@@ -2,6 +2,7 @@ module RCPU
class Emulator
# An Array with bound checking
class Memory
+ attr_reader :array
SIZE = 0x10000
def initialize(source)
View
10 test/helper.rb
@@ -15,6 +15,16 @@ def rcpu(run = true, &blk)
@emu.run if run
end
+ def asm(str, run = true)
+ lib = Library.new
+ lib.parse(str)
+ @asmlinker = Linker.new
+ @asmlinker.compile_library(lib)
+ @asmemu = Emulator.new(@asmlinker.finalize)
+ @asmemu.memory.add_extensions(@asmlinker.extensions)
+ @asmemu.run if run
+ end
+
def debug!
Debugger.new(@emu, @linker).start
end
View
110 test/test_asm.rb
@@ -0,0 +1,110 @@
+require 'helper'
+
+module RCPU
+ class TestASM < TestCase
+ def test_notch
+ asm <<-ASM, false
+;Notch's examples
+;Should compile fine, by default
+
+;Try some basic stuff
+ SET A, 0x30 ; 7c01 0030
+ SET [0x1000], 0x20 ; 7de1 1000 0020
+ SUB A, [0x1000] ; 7803 1000
+ IFN A, 0x10 ; c00d
+ SET PC, crash ; 7dc1 001a [*]
+
+;Do a loopy thing
+ SET I, 10 ; a861
+ SET A, 0x2000 ; 7c01 2000
+:loop SET [0x2000+I], [A] ; 2161 2000
+ SUB I, 1 ; 8463
+ IFN I, 0 ; 806d
+ SET PC, loop ; 7dc1 000d [*]
+
+;Call a subroutine
+ SET X, 0x4 ; 9031
+ JSR testsub ; 7c10 0018 [*]
+ SET PC, crash ; 7dc1 001a [*]
+
+:testsub
+ SHL X, 4 ; 9037
+ SET PC, POP ; 61c1
+
+
+;Hang forever. X should now be 0x40 if everything went right.
+:crash SET PC, crash ; 7dc1 001a [*]
+
+; Assembler test for DCPU
+; by Markus Persson
+
+ set a, 0xbeef ; Assign 0xbeef to register a
+ set [0x1000], a ; Assign memory at 0x1000 to value of register a
+ ifn a, [0x1000] ; Compare value of register a to memory at 0x1000 ..
+ set PC, end ; .. and jump to end if they don't match
+
+ set i, 0; Init loop counter, for clarity
+:nextchar
+ ife [data+i] , 0; If the character is 0 ..
+ set PC, end ; .. jump to the end
+ set [0x8000+i], [data+i] ; Video ram starts at 0x8000, copy char there
+ add i, 1 ; Increase loop counter
+ set PC, nextchar ; Loop
+
+:data dat "Hello world!", 0; Zero terminated string
+
+:end sub PC, 1; Freeze the CPU forever
+ ASM
+
+ rcpu(false) do
+ block :main do
+ SET a, 0x30
+ SET [0x1000], 0x20
+ SUB a, [0x1000]
+ IFN a, 0x10
+ SET pc, :crash
+
+
+ SET i, 10
+ SET a, 0x2000
+ label :loop
+ SET [i+0x2000], [a]
+ SUB i, 1
+ IFN i, 0
+ SET pc, :loop
+
+ SET x, 0x4
+ JSR :testsub
+ SET pc, :crash
+
+ label :testsub
+ SHL x, 4
+ SET pc, pop
+
+ label :crash
+ SET pc, :crash
+
+ SET a, 0xbeef
+ SET [0x1000], a
+ IFN a, [0x1000]
+ SET pc, :end
+
+ SET i, 0
+ label :nextchar
+ IFE [i+:data], 0
+ SET pc, :end
+ SET [i+0x8000], [i+:data]
+ ADD i, 1
+ SET pc, :nextchar
+
+ data :data, "Hello world!\0"
+ label :end
+ SUB pc, 1
+ end
+ end
+
+ assert_equal memory.array[0, 70], @asmemu.memory.array[0, 70]
+ end
+ end
+end
+
Please sign in to comment.
Something went wrong with that request. Please try again.