bootstrap compiler

This commit is contained in:
2026-04-25 16:59:45 +02:00
commit e0546bcda5
10 changed files with 3632 additions and 0 deletions

48
main.lua Normal file
View File

@@ -0,0 +1,48 @@
require('src.util')
require("src.types")
require("src.lexer")
require("src.parser")
require("src.lower")
require("src.output")
require("src.methods.debug")
require("src.methods.small")
require("src.cli")
-- local file = assert(io.open("code.lake", "r"))
-- local code = file:read("*all")
-- local lexer = Lexer:new(code, "test.lake")
-- local lexer2 = Lexer:new(code, "test.lake")
-- while true do
-- local tok, err = lexer2:consume()
-- if not tok then
-- error("oh no " .. tostring(err.message))
-- end
-- print(tok.kind, tok.content)
-- if tok.kind == "eof" then break end
-- end
-- print'\n\n'
-- local parser = Parser:new(lexer)
-- local program, err = parser:parse()
-- if not program then
-- error(err.message)
-- end
-- ERGON = require("ERGON")
-- print(ERGON.encodeIndented(program, 1, " "))
-- local output = Output:new("lua54", "debug")
-- output:output(program)
-- print'\n\n'
-- print(table.concat(output.lines, "\n"))

171
src/cli.lua Normal file
View File

@@ -0,0 +1,171 @@
---@param args string[]
---@param argConfig table<string, string[]>
function ParseArgData(args, argConfig)
local i = 1
local knorps = {}
local flarples = {}
for k,v in pairs(argConfig) do
if v[1] == "flag" then
flarples[v[2]] = false
elseif v[1] == "list" then
flarples[v[2]] = {}
elseif v[1] == "input" then
-- no
end
end
while i <= #args do
local argument = args[i]
if argConfig[argument] then
local argconf = argConfig[argument]
local argtype = argconf[1]
local storageplace = argconf[2]
if argtype == "flag" then
flarples[storageplace] = true
elseif argtype == "input" then
local next = args[i+1]
i = i + 1 -- don't get it checked as a flag because that'd be rerarded
if not next then
error("gdsiohjubhoupidsgohupigdshoupigsdhpoid")
end
flarples[storageplace] = next
elseif argtype == "list" then
local next = args[i+1]
i = i + 1 -- don't get it checked as a flag because that'd be rerarded
if not next then
error("gdsiohjubhoupidsgohupigdshoupigsdhpoid")
end
table.insert(flarples[storageplace], next)
end
else
if argument:sub(1,1) == "-" then
error("fuck you")
end
knorps[#knorps+1] = argument
end
i = i + 1
end
return knorps, flarples
end
if #arg == 0 then
while true do
io.write("\x1b[36minput>\x1b[0m ")
io.flush()
local line = io.read("L")
if not line then break end
local lexer = Lexer:new(line, "stdin")
local parser = Parser:new(lexer)
local ast, err = parser:parse()
if err then
local msg = string.format("%s:%d %s", err.location.file, err.location.line, err.message)
print("\x1b[31m" .. msg .. "\x1b[0m")
else
assert(ast, "incredibly bad no good state very bad crash now")
-- this is the best code ever made
local output = Output:new("lua54", "small")
output:output(ast)
local outdata = table.concat(output.lines, '\n')
print(outdata)
end
end
return
end
if arg[1] == "compile" then
table.remove(arg, 1)
local options = {
['-v'] = {'flag', 'verbose'},
['-o'] = {'input', 'output'},
['-s'] = {'input', 'sourcemap'},
-- ['-d'] = {'list', 'dump'},
-- ['-D'] = {'input', 'dependency'},
['-m'] = {'input', 'method'},
['-t'] = {'input', 'target'}
}
local knorps, flarples = ParseArgData(arg, options)
flarples.target = flarples.target or "lua54"
flarples.method = flarples.method or "debug"
if not flarples.output then
print("\x1b[31mno output file given\x1b[0m")
os.exit(1)
end
local outfile = io.open(flarples.output, "w")
if not outfile then
print("\x1b[31moutput file not writable\x1b[0m")
os.exit(1)
end
local SMfile
if flarples.sourcemap then
SMfile = io.open(flarples.sourcemap, 'w')
if not SMfile then
print("\x1b[31msourcemap file not writable\x1b[0m")
os.exit(1)
end
end
for i = 1,#knorps do
local file = io.open(knorps[i],"r")
if not file then
print("\x1b[31mcould not read file " .. tostring(knorps[i]) .."\x1b[0m")
os.exit(1)
end
local code = file:read("*all")
file:close()
local lexer = Lexer:new(code, knorps[i])
local parser = Parser:new(lexer)
local ast,err = parser:parse()
if not ast then
if err then
err:print()
else
print("\x1b[31m!!! an unknown error occured !!!\x1b[0m")
end
os.exit(1)
end
local output = Output:new(flarples.target, flarples.method)
output:output(ast)
local outdata = table.concat(output.lines, '\n')
local smdata = table.concat(output.debug, '\n')
outfile:write(outdata);
if SMfile then
SMfile:write(smdata)
end
end
outfile:close()
if SMfile then
SMfile:close()
end
end

321
src/lexer.lua Normal file
View File

@@ -0,0 +1,321 @@
---@class Lexer
---@field cache Token?
---@field cursor integer
---@field location Location
---@field code string
Lexer = {}
Lexer.keywords = {
"var",
"return",
"fn",
"true",
"false",
"null",
"if",
"var",
"else",
"elseif",
"while",
"for",
"do",
"break",
"foreach"
}
Lexer.symbols = {
"!=",
"==",
"<=",
">=",
"&&=",
"||=",
"&&",
"||",
"+=",
"-=",
"*=",
"/=",
"%=",
"^=",
"..=",
"..",
">",
"<",
"+",
"-",
"*",
"/",
"^",
"=",
";",
"#",
"!",
"[",
"]",
".",
"{",
"}",
"(",
")",
",",
"%",
':'
}
function Lexer:new(code, fname)
return setmetatable({
cache = nil,
cursor = 1,
location = Location:new(fname),
code = code,
}, {__index = self})
end
function Lexer:next(n)
n = n or 1
for i = 1,n do
self.location:skip(self.code:sub(self.cursor,self.cursor))
self.cursor = self.cursor + 1
end
end
function Lexer:get(n)
n = n or 1
return self.code:sub(self.cursor, self.cursor+n-1)
end
function Lexer:isWhitespace(ch)
return ch == "\r" or ch == "\n" or ch == "\t" or ch == " "
end
function Lexer:isDigit(ch)
local n = string.byte(ch)
if not n then return false end
return n >= 48 and n <= 57
end
function Lexer:isHex(ch)
return (ch:match("%x") ~= nil)
end
function Lexer:isIdentifierValid(ch)
return (ch:match("%a") ~= nil) or (Lexer:isDigit(ch)) or (ch == '_')
end
function Lexer:run()
while true do -- skip skippables
if self:isWhitespace(self:get()) then
self:next()
elseif self:get(2) == "//" then -- comment yay wooo i love it i'm so happy yayyyy
while (self:get() ~= "\n") and (self:get() ~= "") do
self:next()
end
elseif self:get() == '' then
return Token:new("eof", "eof", self.location:clone())
else
break
end
end
if self:isDigit(self:get()) then
-- number!!!
local stpos = self.location:clone()
local st = ""
while self:isDigit(self:get()) do
st = st .. self:get()
self:next()
end
if self:get() == '.' then
-- omg number
self:next()
st = st .. "."
while self:isDigit(self:get()) do
st = st .. self:get()
self:next()
end
end
return Token:new("number", st, stpos)
end
if self:get() == "'" then -- rawer string
local d = "'"
local stpos = self.location:clone()
self:next()
while self:get() ~= "'" do
local ch = self:get()
if ch == '' then break end
d = d .. ch
self:next()
end
if self:get() ~= "'" then
return nil, Error:new("unfinished string", "lexer", self.location:clone())
end
d = d .. "'"
self:next()
return Token:new("string", d, stpos)
end
if self:get() == '"' then -- not raw string
local d = '"'
local stpos = self.location:clone()
self:next()
while true do
local ch = self:get()
if ch == '\\' then
d = d .. ch
self:next()
local nch = self:get()
if nch == '\\' then
d = d .. '\\'
self:next()
elseif nch == 'n' then
d = d .. 'n'
self:next()
elseif nch == 'r' then
d = d .. 'r'
self:next()
elseif nch == 'v' then
d = d .. 'v'
self:next()
elseif nch == '"' then
d = d .. '"'
self:next()
elseif nch == 'f' then
d = d .. 'f'
self:next()
elseif nch == 't' then
d = d .. "t"
self:next()
elseif nch == 'x' then
d = d .. 'x'
self:next()
local hexdata = self:get(2)
if (not self:isHex(hexdata:sub(1,1))) or (not self:isHex(hexdata:sub(2,2))) then
return nil, Error:new("invalid hex digits after x escape code", 'lexer', self.location:clone());
end
d = d .. hexdata
self:next(2)
elseif nch == "u" then
d = d .. "u"
self:next()
if self:get() ~= '{' then
return nil, Error:new('expected { after u escape', 'lexer', self.location:clone());
end
d = d .. "{"
self:next()
local i = 0
while self:isHex(self:get()) and (self:get() ~= '') do -- TODO: check validity
d = d .. self:get()
self:next()
i = i + 1
end
if i > 0 then
local hexpart = d:sub(-i)
local value = HexDecode(hexpart)
if value >= 2^31 then
return nil, Error:new("invalid utf8 codepoint in u escape", 'lexer', self.location:clone())
end
end
if self:get() ~= '}' then
return nil, Error:new('expected } to close { in u escape', 'lexer', self.location:clone())
end
d = d .. "}"
self:next()
end
elseif ch == '' then
break;
elseif ch == '"' then
break;
else
d = d .. ch; -- assume it's just something you can have in there
self:next()
end
end
if self:get() ~= '"' then
return nil, Error:new('unfinished double-quote string', 'lexer', self.location:clone())
end
d = d .. '"'
self:next()
return Token:new("string", d, stpos)
end
do
local stloc = self.location:clone()
for i = 1,#self.symbols do
local symbol = self.symbols[i]
if self:get(#symbol) == symbol then
-- ladies and gentlemen, we got him.
self:next(#symbol)
return Token:new("symbol", symbol, stloc)
end
end
end
-- ident!
if self:isIdentifierValid(self:get()) then
local ident = ""
local stpos = self.location:clone()
while self:isIdentifierValid(self:get()) do
ident = ident .. self:get()
self:next()
end
for i = 1,#self.keywords do
local kw = self.keywords[i]
if ident == kw then
return Token:new("keyword", kw, stpos)
end
end
return Token:new("identifier", ident, stpos)
end
return nil, Error:new("tried to lex invalid code", "lexer", self.location:clone())
end
function Lexer:peek()
if self.cache ~= nil then
return self.cache, nil
end
local token, err = self:run()
self.cache = token
return token, err
end
function Lexer:consume()
if self.cache == nil then
return self:run()
end
local token = self.cache
self.cache = nil
return token, nil
end

129
src/lower.lua Normal file
View File

@@ -0,0 +1,129 @@
Lower = {}
function Lower.makeUTF8Char(decimal)
if decimal < 128 then
return string.char(decimal)
elseif decimal < 2048 then
local byte2 = (128 + (decimal % 64))
local byte1 = (192 + math.floor(decimal / 64))
return string.char(byte1, byte2)
elseif decimal < 65536 then
local byte3 = (128 + (decimal % 64))
decimal = math.floor(decimal / 64)
local byte2 = (128 + (decimal % 64))
local byte1 = (224 + math.floor(decimal / 64))
return string.char(byte1, byte2, byte3)
elseif decimal < 1114112 then
local byte4 = (128 + (decimal % 64))
decimal = math.floor(decimal / 64)
local byte3 = (128 + (decimal % 64))
decimal = math.floor(decimal / 64)
local byte2 = (128 + (decimal % 64))
local byte1 = (240 + math.floor(decimal / 64))
return string.char(byte1, byte2, byte3, byte4)
else
return nil -- Invalid Unicode code point
end
end
function Lower.decodeString(lakeString)
if lakeString:sub(1,1) == "'" then
-- easy
return lakeString:sub(2,-2) -- bad way to do it but it works lol lmfao rofl
end
if lakeString:sub(1,1) == '"' then
local i = 2
local out = ""
while true do
local ch = lakeString:sub(i,i)
if ch == '\\' then
i = i + 1
local nextch = lakeString:sub(i,i)
if nextch == '\\' then
out = out .. '\\'
i = i + 1;
elseif nextch == 'n' then
out = out .. '\n'
i = i + 1;
elseif nextch == 'r' then
out = out .. '\r'
i = i + 1;
elseif nextch == 'v' then
out = out .. '\v'
i = i + 1;
elseif nextch == '"' then
out = out .. '"'
i = i + 1;
elseif nextch == 'f' then
out = out .. '\f'
i = i + 1;
elseif nextch == "t" then
out = out .. "\t"
i = i + 1
elseif nextch == 'x' then
i = i + 1;
local hex = lakeString:sub(i, i + 1)
i = i + 2;
local decoded = HexDecode(hex);
out = out .. string.char(decoded)
elseif nextch == "u" then
i = i + 2; -- skip u and {, if the { wasn't there that's the lexer's fault and their bug.
local hexpart = ""
while lakeString:sub(i,i) ~= '}' do
hexpart = hexpart .. lakeString:sub(i,i)
i = i + 1
end
-- on the }, skip it:
i = i + 1 -- if we were not on it, welp
local decoded = HexDecode(hexpart)
out = out .. Lower.makeUTF8Char(decoded);
end
elseif ch == '"' then
break;
else
i = i + 1
out = out .. ch
end
end
return out;
end
end
function Lower.decodeNumber(num)
local numeric = 0
local decimal = false
for i = 1,#num do
local ch = num:sub(i,i)
if ch == "." then
decimal = i + 1
break
end
numeric = numeric * 10
numeric = numeric + (string.byte(ch) - string.byte'0')
end
if decimal then
local n = 0.1
for j = decimal, #num do
local ch = num:sub(j,j)
numeric = numeric + (string.byte(ch) - string.byte'0')*n
n = n * 0.1
end
end
return numeric
end

676
src/methods/debug.lua Normal file
View File

@@ -0,0 +1,676 @@
Debug = {}
-- very basic, but should always be correct and doesn't try any optimization shenanigans
function Debug.encodeString(raw, islua51)
local out = '"'
for i = 1,#raw do
local ch = raw:sub(i,i)
local code = string.byte(ch)
if ch == '\\' then
out = out .. "\\\\"
elseif ch == '"' then
out = out .. '\\"'
elseif code >= 32 and code <= 126 then
out = out .. ch
else
if islua51 then
out = out .. "\\" .. DecEncode(code, 3)
else
out = out .. "\\x" .. HexEncode(code, 2)
end
end
end
out = out .. '"'
return out
end
function Debug.encodeNumber(n)
local num = ''
local isneg = false
if n < 0 then
n = n * -1
isneg = true;
end
local whole = math.floor(n)
if whole == 0 then
num = num .. '0'
end
while whole > 0 do
local digit = whole % 10
num = string.char(string.byte'0' + digit) .. num
whole = math.floor(whole / 10)
end
local dec = n % 1
if dec > 0 then
num = num .. "."
while dec > 0 do
-- aw nah
dec = dec * 10
local digit = math.floor(dec)
num = num .. string.char(string.byte'0' + digit)
dec = dec % 1
end
end
if isneg then
num = '-' .. num
end
return num
end
function Debug.convertBinaryOperator(lakeop)
if lakeop == "+" then
return "+"
elseif lakeop == "-" then
return "-"
elseif lakeop == "*" then
return "*"
elseif lakeop == "/" then
return "/"
elseif lakeop == "%" then
return "%"
elseif lakeop == "^" then
return "^"
elseif lakeop == ".." then
return ".."
elseif lakeop == "<" then
return "<"
elseif lakeop == ">" then
return ">"
elseif lakeop == "<=" then
return "<="
elseif lakeop == ">=" then
return ">="
elseif lakeop == "!=" then
return "~="
elseif lakeop == "==" then
return "=="
elseif lakeop == "&&" then
return "and"
elseif lakeop == "||" then
return "or"
end
end
function Debug.convertUnaryOperator(op)
if op == "#" then
return "#"
elseif op == "-" then
return "-"
elseif op == "!" then
return "not"
end
end
function Debug.output(outp, ast)
Debug.outputStatement(outp, ast)
end
---@param outp Output
---@param node Node
function Debug.outputStatement(outp, node)
if node.kind == "Program" then
outp:addLine("do", node.location:format())
for i = 1,#node.content do
local stmt = node.content[i]
Debug.outputStatement(outp, stmt)
end
outp:addLine("end;", node.location:format())
elseif node.kind == "VariableDeclaration" then
outp:addLine("local", node.location:format())
local varnames = node.content[1]
for i = 1,#varnames do
local vname = varnames[i]
outp:addLine(vname[1], vname[2]:format())
if i ~= #varnames then
outp:addLine(",", node.location:format())
end
end
local values = node.content[2]
if #values > 0 then
outp:addLine("=", node.location:format())
for i = 1,#values do
local val = values[i]
Debug.outputExpression(outp, val)
if i ~= #values then
outp:addLine(",", node.location:format())
end
end
end
outp:addLine(";", node.location:format())
elseif node.kind == "Block" then
outp:addLine("do", node.location:format())
for i = 1,#node.content do
local stmt = node.content[i]
Debug.outputStatement(outp, stmt)
end
outp:addLine("end;", node.location:format())
elseif node.kind == "IfStatement" then
outp:addLine("if", node.location:format())
Debug.outputExpression(outp, node.content[1][1][1])
outp:addLine("then", node.location:format())
Debug.outputStatement(outp, node.content[1][1][2])
for i = 2, #node.content[1] do
local item = node.content[1][i]
-- print(item, item[1], item[2])
outp:addLine("elseif", node.location:format())
Debug.outputExpression(outp,item[1]);
outp:addLine("then", node.location:format())
Debug.outputStatement(outp,item[2]);
end
if node.content[2] then
outp:addLine("else", node.location:format())
Debug.outputStatement(outp, node.content[2])
end
outp:addLine("end;", node.location:format())
elseif node.kind == "FunctionDeclaration" then
outp:addLine("function", node.location:format())
Debug.outputFunctionName(outp, node.content[1]);
outp:addLine("(", node.location:format())
for i = 1,#node.content[2] do
local part = node.content[2][i]
outp:addLine(part, node.location:format())
if i ~= #node.content[2] then
outp:addLine(",", node.location:format())
end
end
outp:addLine(")", node.location:format())
Debug.outputStatement(outp, node.content[3])
outp:addLine("end;", node.location:format())
elseif node.kind == "LocalFunctionDeclaration" then
outp:addLine("local function", node.location:format())
outp:addLine(node.content[1].content, node.content[1].location:format());
outp:addLine("(", node.location:format())
for i = 1,#node.content[2] do
local part = node.content[2][i]
outp:addLine(part, node.location:format())
if i ~= #node.content[2] then
outp:addLine(",", node.location:format())
end
end
outp:addLine(")", node.location:format())
Debug.outputStatement(outp, node.content[3])
outp:addLine("end;", node.location:format())
elseif node.kind == "Return" then
outp:addLine("return", node.location:format())
for i = 1,#node.content do
local expr = node.content[i]
Debug.outputExpression(outp, expr)
if i ~= #node.content then
outp:addLine(",", node.location:format())
end
end
outp:addLine(";", node.location:format())
elseif node.kind == "Assignment" then
local vars = node.content[1]
local vals = node.content[2]
for i = 1,#vars do
local var = vars[i]
Debug.outputVariable(outp, var)
if i ~= #vars then
outp:addLine(",", node.location:format())
end
end
outp:addLine("=", node.location:format())
for i = 1,#vals do
local val = vals[i]
Debug.outputExpression(outp, val)
if i ~= #vals then
outp:addLine(",", node.location:format())
end
end
outp:addLine(";", node.location:format())
elseif node.kind == "Call" then
outp:addLine("(", node.location:format())
Debug.outputExpression(outp, node.content[1])
outp:addLine(")(", node.location:format())
local args = node.content[2]
for i = 1,#args do
local arg = args[i]
Debug.outputExpression(outp, arg)
if i ~= #args then
outp:addLine(",", node.location:format())
end
end
outp:addLine(")", node.location:format())
outp:addLine(";", node.location:format())
elseif node.kind == "MethodCall" then
outp:addLine("(", node.location:format())
Debug.outputExpression(outp, node.content[1])
outp:addLine("):", node.location:format())
outp:addLine(node.content[2] .. '(', node.location:format())
local args = node.content[3]
for i = 1,#args do
local arg = args[i]
Debug.outputExpression(outp, arg)
if i ~= #args then
outp:addLine(",", node.location:format())
end
end
outp:addLine(")", node.location:format())
outp:addLine(";", node.location:format())
elseif node.kind == "While" then
outp:addLine("while", node.location:format())
Debug.outputExpression(outp, node.content[1])
outp:addLine("do", node.location:format())
Debug.outputStatement(outp, node.content[2])
outp:addLine("end;", node.location:format())
elseif node.kind == "For" then
outp:addLine("do", node.location:format())
Debug.outputStatement(outp,node.content[1]);
outp:addLine("while", node.location:format())
Debug.outputExpression(outp,node.content[2]);
outp:addLine("do", node.location:format())
outp:addLine("do", node.location:format())
Debug.outputStatement(outp,node.content[4]);
outp:addLine("end;", node.location:format())
Debug.outputStatement(outp,node.content[3]);
outp:addLine("end;", node.location:format())
outp:addLine("end;", node.location:format())
elseif node.kind == "OperatorAssignment" then
local lake_op = node.content[2]
local op = Debug.convertBinaryOperator(lake_op)
local lhs = node.content[1]
local rhs = node.content[3]
if lhs.kind == "Variable" then
local varname = lhs.content
outp:addLine(varname, node.location:format())
outp:addLine('=', node.location:format())
outp:addLine('(', node.location:format())
outp:addLine(varname, node.location:format())
outp:addLine(op, node.location:format())
outp:addLine('(', node.location:format())
Debug.outputExpression(outp, rhs)
outp:addLine('));', node.location:format())
elseif lhs.kind == "Field" then
outp:addLine('do', node.location:format())
outp:addLine('local _lake_operator_assignment_tmp =', node.location:format())
Debug.outputExpression(outp, lhs.content[1])
outp:addLine(';', node.location:format());
outp:addLine('(_lake_operator_assignment_tmp).', node.location:format())
outp:addLine(lhs.content[2], node.location:format())
outp:addLine('=', node.location:format())
outp:addLine('(', node.location:format())
outp:addLine('(_lake_operator_assignment_tmp).', node.location:format())
outp:addLine(lhs.content[2], node.location:format())
outp:addLine(op, node.location:format())
outp:addLine('(', node.location:format())
Debug.outputExpression(outp, rhs)
outp:addLine('));', node.location:format())
outp:addLine('end;', node.location:format())
elseif lhs.kind == "Index" then
outp:addLine('do', node.location:format())
outp:addLine('local _lake_operator_assignment_tmp =', node.location:format())
Debug.outputExpression(outp, lhs.content[1])
outp:addLine(';', node.location:format());
outp:addLine('(_lake_operator_assignment_tmp)[', node.location:format())
Debug.outputExpression(outp, lhs.content[2])
outp:addLine(']', node.location:format())
outp:addLine('=', node.location:format())
outp:addLine('(', node.location:format())
outp:addLine('(_lake_operator_assignment_tmp)[', node.location:format())
Debug.outputExpression(outp, lhs.content[2])
outp:addLine(']', node.location:format())
outp:addLine(op, node.location:format())
outp:addLine('(', node.location:format())
Debug.outputExpression(outp, rhs)
outp:addLine('));', node.location:format())
outp:addLine('end;', node.location:format())
end
elseif node.kind == "Break" then
outp:addLine('break;', node.location:format())
elseif node.kind == "Foreach" then
outp:addLine("for", node.location:format())
local varlist,exprlist,block = node.content[1],node.content[2],node.content[3]
for i = 1,#varlist do
outp:addLine(varlist[i], node.location:format())
if i ~= #varlist then
outp:addLine(',', node.location:format())
end
end
outp:addLine("in", node.location:format())
for i = 1,#exprlist do
Debug.outputExpression(outp, exprlist[i])
if i ~= #exprlist then
outp:addLine(",", node.location:format())
end
end
outp:addLine('do', node.location:format())
Debug.outputStatement(outp, block);
outp:addLine('end;', node.location:format())
end
end
---@param outp Output
---@param node Node
function Debug.outputExpression(outp, node)
if node.kind == "StringLiteral" then
local lakestr = node.content
local raw = Lower.decodeString(lakestr)
local enc = Debug.encodeString(raw, outp.target == "lua51")
outp:addLine(enc, node.location:format())
elseif node.kind == "ParenthesizedExpression" then
outp:addLine("(", node.location:format())
Debug.outputExpression(outp, node.content)
outp:addLine(")", node.location:format())
elseif node.kind == "NumberLiteral" then
local lakenum = node.content
local raw = Lower.decodeNumber(lakenum)
-- print(raw, type(raw))
local enc = Debug.encodeNumber(raw)
outp:addLine("(", node.location:format())
outp:addLine(enc, node.location:format())
outp:addLine(")", node.location:format())
elseif node.kind == "TableLiteral" then
outp:addLine('{', node.location:format())
local list_part = node.content[1]
local table_part = node.content[2]
for i = 1,#list_part do
local item = list_part[i]
Debug.outputExpression(outp, item);
outp:addLine(',', node.location:format())
end
for i = 1,#table_part do
local item = table_part[i]
outp:addLine('[', node.location:format())
Debug.outputExpression(outp, item[1])
outp:addLine(']', node.location:format())
outp:addLine('=', node.location:format())
Debug.outputExpression(outp, item[2])
outp:addLine(',', node.location:format())
end
outp:addLine('}', node.location:format())
elseif node.kind == "LambdaFunctionLiteral" then
outp:addLine('function(', node.location:format())
for i = 1,#node.content[1] do
local part = node.content[1][i]
outp:addLine(part, node.location:format())
if i ~= #node.content[1] then
outp:addLine(",", node.location:format())
end
end
outp:addLine(")", node.location:format())
Debug.outputStatement(outp, node.content[2])
outp:addLine("end ", node.location:format())
elseif node.kind == "BinaryOperator" then
local op = node.content[1]
local lhs = node.content[2]
local rhs = node.content[3]
outp:addLine("((", node.location:format())
Debug.outputExpression(outp,lhs)
outp:addLine(")", node.location:format())
local luaop = Debug.convertBinaryOperator(op)
outp:addLine(luaop, node.location:format())
outp:addLine("(", node.location:format())
Debug.outputExpression(outp,rhs)
outp:addLine("))", node.location:format())
elseif node.kind == "UnaryOperator" then
local op = node.content[1]
local val = node.content[2]
outp:addLine("(", node.location:format())
local luaop = Debug.convertUnaryOperator(op)
outp:addLine(luaop, node.location:format())
outp:addLine("(", node.location:format())
Debug.outputExpression(outp,val)
outp:addLine("))", node.location:format())
elseif node.kind == "Field" then
local left = node.content[1]
local right = node.content[2]
outp:addLine("((", node.location:format())
Debug.outputExpression(outp, left)
outp:addLine(").", node.location:format())
outp:addLine(right, node.location:format())
outp:addLine(")", node.location:format())
elseif node.kind == "Index" then
local left = node.content[1]
local right = node.content[2]
outp:addLine("((", node.location:format())
Debug.outputExpression(outp, left)
outp:addLine(")", node.location:format())
outp:addLine("[", node.location:format())
Debug.outputExpression(outp, right)
outp:addLine("]", node.location:format())
outp:addLine(")", node.location:format())
elseif node.kind == "Variable" then
outp:addLine("(", node.location:format())
outp:addLine(node.content, node.location:format())
outp:addLine(")", node.location:format())
elseif node.kind == "BooleanLiteral" then
outp:addLine("(", node.location:format())
outp:addLine(node.content, node.location:format())
outp:addLine(")", node.location:format())
elseif node.kind == "NullLiteral" then
outp:addLine("(nil)", node.location:format())
elseif node.kind == "Call" then
outp:addLine("(", node.location:format())
Debug.outputExpression(outp, node.content[1])
outp:addLine(")(", node.location:format())
local args = node.content[2]
for i = 1,#args do
local arg = args[i]
Debug.outputExpression(outp, arg)
if i ~= #args then
outp:addLine(",", node.location:format())
end
end
outp:addLine(")", node.location:format())
elseif node.kind == "MethodCall" then
outp:addLine("(", node.location:format())
Debug.outputExpression(outp, node.content[1])
outp:addLine("):", node.location:format())
outp:addLine(node.content[2] .. "(", node.location:format())
local args = node.content[3]
for i = 1,#args do
local arg = args[i]
Debug.outputExpression(outp, arg)
if i ~= #args then
outp:addLine(",", node.location:format())
end
end
outp:addLine(")", node.location:format())
end
end
---@param outp Output
---@param node Node
function Debug.outputFunctionName(outp, node)
if node.kind == "Field" then
local left = node.content[1]
local right = node.content[2]
Debug.outputFunctionName(outp, left)
outp:addLine(".", node.location:format())
outp:addLine(right, node.location:format())
elseif node.kind == "Index" then
local left = node.content[1]
local right = node.content[2]
Debug.outputFunctionName(outp, left)
outp:addLine("[", node.location:format())
Debug.outputExpression(outp, right)
outp:addLine("]", node.location:format())
elseif node.kind == "Variable" then
outp:addLine(node.content, node.location:format())
elseif node.kind == "Method" then
local left = node.content[1]
local right = node.content[2]
Debug.outputFunctionName(outp, left)
outp:addLine(":", node.location:format())
outp:addLine(right, node.location:format())
end
end
---@param outp Output
---@param node Node
function Debug.outputVariable(outp, node)
if node.kind == "Field" then
local left = node.content[1]
local right = node.content[2]
Debug.outputExpression(outp, left)
outp:addLine(".", node.location:format())
outp:addLine(right, node.location:format())
elseif node.kind == "Index" then
local left = node.content[1]
local right = node.content[2]
Debug.outputExpression(outp, left)
outp:addLine("[", node.location:format())
Debug.outputExpression(outp, right)
outp:addLine("]", node.location:format())
elseif node.kind == "Variable" then
outp:addLine(node.content, node.location:format())
end
end
RegisterOutMethod("debug", Debug)

719
src/methods/small.lua Normal file
View File

@@ -0,0 +1,719 @@
Small = {}
function Small.isDigit(ch)
local n = string.byte(ch)
if not n then return false end
return n >= 48 and n <= 57
end
function Small.encodeString(raw, islua51)
local out = '"'
for i = 1,#raw do
local ch = raw:sub(i,i)
local code = string.byte(ch)
if ch == '\\' then
out = out .. "\\\\"
elseif ch == '"' then
out = out .. '\\"'
elseif ch == "\n" then
out = out .. "\\n"
elseif ch == '\r' then
out = out .. "\\r"
elseif ch == "\a" then
out = out .. "\\a"
elseif ch == "\b" then
out = out .. "\\b"
elseif ch == '\f' then
out = out .. "\\f"
elseif ch == "\t" then
out = out .. "\\t"
elseif code >= 32 and code <= 126 then
out = out .. ch
else
local nextchar = raw:sub(i+1,i+1)
if Small.isDigit(nextchar) then
out = out .. "\\" .. DecEncode(code, 3)
else
out = out .. "\\" .. DecEncode(code, 1)
end
end
end
out = out .. '"'
return out
end
function Small.encodeNumber(n)
local num = ''
local isneg = false
if n < 0 then
n = n * -1
isneg = true;
end
local whole = math.floor(n)
if whole == 0 then
num = num .. '0'
end
while whole > 0 do
local digit = whole % 10
num = string.char(string.byte'0' + digit) .. num
whole = math.floor(whole / 10)
end
local dec = n % 1
if dec > 0 then
num = num .. "."
while dec > 0 do
-- aw nah
dec = dec * 10
local digit = math.floor(dec)
num = num .. string.char(string.byte'0' + digit)
dec = dec % 1
end
end
if isneg then
num = '-' .. num
end
return num
end
function Small.convertBinaryOperator(lakeop, spacer)
if lakeop == "+" then
return "+"
elseif lakeop == "-" then
return "-"
elseif lakeop == "*" then
return "*"
elseif lakeop == "/" then
return "/"
elseif lakeop == "%" then
return "%"
elseif lakeop == "^" then
return "^"
elseif lakeop == ".." then
return ".."
elseif lakeop == "<" then
return "<"
elseif lakeop == ">" then
return ">"
elseif lakeop == "<=" then
return "<="
elseif lakeop == ">=" then
return ">="
elseif lakeop == "!=" then
return "~="
elseif lakeop == "==" then
return "=="
elseif lakeop == "&&" then
return spacer and ' and ' or "and"
elseif lakeop == "||" then
return spacer and ' or ' or "or"
end
end
function Small.convertUnaryOperator(op)
if op == "#" then
return "#"
elseif op == "-" then
return "-"
elseif op == "!" then
return "not"
end
end
function Small.output(outp, ast)
Small.outputStatement(outp, ast)
end
---@param outp Output
---@param node Node
function Small.outputStatement(outp, node, noblock)
if node.kind == "Program" then
outp:appendLine("do ", '')
for i = 1,#node.content do
local stmt = node.content[i]
Small.outputStatement(outp, stmt)
end
outp:appendLine("end;", '')
elseif node.kind == "VariableDeclaration" then
outp:appendLine("local ", '')
local varnames = node.content[1]
for i = 1,#varnames do
local vname = varnames[i]
outp:appendLine(vname[1], '')
if i ~= #varnames then
outp:appendLine(",", '')
end
end
local values = node.content[2]
if #values > 0 then
outp:appendLine("=", '')
for i = 1,#values do
local val = values[i]
Small.outputExpression(outp, val, false)
if i ~= #values then
outp:appendLine(",", '')
end
end
end
outp:appendLine(";", '')
elseif node.kind == "Block" then
if not noblock then
outp:appendLine("do ", '')
end
for i = 1,#node.content do
local stmt = node.content[i]
Small.outputStatement(outp, stmt, #node.content == 1)
end
if not noblock then
outp:appendLine("end;", '')
end
elseif node.kind == "IfStatement" then
outp:appendLine("if ", '')
Small.outputExpression(outp, node.content[1][1][1])
outp:appendLine("then ", '')
Small.outputStatement(outp, node.content[1][1][2], true)
for i = 2, #node.content[1] do
local item = node.content[1][i]
-- print(item, item[1], item[2])
outp:appendLine("elseif ", '')
Small.outputExpression(outp,item[1]);
outp:appendLine("then ", '')
Small.outputStatement(outp,item[2], true);
end
if node.content[2] then
outp:appendLine("else ", '')
Small.outputStatement(outp, node.content[2], true)
end
outp:appendLine("end;", '')
elseif node.kind == "FunctionDeclaration" then
outp:appendLine("function ", '')
Small.outputFunctionName(outp, node.content[1]);
outp:appendLine("(", '')
for i = 1,#node.content[2] do
local part = node.content[2][i]
outp:appendLine(part, '')
if i ~= #node.content[2] then
outp:appendLine(",", '')
end
end
outp:appendLine(")", '')
Small.outputStatement(outp, node.content[3], true)
outp:appendLine("end;", '')
elseif node.kind == "LocalFunctionDeclaration" then
outp:appendLine("local function ", '')
outp:appendLine(node.content[1].content, '');
outp:appendLine("(", '')
for i = 1,#node.content[2] do
local part = node.content[2][i]
outp:appendLine(part, '')
if i ~= #node.content[2] then
outp:appendLine(",", '')
end
end
outp:appendLine(")", '')
Small.outputStatement(outp, node.content[3], true)
outp:appendLine("end;", '')
elseif node.kind == "Return" then
if #node.content > 0 then
outp:appendLine("return ", '')
else
outp:appendLine('return', '')
end
for i = 1,#node.content do
local expr = node.content[i]
Small.outputExpression(outp, expr, false)
if i ~= #node.content then
outp:appendLine(",", '')
end
end
outp:appendLine(";", '')
elseif node.kind == "Assignment" then
local vars = node.content[1]
local vals = node.content[2]
for i = 1,#vars do
local var = vars[i]
Small.outputVariable(outp, var)
if i ~= #vars then
outp:appendLine(",", '')
end
end
outp:appendLine("=", '')
for i = 1,#vals do
local val = vals[i]
Small.outputExpression(outp, val, false)
if i ~= #vals then
outp:appendLine(",", '')
end
end
outp:appendLine(";", '')
elseif node.kind == "Call" then
outp:appendLine("(", '')
Small.outputExpression(outp, node.content[1], false)
outp:appendLine(")", '')
outp:appendLine("(", '')
local args = node.content[2]
for i = 1,#args do
local arg = args[i]
Small.outputExpression(outp, arg, false)
if i ~= #args then
outp:appendLine(",", '')
end
end
outp:appendLine(")", '')
outp:appendLine(";", '')
elseif node.kind == "MethodCall" then
outp:appendLine("(", '')
Small.outputExpression(outp, node.content[1], false)
outp:appendLine("):", '')
outp:appendLine(node.content[2], '')
outp:appendLine("(", '')
local args = node.content[3]
for i = 1,#args do
local arg = args[i]
Small.outputExpression(outp, arg, false)
if i ~= #args then
outp:appendLine(",", '')
end
end
outp:appendLine(")", '')
outp:appendLine(";", '')
elseif node.kind == "While" then
outp:appendLine("while ", '')
Small.outputExpression(outp, node.content[1])
outp:appendLine("do ", '')
Small.outputStatement(outp, node.content[2], true)
outp:appendLine("end;", '')
elseif node.kind == "For" then
outp:appendLine("do ", '')
Small.outputStatement(outp,node.content[1]);
outp:appendLine("while ", '')
Small.outputExpression(outp,node.content[2]);
outp:appendLine("do ", '')
outp:appendLine("do ", '')
Small.outputStatement(outp,node.content[4], true);
outp:appendLine("end;", '')
Small.outputStatement(outp,node.content[3], true);
outp:appendLine("end;", '')
outp:appendLine("end;", '')
elseif node.kind == "OperatorAssignment" then
local lake_op = node.content[2]
local op = Small.convertBinaryOperator(lake_op, true)
local lhs = node.content[1]
local rhs = node.content[3]
if lhs.kind == "Variable" then
local varname = lhs.content
outp:appendLine(varname, '')
outp:appendLine('=', '')
outp:appendLine(varname, '')
outp:appendLine(op, '')
outp:appendLine('(', '')
Small.outputExpression(outp, rhs, false)
outp:appendLine(');', '')
elseif lhs.kind == "Field" then
outp:appendLine('do ', '')
outp:appendLine('local _lake_oat=', '')
Small.outputExpression(outp, lhs.content[1], false)
outp:appendLine(';', '');
outp:appendLine('_lake_oat.', '')
outp:appendLine(lhs.content[2], '')
outp:appendLine('=', '')
outp:appendLine('_lake_oat.', '')
outp:appendLine(lhs.content[2], '')
outp:appendLine(op, '')
outp:appendLine('(', '')
Small.outputExpression(outp, rhs, false)
outp:appendLine(');', '')
outp:appendLine('end;', '')
elseif lhs.kind == "Index" then
outp:appendLine('do ', '')
outp:appendLine('local _lake_oat=', '')
Small.outputExpression(outp, lhs.content[1], false)
outp:appendLine(';', '');
outp:appendLine('_lake_oat[', '')
Small.outputExpression(outp, lhs.content[2], false)
outp:appendLine(']', '')
outp:appendLine('=', '')
outp:appendLine('_lake_oat[', '')
Small.outputExpression(outp, lhs.content[2], false)
outp:appendLine(']', '')
outp:appendLine(op, '')
outp:appendLine('(', '')
Small.outputExpression(outp, rhs, false)
outp:appendLine(');', '')
outp:appendLine('end;', '')
end
elseif node.kind == "Break" then
outp:appendLine('break;', '')
elseif node.kind == "Foreach" then
outp:appendLine("for ", '')
local varlist,exprlist,block = node.content[1],node.content[2],node.content[3]
for i = 1,#varlist do
outp:appendLine(varlist[i], '')
if i ~= #varlist then
outp:appendLine(',', '')
end
end
outp:appendLine(" in ", '')
for i = 1,#exprlist do
Small.outputExpression(outp, exprlist[i], i == #exprlist)
if i ~= #exprlist then
outp:appendLine(",", '')
end
end
outp:appendLine('do ', '')
Small.outputStatement(outp, block, true);
outp:appendLine('end;', '')
end
end
---@param outp Output
---@param node Node
function Small.outputExpression(outp, node, needsp)
if needsp == nil then needsp = true end
if node.kind == "StringLiteral" then
local lakestr = node.content
local raw = Lower.decodeString(lakestr)
local enc = Small.encodeString(raw, outp.target == "lua51")
outp:appendLine(enc, '')
elseif node.kind == "ParenthesizedExpression" then
outp:appendLine("(", '')
Small.outputExpression(outp, node.content, false)
outp:appendLine(")", '')
elseif node.kind == "NumberLiteral" then
local lakenum = node.content
local raw = Lower.decodeNumber(lakenum)
-- print(raw, type(raw))
local enc = Small.encodeNumber(raw)
outp:appendLine(enc, '')
if needsp then
outp:appendLine(' ', '')
end
elseif node.kind == "TableLiteral" then
outp:appendLine('{', '')
local list_part = node.content[1]
local table_part = node.content[2]
for i = 1,#list_part do
local item = list_part[i]
Small.outputExpression(outp, item, false);
if (#table_part > 0) or (i < #list_part) then
outp:appendLine(',', '')
end
end
for i = 1,#table_part do
local item = table_part[i]
outp:appendLine('[', '')
Small.outputExpression(outp, item[1], false)
outp:appendLine(']', '')
outp:appendLine('=', '')
Small.outputExpression(outp, item[2], false)
if (i < #table_part) then
outp:appendLine(',', '')
end
end
outp:appendLine('}', '')
elseif node.kind == "LambdaFunctionLiteral" then
outp:appendLine('function(', '')
for i = 1,#node.content[1] do
local part = node.content[1][i]
outp:appendLine(part, '')
if i ~= #node.content[1] then
outp:appendLine(",", '')
end
end
outp:appendLine(")", '')
Small.outputStatement(outp, node.content[2], true)
outp:appendLine("end", '')
if needsp then
outp:appendLine(' ', '')
end
elseif node.kind == "BinaryOperator" then
local op = node.content[1]
local lhs = node.content[2]
local rhs = node.content[3]
outp:appendLine("((", '')
Small.outputExpression(outp,lhs, false)
outp:appendLine(")", '')
local luaop = Small.convertBinaryOperator(op)
outp:appendLine(luaop, '')
outp:appendLine("(", '')
Small.outputExpression(outp,rhs,false)
outp:appendLine("))", '')
elseif node.kind == "UnaryOperator" then
local op = node.content[1]
local val = node.content[2]
outp:appendLine("(", '')
local luaop = Small.convertUnaryOperator(op)
outp:appendLine(luaop, '')
outp:appendLine("(", '')
Small.outputExpression(outp,val,false)
outp:appendLine("))", '')
elseif node.kind == "Field" then
local left = node.content[1]
local right = node.content[2]
outp:appendLine("(", '')
Small.outputExpression(outp, left, false)
outp:appendLine(").", '')
outp:appendLine(right, '')
if needsp then
outp:appendLine(' ', '')
end
elseif node.kind == "Index" then
local left = node.content[1]
local right = node.content[2]
outp:appendLine("(", '')
Small.outputExpression(outp, left, false)
outp:appendLine(")", '')
outp:appendLine("[", '')
Small.outputExpression(outp, right, false)
outp:appendLine("]", '')
elseif node.kind == "Variable" then
outp:appendLine(node.content, '')
if needsp then
outp:appendLine(' ', '')
end
elseif node.kind == "BooleanLiteral" then
outp:appendLine(node.content, '')
if needsp then
outp:appendLine(' ', '')
end
elseif node.kind == "NullLiteral" then
outp:appendLine("nil", '')
if needsp then
outp:appendLine(' ', '')
end
elseif node.kind == "Call" then
outp:appendLine("(", '')
Small.outputExpression(outp, node.content[1], false)
outp:appendLine(")", '')
outp:appendLine("(", '')
local args = node.content[2]
for i = 1,#args do
local arg = args[i]
Small.outputExpression(outp, arg, false)
if i ~= #args then
outp:appendLine(",", '')
end
end
outp:appendLine(")", '')
elseif node.kind == "MethodCall" then
outp:appendLine("(", '')
Small.outputExpression(outp, node.content[1], false)
outp:appendLine("):", '')
outp:appendLine(node.content[2], '')
outp:appendLine("(", '')
local args = node.content[3]
for i = 1,#args do
local arg = args[i]
Small.outputExpression(outp, arg, false)
if i ~= #args then
outp:appendLine(",", '')
end
end
outp:appendLine(")", '')
end
end
---@param outp Output
---@param node Node
function Small.outputFunctionName(outp, node)
if node.kind == "Field" then
local left = node.content[1]
local right = node.content[2]
Small.outputFunctionName(outp, left)
outp:appendLine(".", '')
outp:appendLine(right, '')
elseif node.kind == "Index" then
local left = node.content[1]
local right = node.content[2]
Small.outputFunctionName(outp, left)
outp:appendLine("[", '')
Small.outputExpression(outp, right, false)
outp:appendLine("]", '')
elseif node.kind == "Variable" then
outp:appendLine(node.content, '')
elseif node.kind == "Method" then
local left = node.content[1]
local right = node.content[2]
Small.outputFunctionName(outp, left)
outp:appendLine(":", '')
outp:appendLine(right, '')
end
end
---@param outp Output
---@param node Node
function Small.outputVariable(outp, node)
if node.kind == "Field" then
local left = node.content[1]
local right = node.content[2]
Small.outputExpression(outp, left, false)
outp:appendLine(".", '')
outp:appendLine(right, '')
elseif node.kind == "Index" then
local left = node.content[1]
local right = node.content[2]
Small.outputExpression(outp, left, false)
outp:appendLine("[", '')
Small.outputExpression(outp, right, false)
outp:appendLine("]", '')
elseif node.kind == "Variable" then
outp:appendLine(node.content, '')
end
end
RegisterOutMethod("small", Small)

46
src/output.lua Normal file
View File

@@ -0,0 +1,46 @@
OutputMethods = {}
function RegisterOutMethod(name, data)
OutputMethods[name] = data
end
---@class Output
---@field target string
---@field method string
---@field lines string[]
---@field debug string[]
Output = {}
function Output:new(target, method)
return setmetatable({
target = target,
method = method,
lines = {},
debug = {}
}, {__index = self})
end
function Output:output(ast)
local method = OutputMethods[self.method]
assert(method)
method.output(self, ast)
end
function Output:addLine(line, debuginfo)
self.lines[#self.lines+1] = line
self.debug[#self.debug+1] = debuginfo
end
function Output:appendLine(data, debuginfo)
if not self.lines[#self.lines] then
self.lines[#self.lines+1] = ""
end
self.lines[#self.lines] = self.lines[#self.lines] .. data
if not debuginfo then print(data) end
if not self.debug[#self.debug] then
self.debug[#self.debug+1] = ""
end
self.debug[#self.debug] = self.debug[#self.debug] .. debuginfo
end

1373
src/parser.lua Normal file

File diff suppressed because it is too large Load Diff

85
src/types.lua Normal file
View File

@@ -0,0 +1,85 @@
---@class Location
---@field file string
---@field line integer
---@field column integer
Location = {}
function Location:new(file)
return setmetatable({file = file, column = 1, line = 1},{__index = self})
end
---@return Location
function Location:clone()
return setmetatable({file = self.file, column = self.column, line = self.line}, getmetatable(self))
end
function Location:skip(chr)
for i=1,#chr do
self.column = self.column + 1
local c = chr:sub(i, i)
if c == '\n' then
self.column = 1
self.line = self.line + 1
end
end
end
function Location:format()
return self.file .. ":" .. self.line .. ":" .. self.column
end
---@class Token
---@field kind string
---@field content any
---@field location Location
Token = {}
function Token:new(kind, content, location)
return setmetatable({
kind = kind,
content = content,
location = location
}, {__index = self})
end
---@class Error
---@field location Location
---@field message string
---@field origin string
Error = {}
function Error:new(message,origin, location)
return setmetatable({
message = message,
origin = origin,
location = location
}, {__index = self})
end
function Error:format()
local capitalizedOrigin = self.origin:sub(1,1):upper() .. self.origin:sub(2):lower()
local locationString = self.location:format()
-- local msg = string.format("%s:%d %s", err.location.file, err.location.line, err.message)
return capitalizedOrigin .. " error: " .. locationString .. ": " .. self.message;
-- print("\x1b[31m" .. msg .. "\x1b[0m")
end
function Error:print()
print("\x1b[31m" .. self:format() .. "\x1b[0m")
end
---@class Node
---@field kind string
---@field content any
---@field location Location
Node = {}
function Node:new(kind,content,location)
return setmetatable({
kind = kind,
content = content,
location = location,
}, {__index = self})
end

64
src/util.lua Normal file
View File

@@ -0,0 +1,64 @@
local deckey = "0123456789"
function DecEncode(num, minchars)
local outp = ""
while num > 0 do
local dig = num % (#deckey)
local ch = deckey:sub(dig + 1, dig + 1)
outp = ch .. outp
num = math.floor(num / #deckey)
end
if minchars then
while #outp < minchars do
outp = deckey:sub(1,1) .. outp
end
end
return outp
end
local hexkey = "0123456789abcdef"
function HexEncode(num, minchars)
local outp = ""
while num > 0 do
local dig = num % (#hexkey)
local ch = hexkey:sub(dig + 1, dig + 1)
outp = ch .. outp
num = math.floor(num / #hexkey)
end
if minchars then
while #outp < minchars do
outp = hexkey:sub(1,1) .. outp
end
end
return outp
end
function HexDecode(hexstr)
local out = 0
for i = 1,#hexstr do
local char = hexstr:sub(i,i):lower()
local pos = hexkey:find(char, nil, true) - 1
out = out * 16;
out = out + pos;
end
return out;
end