using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace codelanguage; public class DebuggerService { public event EventHandler? BreakpointHit; public event EventHandler? StateChanged; public event EventHandler? OutputMessage; private readonly Interpreter _interpreter; private bool _attached; private bool _paused; private Dictionary> _breakpoints = new(); private HashSet _disabledBreakpoints = new(); private int _currentLine; private string _currentFile = ""; private Stack _callStack = new(); private List _watches = new(); private Dictionary _locals = new(); public bool IsAttached => _attached; public bool IsPaused => _paused; public int CurrentLine => _currentLine; public string CurrentFile => _currentFile; public IReadOnlyDictionary> Breakpoints => _breakpoints; public DebuggerService(Interpreter interpreter) { _interpreter = interpreter; } public void Attach() { _attached = true; _paused = false; StateChanged?.Invoke(this, new DebuggerState { IsRunning = !_paused, IsAttached = true }); } public void Detach() { _attached = false; _paused = false; StateChanged?.Invoke(this, new DebuggerState { IsRunning = true, IsAttached = false }); } public void Pause() { _paused = true; StateChanged?.Invoke(this, new DebuggerState { IsRunning = false, CurrentFile = _currentFile, CurrentLine = _currentLine }); } public void Continue() { _paused = false; StateChanged?.Invoke(this, new DebuggerState { IsRunning = true }); } public void StepOver() { _paused = false; StateChanged?.Invoke(this, new DebuggerState { IsRunning = true, StepMode = StepMode.StepOver }); } public void StepInto() { _paused = false; StateChanged?.Invoke(this, new DebuggerState { IsRunning = true, StepMode = StepMode.StepInto }); } public void StepOut() { _paused = false; StateChanged?.Invoke(this, new DebuggerState { IsRunning = true, StepMode = StepMode.StepOut }); } public void ToggleBreakpoint(string file, int line) { if (string.IsNullOrEmpty(file)) return; if (!_breakpoints.ContainsKey(file)) _breakpoints[file] = new HashSet(); if (!_breakpoints[file].Add(line)) { _breakpoints[file].Remove(line); if (_breakpoints[file].Count == 0) _breakpoints.Remove(file); } StateChanged?.Invoke(this, new DebuggerState { Breakpoints = new Dictionary>(_breakpoints) }); } public void SetBreakpointEnabled(string file, int line, bool enabled) { if (enabled) _disabledBreakpoints.Remove($"{file}:{line}"); else _disabledBreakpoints.Add($"{file}:{line}"); } public void ClearAllBreakpoints() { _breakpoints.Clear(); _disabledBreakpoints.Clear(); StateChanged?.Invoke(this, new DebuggerState { Breakpoints = new Dictionary>() }); } public void AddWatch(string expression) { if (!_watches.Contains(expression)) _watches.Add(expression); } public void RemoveWatch(string expression) => _watches.Remove(expression); public List GetWatches() => new(_watches); public List GetCallStack() { // Stack iteration goes from top to bottom, so no need to reverse return _callStack.ToList(); } public Dictionary GetLocals() { return new Dictionary(_locals); } private void OnLineNotify(string file, int line) { if (!_attached) return; _currentFile = file; _currentLine = line; var bpKey = $"{file}:{line}"; if (_breakpoints.ContainsKey(file) && _breakpoints[file].Contains(line) && !_disabledBreakpoints.Contains(bpKey)) { _paused = true; BreakpointHit?.Invoke(this, line); StateChanged?.Invoke(this, new DebuggerState { IsRunning = false, CurrentFile = file, CurrentLine = line }); return; } if (_paused) { StateChanged?.Invoke(this, new DebuggerState { IsRunning = false, CurrentFile = file, CurrentLine = line }); } } public void NotifyLine(string file, int line) => OnLineNotify(file, line); public void NotifyCall(string procName, string file, int line) { _callStack.Push(new DebuggerFrame { ProcName = procName, File = file, Line = line }); } public void NotifyReturn() { if (_callStack.Count > 0) _callStack.Pop(); } public void UpdateLocals(Dictionary locals) { _locals = locals ?? new Dictionary(); } } public class DebuggerState { public bool IsAttached { get; set; } public bool IsRunning { get; set; } = true; public StepMode StepMode { get; set; } = StepMode.None; public string CurrentFile { get; set; } = ""; public int CurrentLine { get; set; } public Dictionary> Breakpoints { get; set; } = new(); public List Watches { get; set; } = new(); public List CallStack { get; set; } = new(); public Dictionary Locals { get; set; } = new(); } public enum StepMode { None, StepOver, StepInto, StepOut } public class DebuggerFrame { public string ProcName { get; set; } = ""; public string File { get; set; } = ""; public int Line { get; set; } public override string ToString() => $"{ProcName} ({System.IO.Path.GetFileName(File)}:{Line})"; }