1
0
Fork 0
mirror of https://github.com/notwa/mm synced 2024-05-17 21:23:22 -07:00

update lips; use new features

This commit is contained in:
Connor Olding 2016-01-13 11:20:28 -08:00
parent 5e684e6aeb
commit 5fbb56f472
14 changed files with 731 additions and 403 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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
View 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
View 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

View File

@ -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 = {}

View File

@ -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,

View File

@ -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
View 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,
}