Project 404

📄 syntaxer.php

🕒 1 month ago 👤 dasho
🏷️ php

This is the syntax highlighter for 404. This engine powers the highlighting that shows up on previews of code files, as well the pastes page. I'm quite proud of it, even if it is simple. I learned a lot about regex making this.

>WARN> Use carefully, as I don't know if it can be use to run code injections...

<?php

class SyntaxHighlighter {
    
    private static $languageMap = [
        'py' => 'python',
        'pyw' => 'python',
        'js' => 'javascript',
        'mjs' => 'javascript',
        'go' => 'go',
        'rs' => 'rust',
        'sh' => 'bash',
        'bash' => 'bash',
        'zsh' => 'bash',
        'fish' => 'bash',
        'md' => 'markdown',
        'markdown' => 'markdown',
        'css' => 'css',
        'scss' => 'css',
        'sass' => 'css',
        'html' => 'html',
        'htm' => 'html',
        'xhtml' => 'html',
        'php' => 'php',
        'php3' => 'php',
        'php4' => 'php',
        'php5' => 'php',
        'phtml' => 'php',
        'env' => 'env',
        'json' => 'json',
        'yaml' => 'yaml',
        'yml' => 'yaml',
        'config' => 'config',
        'conf' => 'config',
        'ini' => 'config',
        'cfg' => 'config',
        'txt' => 'text',
        'log' => 'text',
        'sql' => 'sql',
        'mysql' => 'sql',
        'pgsql' => 'sql',
        'xml' => 'xml',
        'c' => 'c',
        'cpp' => 'cpp',
        'cxx' => 'cpp',
        'cc' => 'cpp',
        'h' => 'c',
        'hpp' => 'cpp',
        'hxx' => 'cpp',
        'java' => 'java',
        'rb' => 'ruby',
        'ruby' => 'ruby',
        'pl' => 'perl',
        'pm' => 'perl',
        'ts' => 'typescript',
        'tsx' => 'typescript',
        'jsx' => 'javascript',
        'vue' => 'html',
        'dockerfile' => 'bash',
        'makefile' => 'bash',
        'gitignore' => 'text',
        'toml' => 'config',
        'properties' => 'config'
    ];
    
    private static function getLanguage($extension) {
        return self::$languageMap[strtolower($extension)] ?? 'text';
    }
    
    public static function highlight($code, $extension, $theme = 'dark', $showLineNumbers = true) {
        $language = self::getLanguage($extension);
        
        // Escape HTML first
        $code = htmlspecialchars($code, ENT_NOQUOTES);
        
        // Apply syntax highlighting based on language
        switch ($language) {
            case 'python':
                $highlighted = self::highlightPython($code, $theme);
                break;
            case 'javascript':
            case 'typescript':
                $highlighted = self::highlightJavaScript($code, $theme);
                break;
            case 'go':
                $highlighted = self::highlightGo($code, $theme);
                break;
            case 'rust':
                $highlighted = self::highlightRust($code, $theme);
                break;
            case 'bash':
                $highlighted = self::highlightBash($code, $theme);
                break;
            case 'markdown':
                $highlighted = self::highlightMarkdown($code, $theme);
                break;
            case 'css':
                $highlighted = self::highlightCSS($code, $theme);
                break;
            case 'html':
                $highlighted = self::highlightHTML($code, $theme);
                break;
            case 'php':
                $highlighted = self::highlightPHP($code, $theme);
                break;
            case 'json':
                $highlighted = self::highlightJSON($code, $theme);
                break;
            case 'yaml':
                $highlighted = self::highlightYAML($code, $theme);
                break;
            case 'sql':
                $highlighted = self::highlightSQL($code, $theme);
                break;
            case 'c':
            case 'cpp':
                $highlighted = self::highlightC($code, $theme);
                break;
            case 'java':
                $highlighted = self::highlightJava($code, $theme);
                break;
            default:
                $highlighted = self::highlightGeneric($code, $theme);
                break;
        }
        
        // Add line numbers if requested
        if ($showLineNumbers) {
            return self::addLineNumbers($highlighted, $theme);
        }
        
        return $highlighted;
    }
    
    private static function getColors($theme = 'dark') {
        if ($theme === 'dark') {
            return [
                'keyword' => '#569CD6',      // Blue
                'string' => '#CE9178',       // Orange
                'comment' => '#6A9955',      // Green
                'number' => '#B5CEA8',       // Light green
                'operator' => '#D4D4D4',     // Light gray
                'function' => '#DCDCAA',     // Yellow
                'variable' => '#9CDCFE',     // Light blue
                'type' => '#4EC9B0',         // Teal
                'constant' => '#4FC1FF',     // Bright blue
                'tag' => '#92C5F8',          // Light blue
                'attribute' => '#FFCB6B',    // Yellow
                'punctuation' => '#D4D4D4',  // Light gray
                'builtin' => '#FFCB6B'       // Yellow
            ];
        } else {
            return [
                'keyword' => '#0000FF',      // Blue
                'string' => '#008000',       // Green
                'comment' => '#008000',      // Green
                'number' => '#098658',       // Dark green
                'operator' => '#000000',     // Black
                'function' => '#795E26',     // Brown
                'variable' => '#001080',     // Dark blue
                'type' => '#267F99',         // Teal
                'constant' => '#0070C1',     // Blue
                'tag' => '#800000',          // Maroon
                'attribute' => '#FF0000',    // Red
                'punctuation' => '#000000',  // Black
                'builtin' => '#0000FF'       // Blue
            ];
        }
    }
    
    private static function highlightPython($code, $theme) {
        $colors = self::getColors($theme);
        
        // Python keywords
        $keywords = ['def', 'class', 'if', 'elif', 'else', 'for', 'while', 'try', 'except', 'finally', 
                    'import', 'from', 'as', 'return', 'yield', 'lambda', 'with', 'pass', 'break', 
                    'continue', 'and', 'or', 'not', 'in', 'is', 'True', 'False', 'None'];
        
        $builtins = ['print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'tuple', 'set'];
        
        // Comments(avoid conflicts by doing this first)
        $code = preg_replace('/^(\s*)(#(?!["\']).*?)$/m', '$1<span style="color: ' . $colors['comment'] . ';">$2</span>', $code);
        
        // Multi-line strings first
        $code = preg_replace('/(""".*?"""|\'\'\'.*?\'\'\')/s', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Single line strings
        $code = preg_replace('/(?<!")(["\'])(?:(?=(\\\\?))\\2.)*?\\1(?!")/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        // Built-ins
        foreach ($builtins as $builtin) {
            $code = preg_replace('/\b(' . preg_quote($builtin) . ')\b(?=\s*\()/', '<span style="color: ' . $colors['builtin'] . ';">$0</span>', $code);
        }
        
        // Functions
        $code = preg_replace('/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/', '<span style="color: ' . $colors['function'] . ';">$1</span>', $code);
        
        return $code;
    }
    
    private static function highlightJavaScript($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['var', 'let', 'const', 'function', 'if', 'else', 'for', 'while', 'do', 'switch', 
                    'case', 'default', 'break', 'continue', 'return', 'try', 'catch', 'finally', 
                    'throw', 'new', 'this', 'typeof', 'instanceof', 'true', 'false', 'null', 'undefined'];
        
        // Comments
        $code = preg_replace('/\/\/.*$|\/\*.*?\*\//ms', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1|`(?:[^`\\\\]|\\\\.)*`/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        // Functions
        $code = preg_replace('/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()/', '<span style="color: ' . $colors['function'] . ';">$1</span>', $code);
        
        return $code;
    }
    
    private static function highlightGo($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['package', 'import', 'func', 'var', 'const', 'type', 'struct', 'interface', 
                    'if', 'else', 'for', 'range', 'switch', 'case', 'default', 'return', 'break', 
                    'continue', 'go', 'defer', 'select', 'chan', 'map', 'true', 'false', 'nil'];
        
        $types = ['string', 'int', 'int8', 'int16', 'int32', 'int64', 'uint', 'uint8', 'uint16', 
                 'uint32', 'uint64', 'float32', 'float64', 'bool', 'byte', 'rune', 'error'];
        
        // Comments
        $code = preg_replace('/\/\/.*$|\/\*.*?\*\//ms', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1|`[^`]*`/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        // Types
        foreach ($types as $type) {
            $code = preg_replace('/\b(' . preg_quote($type) . ')\b/', '<span style="color: ' . $colors['type'] . ';">$0</span>', $code);
        }
        
        // Functions
        $code = preg_replace('/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/', '<span style="color: ' . $colors['function'] . ';">$1</span>', $code);
        
        return $code;
    }
    
    private static function highlightRust($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['fn', 'let', 'mut', 'const', 'static', 'if', 'else', 'match', 'for', 'while', 
                    'loop', 'break', 'continue', 'return', 'struct', 'enum', 'impl', 'trait', 
                    'use', 'mod', 'pub', 'crate', 'super', 'self', 'Self', 'true', 'false'];
        
        $types = ['i8', 'i16', 'i32', 'i64', 'i128', 'isize', 'u8', 'u16', 'u32', 'u64', 'u128', 
                 'usize', 'f32', 'f64', 'bool', 'char', 'str', 'String', 'Vec', 'Option', 'Result'];
        
        // Comments
        $code = preg_replace('/\/\/.*$|\/\*.*?\*\//ms', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        // Types
        foreach ($types as $type) {
            $code = preg_replace('/\b(' . preg_quote($type) . ')\b/', '<span style="color: ' . $colors['type'] . ';">$0</span>', $code);
        }
        
        // Functions
        $code = preg_replace('/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/', '<span style="color: ' . $colors['function'] . ';">$1</span>', $code);
        
        return $code;
    }
    
    private static function highlightBash($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['if', 'then', 'else', 'elif', 'fi', 'case', 'esac', 'for', 'while', 'until', 
                    'do', 'done', 'function', 'return', 'exit', 'break', 'continue', 'export', 
                    'local', 'readonly', 'declare', 'typeset', 'unset'];
        
        // Comments
        $code = preg_replace('/^(\s*)(#.*)$/m', '$1<span style="color: ' . $colors['comment'] . ';">$2</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Variables
        $code = preg_replace('/\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?|\$[0-9@*#?$!-]/', '<span style="color: ' . $colors['variable'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        return $code;
    }
    
    private static function highlightJSON($code, $theme) {
        $colors = self::getColors($theme);
        
        // Strings(keys and values)
        $code = preg_replace('/"([^"\\\\]|\\\\.)*"/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Booleans and null
        $code = preg_replace('/\b(true|false|null)\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        
        return $code;
    }
    
    private static function highlightPHP($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['class', 'function', 'if', 'else', 'elseif', 'for', 'foreach', 'while', 'do', 
                    'switch', 'case', 'default', 'break', 'continue', 'return', 'try', 'catch', 
                    'finally', 'throw', 'new', 'public', 'private', 'protected', 'static', 
                    'final', 'abstract', 'interface', 'extends', 'implements', 'namespace', 
                    'use', 'as', 'true', 'false', 'null'];
        
        // Comments
        $code = preg_replace('/\/\/.*$|\/\*.*?\*\/|#.*$/ms', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Variables
        $code = preg_replace('/\$[a-zA-Z_][a-zA-Z0-9_]*/', '<span style="color: ' . $colors['variable'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        // Functions
        $code = preg_replace('/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/', '<span style="color: ' . $colors['function'] . ';">$1</span>', $code);
        
        // PHP tags
        $code = preg_replace('/<\?php|<\?|\?>/', '<span style="color: ' . $colors['tag'] . '; font-weight: bold;">$0</span>', $code);
        
        return $code;
    }
    
    private static function highlightCSS($code, $theme) {
        $colors = self::getColors($theme);
        
        // Selectors
        $code = preg_replace('/^([^{]+)\s*\{/m', '<span style="color: ' . $colors['tag'] . ';">$1</span> {', $code);
        
        // Properties
        $code = preg_replace('/([a-zA-Z-]+)\s*:/', '<span style="color: ' . $colors['attribute'] . ';">$1</span>:', $code);
        
        // Values
        $code = preg_replace('/:\s*([^;]+);/', ': <span style="color: ' . $colors['string'] . ';">$1</span>;', $code);
        
        // Comments
        $code = preg_replace('/\/\*.*?\*\//', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        return $code;
    }
    
    private static function highlightHTML($code, $theme) {
        $colors = self::getColors($theme);
        
        // Tags
        $code = preg_replace('/<(\/?[a-zA-Z][a-zA-Z0-9]*)[^&]*?>/', '<span style="color: ' . $colors['tag'] . ';">$0</span>', $code);
        
        // Attributes
        $code = preg_replace('/([a-zA-Z-]+)=/', '<span style="color: ' . $colors['attribute'] . ';">$1</span>=', $code);
        
        // Attribute values
        $code = preg_replace('/=(["\'])([^"\']*)\1/', '=<span style="color: ' . $colors['string'] . ';">$1$2$1</span>', $code);
        
        // Comments
        $code = preg_replace('/<!--.*?-->/', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        return $code;
    }
    
    private static function highlightMarkdown($code, $theme) {
        $colors = self::getColors($theme);
        
        // Headers
        $code = preg_replace('/^(#{1,6})\s*(.*)$/m', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$1</span> <span style="color: ' . $colors['function'] . '; font-weight: bold;">$2</span>', $code);
        
        // Bold
        $code = preg_replace('/\*\*(.*?)\*\*/', '<span style="font-weight: bold;">**$1**</span>', $code);
        
        // Italic
        $code = preg_replace('/\*(.*?)\*/', '<span style="font-style: italic;">*$1*</span>', $code);
        
        // Code blocks
        $code = preg_replace('/`([^`]+)`/', '<span style="color: ' . $colors['string'] . '; background-color: rgba(255,255,255,0.1);">$0</span>', $code);
        
        // Links
        $code = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<span style="color: ' . $colors['variable'] . ';">[$1]</span><span style="color: ' . $colors['string'] . ';">($2)</span>', $code);
        
        return $code;
    }
    
    private static function highlightYAML($code, $theme) {
        $colors = self::getColors($theme);
        
        // Comments
        $code = preg_replace('/^(\s*)(#.*)$/m', '$1<span style="color: ' . $colors['comment'] . ';">$2</span>', $code);
        
        // Keys
        $code = preg_replace('/^(\s*)([a-zA-Z_][a-zA-Z0-9_-]*)\s*:/m', '$1<span style="color: ' . $colors['attribute'] . ';">$2</span>:', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Booleans and null
        $code = preg_replace('/\b(true|false|null|yes|no)\b/', '<span style="color: ' . $colors['keyword'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        return $code;
    }
    
    private static function highlightSQL($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['SELECT', 'FROM', 'WHERE', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 
                    'ALTER', 'TABLE', 'INDEX', 'JOIN', 'LEFT', 'RIGHT', 'INNER', 'OUTER', 'ON', 
                    'GROUP', 'ORDER', 'BY', 'HAVING', 'LIMIT', 'OFFSET', 'AND', 'OR', 'NOT', 
                    'IN', 'EXISTS', 'BETWEEN', 'LIKE', 'IS', 'NULL', 'TRUE', 'FALSE'];
        
        // Comments
        $code = preg_replace('/--.*$|\/\*.*?\*\//ms', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords(case insensitive)
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/i', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        return $code;
    }
    
    private static function highlightC($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default', 'break', 
                    'continue', 'return', 'goto', 'sizeof', 'typedef', 'struct', 'union', 'enum', 
                    'static', 'extern', 'auto', 'register', 'const', 'volatile', 'inline'];
        
        $types = ['void', 'char', 'short', 'int', 'long', 'float', 'double', 'signed', 'unsigned'];
        
        // Comments
        $code = preg_replace('/\/\/.*$|\/\*.*?\*\//ms', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Preprocessor directives
        $code = preg_replace('/^(\s*)(#.*)$/m', '$1<span style="color: ' . $colors['builtin'] . ';">$2</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?[fFlL]?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        // Types
        foreach ($types as $type) {
            $code = preg_replace('/\b(' . preg_quote($type) . ')\b/', '<span style="color: ' . $colors['type'] . ';">$0</span>', $code);
        }
        
        // Functions
        $code = preg_replace('/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/', '<span style="color: ' . $colors['function'] . ';">$1</span>', $code);
        
        return $code;
    }
    
    private static function highlightJava($code, $theme) {
        $colors = self::getColors($theme);
        
        $keywords = ['abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 
                    'class', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 
                    'extends', 'final', 'finally', 'float', 'for', 'goto', 'if', 'implements', 
                    'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'package', 
                    'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp', 
                    'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 
                    'try', 'void', 'volatile', 'while', 'true', 'false', 'null'];
        
        // Comments
        $code = preg_replace('/\/\/.*$|\/\*.*?\*\//ms', '<span style="color: ' . $colors['comment'] . ';">$0</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?[fFlL]?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        // Keywords
        foreach ($keywords as $keyword) {
            $code = preg_replace('/\b(' . preg_quote($keyword) . ')\b/', '<span style="color: ' . $colors['keyword'] . '; font-weight: bold;">$0</span>', $code);
        }
        
        // Functions
        $code = preg_replace('/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/', '<span style="color: ' . $colors['function'] . ';">$1</span>', $code);
        
        return $code;
    }
    
    private static function highlightGeneric($code, $theme) {
        $colors = self::getColors($theme);
        
        // Basic highlighting for unknown languages
        // Comments(# and //)
        $code = preg_replace('/^(\s*)(#.*|\/\/.*)$/m', '$1<span style="color: ' . $colors['comment'] . ';">$2</span>', $code);
        
        // Strings
        $code = preg_replace('/(["\'])(?:(?=(\\\\?))\\2.)*?\\1/', '<span style="color: ' . $colors['string'] . ';">$0</span>', $code);
        
        // Numbers
        $code = preg_replace('/\b(\d+(?:\.\d+)?)\b/', '<span style="color: ' . $colors['number'] . ';">$0</span>', $code);
        
        return $code;
    }
    
    private static function addLineNumbers($code, $theme = 'dark') {
        $colors = self::getColors($theme);
        $lines = explode("\n", $code);
        $totalLines = count($lines);
        $lineNumberWidth = strlen((string)$totalLines);
        
        $numberedLines = [];
        for ($i = 0; $i < $totalLines; $i++) {
            $lineNumber = $i + 1;
            $paddedNumber = str_pad($lineNumber, $lineNumberWidth, ' ', STR_PAD_LEFT);
            $lineNumberSpan = '<span class="line-number" style="color: #717171; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; margin-right: 1em; opacity: 0.7;">' . $paddedNumber . '</span>';
            $numberedLines[] = $lineNumberSpan . $lines[$i];
        }
        
        return implode("\n", $numberedLines);
    }
    
    public static function getStyle($theme = 'dark') {
        if ($theme === 'dark') {
            return '
                background-color: #1A1E23;
                color: #d4d4d4;
                font-family: "Consolas", "Monaco", "Courier New", monospace;
                font-size: 14px;
                line-height: 1.4;
                padding: 16px;
                border-radius: 8px;
                overflow-x: auto;
                white-space: pre;
                border: 1px solid #333;
                counter-reset: line;
            ';
        } else {
            return '
                background-color: #ffffff;
                color: #000000;
                font-family: "Consolas", "Monaco", "Courier New", monospace;
                font-size: 14px;
                line-height: 1.4;
                padding: 16px;
                border-radius: 8px;
                overflow-x: auto;
                white-space: pre;
                border: 1px solid #ccc;
                counter-reset: line;
            ';
        }
    }
}