1
0
Fork 0
mirror of https://github.com/notwa/mm synced 2024-11-05 08:29:02 -08:00
mm/Lua/lib/lips/Collector.lua

238 lines
6.7 KiB
Lua
Raw Normal View History

2016-04-21 13:04:49 -07:00
local insert = table.insert
local path = string.gsub(..., "[^.]+$", "")
2016-11-27 06:09:18 -08:00
local Base = require(path.."Base")
2016-04-21 13:04:49 -07:00
local Token = require(path.."Token")
2016-11-27 06:09:18 -08:00
local TokenIter = require(path.."TokenIter")
2016-04-21 13:04:49 -07:00
local Statement = require(path.."Statement")
2016-11-27 06:09:18 -08:00
local Collector = Base:extend()
2016-04-21 13:04:49 -07:00
function Collector:init(options)
self.options = options or {}
end
function Collector:statement(...)
2016-11-27 06:09:18 -08:00
local I = self.iter
local s = Statement(I.fn, I.line, ...)
2016-04-21 13:04:49 -07:00
return s
end
function Collector:push_data(datum, size)
2016-11-27 06:09:18 -08:00
local I = self.iter
2016-04-21 13:04:49 -07:00
--[[ pseudo-example:
Statement{type='!DATA',
{tt='BYTES', tok={0, 1, 2}},
{tt='HALFWORDS', tok={3, 4, 5}},
{tt='WORDS', tok={6, 7, 8}},
{tt='LABEL', tok='myLabel'},
}
--]]
2016-11-27 06:09:18 -08:00
-- FIXME: optimize the hell out of this garbage, preferably in the lexer
2016-04-21 13:04:49 -07:00
-- TODO: consider not scrunching data statements, just their tokens
2016-11-27 06:09:18 -08:00
-- TODO: concatenate strings; use !BIN instead of !DATA
2016-04-21 13:04:49 -07:00
2016-04-26 14:48:39 -07:00
if type(datum) == 'number' then
2016-11-27 06:09:18 -08:00
datum = I:token(datum)
2016-04-26 14:48:39 -07:00
end
2016-04-21 13:04:49 -07:00
local last_statement = self.statements[#self.statements]
local s
if last_statement and last_statement.type == '!DATA' then
s = last_statement
else
s = self:statement('!DATA')
insert(self.statements, s)
end
if size ~= 'BYTE' and size ~= 'HALFWORD' and size ~= 'WORD' then
error('Internal Error: unknown data size argument')
end
2016-04-26 14:48:39 -07:00
if datum.tt == 'LABELSYM' then
if size == 'WORD' then
-- labels will be assembled to words
insert(s, datum)
return
else
2016-11-27 06:09:18 -08:00
I:error('labels are too large to be used in this directive')
2016-04-26 14:48:39 -07:00
end
elseif datum.tt == 'VARSYM' then
insert(s, datum:set('size', size))
return
elseif datum.tt ~= 'NUM' then
2016-11-27 06:09:18 -08:00
I:error('unsupported data type', datum.tt)
2016-04-26 14:48:39 -07:00
end
2016-04-21 13:04:49 -07:00
local sizes = size..'S'
local last_token = s[#s]
local t
if last_token and last_token.tt == sizes then
t = last_token
else
2016-11-27 06:09:18 -08:00
t = I:token(sizes, {})
2016-04-21 13:04:49 -07:00
insert(s, t)
s:validate()
end
2016-04-26 14:48:39 -07:00
insert(t.tok, datum.tok)
2016-04-21 13:04:49 -07:00
end
2016-11-27 06:09:18 -08:00
function Collector:directive(name)
local I = self.iter
2016-04-21 13:04:49 -07:00
local function add(kind, ...)
insert(self.statements, self:statement('!'..kind, ...))
end
2016-11-27 06:09:18 -08:00
2016-04-21 13:04:49 -07:00
if name == 'ORG' or name == 'BASE' then
2016-11-27 06:09:18 -08:00
add(name, I:const(nil, 'no labels'))
2016-04-26 14:48:39 -07:00
elseif name == 'PUSH' or name == 'POP' then
2016-11-27 06:09:18 -08:00
add(name, I:const())
while not I:is_EOL() do
I:eat_comma()
add(name, I:const())
2016-04-26 14:48:39 -07:00
end
2016-04-21 13:04:49 -07:00
elseif name == 'ALIGN' or name == 'SKIP' then
2016-11-27 06:09:18 -08:00
if I:is_EOL() and name == 'ALIGN' then
2016-04-21 13:04:49 -07:00
add(name)
else
2016-11-27 06:09:18 -08:00
local size = I:const(nil, 'no label')
if I:is_EOL() then
2016-04-21 13:04:49 -07:00
add(name, size)
else
2016-11-27 06:09:18 -08:00
I:eat_comma()
add(name, size, I:const(nil, 'no label'))
2016-04-21 13:04:49 -07:00
end
end
2016-11-27 06:09:18 -08:00
elseif name == 'BIN' then
-- FIXME: not a real directive, just a workaround
add(name, I:string())
2016-04-21 13:04:49 -07:00
elseif name == 'BYTE' or name == 'HALFWORD' or name == 'WORD' then
2016-11-27 06:09:18 -08:00
self:push_data(I:const(), name)
while not I:is_EOL() do
I:eat_comma()
self:push_data(I:const(), name)
2016-04-26 14:48:39 -07:00
end
elseif name == 'HEX' then
2016-11-27 06:09:18 -08:00
if I.tt ~= 'OPEN' then
I:error('expected opening brace for hex directive', I.tt)
2016-04-26 14:48:39 -07:00
end
2016-11-27 06:09:18 -08:00
I:next()
2016-04-26 14:48:39 -07:00
2016-11-27 06:09:18 -08:00
while I.tt ~= 'CLOSE' do
if I.tt == 'EOL' then
I:next()
2016-04-26 14:48:39 -07:00
else
2016-11-27 06:09:18 -08:00
self:push_data(I:const(), 'BYTE')
2016-04-26 14:48:39 -07:00
end
2016-04-21 13:04:49 -07:00
end
2016-11-27 06:09:18 -08:00
I:next()
2016-04-21 13:04:49 -07:00
elseif name == 'INC' or name == 'INCBIN' then
-- noop, handled by lexer
2016-11-27 06:09:18 -08:00
I:string()
2016-04-21 13:04:49 -07:00
elseif name == 'ASCII' or name == 'ASCIIZ' then
2016-11-27 06:09:18 -08:00
local bytes = I:string()
2016-04-21 13:04:49 -07:00
for i, number in ipairs(bytes.tok) do
self:push_data(number, 'BYTE')
end
if name == 'ASCIIZ' then
self:push_data(0, 'BYTE')
end
elseif name == 'FLOAT' then
2016-11-27 06:09:18 -08:00
I:error('unimplemented directive', name)
2016-04-21 13:04:49 -07:00
else
2016-11-27 06:09:18 -08:00
I:error('unknown directive', name)
2016-04-21 13:04:49 -07:00
end
2016-11-27 06:09:18 -08:00
I:expect_EOL()
2016-04-21 13:04:49 -07:00
end
2016-11-27 06:09:18 -08:00
function Collector:instruction(name)
local I = self.iter
local s = self:statement(name)
2016-04-21 13:04:49 -07:00
insert(self.statements, s)
2016-11-27 06:09:18 -08:00
while I.tt ~= 'EOL' do
local t = I.t
if I.tt == 'OPEN' then
insert(s, I:deref())
elseif I.tt == 'UNARY' then
local peek = assert(I:peek())
2016-04-21 13:04:49 -07:00
if peek.tt == 'VARSYM' then
local negate = t.tok == -1
2016-11-27 06:09:18 -08:00
t = I:next()
2016-04-21 13:04:49 -07:00
t = Token(t):set('negate', negate)
insert(s, t)
2016-11-27 06:09:18 -08:00
I:next()
2016-04-21 13:04:49 -07:00
elseif peek.tt == 'EOL' or peek.tt == 'SEP' then
local tok = t.tok == 1 and '+' or t.tok == -1 and '-'
2016-11-27 06:09:18 -08:00
t = Token(I.fn, I.line, 'RELLABELSYM', tok)
2016-04-21 13:04:49 -07:00
insert(s, t)
2016-11-27 06:09:18 -08:00
I:next()
2016-04-21 13:04:49 -07:00
else
2016-11-27 06:09:18 -08:00
I:error('unexpected token after unary operator', peek.tt)
2016-04-21 13:04:49 -07:00
end
2016-11-27 06:09:18 -08:00
elseif I.tt == 'SPECIAL' then
t = I:basic_special()
2016-04-21 13:04:49 -07:00
insert(s, t)
2016-11-27 06:09:18 -08:00
I:next()
elseif I.tt == 'SEP' then
I:error('extraneous comma')
elseif not I.arg_types[I.tt] then
I:error('unexpected argument type in instruction', I.tt)
2016-04-21 13:04:49 -07:00
else
insert(s, t)
2016-11-27 06:09:18 -08:00
I:next()
2016-04-21 13:04:49 -07:00
end
2016-11-27 06:09:18 -08:00
I:eat_comma()
2016-04-21 13:04:49 -07:00
end
2016-11-27 06:09:18 -08:00
I:expect_EOL()
2016-04-21 13:04:49 -07:00
s:validate()
end
function Collector:collect(tokens, fn)
2016-11-27 06:09:18 -08:00
self.iter = TokenIter(tokens)
local I = self.iter
2016-04-21 13:04:49 -07:00
self.statements = {}
-- this works, but probably shouldn't be in this function specifically
2016-04-26 14:48:39 -07:00
if self.options.origin then
local s = Statement('(options)', 0, '!ORG', self.options.origin)
2016-04-21 13:04:49 -07:00
insert(self.statements, s)
end
if self.options.base then
local s = Statement('(options)', 0, '!BASE', self.options.base)
insert(self.statements, s)
end
2016-11-27 06:09:18 -08:00
for t in I do
if t.tt == 'EOF' then
-- noop
elseif t.tt == 'EOL' then
-- noop; empty line
elseif t.tt == 'LABEL' or t.tt == 'RELLABEL' then
insert(self.statements, self:statement('!LABEL', t))
elseif t.tt == 'VAR' then
local t2 = I:next()
I:next()
local s = self:statement('!VAR', t, t2)
insert(self.statements, s)
I:expect_EOL()
elseif t.tt == 'DIR' then
I:next()
self:directive(t.tok)
elseif t.tt == 'INSTR' then
I:next()
self:instruction(t.tok)
2016-04-21 13:04:49 -07:00
else
2016-11-27 06:09:18 -08:00
I:error('expected starting token for statement', t.tt)
2016-04-21 13:04:49 -07:00
end
end
return self.statements
end
return Collector