Sicherung

This commit is contained in:
Maier Stephan SI
2023-01-20 16:09:00 +01:00
parent e5257d8413
commit b684704bf8
139 changed files with 95678 additions and 499 deletions

View File

@@ -0,0 +1,460 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Consolus;
using Scriban;
using Scriban.Functions;
using Scriban.Parsing;
using Scriban.Runtime;
using Scriban.Syntax;
namespace Kalk.Core
{
public partial class KalkEngine
{
private Stopwatch _clockReplInput;
public ConsoleRepl Repl { get; private set; }
public int OnErrorToNextLineMaxDelayInMilliseconds { get; set; }
private void InitializeRepl()
{
_clockReplInput = Stopwatch.StartNew();
OnErrorToNextLineMaxDelayInMilliseconds = 300;
OnClearScreen = ClearScreen;
Repl.GetClipboardTextImpl = GetClipboardText;
Repl.SetClipboardTextImpl = SetClipboardText;
Repl.BeforeRender = OnBeforeRendering;
Repl.GetCancellationTokenSource = () => _cancellationTokenSource;
Repl.TryPreProcessKey = TryPreProcessKey;
Repl.OnTextValidatingEnter = OnTextValidatingEnter;
Repl.Prompt.Clear();
Repl.Prompt.Begin(ConsoleStyle.BrightBlack).Append(">>> ").Append(ConsoleStyle.BrightBlack, false);
}
private bool OnTextValidatingEnter(string text, bool hasControl)
{
try
{
return OnTextValidatingEnterInternal(text, hasControl);
}
finally
{
_clockReplInput.Restart();
}
}
private bool OnTextValidatingEnterInternal(string text, bool hasControl)
{
_cancellationTokenSource = new CancellationTokenSource();
CancellationToken = _cancellationTokenSource.Token;
var elapsed = _clockReplInput.ElapsedMilliseconds;
Template script = null;
object result = null;
string error = null;
int column = -1;
bool isCancelled = false;
try
{
script = Parse(text);
if (script.HasErrors)
{
var errorBuilder = new StringBuilder();
foreach (var message in script.Messages)
{
if (errorBuilder.Length > 0) errorBuilder.AppendLine();
if (column <= 0 && message.Type == ParserMessageType.Error)
{
column = message.Span.Start.Column;
}
errorBuilder.Append(message.Message);
}
error = errorBuilder.ToString();
}
else
{
Repl.AfterEditLine.Clear();
HighlightOutput.Clear();
result = EvaluatePage(script.Page);
if (Repl.ExitOnNextEval)
{
return false;
}
}
}
catch (Exception ex)
{
if (ex is ScriptRuntimeException scriptEx)
{
column = scriptEx.Span.Start.Column;
error = scriptEx.OriginalMessage;
isCancelled = ex is ScriptAbortException;
}
else
{
error = ex.Message;
}
}
if (error != null)
{
Repl.AfterEditLine.Clear();
Repl.AfterEditLine.Append('\n');
Repl.AfterEditLine.Begin(ConsoleStyle.Red);
Repl.AfterEditLine.Append(error);
if (column >= 0 && column <= Repl.EditLine.Count)
{
Repl.EnableCursorChanged = false;
Repl.CursorIndex = column;
Repl.EnableCursorChanged = true;
}
bool emitReturnOnError = hasControl || elapsed < OnErrorToNextLineMaxDelayInMilliseconds || isCancelled;
if (emitReturnOnError)
{
Repl.AfterEditLine.Append('\n');
}
return emitReturnOnError;
}
else
{
if (result != null)
{
Write(script.Page.Span, result);
}
var resultStr = Output.ToString();
var output = Output as StringBuilderOutput;
if (output != null)
{
output.Builder.Length = 0;
}
Repl.AfterEditLine.Clear();
bool hasOutput = resultStr != string.Empty || HighlightOutput.Count > 0;
if (!Repl.IsClean || hasOutput)
{
Repl.AfterEditLine.Append('\n');
bool hasNextOutput = HighlightOutput.Count > 0;
if (hasNextOutput)
{
Repl.AfterEditLine.AddRange(HighlightOutput);
HighlightOutput.Clear();
}
if (resultStr != string.Empty)
{
if (hasNextOutput)
{
Repl.AfterEditLine.AppendLine();
}
Repl.AfterEditLine.Begin(ConsoleStyle.Bold);
Repl.AfterEditLine.Append(resultStr);
}
if (hasOutput)
{
Repl.AfterEditLine.AppendLine();
}
}
}
return true;
}
private void OnEnterNextText(string textToEnter)
{
Repl?.EnqueuePendingTextToEnter(textToEnter);
}
private bool TryPreProcessKey(ConsoleKeyInfo arg, ref int cursorIndex)
{
return OnKey(arg, Repl.EditLine, ref cursorIndex);
}
private void OnBeforeRendering()
{
UpdateSyntaxHighlighting();
}
private void UpdateSyntaxHighlighting()
{
if (Repl != null)
{
Repl.EditLine.ClearStyles();
Highlight(Repl.EditLine, Repl.CursorIndex);
}
}
public void ClearScreen()
{
Repl?.Clear();
HighlightOutput.Clear();
}
public void ReplExit()
{
if (Repl == null) return;
Repl.ExitOnNextEval = true;
}
internal bool OnKey(ConsoleKeyInfo arg, ConsoleText line, ref int cursorIndex)
{
bool resetMap = true;
bool resetCompletion = true;
try
{
bool hasShift = (arg.Modifiers & ConsoleModifiers.Shift) != 0;
// For now, we discard SHIFT entirely, as it is handled separately for range selection
// TODO: we should handle them not differently from ctrl/alt, but it's complicated
arg = new ConsoleKeyInfo(arg.KeyChar, arg.Key, false, (arg.Modifiers & ConsoleModifiers.Alt) != 0, (arg.Modifiers & ConsoleModifiers.Control) != 0);
KalkConsoleKey kalkKey = arg;
if (cursorIndex >= 0 && cursorIndex <= line.Count)
{
if (_currentShortcutKeyMap.TryGetValue(kalkKey, out var value))
{
if (value is KalkShortcutKeyMap map)
{
_currentShortcutKeyMap = map;
resetMap = false; // we don't reset if
}
else
{
var expression = (ScriptExpression) value;
var result = EvaluateExpression(expression);
if (result is KalkActionObject command)
{
// Particular case the completion action, we handle it here
if (command.Action == "completion")
{
// In case of shift we go backward
if (OnCompletionRequested(hasShift, line, ref cursorIndex))
{
resetCompletion = false;
return true;
}
}
else if (OnAction != null)
{
command.Call(OnAction);
}
}
else if (result != null)
{
var resultStr = ObjectToString(result);
line.Insert(cursorIndex, resultStr);
cursorIndex += resultStr.Length;
}
}
return true;
}
}
}
finally
{
if (resetMap)
{
// Restore the root key map in case of an error.
_currentShortcutKeyMap = Shortcuts.ShortcutKeyMap;
}
if (resetCompletion)
{
ResetCompletion();
}
}
return false;
}
private bool OnCompletionRequested(bool backward, ConsoleText line, ref int cursorIndex)
{
// Nothing to complete
if (cursorIndex == 0) return false;
// We expect to have at least:
// - one letter identifier before the before the cursor
// - no letter identifier on the cursor (e.g middle of an existing identifier)
if (cursorIndex < line.Count && IsIdentifierLetter(line[cursorIndex].Value) || !IsIdentifierLetter(line[cursorIndex - 1].Value))
{
return false;
}
// _currentIndexInCompletionMatchingList
if (_currentIndexInCompletionMatchingList < 0)
{
if (!CollectCompletionList(line, cursorIndex))
{
return false;
}
}
// Go to next word
_currentIndexInCompletionMatchingList = (_currentIndexInCompletionMatchingList + (backward ? -1 : 1));
// Wrap the result
if (_currentIndexInCompletionMatchingList >= _completionMatchingList.Count) _currentIndexInCompletionMatchingList = 0;
if (_currentIndexInCompletionMatchingList < 0) _currentIndexInCompletionMatchingList = _completionMatchingList.Count - 1;
if (_currentIndexInCompletionMatchingList < 0 || _currentIndexInCompletionMatchingList >= _completionMatchingList.Count) return false;
var index = _startIndexForCompletion;
var newText = _completionMatchingList[_currentIndexInCompletionMatchingList];
line.RemoveRangeAt(index, cursorIndex - index);
line.Insert(index, newText);
cursorIndex = index + newText.Length;
return true;
}
private void ResetCompletion()
{
_currentIndexInCompletionMatchingList = -1;
_completionMatchingList.Clear();
}
private bool CollectCompletionList(ConsoleText line, int cursorIndex)
{
var text = line.ToString();
var lexer = new Lexer(text, options: _lexerOptions);
var tokens = lexer.ToList();
// Find that we are in a correct place
var index = FindTokenIndexFromColumnIndex(cursorIndex, text.Length, tokens);
if (index >= 0)
{
// If we are in the middle of a comment/integer/float/string
// we don't expect to make any completion
var token = tokens[index];
switch (token.Type)
{
case TokenType.Comment:
case TokenType.CommentMulti:
case TokenType.Identifier:
case TokenType.IdentifierSpecial:
case TokenType.Integer:
case TokenType.HexaInteger:
case TokenType.BinaryInteger:
case TokenType.Float:
case TokenType.String:
case TokenType.ImplicitString:
case TokenType.VerbatimString:
return false;
}
}
// Look for the start of the work to complete
_startIndexForCompletion = cursorIndex - 1;
while (_startIndexForCompletion >= 0)
{
var c = text[_startIndexForCompletion];
if (!IsIdentifierLetter(c))
{
break;
}
_startIndexForCompletion--;
}
_startIndexForCompletion++;
if (!IsFirstIdentifierLetter(text[_startIndexForCompletion]))
{
return false;
}
var startTextToFind = text.Substring(_startIndexForCompletion, cursorIndex - _startIndexForCompletion);
Collect(startTextToFind, ScriptKeywords, _completionMatchingList);
Collect(startTextToFind, ValueKeywords, _completionMatchingList);
Collect(startTextToFind, Variables.Keys, _completionMatchingList);
Collect(startTextToFind, Builtins.Keys, _completionMatchingList);
// If we are not able to match anything from builtin and user variables/functions
// continue on units
if (_completionMatchingList.Count == 0)
{
Collect(startTextToFind, Units.Keys, _completionMatchingList);
}
return true;
}
private static void Collect(string startText, IEnumerable<string> keys, List<string> matchingList)
{
foreach (var key in keys.OrderBy(x => x))
{
if (key.StartsWith(startText))
{
matchingList.Add(key);
}
}
}
private static bool IsIdentifierCharacter(char c)
{
var newCategory = GetCharCategory(c);
switch (newCategory)
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.OtherLetter:
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.DecimalDigitNumber:
case UnicodeCategory.ModifierSymbol:
case UnicodeCategory.ConnectorPunctuation:
return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsFirstIdentifierLetter(char c)
{
return c == '_' || char.IsLetter(c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIdentifierLetter(char c)
{
return IsFirstIdentifierLetter(c) || char.IsDigit(c);
}
private static UnicodeCategory GetCharCategory(char c)
{
if (c == '_' || (c >= '0' && c <= '9')) return UnicodeCategory.LowercaseLetter;
c = char.ToLowerInvariant(c);
return char.GetUnicodeCategory(c);
}
}
}