1
0
Fork 0
mirror of https://github.com/notwa/lips synced 2024-05-03 10:03:23 -07:00
lips/lips/Preproc.lua
Connor Olding cdc0f8edb2 add a barebones expression parser
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.
2016-10-14 09:27:19 -07:00

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