Загрузка данных
import sublime, sublime_plugin, re, json, math, random
class LuxScriptLiveVisualizer(sublime_plugin.EventListener):
def on_modified_async(self, view):
fn = view.file_name()
if not fn or not fn.endswith(('.luxs', '.lux', '.ls')): return
view.erase_phantoms("lux_live")
code = view.substr(sublime.Region(0, view.size()))
lines = code.split('\n')
res, calcVars, repeatVars = {}, {}, {}
step, lastLightVal, end_idx = 1, "0", len(lines) - 1
def formatNum(v, d=2):
try: v = float(v)
except: return "0"
if v < 0: v = 0.0
if v > 1: v = 1.0
s = f"{v:.{d}f}".replace('.', ',')
return s.rstrip('0').rstrip(',') if ',' in s else s
def safe_eval(expr):
cl = expr.replace(',', '.').strip()
if not cl: return 0.0
try: return float(eval(re.sub(r'[^0-9.+\-*/() ]', '', cl), {"__builtins__": None}))
except: return 0.0
for idx, line in enumerate(lines):
line = line.strip()
if not line: continue
if 'stop()' in line: res[step] = "stop"; step += 1; end_idx = idx; break
if line.startswith('num('):
eq = line.find('=')
if eq == -1: continue
name = line[eq+1:].replace('*', '').strip(); inner = line[4:line.find(')')]; arr = []
for p in inner.split(';'):
p = p.strip()
if p.startswith('*') and p[1:].strip() in repeatVars: arr.extend(repeatVars[p[1:].strip()])
elif p.startswith('*') and p[1:].strip() in calcVars: arr.append(formatNum(calcVars[p[1:].strip()], 6))
elif not p.startswith('*'): arr.append(formatNum(safe_eval(p), 6))
else: arr.append(["0"])
repeatVars[name] = arr; continue
if line.startswith('calc('):
eq = line.find('=')
if eq == -1: continue
name = line[eq+1:].replace('*', '').strip(); inner = line[5:line.find(')')]; v_m = re.findall(r'\*([a-zA-Z0-9_а-яА-ЯёЁ]+)', inner)
is_arr = any(v in repeatVars for v in v_m); lens = [len(repeatVars[v]) for v in v_m if v in repeatVars]; max_l = max(lens) if lens else 1
if is_arr:
arr_res = []
for i in range(max_l):
ex = inner
for v in v_m:
if v in calcVars: val = str(calcVars[v])
elif v in repeatVars and repeatVars[v]: val = repeatVars[v][i if i < len(repeatVars[v]) else -1].replace(',', '.')
else: val = '0'
ex = ex.replace(f"*{v}", val)
arr_res.append(formatNum(safe_eval(ex), 6))
repeatVars[name] = arr_res
else:
ex = inner
for v in v_m: ex = ex.replace(f"*{v}", str(calcVars[v]) if v in calcVars else '0')
calcVars[name] = safe_eval(ex)
continue
if line.startswith('calcrepeat('):
eq = line.find('=')
if eq == -1: continue
name = line[eq+1:].replace('*', '').strip(); inner = line[11:line.find(')')]; semi = inner.find(';')
if semi == -1: continue
count = int(inner[semi+1:].strip() or 0); expr = inner[:semi]; op = next((o for o in ['+','-','*','/'] if o in expr), None)
if not op: continue
opIdx = expr.find(op); ex_l, ex_r = expr[:opIdx], expr[opIdx+1:]
for v in re.findall(r'\*([a-zA-Z0-9_а-яА-ЯёЁ]+)', ex_l): ex_l = ex_l.replace(f"*{v}", str(calcVars.get(v, 0)))
for v in re.findall(r'\*([a-zA-Z0-9_а-яА-ЯёЁ]+)', ex_r): ex_r = ex_r.replace(f"*{v}", str(calcVars.get(v, 0)))
start, opVal, r = float(safe_eval(ex_l)), float(safe_eval(ex_r)), []
for i in range(count):
if op == '+': val = start + (opVal * i)
elif op == '-': val = start - (opVal * i)
elif op == '*': val = start * (opVal ** i)
else: val = start / (opVal ** i) if opVal != 0 else start
r.append(formatNum(val, 6))
repeatVars[name] = r; continue
if line.startswith('speed('):
eq = line.find('=')
if eq == -1: continue
name = line[eq+1:].replace('*', '').strip(); inner = line[6:line.find(')')]; semi = inner.find(';')
if semi == -1: continue
src = inner[:semi].replace('*', '').strip()
try: factor = float(inner[semi+1:].strip().replace(',', '.') or 1.0)
except: factor = 1.0
if src in repeatVars and len(repeatVars[src]) > 0 and factor > 0:
srcArr, r = repeatVars[src], []
if factor.is_integer(): r = [srcArr[i] for i in range(0, len(srcArr), int(factor))]
else:
for i in range(max(1, math.floor((len(srcArr) - 1) / factor) + 1)):
idx_f = i * factor; b = math.floor(idx_f); f = idx_f - b
if b + 1 < len(srcArr): r.append(formatNum(float(srcArr[b].replace(',', '.')) + (float(srcArr[b+1].replace(',', '.')) - float(srcArr[b].replace(',', '.'))) * f, 6))
else: r.append(srcArr[b])
repeatVars[name] = r
else: repeatVars[name] = []; continue
if line.startswith('invert('):
eq = line.find('=')
if eq == -1: continue
src = line[7:line.find(')')].replace('*', '').strip()
repeatVars[line[eq+1:].replace('*', '').strip()] = list(reversed(repeatVars[src])) if src in repeatVars else []
continue
if line.startswith('rand('):
eq = line.find('=')
if eq == -1: continue
name = line[eq+1:].replace('*', '').strip(); inner = line[5:line.find(')')]; semi = inner.find(';')
if semi == -1: continue
count = int(inner[semi+1:].strip() or 0); range_str = inner[:semi]; parts = [float(safe_eval(s)) for s in range_str.split('-') if s.strip()]
if len(parts) == 2: repeatVars[name] = [formatNum(random.uniform(min(parts), max(parts)), 6) for _ in range(count)]
continue
if line.startswith('round('):
eq = line.find('=')
if eq == -1: continue
name = line[eq+1:].replace('*', '').strip(); inner = line[6:line.find(')')]; semi = inner.find(';')
if semi == -1: continue
src = inner[:semi].replace('*', '').strip(); step_s = inner[semi+1:].strip().replace(',', '.'); dec = len(step_s.split('.')) if '.' in step_s else 0
if src in repeatVars:
repeatVars[name] = []
for v in repeatVars[src]:
try: repeatVars[name].append(formatNum(round(float(v.replace(',', '.')), dec), dec))
except: repeatVars[name].append("0")
elif src in calcVars:
try: calcVars[name] = round(calcVars[src], dec)
except: calcVars[name] = calcVars[src]
continue
if line.startswith('wait('):
try: count = int(line[5:line.find(')')].strip() or 0)
except: count = 0
for _ in range(count): res[step] = lastLightVal; step += 1
continue
if line.startswith('repeat('):
semi = line.rfind(';'); if semi == -1: continue
parts = [s[:s.find(')')].replace('*', '').strip() for s in line[7:semi].split('light(') if s.strip()]
try: rep_count = int(line[semi+1:line.rfind(')')].strip() or 0)
except: rep_count = 0
for _ in range(rep_count):
for p in parts:
vals = repeatVars[p] if p in repeatVars else [formatNum(calcVars[p])] if p in calcVars else [p.replace('.', ',')]
for v in vals: res[step] = v; step += 1; lastLightVal = v
continue
if line.startswith('light('):
p = line[6:line.find(')')].replace('*', '').strip()
vals = repeatVars[p] if p in repeatVars else [formatNum(calcVars[p])] if p in calcVars else [p.replace('.', ',')]
for v in vals: res[step] = v; step += 1; lastLightVal = v
if not res: return
output_text = json.dumps(res, indent=2, ensure_ascii=False).replace('\n', '<br>').replace(' ', ' ')
view.add_phantom("lux_live", sublime.Region(view.text_point(end_idx + 1, 0)), f'<body style="padding:10px; background-color:#111; color:#fff; font-family:monospace; font-size:11px;">{output_text}</body>', sublime.LAYOUT_BELOW)