Загрузка данных


import sublime
import sublime_plugin
import re
import json
import math
import 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('.', ',')
            if ',' in s:
                s = s.rstrip('0').rstrip(',')
                if not s:
                    s = "0"
            return s

        def safe_eval(expr):
            cl = expr.replace(',', '.').strip()
            if not cl:
                return 0.0
            try:
                cl = re.sub(r'[^0-9.+\-*/() ]', '', cl)
                return float(eval(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]:
                                idx_v = i if i < len(repeatVars[v]) else -1
                                val = repeatVars[v][idx_v].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:
                        val = str(calcVars[v]) if v in calcVars else '0'
                        ex = ex.replace(f"*{v}", val)
                    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 = float(safe_eval(ex_l))
                opVal = float(safe_eval(ex_r))
                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:
                    f_s = inner[semi+1:].strip().replace(',', '.')
                    factor = float(f_s 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:
                        n_l = max(1, math.floor((len(srcArr) - 1) / factor) + 1)
                        for i in range(n_l):
                            idx_f = i * factor
                            b = math.floor(idx_f)
                            f = idx_f - b
                            if b + 1 < len(srcArr):
                                v1 = float(srcArr[b].replace(',', '.'))
                                v2 = float(srcArr[b+1].replace(',', '.'))
                                r.append(formatNum(v1 + (v2 - v1) * 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()
                if src in repeatVars:
                    repeatVars[line[eq+1:].replace('*', '').strip()] = list(reversed(repeatVars[src]))
                else:
                    repeatVars[line[eq+1:].replace('*', '').strip()] = []
                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:
                            v_f = float(v.replace(',', '.'))
                            repeatVars[name].append(formatNum(round(v_f, 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
            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:
                        if p in repeatVars:
                            vals = repeatVars[p]
                        elif p in calcVars:
                            vals = [formatNum(calcVars[p])]
                        else:
                            vals = [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()
                if p in repeatVars:
                    vals = repeatVars[p]
                elif p in calcVars:
                    vals = [formatNum(calcVars[p])]
                else:
                    vals = [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(' ', '&nbsp;')
        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)