From dd66a7de03b864e5ee2d0a06e47db541f508523d Mon Sep 17 00:00:00 2001 From: Sun Zizhuo <211530w@student.hci.edu.sg> Date: Fri, 10 Jun 2022 19:48:52 +0800 Subject: [PATCH] added functions to repl --- mc/LanguageRepl.cs | 130 +++++++++++++++ mc/Program.cs | 127 +-------------- mc/Repl.cs | 393 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 529 insertions(+), 121 deletions(-) create mode 100644 mc/LanguageRepl.cs create mode 100644 mc/Repl.cs diff --git a/mc/LanguageRepl.cs b/mc/LanguageRepl.cs new file mode 100644 index 0000000..1baa3af --- /dev/null +++ b/mc/LanguageRepl.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Language.CodeAnalysis; +using Language.CodeAnalysis.Text; + +namespace Language +{ + internal sealed class LanguageRepl : Repl + { + private Compilation _previous; + private bool _showTree; + private bool _showProgram; + private readonly Dictionary _variables = new Dictionary(); + + protected override void RenderLine(string line) + { + var tokens = SyntaxTree.ParseTokens(line, false); + foreach (var token in tokens) + { + var isKeyword = token.Kind.ToString().EndsWith("Keyword"); + var isNumber = token.Kind == SyntaxKind.NumberToken; + if (isKeyword) + Console.ForegroundColor = ConsoleColor.Blue; + else if(!isNumber) + Console.ForegroundColor = ConsoleColor.White; + + Console.WriteLine(token.Text); + Console.ResetColor(); + } + } + + protected override void EvaluateMetaCommand(string input) + { + switch (input) + { + case "/showtree": + _showTree = !_showTree; + Console.WriteLine(_showTree ? "Now showing parse trees" : "No longer showing parse trees"); + break; + case "/showprogram": + _showProgram = !_showProgram; + Console.WriteLine(_showProgram ? "Now showing bound tree" : "No longer showing bound tree"); + break; + case "/cls": + Console.Clear(); + break; + case "/reset": + _previous = null; + _variables.Clear(); + break; + default: + base.EvaluateMetaCommand(input); + break; + } + } + + protected override bool IsCompleteSubmission(string text) + { + if (string.IsNullOrEmpty(text)) + return true; + + var syntaxTree = SyntaxTree.Parse(text); + if(syntaxTree.Diagnostics.Any()) + return false; + + return true; + } + + protected override void EvaluateSubmission(string text) + { + var syntaxTree = SyntaxTree.Parse(text); + + var compilation = _previous == null + ? new Compilation(syntaxTree) + : _previous.ContinueWith(syntaxTree); + + if (_showTree) + syntaxTree.Root.WriteTo(Console.Out); + + if (_showProgram) + compilation.EmitTree(Console.Out); + + var result = compilation.Evaluate(_variables); + + if (!result.Diagnostics.Any()) + { + Console.WriteLine(result.Value); + _previous = compilation; + } + else + { + foreach (var diagnostic in result.Diagnostics) + { + var lineIndex = syntaxTree.Text.GetLineIndex(diagnostic.Span.Start); + var line = syntaxTree.Text.Lines[lineIndex]; + var lineNumber = lineIndex + 1; + var character = diagnostic.Span.Start - line.Start + 1; + + Console.WriteLine(); + + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.Write($"({lineNumber}, {character}): "); + Console.WriteLine(diagnostic); + Console.ResetColor(); + + var prefixSpan = TextSpan.FromBounds(line.Start, diagnostic.Span.Start); + var suffixSpan = TextSpan.FromBounds(diagnostic.Span.End, line.End); + + var prefix = syntaxTree.Text.ToString(prefixSpan); + var error = syntaxTree.Text.ToString(diagnostic.Span); + var suffix = syntaxTree.Text.ToString(suffixSpan); + + Console.Write(" "); + Console.Write(prefix); + + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.Write(error); + Console.ResetColor(); + + Console.Write(suffix); + + Console.WriteLine(); + } + + Console.WriteLine(); + } + } + } +} \ No newline at end of file diff --git a/mc/Program.cs b/mc/Program.cs index bcfe8b4..7ad31ad 100644 --- a/mc/Program.cs +++ b/mc/Program.cs @@ -1,129 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Language.CodeAnalysis; -using Language.CodeAnalysis.Binding; +using Language.CodeAnalysis.Binding; using Language.CodeAnalysis.Syntax; -using Language.CodeAnalysis.Text; namespace Language { - internal class Program + internal static class Program { - private static void Main(string[] args) + private static void Main() { - var showTree = false; - var showProgram = false; - var variables = new Dictionary(); - var textBuilder = new StringBuilder(); - - Compilation previous = null; - - while(true) - { - if (textBuilder.Length == 0) - Console.Write("> "); - else - Console.Write("· "); - - var input = Console.ReadLine(); - var isBlank = string.IsNullOrWhiteSpace(input); - - if(textBuilder.Length == 0) - { - if (isBlank) - { - break; - } - else if (input == "/showtree") - { - showTree = !showTree; - Console.WriteLine(showTree ? "Now showing parse trees" : "No longer showing parse trees"); - continue; - } - else if (input == "/showprogram") - { - showProgram = !showProgram; - Console.WriteLine(showProgram ? "Now showing bound tree" : "No longer showing bound tree"); - continue; - } - else if (input == "/cls") - { - Console.Clear(); - continue; - } - else if(input == "/reset") - { - previous = null; - continue; - } - } - - textBuilder.AppendLine(input); - var text = textBuilder.ToString(); - - var syntaxTree = SyntaxTree.Parse(text); - - if (!isBlank && syntaxTree.Diagnostics.Any()) - continue; - - var compilation = previous == null ? new Compilation(syntaxTree) : previous.ContinueWith(syntaxTree); - - - if (showTree) - syntaxTree.Root.WriteTo(Console.Out); - - if (showProgram) - compilation.EmitTree(Console.Out); - - var result = compilation.Evaluate(variables); - - if (!result.Diagnostics.Any()) - { - Console.WriteLine(result.Value); - previous = compilation; - } - else - { - foreach (var diagnostic in result.Diagnostics) - { - var lineIndex = syntaxTree.Text.GetLineIndex(diagnostic.Span.Start); - var line = syntaxTree.Text.Lines[lineIndex]; - var lineNumber = lineIndex + 1; - - var character = diagnostic.Span.Start - line.Start + 1; - - Console.WriteLine(); - - Console.ForegroundColor = ConsoleColor.DarkRed; - Console.Write($"({lineNumber}, {character}): "); - Console.WriteLine(diagnostic); - Console.ResetColor(); - - var prefixSpan = TextSpan.FromBounds(line.Start, diagnostic.Span.Start); - var suffixSpan = TextSpan.FromBounds(diagnostic.Span.End, line.End); - - var prefix = syntaxTree.Text.ToString(prefixSpan); - var error = syntaxTree.Text.ToString(diagnostic.Span); - var suffix = syntaxTree.Text.ToString(suffixSpan); - - Console.Write(" "); - Console.Write(prefix); - - Console.ForegroundColor = ConsoleColor.DarkRed; - Console.Write(error); - Console.ResetColor(); - - Console.Write(suffix); - Console.WriteLine(); - } - - Console.WriteLine(); - } - - textBuilder.Clear(); - } + var repl = new LanguageRepl(); + repl.Run(); } } -} +} \ No newline at end of file diff --git a/mc/Repl.cs b/mc/Repl.cs new file mode 100644 index 0000000..99a6885 --- /dev/null +++ b/mc/Repl.cs @@ -0,0 +1,393 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Text; + +namespace Language +{ + internal abstract class Repl + { + private List _submissionHistory = new List(); + private int _submissionHistoryIndex; + private bool _done; + + public void Run() + { + while (true) + { + var text = EditSubmission(); + + if (string.IsNullOrEmpty(text)) + return; + + if(!text.Contains(Environment.NewLine) && text.StartsWith("/")) + EvaluateMetaCommand(text); + else + EvaluateSubmission(text); + + _submissionHistory.Add(text); + _submissionHistoryIndex = 0; + } + } + + private sealed class SubmissionView + { + private readonly Action _lineRenderer; + private readonly ObservableCollection _submissionDocument; + private readonly int _cursorTop; + private int _renderedLineCount; + private int _currentLine; + private int _currentCharacter; + + public SubmissionView(Action lineRenderer, ObservableCollection submissionDocument) + { + _lineRenderer = lineRenderer; + _submissionDocument = submissionDocument; + _submissionDocument.CollectionChanged += SubmissionDocumentChanged; + _cursorTop = Console.CursorTop; + Render(); + } + + private void SubmissionDocumentChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Render(); + } + + private void Render() + { + Console.CursorVisible = false; + + var lineCount = 0; + + foreach (var line in _submissionDocument) + { + Console.SetCursorPosition(0, _cursorTop + lineCount); + + if (lineCount == 0) + Console.Write("> "); + else + Console.Write("· "); + + _lineRenderer(line); + Console.WriteLine(new string(' ', Console.WindowWidth - line.Length)); + lineCount++; + } + + var blankLineNumber = _renderedLineCount - lineCount; + + if (blankLineNumber > 0) + { + var blankLine = new string(' ', Console.WindowWidth); + for(int i = 0; i < blankLineNumber; i++) + { + Console.SetCursorPosition(0, _cursorTop + lineCount + i); + Console.WriteLine(blankLine); + } + } + + _renderedLineCount = lineCount; + Console.CursorVisible = true; + UpdateCursorPosition(); + } + + private void UpdateCursorPosition() + { + Console.CursorTop = _cursorTop + _currentLine; + Console.CursorLeft = 2 + _currentCharacter; + } + + public int CurrentLine + { + get => _currentLine; + set + { + if(_currentCharacter != value) + { + _currentLine = value; + _currentCharacter = Math.Min(_submissionDocument[_currentLine].Length, _currentCharacter); + + UpdateCursorPosition(); + } + + } + } + + public int CurrentCharacter + { + get => _currentCharacter; + set + { + if(_currentCharacter != value) + { + _currentCharacter = value; + UpdateCursorPosition(); + } + + } + } + } + + private string EditSubmission() + { + _done = false; + + var document = new ObservableCollection() { "" }; + var view = new SubmissionView(RenderLine, document); + + while (!_done) + { + var key = Console.ReadKey(true); + HandleKey(key, document, view); + } + + view.CurrentLine = document.Count - 1; + view.CurrentCharacter = document[view.CurrentLine].Length; + + Console.WriteLine(); + + return string.Join(Environment.NewLine, document); + } + + private void HandleKey(ConsoleKeyInfo key, ObservableCollection document, SubmissionView view) + { + if(key.Modifiers == default) + { + switch (key.Key) + { + case ConsoleKey.Escape: + HandleEscape(document, view); + break; + case ConsoleKey.Enter: + HandleEnter(document, view); + break; + case ConsoleKey.LeftArrow: + HandleLeftArrow(document, view); + break; + case ConsoleKey.RightArrow: + HandleRightArrow(document, view); + break; + case ConsoleKey.UpArrow: + HandleUpArrow(document, view); + break; + case ConsoleKey.DownArrow: + HandleDownArrow(document, view); + break; + case ConsoleKey.Backspace: + HandleBackspace(document, view); + break; + case ConsoleKey.Delete: + HandleDelete(document, view); + break; + case ConsoleKey.Home: + HandleHome(document, view); + break; + case ConsoleKey.End: + HandleEnd(document, view); + break; + case ConsoleKey.Tab: + HandleTab(document, view); + break; + case ConsoleKey.PageUp: + HandlePageUp(document, view); + break; + case ConsoleKey.PageDown: + HandlePageDown(document, view); + break; + } + } + else if(key.Modifiers == ConsoleModifiers.Control) + { + switch (key.Key) + { + case ConsoleKey.Enter: + HandleControlEnter(document, view); + break; + } + } + + if (key.KeyChar >= ' ') + HandleTyping(document, view, key.KeyChar.ToString()); + } + + private void HandleEscape(ObservableCollection document, SubmissionView view) + { + document[view.CurrentLine] = String.Empty; + view.CurrentCharacter = 0; + } + + private void HandleEnter(ObservableCollection document, SubmissionView view) + { + var submissionText = string.Join(Environment.NewLine, document); + if (submissionText.StartsWith("/") || IsCompleteSubmission(submissionText)) + { + _done = true; + return; + } + + InsertLine(document, view); + } + + private void HandleControlEnter(ObservableCollection document, SubmissionView view) + { + InsertLine(document, view); + } + + private void HandleLeftArrow(ObservableCollection document, SubmissionView view) + { + if(view.CurrentCharacter > 0) + view.CurrentCharacter--; + } + private static void InsertLine(ObservableCollection document, SubmissionView view) + { + var remainder = document[view.CurrentLine].Substring(view.CurrentCharacter); + document[view.CurrentLine] = document[view.CurrentLine].Substring(0, view.CurrentCharacter); + var lineIndex = view.CurrentLine + 1; + + document.Insert(lineIndex, remainder); + view.CurrentCharacter = 0; + view.CurrentLine = lineIndex; + } + + private void HandleRightArrow(ObservableCollection document, SubmissionView view) + { + var line = document[view.CurrentLine]; + if (view.CurrentCharacter <= line.Length - 1) + view.CurrentCharacter++; + } + + private void HandleUpArrow(ObservableCollection document, SubmissionView view) + { + if(view.CurrentLine > 0) + view.CurrentLine--; + } + + private void HandleDownArrow(ObservableCollection document, SubmissionView view) + { + if(view.CurrentLine < document.Count - 1) + view.CurrentLine++; + } + + private void HandleBackspace(ObservableCollection document, SubmissionView view) + { + var start = view.CurrentCharacter; + if (start == 0) + { + if (view.CurrentLine == 0) + return; + + var currentLine = document[view.CurrentLine]; + var previousLine = document[view.CurrentLine - 1]; + document.RemoveAt(view.CurrentLine); + view.CurrentLine--; + document[view.CurrentLine] = previousLine + currentLine; + view.CurrentCharacter = previousLine.Length; + } + else + { + var lineIndex = view.CurrentLine; + var line = document[lineIndex]; + var before = line.Substring(0, start - 1); + var after = line.Substring(start); + + document[lineIndex] = before + after; + view.CurrentCharacter--; + } + + + } + + private void HandleDelete(ObservableCollection document, SubmissionView view) + { + var lineIndex = view.CurrentLine; + var line = document[lineIndex]; + var start = view.CurrentCharacter; + if (start >= line.Length) + return; + + var before = line.Substring(0, start); + var after = line.Substring(start + 1); + + document[lineIndex] = before + after; + } + private void HandleHome(ObservableCollection document, SubmissionView view) + { + view.CurrentCharacter = 0; + } + + private void HandleEnd(ObservableCollection document, SubmissionView view) + { + view.CurrentCharacter = document[view.CurrentLine].Length; + } + + private void HandleTab(ObservableCollection document, SubmissionView view) + { + const int TabWidth = 4; + var start = view.CurrentCharacter; + var remainingSpaces = TabWidth - start % TabWidth; + var line = document[view.CurrentLine]; + document[view.CurrentLine] = line.Insert(start, new string(' ', remainingSpaces)); + view.CurrentCharacter += remainingSpaces; + } + private void HandlePageUp(ObservableCollection document, SubmissionView view) + { + _submissionHistoryIndex--; + if (_submissionHistoryIndex < 0) + _submissionHistoryIndex = _submissionHistory.Count - 1; + + UpdateDocumentFromHistory(document, view); + } + + private void HandlePageDown(ObservableCollection document, SubmissionView view) + { + _submissionHistoryIndex++; + if (_submissionHistoryIndex > _submissionHistory.Count - 1) + _submissionHistoryIndex = 0; + + UpdateDocumentFromHistory(document, view); + } + + private void UpdateDocumentFromHistory(ObservableCollection document, SubmissionView view) + { + document.Clear(); + + var historyItem = _submissionHistory[_submissionHistoryIndex]; + var lines = historyItem.Split(Environment.NewLine); + + foreach(var line in lines) + document.Add(line); + + view.CurrentLine = document.Count - 1; + view.CurrentCharacter = document[view.CurrentLine].Length; + } + + private void HandleTyping(ObservableCollection document, SubmissionView view, string text) + { + var lineIndex = view.CurrentLine; + var start = view.CurrentCharacter; + + document[lineIndex] = document[lineIndex].Insert(start, text); + view.CurrentCharacter += text.Length; + } + + protected void ClearHistory() + { + _submissionHistory.Clear(); + } + + protected virtual void RenderLine(string line) + { + Console.Write(line); + } + + protected virtual void EvaluateMetaCommand(string input) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Invalid command {input}"); + Console.ResetColor(); + } + + protected abstract bool IsCompleteSubmission(string text); + protected abstract void EvaluateSubmission(string text); + } +} \ No newline at end of file