mirror of
https://github.com/notwa/lips
synced 2024-05-03 10:03:23 -07:00
Connor Olding
cdc0f8edb2
at the moment, this probably only works in directives. some of the operators are still unimplemented, and the errors are poor. there will be support for accessing variables in the future.
283 lines
8.1 KiB
Lua
283 lines
8.1 KiB
Lua
local insert = table.insert
|
|
|
|
local path = string.gsub(..., "[^.]+$", "")
|
|
local data = require(path.."data")
|
|
local overrides = require(path.."overrides")
|
|
local Statement = require(path.."Statement")
|
|
local Reader = require(path.."Reader")
|
|
local Expression = require(path.."Expression")
|
|
|
|
local abs = math.abs
|
|
|
|
local function signs(s)
|
|
local start, end_ = s:find('[+-]+')
|
|
if start ~= 1 then
|
|
return 0
|
|
end
|
|
if s:sub(1, 1) == '+' then
|
|
return end_
|
|
elseif s:sub(1, 1) == '-' then
|
|
return -end_
|
|
end
|
|
end
|
|
|
|
local Preproc = Reader:extend()
|
|
function Preproc:init(options)
|
|
self.options = options or {}
|
|
end
|
|
|
|
function Preproc:lookup(t)
|
|
if t.tt == 'VARSYM' then
|
|
local name = t.tok
|
|
t.tt = 'NUM'
|
|
t.tok = self.variables[name]
|
|
if t.tok == nil then
|
|
self:error('undefined variable', name)
|
|
end
|
|
elseif self.do_labels and t.tt == 'RELLABELSYM' or t.tt == 'RELLABEL' then
|
|
if t.tt == 'RELLABEL' then
|
|
t.tt = 'LABEL'
|
|
-- exploits the fact that user labels can't begin with a number
|
|
local name = t.tok:sub(2)
|
|
t.tok = tostring(self.i)..name
|
|
elseif t.tt == 'RELLABELSYM' then
|
|
local i = self.i
|
|
t.tt = 'LABELSYM'
|
|
|
|
local rel = signs(t.tok)
|
|
assert(rel ~= 0, 'Internal Error: relative label without signs')
|
|
|
|
local name = t.tok:sub(abs(rel) + 1)
|
|
local seen = 0
|
|
|
|
-- TODO: don't iterate over *every* label, just the ones nearby.
|
|
-- we could do this by popping labels as we pass over them.
|
|
-- (would need to iterate once forwards and once backwards
|
|
-- for plus and minus labels respectively)
|
|
if rel > 0 then
|
|
for _, rl in ipairs(self.plus_labels) do
|
|
if rl.name == name and rl.index > i then
|
|
seen = seen + 1
|
|
if seen == rel then
|
|
t.tok = tostring(rl.index)..name
|
|
break
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for _, rl in ipairs(self.minus_labels) do
|
|
if rl.name == name and rl.index < i then
|
|
seen = seen - 1
|
|
if seen == rel then
|
|
t.tok = tostring(rl.index)..name
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if seen ~= rel then
|
|
self:error('could not find appropriate relative label', t.tok)
|
|
end
|
|
end
|
|
else
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function Preproc:check(s, i, tt)
|
|
s = s or self.s
|
|
i = i or self.i
|
|
local t = s[i]
|
|
if t == nil then
|
|
local err = ("expected another argument for %s at position %i"):format(self.s.type, self.i)
|
|
self:error(err)
|
|
end
|
|
|
|
self.fn = t.fn
|
|
self.line = t.line
|
|
|
|
if t.tt ~= tt then
|
|
self:lookup(t)
|
|
end
|
|
|
|
if t.tt ~= tt then
|
|
local err = ("argument %i of %s expected type %s"):format(i, s.type, tt)
|
|
self:error(err, t.tt)
|
|
end
|
|
return t.tok
|
|
end
|
|
|
|
function Preproc:process(statements)
|
|
self.statements = statements
|
|
|
|
self.variables = {}
|
|
self.plus_labels = {} -- constructed forwards
|
|
self.minus_labels = {} -- constructed backwards
|
|
self.do_labels = false
|
|
|
|
-- first pass: resolve variables and collect relative labels
|
|
local new_statements = {}
|
|
for i=1, #self.statements do
|
|
local s = self.statements[i]
|
|
self.fn = s.fn
|
|
self.line = s.line
|
|
if s.type:sub(1, 1) == '!' then
|
|
-- directive, label, etc.
|
|
if s.type == '!VAR' then
|
|
local a = self:check(s, 1, 'VAR')
|
|
local b = self:check(s, 2, 'NUM')
|
|
self.variables[a] = b
|
|
elseif s.type == '!LABEL' then
|
|
if s[1].tt == 'RELLABEL' then
|
|
local label = s[1].tok
|
|
local rl = {
|
|
index = #new_statements + 1,
|
|
name = label:sub(2)
|
|
}
|
|
local c = label:sub(1, 1)
|
|
if c == '+' then
|
|
insert(self.plus_labels, rl)
|
|
elseif c == '-' then
|
|
insert(self.minus_labels, 1, rl) -- remember, it's backwards
|
|
else
|
|
error('Internal Error: unexpected token for relative label')
|
|
end
|
|
end
|
|
insert(new_statements, s)
|
|
else
|
|
for j, t in ipairs(s) do
|
|
self:lookup(t)
|
|
end
|
|
insert(new_statements, s)
|
|
end
|
|
else
|
|
-- regular instruction
|
|
for j, t in ipairs(s) do
|
|
self:lookup(t)
|
|
end
|
|
insert(new_statements, s)
|
|
end
|
|
end
|
|
|
|
-- second pass: resolve relative labels
|
|
self.do_labels = true
|
|
for i=1, #new_statements do
|
|
self.i = i -- make visible to :lookup
|
|
local s = new_statements[i]
|
|
self.fn = s.fn
|
|
self.line = s.line
|
|
for j, t in ipairs(s) do
|
|
self:lookup(t)
|
|
end
|
|
end
|
|
|
|
-- third pass: evaluate constant expressions
|
|
for i=1, #new_statements do
|
|
local s = new_statements[i]
|
|
self.fn = s.fn
|
|
self.line = s.line
|
|
for j, t in ipairs(s) do
|
|
if t.tt == 'EXPR' then
|
|
local expr = Expression()
|
|
local result, err = expr:eval(t.tok)
|
|
if err then
|
|
self:error('failed to evaulate ('..t.tok..')', err)
|
|
end
|
|
t.tt = 'NUM'
|
|
t.tok = result
|
|
end
|
|
end
|
|
end
|
|
|
|
return new_statements
|
|
end
|
|
|
|
function Preproc:statement(...)
|
|
self.fn = self.s.fn
|
|
self.line = self.s.line
|
|
local s = Statement(self.fn, self.line, ...)
|
|
return s
|
|
end
|
|
|
|
function Preproc:push(s)
|
|
s:validate()
|
|
insert(self.statements, s)
|
|
end
|
|
|
|
function Preproc:push_new(...)
|
|
self:push(self:statement(...))
|
|
end
|
|
|
|
function Preproc:pop(kind)
|
|
local ret
|
|
if kind == nil then
|
|
ret = self.s[self.i]
|
|
elseif kind == 'CPU' then
|
|
ret = self:register(data.registers)
|
|
elseif kind == 'DEREF' then
|
|
ret = self:deref()
|
|
elseif kind == 'CONST' then
|
|
ret = self:const()
|
|
elseif kind == 'END' then
|
|
if self.s[self.i] ~= nil then
|
|
self:error('expected EOL; too many arguments')
|
|
end
|
|
return -- don't increment self.i past end of arguments
|
|
else
|
|
error('Internal Error: unknown kind, got '..tostring(kind))
|
|
end
|
|
self.i = self.i + 1
|
|
return ret
|
|
end
|
|
|
|
function Preproc:expand(statements)
|
|
-- fourth pass: expand pseudo-instructions and register arguments
|
|
self.statements = {}
|
|
for i=1, #statements do
|
|
local s = statements[i]
|
|
self.s = s
|
|
self.fn = s.fn
|
|
self.line = s.line
|
|
if s.type:sub(1, 1) == '!' then
|
|
self:push(s)
|
|
else
|
|
local name = s.type
|
|
local h = data.instructions[name]
|
|
if h == nil then
|
|
error('Internal Error: unknown instruction')
|
|
end
|
|
|
|
if data.one_register_variants[name] then
|
|
self.i = 1
|
|
local a = self:register(data.all_registers)
|
|
local b = s[2]
|
|
if b == nil or b.tt ~= 'REG' then
|
|
insert(s, 2, self:token(a))
|
|
end
|
|
elseif data.two_register_variants[name] then
|
|
self.i = 1
|
|
local a = self:register(data.all_registers)
|
|
local b = self:register(data.all_registers)
|
|
local c = s[3]
|
|
if c == nil or c.tt ~= 'REG' then
|
|
insert(s, 2, self:token(a))
|
|
end
|
|
end
|
|
|
|
if overrides[name] then
|
|
self.i = 1
|
|
overrides[name](self, name)
|
|
self:pop('END')
|
|
else
|
|
self:push(s)
|
|
end
|
|
end
|
|
end
|
|
|
|
return self.statements
|
|
end
|
|
|
|
return Preproc
|