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
|