Merge pull request #506 from Onran0/main

Bitwise expressions compiler and executor
This commit is contained in:
MihailRis 2025-04-06 17:44:54 +03:00 committed by GitHub
commit 7a7ac54b5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 487 additions and 0 deletions

View 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..'('..b..','..a..')')
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

View 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)
if not cache[argsHash] then cache[argsHash] = { } end
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

View 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

View 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
}

View 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

View File

@ -625,3 +625,6 @@ function file.join(a, b)
end
return a .. "/" .. b
end
bit.compile = require "core:bitwise/compiler"
bit.execute = require "core:bitwise/executor"