18:58
This commit is contained in:
parent
60d9aabf8d
commit
c15cdd13cf
Binary file not shown.
Binary file not shown.
@ -46,7 +46,7 @@ class LuauVMObfuscator:
|
|||||||
def minify(self, code):
|
def minify(self, code):
|
||||||
code = re.sub(r'--.*', '', code)
|
code = re.sub(r'--.*', '', code)
|
||||||
code = re.sub(r'\s+', ' ', code)
|
code = re.sub(r'\s+', ' ', code)
|
||||||
for op in ['==', '~=', '<=', '>=', '=', r'\+', '-', r'\*', '/', '>', '<', r'\(', r'\)', r'\{', r'\}', r'\[', r'\]', ',', ';', ':']:
|
for op in ['==', '~=', '<=', '>=', '=', r'\+', r'\*', '/', '>', '<', ',', ';', ':']:
|
||||||
code = re.sub(r'\s*' + op + r'\s*', op.replace('\\', ''), code)
|
code = re.sub(r'\s*' + op + r'\s*', op.replace('\\', ''), code)
|
||||||
return code.strip()
|
return code.strip()
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class LuauVMObfuscator:
|
|||||||
k = (i * 149 + salt) % 256; enc = self.encrypt_string(c['value'], k)
|
k = (i * 149 + salt) % 256; enc = self.encrypt_string(c['value'], k)
|
||||||
consts.append({"t": 1, "v": base64.b64encode(enc.encode('latin-1')).decode()})
|
consts.append({"t": 1, "v": base64.b64encode(enc.encode('latin-1')).decode()})
|
||||||
elif c['type'] == 'number': consts.append({"t": 2, "v": c['value']})
|
elif c['type'] == 'number': consts.append({"t": 2, "v": c['value']})
|
||||||
else: consts.append({"t": 3, "v": c['value']})
|
else: consts.append({"t": 3, "v": str(c['value'])})
|
||||||
|
|
||||||
v = {
|
v = {
|
||||||
"BIT": "bit32", "ENV": self.get_var("env"), "DEC": self.get_var("dec"),
|
"BIT": "bit32", "ENV": self.get_var("env"), "DEC": self.get_var("dec"),
|
||||||
@ -81,59 +81,64 @@ class LuauVMObfuscator:
|
|||||||
"CHARS": self.get_var("chars"), "LOOKUP": self.get_var("lookup"), "GETC": self.get_var("getc")
|
"CHARS": self.get_var("chars"), "LOOKUP": self.get_var("lookup"), "GETC": self.get_var("getc")
|
||||||
}
|
}
|
||||||
|
|
||||||
vm_lua = f"""local {v['ENV']}=setmetatable({{}},{{__index=getfenv()}})
|
# Using triple single quotes to avoid confusion
|
||||||
local {v['CHARS']}='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
vm_lua = f'''local {v['ENV']}=setmetatable({{}},{{__index=getfenv()}});
|
||||||
local {v['LOOKUP']}={{}}
|
local {v['CHARS']}='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
for i=1,64 do {v['LOOKUP']}[{v['CHARS']}:sub(i,i)]=i-1 end
|
local {v['LOOKUP']}={{}};
|
||||||
|
for i=1,64 do {v['LOOKUP']}[{v['CHARS']}:sub(i,i)]=i-1 end;
|
||||||
local function {v['DEC']}(data)
|
local function {v['DEC']}(data)
|
||||||
data=data:gsub('[^%a%d%+/]','')
|
data=data:gsub('[^%a%d%+/]','');
|
||||||
local res={{}}
|
local res={{}};
|
||||||
for i=1,#data,4 do
|
for i=1,#data,4 do
|
||||||
local a,b,c,d={v['LOOKUP']}[data:sub(i,i)],{v['LOOKUP']}[data:sub(i+1,i+1)],{v['LOOKUP']}[data:sub(i+2,i+2)],{v['LOOKUP']}[data:sub(i+3,i+3)]
|
local a,b,c,d={v['LOOKUP']}[data:sub(i,i)],{v['LOOKUP']}[data:sub(i+1,i+1)],{v['LOOKUP']}[data:sub(i+2,i+2)],{v['LOOKUP']}[data:sub(i+3,i+3)];
|
||||||
local n={v['BIT']}.lshift(a or 0,18)+{v['BIT']}.lshift(b or 0,12)+{v['BIT']}.lshift(c or 0,6)+(d or 0)
|
local n={v['BIT']}.lshift(a or 0,18)+{v['BIT']}.lshift(b or 0,12)+{v['BIT']}.lshift(c or 0,6)+(d or 0);
|
||||||
res[#res+1]=string.char({v['BIT']}.extract(n,16,8))
|
res[#res+1]=string.char({v['BIT']}.extract(n,16,8));
|
||||||
if c then res[#res+1]=string.char({v['BIT']}.extract(n,8,8)) end
|
if c then res[#res+1]=string.char({v['BIT']}.extract(n,8,8)) end;
|
||||||
if d then res[#res+1]=string.char({v['BIT']}.extract(n,0,8)) end
|
if d then res[#res+1]=string.char({v['BIT']}.extract(n,0,8)) end;
|
||||||
end
|
end;
|
||||||
return table.concat(res)
|
return table.concat(res);
|
||||||
end
|
end;
|
||||||
local {v['INST']}={v['DEC']}('{inst_b64}')
|
local {v['INST']}={v['DEC']}(''{inst_b64}');
|
||||||
local {v['CONSTS']}={v['JSON']}:JSONDecode([=[{json.dumps(consts)}]=])
|
local {v['CONSTS']}={v['JSON']}:JSONDecode([=[{json.dumps(consts)}]=]);
|
||||||
local {v['EXEC']}=function()
|
local {v['EXEC']}=function()
|
||||||
local {v['REGS']}={{}}
|
local {v['REGS']}={{}};
|
||||||
local {v['CURR']}={self.to_expr(pos_map[0])}
|
local {v['CURR']}={self.to_expr(pos_map[0])};
|
||||||
local function {v['GETC']}(idx)
|
local function {v['GETC']}(idx)
|
||||||
local c={v['CONSTS']}[idx+1]
|
local c={v['CONSTS']}[idx+1];
|
||||||
if not c then return end
|
if not c then return end;
|
||||||
if c.t==1 then
|
if c.t==1 then
|
||||||
local r,k={v['DEC']}(c.v),(idx*149+{v['SALT']})%256
|
local r,k={v['DEC']}(c.v),(idx*149+{v['SALT']})%256;
|
||||||
local res,lst={{}},k%256
|
local res,lst={{}},k%256;
|
||||||
for i=1,#r do
|
for i=1,#r do
|
||||||
local char={v['BIT']}.bxor(string.byte(r,i),(k+i+lst-1)%256)
|
local char={v['BIT']}.bxor(string.byte(r,i),(k+i+lst-1)%256);
|
||||||
res[i]=string.char(char)
|
res[i]=string.char(char);
|
||||||
lst=char
|
lst=char;
|
||||||
end
|
end;
|
||||||
return table.concat(res)
|
return table.concat(res);
|
||||||
elseif c.t==3 then return c.v=='true' and true or (c.v=='false' and false or nil) end
|
elseif c.t==3 then return c.v=='true' and true or (c.v=='false' and false or nil) end;
|
||||||
return c.v
|
return c.v;
|
||||||
end
|
end;
|
||||||
while true do
|
while true do
|
||||||
local {v['PTR']}={v['CURR']}*7+1
|
local {v['PTR']}={v['CURR']}*7+1;
|
||||||
local b1,b2,b3,b4,b5,b6,b7=string.byte({v['INST']},{v['PTR']},{v['PTR']}+6)
|
local b1,b2,b3,b4,b5,b6,b7=string.byte({v['INST']},{v['PTR']},{v['PTR']}+6);
|
||||||
if not b1 then break end
|
if not b1 then break end;
|
||||||
{v['CURR']}=b6+({v['BIT']}.lshift(b7,8))
|
{v['CURR']}=b6+({v['BIT']}.lshift(b7,8));
|
||||||
local {v['OP']}={v['BIT']}.bxor(b1+{v['BIT']}.lshift(b2,8),{self.to_expr(self.k2)})-{self.to_expr(self.k1)}
|
local {v['OP']}={v['BIT']}.bxor(b1+{v['BIT']}.lshift(b2,8),{self.to_expr(self.k2)})-{self.to_expr(self.k1)};
|
||||||
local {v['A']},{v['B']},{v['C']}=b3,b4,b5
|
local {v['A']},{v['B']},{v['C']}=b3,b4,b5;
|
||||||
if {v['OP']}=={self.opcodes.index('MOVE')} then {v['REGS']}[{v['A']}]={v['REGS']}[{v['B']}]
|
if {v['OP']}=={self.opcodes.index('MOVE')} then {v['REGS']}[{v['A']}]={v['REGS']}[{v['B']}]
|
||||||
elseif {v['OP']}=={self.opcodes.index('LOADK')} then {v['REGS']}[{v['A']}]={v['GETC']}({v['B']})
|
elseif {v['OP']}=={self.opcodes.index('LOADK')} then {v['REGS']}[{v['A']}]={v['GETC']}({v['B']})
|
||||||
elseif {v['OP']}=={self.opcodes.index('GETGLOBAL')} then {v['REGS']}[{v['A']}]={v['ENV']}[{v['GETC']}({v['B']})]
|
elseif {v['OP']}=={self.opcodes.index('GETGLOBAL')} then {v['REGS']}[{v['A']}]={v['ENV']}[{v['GETC']}({v['B']})]
|
||||||
elseif {v['OP']}=={self.opcodes.index('SETGLOBAL')} then {v['ENV']}[{v['GETC']}({v['B']})]={v['REGS']}[{v['A']}]
|
elseif {v['OP']}=={self.opcodes.index('SETGLOBAL')} then {v['ENV']}[{v['GETC']}({v['B']})]={v['REGS']}[{v['A']}]
|
||||||
elseif {v['OP']}=={self.opcodes.index('GETTABLE')} then
|
elseif {v['OP']}=={self.opcodes.index('GETTABLE')} then
|
||||||
local b={v['REGS']}[{v['B']}]
|
local b={v['REGS']}[{v['B']}];
|
||||||
if b then {v['REGS']}[{v['A']}]=b[{v['REGS']}[{v['C']}] or {v['GETC']}({v['C']})] end
|
if b then {v['REGS']}[{v['A']}]=b[{v['REGS']}[{v['C']}] or {v['GETC']}({v['C']})] end
|
||||||
elseif {v['OP']}=={self.opcodes.index('SETTABLE')} then
|
elseif {v['OP']}=={self.opcodes.index('SETTABLE')} then
|
||||||
local a={v['REGS']}[{v['A']}]
|
local a={v['REGS']}[{v['A']}];
|
||||||
if a then a[{v['REGS']}[{v['B']}] or {v['GETC']}({v['B']})]={v['REGS']}[{v['C']}] end
|
if a then a[{v['REGS']}[{v['B']}] or {v['GETC']}({v['B']})]={v['REGS']}[{v['C']}] end
|
||||||
|
elseif {v['OP']}=={self.opcodes.index('SELF')} then
|
||||||
|
local b={v['REGS']}[{v['B']}];
|
||||||
|
{v['REGS']}[{v['A']}+1]=b;
|
||||||
|
{v['REGS']}[{v['A']}]=b[{v['GETC']}({v['C']})];
|
||||||
elseif {v['OP']}=={self.opcodes.index('ADD')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)+({v['REGS']}[{v['C']}] or 0)
|
elseif {v['OP']}=={self.opcodes.index('ADD')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)+({v['REGS']}[{v['C']}] or 0)
|
||||||
elseif {v['OP']}=={self.opcodes.index('SUB')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)-({v['REGS']}[{v['C']}] or 0)
|
elseif {v['OP']}=={self.opcodes.index('SUB')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)-({v['REGS']}[{v['C']}] or 0)
|
||||||
elseif {v['OP']}=={self.opcodes.index('MUL')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)*({v['REGS']}[{v['C']}] or 0)
|
elseif {v['OP']}=={self.opcodes.index('MUL')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)*({v['REGS']}[{v['C']}] or 0)
|
||||||
@ -142,24 +147,24 @@ local {v['EXEC']}=function()
|
|||||||
elseif {v['OP']}=={self.opcodes.index('NOT')} then {v['REGS']}[{v['A']}]=not {v['REGS']}[{v['B']}]
|
elseif {v['OP']}=={self.opcodes.index('NOT')} then {v['REGS']}[{v['A']}]=not {v['REGS']}[{v['B']}]
|
||||||
elseif {v['OP']}=={self.opcodes.index('LEN')} then {v['REGS']}[{v['A']}]=#{v['REGS']}[{v['B']}] or 0
|
elseif {v['OP']}=={self.opcodes.index('LEN')} then {v['REGS']}[{v['A']}]=#{v['REGS']}[{v['B']}] or 0
|
||||||
elseif {v['OP']}=={self.opcodes.index('CALL')} then
|
elseif {v['OP']}=={self.opcodes.index('CALL')} then
|
||||||
local f={v['REGS']}[{v['A']}]
|
local f={v['REGS']}[{v['A']}];
|
||||||
if f then
|
if f then
|
||||||
local args={{}}
|
local args={{}};
|
||||||
if {v['B']}>1 then for i=1,{v['B']}-1 do args[i]={v['REGS']}[{v['A']}+i] end end
|
if {v['B']}>1 then for i=1,{v['B']}-1 do args[i]={v['REGS']}[{v['A']} + i] end end;
|
||||||
local res={{f(({v['UNP']})(args))}}
|
local res={{f(({v['UNP']})(args))}};
|
||||||
if {v['C']}>1 then for i=1,{v['C']}-1 do {v['REGS']}[{v['A']}+i-1]=res[i] end end
|
if {v['C']}>1 then for i=1,{v['C']}-1 do {v['REGS']}[{v['A']} + i - 1]=res[i] end end;
|
||||||
end
|
end
|
||||||
elseif {v['OP']}=={self.opcodes.index('RETURN')} then break end
|
elseif {v['OP']}=={self.opcodes.index('RETURN')} then break end;
|
||||||
end
|
end;
|
||||||
end
|
end;
|
||||||
{v['SPW']}({v['EXEC']})"""
|
{v['SPW']}({v['EXEC']});'''
|
||||||
return self.minify(vm_lua)
|
return self.minify(vm_lua)
|
||||||
|
|
||||||
def compile_to_bytecode(self, ast):
|
def compile_to_bytecode(self, ast):
|
||||||
constants, instructions, locals_map = [], [], {}
|
constants, instructions, locals_map = [], [], {}
|
||||||
self.next_reg = 0
|
self.next_reg = 0
|
||||||
def add_const(val):
|
def add_const(val):
|
||||||
if isinstance(val, str) and ((val.startswith("'") and val.endswith("'")) or (val.startswith('"') and val.endswith('"'))): val = val[1:-1]
|
if isinstance(val, str) and ((val.startswith("'" ) and val.endswith("'" )) or (val.startswith('"') and val.endswith('"'))): val = val[1:-1]
|
||||||
for i, c in enumerate(constants):
|
for i, c in enumerate(constants):
|
||||||
if c['value'] == val: return i
|
if c['value'] == val: return i
|
||||||
t = 'string' if isinstance(val, str) else 'number'
|
t = 'string' if isinstance(val, str) else 'number'
|
||||||
@ -177,8 +182,12 @@ end
|
|||||||
else: emit("GETGLOBAL", target, add_const(expr['name']))
|
else: emit("GETGLOBAL", target, add_const(expr['name']))
|
||||||
elif expr['type'] == 'index':
|
elif expr['type'] == 'index':
|
||||||
br = self.next_reg; self.next_reg += 1; gen_expr(expr['base'], br)
|
br = self.next_reg; self.next_reg += 1; gen_expr(expr['base'], br)
|
||||||
kr = self.next_reg; self.next_reg += 1; gen_expr(expr['key'], kr)
|
if expr['key']['type'] == 'STRING':
|
||||||
emit("GETTABLE", target, br, kr); self.next_reg -= 2
|
emit("GETTABLE", target, br, add_const(expr['key']['value']))
|
||||||
|
else:
|
||||||
|
kr = self.next_reg; self.next_reg += 1; gen_expr(expr['key'], kr)
|
||||||
|
emit("GETTABLE", target, br, kr); self.next_reg -= 1
|
||||||
|
self.next_reg -= 1
|
||||||
elif expr['type'] == 'binary':
|
elif expr['type'] == 'binary':
|
||||||
lr = self.next_reg; self.next_reg += 1; gen_expr(expr['left'], lr)
|
lr = self.next_reg; self.next_reg += 1; gen_expr(expr['left'], lr)
|
||||||
rr = self.next_reg; self.next_reg += 1; gen_expr(expr['right'], rr)
|
rr = self.next_reg; self.next_reg += 1; gen_expr(expr['right'], rr)
|
||||||
@ -189,16 +198,25 @@ end
|
|||||||
op_m = {'-': 'UNM', '#': 'LEN', 'not': 'NOT'}
|
op_m = {'-': 'UNM', '#': 'LEN', 'not': 'NOT'}
|
||||||
emit(op_m.get(expr['op'], 'UNM'), target, or_reg); self.next_reg -= 1
|
emit(op_m.get(expr['op'], 'UNM'), target, or_reg); self.next_reg -= 1
|
||||||
elif expr['type'] == 'call': gen_call(expr, target)
|
elif expr['type'] == 'call': gen_call(expr, target)
|
||||||
|
elif expr['type'] == 'method_call': gen_method_call(expr, target)
|
||||||
def gen_call(node, target):
|
def gen_call(node, target):
|
||||||
fr = self.next_reg; self.next_reg += 1
|
fr = self.next_reg; self.next_reg += 1
|
||||||
if node['func']['type'] == 'variable':
|
gen_expr(node['func'], fr)
|
||||||
if node['func']['name'] in locals_map: emit("MOVE", fr, locals_map[node['func']['name']])
|
|
||||||
else: emit("GETGLOBAL", fr, add_const(node['func']['name']))
|
|
||||||
else: gen_expr(node['func'], fr)
|
|
||||||
for i, arg in enumerate(node['args']):
|
for i, arg in enumerate(node['args']):
|
||||||
ar = self.next_reg; self.next_reg += 1; gen_expr(arg, ar)
|
ar = self.next_reg; self.next_reg += 1; gen_expr(arg, ar)
|
||||||
emit("CALL", fr, len(node['args']) + 1, 2)
|
emit("CALL", fr, len(node['args']) + 1, 2)
|
||||||
emit("MOVE", target, fr)
|
if target != fr: emit("MOVE", target, fr)
|
||||||
|
self.next_reg = fr
|
||||||
|
def gen_method_call(node, target):
|
||||||
|
fr = self.next_reg; self.next_reg += 1
|
||||||
|
br = self.next_reg; self.next_reg += 1
|
||||||
|
gen_expr(node['base'], br)
|
||||||
|
emit("SELF", fr, br, add_const(node['method']))
|
||||||
|
self.next_reg = fr + 2
|
||||||
|
for i, arg in enumerate(node['args']):
|
||||||
|
ar = self.next_reg; self.next_reg += 1; gen_expr(arg, ar)
|
||||||
|
emit("CALL", fr, len(node['args']) + 2, 2)
|
||||||
|
if target != fr: emit("MOVE", target, fr)
|
||||||
self.next_reg = fr
|
self.next_reg = fr
|
||||||
for node in ast:
|
for node in ast:
|
||||||
if node['type'] == 'assign':
|
if node['type'] == 'assign':
|
||||||
@ -208,10 +226,15 @@ end
|
|||||||
t = node['target']
|
t = node['target']
|
||||||
if t['type'] == 'index':
|
if t['type'] == 'index':
|
||||||
br = self.next_reg; self.next_reg += 1; gen_expr(t['base'], br)
|
br = self.next_reg; self.next_reg += 1; gen_expr(t['base'], br)
|
||||||
kr = self.next_reg; self.next_reg += 1; gen_expr(t['key'], kr)
|
if t['key']['type'] == 'STRING':
|
||||||
emit("SETTABLE", br, kr, vr); self.next_reg -= 2
|
emit("SETTABLE", br, add_const(t['key']['value']), vr)
|
||||||
|
else:
|
||||||
|
kr = self.next_reg; self.next_reg += 1; gen_expr(t['key'], kr)
|
||||||
|
emit("SETTABLE", br, kr, vr); self.next_reg -= 1
|
||||||
|
self.next_reg -= 1
|
||||||
else: emit("SETGLOBAL", vr, add_const(node['name']))
|
else: emit("SETGLOBAL", vr, add_const(node['name']))
|
||||||
elif node['type'] == 'call': gen_call(node, self.next_reg)
|
elif node['type'] == 'call': gen_call(node, self.next_reg)
|
||||||
|
elif node['type'] == 'method_call': gen_method_call(node, self.next_reg)
|
||||||
emit("RETURN")
|
emit("RETURN")
|
||||||
return {"instructions": instructions, "constants": constants}
|
return {"instructions": instructions, "constants": constants}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ class Lexer:
|
|||||||
self.tokens = []
|
self.tokens = []
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
LB, RB, DQ, SQ, BS = chr(91), chr(93), chr(34), chr(39), chr(92)
|
LB, RB, DQ, SQ, BS = chr(91), chr(93), chr(34), chr(39), chr(92)
|
||||||
|
# Added : to OP_LIST
|
||||||
OP_LIST = r'==|~=|<=|>=|\.\.\.|\.\.|>>|<<|\+|\-|\*|/|%|\^|#|=|\<|\>|\(|\)|\{|\}|' + BS + LB + '|' + BS + RB + '|;|:|,|\.'
|
OP_LIST = r'==|~=|<=|>=|\.\.\.|\.\.|>>|<<|\+|\-|\*|/|%|\^|#|=|\<|\>|\(|\)|\{|\}|' + BS + LB + '|' + BS + RB + '|;|:|,|\.'
|
||||||
self.rules = [
|
self.rules = [
|
||||||
('COMMENT', re.compile('--' + LB + LB + '.*?' + RB + RB + '|--.*', re.DOTALL)),
|
('COMMENT', re.compile('--' + LB + LB + '.*?' + RB + RB + '|--.*', re.DOTALL)),
|
||||||
@ -43,7 +44,7 @@ class Parser:
|
|||||||
while self.pos < len(self.tokens):
|
while self.pos < len(self.tokens):
|
||||||
node = self.parse_statement()
|
node = self.parse_statement()
|
||||||
if node: nodes.append(node)
|
if node: nodes.append(node)
|
||||||
else: self.pos += 1
|
else: self.pos += 1 # Still skipping, but hopefully parse_statement is better
|
||||||
return nodes
|
return nodes
|
||||||
def parse_statement(self):
|
def parse_statement(self):
|
||||||
tk = self.peek()
|
tk = self.peek()
|
||||||
@ -51,12 +52,15 @@ class Parser:
|
|||||||
if tk[1] == 'local':
|
if tk[1] == 'local':
|
||||||
self.consume(); ident = self.consume('IDENT')
|
self.consume(); ident = self.consume('IDENT')
|
||||||
if ident:
|
if ident:
|
||||||
if self.peek()[1] == '=': self.consume(); return {'type': 'assign', 'name': ident[1], 'value': self.parse_expression(), 'local': True}
|
if self.peek()[1] == '=':
|
||||||
|
self.consume(); return {'type': 'assign', 'name': ident[1], 'value': self.parse_expression(), 'local': True}
|
||||||
|
return {'type': 'assign', 'name': ident[1], 'value': {'type': 'KEYWORD', 'value': 'nil'}, 'local': True}
|
||||||
return None
|
return None
|
||||||
start = self.pos; expr = self.parse_prefix_expression()
|
start = self.pos; expr = self.parse_prefix_expression()
|
||||||
if expr:
|
if expr:
|
||||||
if self.peek()[1] == '=': self.consume(); return {'type': 'assign', 'target': expr, 'value': self.parse_expression()}
|
if self.peek()[1] == '=':
|
||||||
elif expr['type'] == 'call': return expr
|
self.consume(); return {'type': 'assign', 'target': expr, 'value': self.parse_expression()}
|
||||||
|
elif expr['type'] in ['call', 'method_call']: return expr
|
||||||
self.pos = start; return None
|
self.pos = start; return None
|
||||||
def parse_prefix_expression(self):
|
def parse_prefix_expression(self):
|
||||||
tk = self.peek()
|
tk = self.peek()
|
||||||
@ -65,8 +69,24 @@ class Parser:
|
|||||||
while True:
|
while True:
|
||||||
nt = self.peek()
|
nt = self.peek()
|
||||||
if nt[1] == '.':
|
if nt[1] == '.':
|
||||||
self.consume(); m = self.consume('IDENT'); expr = {'type': 'index', 'base': expr, 'key': {'type': 'STRING', 'value': '"'+m[1]+'"'}}
|
self.consume(); m = self.consume('IDENT')
|
||||||
elif nt[1] == '[': self.consume(); key = self.parse_expression(); self.consume('OP', ']'); expr = {'type': 'index', 'base': expr, 'key': key}
|
if m: expr = {'type': 'index', 'base': expr, 'key': {'type': 'STRING', 'value': '"'+m[1]+'"'}}
|
||||||
|
else: break
|
||||||
|
elif nt[1] == ':':
|
||||||
|
self.consume(); m = self.consume('IDENT')
|
||||||
|
if m:
|
||||||
|
# Method call
|
||||||
|
if self.peek()[1] == '(':
|
||||||
|
self.consume(); args = []
|
||||||
|
if self.peek()[1] != ')':
|
||||||
|
args.append(self.parse_expression())
|
||||||
|
while self.peek()[1] == ',': self.consume(); args.append(self.parse_expression())
|
||||||
|
self.consume('OP', ')'); expr = {'type': 'method_call', 'base': expr, 'method': m[1], 'args': args}
|
||||||
|
else: # Not a call, just a colon access? (Rare in Lua, usually an error, but let's be safe)
|
||||||
|
expr = {'type': 'index', 'base': expr, 'key': {'type': 'STRING', 'value': '"'+m[1]+'"'}}
|
||||||
|
else: break
|
||||||
|
elif nt[1] == '[':
|
||||||
|
self.consume(); key = self.parse_expression(); self.consume('OP', ']'); expr = {'type': 'index', 'base': expr, 'key': key}
|
||||||
elif nt[1] == '(':
|
elif nt[1] == '(':
|
||||||
self.consume(); args = []
|
self.consume(); args = []
|
||||||
if self.peek()[1] != ')':
|
if self.peek()[1] != ')':
|
||||||
@ -77,15 +97,18 @@ class Parser:
|
|||||||
return expr
|
return expr
|
||||||
def parse_expression(self):
|
def parse_expression(self):
|
||||||
left = self.parse_primary_expression()
|
left = self.parse_primary_expression()
|
||||||
|
if not left: return None
|
||||||
while self.peek()[1] in ['+', '-', '*', '/', '..', '==', '<', '>', '<=', '>=', '~=']:
|
while self.peek()[1] in ['+', '-', '*', '/', '..', '==', '<', '>', '<=', '>=', '~=']:
|
||||||
op = self.consume()[1]; right = self.parse_primary_expression()
|
op = self.consume()[1]; right = self.parse_primary_expression()
|
||||||
|
if not right: break
|
||||||
left = {'type': 'binary', 'op': op, 'left': left, 'right': right}
|
left = {'type': 'binary', 'op': op, 'left': left, 'right': right}
|
||||||
return left
|
return left
|
||||||
def parse_primary_expression(self):
|
def parse_primary_expression(self):
|
||||||
tk = self.peek()
|
tk = self.peek()
|
||||||
if not tk: return None
|
if not tk: return None
|
||||||
if tk[1] in ['-', '#', 'not']:
|
if tk[1] in ['-', '#', 'not']:
|
||||||
op = self.consume()[1]; return {'type': 'unary', 'op': op, 'operand': self.parse_primary_expression()}
|
op = self.consume()[1]; sub = self.parse_primary_expression()
|
||||||
|
return {'type': 'unary', 'op': op, 'operand': sub} if sub else None
|
||||||
if tk[0] in ['STRING', 'NUMBER']: v = tk[1]; self.consume(); return {'type': tk[0], 'value': v}
|
if tk[0] in ['STRING', 'NUMBER']: v = tk[1]; self.consume(); return {'type': tk[0], 'value': v}
|
||||||
if tk[1] in ['true', 'false', 'nil']: return {'type': 'KEYWORD', 'value': self.consume()[1]}
|
if tk[1] in ['true', 'false', 'nil']: return {'type': 'KEYWORD', 'value': self.consume()[1]}
|
||||||
if tk[1] == '(': self.consume(); e = self.parse_expression(); self.consume('OP', ')'); return e
|
if tk[1] == '(': self.consume(); e = self.parse_expression(); self.consume('OP', ')'); return e
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user