Skip to content
Browse files

be more flexible when recognizing [R+expr].

  • Loading branch information...
1 parent d48cc4e commit d67b463d930b2adb9089b702d2bdd49e4f6e158d @robey committed Feb 27, 2013
View
66 src/d16bunny/assembler.coffee
@@ -96,7 +96,8 @@ class Assembler
reset: ->
# current symbol table for resolving named references
@symtab = {}
- # constants (copied into symtab) which don't change when code size changes
+ # constants (copied into symtab) which don't change when code size changes.
+ # they may still be unresolved (referring to labels).
@constants = {}
@errors = []
@recompile = false
@@ -136,7 +137,20 @@ class Assembler
@error(filename, lineNumber, pos, reason)
if @giveUp() then @error(filename, lineNumber, pos, "Too many errors; giving up.")
null
-
+
+ # do a full two-stage compile of this source.
+ # returns an AssemblerOutput object with:
+ # - errors: list of errors discovered (also reported through @logger)
+ # - lines: the list of compiled line objects. each compiled line is:
+ # - address: memory address of this line
+ # - data: words of compiled data (length may be 0, or quite large for
+ # expanded macros or "dat" blocks)
+ # the 'lines' output array will always be the same length as the 'lines'
+ # input array, but the 'data' field on some lines may be empty if no code
+ # was compiled for that line, or there were too many errors.
+ #
+ # the compiler will try to continue if there are errors, to greedily find
+ # as many of the errors as it can. after 'maxErrors', it will stop.
compile: (textLines, address=0, filename="") ->
plines = @parse(textLines, filename)
if @giveUp() then return new AssemblerOutput(@errors, [], @symtab)
@@ -168,7 +182,7 @@ class Assembler
if typeof dline.data[i] == "object" then dline.data[i] = 0
# make sure everything in the symtab is resolved now.
for k, v of @symtab
- if v instanceof Expression then @symtab[k] = v.evaluate(@symtab)
+ if v instanceof Expression then @symtab[k] = v.evaluate(@symtab) & 0xffff
new AssemblerOutput(@errors, dlines, @symtab)
# ----- parse phase
@@ -178,9 +192,9 @@ class Assembler
parser.debugger = @debugger
@addBuiltinMacros(parser)
plines = @parseChunk(parser, textLines, filename)
- @addConstants(parser.constants)
+ for k, v of parser.constants then @constants[k] = v
# resolve any expressions that can be taken care of by the constants
- for pline in plines then if pline? then pline.foldConstants(@symtab)
+ for pline in plines then if pline? then pline.foldConstants(@constants)
plines
parseChunk: (parser, textLines, filename) ->
@@ -199,26 +213,6 @@ class Assembler
plines.push(pline)
plines
- # given a map of (key -> expr), try to resolve them all and add them into
- # the symtab of (key -> value). throw an error if any are unresolvable.
- addConstants: (constants) ->
- for k, v of constants then @constants[k] = v
- return
-
- unresolved = {}
- for k, v of constants then unresolved[k] = v
- while Object.keys(unresolved).length > 0
- progress = false
- for k, v of unresolved
- if v.resolvable(@constants)
- @constants[k] = v.evaluate(@constants) & 0xffff
- delete unresolved[k]
- progress = true
- if not progress
- for k, v of unresolved
- @process v.filename, v.lineNumber, => v.evaluate(@constants) & 0xffff
- return
-
addBuiltinMacros: (parser) ->
for text, lineNumber in BuiltinMacros.split("\n")
parser.parseLine(text, "(builtins)", lineNumber)
@@ -262,7 +256,6 @@ class Assembler
resolveLine: (dline, transformer=null) ->
@debug "+ resolving @ ", dline.address, ": ", dline
- @debug " in ", @symtab
@symtab["."] = dline.address
@symtab["$"] = dline.address
dline.data = dline.data.map (item) =>
@@ -281,7 +274,7 @@ class Assembler
if operands.length > 0
operand = operands[operands.length - 1]
if operand.checkCompact(@symtab)
- @debug " compacted ", operand
+ @debug " compacted changed on ", operand
# signal that we have to re-run compilation
@recompile = true
if dline.expanded?
@@ -336,24 +329,5 @@ class Assembler
@debug " optimize/add: ", pline
-
-
-
- # do a full two-stage compile of this source.
- # returns an AssemblerOutput object with:
- # - errorCount: number of errors discovered (reported through @logger)
- # - lines: the list of compiled line objects. each compiled line is:
- # - org: memory address of this line
- # - data: words of compiled data (length may be 0, or quite large for
- # expanded macros or "dat" blocks)
- # the 'lines' output array will always be the same length as the 'lines'
- # input array, but the 'data' field on some lines may be empty if no code
- # was compiled for that line, or there were too many errors.
- #
- # the compiler will try to continue if there are errors, to greedily find
- # as many of the errors as it can. after 'maxErrors', it will stop.
-
-
-
exports.DataLine = DataLine
exports.Assembler = Assembler
View
19 src/d16bunny/expression.coffee
@@ -90,6 +90,20 @@ class Expression
else throw new AssemblerError(@text, @pos, "Internal error (undefined binary operator)")
e.toString = -> "(" + @left.toString() + " " + @binary + " " + @right.toString() + ")"
e.resolvable = (symtab={}) -> @left.resolvable(symtab) and @right.resolvable(symtab)
+ e.extractRegister = ->
+ return null unless @binary in [ "+", "-" ]
+ if @left.register?
+ expr = @right
+ if @binary == "-" then expr = Expression::Unary(expr.text, expr.pos, "-", expr)
+ return [ @left.register, expr ]
+ if @binary == "+" and @right.register?
+ return [ @right.register, @left ]
+ [ r, expr ] = @left.extractRegister(true)
+ if r? then return [ r, Expression::Binary(@text, @pos, @binary, expr, @right) ]
+ if @binary == "+"
+ [ r, expr ] = @right.extractRegister()
+ if r? then return [ r, Expression::Binary(@text, @pos, @binary, @left, expr) ]
+ [ null, null ]
e
constructor: (@text, @pos) ->
@@ -106,4 +120,9 @@ class Expression
# that isn't defined in 'symtab' will be an error.
evaluate: (symtab={}) -> throw "must be implemented in objects"
+ # if the expression can boil down to some form of (register + expr), then
+ # extract and return [register, expr]. otherwise, null.
+ extractRegister: -> [ null, null ]
+
+
exports.Expression = Expression
View
18 src/d16bunny/parser.coffee
@@ -394,19 +394,13 @@ class Parser
code = if dereference then Operand.RegisterDereference else Operand.Register
return new Operand(m.pos, code + Dcpu.Registers[expr.register])
# special case: [literal + register]
- if dereference and expr.binary? and (expr.left.register? or expr.right.register?)
- if expr.binary == '+' or (expr.binary == '-' and expr.left.register?)
- register = if expr.left.register? then expr.left.register else expr.right.register
- if not Dcpu.Registers[register]?
+ if dereference
+ [ r, e ] = expr.extractRegister()
+ if r?
+ if not Dcpu.Registers[r]?
line.pointTo(m)
- line.fail "You can't use #{register.toUpperCase()} in [R+n] form"
- op = expr.binary
- expr = if expr.left.register? then expr.right else expr.left
- # allow [R-n]
- if op == '-' then expr = Expression::Unary(expr.text, expr.pos, '-', expr)
- return new Operand(m.pos, Operand.RegisterIndex + Dcpu.Registers[register], expr)
- line.pointTo(m)
- line.fail "Only a register +/- a constant is allowed"
+ line.fail "You can't use #{r.toUpperCase()} in [R+n] form"
+ return new Operand(m.pos, Operand.RegisterIndex + Dcpu.Registers[r], e)
new Operand(m.pos, (if dereference then Operand.ImmediateDereference else Operand.Immediate), expr)
# ----- data
View
5 test/test_assembler.coffee
@@ -106,3 +106,8 @@ describe "Assembler.compileLine", ->
[ dline, symtab ] = compileLine("set 1, a", 0)
dline.toString().should.eql("0x0000: 0x03e1, 0x0001")
+ it "compiles a complex-looking operation", ->
+ [ dline, symtab ] = compileLine("XOR [I+VISFONT+2], [I+FONTXOR1]", 0x200, VISFONT: 0xf000, FONTXOR1: 0xd000, resolve: true)
+ dline.toString().should.eql("0x0200: 0x5acc, 0xd000, 0xf002")
+
+
View
10 test/test_parser.coffee
@@ -157,13 +157,19 @@ describe "Parser", ->
it "parses pointer operations", ->
parseOperand("[0x20 + x]").should.eql("<19, 32>")
html.should.eql("{operator:[}{number:0x20} {operator:+} {register:x}{operator:]}")
- parseOperand("[15+24+i]").should.eql("<22, 39>")
- html.should.eql("{operator:[}{number:15}{operator:+}{number:24}{operator:+}{register:i}{operator:]}")
parseOperand("[a+9]").should.eql("<16, 9>")
html.should.eql("{operator:[}{register:a}{operator:+}{number:9}{operator:]}")
parseOperand("[a-2]").should.eql("<16, 65534>")
html.should.eql("{operator:[}{register:a}{operator:-}{number:2}{operator:]}")
+ it "parses complex pointer operations", ->
+ parseOperand("[15+24+i]").should.eql("<22, 39>")
+ html.should.eql("{operator:[}{number:15}{operator:+}{number:24}{operator:+}{register:i}{operator:]}")
+ parseOperand("[i+15+24]").should.eql("<22, 39>")
+ html.should.eql("{operator:[}{register:i}{operator:+}{number:15}{operator:+}{number:24}{operator:]}")
+ parseOperand("[15+i+24]").should.eql("<22, 39>")
+ html.should.eql("{operator:[}{number:15}{operator:+}{register:i}{operator:+}{number:24}{operator:]}")
+
it "parses pick", ->
parseOperand("pick 23").should.eql("<26, 23>")
html.should.eql("{register:pick} {number:23}")

0 comments on commit d67b463

Please sign in to comment.
Something went wrong with that request. Please try again.