Module:Script-MBTL: Difference between revisions

From Mizuumi Wiki
Jump to navigation Jump to search
(Testing making the colors the exact hex codes found in Clr/styles.css. Primarily to satisfy my own curiosity as to how different they could possibly be.)
(potentially breaking the whole wiki again but if it works, here is a completely reworked input parser with more flexibility and customization options and less room for errors)
Line 7: Line 7:




--local functions used by p.gameInput()
--local objects used by p.gameInput()
local Type = {WORD=0, CONNECTOR=1, PREFIX=2, SUFFIX=3, SYMBOL=4, BUTTON=5, DIRECTION=6, SYS=7}


local function getColor(btn)
local Token = {
ttype = nil,
targ = nil,
new = function(self,typ,arg)
t = {}
self.ttype = typ
self.targ = arg
setmetatable(t,self)
self.__index = self
return t
end
}
 
local lexer = {
buffer = "",
readb = "",
peek = " ",
backup_buffer = "",
backup_readb = "",
newBuffer = function(self,buf)
self.buffer = buf
end,
readch = function(self)
if self.buffer:len() >= 1 then
local r = self.buffer:sub(1,1)
self.readb = self.readb .. r
self.buffer = self.buffer:sub(2,-1)
self.peek = r
else
self.peek = ":EOF:"
end
end,
scan = function(self)
while self.peek==" " or self.peek=="\t" or self.peek=="\n" or self.peek=="\r" do
self:readch()
end
return self:cswitch()
end,
cswitch = function(self)
if self.peek == ">" then
self.peek = " "
return Token:new(Type.CONNECTOR," > ")
elseif self.peek == "," then
self.peek = " "
return Token:new(Type.CONNECTOR," , ")
elseif self.peek == "(" then
self.peek = " "
return Token:new(Type.SYMBOL,"(")
elseif self.peek == ")" then
self.peek = " "
return Token:new(Type.SYMBOL,")")
elseif self.peek == "[" then
self.peek = " "
return Token:new(Type.SYMBOL,"[")
elseif self.peek == "]" then
self.peek = " "
return Token:new(Type.SYMBOL,"]")
elseif self.peek == "{" then
self.peek = " "
return Token:new(Type.SYMBOL,"{")
elseif self.peek == "}" then
self.peek = " "
return Token:new(Type.SYMBOL,"}")
elseif self.peek == "~" then
self.peek = " "
return Token:new(Type.CONNECTOR,"~")
elseif self.peek == "+" then
self.peek = " "
return Token:new(Type.CONNECTOR,"+")
elseif self.peek == "/" then
self.peek = " "
return Token:new(Type.CONNECTOR,"/")
elseif self.peek == ":EOF:" then
self.peek = " "
return Token:new(Type.SYS,"EOF")
else
if tonumber(self.peek) == nil then
w = ""
self.backup_buffer = self.buffer
self.backup_readb = self.readb
repeat
w = w .. self.peek
self:readch()
until self.peek:match("%a")==nil or self.peek:match(":EOF:") --peek is not a letter or reached EOF
if self.peek=="." then self.peek=" " end
return self:wswitch(w)
else
d = ""
repeat
d = d .. self.peek
self:readch()
until tonumber(self.peek) == nil
return Token:new(Type.DIRECTION,d)
end
end
end,
wswitch = function(self,word)
if word=="dl" then
return Token:new(Type.PREFIX,"dl.")
elseif word=="j" then
return Token:new(Type.PREFIX,"j.")
elseif word=="dj" then
return Token:new(Type.PREFIX,"dj.")
elseif word=="w" then
return Token:new(Type.PREFIX,"w.")
elseif word=="tk" then
return Token:new(Type.PREFIX,"tk.")
elseif word=="md" or word=="microdash" then
return Token:new(Type.PREFIX,"md.")
elseif word=="A" then
return Token:new(Type.BUTTON,"A")
elseif word=="B" then
return Token:new(Type.BUTTON,"B")
elseif word=="C" then
return Token:new(Type.BUTTON,"C")
elseif word=="D" then
return Token:new(Type.BUTTON,"D")
elseif word=="X" then
return Token:new(Type.BUTTON,"X")
elseif word=="jc" then
return Token:new(Type.WORD,"jc")
elseif word=="sj" or word=="superjump" then
return Token:new(Type.WORD,"sj")
elseif word=="djc" then
return Token:new(Type.WORD,"djc")
elseif word=="IAD" or word=="iad" then
return Token:new(Type.WORD,"IAD")
elseif word=="AT" or word=="at" then
return Token:new(Type.WORD,"AT")
elseif word=="OR" or word=="or" then
return Token:new(Type.WORD," OR ")
elseif word=="CH" or word=="ch" then
return Token:new(Type.WORD,"CH ")
elseif word=="FC" or word=="fc" then
return Token:new(Type.WORD,"FC ")
elseif word=="OTG" or word=="otg" then
return Token:new(Type.WORD,"OTG ")
elseif word=="MD" then
return Token:new(Type.WORD,"MD")
elseif word=="AD" or word=="ad" then
return Token:new(Type.WORD,"AD")
elseif word=="LA" or word=="la" then
return Token:new(Type.WORD,"LA")
elseif word=="RB" or word=="rb" then
if self.peek=="1" then
self.peek = " "
return Token:new(Type.WORD,"RB1")
elseif self.peek=="2" then
self.peek = " "
return Token:new(Type.WORD,"RB2")
else return nil
end
elseif word=="Heat" or word=="heat" then
return Token:new(Type.WORD,"Heat")
elseif word=="Dash" or word=="dash" then
return Token:new(Type.WORD,"Dash")
elseif word=="Airdash" or word=="airdash" then
return Token:new(Type.WORD,"Airdash")
elseif word=="Throw" or word=="throw" then
return Token:new(Type.WORD,"Throw")
elseif word=="Sideswap" or word=="sideswap" then
return Token:new(Type.WORD,"Sideswap")
elseif word=="Jump" or word=="jump" then
return Token:new(Type.WORD,"Jump")
elseif word=="Starter" or word=="starter" then
return Token:new(Type.WORD,"Starter")
elseif word=="Ender" or word=="ender" then
return Token:new(Type.WORD,"Ender")
else
self.buffer = self.backup_buffer
self.readb = self.backup_readb
return self:wswitch(word:sub(1,1))
end
end,
}
 
local color = {
["A"] = "#e2258a",
["B"] = "#ff7606",
["C"] = "#36b270",
["D"] = "#1e8bcb",
["X"] = "#686868",
}


--recognized buttons
local parser = {
switch = {
v = {COMBOLIST=0, COMBOLISTP=1, COMBO=2, INPUTLIST=3, INPUTLISTP=4, INPUTN=5, INPUT=6, BLOCKLIST=7, BLOCKLISTP=8, BLOCK=9, UCPREFIXLIST=10, UCPREFIX=11, PREFIXLIST=12, PREFIX=13, DIR=14, BTNSLIST=15, BTNSLISTP=16, BTNS=17, SUFFIXLIST=18, SUFFIX=19, BTN=20, BTNP=21},
["A"] = "#e2258a",
look = Token:new(Type.SYS,"START"),
["B"] = "#ff7606",
["C"] = "#36b270",
move = function(self)
["D"] = "#1e8bcb",
self.look = lexer:scan()
["X"] = "#686868",
end,
}
formatElem = function(self,t,clr)
if clr then
return "<span style=\"color:" .. clr .. ";\">" .. (t.targ or t) .. "</span>" --works because the or is lazy so if t is a token it will not return t even if it's technically not nil because t.targ comes first
else
return t.targ or t
end
end,
start = function(self)
self:move()
return (self:combolist()) --only return the line parameter
end,
combolist = function(self)
if self:first(self.v.COMBOLIST) then
local c_line = self:combo()
local cp_line = self:combolistp()
if self.look.targ~="EOF" then error("illegal token: " .. self.look.targ) end
return c_line..cp_line
else error("illegal starting token") end
end,
combolistp = function(self)
if self:first(self.v.COMBOLISTP) then
local c_line = self:combo()
local cp_line = self:combolistp()
return c_line..cp_line
elseif not self:follow(self.v.COMBOLISTP) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
combo = function(self)
if self:first(self.v.COMBO) then
local ret_line
if self.look.targ=="(" then
ret_line = self:formatElem(self.look)
self:move()
ret_line = ret_line .. self:inputlist()
ret_line = ret_line .. self:combolistp()
if self.look.targ==")" then
ret_line = ret_line .. self:formatElem(self.look)
self:move()
else error("illegal token: " .. self.look.targ) end
else ret_line = self:inputlist() end
return ret_line
else error("illegal token: " .. self.look.targ) end
end,
inputlist = function(self)
if self:first(self.v.INPUTLIST) then
local ret_line
ret_line = self:input()
ret_line = ret_line .. self:inputlistp()
return ret_line
else error("illegal token: " .. self.look.targ) end
end,
inputlistp = function(self)
if self:first(self.v.INPUTLISTP) then
local ret_line
ret_line = self:formatElem(self.look)
self:move()
ret_line = ret_line .. self:inputn()
ret_line = ret_line .. self:inputlistp()
return ret_line
elseif not self:follow(self.v.INPUTLISTP) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
inputn = function(self)
if self:first(self.v.INPUTN) then
return (self:input())
elseif not self:follow(self.v.INPUTN) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
input = function(self)
if self:first(self.v.INPUT) then
local ret_line
if self.look.ttype==Type.WORD then
ret_line = self:formatElem(self.look)
self:move()
else ret_line = self:blocklist() end
return ret_line
else error("illegal token: " .. self.look.targ) end
end,
blocklist = function(self)
if self:first(self.v.BLOCKLIST) then
local ret_line
ret_line = self:block()
ret_line = ret_line .. self:blocklistp()
return ret_line
else error("illegal token: " .. self.look.targ) end
end,
blocklistp = function(self)
if self:first(self.v.BLOCKLISTP) then
local ret_line
ret_line = self:formatElem(self.look)
self:move()
ret_line = ret_line .. self:block()
ret_line = ret_line .. self:blocklistp()
return ret_line
elseif not self:follow(self.v.BLOCKLISTP) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
block = function(self)
if self:first(self.v.BLOCK) then
local ret_line
local up = self:ucprefixlist()
local pf = self:prefixlist()
local bl_line, bl_button = self:btnslist()
ret_line = self:formatElem(up) .. self:formatElem(pf,bl_button) .. bl_line
return ret_line
else error("illegal token: " .. self.look.targ) end
end,
ucprefixlist = function(self)
if self:first(self.v.UCPREFIXLIST) then
local ret_line
ret_line = self:ucprefix()
ret_line = ret_line .. self:ucprefixlist()
return ret_line
else return "" end
end,
ucprefix = function(self)
if self:first(self.v.UCPREFIX) then
local u = self.look.targ
self:move()
return u
else return "" end
end,
prefixlist = function(self)
if self:first(self.v.PREFIXLIST) then
local ret_line
ret_line = self:prefix()
ret_line = ret_line .. self:prefixlist()
return ret_line
elseif not self:follow(self.v.PREFIXLIST) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
prefix = function(self)
if self:first(self.v.PREFIX) then
local p = self.look.targ
self:move()
return p
else error("illegal token: " .. self.look.targ) end
end,
dir = function(self)
if self:first(self.v.DIR) then
local d = self.look.targ
self:move()
return d
elseif not self:follow(self.v.DIR) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
btnslist = function(self)
if self:first(self.v.BTNSLIST) then
local ret_line, ret_button
ret_line, ret_button = self:btns()
ret_line = ret_line .. self:btnslistp()
return ret_line, ret_button
else error("illegal token: " .. self.look.targ) end
end,
btnslistp = function(self)
if self:first(self.v.BTNSLISTP) then
local ret_line
ret_line = self:formatElem(self.look)
self:move()
ret_line = ret_line .. self:btns()
ret_line = ret_line .. self:btnslistp()
return ret_line
elseif not self:follow(self.v.BTNSLISTP) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
btns = function(self)
if self:first(self.v.BTNS) then
local dr = self:dir()
local b_line, b_button = self:btn()
b_line = self:formatElem(dr,b_button) .. b_line .. self:suffixlist(b_button)
return b_line, b_button
else error("illegal token: " .. self.look.targ) end
end,
suffixlist = function(self,mainb)
if self:first(self.v.SUFFIXLIST) then
local ret_line
ret_line = self:suffix(mainb)
ret_line = ret_line .. self:suffixlist(mainb)
return ret_line
elseif not self:follow(self.v.SUFFIXLIST) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
suffix = function(self,mainb)
if self:first(self.v.SUFFIX) then
local ret_line
ret_line = self:formatElem(self.look, mainb)
self:move()
local sufbody = ""
if self.look.targ=="w." then
sufbody = self:formatElem("w",mainb)
self:move()
else
sufbody = self:formatElem(self:dir(),mainb)
end
ret_line = ret_line .. sufbody
if self.look.targ==")" then
ret_line = ret_line .. self:formatElem(self.look,mainb)
self:move()
return ret_line
else error("illegal token: " .. self.look.targ) end
else error("illegal token: " .. self.look.targ) end
end,
btn = function(self)
if self:first(self.v.BTN) then
local ret_line, ret_button
if self.look.ttype==Type.BUTTON then
ret_button = color[self.look.targ]
ret_line = self:formatElem(self.look,ret_button)
self:move()
ret_line = ret_line .. self:btnp()
return ret_line, ret_button
elseif self.look.targ=="[" then
local pre = self.look.targ
self:move()
if self.look.ttype==Type.BUTTON then
ret_button = color[self.look.targ]
ret_line = self:formatElem(pre,ret_button)
ret_line = ret_line .. self:formatElem(self.look,ret_button)
self:move()
ret_line = ret_line .. self:btnp()
if self.look.targ=="]" then
ret_line = ret_line .. self:formatElem(self.look,ret_button)
self:move()
return ret_line, ret_button
else error("illegal token: " .. self.look.targ) end
else error("illegal token: " .. self.look.targ) end
elseif self.look.targ=="]" then
local pre = self.look.targ
self:move()
if self.look.ttype==Type.BUTTON then
ret_button = color[self.look.targ]
ret_line = self:formatElem(pre,ret_button)
ret_line = ret_line .. self:formatElem(self.look,ret_button)
self:move()
ret_line = ret_line .. self:btnp()
if self.look.targ=="[" then
ret_line = ret_line .. self:formatElem(self.look,ret_button)
self:move()
return ret_line, ret_button
else error("illegal token: " .. self.look.targ) end
else error("illegal token: " .. self.look.targ) end
elseif self.look.targ=="{" then
local pre = self.look.targ
self:move()
if self.look.ttype==Type.BUTTON then
ret_button = color[self.look.targ]
ret_line = self:formatElem(pre,ret_button)
ret_line = ret_line .. self:formatElem(self.look,ret_button)
self:move()
ret_line = ret_line .. self:btnp()
if self.look.targ=="}" then
ret_line = ret_line .. self:formatElem(self.look,ret_button)
self:move()
return ret_line, ret_button
else error("illegal token: " .. self.look.targ) end
else error("illegal token: " .. self.look.targ) end
elseif self.look.targ=="AT" then
ret_line = self:formatElem(self.look)
self:move()
return ret_line
end
else error("illegal token: " .. self.look.targ) end
end,
btn = btn:gsub('+','')
btnp = function(self)
return switch[btn]
if self:first(self.v.BTNP) then
end
local ret_line
 
ret_line = self:formatElem(self.look)
local function colorFormat(button, text)
self:move()
if self.look.ttype==Type.BUTTON then
local subb = color[self.look.targ]
ret_line = ret_line .. self:formatElem(self.look,subb)
self:move()
ret_line = ret_line .. self:btnp()
return ret_line
else error("illegal token: " .. self.look.targ) end
elseif not self:follow(self.v.BTNP) then return error("illegal token: " .. self.look.targ)
else return "" end
end,
if text == '' then return '' end
first = function(self,var)
if button == nil then return text end
if var==self.v.COMBOLIST then return self:first(self.v.COMBO)
local clr = getColor(button)
elseif var==self.v.COMBOLISTP then return self:first(self.v.COMBO)
if clr == nil then return text end
elseif var==self.v.COMBO then return self.look.targ=="(" or self:first(self.v.INPUTLIST)
local start = "<span style=\"color:"
elseif var==self.v.INPUTLIST then return self:first(self.v.INPUT)
local mid = ";\">"
elseif var==self.v.INPUTLISTP then return self.look.ttype==Type.CONNECTOR
local endstr = "</span>"
elseif var==self.v.INPUTN then return self:first(self.v.INPUT)
local ctext = start .. clr .. mid .. text .. endstr
elseif var==self.v.INPUT then return self.look.ttype==Type.WORD or self:first(self.v.BLOCKLIST)
elseif var==self.v.BLOCKLIST then return self:first(self.v.BLOCK)
elseif var==self.v.BLOCKLISTP then return self.look.targ=="/"
elseif var==self.v.BLOCK then return self.look.ttype==Type.DIRECTION or self:first(self.v.PREFIXLIST) or self:first(self.v.BTNSLIST)
elseif var==self.v.UCPREFIXLIST then return self:first(self.v.UCPREFIX)
elseif var==self.v.COMBOLISTP then return self:first(self.v.COMBO)
elseif var==self.v.UCPREFIX then return self.look.targ=="dl." or self.look.targ=="tk." or self.look.targ=="w."
elseif var==self.v.PREFIXLIST then return self:first(self.v.PREFIX)
elseif var==self.v.PREFIX then return self.look.ttype==Type.PREFIX
elseif var==self.v.DIR then return self.look.ttype==Type.DIRECTION
elseif var==self.v.BTNSLIST then return self:first(self.v.BTNS)
elseif var==self.v.BTNSLISTP then return self.look.targ=="~"
elseif var==self.v.BTNS then return self.look.ttype==Type.DIRECTION or self:first(self.v.BTN)
elseif var==self.v.SUFFIXLIST then return self:first(self.v.SUFFIX)
elseif var==self.v.SUFFIX then return self.look.targ=="("
elseif var==self.v.BTN then return self.look.ttype==Type.BUTTON or self.look.targ=="AT" or self.look.targ=="[" or self.look.targ=="]" or self.look.targ=="{"
elseif var==self.v.BTNP then return self.look.targ=="+"
else return false end
end,
return ctext
follow = function(self, var)
end
if var==self.v.COMBOLISTP then return self.look.targ=="(" or self.look.targ==")" or self.look.targ=="EOF"
elseif var==self.v.INPUTLISTP then return self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
elseif var==self.v.INPUTN then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
elseif var==self.v.BLOCKLISTP then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
elseif var==self.v.PREFIXLIST then return self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="]" or self.look.targ=="{"
elseif var==self.v.DIR then return self.look.ttype==Type.BUTTON or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="]" or self.look.targ=="{"
elseif var==self.v.BTNSLISTP then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="/" or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
elseif var==self.v.SUFFIXLIST then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="/" or self.look.targ=="~" or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
elseif var==self.v.BTNP then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="/" or self.look.targ=="~" or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="}" or self.look.targ=="EOF"
else return false end
end,
}
 


--actual method functions
--actual method functions
Line 68: Line 576:
local str = frame.args[1]
local str = frame.args[1]
local inputs = {}
lexer:newBuffer(str) --set the input string as the input buffer
--split the sequence into separate moves
return parser:start()
for link, command, separator, leftbracket, button, rightbracket, s1, suffix, s2 in string.gmatch(str, "([^%w]*)([t]?[k]?[Jj]?%.?[%d]*)([^%w%[%{ ]*)([%[%{]*)([%a%+]*)([%}%]]*)(%(?)(%w*)(%)?)") do
-- link: Everything in between moves. All whitespace and move linking syntax ends up here.
-- command: Directional numbers with an optional J for air moves.
-- separator: Any symbols between the command and button.
-- leftbracket: [ or { around the button
-- button: The button. Can be a single letter or a full word.
-- rightbracket: ] or } around the button
-- suffix: Extra alphanumeric characters directly after the move. s1 and s2 match surrounding parenthesis.
table.insert(inputs, link)
mainb = nil
--list of buttons not to be colored
bskip = {
"IAD",
"MD",
"RB",
"AT",
"Air",
"FC",
"CH",
}
skipf = false
for i, k in ipairs(bskip) do
if (button==k) then skipf = true end
end
if (skipf==false) then
buttons = {}
--get elements separated by +
    for b in string.gmatch(button, "([^+]+)") do
        table.insert(buttons,b)
    end
 
    --for each element split the single letters (for followups)
    for i,p in ipairs(buttons) do
singlebuttons = {}
    for b2 in string.gmatch(p, "([%a])") do
    table.insert(singlebuttons, colorFormat(b2, b2))
if mainb==nil then mainb = b2 end
    end
buttons[i] = table.concat(singlebuttons)
    end
    --combine everything into one string
    button = table.concat(buttons, "+")
    end
   
if separator ~= '' then
-- If the command and button input are separate, do not color the command input.
table.insert(inputs, command .. separator)
table.insert(inputs, colorFormat(mainb, leftbracket))
table.insert(inputs, button)
table.insert(inputs, colorFormat(mainb, rightbracket))
else
table.insert(inputs, colorFormat(mainb, command .. leftbracket))
table.insert(inputs, button)
table.insert(inputs, colorFormat(mainb, rightbracket))
end
if suffix ~= '' then
-- If there is a suffix, color it the same as the button.
table.insert(inputs, colorFormat(mainb, s1 .. suffix .. s2))
else
table.insert(inputs, s1 .. suffix .. s2)
end
end
local ret = "<span>" .. table.concat(inputs, "") .. "</span>"
return ret
end
end


return p
return p

Revision as of 10:01, 14 December 2023

Documentation for this module may be created at Module:Script-MBTL/doc

local p = {}

--custom script for the IS wiki, feel free to make a copy of it or take any part of the code for other wikis. ohoho I DID!!!!! 
--MBTL wiki fork

--split multiple properties passed as a single param to a template call and return the converted property text


--local objects used by p.gameInput()
local Type = {WORD=0, CONNECTOR=1, PREFIX=2, SUFFIX=3, SYMBOL=4, BUTTON=5, DIRECTION=6, SYS=7}

local Token = {
	ttype = nil,
	targ = nil,
	new = function(self,typ,arg)
		t = {}
		self.ttype = typ
		self.targ = arg
		setmetatable(t,self)
		self.__index = self
		return t
	end
}

local lexer = {
	buffer = "",
	readb = "",
	peek = " ",
	backup_buffer = "",
	backup_readb = "",
	newBuffer = function(self,buf)
		self.buffer = buf
	end,
	readch = function(self)
		if self.buffer:len() >= 1 then
			local r = self.buffer:sub(1,1)
			self.readb = self.readb .. r
			self.buffer = self.buffer:sub(2,-1)
			self.peek = r
		else
			self.peek = ":EOF:"
		end
	end,
	scan = function(self)
		while self.peek==" " or self.peek=="\t" or self.peek=="\n" or self.peek=="\r" do
			self:readch()
		end
		return self:cswitch()
	end,
	cswitch = function(self)
		if self.peek == ">" then 
			self.peek = " "
			return Token:new(Type.CONNECTOR," > ")
		elseif self.peek == "," then
			self.peek = " "
			return Token:new(Type.CONNECTOR," , ")
		elseif self.peek == "(" then
			self.peek = " "
			return Token:new(Type.SYMBOL,"(")
		elseif self.peek == ")" then
			self.peek = " "
			return Token:new(Type.SYMBOL,")")
		elseif self.peek == "[" then
			self.peek = " "
			return Token:new(Type.SYMBOL,"[")
		elseif self.peek == "]" then
			self.peek = " "
			return Token:new(Type.SYMBOL,"]")
		elseif self.peek == "{" then
			self.peek = " "
			return Token:new(Type.SYMBOL,"{")
		elseif self.peek == "}" then
			self.peek = " "
			return Token:new(Type.SYMBOL,"}")
		elseif self.peek == "~" then
			self.peek = " "
			return Token:new(Type.CONNECTOR,"~")
		elseif self.peek == "+" then
			self.peek = " "
			return Token:new(Type.CONNECTOR,"+")
		elseif self.peek == "/" then
			self.peek = " "
			return Token:new(Type.CONNECTOR,"/")
		elseif self.peek == ":EOF:" then
			self.peek = " "
			return Token:new(Type.SYS,"EOF")
		else
			if tonumber(self.peek) == nil then
				w = ""
				self.backup_buffer = self.buffer
				self.backup_readb = self.readb
				repeat
					w = w .. self.peek
					self:readch()
				until self.peek:match("%a")==nil or self.peek:match(":EOF:") --peek is not a letter or reached EOF
				if self.peek=="." then self.peek=" " end
				return self:wswitch(w)
			else
				d = ""
				repeat
					d = d .. self.peek
					self:readch()
				until tonumber(self.peek) == nil
				return Token:new(Type.DIRECTION,d)
			end
		end
	end,
	wswitch = function(self,word)
		if word=="dl" then
			return Token:new(Type.PREFIX,"dl.")
		elseif word=="j" then
			return Token:new(Type.PREFIX,"j.")
		elseif word=="dj" then
			return Token:new(Type.PREFIX,"dj.")
		elseif word=="w" then
			return Token:new(Type.PREFIX,"w.")
		elseif word=="tk" then
			return Token:new(Type.PREFIX,"tk.")
		elseif word=="md" or word=="microdash" then
			return Token:new(Type.PREFIX,"md.")
		elseif word=="A" then
			return Token:new(Type.BUTTON,"A")
		elseif word=="B" then
			return Token:new(Type.BUTTON,"B")
		elseif word=="C" then
			return Token:new(Type.BUTTON,"C")
		elseif word=="D" then
			return Token:new(Type.BUTTON,"D")
		elseif word=="X" then
			return Token:new(Type.BUTTON,"X")
		elseif word=="jc" then
			return Token:new(Type.WORD,"jc")
		elseif word=="sj" or word=="superjump" then
			return Token:new(Type.WORD,"sj")
		elseif word=="djc" then
			return Token:new(Type.WORD,"djc")
		elseif word=="IAD" or word=="iad" then
			return Token:new(Type.WORD,"IAD")
		elseif word=="AT" or word=="at" then
			return Token:new(Type.WORD,"AT")
		elseif word=="OR" or word=="or" then
			return Token:new(Type.WORD," OR ")
		elseif word=="CH" or word=="ch" then
			return Token:new(Type.WORD,"CH ")
		elseif word=="FC" or word=="fc" then
			return Token:new(Type.WORD,"FC ")
		elseif word=="OTG" or word=="otg" then
			return Token:new(Type.WORD,"OTG ")
		elseif word=="MD" then
			return Token:new(Type.WORD,"MD")
		elseif word=="AD" or word=="ad" then
			return Token:new(Type.WORD,"AD")
		elseif word=="LA" or word=="la" then
			return Token:new(Type.WORD,"LA")
		elseif word=="RB" or word=="rb" then
			if self.peek=="1" then
				self.peek = " "
				return Token:new(Type.WORD,"RB1")
			elseif self.peek=="2" then
				self.peek = " "
				return Token:new(Type.WORD,"RB2")
			else return nil
			end
		elseif word=="Heat" or word=="heat" then
			return Token:new(Type.WORD,"Heat")
		elseif word=="Dash" or word=="dash" then
			return Token:new(Type.WORD,"Dash")
		elseif word=="Airdash" or word=="airdash" then
			return Token:new(Type.WORD,"Airdash")
		elseif word=="Throw" or word=="throw" then
			return Token:new(Type.WORD,"Throw")
		elseif word=="Sideswap" or word=="sideswap" then
			return Token:new(Type.WORD,"Sideswap")
		elseif word=="Jump" or word=="jump" then
			return Token:new(Type.WORD,"Jump")
		elseif word=="Starter" or word=="starter" then
			return Token:new(Type.WORD,"Starter")
		elseif word=="Ender" or word=="ender" then
			return Token:new(Type.WORD,"Ender")
		else
			self.buffer = self.backup_buffer
			self.readb = self.backup_readb
			return self:wswitch(word:sub(1,1))
		end
	end,
}

local color = {
	["A"] = "#e2258a",
	["B"] = "#ff7606",
	["C"] = "#36b270",
	["D"] = "#1e8bcb",
	["X"] = "#686868",
}

local parser = {
	v = {COMBOLIST=0, COMBOLISTP=1, COMBO=2, INPUTLIST=3, INPUTLISTP=4, INPUTN=5, INPUT=6, BLOCKLIST=7, BLOCKLISTP=8, BLOCK=9, UCPREFIXLIST=10, UCPREFIX=11, PREFIXLIST=12, PREFIX=13, DIR=14, BTNSLIST=15, BTNSLISTP=16, BTNS=17, SUFFIXLIST=18, SUFFIX=19, BTN=20, BTNP=21},
	look = Token:new(Type.SYS,"START"),
	
	move = function(self)
		self.look = lexer:scan()
	end,
	
	formatElem = function(self,t,clr)
		if clr then
			return "<span style=\"color:" .. clr .. ";\">" .. (t.targ or t) .. "</span>" --works because the or is lazy so if t is a token it will not return t even if it's technically not nil because t.targ comes first
		else
			return t.targ or t
		end
	end,
	
	start = function(self)
		self:move()
		return (self:combolist()) --only return the line parameter
	end,
	
	combolist = function(self)
		if self:first(self.v.COMBOLIST) then
			local c_line = self:combo()
			local cp_line = self:combolistp()
			if self.look.targ~="EOF" then error("illegal token: " .. self.look.targ) end
			return c_line..cp_line
		else error("illegal starting token") end
	end,
	
	combolistp = function(self)
		if self:first(self.v.COMBOLISTP) then
			local c_line = self:combo()
			local cp_line = self:combolistp()
			return c_line..cp_line
		elseif not self:follow(self.v.COMBOLISTP) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	combo = function(self)
		if self:first(self.v.COMBO) then
			local ret_line
			if self.look.targ=="(" then
				ret_line = self:formatElem(self.look)
				self:move()
				ret_line = ret_line .. self:inputlist()
				ret_line = ret_line .. self:combolistp()
				if self.look.targ==")" then
					ret_line = ret_line .. self:formatElem(self.look)
					self:move()
				else error("illegal token: " .. self.look.targ) end
			else ret_line = self:inputlist() end
			return ret_line
		else error("illegal token: " .. self.look.targ) end
	end,
	
	inputlist = function(self)
		if self:first(self.v.INPUTLIST) then
			local ret_line
			ret_line = self:input()
			ret_line = ret_line .. self:inputlistp()
			return ret_line
		else error("illegal token: " .. self.look.targ) end
	end,
	
	inputlistp = function(self)
		if self:first(self.v.INPUTLISTP) then
			local ret_line
			ret_line = self:formatElem(self.look)
			self:move()
			ret_line = ret_line .. self:inputn()
			ret_line = ret_line .. self:inputlistp()
			return ret_line
		elseif not self:follow(self.v.INPUTLISTP) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	inputn = function(self)
		if self:first(self.v.INPUTN) then
			return (self:input())
		elseif not self:follow(self.v.INPUTN) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	input = function(self)
		if self:first(self.v.INPUT) then
			local ret_line
			if self.look.ttype==Type.WORD then
				ret_line = self:formatElem(self.look)
				self:move()
			else ret_line = self:blocklist() end
			return ret_line
		else error("illegal token: " .. self.look.targ) end
	end,
	
	blocklist = function(self)
		if self:first(self.v.BLOCKLIST) then
			local ret_line
			ret_line = self:block()
			ret_line = ret_line .. self:blocklistp()
			return ret_line
		else error("illegal token: " .. self.look.targ) end
	end,
	
	blocklistp = function(self)
		if self:first(self.v.BLOCKLISTP) then
			local ret_line
			ret_line = self:formatElem(self.look)
			self:move()
			ret_line = ret_line .. self:block()
			ret_line = ret_line .. self:blocklistp()
			return ret_line
		elseif not self:follow(self.v.BLOCKLISTP) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	block = function(self)
		if self:first(self.v.BLOCK) then
			local ret_line
			local up = self:ucprefixlist()
			local pf = self:prefixlist()
			local bl_line, bl_button = self:btnslist()
			ret_line = self:formatElem(up) .. self:formatElem(pf,bl_button) .. bl_line
			return ret_line
		else error("illegal token: " .. self.look.targ) end
	end,
	
	ucprefixlist = function(self)
		if self:first(self.v.UCPREFIXLIST) then
			local ret_line
			ret_line = self:ucprefix()
			ret_line = ret_line .. self:ucprefixlist()
			return ret_line
		else return "" end
	end,
	
	ucprefix = function(self)
		if self:first(self.v.UCPREFIX) then
			local u = self.look.targ
			self:move()
			return u
		else return "" end
	end,
	
	prefixlist = function(self)
		if self:first(self.v.PREFIXLIST) then
			local ret_line
			ret_line = self:prefix()
			ret_line = ret_line .. self:prefixlist()
			return ret_line
		elseif not self:follow(self.v.PREFIXLIST) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	prefix = function(self)
		if self:first(self.v.PREFIX) then
			local p = self.look.targ
			self:move()
			return p
		else error("illegal token: " .. self.look.targ) end
	end,
	
	dir = function(self)
		if self:first(self.v.DIR) then
			local d = self.look.targ
			self:move()
			return d
		elseif not self:follow(self.v.DIR) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	btnslist = function(self)
		if self:first(self.v.BTNSLIST) then
			local ret_line, ret_button
			ret_line, ret_button = self:btns()
			ret_line = ret_line .. self:btnslistp()
			return ret_line, ret_button
		else error("illegal token: " .. self.look.targ) end
	end,
	
	btnslistp = function(self)
		if self:first(self.v.BTNSLISTP) then
			local ret_line
			ret_line = self:formatElem(self.look)
			self:move()
			ret_line = ret_line .. self:btns()
			ret_line = ret_line .. self:btnslistp()
			return ret_line
		elseif not self:follow(self.v.BTNSLISTP) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	btns = function(self)
		if self:first(self.v.BTNS) then
			local dr = self:dir()
			local b_line, b_button = self:btn()
			b_line = self:formatElem(dr,b_button) .. b_line .. self:suffixlist(b_button)
			return b_line, b_button
		else error("illegal token: " .. self.look.targ) end
	end,
	
	suffixlist = function(self,mainb)
		if self:first(self.v.SUFFIXLIST) then
			local ret_line
			ret_line = self:suffix(mainb)
			ret_line = ret_line .. self:suffixlist(mainb)
			return ret_line
		elseif not self:follow(self.v.SUFFIXLIST) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	suffix = function(self,mainb)
		if self:first(self.v.SUFFIX) then
			local ret_line
			ret_line = self:formatElem(self.look, mainb)
			self:move()
			local sufbody = ""
			if self.look.targ=="w." then
				sufbody = self:formatElem("w",mainb)
				self:move()
			else 
				sufbody = self:formatElem(self:dir(),mainb)
			end
			ret_line = ret_line .. sufbody
			if self.look.targ==")" then
				ret_line = ret_line .. self:formatElem(self.look,mainb)
				self:move()
				return ret_line
			else error("illegal token: " .. self.look.targ) end
		else error("illegal token: " .. self.look.targ) end
	end,
	
	btn = function(self)
		if self:first(self.v.BTN) then
			local ret_line, ret_button
			if self.look.ttype==Type.BUTTON then
				ret_button = color[self.look.targ]
				ret_line = self:formatElem(self.look,ret_button)
				self:move()
				ret_line = ret_line .. self:btnp()
				return ret_line, ret_button
			elseif self.look.targ=="[" then
				local pre = self.look.targ
				self:move()
				if self.look.ttype==Type.BUTTON then
					ret_button = color[self.look.targ]
					ret_line = self:formatElem(pre,ret_button)
					ret_line = ret_line .. self:formatElem(self.look,ret_button)
					self:move()
					ret_line = ret_line .. self:btnp()
					if self.look.targ=="]" then
						ret_line = ret_line .. self:formatElem(self.look,ret_button)
						self:move()
						return ret_line, ret_button
					else error("illegal token: " .. self.look.targ) end
				else error("illegal token: " .. self.look.targ) end
			elseif self.look.targ=="]" then
				local pre = self.look.targ
				self:move()
				if self.look.ttype==Type.BUTTON then
					ret_button = color[self.look.targ]
					ret_line = self:formatElem(pre,ret_button)
					ret_line = ret_line .. self:formatElem(self.look,ret_button)
					self:move()
					ret_line = ret_line .. self:btnp()
					if self.look.targ=="[" then
						ret_line = ret_line .. self:formatElem(self.look,ret_button)
						self:move()
						return ret_line, ret_button
					else error("illegal token: " .. self.look.targ) end
				else error("illegal token: " .. self.look.targ) end
			elseif self.look.targ=="{" then
				local pre = self.look.targ
				self:move()
				if self.look.ttype==Type.BUTTON then
					ret_button = color[self.look.targ]
					ret_line = self:formatElem(pre,ret_button)
					ret_line = ret_line .. self:formatElem(self.look,ret_button)
					self:move()
					ret_line = ret_line .. self:btnp()
					if self.look.targ=="}" then
						ret_line = ret_line .. self:formatElem(self.look,ret_button)
						self:move()
						return ret_line, ret_button
					else error("illegal token: " .. self.look.targ) end
				else error("illegal token: " .. self.look.targ) end
			elseif self.look.targ=="AT" then
				ret_line = self:formatElem(self.look)
				self:move()
				return ret_line
			end
		else error("illegal token: " .. self.look.targ) end
	end,
	
	btnp = function(self)
		if self:first(self.v.BTNP) then
			local ret_line
			ret_line = self:formatElem(self.look)
			self:move()
			if self.look.ttype==Type.BUTTON then
				local subb = color[self.look.targ]
				ret_line = ret_line .. self:formatElem(self.look,subb)
				self:move()
				ret_line = ret_line .. self:btnp()
				return ret_line
			else error("illegal token: " .. self.look.targ) end
		elseif not self:follow(self.v.BTNP) then return error("illegal token: " .. self.look.targ)
		else return "" end
	end,
	
	first = function(self,var)
		if var==self.v.COMBOLIST then return self:first(self.v.COMBO)
		elseif var==self.v.COMBOLISTP then return self:first(self.v.COMBO)
		elseif var==self.v.COMBO then return self.look.targ=="(" or self:first(self.v.INPUTLIST)
		elseif var==self.v.INPUTLIST then return self:first(self.v.INPUT)
		elseif var==self.v.INPUTLISTP then return self.look.ttype==Type.CONNECTOR
		elseif var==self.v.INPUTN then return self:first(self.v.INPUT)
		elseif var==self.v.INPUT then return self.look.ttype==Type.WORD or self:first(self.v.BLOCKLIST)
		elseif var==self.v.BLOCKLIST then return self:first(self.v.BLOCK)
		elseif var==self.v.BLOCKLISTP then return self.look.targ=="/"
		elseif var==self.v.BLOCK then return self.look.ttype==Type.DIRECTION or self:first(self.v.PREFIXLIST) or self:first(self.v.BTNSLIST)
		elseif var==self.v.UCPREFIXLIST then return self:first(self.v.UCPREFIX)
		elseif var==self.v.COMBOLISTP then return self:first(self.v.COMBO)
		elseif var==self.v.UCPREFIX then return self.look.targ=="dl." or self.look.targ=="tk." or self.look.targ=="w."
		elseif var==self.v.PREFIXLIST then return self:first(self.v.PREFIX)
		elseif var==self.v.PREFIX then return self.look.ttype==Type.PREFIX
		elseif var==self.v.DIR then return self.look.ttype==Type.DIRECTION
		elseif var==self.v.BTNSLIST then return self:first(self.v.BTNS)
		elseif var==self.v.BTNSLISTP then return self.look.targ=="~"
		elseif var==self.v.BTNS then return self.look.ttype==Type.DIRECTION or self:first(self.v.BTN)
		elseif var==self.v.SUFFIXLIST then return self:first(self.v.SUFFIX)
		elseif var==self.v.SUFFIX then return self.look.targ=="("
		elseif var==self.v.BTN then return self.look.ttype==Type.BUTTON or self.look.targ=="AT" or self.look.targ=="[" or self.look.targ=="]" or self.look.targ=="{"
		elseif var==self.v.BTNP then return self.look.targ=="+"
		else return false end
	end,
	
	follow = function(self, var)
		if var==self.v.COMBOLISTP then return self.look.targ=="(" or self.look.targ==")" or self.look.targ=="EOF"
		elseif var==self.v.INPUTLISTP then return self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
		elseif var==self.v.INPUTN then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
		elseif var==self.v.BLOCKLISTP then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
		elseif var==self.v.PREFIXLIST then return self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="]" or self.look.targ=="{"
		elseif var==self.v.DIR then return self.look.ttype==Type.BUTTON or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="]" or self.look.targ=="{"
		elseif var==self.v.BTNSLISTP then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="/" or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
		elseif var==self.v.SUFFIXLIST then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="/" or self.look.targ=="~" or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="EOF"
		elseif var==self.v.BTNP then return self.look.ttype==Type.CONNECTOR or self.look.ttype==Type.WORD or self.look.ttype==Type.PREFIX or self.look.ttype==Type.BUTTON or self.look.ttype==Type.DIRECTION or self.look.targ=="/" or self.look.targ=="~" or self.look.targ==")" or self.look.targ=="(" or self.look.targ=="]" or self.look.targ=="[" or self.look.targ=="{" or self.look.targ=="}" or self.look.targ=="EOF"
		else return false end
	end,
}


--actual method functions
	
function p.multiprop(frame)
	
	local str = frame.args[1]
	local sep = ","
	local t={}
	--split properties
	for str in string.gmatch(str, "([^"..sep.."]+)") do
			table.insert(t, str)
	end
	--get the string done
	for index, prop in ipairs(t) do
		if prop:sub(1,1) == "%s" then
			prop = prop:sub(2,-1)
		end
		expanded = frame:expandTemplate{ title="Property-MBTL", args={prop,"IGNOREPROPERTYERROR"}}
		t[index] = expanded
	end
	
	ret = table.concat(t,", ")
	
	return ret
end

--color code and format game inputs.
--supports entire combos as arguments.
function p.gameInput(frame)
	
	local str = frame.args[1]
	lexer:newBuffer(str) --set the input string as the input buffer
	return parser:start()
	
end

return p