mirror of
https://github.com/notwa/lips
synced 2024-06-25 16:17:12 -07:00
Connor Olding
659bec36f8
defines now resolve to the previous definition, rather than the lastmost. also, defines are invisible to the parser now.
278 lines
8.5 KiB
Lua
278 lines
8.5 KiB
Lua
local insert = table.insert
|
|
|
|
local data = require "lips.data"
|
|
local overrides = require "lips.overrides"
|
|
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")(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)
|
|
end
|
|
|
|
function Parser:directive()
|
|
local name = self.tok
|
|
self:advance()
|
|
local function add(...)
|
|
self.dumper:add_directive(self.fn, self.line, ...)
|
|
end
|
|
if name == 'ORG' then
|
|
add(name, self:number())
|
|
elseif name == 'ALIGN' or name == 'SKIP' then
|
|
if self:is_EOL() and name == 'ALIGN' then
|
|
add(name, 0)
|
|
else
|
|
local size = self:number()
|
|
if self:is_EOL() then
|
|
add(name, size)
|
|
else
|
|
self:optional_comma()
|
|
add(name, size, self:number())
|
|
end
|
|
self:expect_EOL()
|
|
end
|
|
elseif name == 'BYTE' or name == 'HALFWORD' then
|
|
add(name, self:number())
|
|
while not self:is_EOL() do
|
|
self:advance()
|
|
self:optional_comma()
|
|
add(name, self:number())
|
|
end
|
|
self:expect_EOL()
|
|
elseif name == 'WORD' then -- allow labels in word directives
|
|
add(name, self:const()[2])
|
|
while not self:is_EOL() do
|
|
self:advance()
|
|
self:optional_comma()
|
|
add(name, self:const()[2])
|
|
end
|
|
self:expect_EOL()
|
|
elseif name == 'INC' then
|
|
-- noop, handled by lexer
|
|
elseif name == 'ASCII' or name == 'ASCIIZ' then
|
|
local bytes = self:string()
|
|
for i, number in ipairs(bytes) 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' then
|
|
self:error('unimplemented')
|
|
else
|
|
self:error('unknown directive')
|
|
end
|
|
end
|
|
|
|
function Parser:format_in(informat)
|
|
local args = {}
|
|
for i=1,#informat do
|
|
local c = informat:sub(i, i)
|
|
local c2 = informat:sub(i + 1, i + 1)
|
|
if c == 'd' and not args.rd then
|
|
args.rd = self:register()
|
|
elseif c == 's' and not args.rs then
|
|
args.rs = self:register()
|
|
elseif c == 't' and not args.rt then
|
|
args.rt = self:register()
|
|
elseif c == 'D' and not args.fd then
|
|
args.fd = self:register(data.fpu_registers)
|
|
elseif c == 'S' and not args.fs then
|
|
args.fs = self:register(data.fpu_registers)
|
|
elseif c == 'T' and not args.ft then
|
|
args.ft = self:register(data.fpu_registers)
|
|
elseif c == 'X' and not args.rd then
|
|
args.rd = self:register(data.sys_registers)
|
|
elseif c == 'Y' and not args.rs then
|
|
args.rs = self:register(data.sys_registers)
|
|
elseif c == 'Z' and not args.rt then
|
|
args.rt = self:register(data.sys_registers)
|
|
elseif c == 'o' and not args.offset then
|
|
args.offset = {'SIGNED', self:const()}
|
|
elseif c == 'r' and not args.offset then
|
|
args.offset = {'SIGNED', self:const('relative')}
|
|
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()}
|
|
elseif c == 'k' and not args.immediate then
|
|
args.immediate = {'NEGATE', self:const(nil, 'no label')}
|
|
elseif c == 'K' and not args.immediate then
|
|
args.immediate = {'SIGNED', self:const(nil, 'no label')}
|
|
elseif c == 'b' and not args.base then
|
|
args.base = self:deref()
|
|
else
|
|
error('Internal Error: invalid input formatting string', 1)
|
|
end
|
|
if c2:find('[dstDSTorIikKXYZ]') then
|
|
self:optional_comma()
|
|
end
|
|
end
|
|
return args
|
|
end
|
|
|
|
function Parser:format_out_raw(outformat, first, args, const, formatconst)
|
|
local lookup = {
|
|
[1]=self.dumper.add_instruction_j,
|
|
[3]=self.dumper.add_instruction_i,
|
|
[5]=self.dumper.add_instruction_r,
|
|
}
|
|
local out = {}
|
|
for i=1,#outformat do
|
|
local c = outformat:sub(i, i)
|
|
if c == 'd' then
|
|
out[#out+1] = args.rd
|
|
elseif c == 's' then
|
|
out[#out+1] = args.rs
|
|
elseif c == 't' then
|
|
out[#out+1] = args.rt
|
|
elseif c == 'D' then
|
|
out[#out+1] = args.fd
|
|
elseif c == 'S' then
|
|
out[#out+1] = args.fs
|
|
elseif c == 'T' then
|
|
out[#out+1] = args.ft
|
|
elseif c == 'o' then
|
|
out[#out+1] = args.offset
|
|
elseif c == 'i' then
|
|
out[#out+1] = args.immediate
|
|
elseif c == 'I' then
|
|
out[#out+1] = args.index
|
|
elseif c == 'b' then
|
|
out[#out+1] = args.base
|
|
elseif c == '0' then
|
|
out[#out+1] = 0
|
|
elseif c == 'C' then
|
|
out[#out+1] = const
|
|
elseif c == 'F' then
|
|
out[#out+1] = formatconst
|
|
end
|
|
end
|
|
local f = lookup[#outformat]
|
|
if f == nil then
|
|
error('Internal Error: invalid output formatting string', 1)
|
|
end
|
|
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)
|
|
self:format_out_raw(t[3], t[1], args, t[4], t[5])
|
|
end
|
|
|
|
function Parser:instruction()
|
|
local name = self.tok
|
|
local h = data.instructions[name]
|
|
self:advance()
|
|
|
|
if h == nil then
|
|
error('Internal Error: undefined instruction')
|
|
elseif overrides[name] then
|
|
overrides[name](self, name)
|
|
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()
|
|
if self.tt == 'DEREF' then
|
|
args.offset = {'NUM', 0}
|
|
args.base = self:deref()
|
|
else -- NUM or LABELSYM
|
|
local lui_args = {}
|
|
local addu_args = {}
|
|
local o = self:const()
|
|
args.offset = {'LOWER', o}
|
|
if o[1] == 'LABELSYM' or o[2] >= 0x80000000 then
|
|
lui_args.immediate = {'UPPEROFF', o}
|
|
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
|
|
local args = self:format_in(h[2])
|
|
self:format_out(h, args)
|
|
else
|
|
self:error('unimplemented instruction')
|
|
end
|
|
self:expect_EOL()
|
|
end
|
|
|
|
function Parser:tokenize(asm)
|
|
self.i = 0
|
|
|
|
local routine = coroutine.create(function()
|
|
local lexer = Lexer(asm, self.main_fn, self.options)
|
|
lexer:lex(coroutine.yield)
|
|
end)
|
|
|
|
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
|
|
assert(a, 'Internal Error: missing token')
|
|
|
|
local t = {}
|
|
t.tt = a
|
|
t.tok = b
|
|
t.fn = c
|
|
t.line = d
|
|
insert(tokens, t)
|
|
|
|
if t.tt == 'EOF' and t.fn == self.main_fn then
|
|
break
|
|
end
|
|
end
|
|
|
|
local preproc = Preproc(self.options)
|
|
self.tokens = preproc:process(tokens)
|
|
end
|
|
|
|
function Parser:parse(asm)
|
|
self:tokenize(asm)
|
|
self:advance()
|
|
while true do
|
|
if self.tt == 'EOF' then
|
|
if self.fn == self.main_fn then
|
|
break
|
|
end
|
|
self:advance()
|
|
elseif self.tt == 'EOL' then
|
|
-- empty line
|
|
self:advance()
|
|
elseif self.tt == 'DIR' then
|
|
self:directive()
|
|
elseif self.tt == 'LABEL' then
|
|
self.dumper:add_label(self.tok)
|
|
self:advance()
|
|
elseif self.tt == 'INSTR' then
|
|
self:instruction()
|
|
else
|
|
self:error('unexpected token (unknown instruction?)')
|
|
end
|
|
end
|
|
return self.dumper:dump()
|
|
end
|
|
|
|
return Parser
|