mirror of
https://github.com/notwa/mm
synced 2025-02-05 05:23:22 -08:00
update lips; use new features
This commit is contained in:
parent
5e684e6aeb
commit
5fbb56f472
14 changed files with 731 additions and 403 deletions
|
@ -108,6 +108,7 @@ local function inject(fn)
|
|||
dprint(("%08X"):format(pos), line)
|
||||
pos = pos % 0x80000000
|
||||
size = size + 1
|
||||
-- FIXME: doesn't detect .skip/.space directives
|
||||
if pos > cons_pos and (pos < inject_end or cons_pos == pos - 1) then
|
||||
cons_pos = pos
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ dpad_control:
|
|||
dpad_values:
|
||||
// use table of values for branchless operation
|
||||
.byte 0, 1, -1, 0
|
||||
.byte -16, -8, -32, -16
|
||||
.byte +16, +32, +8, +16
|
||||
.byte -16, -4, -64, -16
|
||||
.byte +16, +64, +4, +16
|
||||
.byte 0, 1, -1, 0
|
||||
.align
|
||||
|
|
|
@ -34,10 +34,10 @@
|
|||
jpop 4, 1, ra
|
||||
|
||||
fmt:
|
||||
.byte 0x25,0x73,0x00 // %s
|
||||
.asciiz "%s"
|
||||
.align
|
||||
str:
|
||||
.byte 0x68,0x65,0x79,0x00 // hey
|
||||
.asciiz "hey"
|
||||
.align
|
||||
|
||||
.include "simple text.asm"
|
||||
|
@ -72,10 +72,9 @@ spawned:
|
|||
buffer_pos:
|
||||
.word 0
|
||||
|
||||
// we'll just let this overflow
|
||||
.align 8
|
||||
.align 4
|
||||
buffer:
|
||||
.word 0
|
||||
.skip 0x3000
|
||||
|
||||
// overwrite (not hook) the debug printing function
|
||||
.org 0x800021B0
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
[button_R]: 0x0010
|
||||
[button_any]: 0x0F20
|
||||
|
||||
[min_actor_no]: 0
|
||||
|
||||
[hold_delay_amount]: 3
|
||||
|
||||
push 4, s0, s1, s2, s3, s4, ra,
|
||||
|
@ -40,7 +42,7 @@
|
|||
mov a0, s3
|
||||
andi s3, v0, 0xFFFF
|
||||
+: // set min/max on actor number
|
||||
subi t4, s0, 1
|
||||
subi t4, s0, @min_actor_no
|
||||
bgez t4, +
|
||||
nop
|
||||
li s0, @max_actor_no
|
||||
|
@ -48,7 +50,7 @@
|
|||
subi t4, s0, @max_actor_no
|
||||
blez t4, +
|
||||
nop
|
||||
li s0, 1
|
||||
li s0, @min_actor_no
|
||||
+: // spawn
|
||||
andi t3, s2, @button_L
|
||||
beqz t3, return
|
||||
|
@ -87,7 +89,7 @@ selected:
|
|||
.word 0
|
||||
|
||||
fmt:
|
||||
.byte 0x25,0x30,0x34,0x58,0x00 // %04X
|
||||
.asciiz "%04X"
|
||||
.align
|
||||
|
||||
.include "dpad control.asm"
|
||||
|
@ -97,7 +99,24 @@ fmt:
|
|||
hold_delay:
|
||||
.word 0
|
||||
|
||||
object_spawn_wrap:
|
||||
// a0: object table
|
||||
// a1: object number
|
||||
beqz a0, +
|
||||
nop
|
||||
beqi a0, 1, +
|
||||
nop
|
||||
beqi a0, 2, +
|
||||
nop
|
||||
jal @object_spawn
|
||||
nop
|
||||
+:
|
||||
jr
|
||||
nop
|
||||
|
||||
.org @object_index
|
||||
// a0: object table
|
||||
// a1: object number
|
||||
// we have space for 22 instructions (on debug, 23 on 1.0?)
|
||||
push 4, ra, 1
|
||||
mov t0, a0
|
||||
|
@ -116,8 +135,6 @@ hold_delay:
|
|||
addi t0, t0, 68
|
||||
bnez t1, -
|
||||
nop
|
||||
// NOTE: this allows object 0002 to load in places it's not meant to.
|
||||
// this can mess up door graphics (among other things?)
|
||||
jal @object_spawn
|
||||
nop
|
||||
//subiu v0, r0, -1 // original code
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
local insert = table.insert
|
||||
local floor = math.floor
|
||||
local format = string.format
|
||||
local insert = table.insert
|
||||
|
||||
local data = require "lips.data"
|
||||
local util = require "lips.util"
|
||||
|
||||
local function bitrange(x, lower, upper)
|
||||
return floor(x/2^lower) % 2^(upper - lower + 1)
|
||||
end
|
||||
local bitrange = util.bitrange
|
||||
|
||||
local Dumper = require("lips.Class")()
|
||||
local Dumper = util.Class()
|
||||
function Dumper:init(writer, fn, options)
|
||||
self.writer = writer
|
||||
self.fn = fn or '(string)'
|
||||
|
@ -32,16 +32,16 @@ function Dumper:push_instruction(t)
|
|||
self:advance(4)
|
||||
end
|
||||
|
||||
function Dumper:add_instruction_j(line, o, T)
|
||||
self:push_instruction{line=line, o, T}
|
||||
function Dumper:add_instruction_j(fn, line, o, T)
|
||||
self:push_instruction{fn=fn, line=line, o, T}
|
||||
end
|
||||
|
||||
function Dumper:add_instruction_i(line, o, s, t, i)
|
||||
self:push_instruction{line=line, o, s, t, i}
|
||||
function Dumper:add_instruction_i(fn, line, o, s, t, i)
|
||||
self:push_instruction{fn=fn, line=line, o, s, t, i}
|
||||
end
|
||||
|
||||
function Dumper:add_instruction_r(line, o, s, t, d, f, c)
|
||||
self:push_instruction{line=line, o, s, t, d, f, c}
|
||||
function Dumper:add_instruction_r(fn, line, o, s, t, d, f, c)
|
||||
self:push_instruction{fn=fn, line=line, o, s, t, d, f, c}
|
||||
end
|
||||
|
||||
function Dumper:add_label(name)
|
||||
|
@ -57,6 +57,8 @@ function Dumper:add_bytes(line, ...)
|
|||
t = {}
|
||||
t.kind = 'bytes'
|
||||
t.size = 0
|
||||
t.fn = self.fn
|
||||
t.line = self.line
|
||||
end
|
||||
t.line = line
|
||||
for _, b in ipairs{...} do
|
||||
|
@ -69,9 +71,12 @@ function Dumper:add_bytes(line, ...)
|
|||
self:advance(t.size)
|
||||
end
|
||||
|
||||
function Dumper:add_directive(line, name, a, b)
|
||||
function Dumper:add_directive(fn, line, name, a, b)
|
||||
self.fn = fn
|
||||
self.line = line
|
||||
local t = {}
|
||||
t.line = line
|
||||
t.fn = self.fn
|
||||
t.line = self.line
|
||||
if name == 'BYTE' then
|
||||
self:add_bytes(line, a % 0x100)
|
||||
elseif name == 'HALFWORD' then
|
||||
|
@ -79,11 +84,17 @@ function Dumper:add_directive(line, name, a, b)
|
|||
local b1 = bitrange(a, 8, 15)
|
||||
self:add_bytes(line, b1, b0)
|
||||
elseif name == 'WORD' then
|
||||
local b0 = bitrange(a, 0, 7)
|
||||
local b1 = bitrange(a, 8, 15)
|
||||
local b2 = bitrange(a, 16, 23)
|
||||
local b3 = bitrange(a, 24, 31)
|
||||
self:add_bytes(line, b3, b2, b1, b0)
|
||||
if type(a) == 'string' then
|
||||
local t = {line=line, kind='label', name=a}
|
||||
insert(self.commands, t)
|
||||
self:advance(4)
|
||||
else
|
||||
local b0 = bitrange(a, 0, 7)
|
||||
local b1 = bitrange(a, 8, 15)
|
||||
local b2 = bitrange(a, 16, 23)
|
||||
local b3 = bitrange(a, 24, 31)
|
||||
self:add_bytes(line, b3, b2, b1, b0)
|
||||
end
|
||||
elseif name == 'ORG' then
|
||||
t.kind = 'goto'
|
||||
t.addr = a
|
||||
|
@ -92,11 +103,13 @@ function Dumper:add_directive(line, name, a, b)
|
|||
self:advance(0)
|
||||
elseif name == 'ALIGN' then
|
||||
t.kind = 'ahead'
|
||||
local align = a*2
|
||||
if align == 0 then
|
||||
local align
|
||||
if a == 0 then
|
||||
align = 4
|
||||
elseif align < 0 then
|
||||
elseif a < 0 then
|
||||
self:error('negative alignment')
|
||||
else
|
||||
align = 2^a
|
||||
end
|
||||
local temp = self.pos + align - 1
|
||||
t.skip = temp - (temp % align) - self.pos
|
||||
|
@ -114,18 +127,20 @@ function Dumper:add_directive(line, name, a, b)
|
|||
end
|
||||
end
|
||||
|
||||
function Dumper:desym(tok)
|
||||
-- FIXME: errors can give wrong filename, also off by one
|
||||
if type(tok[2]) == 'number' then
|
||||
return tok[2]
|
||||
elseif tok[1] == 'LABELSYM' then
|
||||
local label = self.labels[tok[2]]
|
||||
function Dumper:desym(t)
|
||||
if type(t.tok) == 'number' then
|
||||
return t.tok
|
||||
elseif t.tt == 'REG' then
|
||||
assert(data.all_registers[t.tok], 'Internal Error: unknown register')
|
||||
return data.registers[t.tok] or data.fpu_registers[t.tok] or data.sys_registers[t.tok]
|
||||
elseif t.tt == 'LABELSYM' then
|
||||
local label = self.labels[t.tok]
|
||||
if label == nil then
|
||||
self:error('undefined label')
|
||||
end
|
||||
return label
|
||||
elseif tok[1] == 'LABELREL' then
|
||||
local label = self.labels[tok[2]]
|
||||
elseif t.tt == 'LABELREL' then
|
||||
local label = self.labels[t.tok]
|
||||
if label == nil then
|
||||
self:error('undefined label')
|
||||
end
|
||||
|
@ -137,57 +152,43 @@ function Dumper:desym(tok)
|
|||
end
|
||||
return rel % 0x10000
|
||||
end
|
||||
self:error('failed to desym') -- internal error?
|
||||
error('Internal Error: failed to desym')
|
||||
end
|
||||
|
||||
function Dumper:toval(tok)
|
||||
if tok == nil then
|
||||
self:error('nil value')
|
||||
elseif type(tok) == 'number' then
|
||||
return tok
|
||||
elseif data.all_registers[tok] then
|
||||
return data.registers[tok] or data.fpu_registers[tok] or data.sys_registers[tok]
|
||||
function Dumper:toval(t)
|
||||
assert(type(t) == 'table', 'Internal Error: invalid value')
|
||||
|
||||
local val = self:desym(t)
|
||||
|
||||
if t.index then
|
||||
val = val % 0x80000000
|
||||
val = floor(val/4)
|
||||
end
|
||||
if type(tok) == 'table' then
|
||||
if #tok ~= 2 then
|
||||
self:error('invalid token')
|
||||
end
|
||||
if tok[1] == 'UPPER' then
|
||||
local val = self:desym(tok[2])
|
||||
return bitrange(val, 16, 31)
|
||||
elseif tok[1] == 'LOWER' then
|
||||
local val = self:desym(tok[2])
|
||||
return bitrange(val, 0, 15)
|
||||
elseif tok[1] == 'UPPEROFF' then
|
||||
local val = self:desym(tok[2])
|
||||
local upper = bitrange(val, 16, 31)
|
||||
local lower = bitrange(val, 0, 15)
|
||||
if lower >= 0x8000 then
|
||||
-- accommodate for offsets being signed
|
||||
upper = (upper + 1) % 0x10000
|
||||
end
|
||||
return upper
|
||||
elseif tok[1] == 'SIGNED' then
|
||||
local val = self:desym(tok[2])
|
||||
if val >= 0x10000 or val < -0x8000 then
|
||||
self:error('value out of range')
|
||||
end
|
||||
return val % 0x10000
|
||||
elseif tok[1] == 'NEGATE' then
|
||||
local val = -self:desym(tok[2])
|
||||
if val >= 0x10000 or val < -0x8000 then
|
||||
self:error('value out of range')
|
||||
end
|
||||
return val % 0x10000
|
||||
elseif tok[1] == 'INDEX' then
|
||||
local val = self:desym(tok[2]) % 0x80000000
|
||||
val = floor(val/4)
|
||||
return val
|
||||
else
|
||||
return self:desym(tok)
|
||||
end
|
||||
if t.negate then
|
||||
val = -val
|
||||
end
|
||||
self:error('invalid value') -- internal error?
|
||||
if t.negate or t.signed then
|
||||
if val >= 0x10000 or val < -0x8000 then
|
||||
self:error('value out of range')
|
||||
end
|
||||
val = val % 0x10000
|
||||
end
|
||||
|
||||
if t.portion == 'upper' then
|
||||
val = bitrange(val, 16, 31)
|
||||
elseif t.portion == 'lower' then
|
||||
val = bitrange(val, 0, 15)
|
||||
elseif t.portion == 'upperoff' then
|
||||
local upper = bitrange(val, 16, 31)
|
||||
local lower = bitrange(val, 0, 15)
|
||||
if lower >= 0x8000 then
|
||||
-- accommodate for offsets being signed
|
||||
upper = (upper + 1) % 0x10000
|
||||
end
|
||||
val = upper
|
||||
end
|
||||
|
||||
return val
|
||||
end
|
||||
|
||||
function Dumper:validate(n, bits)
|
||||
|
@ -200,8 +201,8 @@ function Dumper:validate(n, bits)
|
|||
end
|
||||
end
|
||||
|
||||
function Dumper:valvar(tok, bits)
|
||||
local val = self:toval(tok)
|
||||
function Dumper:valvar(t, bits)
|
||||
local val = self:toval(t)
|
||||
self:validate(val, bits)
|
||||
return val
|
||||
end
|
||||
|
@ -236,7 +237,7 @@ function Dumper:dump_instruction(t)
|
|||
lw = lw + self:valvar(t[5], 5)*0x40
|
||||
lw = lw + self:valvar(t[6], 6)
|
||||
else
|
||||
error('Internal Error: unknown n-size', 1)
|
||||
error('Internal Error: unknown n-size')
|
||||
end
|
||||
|
||||
return uw, lw
|
||||
|
@ -245,9 +246,9 @@ end
|
|||
function Dumper:dump()
|
||||
self.pos = self.options.offset or 0
|
||||
for i, t in ipairs(self.commands) do
|
||||
if t.line == nil then
|
||||
error('Internal Error: no line number available')
|
||||
end
|
||||
assert(t.fn, 'Internal Error: no file name available')
|
||||
assert(t.line, 'Internal Error: no line number available')
|
||||
self.fn = t.fn
|
||||
self.line = t.line
|
||||
if t.kind == 'instruction' then
|
||||
local uw, lw = self:dump_instruction(t)
|
||||
|
@ -268,8 +269,16 @@ function Dumper:dump()
|
|||
else
|
||||
self.pos = self.pos + t.skip
|
||||
end
|
||||
elseif t.kind == 'label' then
|
||||
local val = self:desym{'LABELSYM', t.name}
|
||||
val = (val % 0x80000000) + 0x80000000
|
||||
local b0 = bitrange(val, 0, 7)
|
||||
local b1 = bitrange(val, 8, 15)
|
||||
local b2 = bitrange(val, 16, 23)
|
||||
local b3 = bitrange(val, 24, 31)
|
||||
self:write{b3, b2, b1, b0}
|
||||
else
|
||||
error('Internal Error: unknown command', 1)
|
||||
error('Internal Error: unknown command')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
local byte = string.byte
|
||||
local char = string.char
|
||||
local find = string.find
|
||||
local open = io.open
|
||||
local format = string.format
|
||||
local insert = table.insert
|
||||
|
||||
local data = require "lips.data"
|
||||
local util = require "lips.util"
|
||||
|
||||
local function readfile(fn)
|
||||
local f = open(fn, 'r')
|
||||
if not f then
|
||||
error('could not open assembly file for reading: '..tostring(fn), 2)
|
||||
end
|
||||
local asm = f:read('*a')
|
||||
f:close()
|
||||
return asm
|
||||
end
|
||||
local simple_escapes = {
|
||||
['0'] = 0x00,
|
||||
['\\'] = 0x5C,
|
||||
['"'] = 0x22,
|
||||
['a'] = 0x07,
|
||||
['b'] = 0x08,
|
||||
['f'] = 0x0C,
|
||||
['n'] = 0x0A,
|
||||
['r'] = 0x0D,
|
||||
['t'] = 0x09,
|
||||
['v'] = 0x0B,
|
||||
}
|
||||
|
||||
local Lexer = require("lips.Class")()
|
||||
local Lexer = util.Class()
|
||||
function Lexer:init(asm, fn, options)
|
||||
self.asm = asm
|
||||
self.fn = fn or '(string)'
|
||||
|
@ -139,8 +144,8 @@ function Lexer:lex_hex(yield)
|
|||
local entered = false
|
||||
while true do
|
||||
if self.chr == '\n' then
|
||||
self:nextc()
|
||||
yield('EOL', '\n')
|
||||
self:nextc()
|
||||
elseif self.ord == self.EOF then
|
||||
self:error('unexpected EOF; incomplete hex directive')
|
||||
elseif self.chr == ';' then
|
||||
|
@ -191,8 +196,8 @@ end
|
|||
function Lexer:lex_block_comment(yield)
|
||||
while true do
|
||||
if self.chr == '\n' then
|
||||
self:nextc()
|
||||
yield('EOL', '\n')
|
||||
self:nextc()
|
||||
elseif self.ord == self.EOF then
|
||||
self:error('unexpected EOF; incomplete block comment')
|
||||
elseif self.chrchr == '*/' then
|
||||
|
@ -206,14 +211,49 @@ function Lexer:lex_block_comment(yield)
|
|||
end
|
||||
|
||||
function Lexer:lex_string(yield)
|
||||
-- TODO: support escaping
|
||||
if self.chr ~= '"' then
|
||||
self:error("expected opening double quote")
|
||||
self:error('expected opening double quote')
|
||||
end
|
||||
self:nextc()
|
||||
|
||||
local bytes = {}
|
||||
while true do
|
||||
if self.chr == '\n' then
|
||||
self:error('unimplemented')
|
||||
yield('EOL', '\n')
|
||||
self:nextc()
|
||||
elseif self.ord == self.EOF then
|
||||
self:nextc()
|
||||
self:error('unexpected EOF; incomplete string')
|
||||
elseif self.chr == '"' then
|
||||
self:nextc()
|
||||
break
|
||||
elseif self.chr == '\\' then
|
||||
self:nextc()
|
||||
local simple = simple_escapes[self.chr]
|
||||
if simple then
|
||||
insert(bytes, simple)
|
||||
else
|
||||
self:error('unknown escape sequence')
|
||||
end
|
||||
self:nextc()
|
||||
else
|
||||
insert(bytes, byte(self.chr))
|
||||
self:nextc()
|
||||
end
|
||||
end
|
||||
|
||||
yield('STRING', bytes)
|
||||
end
|
||||
|
||||
function Lexer:lex_string_naive(yield) -- no escape sequences
|
||||
if self.chr ~= '"' then
|
||||
self:error('expected opening double quote')
|
||||
end
|
||||
self:nextc()
|
||||
local buff = self:read_chars('[^"\n]')
|
||||
if self.chr ~= '"' then
|
||||
self:error("expected closing double quote")
|
||||
self:error('expected closing double quote')
|
||||
end
|
||||
self:nextc()
|
||||
yield('STRING', buff)
|
||||
|
@ -222,13 +262,13 @@ end
|
|||
function Lexer:lex_include(_yield)
|
||||
self:read_chars('%s')
|
||||
local fn
|
||||
self:lex_string(function(tt, tok)
|
||||
self:lex_string_naive(function(tt, tok)
|
||||
fn = tok
|
||||
end)
|
||||
if self.options.path then
|
||||
fn = self.options.path..fn
|
||||
end
|
||||
local sublexer = Lexer(readfile(fn), fn, self.options)
|
||||
local sublexer = Lexer(util.readfile(fn), fn, self.options)
|
||||
sublexer:lex(_yield)
|
||||
end
|
||||
|
||||
|
@ -238,8 +278,8 @@ function Lexer:lex(_yield)
|
|||
end
|
||||
while true do
|
||||
if self.chr == '\n' then
|
||||
self:nextc()
|
||||
yield('EOL', '\n')
|
||||
self:nextc()
|
||||
elseif self.ord == self.EOF then
|
||||
yield('EOF', self.EOF)
|
||||
break
|
||||
|
@ -268,24 +308,23 @@ function Lexer:lex(_yield)
|
|||
end
|
||||
self:nextc()
|
||||
yield('DEF', buff)
|
||||
elseif self.chr == ']' then
|
||||
self:error('unmatched closing bracket')
|
||||
elseif self.chr == '(' then
|
||||
self:nextc()
|
||||
local buff = self:read_chars('[%w_]')
|
||||
if self.chr ~= ')' then
|
||||
self:error('invalid register name')
|
||||
end
|
||||
yield('OPEN', '(')
|
||||
elseif self.chr == ')' then
|
||||
self:nextc()
|
||||
local up = buff:upper()
|
||||
if not data.all_registers[up] then
|
||||
self:error('not a register')
|
||||
end
|
||||
yield('DEREF', up)
|
||||
yield('CLOSE', ')')
|
||||
elseif self.chr == '.' then
|
||||
self:nextc()
|
||||
local buff = self:read_chars('[%w]')
|
||||
local up = buff:upper()
|
||||
if data.directive_aliases[up] then
|
||||
up = data.directive_aliases[up]
|
||||
end
|
||||
if not data.all_directives[up] then
|
||||
self:error('not a directive')
|
||||
self:error('unknown directive')
|
||||
end
|
||||
if up == 'INC' or up == 'INCASM' or up == 'INCLUDE' then
|
||||
yield('DIR', 'INC')
|
||||
|
@ -293,10 +332,16 @@ function Lexer:lex(_yield)
|
|||
else
|
||||
yield('DIR', up)
|
||||
end
|
||||
elseif self.chr == '"' then
|
||||
self:lex_string(yield)
|
||||
elseif self.chr == '@' then
|
||||
self:nextc()
|
||||
local buff = self:read_chars('[%w_]')
|
||||
yield('DEFSYM', buff)
|
||||
elseif self.chr == '%' then
|
||||
self:nextc()
|
||||
local call = self:read_chars('[%w_]')
|
||||
yield('SPECIAL', call)
|
||||
elseif self.chr:find('[%a_]') then
|
||||
local buff = self:read_chars('[%w_.]')
|
||||
local up = buff:upper()
|
||||
|
@ -318,10 +363,6 @@ function Lexer:lex(_yield)
|
|||
end
|
||||
yield('LABELSYM', buff)
|
||||
end
|
||||
elseif self.chr == ']' then
|
||||
self:error('unmatched closing bracket')
|
||||
elseif self.chr == ')' then
|
||||
self:error('unmatched closing parenthesis')
|
||||
elseif self.chr == '+' or self.chr == '-' then
|
||||
local sign_chr = self.chr
|
||||
local sign = sign_chr == '+' and 1 or -1
|
||||
|
|
157
Lua/lib/lips/Muncher.lua
Normal file
157
Lua/lib/lips/Muncher.lua
Normal file
|
@ -0,0 +1,157 @@
|
|||
local format = string.format
|
||||
local insert = table.insert
|
||||
|
||||
local data = require "lips.data"
|
||||
local util = require "lips.util"
|
||||
local Token = require "lips.Token"
|
||||
|
||||
local arg_types = {
|
||||
NUM = true,
|
||||
REG = true,
|
||||
DEFSYM = true,
|
||||
LABELSYM = true,
|
||||
RELLABELSYM = true,
|
||||
}
|
||||
|
||||
local Muncher = util.Class()
|
||||
-- no base init method
|
||||
|
||||
function Muncher:error(msg)
|
||||
error(format('%s:%d: Error: %s', self.fn, self.line, msg), 2)
|
||||
end
|
||||
|
||||
function Muncher:token(t, val)
|
||||
-- note: call Token directly if you want to specify fn and line manually
|
||||
if type(t) == 'table' then
|
||||
t.fn = self.fn
|
||||
t.line = self.line
|
||||
return Token(t)
|
||||
else
|
||||
return Token(self.fn, self.line, t, val)
|
||||
end
|
||||
end
|
||||
|
||||
function Muncher:advance()
|
||||
self.i = self.i + 1
|
||||
self.t = self.tokens[self.i]
|
||||
self.tt = self.t.tt
|
||||
self.tok = self.t.tok
|
||||
self.fn = self.t.fn
|
||||
self.line = self.t.line
|
||||
return self.t
|
||||
end
|
||||
|
||||
function Muncher:is_EOL()
|
||||
return self.tt == 'EOL' or self.tt == 'EOF'
|
||||
end
|
||||
|
||||
function Muncher:expect_EOL()
|
||||
if self:is_EOL() then
|
||||
self:advance()
|
||||
return
|
||||
end
|
||||
self:error('expected end of line')
|
||||
end
|
||||
|
||||
function Muncher:optional_comma()
|
||||
if self.tt == 'SEP' and self.tok == ',' then
|
||||
self:advance()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Muncher:number()
|
||||
if self.tt ~= 'NUM' then
|
||||
self:error('expected number')
|
||||
end
|
||||
local t = self.t
|
||||
self:advance()
|
||||
return self:token(t)
|
||||
end
|
||||
|
||||
function Muncher:string()
|
||||
if self.tt ~= 'STRING' then
|
||||
self:error('expected string')
|
||||
end
|
||||
local t = self.t
|
||||
self:advance()
|
||||
return self:token(t)
|
||||
end
|
||||
|
||||
function Muncher:register(registers)
|
||||
registers = registers or data.registers
|
||||
if self.tt ~= 'REG' then
|
||||
self:error('expected register')
|
||||
end
|
||||
local t = self.t
|
||||
if not registers[t.tok] then
|
||||
self:error('wrong type of register')
|
||||
end
|
||||
self:advance()
|
||||
return self:token(t)
|
||||
end
|
||||
|
||||
function Muncher:deref()
|
||||
if self.tt ~= 'OPEN' then
|
||||
self:error('expected opening parenthesis for dereferencing')
|
||||
end
|
||||
self:advance()
|
||||
if self.tt ~= 'REG' then
|
||||
self:error('expected register to dereference')
|
||||
end
|
||||
local t = self.t
|
||||
self:advance()
|
||||
if self.tt ~= 'CLOSE' then
|
||||
self:error('expected closing parenthesis for dereferencing')
|
||||
end
|
||||
self:advance()
|
||||
return self:token(t)
|
||||
end
|
||||
|
||||
function Muncher:const(relative, no_label)
|
||||
if self.tt ~= 'NUM' and self.tt ~= 'LABELSYM' then
|
||||
self:error('expected constant')
|
||||
end
|
||||
if no_label and self.tt == 'LABELSYM' then
|
||||
self:error('labels are not allowed here')
|
||||
end
|
||||
local t = self:token(self.t)
|
||||
if relative and self.tt == 'LABELSYM' then
|
||||
t.tt = 'LABELREL'
|
||||
end
|
||||
self:advance()
|
||||
return t
|
||||
end
|
||||
|
||||
function Muncher:special()
|
||||
if self.tt ~= 'SPECIAL' then
|
||||
self:error('expected special name to call')
|
||||
end
|
||||
local name = self.tok
|
||||
self:advance()
|
||||
if self.tt ~= 'OPEN' then
|
||||
self:error('expected opening parenthesis for special call')
|
||||
end
|
||||
|
||||
local args = {}
|
||||
while true do
|
||||
local arg = self:advance()
|
||||
if not arg_types[arg.tt] then
|
||||
self:error('invalid argument type')
|
||||
else
|
||||
self:advance()
|
||||
end
|
||||
if self.tt == 'SEP' then
|
||||
insert(args, arg)
|
||||
elseif self.tt == 'CLOSE' then
|
||||
insert(args, arg)
|
||||
break
|
||||
else
|
||||
self:error('unexpected token in argument list')
|
||||
end
|
||||
end
|
||||
|
||||
return name, args
|
||||
end
|
||||
|
||||
return Muncher
|
|
@ -1,139 +1,79 @@
|
|||
local insert = table.insert
|
||||
local format = string.format
|
||||
|
||||
local data = require "lips.data"
|
||||
local util = require "lips.util"
|
||||
local overrides = require "lips.overrides"
|
||||
local Dumper = require "lips.Dumper"
|
||||
local Token = require "lips.Token"
|
||||
local Lexer = require "lips.Lexer"
|
||||
local Dumper = require "lips.Dumper"
|
||||
local Muncher = require "lips.Muncher"
|
||||
local Preproc = require "lips.Preproc"
|
||||
|
||||
local Parser = require("lips.Class")()
|
||||
local Parser = util.Class(Muncher)
|
||||
function Parser:init(writer, fn, options)
|
||||
self.fn = fn or '(string)'
|
||||
self.main_fn = self.fn
|
||||
self.options = options or {}
|
||||
self.dumper = Dumper(writer, fn, options)
|
||||
self.defines = {}
|
||||
end
|
||||
|
||||
function Parser:error(msg)
|
||||
error(format('%s:%d: Error: %s', self.fn, self.line, msg), 2)
|
||||
end
|
||||
|
||||
function Parser:advance()
|
||||
self.i = self.i + 1
|
||||
local t = self.tokens[self.i]
|
||||
self.tt = t.tt
|
||||
self.tok = t.tok
|
||||
self.fn = t.fn
|
||||
self.line = t.line
|
||||
return t.tt, t.tok
|
||||
end
|
||||
|
||||
function Parser:is_EOL()
|
||||
return self.tt == 'EOL' or self.tt == 'EOF'
|
||||
end
|
||||
|
||||
function Parser:expect_EOL()
|
||||
if self:is_EOL() then
|
||||
self:advance()
|
||||
return
|
||||
end
|
||||
self:error('expected end of line')
|
||||
end
|
||||
|
||||
function Parser:optional_comma()
|
||||
if self.tt == 'SEP' and self.tok == ',' then
|
||||
self:advance()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function Parser:number()
|
||||
if self.tt ~= 'NUM' then
|
||||
self:error('expected number')
|
||||
end
|
||||
local value = self.tok
|
||||
self:advance()
|
||||
return value
|
||||
end
|
||||
|
||||
function Parser:directive()
|
||||
local name = self.tok
|
||||
self:advance()
|
||||
local line = self.line
|
||||
local function add(...)
|
||||
self.dumper:add_directive(self.fn, self.line, ...)
|
||||
end
|
||||
if name == 'ORG' then
|
||||
self.dumper:add_directive(line, name, self:number())
|
||||
add(name, self:number().tok)
|
||||
elseif name == 'ALIGN' or name == 'SKIP' then
|
||||
if self:is_EOL() and name == 'ALIGN' then
|
||||
self.dumper:add_directive(line, name, 0)
|
||||
add(name, 0)
|
||||
else
|
||||
local size = self:number()
|
||||
if self:is_EOL() then
|
||||
self.dumper:add_directive(line, name, size)
|
||||
add(name, size)
|
||||
else
|
||||
self:optional_comma()
|
||||
self.dumper:add_directive(line, name, size, self:number())
|
||||
add(name, size, self:number().tok)
|
||||
end
|
||||
self:expect_EOL()
|
||||
end
|
||||
elseif name == 'BYTE' or name == 'HALFWORD' or name == 'WORD' then
|
||||
self.dumper:add_directive(line, name, self:number())
|
||||
elseif name == 'BYTE' or name == 'HALFWORD' then
|
||||
add(name, self:number().tok)
|
||||
while not self:is_EOL() do
|
||||
self:advance()
|
||||
self:optional_comma()
|
||||
self.dumper:add_directive(line, name, self:number())
|
||||
add(name, self:number().tok)
|
||||
end
|
||||
self:expect_EOL()
|
||||
elseif name == 'WORD' then -- allow labels in word directives
|
||||
add(name, self:const().tok)
|
||||
while not self:is_EOL() do
|
||||
self:advance()
|
||||
self:optional_comma()
|
||||
add(name, self:const().tok)
|
||||
end
|
||||
self:expect_EOL()
|
||||
elseif name == 'HEX' then
|
||||
self:error('unimplemented')
|
||||
elseif name == 'INC' then
|
||||
-- noop
|
||||
-- noop, handled by lexer
|
||||
elseif name == 'ASCII' or name == 'ASCIIZ' then
|
||||
local bytes = self:string()
|
||||
for i, number in ipairs(bytes.tok) do
|
||||
add('BYTE', number)
|
||||
end
|
||||
if name == 'ASCIIZ' then
|
||||
add('BYTE', 0)
|
||||
end
|
||||
self:expect_EOL()
|
||||
elseif name == 'INCBIN' then
|
||||
self:error('unimplemented')
|
||||
elseif name == 'FLOAT' or name == 'ASCII' or name == 'ASCIIZ' then
|
||||
elseif name == 'FLOAT' then
|
||||
self:error('unimplemented')
|
||||
else
|
||||
self:error('unknown directive')
|
||||
end
|
||||
end
|
||||
|
||||
function Parser:register(t)
|
||||
t = t or data.registers
|
||||
if self.tt ~= 'REG' then
|
||||
self:error('expected register')
|
||||
end
|
||||
local reg = self.tok
|
||||
if not t[reg] then
|
||||
self:error('wrong type of register')
|
||||
end
|
||||
self:advance()
|
||||
return reg
|
||||
end
|
||||
|
||||
function Parser:deref()
|
||||
if self.tt ~= 'DEREF' then
|
||||
self:error('expected register to dereference')
|
||||
end
|
||||
local reg = self.tok
|
||||
self:advance()
|
||||
return reg
|
||||
end
|
||||
|
||||
function Parser:const(relative, no_label)
|
||||
if self.tt ~= 'NUM' and self.tt ~= 'LABELSYM' then
|
||||
self:error('expected constant')
|
||||
end
|
||||
if no_label and self.tt == 'LABELSYM' then
|
||||
self:error('labels are not allowed here')
|
||||
end
|
||||
if relative and self.tt == 'LABELSYM' then
|
||||
self.tt = 'LABELREL'
|
||||
end
|
||||
local t = {self.tt, self.tok}
|
||||
self:advance()
|
||||
return t
|
||||
end
|
||||
|
||||
function Parser:format_in(informat)
|
||||
local args = {}
|
||||
for i=1,#informat do
|
||||
|
@ -146,33 +86,33 @@ function Parser:format_in(informat)
|
|||
elseif c == 't' and not args.rt then
|
||||
args.rt = self:register()
|
||||
elseif c == 'D' and not args.fd then
|
||||
args.fd = self:register(fpu_registers)
|
||||
args.fd = self:register(data.fpu_registers)
|
||||
elseif c == 'S' and not args.fs then
|
||||
args.fs = self:register(fpu_registers)
|
||||
args.fs = self:register(data.fpu_registers)
|
||||
elseif c == 'T' and not args.ft then
|
||||
args.ft = self:register(fpu_registers)
|
||||
args.ft = self:register(data.fpu_registers)
|
||||
elseif c == 'X' and not args.rd then
|
||||
args.rd = self:register(sys_registers)
|
||||
args.rd = self:register(data.sys_registers)
|
||||
elseif c == 'Y' and not args.rs then
|
||||
args.rs = self:register(sys_registers)
|
||||
args.rs = self:register(data.sys_registers)
|
||||
elseif c == 'Z' and not args.rt then
|
||||
args.rt = self:register(sys_registers)
|
||||
args.rt = self:register(data.sys_registers)
|
||||
elseif c == 'o' and not args.offset then
|
||||
args.offset = {'SIGNED', self:const()}
|
||||
args.offset = Token(self:const()):set('signed')
|
||||
elseif c == 'r' and not args.offset then
|
||||
args.offset = {'SIGNED', self:const('relative')}
|
||||
args.offset = Token(self:const('relative')):set('signed')
|
||||
elseif c == 'i' and not args.immediate then
|
||||
args.immediate = self:const(nil, 'no label')
|
||||
elseif c == 'I' and not args.index then
|
||||
args.index = {'INDEX', self:const()}
|
||||
args.index = Token(self:const()):set('index')
|
||||
elseif c == 'k' and not args.immediate then
|
||||
args.immediate = {'NEGATE', self:const(nil, 'no label')}
|
||||
args.immediate = Token(self:const(nil, 'no label')):set('negate')
|
||||
elseif c == 'K' and not args.immediate then
|
||||
args.immediate = {'SIGNED', self:const(nil, 'no label')}
|
||||
args.immediate = Token(self:const(nil, 'no label')):set('signed')
|
||||
elseif c == 'b' and not args.base then
|
||||
args.base = self:deref()
|
||||
else
|
||||
error('Internal Error: invalid input formatting string', 1)
|
||||
error('Internal Error: invalid input formatting string')
|
||||
end
|
||||
if c2:find('[dstDSTorIikKXYZ]') then
|
||||
self:optional_comma()
|
||||
|
@ -191,38 +131,38 @@ function Parser:format_out_raw(outformat, first, args, const, formatconst)
|
|||
for i=1,#outformat do
|
||||
local c = outformat:sub(i, i)
|
||||
if c == 'd' then
|
||||
out[#out+1] = args.rd
|
||||
out[#out+1] = self:token(args.rd)
|
||||
elseif c == 's' then
|
||||
out[#out+1] = args.rs
|
||||
out[#out+1] = self:token(args.rs)
|
||||
elseif c == 't' then
|
||||
out[#out+1] = args.rt
|
||||
out[#out+1] = self:token(args.rt)
|
||||
elseif c == 'D' then
|
||||
out[#out+1] = args.fd
|
||||
out[#out+1] = self:token(args.fd)
|
||||
elseif c == 'S' then
|
||||
out[#out+1] = args.fs
|
||||
out[#out+1] = self:token(args.fs)
|
||||
elseif c == 'T' then
|
||||
out[#out+1] = args.ft
|
||||
out[#out+1] = self:token(args.ft)
|
||||
elseif c == 'o' then
|
||||
out[#out+1] = args.offset
|
||||
out[#out+1] = self:token(args.offset)
|
||||
elseif c == 'i' then
|
||||
out[#out+1] = args.immediate
|
||||
out[#out+1] = self:token(args.immediate)
|
||||
elseif c == 'I' then
|
||||
out[#out+1] = args.index
|
||||
out[#out+1] = self:token(args.index)
|
||||
elseif c == 'b' then
|
||||
out[#out+1] = args.base
|
||||
out[#out+1] = self:token(args.base)
|
||||
elseif c == '0' then
|
||||
out[#out+1] = 0
|
||||
out[#out+1] = self:token(0)
|
||||
elseif c == 'C' then
|
||||
out[#out+1] = const
|
||||
out[#out+1] = self:token(const)
|
||||
elseif c == 'F' then
|
||||
out[#out+1] = formatconst
|
||||
out[#out+1] = self:token(formatconst)
|
||||
end
|
||||
end
|
||||
local f = lookup[#outformat]
|
||||
if f == nil then
|
||||
error('Internal Error: invalid output formatting string', 1)
|
||||
error('Internal Error: invalid output formatting string')
|
||||
end
|
||||
f(self.dumper, self.line, first, out[1], out[2], out[3], out[4], out[5])
|
||||
f(self.dumper, self.fn, self.line, first, out[1], out[2], out[3], out[4], out[5])
|
||||
end
|
||||
|
||||
function Parser:format_out(t, args)
|
||||
|
@ -234,33 +174,41 @@ function Parser:instruction()
|
|||
local h = data.instructions[name]
|
||||
self:advance()
|
||||
|
||||
-- FIXME: errors thrown here probably have the wrong line number (+1)
|
||||
|
||||
if h == nil then
|
||||
self:error('undefined instruction')
|
||||
error('Internal Error: undefined instruction')
|
||||
elseif overrides[name] then
|
||||
overrides[name](self, name)
|
||||
elseif h[2] == 'tob' then -- or h[2] == 'Tob' then
|
||||
elseif h[2] == 'tob' then -- TODO: or h[2] == 'Tob' then
|
||||
local lui = data.instructions['LUI']
|
||||
local addu = data.instructions['ADDU']
|
||||
local args = {}
|
||||
args.rt = self:register()
|
||||
self:optional_comma()
|
||||
local o = self:const()
|
||||
local is_label = o[1] == 'LABELSYM'
|
||||
if self:is_EOL() then
|
||||
local lui_args = {}
|
||||
lui_args.immediate = {'UPPEROFF', o}
|
||||
lui_args.rt = 'AT'
|
||||
self:format_out(lui, lui_args)
|
||||
args.offset = {'LOWER', o}
|
||||
args.base = 'AT'
|
||||
else
|
||||
if is_label then
|
||||
self:error('labels cannot be used as offsets')
|
||||
end
|
||||
args.offset = {'SIGNED', o}
|
||||
self:optional_comma()
|
||||
if self.tt == 'OPEN' then
|
||||
args.offset = 0
|
||||
args.base = self:deref()
|
||||
else -- NUM or LABELSYM
|
||||
local lui_args = {}
|
||||
local addu_args = {}
|
||||
local o = self:const()
|
||||
args.offset = self:token(o)
|
||||
if not o.portion then
|
||||
args.offset:set('portion', 'lower')
|
||||
end
|
||||
if not o.portion and (o.tt == 'LABELSYM' or o.tok >= 0x80000000) then
|
||||
lui_args.immediate = Token(o):set('portion', 'upperoff')
|
||||
lui_args.rt = 'AT'
|
||||
self:format_out(lui, lui_args)
|
||||
if not self:is_EOL() then
|
||||
addu_args.rd = 'AT'
|
||||
addu_args.rs = 'AT'
|
||||
addu_args.rt = self:deref()
|
||||
self:format_out(addu, addu_args)
|
||||
end
|
||||
args.base = 'AT'
|
||||
else
|
||||
args.base = self:deref()
|
||||
end
|
||||
end
|
||||
self:format_out(h, args)
|
||||
elseif h[2] ~= nil then
|
||||
|
@ -273,7 +221,6 @@ function Parser:instruction()
|
|||
end
|
||||
|
||||
function Parser:tokenize(asm)
|
||||
self.tokens = {}
|
||||
self.i = 0
|
||||
|
||||
local routine = coroutine.create(function()
|
||||
|
@ -281,103 +228,27 @@ function Parser:tokenize(asm)
|
|||
lexer:lex(coroutine.yield)
|
||||
end)
|
||||
|
||||
local function lex()
|
||||
local t = {}
|
||||
local tokens = {}
|
||||
while true do
|
||||
local ok, a, b, c, d = coroutine.resume(routine)
|
||||
if not ok then
|
||||
a = a or 'Internal Error: lexer coroutine has stopped'
|
||||
error(a)
|
||||
end
|
||||
t.tt = a
|
||||
t.tok = b
|
||||
t.fn = c
|
||||
t.line = d
|
||||
insert(self.tokens, t)
|
||||
return t.tt, t.tok, t.fn, t.line
|
||||
end
|
||||
assert(a, 'Internal Error: missing token')
|
||||
|
||||
-- first pass: collect tokens, constants, and relative labels.
|
||||
-- can't do more because instruction size can depend on a constant's size
|
||||
-- and labels depend on instruction size.
|
||||
-- note however, instruction size does not depend on label size.
|
||||
-- this would cause a recursive problem to solve,
|
||||
-- which is too much for our simple assembler.
|
||||
local plus_labels = {} -- constructed forwards
|
||||
local minus_labels = {} -- constructed backwards
|
||||
while true do
|
||||
local tt, tok, fn, line = lex()
|
||||
self.fn = fn
|
||||
self.line = line
|
||||
if tt == 'DEF' then
|
||||
local tt2, tok2 = lex()
|
||||
if tt2 ~= 'NUM' then
|
||||
self:error('expected number for define')
|
||||
end
|
||||
self.defines[tok] = tok2
|
||||
elseif tt == 'RELLABEL' then
|
||||
if tok == '+' then
|
||||
insert(plus_labels, #self.tokens)
|
||||
elseif tok == '-' then
|
||||
insert(minus_labels, 1, #self.tokens)
|
||||
else
|
||||
error('Internal Error: unexpected token for relative label', 1)
|
||||
end
|
||||
elseif tt == 'EOL' then
|
||||
-- noop
|
||||
elseif tt == 'EOF' then
|
||||
if fn == self.main_fn then
|
||||
break
|
||||
end
|
||||
elseif tt == nil then
|
||||
error('Internal Error: missing token', 1)
|
||||
local t = Token(c, d, a, b)
|
||||
insert(tokens, t)
|
||||
|
||||
if t.tt == 'EOF' and t.fn == self.main_fn then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- resolve defines and relative labels
|
||||
for i, t in ipairs(self.tokens) do
|
||||
self.fn = t.fn
|
||||
self.line = t.line
|
||||
if t.tt == 'DEFSYM' then
|
||||
t.tt = 'NUM'
|
||||
t.tok = self.defines[t.tok]
|
||||
if t.tok == nil then
|
||||
self:error('undefined define') -- uhhh nice wording
|
||||
end
|
||||
elseif t.tt == 'RELLABEL' then
|
||||
t.tt = 'LABEL'
|
||||
-- exploits the fact that user labels can't begin with a number
|
||||
t.tok = tostring(i)
|
||||
elseif t.tt == 'RELLABELSYM' then
|
||||
t.tt = 'LABELSYM'
|
||||
local rel = t.tok
|
||||
local seen = 0
|
||||
-- TODO: don't iterate over *every* label, just the ones nearby
|
||||
if rel > 0 then
|
||||
for _, label_i in ipairs(plus_labels) do
|
||||
if label_i > i then
|
||||
seen = seen + 1
|
||||
if seen == rel then
|
||||
t.tok = tostring(label_i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, label_i in ipairs(minus_labels) do
|
||||
if label_i < i then
|
||||
seen = seen - 1
|
||||
if seen == rel then
|
||||
t.tok = tostring(label_i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if seen ~= rel then
|
||||
self:error('could not find appropriate relative label')
|
||||
end
|
||||
end
|
||||
end
|
||||
local preproc = Preproc(self.options)
|
||||
self.tokens = preproc:process(tokens)
|
||||
|
||||
assert(#self.tokens > 0, 'Internal Error: no tokens after preprocessing')
|
||||
end
|
||||
|
||||
function Parser:parse(asm)
|
||||
|
@ -392,9 +263,6 @@ function Parser:parse(asm)
|
|||
elseif self.tt == 'EOL' then
|
||||
-- empty line
|
||||
self:advance()
|
||||
elseif self.tt == 'DEF' then
|
||||
self:advance()
|
||||
self:advance()
|
||||
elseif self.tt == 'DIR' then
|
||||
self:directive()
|
||||
elseif self.tt == 'LABEL' then
|
||||
|
|
137
Lua/lib/lips/Preproc.lua
Normal file
137
Lua/lib/lips/Preproc.lua
Normal file
|
@ -0,0 +1,137 @@
|
|||
local insert = table.insert
|
||||
|
||||
local data = require "lips.data"
|
||||
local util = require "lips.util"
|
||||
local Muncher = require "lips.Muncher"
|
||||
local Token = require "lips.Token"
|
||||
|
||||
local Preproc = util.Class(Muncher)
|
||||
function Preproc:init(options)
|
||||
self.options = options or {}
|
||||
end
|
||||
|
||||
function Preproc:process(tokens)
|
||||
self.tokens = tokens
|
||||
|
||||
local defines = {}
|
||||
local plus_labels = {} -- constructed forwards
|
||||
local minus_labels = {} -- constructed backwards
|
||||
|
||||
-- first pass: resolve defines, collect relative labels
|
||||
local new_tokens = {}
|
||||
self.i = 0
|
||||
while self.i < #self.tokens do
|
||||
local t = self:advance()
|
||||
if t.tt == nil then
|
||||
error('Internal Error: missing token')
|
||||
elseif t.tt == 'DEF' then
|
||||
local t2 = self:advance()
|
||||
if t2.tt ~= 'NUM' then
|
||||
self:error('expected number for define')
|
||||
end
|
||||
defines[t.tok] = t2.tok
|
||||
elseif t.tt == 'DEFSYM' then
|
||||
local tt = 'NUM'
|
||||
local tok = defines[t.tok]
|
||||
if tok == nil then
|
||||
self:error('undefined define') -- uhhh nice wording
|
||||
end
|
||||
insert(new_tokens, self:token(tt, tok))
|
||||
elseif t.tt == 'RELLABEL' then
|
||||
if t.tok == '+' then
|
||||
insert(plus_labels, #new_tokens + 1)
|
||||
elseif t.tok == '-' then
|
||||
insert(minus_labels, 1, #new_tokens + 1)
|
||||
else
|
||||
error('Internal Error: unexpected token for relative label')
|
||||
end
|
||||
insert(new_tokens, t)
|
||||
else
|
||||
insert(new_tokens, t)
|
||||
end
|
||||
end
|
||||
|
||||
-- second pass: resolve relative labels
|
||||
for i, t in ipairs(new_tokens) do
|
||||
self.fn = t.fn
|
||||
self.line = t.line
|
||||
if t.tt == 'RELLABEL' then
|
||||
t.tt = 'LABEL'
|
||||
-- exploits the fact that user labels can't begin with a number
|
||||
t.tok = tostring(i)
|
||||
elseif t.tt == 'RELLABELSYM' then
|
||||
t.tt = 'LABELSYM'
|
||||
|
||||
local rel = t.tok
|
||||
local seen = 0
|
||||
-- TODO: don't iterate over *every* label, just the ones nearby
|
||||
if rel > 0 then
|
||||
for _, label_i in ipairs(plus_labels) do
|
||||
if label_i > i then
|
||||
seen = seen + 1
|
||||
if seen == rel then
|
||||
t.tok = tostring(label_i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, label_i in ipairs(minus_labels) do
|
||||
if label_i < i then
|
||||
seen = seen - 1
|
||||
if seen == rel then
|
||||
t.tok = tostring(label_i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if seen ~= rel then
|
||||
self:error('could not find appropriate relative label')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.tokens = new_tokens
|
||||
new_tokens = {}
|
||||
|
||||
-- third pass: resolve specials
|
||||
self.i = 0
|
||||
while self.i < #self.tokens do
|
||||
local t = self:advance()
|
||||
if t.tt == 'SPECIAL' then
|
||||
local name, args = self:special()
|
||||
-- TODO: split to its own file, not unlike overrides.lua
|
||||
if name == 'hi' then
|
||||
if #args ~= 1 then
|
||||
self:error('%hi expected exactly one argument')
|
||||
end
|
||||
local tnew = self:token(args[1]):set('portion', 'upperoff')
|
||||
insert(new_tokens, tnew)
|
||||
elseif name == 'up' then
|
||||
if #args ~= 1 then
|
||||
self:error('%up expected exactly one argument')
|
||||
end
|
||||
local tnew = self:token(args[1]):set('portion', 'upper')
|
||||
insert(new_tokens, tnew)
|
||||
elseif name == 'lo' then
|
||||
if #args ~= 1 then
|
||||
self:error('%lo expected exactly one argument')
|
||||
end
|
||||
local tnew = self:token(args[1]):set('portion', 'lower')
|
||||
insert(new_tokens, tnew)
|
||||
else
|
||||
self:error('unknown special')
|
||||
end
|
||||
else
|
||||
insert(new_tokens, t)
|
||||
end
|
||||
end
|
||||
|
||||
self.tokens = new_tokens
|
||||
|
||||
return self.tokens
|
||||
end
|
||||
|
||||
return Preproc
|
60
Lua/lib/lips/Token.lua
Normal file
60
Lua/lib/lips/Token.lua
Normal file
|
@ -0,0 +1,60 @@
|
|||
local util = require "lips.util"
|
||||
|
||||
local Token = util.Class()
|
||||
function Token:init(...)
|
||||
local args = {...}
|
||||
if #args == 1 then
|
||||
local t = args[1]
|
||||
if type(t) == 'table' then
|
||||
for k, v in pairs(t) do
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
elseif #args == 3 then
|
||||
self.fn = args[1]
|
||||
self.line = args[2]
|
||||
local t = args[3]
|
||||
if type(t) == 'table' then
|
||||
self.tt = t[1]
|
||||
self.tok = t[2]
|
||||
elseif type(t) == 'string' then
|
||||
self.tt = 'REG'
|
||||
self.tok = t
|
||||
elseif type(t) == 'number' then
|
||||
self.tt = 'NUM'
|
||||
self.tok = t
|
||||
else
|
||||
error('Internal Error: unknown type to construct', 3)
|
||||
end
|
||||
elseif #args == 4 then
|
||||
self.fn = args[1]
|
||||
self.line = args[2]
|
||||
self.tt = args[3]
|
||||
self.tok = args[4]
|
||||
else
|
||||
error('Internal Error: init takes 1, 3 or 4 arguments', 3)
|
||||
end
|
||||
if not self.fn then
|
||||
error('Internal Error: tokens require a filename', 3)
|
||||
end
|
||||
if not self.line then
|
||||
error('Internal Error: tokens require a line number', 3)
|
||||
end
|
||||
if not self.tt then
|
||||
error('Internal Error: token is missing a type', 3)
|
||||
end
|
||||
if not self.tok then
|
||||
error('Internal Error: token is missing a value', 3)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Token:set(key, value)
|
||||
if value == nil then
|
||||
value = true
|
||||
end
|
||||
self[key] = value
|
||||
return self
|
||||
end
|
||||
|
||||
return Token
|
|
@ -29,13 +29,19 @@ data.fpu_registers = {
|
|||
}
|
||||
|
||||
data.all_directives = {
|
||||
'ALIGN', 'SKIP',
|
||||
'ORG', 'ALIGN', 'SKIP',
|
||||
'ASCII', 'ASCIIZ',
|
||||
'BYTE', 'HALFWORD', 'WORD', 'FLOAT',
|
||||
'BYTE', 'HALFWORD', 'WORD',
|
||||
--'HEX', -- excluded here due to different syntax
|
||||
'INC', 'INCASM', 'INCLUDE',
|
||||
'INCBIN',
|
||||
'ORG',
|
||||
-- these are unlikely to be implemented
|
||||
'FLOAT', 'DOUBLE',
|
||||
}
|
||||
|
||||
data.directive_aliases = {
|
||||
SPACE = 'SKIP',
|
||||
HALF = 'HALFWORD',
|
||||
}
|
||||
|
||||
data.all_registers = {}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
-- split lips.lua
|
||||
|
||||
local assembler = {
|
||||
local lips = {
|
||||
_DESCRIPTION = 'Assembles MIPS assembly files for the R4300i CPU.',
|
||||
_URL = 'https://github.com/notwa/lips/',
|
||||
_LICENSE = [[
|
||||
|
@ -12,21 +10,10 @@ local assembler = {
|
|||
]],
|
||||
}
|
||||
|
||||
local open = io.open
|
||||
|
||||
local util = require "lips.util"
|
||||
local Parser = require "lips.Parser"
|
||||
|
||||
local function readfile(fn)
|
||||
local f = open(fn, 'r')
|
||||
if not f then
|
||||
error('could not open assembly file for reading: '..tostring(fn), 2)
|
||||
end
|
||||
local asm = f:read('*a')
|
||||
f:close()
|
||||
return asm
|
||||
end
|
||||
|
||||
function assembler.word_writer()
|
||||
function lips.word_writer()
|
||||
local buff = {}
|
||||
local max = -1
|
||||
return function(pos, b)
|
||||
|
@ -47,13 +34,13 @@ function assembler.word_writer()
|
|||
end
|
||||
end
|
||||
|
||||
function assembler.assemble(fn_or_asm, writer, options)
|
||||
function lips.assemble(fn_or_asm, writer, options)
|
||||
-- assemble MIPS R4300i assembly code.
|
||||
-- if fn_or_asm contains a newline; treat as assembly, otherwise load file.
|
||||
-- returns error message on error, or nil on success.
|
||||
fn_or_asm = tostring(fn_or_asm)
|
||||
local default_writer = not writer
|
||||
writer = writer or assembler.word_writer()
|
||||
writer = writer or lips.word_writer()
|
||||
options = options or {}
|
||||
|
||||
local function main()
|
||||
|
@ -63,7 +50,7 @@ function assembler.assemble(fn_or_asm, writer, options)
|
|||
asm = fn_or_asm
|
||||
else
|
||||
fn = fn_or_asm
|
||||
asm = readfile(fn)
|
||||
asm = util.readfile(fn)
|
||||
options.path = fn:match(".*/")
|
||||
end
|
||||
|
||||
|
@ -83,7 +70,7 @@ function assembler.assemble(fn_or_asm, writer, options)
|
|||
end
|
||||
end
|
||||
|
||||
return setmetatable(assembler, {
|
||||
return setmetatable(lips, {
|
||||
__call = function(self, ...)
|
||||
return self.assemble(...)
|
||||
end,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
local insert = table.insert
|
||||
|
||||
local data = require "lips.data"
|
||||
local util = require "lips.util"
|
||||
|
||||
local instructions = data.instructions
|
||||
|
||||
local overrides = {}
|
||||
|
@ -17,26 +19,33 @@ function overrides.LI(self, name)
|
|||
|
||||
-- for us, this is just semantics. for a "real" assembler,
|
||||
-- LA could add appropriate RELO LUI/ADDIU directives.
|
||||
if im[1] == 'LABELSYM' then
|
||||
if im.tt == 'LABELSYM' then
|
||||
self:error('use LA for labels')
|
||||
end
|
||||
|
||||
im[2] = im[2] % 0x100000000
|
||||
if im[2] >= 0x10000 and im[2] <= 0xFFFF8000 then
|
||||
if im.portion then
|
||||
args.rs = 'R0'
|
||||
args.immediate = im
|
||||
self:format_out(addiu, args)
|
||||
return
|
||||
end
|
||||
|
||||
im.tok = im.tok % 0x100000000
|
||||
if im.tok >= 0x10000 and im.tok <= 0xFFFF8000 then
|
||||
args.rs = args.rt
|
||||
args.immediate = {'UPPER', im}
|
||||
args.immediate = self:token(im):set('portion', 'upper')
|
||||
self:format_out(lui, args)
|
||||
if im[2] % 0x10000 ~= 0 then
|
||||
args.immediate = {'LOWER', im}
|
||||
if im.tok % 0x10000 ~= 0 then
|
||||
args.immediate = self:token(im):set('portion', 'lower')
|
||||
self:format_out(ori, args)
|
||||
end
|
||||
elseif im[2] >= 0x8000 and im[2] < 0x10000 then
|
||||
elseif im.tok >= 0x8000 and im.tok < 0x10000 then
|
||||
args.rs = 'R0'
|
||||
args.immediate = {'LOWER', im}
|
||||
args.immediate = self:token(im):set('portion', 'lower')
|
||||
self:format_out(ori, args)
|
||||
else
|
||||
args.rs = 'R0'
|
||||
args.immediate = {'LOWER', im}
|
||||
args.immediate = self:token(im):set('portion', 'lower')
|
||||
self:format_out(addiu, args)
|
||||
end
|
||||
end
|
||||
|
@ -50,9 +59,9 @@ function overrides.LA(self, name)
|
|||
local im = self:const()
|
||||
|
||||
args.rs = args.rt
|
||||
args.immediate = {'UPPEROFF', im}
|
||||
args.immediate = self:token(im):set('portion', 'upperoff')
|
||||
self:format_out(lui, args)
|
||||
args.immediate = {'LOWER', im}
|
||||
args.immediate = self:token(im):set('portion', 'lower')
|
||||
self:format_out(addiu, args)
|
||||
end
|
||||
|
||||
|
@ -84,14 +93,14 @@ function overrides.PUSH(self, name)
|
|||
if name == 'PUSH' then
|
||||
args.rt = 'SP'
|
||||
args.rs = 'SP'
|
||||
args.immediate = {'NEGATE', {'NUM', #stack*4}}
|
||||
args.immediate = self:token(#stack*4):set('negate')
|
||||
self:format_out(addi, args)
|
||||
end
|
||||
args.base = 'SP'
|
||||
for i, r in ipairs(stack) do
|
||||
args.rt = r
|
||||
if r ~= '' then
|
||||
args.offset = {'NUM', (i - 1)*4}
|
||||
args.offset = (i - 1)*4
|
||||
self:format_out(w, args)
|
||||
end
|
||||
end
|
||||
|
@ -102,7 +111,7 @@ function overrides.PUSH(self, name)
|
|||
if name == 'POP' or name == 'JPOP' then
|
||||
args.rt = 'SP'
|
||||
args.rs = 'SP'
|
||||
args.immediate = {'NUM', #stack*4}
|
||||
args.immediate = #stack*4
|
||||
self:format_out(addi, args)
|
||||
end
|
||||
end
|
||||
|
@ -175,7 +184,7 @@ function overrides.ROL(self, name)
|
|||
end
|
||||
self:format_out(sll, args)
|
||||
args.rd = 'AT'
|
||||
args.immediate = {'NUM', 32 - args.immediate[2]}
|
||||
args.immediate = 32 - args.immediate[2]
|
||||
self:format_out(srl, args)
|
||||
args.rd = left
|
||||
args.rs = left
|
||||
|
@ -202,7 +211,7 @@ function overrides.ROR(self, name)
|
|||
end
|
||||
self:format_out(srl, args)
|
||||
args.rd = 'AT'
|
||||
args.immediate = {'NUM', 32 - args.immediate[2]}
|
||||
args.immediate = 32 - args.immediate[2]
|
||||
self:format_out(sll, args)
|
||||
args.rd = right
|
||||
args.rs = right
|
||||
|
@ -238,7 +247,7 @@ function overrides.BEQI(self, name)
|
|||
self:optional_comma()
|
||||
args.immediate = self:const()
|
||||
self:optional_comma()
|
||||
args.offset = {'SIGNED', self:const('relative')}
|
||||
args.offset = self:token(self:const('relative')):set('signed')
|
||||
|
||||
if reg == 'AT' then
|
||||
self:error('register cannot be AT in this pseudo-instruction')
|
||||
|
@ -261,7 +270,7 @@ function overrides.BLTI(self, name)
|
|||
self:optional_comma()
|
||||
args.immediate = self:const()
|
||||
self:optional_comma()
|
||||
args.offset = {'SIGNED', self:const('relative')}
|
||||
args.offset = self:token(self:const('relative')):set('signed')
|
||||
|
||||
if args.rs == 'AT' then
|
||||
self:error('register cannot be AT in this pseudo-instruction')
|
||||
|
@ -287,7 +296,7 @@ function overrides.BLEI(self, name)
|
|||
self:optional_comma()
|
||||
args.immediate = self:const()
|
||||
self:optional_comma()
|
||||
local offset = {'SIGNED', self:const('relative')}
|
||||
local offset = self:token(self:const('relative')):set('signed')
|
||||
|
||||
if reg == 'AT' then
|
||||
self:error('register cannot be AT in this pseudo-instruction')
|
||||
|
|
37
Lua/lib/lips/util.lua
Normal file
37
Lua/lib/lips/util.lua
Normal file
|
@ -0,0 +1,37 @@
|
|||
local floor = math.floor
|
||||
local open = io.open
|
||||
|
||||
local function Class(inherit)
|
||||
local class = {}
|
||||
local mt_obj = {__index = class}
|
||||
local mt_class = {
|
||||
__call = function(self, ...)
|
||||
local obj = setmetatable({}, mt_obj)
|
||||
obj:init(...)
|
||||
return obj
|
||||
end,
|
||||
__index = inherit,
|
||||
}
|
||||
|
||||
return setmetatable(class, mt_class)
|
||||
end
|
||||
|
||||
local function readfile(fn)
|
||||
local f = open(fn, 'r')
|
||||
if not f then
|
||||
error('could not open assembly file for reading: '..tostring(fn), 2)
|
||||
end
|
||||
local asm = f:read('*a')
|
||||
f:close()
|
||||
return asm
|
||||
end
|
||||
|
||||
local function bitrange(x, lower, upper)
|
||||
return floor(x/2^lower) % 2^(upper - lower + 1)
|
||||
end
|
||||
|
||||
return {
|
||||
Class = Class,
|
||||
readfile = readfile,
|
||||
bitrange = bitrange,
|
||||
}
|
Loading…
Add table
Reference in a new issue