l65/l7801.lua

2752 lines
110 KiB
Lua

#!/usr/bin/env lua
local function lookupify(tb, dst, not_val)
if not dst then dst = tb end
local val = not not_val
for _, v in pairs(tb) do
dst[v] = val
end
return tb
end
local WhiteChars = lookupify{' ', '\n', '\t', '\r'}
local Spaces = lookupify{' ', '\t'}
local EscapeLookup = {['\r'] = '\\r', ['\n'] = '\\n', ['\t'] = '\\t', ['"'] = '\\"', ["'"] = "\\'"}
local LowerChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
local HexDigits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'}
local BinDigits = lookupify{'0', '1'}
local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#', '&', '|', '!'}
local Keywords = lookupify{
'and', 'break', 'do', 'else', 'elseif',
'end', 'false', 'for', 'function', 'goto', 'if',
'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while',
};
local Keywords_control = {
}
local Keywords_data = {
'dc',
}
local Keywords_7801 = {
'aci','adi','adinc','ani','bit0','bit1','bit2','bit3',
'bit4','bit5','bit6','bit7','block','calb','calf','call',
'calt','clc','ei','eqi','eqiw','daa','di','dcr',
'dcrw','dcx','ex','exx','gti','halt','in','inr','inrw',
'inx','jb','jmp','jr','jre','ldaw','ldax','ldaxd',
'ldaxi','lti','lxi','mov','mvi','mviw','mvix','nei',
'nop','offi','oni','ori','out','pen','per','pex','pop','push','ret',
'reti','rets','rld','rll','rlr','rrd','sbi','sio','skc',
'skit','sknit','skz','sknc','sknz','sll','slr','softi',
'staw','stax','staxd','staxi','stc','stm','sui','suinb',
'table','xri',
'ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa',
'ona','offa',
'anaw','xraw','oraw','addncw','gtaw','subnbw','ltaw','addw','onaw','adcw','offaw','subw','neaw','sbbw','eqaw',
'sspd','lspd','sbcd','lbcd','sded','lded','shld','lhld',
'anax','xrax','orax','addncx','gtax','subnbx','ltax','addx','onax','adcx','offax','subx','neax','sbbx','eqax',
'anaxi','xraxi','oraxi','addncxi','gtaxi','subnbxi','ltaxi','addxi','onaxi','adcxi','offaxi','subxi','neaxi','sbbxi','eqaxi',
'anaxd','xraxd','oraxd','addncxd','gtaxd','subnbxd','ltaxd','addxd','onaxd','adcxd','offaxd','subxd','neaxd','sbbxd','eqaxd',
}
local Registers_7801 = {
a=8,b=8,c=8,d=8,e=8,h=8,l=8,v=8,
bc=16,de=16,hl=16,sp=16,va=16,
pa=8,pb=8,pc=8,mk=8,mb=8,mc=8,tm0=8,tm1=8,s=8,
}
local Interrupts_7801 = lookupify{
'f0','ft','f1','f2','fs'
}
local function syntax7801(on)
syntax7801_on = on
lookupify(Keywords_control, Keywords, not on)
lookupify(Keywords_data, Keywords, not on)
lookupify(Keywords_7801, Keywords, not on)
end
syntax7801(true)
local opcode_arg_encapsulate_on
local function opcode_arg_encapsulate(on)
opcode_arg_encapsulate_on = not opcode_arg_encapsulate_on
end
opcode_arg_encapsulate(true)
local opcode_encapsulate = {} -- additionnal opcode, to have basic encapsulation (function(a) return a end)
local opcode_alias = {} -- alternate user names for opcodes
local opcode_implied = lookupify{
'block','calb','clc','ei','daa','dcr','di','ex','exx','halt','inr','jb','nop','pen',
'per','pex','ret','reti','rets','rld','rrd','sio','skc','skz','sknc','sknz','softi',
'stc','stm','table'
}
local opcode_immediate = lookupify{
'calf','calt','call','in','jmp','out',
}
local opcode_wa = lookupify{
'inrw','ldaw','dcrw','staw',
'bit0','bit1','bit2','bit3','bit4','bit5','bit6','bit7',
'anaw','xraw','oraw','addncw','gtaw','subnbw','ltaw','addw','onaw','adcw','offaw','subw','neaw','sbbw','eqaw',
}
local opcode_wab = lookupify{
'mviw','eqiw'
}
local opcode_relative = lookupify{
'jr','jre'
}
local opcode_reg = lookupify{
'dcr','dcx','inr','inx','pop','push','rll','rlr','sll','slr',
}
local opcode_mov = lookupify{
'mov'
}
local opcode_reg_reg = lookupify{
'ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa','ona','offa'
}
local opcode_regb = lookupify{
'aci','adi','adinc','ani','eqi','gti','lti','mvi','nei','offi','oni','ori','sbi','sui','suinb','xri',
}
local opcode_reg_ind = lookupify{
'ldax','ldaxd','ldaxi',
'stax','staxd','staxi',
'anax','xrax','orax','addncx','gtax','subnbx','ltax','addx','onax','adcx','offax','subx','neax','sbbx','eqax',
'anaxi','xraxi','oraxi','addncxi','gtaxi','subnbxi','ltaxi','addxi','onaxi','adcxi','offaxi','subxi','neaxi','sbbxi','eqaxi',
'anaxd','xraxd','oraxd','addncxd','gtaxd','subnbxd','ltaxd','addxd','onaxd','adcxd','offaxd','subxd','neaxd','sbbxd','eqaxd',
}
local opcode_reg_ind_ex = lookupify{
'mvix'
}
local opcode_regw = lookupify{
'lxi'
}
local opcode_timer = lookupify{
'skit','sknit',
}
local opcode_ind = lookupify{
'sspd','lspd','sbcd','lbcd','sded','lded','shld','lhld',
}
local opcode_reg_list = {
a = lookupify{'aci','adi','adinc','ani','dcr','inr','eqi','gti','lti','mvi','nei','offi','oni','ori','rll','rlr','sbi','sll','slr','sui','suinb','xri'},
b = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi','dcr','inr','mvi'},
c = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi','dcr','inr','mvi','rll','rlr','sll','slr'},
d = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi','mvi'},
e = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi','mvi'},
h = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi','mvi'},
l = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi','mvi'},
v = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi',
'inrw','ldaw','dcrw','eqiw','mvi','mviw','staw',
'bit0','bit1','bit2','bit3','bit4','bit5','bit6','bit7',
'anaw','xraw','oraw','addncw','gtaw','subnbw','ltaw','addw','onaw','adcw','offaw','subw','neaw','sbbw','eqaw',
},
bc = lookupify{
'ldax','lxi','mvix','pop','push','stax',
'anax','xrax','orax','addncx','gtax','subnbx','ltax','addx','onax','adcx','offax','subx','neax','sbbx','eqax',
},
de = lookupify{
'ldax','ldaxd','ldaxi','lxi','mvix','pop','push','stax','staxd','staxi',
'anax','xrax','orax','addncx','gtax','subnbx','ltax','addx','onax','adcx','offax','subx','neax','sbbx','eqax',
'anaxi','xraxi','oraxi','addncxi','gtaxi','subnbxi','ltaxi','addxi','onaxi','adcxi','offaxi','subxi','neaxi','sbbxi','eqaxi',
'anaxd','xraxd','oraxd','addncxd','gtaxd','subnbxd','ltaxd','addxd','onaxd','adcxd','offaxd','subxd','neaxd','sbbxd','eqaxd',
},
hl = lookupify{
'dcx','inx','ldax','ldaxd','ldaxi','lxi','mvix','pop','push','stax','staxd','staxi',
'anax','xrax','orax','addncx','gtax','subnbx','ltax','addx','onax','adcx','offax','subx','neax','sbbx','eqax',
'anaxi','xraxi','oraxi','addncxi','gtaxi','subnbxi','ltaxi','addxi','onaxi','adcxi','offaxi','subxi','neaxi','sbbxi','eqaxi',
'anaxd','xraxd','oraxd','addncxd','gtaxd','subnbxd','ltaxd','addxd','onaxd','adcxd','offaxd','subxd','neaxd','sbbxd','eqaxd',
},
sp = lookupify{'dcx','inx','lxi'},
va = lookupify{'pop','push'},
pa = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi'},
pb = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi'},
pc = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi'},
mk = lookupify{'ani','xri','ori','adinc','gti','suinb','lti','adi','oni','aci','offi','sui','nei','sbi','eqi'},
}
local opcode_reg_reg_list = {
a = {
a = lookupify{'ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa','ona','offa'},
b = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa','ona','offa'},
c = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa','ona','offa'},
d = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa','ona','offa'},
e = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa','ona','offa'},
h = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa','ona','offa'},
l = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa','ona','offa'},
v = lookupify{'ana','xra','ora','addnc','gta','subnb','lta','add','','adc','','sub','nea','sbb','eqa','ona','offa'},
pa = lookupify{'mov'},
pb = lookupify{'mov'},
pc = lookupify{'mov'},
mk = lookupify{'mov'},
mb = lookupify{'mov'},
mc = lookupify{'mov'},
tm0 = lookupify{'mov'},
tm1 = lookupify{'mov'},
s = lookupify{'mov'},
},
v = { a = lookupify{'ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa'} },
b = { a = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa'} },
c = { a = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa'} },
d = { a = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa'} },
e = { a = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa'} },
h = { a = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa'} },
l = { a = lookupify{'mov','ana','xra','ora','addnc','gta','subnb','lta','add','adc','sub','nea','sbb','eqa'} },
pa = { a = lookupify{'mov'} },
pb = { a = lookupify{'mov'} },
pc = { a = lookupify{'mov'} },
mk = { a = lookupify{'mov'} },
mb = { a = lookupify{'mov'} },
mc = { a = lookupify{'mov'} },
tm0 = { a = lookupify{'mov'} },
tm1 = { a = lookupify{'mov'} },
s = { a = lookupify{'mov'} },
}
local registers_8 = lookupify {'v','a','b','c','d','e','h','l'}
local register_names = {'v','a','b','c','d','e','h','l'}
local addressing_map = {
imp = opcode_implied,
wa = opcode_wa,
wab = opcode_wab,
imm = opcode_immediate,
rel = opcode_relative,
reg = opcode_reg,
regb = opcode_regb,
regw = opcode_regw,
reg_ind = opcode_reg_ind,
reg_ind_ex = opcode_reg_ind_ex,
timer=opcode_timer,
}
local Scope = {
new = function(self, parent)
local s = {
Parent = parent,
Locals = { },
Globals = { },
Children = { },
}
if parent then
table.insert(parent.Children, s)
end
return setmetatable(s, { __index = self })
end,
AddLocal = function(self, v)
table.insert(self.Locals, v)
end,
AddGlobal = function(self, v)
table.insert(self.Globals, v)
end,
CreateLocal = function(self, name)
local v
v = self:GetLocal(name)
if v then return v end
v = { }
v.Scope = self
v.Name = name
v.IsGlobal = false
self:AddLocal(v)
return v
end,
GetLocal = function(self, name)
for k, var in pairs(self.Locals) do
if var.Name == name then return var end
end
if self.Parent then
return self.Parent:GetLocal(name)
end
end,
CreateGlobal = function(self, name)
local v
v = self:GetGlobal(name)
if v then return v end
v = { }
v.Scope = self
v.Name = name
v.IsGlobal = true
self:AddGlobal(v)
return v
end,
GetGlobal = function(self, name)
for k, v in pairs(self.Globals) do
if v.Name == name then return v end
end
if self.Parent then
return self.Parent:GetGlobal(name)
end
end,
}
local function LexLua(src)
--token dump
local tokens = {}
local st, err = pcall(function()
--line / char / pointer tracking
local p = 1
local line = 1
local char = 1
--get / peek functions
local function get()
local c = src:sub(p,p)
if c == '\n' then
char = 1
line = line + 1
else
char = char + 1
end
p = p + 1
return c
end
local function get_n(count) for i=1,count do get() end end
local function peek(n)
n = n or 0
return src:sub(p+n,p+n)
end
local function peek_n(sz) return src:sub(p, p+sz-1) end
local function consume(chars)
local c = peek()
for i = 1, #chars do
if c == chars:sub(i,i) then return get() end
end
end
local function get_word(spaces)
if not spaces then spaces = Spaces end
local c
repeat get() c = peek() until not spaces[c]
local start = p
repeat get() c = peek() until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_')
return src:sub(start, p-1)
end
local function peek_ident(i, spaces)
if not spaces then spaces = Spaces end
local c
while true do c=peek(i) if not spaces[c] then break end i=i+1 end
if not (UpperChars[c] or LowerChars[c] or c == '_') then return end
local start = p+i
repeat i=i+1 c = peek(i) until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_')
return src:sub(start, p+i-1),i,c
end
--shared stuff
local function generateError(err)
return error(">> :"..line..":"..char..": "..err, 0)
end
local function tryGetLongString()
local start = p
if peek() == '[' then
local equalsCount = 0
local depth = 1
while peek(equalsCount+1) == '=' do
equalsCount = equalsCount + 1
end
if peek(equalsCount+1) == '[' then
--start parsing the string. Strip the starting bit
for _ = 0, equalsCount+1 do get() end
--get the contents
local contentStart = p
while true do
--check for eof
if peek() == '' then
generateError("Expected ']"..string.rep('=', equalsCount).."]' near <eof>.", 3)
end
--check for the end
local foundEnd = true
if peek() == ']' then
for i = 1, equalsCount do
if peek(i) ~= '=' then foundEnd = false end
end
if peek(equalsCount+1) ~= ']' then
foundEnd = false
end
else
--[[
if peek() == '[' then
-- is there an embedded long string?
local embedded = true
for i = 1, equalsCount do
if peek(i) ~= '=' then
embedded = false
break
end
end
if peek(equalsCount + 1) == '[' and embedded then
-- oh look, there was
depth = depth + 1
for i = 1, (equalsCount + 2) do
get()
end
end
end
]]
foundEnd = false
end
--
if foundEnd then
depth = depth - 1
if depth == 0 then
break
else
for i = 1, equalsCount + 2 do
get()
end
end
else
get()
end
end
--get the interior string
local contentString = src:sub(contentStart, p-1)
--found the end. Get rid of the trailing bit
for i = 0, equalsCount+1 do get() end
--get the exterior string
local longString = src:sub(start, p-1)
--return the stuff
return contentString, longString
else
return nil
end
else
return nil
end
end
--main token emitting loop
while true do
--get leading whitespace. The leading whitespace will include any comments
--preceding the token. This prevents the parser needing to deal with comments
--separately.
local leading = { }
local leadingWhite = ''
local longStr = false
while true do
local c = peek()
if c == '#' and peek(1) == '!' and line == 1 then
-- #! shebang for linux scripts
get()
get()
leadingWhite = "#!"
while peek() ~= '\n' and peek() ~= '' do
leadingWhite = leadingWhite .. get()
end
local token = {
Type = 'Comment',
CommentType = 'Shebang',
Data = leadingWhite,
Line = line,
Char = char
}
token.Print = function()
return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >"
end
leadingWhite = ""
table.insert(leading, token)
end
if c == ' ' or c == '\t' then
--whitespace
--leadingWhite = leadingWhite..get()
local c2 = get() -- ignore whitespace
table.insert(leading, { Type = 'Whitespace', Line = line, Char = char, Data = c2 })
elseif c == '\n' or c == '\r' then
local nl = get()
if leadingWhite ~= "" then
local token = {
Type = 'Comment',
CommentType = longStr and 'LongComment' or 'Comment',
Data = leadingWhite,
Line = line,
Char = char,
}
token.Print = function()
return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >"
end
table.insert(leading, token)
leadingWhite = ""
end
table.insert(leading, { Type = 'Whitespace', Line = line, Char = char, Data = nl })
elseif c == '-' and peek(1) == '-' then
--comment
get()
get()
leadingWhite = leadingWhite .. '--'
local _, wholeText = tryGetLongString()
if wholeText then
leadingWhite = leadingWhite..wholeText
longStr = true
else
while peek() ~= '\n' and peek() ~= '' do
leadingWhite = leadingWhite..get()
end
end
else
break
end
end
if leadingWhite ~= "" then
local token = {
Type = 'Comment',
CommentType = longStr and 'LongComment' or 'Comment',
Data = leadingWhite,
Line = line,
Char = char,
}
token.Print = function()
return "<"..(token.Type .. string.rep(' ', 7-#token.Type)).." "..(token.Data or '').." >"
end
table.insert(leading, token)
end
--get the initial char
local thisLine = line
local thisChar = char
local errorAt = ":"..line..":"..char..":> "
local c = peek()
--symbol to emit
local toEmit = nil
--pragma
if char == 1 and peek_n(7) == '#pragma' then
get_n(7)
local dat,opt = get_word()
local onoff = function(f, noerr)
opt = get_word()
if opt == 'on' then f(true)
elseif opt == 'off' then f(false)
elseif not noerr then generateError("invalid option for pragma " .. dat .. ", expected: [on,off]")
end
return opt
end
if dat == 'encapsulate' then
local opcode = onoff(function() end, true)
if opcode then
opcode_encapsulate[opcode] = get_word()
table.insert(Keywords_7801, opcode)
Keywords[opcode] = syntax7801_on
toEmit = {Type = 'Symbol', Data = ';'}
else
toEmit = {Type = 'Keyword', Data = 'encapsulate_' .. opt}
end
elseif dat == 'add_opcode' then
local opcode,addressing = get_word(),get_word()
local map = addressing_map[addressing]
if not map then generateError("invalid addressing for pragma add_opcode: " .. addressing .. " (opcode: " .. opcode .. ")") end
map[opcode] = true
table.insert(Keywords_7801, opcode)
Keywords[opcode] = syntax7801_on
toEmit = {Type = 'Symbol', Data = ';'}
elseif dat == 'alias' then
local org,new = get_word(),get_word()
opcode_alias[new] = org
table.insert(Keywords_7801, new)
Keywords[new] = syntax7801_on
toEmit = {Type = 'Symbol', Data = ';'}
else generateError("unknown pragma: " .. dat)
end
--branch on type
elseif c == '' then
--eof
toEmit = { Type = 'Eof' }
elseif UpperChars[c] or LowerChars[c] or c == '_' then
--ident or keyword
local start = p
repeat
get()
c = peek()
until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_')
local dat = src:sub(start, p-1)
if Keywords[dat] then
toEmit = {Type = 'Keyword', Data = dat}
else
toEmit = {Type = 'Ident', Data = dat}
end
elseif Digits[c] or (peek() == '.' and Digits[peek(1)]) then
--number const
local start = p
local data_override
if c == '0' and peek(1) == 'x' then
get();get()
while HexDigits[peek()] do get() end
if consume('Pp') then
consume('+-')
while Digits[peek()] do get() end
end
elseif c == '0' and peek(1) == 'b' then
get();get()
while BinDigits[peek()] do get() end
local pstart, val_s, val = p, src:sub(start+2, p-1), 0
for i=1,#val_s do
if val_s:sub(i,i) == '1' then val = val + (1<<(#val_s-i)) end
end
if consume('Pp') then
consume('+-')
while Digits[peek()] do get() end
end
data_override = val .. src:sub(pstart, p-1)
else
while Digits[peek()] do get() end
if consume('.') then
while Digits[peek()] do get() end
end
if consume('Ee') then
consume('+-')
while Digits[peek()] do get() end
end
end
toEmit = {Type = 'Number', Data = data_override or src:sub(start, p-1)}
elseif c == '\'' or c == '\"' then
local start = p
--string const
local delim = get()
local contentStart = p
while true do
local c = get()
if c == '\\' then
get() --get the escape char
elseif c == delim then
break
elseif c == '' then
generateError("Unfinished string near <eof>")
end
end
local content = src:sub(contentStart, p-2)
local constant = src:sub(start, p-1)
toEmit = {Type = 'String', Data = constant, Constant = content}
elseif c == '[' then
local content, wholetext = tryGetLongString()
if wholetext then
toEmit = {Type = 'String', Data = wholetext, Constant = content}
else
get()
toEmit = {Type = 'Symbol', Data = '['}
end
elseif c == '<' and peek(1) == c then
get() get()
toEmit = {Type = 'Symbol', Data = '<<'}
elseif c == '>' and peek(1) == c then
get() get()
toEmit = {Type = 'Symbol', Data = '>>'}
elseif consume('>=<') then
toEmit = {Type = 'Symbol', Data = consume('=') and c..'=' or c}
elseif consume('~') then
toEmit = {Type = 'Symbol', Data = consume('=') and '~=' or '~'}
elseif consume('.') then
if consume('.') then
if consume('.') then
toEmit = {Type = 'Symbol', Data = '...'}
else
toEmit = {Type = 'Symbol', Data = '..'}
end
else
toEmit = {Type = 'Symbol', Data = '.'}
end
elseif consume(':') then
toEmit = {Type = 'Symbol', Data = consume(':') and '::' or ':'}
elseif consume('/') then
toEmit = {Type = 'Symbol', Data = consume('/') and '//' or '/'}
elseif syntax7801_on and consume('@') then
if consume('@') then
toEmit = {Type = 'Symbol', Data = '@@'}
else
toEmit = {Type = 'Symbol', Data = '@'}
end
elseif syntax7801_on and consume('\\') then
toEmit = {Type = 'Symbol', Data = '\\'}
elseif Symbols[c] then
get()
toEmit = {Type = 'Symbol', Data = c}
else
local contents, all = tryGetLongString()
if contents then
toEmit = {Type = 'String', Data = all, Constant = contents}
else
generateError("Unexpected Symbol '"..c.."' in source.", 2)
end
end
if toEmit[1] then
toEmit[1].LeadingWhite = leading
for k,v in ipairs(toEmit) do
v.Line = thisLine
v.Char = thisChar
v.Print = function()
return "<"..(v.Type..string.rep(' ', 7-#v.Type)).." "..(v.Data or '').." >"
end
tokens[#tokens+1] = v
end
else
--add the emitted symbol, after adding some common data
toEmit.LeadingWhite = leading -- table of leading whitespace/comments
--for k, tok in pairs(leading) do
-- tokens[#tokens + 1] = tok
--end
toEmit.Line = thisLine
toEmit.Char = thisChar
toEmit.Print = function()
return "<"..(toEmit.Type..string.rep(' ', 7-#toEmit.Type)).." "..(toEmit.Data or '').." >"
end
tokens[#tokens+1] = toEmit
--halt after eof has been emitted
if toEmit.Type == 'Eof' then break end
end
end
end)
if not st then
return false, err
end
--public interface:
local tok = {}
local savedP = {}
local p = 1
function tok:getp()
return p
end
function tok:setp(n)
p = n
end
function tok:getTokenList()
return tokens
end
--getters
function tok:Peek(n)
n = n or 0
return tokens[math.min(#tokens, p+n)]
end
function tok:Get(tokenList)
local t = tokens[p]
p = math.min(p + 1, #tokens)
if tokenList then
table.insert(tokenList, t)
end
return t
end
function tok:Is(t)
return tok:Peek().Type == t
end
--save / restore points in the stream
function tok:Save()
savedP[#savedP+1] = p
end
function tok:Commit()
savedP[#savedP] = nil
end
function tok:Restore()
p = savedP[#savedP]
savedP[#savedP] = nil
end
--either return a symbol if there is one, or return true if the requested
--symbol was gotten.
function tok:ConsumeSymbol(symb, tokenList)
local t = self:Peek()
if t.Type == 'Symbol' then
if symb then
if t.Data == symb then
self:Get(tokenList)
return true
else
return nil
end
else
self:Get(tokenList)
return t
end
else
return nil
end
end
function tok:ConsumeKeyword(kw, tokenList)
local t = self:Peek()
if t.Type == 'Keyword' and t.Data == kw then
self:Get(tokenList)
return true
else
return nil
end
end
function tok:IsKeyword(kw)
local t = tok:Peek()
return t.Type == 'Keyword' and t.Data == kw
end
function tok:IsSymbol(s)
local t = tok:Peek()
return t.Type == 'Symbol' and t.Data == s
end
function tok:IsEof()
return tok:Peek().Type == 'Eof'
end
return true, tok
end
local function ParseLua(src, src_name)
local st, tok
if type(src) ~= 'table' then
st, tok = LexLua(src)
else
st, tok = true, src
end
if not st then
return false, tok
end
--
local function GenerateError(msg)
local err = (src_name or '=(string)') .. ":"..tok:Peek().Line..":"..tok:Peek().Char..": "..msg.."\n "
--find the line
local lineNum = 0
if type(src) == 'string' then
for line in src:gmatch("[^\n]*\n?") do
if line:sub(-1,-1) == '\n' then line = line:sub(1,-2) end
lineNum = lineNum+1
if lineNum == tok:Peek().Line then
err = err..line:gsub('\t',' ').."\n"
for i = 1, tok:Peek().Char do
local c = line:sub(i,i)
if c == '\t' then
err = err..' '
else
err = err..' '
end
end
err = err.."^^^^"
break
end
end
end
return err
end
--
local VarUid = 0
local function CreateScope(parent)
local scope = Scope:new(parent)
scope.Print = function() return "<Scope>" end
return scope
end
local ParseExpr
local ParseStatementList
local ParseSimpleExpr,
ParseSubExpr,
ParsePrimaryExpr,
ParseSuffixedExpr
local function ParseFunctionArgsAndBody(scope, tokenList)
local funcScope = CreateScope(scope)
if not tok:ConsumeSymbol('(', tokenList) then
return false, GenerateError("'(' expected")
end
--arg list
local argList = {}
local isVarArg = false
while not tok:ConsumeSymbol(')', tokenList) do
if tok:Is('Ident') then
local arg = funcScope:CreateLocal(tok:Get(tokenList).Data)
argList[#argList+1] = arg
if not tok:ConsumeSymbol(',', tokenList) then
if tok:ConsumeSymbol(')', tokenList) then
break
else
return false, GenerateError("')' expected")
end
end
elseif tok:ConsumeSymbol('...', tokenList) then
isVarArg = true
if not tok:ConsumeSymbol(')', tokenList) then
return false, GenerateError("'...' must be the last argument of a function")
end
break
else
return false, GenerateError("Argument name or '...' expected")
end
end
--body
local st, body = ParseStatementList(funcScope)
if not st then return false, body end
--end
if not tok:ConsumeKeyword('end', tokenList) then
return false, GenerateError("'end' expected after function body")
end
local nodeFunc = {}
nodeFunc.AstType = 'Function'
nodeFunc.Scope = funcScope
nodeFunc.Arguments = argList
nodeFunc.Body = body
nodeFunc.VarArg = isVarArg
nodeFunc.Tokens = tokenList
--
return true, nodeFunc
end
function ParsePrimaryExpr(scope)
local tokenList = {}
if tok:ConsumeSymbol('(', tokenList) then
local st, ex = ParseExpr(scope)
if not st then return false, ex end
if not tok:ConsumeSymbol(')', tokenList) then
return false, GenerateError("')' expected")
end
if false then
--save the information about parenthesized expressions somewhere
ex.ParenCount = (ex.ParenCount or 0) + 1
return true, ex
else
local parensExp = {}
parensExp.AstType = 'Parentheses'
parensExp.Inner = ex
parensExp.Tokens = tokenList
return true, parensExp
end
elseif tok:Is('Ident') then
local id = tok:Get(tokenList)
local var = scope:GetLocal(id.Data)
if not var then
var = scope:GetGlobal(id.Data)
if not var then
var = scope:CreateGlobal(id.Data)
end
end
--
local nodePrimExp = {}
nodePrimExp.AstType = 'VarExpr'
nodePrimExp.Name = id.Data
nodePrimExp.Variable = var
nodePrimExp.Tokens = tokenList
--
return true, nodePrimExp
else
return false, GenerateError("Primary expression expected")
end
end
function ParseSuffixedExpr(scope, onlyDotColon)
--base primary expression
local st, prim = ParsePrimaryExpr(scope)
if not st then return false, prim end
--
while true do
local tokenList = {}
if tok:IsSymbol('.') or tok:IsSymbol(':') then
local symb = tok:Get(tokenList).Data
if not tok:Is('Ident') then
return false, GenerateError("Identifier expected")
end
local id = tok:Get(tokenList)
local nodeIndex = {}
nodeIndex.AstType = 'MemberExpr'
nodeIndex.Base = prim
nodeIndex.Indexer = symb
nodeIndex.Ident = id
nodeIndex.Tokens = tokenList
--
prim = nodeIndex
elseif not onlyDotColon and tok:ConsumeSymbol('[', tokenList) then
local st, ex = ParseExpr(scope)
if not st then return false, ex end
if not tok:ConsumeSymbol(']', tokenList) then
return false, GenerateError("']' expected")
end
local nodeIndex = {}
nodeIndex.AstType = 'IndexExpr'
nodeIndex.Base = prim
nodeIndex.Index = ex
nodeIndex.Tokens = tokenList
--
prim = nodeIndex
elseif not onlyDotColon and tok:ConsumeSymbol('(', tokenList) then
local args = {}
while not tok:ConsumeSymbol(')', tokenList) do
local st, ex = ParseExpr(scope)
if not st then return false, ex end
args[#args+1] = ex
if not tok:ConsumeSymbol(',', tokenList) then
if tok:ConsumeSymbol(')', tokenList) then
break
else
return false, GenerateError("')' expected")
end
end
end
local nodeCall = {}
nodeCall.AstType = 'CallExpr'
nodeCall.Base = prim
nodeCall.Arguments = args
nodeCall.Tokens = tokenList
--
prim = nodeCall
elseif not onlyDotColon and tok:Is('String') then
--string call
local nodeCall = {}
nodeCall.AstType = 'StringCallExpr'
nodeCall.Base = prim
nodeCall.Arguments = { tok:Get(tokenList) }
nodeCall.Tokens = tokenList
--
prim = nodeCall
elseif not onlyDotColon and tok:IsSymbol('{') then
--table call
local st, ex = ParseSimpleExpr(scope)
-- FIX: ParseExpr(scope) parses the table AND and any following binary expressions.
-- We just want the table
if not st then return false, ex end
local nodeCall = {}
nodeCall.AstType = 'TableCallExpr'
nodeCall.Base = prim
nodeCall.Arguments = { ex }
nodeCall.Tokens = tokenList
--
prim = nodeCall
else
break
end
end
return true, prim
end
function ParseSimpleExpr(scope)
local tokenList = {}
if tok:Is('Number') then
local nodeNum = {}
nodeNum.AstType = 'NumberExpr'
nodeNum.Value = tok:Get(tokenList)
nodeNum.Tokens = tokenList
return true, nodeNum
elseif tok:Is('String') then
local nodeStr = {}
nodeStr.AstType = 'StringExpr'
nodeStr.Value = tok:Get(tokenList)
nodeStr.Tokens = tokenList
return true, nodeStr
elseif tok:ConsumeKeyword('nil', tokenList) then
local nodeNil = {}
nodeNil.AstType = 'NilExpr'
nodeNil.Tokens = tokenList
return true, nodeNil
elseif tok:IsKeyword('false') or tok:IsKeyword('true') then
local nodeBoolean = {}
nodeBoolean.AstType = 'BooleanExpr'
nodeBoolean.Value = (tok:Get(tokenList).Data == 'true')
nodeBoolean.Tokens = tokenList
return true, nodeBoolean
elseif tok:ConsumeSymbol('...', tokenList) then
local nodeDots = {}
nodeDots.AstType = 'DotsExpr'
nodeDots.Tokens = tokenList
return true, nodeDots
elseif tok:ConsumeSymbol('{', tokenList) then
local v = {}
v.AstType = 'ConstructorExpr'
v.EntryList = {}
--
while true do
if tok:IsSymbol('[', tokenList) then
--key
tok:Get(tokenList)
local st, key = ParseExpr(scope)
if not st then
return false, GenerateError("Key expression expected")
end
if not tok:ConsumeSymbol(']', tokenList) then
return false, GenerateError("']' expected")
end
if not tok:ConsumeSymbol('=', tokenList) then
return false, GenerateError("'=' expected")
end
local st, value = ParseExpr(scope)
if not st then
return false, GenerateError("Value expression expected")
end
v.EntryList[#v.EntryList+1] = {
Type = 'Key';
Key = key;
Value = value;
}
elseif tok:Is('Ident') then
--value or key
local lookahead = tok:Peek(1)
if lookahead.Type == 'Symbol' and lookahead.Data == '=' then
--we are a key
local key = tok:Get(tokenList)
if not tok:ConsumeSymbol('=', tokenList) then
return false, GenerateError("'=' expected")
end
local st, value = ParseExpr(scope)
if not st then
return false, GenerateError("Value expression expected")
end
v.EntryList[#v.EntryList+1] = {
Type = 'KeyString';
Key = key.Data;
Value = value;
}
else
--we are a value
local st, value = ParseExpr(scope)
if not st then
return false, GenerateError("Value expected")
end
v.EntryList[#v.EntryList+1] = {
Type = 'Value';
Value = value;
}
end
elseif tok:ConsumeSymbol('}', tokenList) then
break
else
--value
local st, value = ParseExpr(scope)
v.EntryList[#v.EntryList+1] = {
Type = 'Value';
Value = value;
}
if not st then
return false, GenerateError("Value expected")
end
end
if tok:ConsumeSymbol(';', tokenList) or tok:ConsumeSymbol(',', tokenList) then
--all is good
elseif tok:ConsumeSymbol('}', tokenList) then
break
else
return false, GenerateError("'}' or table entry expected")
end
end
v.Tokens = tokenList
return true, v
elseif tok:ConsumeKeyword('function', tokenList) then
local st, func = ParseFunctionArgsAndBody(scope, tokenList)
if not st then return false, func end
--
func.IsLocal = true
return true, func
elseif tok:ConsumeSymbol('\\', tokenList) then -- short lambdas \params(expr)
local funcScope = CreateScope(scope)
local argList = {}
if not tok:ConsumeSymbol('(', tokenList) then while true do
if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end
argList[#argList+1] = funcScope:CreateLocal(tok:Get(tokenList).Data)
if tok:ConsumeSymbol('(', tokenList) then break end
if not tok:ConsumeSymbol(',', tokenList) then return false, GenerateError("'(' expected") end
end end
local st, body = ParseExpr(funcScope)
if not st then return false, body end
if not tok:ConsumeSymbol(')', tokenList) then return false, GenerateError("')' expected after lambda body") end
local last_tok = body.Tokens[1]
local last_char,last_line = last_tok.Char,last_tok.Line
local space = { Char=last_char, Line=last_line, Data=' ', Type='Whitespace' }
local ret = { AstType='ReturnStatement', Arguments={body}, TrailingWhite=true, Tokens={
{ Char=last_char, Line=last_line, Print=function() return '<Keyword return >' end, LeadingWhite={space}, Type='Keyword', Data='return' }
}}
local statList = { AstType='Statlist', Scope=CreateScope(funcScope), Tokens={}, Body={ret} }
local tok_first = tokenList[1]
tok_first.Type = 'Keyword'
tok_first.Data = 'function'
tok_first.Print=function() return '<Keyword function >' end
local ntokl = {}
local paren_ix = 2 + math.max(0, #argList*2-1)
table.insert(ntokl, tokenList[1])
table.insert(ntokl, tokenList[paren_ix])
for i=2,paren_ix-1 do table.insert(ntokl, tokenList[i]) end
table.insert(ntokl, tokenList[#tokenList])
table.insert(ntokl, { Char=last_char, Line=last_line, Type='Keyword', Data='end', Print=function() return '<Keyword end >' end, LeadingWhite={space} })
local func = { AstType='Function', Scope=funcScope, Arguments=argList, Body=statList, VarArg=false, Tokens=ntokl, isLocal=true }
return true, func
else
return ParseSuffixedExpr(scope)
end
end
local unops = lookupify{'-', 'not', '#', '~'}
local unopprio = 12
local priority = {
['^'] = {14,13};
['%'] = {11,11};
['//'] = {11,11};
['/'] = {11,11};
['*'] = {11,11};
['+'] = {10,10};
['-'] = {10,10};
['..'] = {9,8};
['>>'] = {7,7};
['<<'] = {7,7};
['&'] = {6,6};
['~'] = {5,5};
['|'] = {4,4};
['=='] = {3,3};
['<'] = {3,3};
['<='] = {3,3};
['~='] = {3,3};
['>'] = {3,3};
['>='] = {3,3};
['and'] = {2,2};
['or'] = {1,1};
}
function ParseSubExpr(scope, level)
--base item, possibly with unop prefix
local st, exp
if unops[tok:Peek().Data] then
local tokenList = {}
local op = tok:Get(tokenList).Data
st, exp = ParseSubExpr(scope, unopprio)
if not st then return false, exp end
local nodeEx = {}
nodeEx.AstType = 'UnopExpr'
nodeEx.Rhs = exp
nodeEx.Op = op
nodeEx.OperatorPrecedence = unopprio
nodeEx.Tokens = tokenList
exp = nodeEx
else
st, exp = ParseSimpleExpr(scope)
if not st then return false, exp end
end
--next items in chain
while true do
local prio = priority[tok:Peek().Data]
if prio and prio[1] > level then
local tokenList = {}
local op = tok:Get(tokenList).Data
local st, rhs = ParseSubExpr(scope, prio[2])
if not st then return false, rhs end
local nodeEx = {}
nodeEx.AstType = 'BinopExpr'
nodeEx.Lhs = exp
nodeEx.Op = op
nodeEx.OperatorPrecedence = prio[1]
nodeEx.Rhs = rhs
nodeEx.Tokens = tokenList
--
exp = nodeEx
else
break
end
end
return true, exp
end
ParseExpr = function(scope)
return ParseSubExpr(scope, 0)
end
local pragma_map = {
encapsulate_on = function() opcode_arg_encapsulate(true) end,
encapsulate_off = function() opcode_arg_encapsulate(false) end,
syntax7801k_on = function() syntax7801k(true) end,
syntax7801k_off = function() syntax7801k(false) end,
}
local function ParseStatement(scope)
local stat = nil
local tokenList = {}
local commaTokenList = {}
local l,c = function() return tokenList[1].Line end, function() return tokenList[1].Char end
local p = function(t,n) return function() return '<' .. t .. string.rep(' ', 7-#t) .. ' ' .. n .. ' >' end end
local t = function(t,s,w) return { Type=t, Data=s, Print=p(t,s), Char=c(), Line=l(), LeadingWhite=w or {} } end
local no_encapsulation = { Function=true, NumberExpr=true, StringExpr=true }
local reg = function(e) local t=e.Tokens[1] if t then return Registers_7801[t.Data] end end
local function emit_call(params)
local name,args,inverse_encapsulate = params.name, params.args or {}, params.inverse_encapsulate
if not params.func_white then params.func_white = tokenList[1].LeadingWhite end
local space = { Char=c(), Line=l(), Data=' ', Type='Whitespace' }
local op_var = {
AstType='VarExpr', Name=name, Variable={ IsGlobal=true, Name=name, Scope=CreateScope(scope) }, Tokens = { t('Ident', name, params.func_white) }
}
local encapsulate = params.encapsulate
if encapsulate == nil then encapsulate = opcode_arg_encapsulate_on end
if type(inverse_encapsulate) == 'table' then
-- this is a list of arguments, where each can be encapsulated
for arg_ix,arg in ipairs(args) do
if ( (encapsulate and not inverse_encapsulate[arg_ix]) or (not encapsulate and inverse_encapsulate[arg_ix]) ) and not no_encapsulation[arg.AstType] then
local inner_call_scope = CreateScope(op_var.Variable.Scope)
local inner_call_body = {
AstType='StatList', Scope=CreateScope(inner_call_scope), Tokens={}, Body={
{ AstType='ReturnStatement', Arguments={arg}, Tokens={ t('Keyword', 'return', {space}) } }
}
}
local inner_call = {
AstType='Function', VarArg=false, IsLocal=true, Scope=inner_call_scope, Body=inner_call_body, Arguments={},
Tokens={ t('Keyword', 'function'), t('Symbol', '('), t('Symbol', ')'), t('Keyword', 'end', {space}) }
}
if #arg.Tokens[1].LeadingWhite == 0 then table.insert(arg.Tokens[1].LeadingWhite, space) end
args[arg_ix] = inner_call
end
end
elseif #args > 0 and ( (encapsulate and not inverse_encapsulate) or (not encapsulate and inverse_encapsulate) ) and not no_encapsulation[args[1].AstType] then
-- opcode arguments of type (late, early), where only late is to be encapsulated with _o parameter to be set to early and added to _f(late)
local inner_call_scope,inner_call = CreateScope(op_var.Variable.Scope)
if params.basic then
local inner_call_body = {
AstType='StatList', Scope=CreateScope(inner_call_scope), Tokens={}, Body={
{ AstType='ReturnStatement', Arguments={args[1]}, Tokens={ t('Keyword', 'return', {space}) } }
}
}
inner_call = {
AstType='Function', VarArg=false, IsLocal=true, Scope=inner_call_scope, Body=inner_call_body, Arguments={},
Tokens={ t('Keyword', 'function'), t('Symbol', '('), t('Symbol', ')'), t('Keyword', 'end', {space}) }
}
else
local fvarexpr = {
AstType='VarExpr', Name='_f', Variable={ Name='_f', Scope=CreateScope(inner_call_scope) }, Tokens = { t('Ident', '_f') }
}
local inner_add = {
AstType='BinopExpr', Op='+', OperatorPrecedence=10, Tokens={ t('Symbol', '+') },
Lhs = {
AstType='VarExpr', Name='_o', Variable={ IsGlobal=false, Name='_o', Scope=inner_call_scope }, Tokens={ t('Ident', '_o', {space}) }
},
Rhs = {
AstType = 'CallExpr', Base = fvarexpr, Arguments = {args[1]}, Tokens = { t('Symbol', '('), t('Symbol', ')') }
}
}
local inner_call_body = {
AstType='StatList', Scope=CreateScope(inner_call_scope), Tokens={}, Body={
{ AstType='ReturnStatement', Arguments={inner_add}, Tokens={ t('Keyword', 'return', {space}) } }
}
}
inner_call = {
AstType='Function', VarArg=false, IsLocal=true, Scope=inner_call_scope, Body=inner_call_body,
Arguments={
{ IsGlobal=false, Name='_o', Scope=inner_call_scope },
{ IsGlobal=false, Name='_f', Scope=inner_call_scope },
},
Tokens={ t('Keyword', 'function'), t('Symbol', '('), t('Ident', '_o'), t('Symbol', ','), t('Ident', '_f'), t('Symbol', ')'), t('Keyword', 'end', {space}) }
}
end
args[1] = inner_call
end
local exp_call = {
AstType = 'CallExpr', Base = op_var, Arguments = args, Tokens = {
t('Symbol', '(', params.paren_open_white), t('Symbol', ')', params.paren_close_white)
}
}
do
local j=#commaTokenList
for i=2,#args do
assert(j > 0)
table.insert(exp_call.Tokens, i, commaTokenList[j])
j = j - 1
end
end
return { AstType = 'CallStatement', Expression = exp_call, Tokens = {} }
end
local function as_string_expr(expr, s)
local ss = '"'..s..'"'
local lw = expr.Tokens and #expr.Tokens > 0 and expr.Tokens[1].LeadingWhite or {}
local p = function() return '<String '..s..' >' end
local v = { LeadingWhite=lw, Type='String', Data=ss, Constant=s, Char=c(), Line=l(), Print=p }
return { AstType = 'StringExpr', Value = v, Tokens = {v} }
end
-- parser pragmas
do
::search_pragma::
for k,v in pairs(pragma_map) do
if tok:ConsumeKeyword(k) then v() goto search_pragma end
end
end
-- label declarations
if not stat then
if tok:ConsumeSymbol('@@', tokenList) then
if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end
local label_name = tok:Get(tokenList)
label_name = as_string_expr(label_name, label_name.Data)
stat = emit_call{name = 'section', args = {label_name}, encapsulate=false}
elseif tok:ConsumeSymbol('@', tokenList) then
if not tok:Is('Ident') then return false, GenerateError("Identifier expected") end
local label_name = tok:Get(tokenList)
label_name = as_string_expr(label_name, label_name.Data)
stat = emit_call{name = 'label', args = {label_name}, encapsulate=false}
end end
-- new statements
if not stat then
local pagestat = function(fpage)
local st, nodeBlock = ParseStatementList(scope)
if not st then return false, nodeBlock end
if not tok:ConsumeKeyword('end', tokenList) then
return false, GenerateError("'end' expected")
end
local nodeDoStat = {}
nodeDoStat.AstType = 'DoStatement'
nodeDoStat.Body = nodeBlock
nodeDoStat.Tokens = tokenList
stat = nodeDoStat
tokenList[1].Data = 'do'
local space = {{ Char=c(), Line=l(), Data=' ', Type='Whitespace' }}
local opencall,closecall = emit_call{name=fpage,func_white=space},emit_call{name='endpage',func_white=space}
table.insert(nodeBlock.Body, 1, opencall)
table.insert(nodeBlock.Body, closecall)
end
if tok:ConsumeKeyword('samepage', tokenList) then pagestat('samepage')
elseif tok:ConsumeKeyword('crosspage', tokenList) then pagestat('crosspage')
end
end
-- declare data
if not stat then
if tok:ConsumeKeyword('dc', tokenList) then
if not tok:ConsumeSymbol('.', tokenList) then return false, GenerateError("'.' expected") end
local suffix_list = { b='byte', w='word', l='long' }
local func = suffix_list[tok:Get(tokenList).Data]
if not func then return false, GenerateError("'b', 'w' or 'l' expected") end
local inverse_encapsulate={}
inverse_encapsulate[1] = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope)
if not st then return false, expr end
if inverse_encapsulate[1] then
local d = expr.Tokens[1].LeadingWhite
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(d, v) end
end
local exprs = { expr }
while tok:ConsumeSymbol(',', tokenList) do
commaTokenList[#commaTokenList+1] = tokenList[#tokenList]
inverse_encapsulate[#exprs+1] = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope)
if not st then return false, expr end
if inverse_encapsulate[#exprs+1] then
local d = expr.Tokens[1].LeadingWhite
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(d, v) end
end
exprs[#exprs+1] = expr
end
stat = emit_call{name=func, args=exprs, inverse_encapsulate=inverse_encapsulate}
end end
-- 7801 opcodes
if not stat then
local mod_st, mod_expr, inverse_encapsulate
for _,op in pairs(Keywords_7801) do
if tok:ConsumeKeyword(op, tokenList) then
if opcode_relative[op] then
local st, expr = ParseExpr(scope) if not st then return false, expr end
if expr.AstType == 'VarExpr' and expr.Variable.IsGlobal then
expr = as_string_expr(expr, expr.Name)
end
stat = emit_call{name=op, args={expr}, encapsulate=false} break
end
if opcode_alias[op] then op = opcode_alias[op] end
if opcode_encapsulate[op] then
inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope) if not st then return false, expr end
local paren_open_whites = {}
if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end
stat = emit_call{name=opcode_encapsulate[op], args={expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, basic=true} break
end
if opcode_immediate[op] then
inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope) if not st then return false, expr end
local paren_open_whites = {}
if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end
if tok:ConsumeSymbol(',', tokenList) then
commaTokenList[1] = tokenList[#tokenList]
mod_st, mod_expr = ParseExpr(scope)
if not mod_st then return false, mod_expr end
end
stat = emit_call{name=op .. 'imm', args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites} break
end
if opcode_ind[op] then
if not tok:ConsumeSymbol('(', tokenList) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope) if not st then return false, expr end
local paren_open_whites,paren_close_whites = {}
if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end
if not tok:ConsumeSymbol(')', tokenList) then return false, expr end
stat = emit_call{name=op, args={expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites} break
end
if opcode_mov[op] then
tok:Save()
local r0_name, r1_name = tok:Get(tokenList).Data, ''
if Registers_7801[r0_name] then
tok:Commit()
if not tok:ConsumeSymbol(',', tokenList) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
tok:Save()
r1_name = tok:Get(tokenList).Data
if Registers_7801[r1_name] then
tok:Commit()
if not (opcode_reg_reg_list[r0_name] and opcode_reg_reg_list[r0_name][r1_name] and opcode_reg_reg_list[r0_name][r1_name][op]) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
stat = emit_call{name=op .. r0_name .. r1_name} break
else
r1_name = ''
end
else
r0_name = ''
end
tok:Restore()
if not tok:ConsumeSymbol('(', tokenList) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope) if not st then return false, expr end
local paren_open_whites,paren_close_whites = {},{}
if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end
if not tok:ConsumeSymbol(')', tokenList) then return false, expr end
for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end
if r0_name == '' then
if not tok:ConsumeSymbol(',', tokenList) then return false, expr end
r1_name = tok:Get(tokenList).Data
if not registers_8[r1_name] then
return false, GenerateError(r1_name .. " is not a valid register")
end
end
stat = emit_call{name=op .. r0_name .. "ind" .. r1_name, args={expr}, inverse_encapsulate=inverse_encapsulates, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break
end
if (opcode_wa[op] or opcode_wab[op] or opcode_reg_ind[op] or opcode_reg_ind_ex[op]) and tok:ConsumeSymbol('(', tokenList) then
local paren_open_whites,paren_close_whites = {},{}
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end
local register_name = tok:Get(tokenList).Data
if (not Registers_7801[register_name]) then
return false, GenerateError(register_name .. " is not a valid register")
end
if not (opcode_reg_list[register_name] and opcode_reg_list[register_name][op]) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
if (opcode_wa[op] or opcode_wab[op]) and tok:ConsumeSymbol(',', tokenList) then
if not (register_name == 'v')
or not (opcode_wa[op] or opcode_wab[op]) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope) if not st then return false, expr end
if not tok:ConsumeSymbol(')', tokenList) then
return false, GenerateError("Unexpected character")
end
if opcode_wa[op] then
for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end
stat = emit_call{name=op .. "wa", args={expr, mod_expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites} break
end
if not tok:ConsumeSymbol(',', tokenList) then
return false, GenerateError("Unexpected character")
end
commaTokenList[1] = tokenList[#tokenList]
local inverse_encapsulates = { inverse_encapsulate }
local exprs = { expr }
inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
inverse_encapsulates[#inverse_encapsulates+1] = inverse_encapsulate
if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end
st, expr = ParseExpr(scope) if not st then return false, expr end
exprs[#exprs+1] = expr
stat = emit_call{name=op .. "waxx", args=exprs, inverse_encapsulate=inverse_encapsulates, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break
end
if (opcode_reg_ind[op] or opcode_reg_ind_ex[op]) and tok:ConsumeSymbol(')', tokenList) then
if opcode_reg_ind[op] then
for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end
stat = emit_call{name=op .. register_name, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break
end
if opcode_reg_ind_ex[op] and tok:ConsumeSymbol(',', tokenList) then
inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope) if not st then return false, expr end
if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_close_whites, v) end end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_close_whites, v) end
stat = emit_call{name=op .. register_name, args={expr}, inverse_encapsulate=inverse_encapsulate, paren_open_white=paren_open_whites, paren_close_white=paren_close_whites} break
end
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
end
if opcode_reg_reg[op] then
local r0_name = tok:Get(tokenList).Data
if not Registers_7801[r0_name] then
return false, GenerateError(r0_name .. " is not a valid register")
end
if not tok:ConsumeSymbol(',', tokenList) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
local r1_name = tok:Get(tokenList).Data
if not Registers_7801[r1_name] then
return false, GenerateError(r0_name .. " is not a valid register")
end
if not (opcode_reg_reg_list[r0_name] and opcode_reg_reg_list[r0_name][r1_name] and opcode_reg_reg_list[r0_name][r1_name][op]) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
stat = emit_call{name=op .. r0_name .. r1_name} break
end
if opcode_reg[op] or opcode_regb[op] or opcode_regw[op] then
local register_name = tok:Get(tokenList).Data
local call_args = {name=op..register_name}
if not Registers_7801[register_name] then
return false, GenerateError(register_name .. " is not a valid register")
end
if (not opcode_reg_list[register_name]) and (not opcode_reg_list[register_name][op]) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
if opcode_regw[op] or opcode_regb[op] then
if not tok:ConsumeSymbol(',', tokenList) then
return false, GenerateError("Opcode " .. op .. " doesn't support this addressing mode")
end
local inverse_encapsulate = tok:ConsumeSymbol('!', tokenList)
local st, expr = ParseExpr(scope) if not st then
return false, expr
end
local paren_open_whites = {}
if inverse_encapsulate then for _,v in ipairs(tokenList[#tokenList-1].LeadingWhite) do table.insert(paren_open_whites, v) end end
for _,v in ipairs(tokenList[#tokenList].LeadingWhite) do table.insert(paren_open_whites, v) end
if tok:ConsumeSymbol(',', tokenList) then
commaTokenList[1] = tokenList[#tokenList]
mod_st, mod_expr = ParseExpr(scope)
if not mod_st then
return false, mod_expr
end
end
call_args.args={expr, mod_expr}
call_args.inverse_encapsulate=inverse_encapsulate
call_args.paren_open_white=paren_open_whites
end
stat = emit_call(call_args) break
end
if opcode_timer[op] then
local timer_name = tok:Get(tokenList).Data
if not Interrupts_7801[timer_name] then
return false, GenerateError(timer_name .. " is not a valid interrupt name")
end
stat = emit_call{name=op .. timer_name} break
end
if opcode_implied[op] then stat = emit_call{name=op .. 'imp'} break end
error("internal error: unable to find addressing of valid opcode " .. op) -- should not happen
end
end end
if stat then -- nothing
elseif tok:ConsumeKeyword('if', tokenList) then
--setup
local nodeIfStat = {}
nodeIfStat.AstType = 'IfStatement'
nodeIfStat.Clauses = {}
--clauses
repeat
local st, nodeCond = ParseExpr(scope)
if not st then return false, nodeCond end
if not tok:ConsumeKeyword('then', tokenList) then
return false, GenerateError("'then' expected")
end
local st, nodeBody = ParseStatementList(scope)
if not st then return false, nodeBody end
nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = {
Condition = nodeCond;
Body = nodeBody;
}
until not tok:ConsumeKeyword('elseif', tokenList)
--else clause
if tok:ConsumeKeyword('else', tokenList) then
local st, nodeBody = ParseStatementList(scope)
if not st then return false, nodeBody end
nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = {
Body = nodeBody;
}
end
--end
if not tok:ConsumeKeyword('end', tokenList) then
return false, GenerateError("'end' expected")
end
nodeIfStat.Tokens = tokenList
stat = nodeIfStat
elseif tok:ConsumeKeyword('while', tokenList) then
--setup
local nodeWhileStat = {}
nodeWhileStat.AstType = 'WhileStatement'
--condition
local st, nodeCond = ParseExpr(scope)
if not st then return false, nodeCond end
--do
if not tok:ConsumeKeyword('do', tokenList) then
return false, GenerateError("'do' expected")
end
--body
local st, nodeBody = ParseStatementList(scope)
if not st then return false, nodeBody end
--end
if not tok:ConsumeKeyword('end', tokenList) then
return false, GenerateError("'end' expected")
end
--return
nodeWhileStat.Condition = nodeCond
nodeWhileStat.Body = nodeBody
nodeWhileStat.Tokens = tokenList
stat = nodeWhileStat
elseif tok:ConsumeKeyword('do', tokenList) then
--do block
local st, nodeBlock = ParseStatementList(scope)
if not st then return false, nodeBlock end
if not tok:ConsumeKeyword('end', tokenList) then
return false, GenerateError("'end' expected")
end
local nodeDoStat = {}
nodeDoStat.AstType = 'DoStatement'
nodeDoStat.Body = nodeBlock
nodeDoStat.Tokens = tokenList
stat = nodeDoStat
elseif tok:ConsumeKeyword('for', tokenList) then
--for block
if not tok:Is('Ident') then
return false, GenerateError("Identifier expected")
end
local baseVarName = tok:Get(tokenList)
if tok:ConsumeSymbol('=', tokenList) then
--numeric for
local forScope = CreateScope(scope)
local forVar = forScope:CreateLocal(baseVarName.Data)
--
local st, startEx = ParseExpr(scope)
if not st then return false, startEx end
if not tok:ConsumeSymbol(',', tokenList) then
return false, GenerateError("',' expected")
end
local st, endEx = ParseExpr(scope)
if not st then return false, endEx end
local st, stepEx;
if tok:ConsumeSymbol(',', tokenList) then
st, stepEx = ParseExpr(scope)
if not st then return false, stepEx end
end
if not tok:ConsumeKeyword('do', tokenList) then
return false, GenerateError("'do' expected")
end
--
local st, body = ParseStatementList(forScope)
if not st then return false, body end
if not tok:ConsumeKeyword('end', tokenList) then
return false, GenerateError("'end' expected")
end
--
local nodeFor = {}
nodeFor.AstType = 'NumericForStatement'
nodeFor.Scope = forScope
nodeFor.Variable = forVar
nodeFor.Start = startEx
nodeFor.End = endEx
nodeFor.Step = stepEx
nodeFor.Body = body
nodeFor.Tokens = tokenList
stat = nodeFor
else
--generic for
local forScope = CreateScope(scope)
--
local varList = { forScope:CreateLocal(baseVarName.Data) }
while tok:ConsumeSymbol(',', tokenList) do
if not tok:Is('Ident') then
return false, GenerateError("for variable expected.")
end
varList[#varList+1] = forScope:CreateLocal(tok:Get(tokenList).Data)
end
if not tok:ConsumeKeyword('in', tokenList) then
return false, GenerateError("'in' expected")
end
local generators = {}
local st, firstGenerator = ParseExpr(scope)
if not st then return false, firstGenerator end
generators[#generators+1] = firstGenerator
while tok:ConsumeSymbol(',', tokenList) do
local st, gen = ParseExpr(scope)
if not st then return false, gen end
generators[#generators+1] = gen
end
if not tok:ConsumeKeyword('do', tokenList) then
return false, GenerateError("'do' expected")
end
local st, body = ParseStatementList(forScope)
if not st then return false, body end
if not tok:ConsumeKeyword('end', tokenList) then
return false, GenerateError("'end' expected")
end
--
local nodeFor = {}
nodeFor.AstType = 'GenericForStatement'
nodeFor.Scope = forScope
nodeFor.VariableList = varList
nodeFor.Generators = generators
nodeFor.Body = body
nodeFor.Tokens = tokenList
stat = nodeFor
end
elseif tok:ConsumeKeyword('repeat', tokenList) then
local st, body = ParseStatementList(scope)
if not st then return false, body end
--
if not tok:ConsumeKeyword('until', tokenList) then
return false, GenerateError("'until' expected")
end
-- FIX: Used to parse in parent scope
-- Now parses in repeat scope
local st, cond = ParseExpr(body.Scope)
if not st then return false, cond end
--
local nodeRepeat = {}
nodeRepeat.AstType = 'RepeatStatement'
nodeRepeat.Condition = cond
nodeRepeat.Body = body
nodeRepeat.Tokens = tokenList
stat = nodeRepeat
elseif tok:ConsumeKeyword('function', tokenList) then
if not tok:Is('Ident') then
return false, GenerateError("function name expected")
end
local st, name = ParseSuffixedExpr(scope, true) --true => only dots and colons
if not st then return false, name end
--
local st, func = ParseFunctionArgsAndBody(scope, tokenList)
if not st then return false, func end
--
func.IsLocal = false
func.Name = name
stat = func
elseif tok:ConsumeKeyword('local', tokenList) then
if tok:Is('Ident') then
local varList = { tok:Get(tokenList).Data }
while tok:ConsumeSymbol(',', tokenList) do
if not tok:Is('Ident') then
return false, GenerateError("local var name expected")
end
varList[#varList+1] = tok:Get(tokenList).Data
end
local initList = {}
if tok:ConsumeSymbol('=', tokenList) then
repeat
local st, ex = ParseExpr(scope)
if not st then return false, ex end
initList[#initList+1] = ex
until not tok:ConsumeSymbol(',', tokenList)
end
--now patch var list
--we can't do this before getting the init list, because the init list does not
--have the locals themselves in scope.
for i, v in pairs(varList) do
varList[i] = scope:CreateLocal(v)
end
local nodeLocal = {}
nodeLocal.AstType = 'LocalStatement'
nodeLocal.LocalList = varList
nodeLocal.InitList = initList
nodeLocal.Tokens = tokenList
--
stat = nodeLocal
elseif tok:ConsumeKeyword('function', tokenList) then
if not tok:Is('Ident') then
return false, GenerateError("function name expected")
end
local name = tok:Get(tokenList).Data
local localVar = scope:CreateLocal(name)
--
local st, func = ParseFunctionArgsAndBody(scope, tokenList)
if not st then return false, func end
--
func.Name = localVar
func.IsLocal = true
stat = func
else
return false, GenerateError("local var or function def expected")
end
elseif tok:ConsumeSymbol('::', tokenList) then
if not tok:Is('Ident') then
return false, GenerateError('Label name expected')
end
local label = tok:Get(tokenList).Data
if not tok:ConsumeSymbol('::', tokenList) then
return false, GenerateError("'::' expected")
end
local nodeLabel = {}
nodeLabel.AstType = 'LabelStatement'
nodeLabel.Label = label
nodeLabel.Tokens = tokenList
stat = nodeLabel
elseif tok:ConsumeKeyword('return', tokenList) then
local exList = {}
if not tok:IsKeyword('end') then
local st, firstEx = ParseExpr(scope)
if st then
exList[1] = firstEx
while tok:ConsumeSymbol(',', tokenList) do
local st, ex = ParseExpr(scope)
if not st then return false, ex end
exList[#exList+1] = ex
end
end
end
local nodeReturn = {}
nodeReturn.AstType = 'ReturnStatement'
nodeReturn.Arguments = exList
nodeReturn.Tokens = tokenList
stat = nodeReturn
elseif tok:ConsumeKeyword('break', tokenList) then
local nodeBreak = {}
nodeBreak.AstType = 'BreakStatement'
nodeBreak.Tokens = tokenList
stat = nodeBreak
elseif tok:ConsumeKeyword('goto', tokenList) then
if not tok:Is('Ident') then
return false, GenerateError("Label expected")
end
local label = tok:Get(tokenList).Data
local nodeGoto = {}
nodeGoto.AstType = 'GotoStatement'
nodeGoto.Label = label
nodeGoto.Tokens = tokenList
stat = nodeGoto
elseif tok:IsSymbol(';') then
stat = { AstType='SemicolonStatement', Tokens={} }
else
--statementParseExpr
local st, suffixed = ParseSuffixedExpr(scope)
if not st then return false, suffixed end
--assignment or call?
if tok:IsSymbol(',') or tok:IsSymbol('=') then
--check that it was not parenthesized, making it not an lvalue
if (suffixed.ParenCount or 0) > 0 then
return false, GenerateError("Can not assign to parenthesized expression, is not an lvalue")
end
--more processing needed
local lhs = { suffixed }
while tok:ConsumeSymbol(',', tokenList) do
local st, lhsPart = ParseSuffixedExpr(scope)
if not st then return false, lhsPart end
lhs[#lhs+1] = lhsPart
end
--equals
if not tok:ConsumeSymbol('=', tokenList) then
return false, GenerateError("'=' expected")
end
--rhs
local rhs = {}
local st, firstRhs = ParseExpr(scope)
if not st then return false, firstRhs end
rhs[1] = firstRhs
while tok:ConsumeSymbol(',', tokenList) do
local st, rhsPart = ParseExpr(scope)
if not st then return false, rhsPart end
rhs[#rhs+1] = rhsPart
end
--done
local nodeAssign = {}
nodeAssign.AstType = 'AssignmentStatement'
nodeAssign.Lhs = lhs
nodeAssign.Rhs = rhs
nodeAssign.Tokens = tokenList
stat = nodeAssign
elseif suffixed.AstType == 'CallExpr' or
suffixed.AstType == 'TableCallExpr' or
suffixed.AstType == 'StringCallExpr'
then
--it's a call statement
local nodeCall = {}
nodeCall.AstType = 'CallStatement'
nodeCall.Expression = suffixed
nodeCall.Tokens = tokenList
stat = nodeCall
else
return false, GenerateError("Assignment statement expected")
end
end
if tok:IsSymbol(';') then
stat.Semicolon = tok:Get( stat.Tokens )
end
return true, stat
end
local statListCloseKeywords = lookupify{'end', 'else', 'elseif', 'until'}
ParseStatementList = function(scope)
local nodeStatlist = {}
nodeStatlist.Scope = CreateScope(scope)
nodeStatlist.AstType = 'Statlist'
nodeStatlist.Body = { }
nodeStatlist.Tokens = { }
--
--local stats = {}
--
while not statListCloseKeywords[tok:Peek().Data] and not tok:IsEof() do
local st, nodeStatement = ParseStatement(nodeStatlist.Scope)
if not st then return false, nodeStatement end
--stats[#stats+1] = nodeStatement
nodeStatlist.Body[#nodeStatlist.Body + 1] = nodeStatement
end
if tok:IsEof() then
local nodeEof = {}
nodeEof.AstType = 'Eof'
nodeEof.Tokens = { tok:Get() }
nodeStatlist.Body[#nodeStatlist.Body + 1] = nodeEof
end
--
--nodeStatlist.Body = stats
return true, nodeStatlist
end
local function mainfunc()
local topScope = CreateScope()
return ParseStatementList(topScope)
end
local st, main = mainfunc()
return st, main
end
local function Format7801(ast)
local function splitLines(str)
if str:match("\n") then
local lines = {}
for line in str:gmatch("[^\n]*") do
table.insert(lines, line)
end
assert(#lines > 0)
return lines
else
return { str }
end
end
local formatStatlist, formatExpr
local out = {
rope = {}, -- List of strings
line = 1,
char = 1,
appendStr = function(self, str)
table.insert(self.rope, str)
local lines = splitLines(str)
if #lines == 1 then
self.char = self.char + #str
else
self.line = self.line + #lines - 1
local lastLine = lines[#lines]
self.char = #lastLine
end
end,
appendToken = function(self, token)
self:appendWhite(token)
self:appendStr(token.Data)
end,
appendTokens = function(self, tokens)
for _,token in ipairs(tokens) do
self:appendToken( token )
end
end,
appendWhite = function(self, token)
if token.LeadingWhite then
self:appendTokens( token.LeadingWhite )
end
end
}
formatExpr = function(expr)
local tok_it = 1
local function appendNextToken(str)
local tok = expr.Tokens[tok_it];
if str and tok.Data ~= str then
error("Expected token '" .. str .. "'.")
end
out:appendToken( tok )
tok_it = tok_it + 1
end
local function appendToken(token)
out:appendToken( token )
tok_it = tok_it + 1
end
local function appendWhite()
local tok = expr.Tokens[tok_it];
if not tok then error("Missing token") end
out:appendWhite( tok )
tok_it = tok_it + 1
end
local function appendStr(str)
appendWhite()
out:appendStr(str)
end
local function peek()
if tok_it < #expr.Tokens then
return expr.Tokens[tok_it].Data
end
end
local function appendComma(mandatory, seperators)
seperators = seperators or { "," }
seperators = lookupify( seperators )
if not mandatory and not seperators[peek()] then
return
end
assert(seperators[peek()], "Missing comma or semicolon")
appendNextToken()
end
if expr.AstType == 'VarExpr' then
if expr.Variable then
appendStr( expr.Variable.Name )
else
appendStr( expr.Name )
end
elseif expr.AstType == 'NumberExpr' then
appendToken( expr.Value )
elseif expr.AstType == 'StringExpr' then
appendToken( expr.Value )
elseif expr.AstType == 'BooleanExpr' then
appendNextToken( expr.Value and "true" or "false" )
elseif expr.AstType == 'NilExpr' then
appendNextToken( "nil" )
elseif expr.AstType == 'BinopExpr' then
formatExpr(expr.Lhs)
appendStr( expr.Op )
formatExpr(expr.Rhs)
elseif expr.AstType == 'UnopExpr' then
appendStr( expr.Op )
formatExpr(expr.Rhs)
elseif expr.AstType == 'DotsExpr' then
appendNextToken( "..." )
elseif expr.AstType == 'CallExpr' then
formatExpr(expr.Base)
appendNextToken( "(" )
for i,arg in ipairs( expr.Arguments ) do
formatExpr(arg)
appendComma( i ~= #expr.Arguments )
end
appendNextToken( ")" )
elseif expr.AstType == 'TableCallExpr' then
formatExpr( expr.Base )
formatExpr( expr.Arguments[1] )
elseif expr.AstType == 'StringCallExpr' then
formatExpr(expr.Base)
appendToken( expr.Arguments[1] )
elseif expr.AstType == 'IndexExpr' then
formatExpr(expr.Base)
appendNextToken( "[" )
formatExpr(expr.Index)
appendNextToken( "]" )
elseif expr.AstType == 'MemberExpr' then
formatExpr(expr.Base)
appendNextToken() -- . or :
appendToken(expr.Ident)
elseif expr.AstType == 'Function' then
-- anonymous function
appendNextToken( "function" )
appendNextToken( "(" )
if #expr.Arguments > 0 then
for i = 1, #expr.Arguments do
appendStr( expr.Arguments[i].Name )
if i ~= #expr.Arguments then
appendNextToken(",")
elseif expr.VarArg then
appendNextToken(",")
appendNextToken("...")
end
end
elseif expr.VarArg then
appendNextToken("...")
end
appendNextToken(")")
formatStatlist(expr.Body)
appendNextToken("end")
elseif expr.AstType == 'ConstructorExpr' then
appendNextToken( "{" )
for i = 1, #expr.EntryList do
local entry = expr.EntryList[i]
if entry.Type == 'Key' then
appendNextToken( "[" )
formatExpr(entry.Key)
appendNextToken( "]" )
appendNextToken( "=" )
formatExpr(entry.Value)
elseif entry.Type == 'Value' then
formatExpr(entry.Value)
elseif entry.Type == 'KeyString' then
appendStr(entry.Key)
appendNextToken( "=" )
formatExpr(entry.Value)
end
appendComma( i ~= #expr.EntryList, { ",", ";" } )
end
appendNextToken( "}" )
elseif expr.AstType == 'Parentheses' then
appendNextToken( "(" )
formatExpr(expr.Inner)
appendNextToken( ")" )
else
error(string.format("Unknown AST Type: %s\n", tostring(statement.AstType)))
end
assert(tok_it == #expr.Tokens + 1)
end
local formatStatement = function(statement)
local tok_it = 1
local function appendNextToken(str)
local tok = statement.Tokens[tok_it];
assert(tok, string.format("Not enough tokens for %q. First token at %i:%i",
str, statement.Tokens[1].Line, statement.Tokens[1].Char))
assert(tok.Data == str,
string.format('Expected token %q, got %q', str, tok.Data))
out:appendToken( tok )
tok_it = tok_it + 1
end
local function appendToken(token)
out:appendToken( str )
tok_it = tok_it + 1
end
local function appendWhite()
local tok = statement.Tokens[tok_it];
out:appendWhite( tok )
tok_it = tok_it + 1
end
local function appendStr(str)
appendWhite()
out:appendStr(str)
end
local function appendComma(mandatory)
if mandatory
or (tok_it < #statement.Tokens and statement.Tokens[tok_it].Data == ",") then
appendNextToken( "," )
end
end
if statement.AstType == 'AssignmentStatement' then
for i,v in ipairs(statement.Lhs) do
formatExpr(v)
appendComma( i ~= #statement.Lhs )
end
if #statement.Rhs > 0 then
appendNextToken( "=" )
for i,v in ipairs(statement.Rhs) do
formatExpr(v)
appendComma( i ~= #statement.Rhs )
end
end
elseif statement.AstType == 'CallStatement' then
formatExpr(statement.Expression)
elseif statement.AstType == 'LocalStatement' then
appendNextToken( "local" )
for i = 1, #statement.LocalList do
appendStr( statement.LocalList[i].Name )
appendComma( i ~= #statement.LocalList )
end
if #statement.InitList > 0 then
appendNextToken( "=" )
for i = 1, #statement.InitList do
formatExpr(statement.InitList[i])
appendComma( i ~= #statement.InitList )
end
end
elseif statement.AstType == 'IfStatement' then
appendNextToken( "if" )
formatExpr( statement.Clauses[1].Condition )
appendNextToken( "then" )
formatStatlist( statement.Clauses[1].Body )
for i = 2, #statement.Clauses do
local st = statement.Clauses[i]
if st.Condition then
appendNextToken( "elseif" )
formatExpr(st.Condition)
appendNextToken( "then" )
else
appendNextToken( "else" )
end
formatStatlist(st.Body)
end
appendNextToken( "end" )
elseif statement.AstType == 'WhileStatement' then
appendNextToken( "while" )
formatExpr(statement.Condition)
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'DoStatement' then
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'ReturnStatement' then
appendNextToken( "return" )
if statement.TrailingWhite then out:appendStr(' ') end
for i = 1, #statement.Arguments do
formatExpr(statement.Arguments[i])
appendComma( i ~= #statement.Arguments )
end
elseif statement.AstType == 'BreakStatement' then
appendNextToken( "break" )
elseif statement.AstType == 'RepeatStatement' then
appendNextToken( "repeat" )
formatStatlist(statement.Body)
appendNextToken( "until" )
formatExpr(statement.Condition)
elseif statement.AstType == 'Function' then
if statement.IsLocal then
appendNextToken( "local" )
end
appendNextToken( "function" )
if statement.IsLocal then
appendStr(statement.Name.Name)
else
formatExpr(statement.Name)
end
appendNextToken( "(" )
if #statement.Arguments > 0 then
for i = 1, #statement.Arguments do
appendStr( statement.Arguments[i].Name )
appendComma( i ~= #statement.Arguments or statement.VarArg )
if i == #statement.Arguments and statement.VarArg then
appendNextToken( "..." )
end
end
elseif statement.VarArg then
appendNextToken( "..." )
end
appendNextToken( ")" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'GenericForStatement' then
appendNextToken( "for" )
for i = 1, #statement.VariableList do
appendStr( statement.VariableList[i].Name )
appendComma( i ~= #statement.VariableList )
end
appendNextToken( "in" )
for i = 1, #statement.Generators do
formatExpr(statement.Generators[i])
appendComma( i ~= #statement.Generators )
end
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'NumericForStatement' then
appendNextToken( "for" )
appendStr( statement.Variable.Name )
appendNextToken( "=" )
formatExpr(statement.Start)
appendNextToken( "," )
formatExpr(statement.End)
if statement.Step then
appendNextToken( "," )
formatExpr(statement.Step)
end
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'LabelStatement' then
appendNextToken( "::" )
appendStr( statement.Label )
appendNextToken( "::" )
elseif statement.AstType == 'GotoStatement' then
appendNextToken( "goto" )
appendStr( statement.Label )
elseif statement.AstType == 'Eof' then
appendWhite()
elseif statement.AstType == 'SemicolonStatement' then
else
error(string.format("Unknown AST Type: %s\n", tostring(statement.AstType)))
end
if statement.Semicolon then
appendNextToken(";")
end
assert(tok_it == #statement.Tokens + 1)
end
formatStatlist = function(statList)
for _, stat in ipairs(statList.Body) do
formatStatement(stat)
end
end
formatStatlist(ast)
return table.concat(out.rope)
end
local dirsep = package.config:sub(1,1)
local dirl7801 = (string.match(arg[0], "(.*[\\/]).*") or ''):gsub('/',dirsep)
local searchl7801 = ''
if #dirl7801 > 0 then
searchl7801 = string.format(";%s?;%s?.l7801", dirl7801, dirl7801)
package.path = package.path .. string.format(";%s?.lua", dirl7801)
end
l7801_def = {
parse = ParseLua,
format = Format7801,
searcher_index = 2,
search_path = string.format(".%s?;.%s?.l7801%s", dirsep, dirsep, searchl7801),
load_org = load,
loadfile_org = loadfile,
dofile_org = dofile,
}
if not l7801 then l7801 = l7801_def else for k,v in pairs(l7801_def) do l7801[k]=v end end
l7801.report = function(success, ...)
if success then return success,... end
local message=... io.stderr:write(tostring(message)..'\n')
os.exit(-1)
end
l7801.msghandler = function(msg)
local o = function(s) io.stderr:write(s) end
msg = tostring(msg)
msg = msg:gsub('%[string "(.-%.l7801)"%]', '%1') -- [string "xxx.l7801"] -> xxx.l7801
local trace_cur = debug.traceback(nil, 2)
trace_cur = trace_cur:gsub('%[string "(.-%.l7801)"%]', '%1') -- [string "xxx.l7801"] -> xxx.l7801
trace_cur = trace_cur:gsub('stack traceback:', '')
local i=2
while debug.getinfo(i) do
local j = 1
while true do
local n,v = debug.getlocal(i, j)
if not n then break end
if n == 'l7801dbg' then
o(string.format("%s\n", msg))
if trace_cur:find("in local 'late'") then
local lines = {}
for line in trace_cur:gmatch("[^\n]+") do
if line:find("in local 'late'") then break end
table.insert(lines, line)
end
o(table.concat(lines,'\n'))
end
local trace = v.trace:match(".-\n(.*)\n.-'xpcall'")
trace = trace:gsub('%[string "(.-%.l7801)"%]', '%1')
trace = trace:gsub('stack traceback:', '')
o(trace .. '\n')
os.exit(-2)
end
j = j + 1
end
i = i + 1
end
trace_cur = trace_cur:match(".-\n(.*)\n.-'xpcall'")
o(string.format("%s\n%s\n", msg, trace_cur))
os.exit(-3)
end
do
local getembedded = type(arg[-1]) == 'function' and arg[-1]
l7801.load_embedded = function(name)
if not getembedded then return end
local src,isl7801 = getembedded(name)
if not src then return end
if isl7801 then
name = name .. '.l7801'
local st, ast = l7801.report(l7801.parse(src, name))
src = l7801.format(ast)
else
name = name .. '.lua'
end
local bc = assert(l7801.load_org(src, name))
return bc, name
end
end
l7801.searcher = function(name)
local filename,err = package.searchpath(name, l7801.search_path, '.', '.')
if not filename then return err end
local file = assert(io.open(filename, 'rb'))
local src = file:read('*a')
file:close()
local st, ast = l7801.report(l7801.parse(src, filename))
local bc = assert(l7801.load_org(l7801.format(ast), filename))
return bc, filename
end
l7801.load = function(chunk, chunkname, mode, ...)
local chunk_t,s = type(chunk)
if chunk_t == 'string' then s = chunk
elseif chunk_t == 'function' then
s={} for x in chunk do if #x==0 then break end s[#s+1]=x end
s = table.concat(s)
else return nil, string.format("invalid type for chunk %s: %s", chunkname or "=(load)", chunk_t)
end
if s:sub(1,4) == "\x1bLua" then -- a binary file
return l7801.load_org(s, chunkname, mode, ...)
end
local st, ast = l7801.report(l7801.parse(s, chunkname or "=(load)"))
return l7801.load_org(l7801.format(ast), chunkname, 't', ...)
end
l7801.loadfile = function(filename, mode, ...)
local s
if not filename then s = io.read('*a')
else
local file = io.open(filename, 'rb')
if not file then return nil,"failed to open " .. filename .. " for reading" end
s = file:read('*a')
file:close()
end
return l7801.load(s, filename, mode, ...)
end
l7801.dofile = function(filename)
local f = l7801.report(l7801.loadfile(filename))
return f()
end
l7801.installhooks = function()
if package.searchers[l7801.searcher_index] ~= l7801.searcher then
table.insert(package.searchers, l7801.searcher_index, l7801.searcher)
end
if not l7801.hooks_installed then l7801.hooks_installed = true
load = l7801.load
loadfile = l7801.loadfile
dofile = l7801.dofile
end
end
l7801.uninstallhooks = function()
for k,v in ipairs(package.searchers) do
if v == l7801.searcher then table.remove(package.searchers, k) break end
end
if l7801.hooks_installed then l7801.hooks_installed = nil
load = l7801.load_org
loadfile = l7801.loadfile_org
dofile = l7801.dofile_org
end
end
table.insert(package.searchers, l7801.searcher_index, l7801.load_embedded)
l7801.installhooks()
function getopt(optstring, ...)
local opts = { }
local args = { ... }
for optc, optv in optstring:gmatch"(%a)(:?)" do
opts[optc] = { hasarg = optv == ":" }
end
return coroutine.wrap(function()
local yield = coroutine.yield
local i = 1
while i <= #args do
local arg,argi = args[i], i
i = i + 1
if arg == "--" then
break
elseif arg:sub(1, 1) == "-" then
for j = 2, #arg do
local opt = arg:sub(j, j)
if opts[opt] then
if opts[opt].hasarg then
if j == #arg then
if args[i] then yield(opt, args[i], argi) i=i+1
elseif optstring:sub(1, 1) == ":" then yield(':', opt, argi)
else yield('?', opt, argi) end
else yield(opt, arg:sub(j + 1), argi) end
break
else yield(opt, false, argi) end
else yield('?', opt, argi) end
end
else yield(false, arg, argi) end
end
for i = i, #args do yield(false, args[i], i) end
end)
end
local cfg=require"l65cfg" l7801.cfg=cfg
local version = function()
print(string.format("l7801 %s", cfg.version))
end
local usage = function(f)
if not f then f = io.stdout end
f:write(string.format([[
Usage: %s [options] file [args]
Options:
-d <file> Dump the Lua code after l7801 parsing into file
-h Display this information
-v Display the release version
]], arg[0]))
end
local invalid_usage = function()
io.stderr:write("Invalid usage.\n")
usage(io.stderr)
end
local inf,dump,optix
for opt,arg,i in getopt("d:hv", ...) do
if opt == '?' then return invalid_usage() end
if opt == 'h' then return usage() end
if opt == 'v' then return version() end
if opt == 'd' then dump = arg end
if opt == false then inf=arg optix=i+1 break end
end
if not inf then return invalid_usage() end
if dump then l7801.format = function(ast)
local s=Format7801(ast) l7801.format = Format7801
local f = assert(io.open(dump, 'wb')) f:write(s) f:close()
return s
end end
local fn='' for i=#inf,1,-1 do local c=inf:sub(i,i) if c==dirsep or c=='/' then break end fn=c..fn if c=='.' then fn='' end end filename=fn
local f = l7801.report(l7801.loadfile(inf))
return xpcall(f, l7801.msghandler, select(optix, ...))