bitwise expressions compiler
This commit is contained in:
parent
d9d4d2b305
commit
cba48e3a0c
113
res/modules/bitwise/compiler.lua
Normal file
113
res/modules/bitwise/compiler.lua
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
local util = require "core:bitwise/util"
|
||||||
|
|
||||||
|
local tokenizer = require "core:bitwise/tokenizer"
|
||||||
|
local parser = require "core:bitwise/parser"
|
||||||
|
|
||||||
|
local types = tokenizer.types
|
||||||
|
|
||||||
|
local operatorToFunctionName =
|
||||||
|
{
|
||||||
|
[types.lshift] = "bit.lshift",
|
||||||
|
[types.rshift] = "bit.rshift",
|
||||||
|
[types.bnot] = "bit.bnot",
|
||||||
|
[types.band] = "bit.band",
|
||||||
|
[types.bxor] = "bit.bxor",
|
||||||
|
[types.bor] = "bit.bor"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function compile(str, args, asFunction)
|
||||||
|
if asFunction == nil then
|
||||||
|
asFunction = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local tokens = parser(str)
|
||||||
|
|
||||||
|
local stack = { }
|
||||||
|
|
||||||
|
local ids = { }
|
||||||
|
|
||||||
|
for _, token in ipairs(tokens) do
|
||||||
|
if table.has(tokenizer.numTypes, token.type) or token.type == types.id then
|
||||||
|
if token.type == tokenizer.types.binNum then
|
||||||
|
util.stack.push(stack, tonumber(token.value, 2))
|
||||||
|
elseif token.type == tokenizer.types.hexNum then
|
||||||
|
util.stack.push(stack, "0x"..token.value)
|
||||||
|
else
|
||||||
|
util.stack.push(stack, token.value)
|
||||||
|
|
||||||
|
if token.type == types.id then
|
||||||
|
local add = true
|
||||||
|
|
||||||
|
for i = 1, #ids do
|
||||||
|
if ids[i].value == token.value then
|
||||||
|
add = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if add then table.insert(ids, token) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local fun = operatorToFunctionName[token.type]
|
||||||
|
|
||||||
|
local a = util.stack.pop(stack)
|
||||||
|
local b = token.type ~= types.bnot and util.stack.pop(stack)
|
||||||
|
|
||||||
|
if not b then
|
||||||
|
util.stack.push(stack, fun..'('..a..')')
|
||||||
|
else
|
||||||
|
util.stack.push(stack, fun..'('..a..','..b..')')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local strArgs = ""
|
||||||
|
|
||||||
|
if args then
|
||||||
|
for _, id in ipairs(ids) do
|
||||||
|
if not table.has(args, id.value) then
|
||||||
|
util.throw(id.column, "undefined identifier")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
args = { }
|
||||||
|
|
||||||
|
table.sort(ids, function(a, b) return a.value:upper() < b.value:upper() end)
|
||||||
|
|
||||||
|
for _, id in ipairs(ids) do
|
||||||
|
table.insert(args, id.value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #args do
|
||||||
|
local str = args[i]
|
||||||
|
|
||||||
|
for j = 1, #str do
|
||||||
|
local char = str:sub(j,j)
|
||||||
|
|
||||||
|
if
|
||||||
|
not string.find(tokenizer.idChars, char) or
|
||||||
|
(j == 1 and string.find(tokenizer.startingDigits, char))
|
||||||
|
then error("invalid argument name ("..i..")") end
|
||||||
|
end
|
||||||
|
|
||||||
|
strArgs = strArgs..str
|
||||||
|
|
||||||
|
if i ~= #args then
|
||||||
|
strArgs = strArgs..','
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local code = stack[1]
|
||||||
|
|
||||||
|
code = "function("..strArgs..") return "..code.." end"
|
||||||
|
|
||||||
|
if asFunction then
|
||||||
|
return load("return "..code)()
|
||||||
|
else
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return compile
|
||||||
61
res/modules/bitwise/executor.lua
Normal file
61
res/modules/bitwise/executor.lua
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
local compiler = require "core:bitwise/compiler"
|
||||||
|
|
||||||
|
local cache = { }
|
||||||
|
|
||||||
|
local cacheLength = 512
|
||||||
|
|
||||||
|
local function hashOfArgs(args)
|
||||||
|
local str = ""
|
||||||
|
|
||||||
|
for i = 1, #args do
|
||||||
|
str = str..args[i]
|
||||||
|
|
||||||
|
if i ~= #args then
|
||||||
|
str = str..';'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
local function execute(str, args, ...)
|
||||||
|
local hasArgs = args and type(args) == 'table'
|
||||||
|
|
||||||
|
local expArgs
|
||||||
|
|
||||||
|
if hasArgs then
|
||||||
|
expArgs = args
|
||||||
|
else
|
||||||
|
expArgs = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local argsHash = hasArgs and hashOfArgs(args) or ""
|
||||||
|
|
||||||
|
local func
|
||||||
|
|
||||||
|
if (#str + #argsHash) <= cacheLength then
|
||||||
|
if cache[argsHash] then
|
||||||
|
local fun = cache[argsHash][str]
|
||||||
|
|
||||||
|
if fun then return fun end
|
||||||
|
end
|
||||||
|
|
||||||
|
local comp = compiler(str, expArgs)
|
||||||
|
|
||||||
|
cache[argsHash] = { }
|
||||||
|
|
||||||
|
cache[argsHash][str] = comp
|
||||||
|
|
||||||
|
func = comp
|
||||||
|
else
|
||||||
|
func = compiler(str, expArgs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasArgs or not args then
|
||||||
|
return func(...)
|
||||||
|
else
|
||||||
|
return func(args, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return execute
|
||||||
61
res/modules/bitwise/parser.lua
Normal file
61
res/modules/bitwise/parser.lua
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
local util = require "core:bitwise/util"
|
||||||
|
|
||||||
|
local tokenizer = require "core:bitwise/tokenizer"
|
||||||
|
|
||||||
|
local types = tokenizer.types
|
||||||
|
|
||||||
|
local priorities =
|
||||||
|
{
|
||||||
|
[types.bnot] = 4,
|
||||||
|
[types.lshift] = 3,
|
||||||
|
[types.rshift] = 3,
|
||||||
|
[types.band] = 2,
|
||||||
|
[types.bxor] = 1,
|
||||||
|
[types.bor] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
local function parse(str)
|
||||||
|
local stack = { }
|
||||||
|
local output = { }
|
||||||
|
|
||||||
|
for _, token in ipairs(tokenizer.get_tokens(str)) do
|
||||||
|
if table.has(tokenizer.numTypes, token.type) or token.type == types.id then
|
||||||
|
table.insert(output, token)
|
||||||
|
elseif table.has(tokenizer.operatorTypes, token.type) then
|
||||||
|
local firstOp = util.stack.top(stack)
|
||||||
|
|
||||||
|
if firstOp and table.has(tokenizer.operatorTypes, firstOp.type) and priorities[firstOp.type] >= priorities[token.type] then
|
||||||
|
table.insert(output, util.stack.pop(stack))
|
||||||
|
end
|
||||||
|
|
||||||
|
util.stack.push(stack, token)
|
||||||
|
elseif token.type == types.openingBracket then
|
||||||
|
util.stack.push(stack, token)
|
||||||
|
elseif token.type == types.closingBracket then
|
||||||
|
while true do
|
||||||
|
local topToken = util.stack.top(stack)
|
||||||
|
|
||||||
|
if not topToken then util.throw(token.column, "unexpected closing bracket")
|
||||||
|
elseif topToken.type == types.openingBracket then break end
|
||||||
|
|
||||||
|
table.insert(output, util.stack.pop(stack))
|
||||||
|
end
|
||||||
|
|
||||||
|
util.stack.pop(stack)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
while #stack > 0 do
|
||||||
|
local token = util.stack.pop(stack)
|
||||||
|
|
||||||
|
if token.type == types.openingBracket then
|
||||||
|
util.throw(token.column, "unclosed bracket")
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(output, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
return parse
|
||||||
228
res/modules/bitwise/tokenizer.lua
Normal file
228
res/modules/bitwise/tokenizer.lua
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
local util = require "core:bitwise/util"
|
||||||
|
|
||||||
|
local operators = "><|&~^()"
|
||||||
|
local digits = "0123456789ABCDEF"
|
||||||
|
local startingDigits = "0123456789"
|
||||||
|
local idChars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789_"
|
||||||
|
local spaces = " \t"
|
||||||
|
|
||||||
|
local function esc(x)
|
||||||
|
return (x:gsub('%%', '%%%%')
|
||||||
|
:gsub('^%^', '%%^')
|
||||||
|
:gsub('%$$', '%%$')
|
||||||
|
:gsub('%(', '%%(')
|
||||||
|
:gsub('%)', '%%)')
|
||||||
|
:gsub('%.', '%%.')
|
||||||
|
:gsub('%[', '%%[')
|
||||||
|
:gsub('%]', '%%]')
|
||||||
|
:gsub('%*', '%%*')
|
||||||
|
:gsub('%+', '%%+')
|
||||||
|
:gsub('%-', '%%-')
|
||||||
|
:gsub('%?', '%%?'))
|
||||||
|
end
|
||||||
|
|
||||||
|
local decNum = "DECIMAL"
|
||||||
|
local hexNum = "HEXADECIMAL"
|
||||||
|
local binNum = "BINARY"
|
||||||
|
|
||||||
|
local rshift = "RIGHT SHIFT"
|
||||||
|
local lshift = "LEFT SHIFT"
|
||||||
|
local bor = "OR"
|
||||||
|
local band = "AND"
|
||||||
|
local bxor = "XOR"
|
||||||
|
local bnot = "NOT"
|
||||||
|
local id = "ID"
|
||||||
|
local openingBracket = "OPENING BRACKET"
|
||||||
|
local closingBracket = "CLOSING BRACKET"
|
||||||
|
|
||||||
|
local opToType =
|
||||||
|
{
|
||||||
|
['^'] = bxor,
|
||||||
|
['|'] = bor,
|
||||||
|
['&'] = band,
|
||||||
|
['('] = openingBracket,
|
||||||
|
[')'] = closingBracket,
|
||||||
|
['~'] = bnot
|
||||||
|
}
|
||||||
|
|
||||||
|
local typeToOp =
|
||||||
|
{
|
||||||
|
[bxor] = '^',
|
||||||
|
[bor] = '|',
|
||||||
|
[band] = '&',
|
||||||
|
[openingBracket] = '(',
|
||||||
|
[closingBracket] = ')',
|
||||||
|
[bnot] = '~'
|
||||||
|
}
|
||||||
|
|
||||||
|
local function contains(str, char)
|
||||||
|
return string.find(str, esc(char)) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function checkDigitSystem(column, digit, base)
|
||||||
|
local i = digits:find(digit)
|
||||||
|
|
||||||
|
if not i or i > base then
|
||||||
|
util.throw(column, "the digit '"..digit.."' does not belong to the "..base.."-based number system")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isEndOfNumOrId(i, set, next, len)
|
||||||
|
if i == len or not contains(set, next) then
|
||||||
|
if i ~= len and not contains(operators, next) and not contains(spaces, next) then
|
||||||
|
util.throw(i + 1, "operator or space expected")
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tokenize(str)
|
||||||
|
local tokens = { }
|
||||||
|
|
||||||
|
local buffer = ""
|
||||||
|
local numType, readingNum, readingId
|
||||||
|
local numSys
|
||||||
|
|
||||||
|
local i = 1
|
||||||
|
|
||||||
|
while i <= #str do
|
||||||
|
local char = str:sub(i, i)
|
||||||
|
local upChar = string.upper(char)
|
||||||
|
local next = str:sub(i + 1, i + 1)
|
||||||
|
|
||||||
|
if contains(operators, char) then
|
||||||
|
local type
|
||||||
|
|
||||||
|
if char == '>' or char == '<' then
|
||||||
|
if next == char then
|
||||||
|
type = char == '>' and rshift or lshift
|
||||||
|
i = i + 1
|
||||||
|
else
|
||||||
|
util.throw(i, "invalid operator")
|
||||||
|
end
|
||||||
|
elseif char == '~' then
|
||||||
|
if contains(idChars, next) then
|
||||||
|
type = bnot
|
||||||
|
else
|
||||||
|
util.throw(i, "number expected")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
type = opToType[char]
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(tokens, { column = i, type = type })
|
||||||
|
elseif ((not readingNum and contains(startingDigits, char)) or (readingNum and contains(digits, upChar))) and not readingId then
|
||||||
|
if not readingNum then
|
||||||
|
readingNum = true
|
||||||
|
|
||||||
|
if char == '0' and (next == 'x' or next == 'b') then
|
||||||
|
numType = next == 'x' and hexNum or binNum
|
||||||
|
numSys = next == 'x' and 16 or 2
|
||||||
|
|
||||||
|
i = i + 2
|
||||||
|
|
||||||
|
if not str:sub(i, i) then
|
||||||
|
util.throw(i, "unexpected end")
|
||||||
|
end
|
||||||
|
|
||||||
|
char = str:sub(i, i)
|
||||||
|
upChar = string.upper(char)
|
||||||
|
next = str:sub(i + 1, i + 1)
|
||||||
|
else
|
||||||
|
numType = decNum
|
||||||
|
numSys = 10
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if readingNum then
|
||||||
|
if numType == hexNum then
|
||||||
|
char, next = upChar, string.upper(next)
|
||||||
|
end
|
||||||
|
|
||||||
|
checkDigitSystem(i, char, numSys)
|
||||||
|
|
||||||
|
buffer = buffer..char
|
||||||
|
|
||||||
|
if isEndOfNumOrId(i, digits, next, #str) then
|
||||||
|
readingNum = false
|
||||||
|
table.insert(tokens, { column = i, type = numType, value = buffer })
|
||||||
|
buffer = ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif contains(idChars, char) then
|
||||||
|
if not readingId then
|
||||||
|
readingId = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if readingId then
|
||||||
|
buffer = buffer..char
|
||||||
|
|
||||||
|
if isEndOfNumOrId(i, idChars, next, #str) then
|
||||||
|
readingId = false
|
||||||
|
table.insert(tokens, { column = i, type = id, value = buffer })
|
||||||
|
buffer = ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local space
|
||||||
|
|
||||||
|
for j = 1, #spaces do
|
||||||
|
if spaces:sub(j, j) == char then
|
||||||
|
space = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not space then
|
||||||
|
util.throw(i, "undefined token: \""..char.."\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return tokens
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printTokens(tokens)
|
||||||
|
for _, token in ipairs(tokens) do
|
||||||
|
local str = "{ "
|
||||||
|
|
||||||
|
for k, v in pairs(token) do
|
||||||
|
str = str..k..' = '..v..', '
|
||||||
|
end
|
||||||
|
|
||||||
|
print(str:sub(1, #str - 2).." }")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
{
|
||||||
|
operators = operators,
|
||||||
|
digits = digits,
|
||||||
|
startingDigits = startingDigits,
|
||||||
|
idChars = idChars,
|
||||||
|
operatorTypes = { lshift, rshift, bnot, band, bxor, bor },
|
||||||
|
numTypes = { decNum, hexNum, binNum },
|
||||||
|
types = {
|
||||||
|
decNum = decNum,
|
||||||
|
hexNum = hexNum,
|
||||||
|
binNum = binNum,
|
||||||
|
rshift = rshift,
|
||||||
|
lshift = lshift,
|
||||||
|
bor = bor,
|
||||||
|
band = band,
|
||||||
|
bxor = bxor,
|
||||||
|
bnot = bnot,
|
||||||
|
id = id,
|
||||||
|
openingBracket = openingBracket,
|
||||||
|
closingBracket = closingBracket
|
||||||
|
},
|
||||||
|
opToType = opToType,
|
||||||
|
typeToOp = typeToOp,
|
||||||
|
get_tokens = tokenize,
|
||||||
|
print_tokens = printTokens
|
||||||
|
}
|
||||||
21
res/modules/bitwise/util.lua
Normal file
21
res/modules/bitwise/util.lua
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
local util = { }
|
||||||
|
|
||||||
|
util.stack = { }
|
||||||
|
|
||||||
|
function util.throw(column, msg)
|
||||||
|
error("column "..column..": "..msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.stack.push(stack, value)
|
||||||
|
table.insert(stack, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.stack.pop(stack)
|
||||||
|
return table.remove(stack, #stack)
|
||||||
|
end
|
||||||
|
|
||||||
|
function util.stack.top(stack)
|
||||||
|
return stack[#stack]
|
||||||
|
end
|
||||||
|
|
||||||
|
return util
|
||||||
Loading…
x
Reference in New Issue
Block a user