From c40d1f45e06fb6297b76f672051714d14a96fff2 Mon Sep 17 00:00:00 2001 From: Stone-Red-Code <56473591+Stone-Red-Code@users.noreply.github.com> Date: Fri, 8 Oct 2021 12:48:48 +0200 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 334 ++++++++++++++ README.md | 2 + YesNt-Interpreter.sln | 31 ++ .../Attributes/StatementAttribute.cs | 25 + .../Attributes/StaticStatementAttribute.cs | 13 + YesNt-Interpreter/Enums/Priority.cs | 12 + YesNt-Interpreter/Enums/SearchMode.cs | 10 + YesNt-Interpreter/Enums/SpaceAround.cs | 10 + YesNt-Interpreter/Program.cs | 12 + .../Properties/launchSettings.json | 13 + YesNt-Interpreter/Runtime/DebugEventArgs.cs | 11 + .../Runtime/RuntimeInformation.cs | 65 +++ .../Runtime/StatementInformation.cs | 12 + .../Runtime/StatementRuntimeInfo.cs | 7 + YesNt-Interpreter/Runtime/YesNtInterpreter.cs | 203 +++++++++ YesNt-Interpreter/Statements.cs | 199 ++++++++ YesNt-Interpreter/Utilities/Evaluator.cs | 168 +++++++ .../Utilities/StringExtentions.cs | 53 +++ YesNt-Interpreter/YesNt.Interpreter.csproj | 11 + YesNt-Interpreter/YesNtInterpreter_OLD.cs | 357 +++++++++++++++ YesNt.CodeEditor/Editor.cs | 426 ++++++++++++++++++ YesNt.CodeEditor/Program.cs | 29 ++ YesNt.CodeEditor/SyntaxHighlighter.cs | 107 +++++ YesNt.CodeEditor/YesNt.CodeEditor.csproj | 12 + 25 files changed, 2124 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 YesNt-Interpreter.sln create mode 100644 YesNt-Interpreter/Attributes/StatementAttribute.cs create mode 100644 YesNt-Interpreter/Attributes/StaticStatementAttribute.cs create mode 100644 YesNt-Interpreter/Enums/Priority.cs create mode 100644 YesNt-Interpreter/Enums/SearchMode.cs create mode 100644 YesNt-Interpreter/Enums/SpaceAround.cs create mode 100644 YesNt-Interpreter/Program.cs create mode 100644 YesNt-Interpreter/Properties/launchSettings.json create mode 100644 YesNt-Interpreter/Runtime/DebugEventArgs.cs create mode 100644 YesNt-Interpreter/Runtime/RuntimeInformation.cs create mode 100644 YesNt-Interpreter/Runtime/StatementInformation.cs create mode 100644 YesNt-Interpreter/Runtime/StatementRuntimeInfo.cs create mode 100644 YesNt-Interpreter/Runtime/YesNtInterpreter.cs create mode 100644 YesNt-Interpreter/Statements.cs create mode 100644 YesNt-Interpreter/Utilities/Evaluator.cs create mode 100644 YesNt-Interpreter/Utilities/StringExtentions.cs create mode 100644 YesNt-Interpreter/YesNt.Interpreter.csproj create mode 100644 YesNt-Interpreter/YesNtInterpreter_OLD.cs create mode 100644 YesNt.CodeEditor/Editor.cs create mode 100644 YesNt.CodeEditor/Program.cs create mode 100644 YesNt.CodeEditor/SyntaxHighlighter.cs create mode 100644 YesNt.CodeEditor/YesNt.CodeEditor.csproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d13c54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,334 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2367b1 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# YesNt + diff --git a/YesNt-Interpreter.sln b/YesNt-Interpreter.sln new file mode 100644 index 0000000..2f72010 --- /dev/null +++ b/YesNt-Interpreter.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31410.357 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YesNt.Interpreter", "YesNt-Interpreter\YesNt.Interpreter.csproj", "{1D79AC8F-9FC7-4B4C-9899-8DAF2B630030}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YesNt.CodeEditor", "YesNt.CodeEditor\YesNt.CodeEditor.csproj", "{33BDA036-A37E-475E-AACF-8ED96E31BA1A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D79AC8F-9FC7-4B4C-9899-8DAF2B630030}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D79AC8F-9FC7-4B4C-9899-8DAF2B630030}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D79AC8F-9FC7-4B4C-9899-8DAF2B630030}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D79AC8F-9FC7-4B4C-9899-8DAF2B630030}.Release|Any CPU.Build.0 = Release|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8597E655-80C0-48F2-AEB1-4A279F5173D6} + EndGlobalSection +EndGlobal diff --git a/YesNt-Interpreter/Attributes/StatementAttribute.cs b/YesNt-Interpreter/Attributes/StatementAttribute.cs new file mode 100644 index 0000000..4353100 --- /dev/null +++ b/YesNt-Interpreter/Attributes/StatementAttribute.cs @@ -0,0 +1,25 @@ +using System; + +using YesNt.Interpreter.Enums; + +namespace YesNt.Interpreter.Attributes +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + internal class StatementAttribute : Attribute + { + public string Name { get; } + public SearchMode SearchMode { get; } + public SpaceAround SpaceAround { get; } + public Priority Priority { get; set; } = Priority.Normal; + public bool ExecuteInSearchLabelMode { get; set; } + public bool KeepStatementInArgs { get; set; } + public bool IgnoreSyntaxHighlighting { get; set; } + + internal StatementAttribute(string name, SearchMode searchMode, SpaceAround spaceAround) + { + Name = name; + SearchMode = searchMode; + SpaceAround = spaceAround; + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Attributes/StaticStatementAttribute.cs b/YesNt-Interpreter/Attributes/StaticStatementAttribute.cs new file mode 100644 index 0000000..9b3da5e --- /dev/null +++ b/YesNt-Interpreter/Attributes/StaticStatementAttribute.cs @@ -0,0 +1,13 @@ +using System; + +using YesNt.Interpreter.Enums; + +namespace YesNt.Interpreter.Attributes +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + internal class StaticStatementAttribute : Attribute + { + public bool ExecuteInSearchLabelMode { get; set; } + public Priority Priority { get; set; } = Priority.Normal; + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Enums/Priority.cs b/YesNt-Interpreter/Enums/Priority.cs new file mode 100644 index 0000000..013dc9e --- /dev/null +++ b/YesNt-Interpreter/Enums/Priority.cs @@ -0,0 +1,12 @@ +namespace YesNt.Interpreter.Enums +{ + internal enum Priority + { + Highest, + VeryHigh, + High, + Normal, + Low, + VeryLow + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Enums/SearchMode.cs b/YesNt-Interpreter/Enums/SearchMode.cs new file mode 100644 index 0000000..0e083b8 --- /dev/null +++ b/YesNt-Interpreter/Enums/SearchMode.cs @@ -0,0 +1,10 @@ +namespace YesNt.Interpreter.Enums +{ + public enum SearchMode + { + StartOfLine, + EndOfLine, + Contains, + Exact + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Enums/SpaceAround.cs b/YesNt-Interpreter/Enums/SpaceAround.cs new file mode 100644 index 0000000..c16fd85 --- /dev/null +++ b/YesNt-Interpreter/Enums/SpaceAround.cs @@ -0,0 +1,10 @@ +namespace YesNt.Interpreter.Enums +{ + public enum SpaceAround + { + StartEnd, + Start, + End, + None + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Program.cs b/YesNt-Interpreter/Program.cs new file mode 100644 index 0000000..1923d00 --- /dev/null +++ b/YesNt-Interpreter/Program.cs @@ -0,0 +1,12 @@ +namespace YesNt.Interpreter +{ + internal class Program + { + private static void Main(string[] args) + { + YesNtInterpreter interpreter = new YesNtInterpreter(); + interpreter.Initialize(); + interpreter.Execute(args[0]); + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Properties/launchSettings.json b/YesNt-Interpreter/Properties/launchSettings.json new file mode 100644 index 0000000..f380490 --- /dev/null +++ b/YesNt-Interpreter/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "YesNt-Interpreter": { + "commandName": "Project", + "commandLineArgs": "code.ynt" + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": {}, + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Runtime/DebugEventArgs.cs b/YesNt-Interpreter/Runtime/DebugEventArgs.cs new file mode 100644 index 0000000..ab99766 --- /dev/null +++ b/YesNt-Interpreter/Runtime/DebugEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace YesNt.Interpreter.Runtime +{ + public class DebugEventArgs : EventArgs + { + public int LineNumber { get; internal set; } + public string CurrentLine { get; internal set; } + public string OriginalLine { get; internal set; } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Runtime/RuntimeInformation.cs b/YesNt-Interpreter/Runtime/RuntimeInformation.cs new file mode 100644 index 0000000..f71c759 --- /dev/null +++ b/YesNt-Interpreter/Runtime/RuntimeInformation.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; + +using YesNt.Interpreter.Utilities; + +namespace YesNt.Interpreter +{ + internal class RuntimeInformation + { + public Dictionary Variables { get; } = new(); + public Dictionary Labels { get; } = new(); + public List Lines { get; set; } = new(); + public string CurrentLine { get; set; } = string.Empty; + public Stack LabelStack { get; set; } = new(); + public string SearchLabel { get; set; } = string.Empty; + public int LineNumber { get; set; } = 0; + public bool Stop { get; private set; } = false; + public bool IsDebugMode { get; set; } + + public event Action OnDebugOutput; + + public void WriteLine(string output) + { + if (IsDebugMode) + { + OnDebugOutput?.Invoke(output.FromSaveString() + Environment.NewLine); + } + else + { + Console.WriteLine(output.FromSaveString()); + } + } + + public void Write(string output) + { + if (IsDebugMode) + { + OnDebugOutput?.Invoke(output.FromSaveString()); + } + else + { + Console.Write(output.FromSaveString()); + } + } + + public void Exit(string message) + { + WriteLine($"{Environment.NewLine}[The process was terminated at line {LineNumber + 1} with the message: {message}]"); + Stop = true; + } + + public void Reset() + { + CurrentLine = string.Empty; + Lines.Clear(); + Variables.Clear(); + Labels.Clear(); + LabelStack.Clear(); + SearchLabel = string.Empty; + Stop = false; + IsDebugMode = false; + LineNumber = 0; + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Runtime/StatementInformation.cs b/YesNt-Interpreter/Runtime/StatementInformation.cs new file mode 100644 index 0000000..617e03e --- /dev/null +++ b/YesNt-Interpreter/Runtime/StatementInformation.cs @@ -0,0 +1,12 @@ +using YesNt.Interpreter.Enums; + +namespace YesNt.Interpreter.Runtime +{ + public class StatementInformation + { + public string Name { get; internal set; } + public SearchMode SearchMode { get; internal set; } + public SpaceAround SpaceAround { get; internal set; } + public bool IgnoreSyntaxHighlighting { get; internal set; } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Runtime/StatementRuntimeInfo.cs b/YesNt-Interpreter/Runtime/StatementRuntimeInfo.cs new file mode 100644 index 0000000..32faf4a --- /dev/null +++ b/YesNt-Interpreter/Runtime/StatementRuntimeInfo.cs @@ -0,0 +1,7 @@ +namespace YesNt.Interpreter +{ + internal abstract class StatementRuntimeInformation + { + public RuntimeInformation RuntimeInfo { get; set; } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Runtime/YesNtInterpreter.cs b/YesNt-Interpreter/Runtime/YesNtInterpreter.cs new file mode 100644 index 0000000..c520cb3 --- /dev/null +++ b/YesNt-Interpreter/Runtime/YesNtInterpreter.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reflection; + +using YesNt.Interpreter.Attributes; +using YesNt.Interpreter.Enums; +using YesNt.Interpreter.Runtime; +using YesNt.Interpreter.Utilities; + +namespace YesNt.Interpreter +{ + public class YesNtInterpreter + { + private readonly RuntimeInformation runtimeInfo = new RuntimeInformation(); + private Dictionary> statements = new(); + private List> staticStatements = new(); + + public ReadOnlyCollection StatementInformation + { + get + { + List informations = statements.Select(s => + { + return new StatementInformation() + { + Name = s.Key.Name, + SearchMode = s.Key.SearchMode, + SpaceAround = s.Key.SpaceAround, + IgnoreSyntaxHighlighting = s.Key.IgnoreSyntaxHighlighting + }; + }).ToList(); + return new ReadOnlyCollection(informations); + } + } + + public event Action OnLineExecuted; + + public event Action OnDebugOutput; + + public void Initialize() + { + Assembly assembly = Assembly.GetExecutingAssembly(); + Type[] types = assembly.GetTypes(); + + IEnumerable statementRuntimeInfos = types.Where(t => t.IsSubclassOf(typeof(StatementRuntimeInformation))); + + statements.Clear(); + + foreach (Type type in statementRuntimeInfos) + { + object statementInfo = Activator.CreateInstance(type); + + MethodInfo[] methodInfos = statementInfo.GetType().GetMethods(); + + StatementRuntimeInformation statementRuntimeInfo = statementInfo as StatementRuntimeInformation; + statementRuntimeInfo.RuntimeInfo = runtimeInfo; + + foreach (MethodInfo methodInfo in methodInfos) + { + StatementAttribute statementAttribute = methodInfo.GetCustomAttribute(); + if (statementAttribute is not null) + { + Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; + statements.Add(statementAttribute, method); + } + + StaticStatementAttribute staticStatementAttribute = methodInfo.GetCustomAttribute(); + if (staticStatementAttribute is not null) + { + Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; + staticStatements.Add(new(staticStatementAttribute, method)); + } + } + } + + statements = statements.OrderBy(s => s.Key.Priority).ToDictionary(x => x.Key, x => x.Value); + staticStatements = staticStatements.OrderBy(s => s.Key.Priority).ToList(); + + runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); + } + + public void Execute(string path, bool isDebugMode = false) + { + runtimeInfo.Reset(); + runtimeInfo.IsDebugMode = isDebugMode; + LoadFile(path); + + for (runtimeInfo.LineNumber = 0; runtimeInfo.LineNumber < runtimeInfo.Lines.Count; runtimeInfo.LineNumber++) + { + if (runtimeInfo.Stop) + { + break; + } + + runtimeInfo.CurrentLine = runtimeInfo.Lines[runtimeInfo.LineNumber].TrimEnd().Replace("\r", string.Empty); + + DebugEventArgs debugEventArgs = new DebugEventArgs() + { + LineNumber = runtimeInfo.LineNumber + 1, + OriginalLine = runtimeInfo.CurrentLine.FromSaveString() + }; + + if (string.IsNullOrWhiteSpace(runtimeInfo.CurrentLine)) + { + continue; + } + + foreach (KeyValuePair staticStatement in staticStatements) + { + StaticStatementAttribute staticStatementAttribute = staticStatement.Key; + if (staticStatementAttribute.ExecuteInSearchLabelMode == false && !string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel)) + { + continue; + } + + staticStatement.Value.Invoke(); + } + + bool statementFound = false; + bool searchingLabel = string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel); + + foreach (KeyValuePair> statement in statements) + { + StatementAttribute statementAttribute = statement.Key; + + if (statementAttribute.ExecuteInSearchLabelMode == false && !string.IsNullOrWhiteSpace(runtimeInfo.SearchLabel)) + { + statementFound = true; + continue; + } + + if (runtimeInfo.Stop) + { + break; + } + + string name = statementAttribute.SpaceAround switch + { + SpaceAround.StartEnd => $" {statementAttribute.Name.Trim()} ", + SpaceAround.Start => $" {statementAttribute.Name.Trim()}", + SpaceAround.End => $"{statementAttribute.Name.Trim()} ", + _ => statementAttribute.Name + }; + + if (statementAttribute.SearchMode == SearchMode.StartOfLine && runtimeInfo.CurrentLine.StartsWith(name)) + { + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(0, name.Length); + statement.Value.Invoke(copyLine); + statementFound = true; + } + else if (statementAttribute.SearchMode == SearchMode.Contains && $"{runtimeInfo.CurrentLine} ".Contains(name)) + { + runtimeInfo.CurrentLine = $"{runtimeInfo.CurrentLine} "; + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Replace(name, string.Empty); + statement.Value.Invoke(copyLine); + statementFound = true; + } + else if (statementAttribute.SearchMode == SearchMode.EndOfLine && runtimeInfo.CurrentLine.EndsWith(name)) + { + string copyLine = statementAttribute.KeepStatementInArgs ? runtimeInfo.CurrentLine : runtimeInfo.CurrentLine.Remove(runtimeInfo.CurrentLine.Length - name.Length); + statement.Value.Invoke(copyLine); + statementFound = true; + } + else if (statementAttribute.SearchMode == SearchMode.Exact && runtimeInfo.CurrentLine.Equals(name)) + { + statement.Value.Invoke(runtimeInfo.CurrentLine); + statementFound = true; + } + } + + if (!statementFound) + { + runtimeInfo.Exit("Invalid statement"); + } + if (isDebugMode && searchingLabel) + { + debugEventArgs.CurrentLine = runtimeInfo.CurrentLine.FromSaveString(); + OnLineExecuted?.Invoke(debugEventArgs); + } + } + + if (runtimeInfo.Stop == false) + { + runtimeInfo.Exit("End of file"); + } + } + + private void LoadFile(string path) + { + if (!File.Exists(path)) + { + runtimeInfo.WriteLine($"File \"{path}\" not found!"); + return; + } + + IEnumerable lines = File.ReadAllLines(path); + runtimeInfo.Lines.AddRange(lines); + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Statements.cs b/YesNt-Interpreter/Statements.cs new file mode 100644 index 0000000..1958c56 --- /dev/null +++ b/YesNt-Interpreter/Statements.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +using YesNt.Interpreter.Attributes; +using YesNt.Interpreter.Enums; +using YesNt.Interpreter.Utilities; + +namespace YesNt.Interpreter +{ + internal class Statements : StatementRuntimeInformation + { + [Statement("cwl", SearchMode.StartOfLine, SpaceAround.End, Priority = Priority.VeryLow)] + public void WriteLine(string args) + { + RuntimeInfo.WriteLine(args); + } + + [Statement("cw", SearchMode.StartOfLine, SpaceAround.End, Priority = Priority.VeryLow)] + public void Write(string args) + { + RuntimeInfo.Write(args); + } + + [Statement("%crl", SearchMode.Contains, SpaceAround.End, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void ReadLine(string args) + { + args += " "; + while (args.Contains("%crl ")) + { + args = args.ReplaceFirstOccurrence("%crl ", Console.ReadLine().ToSaveString() + " "); + } + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("%cr", SearchMode.Contains, SpaceAround.End, KeepStatementInArgs = true, Priority = Priority.Highest)] + public void ReadKey(string args) + { + args += " "; + while (args.Contains("%cr ")) + { + args = args.ReplaceFirstOccurrence("%cr ", Console.ReadKey().KeyChar.ToString().ToSaveString() + " "); + } + RuntimeInfo.CurrentLine = args.TrimEnd(); + } + + [Statement("jmp", SearchMode.StartOfLine, SpaceAround.End)] + public void Jump(string args) + { + string key = args.Trim(); + RuntimeInfo.LabelStack.Push(RuntimeInfo.LineNumber); + + if (RuntimeInfo.Labels.ContainsKey(key)) + { + RuntimeInfo.LineNumber = RuntimeInfo.Labels[key]; + } + else + { + RuntimeInfo.SearchLabel = key; + } + } + + [Statement("jif", SearchMode.StartOfLine, SpaceAround.End)] + public void JumpIf(string args) + { + string[] parts = args.Split('|'); + if (parts.Length != 2) + { + RuntimeInfo.Exit("Invalid syntax"); + return; + } + + string key = parts[0].Trim(); + string condition = parts[1].Trim(); + + bool? result = Evaluator.EvaluateCondition(condition); + + if (result != true) + { + if (result is null) + { + RuntimeInfo.Exit("Invalid operation"); + } + return; + } + + RuntimeInfo.LabelStack.Push(RuntimeInfo.LineNumber); + + if (RuntimeInfo.Labels.ContainsKey(key)) + { + RuntimeInfo.LineNumber = RuntimeInfo.Labels[key]; + } + else + { + RuntimeInfo.SearchLabel = key; + } + } + + [Statement("lbl", SearchMode.StartOfLine, SpaceAround.End, ExecuteInSearchLabelMode = true)] + public void FindLabel(string args) + { + string key = args.Trim(); + if (RuntimeInfo.Labels.ContainsKey(key)) + { + RuntimeInfo.Labels[key] = RuntimeInfo.LineNumber; + } + else + { + RuntimeInfo.Labels.Add(key, RuntimeInfo.LineNumber); + } + + if (!string.IsNullOrWhiteSpace(RuntimeInfo.SearchLabel) && RuntimeInfo.SearchLabel == key) + { + RuntimeInfo.SearchLabel = string.Empty; + } + } + + [Statement("ret", SearchMode.Exact, SpaceAround.None)] + public void Return(string _) + { + if (RuntimeInfo.LabelStack.Count > 0) + { + RuntimeInfo.LineNumber = RuntimeInfo.LabelStack.Pop(); + } + else + { + RuntimeInfo.Exit("No label in stack"); + } + } + + [Statement("end", SearchMode.Exact, SpaceAround.None)] + public void End(string _) + { + RuntimeInfo.Exit("Planned termination by code"); + } + + [Statement("!calc", SearchMode.EndOfLine, SpaceAround.Start, Priority = Priority.High, ExecuteInSearchLabelMode = true)] + public void Calculate(string args) + { + MatchCollection matches = Regex.Matches(args, @"((\)?)+(\(?)+[0-9]+(((\s?)+(\)?)(\s?)+\+(\s?)+(\(?)+(\s?)+|(\s?)+(\)?)(\s?)+\-(\s?)+(\(?)+(\s?)+|(\s?)+(\)?)(\s?)+\*(\s?)+(\(?)+(\s?)+|(\s?)+(\)?)(\s?)+\/(\s?)+(\(?)+(\s?)+)|[,.])(?=[0-9])+)+[0-9]+(\)?)+"); + + for (int i = 0; i < matches.Count; i++) + { + string res = Evaluator.Calculate(matches[i].Value); + if (res is null) + { + RuntimeInfo.Exit("Invalid operation"); + return; + } + args = args.Replace(matches[0].Value, res); + } + + RuntimeInfo.CurrentLine = args; + } + + [Statement("!eval", SearchMode.EndOfLine, SpaceAround.Start, Priority = Priority.VeryHigh, ExecuteInSearchLabelMode = true)] + public void Evaluate(string args) + { + RuntimeInfo.CurrentLine = args.FromSaveString(); + } + + [Statement("<", SearchMode.StartOfLine, SpaceAround.None, Priority = Priority.VeryLow, IgnoreSyntaxHighlighting = true)] + public void DefineVariable(string args) + { + string[] parts = args.Split('='); + if (parts.Length == 2) + { + string key = parts[0].Replace("<", "").Trim(); + if (RuntimeInfo.Variables.ContainsKey(key)) + { + RuntimeInfo.Variables[key] = parts[1].Trim(); + } + else + { + RuntimeInfo.Variables.Add(key, parts[1].Trim()); + } + } + else + { + RuntimeInfo.Exit("Invalid syntax"); + return; + } + } + + [StaticStatement(ExecuteInSearchLabelMode = true)] + public void ReadVariable() + { + if (!RuntimeInfo.CurrentLine.Contains('>')) + { + return; + } + + foreach (KeyValuePair variable in RuntimeInfo.Variables) + { + RuntimeInfo.CurrentLine = RuntimeInfo.CurrentLine.Replace($">{variable.Key}", variable.Value); + } + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Utilities/Evaluator.cs b/YesNt-Interpreter/Utilities/Evaluator.cs new file mode 100644 index 0000000..581fb71 --- /dev/null +++ b/YesNt-Interpreter/Utilities/Evaluator.cs @@ -0,0 +1,168 @@ +using System.Text.RegularExpressions; + +namespace YesNt.Interpreter.Utilities +{ + internal static class Evaluator + { + public static bool? EvaluateCondition(string input) + { + string[] parts = input.Split("=="); + if (parts.Length == 2) + { + string part1 = parts[0].FromSaveString().Trim(); + string part2 = parts[1].FromSaveString().Trim(); + return part1 == part2; + } + + parts = input.Split("!="); + if (parts.Length == 2) + { + string part1 = parts[0].FromSaveString().Trim(); + string part2 = parts[1].FromSaveString().Trim(); + return part1 != part2; + } + + parts = input.Split(">"); + if (parts.Length == 2) + { + bool succ1 = parts[0].ToStandardizedNumber(out double part1); + bool succ2 = parts[1].ToStandardizedNumber(out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 > part2; + } + + parts = input.Split("<"); + if (parts.Length == 2) + { + bool succ1 = parts[0].ToStandardizedNumber(out double part1); + bool succ2 = parts[1].ToStandardizedNumber(out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 < part2; + } + + parts = input.Split(">="); + if (parts.Length == 2) + { + bool succ1 = parts[0].ToStandardizedNumber(out double part1); + bool succ2 = parts[1].ToStandardizedNumber(out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 >= part2; + } + + parts = input.Split("<="); + if (parts.Length == 2) + { + bool succ1 = parts[0].ToStandardizedNumber(out double part1); + bool succ2 = parts[1].ToStandardizedNumber(out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 <= part2; + } + + return null; + } + + /* + ((\)?)+(\(?)+[0-9]+(((\s?)+(\)?)(\s?)+\+(\s?)+(\(?)+(\s?)+|(\s?)+(\)?)(\s?)+\-(\s?)+(\(?)+(\s?)+|(\s?)+(\)?)(\s?)+\*(\s?)+(\(?)+(\s?)+|(\s?)+(\)?)(\s?)+\/(\s?)+(\(?)+(\s?)+)|[,.])(?=[0-9])+)+[0-9]+(\)?)+ + */ + + public static string Calculate(string input, char op = '+') + { + if (input is null) + { + return null; + } + + MatchCollection matches = Regex.Matches(input, @"\(([^()]+)\)"); + while (matches.Count > 0) + { + for (int i = 0; i < matches.Count; i++) + { + string calc = matches[i].Value.Substring(1, matches[i].Length - 2); + string ret = Calculate(calc); + input = input.Replace(matches[i].Value, ret); + } + matches = Regex.Matches(input, @"\(([^()]+)\)"); + } + + string[] parts = input.Split(op); + + double number = double.NaN; + + foreach (string p in parts) + { + string part = p; + + switch (op) + { + case '+': + part = Calculate(part, '-'); + break; + + case '-': + part = Calculate(part, '*'); + break; + + case '*': + part = Calculate(part, '/'); + break; + } + + if (part is null) + { + return null; + } + + if (part.ToStandardizedNumber(out double num)) + { + if (double.IsNaN(number)) + { + number = num; + } + else + { + switch (op) + { + case '+': + number += num; + break; + + case '-': + number -= num; + break; + + case '*': + number *= num; + break; + + case '/': + number /= num; + break; + } + } + } + else + { + return null; + } + } + + return number.ToString(); + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/Utilities/StringExtentions.cs b/YesNt-Interpreter/Utilities/StringExtentions.cs new file mode 100644 index 0000000..a7ee58e --- /dev/null +++ b/YesNt-Interpreter/Utilities/StringExtentions.cs @@ -0,0 +1,53 @@ +using System; +using System.Globalization; +using System.Text; + +namespace YesNt.Interpreter.Utilities +{ + public static class StringExtentions + { + public static string ToSaveString(this string input) + { + StringBuilder output = new StringBuilder(); + foreach (char c in input) + { + output.Append($"\r{c}\r"); + } + return output.ToString(); + } + + public static string FromSaveString(this string input) + { + return input.Replace("\r", ""); + } + + public static bool ToStandardizedNumber(this string input, out double result) + { + return double.TryParse(input.FromSaveString().Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out result); + } + + public static string ReplaceFirstOccurrence(this string input, string oldValue, string newValue) + { + int place = input.IndexOf(oldValue); + return input.Remove(place, oldValue.Length).Insert(place, newValue); + } + + public static string ReplaceLastOccurrence(this string input, string oldValue, string newValue) + { + int place = input.LastIndexOf(oldValue); + return input.Remove(place, Math.Min(oldValue.Length, input.Length - place)).Insert(place, newValue); + } + + public static int WhiteSpaceAtEnd(this string input) + { + int count = 0; + int index = input.Length - 1; + while (index >= 0 && char.IsWhiteSpace(input[index--])) + { + count++; + } + + return count; + } + } +} \ No newline at end of file diff --git a/YesNt-Interpreter/YesNt.Interpreter.csproj b/YesNt-Interpreter/YesNt.Interpreter.csproj new file mode 100644 index 0000000..c017137 --- /dev/null +++ b/YesNt-Interpreter/YesNt.Interpreter.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + YesNt.Interpreter + + Exe + + + + diff --git a/YesNt-Interpreter/YesNtInterpreter_OLD.cs b/YesNt-Interpreter/YesNtInterpreter_OLD.cs new file mode 100644 index 0000000..5ed19fb --- /dev/null +++ b/YesNt-Interpreter/YesNtInterpreter_OLD.cs @@ -0,0 +1,357 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace YesNt.Interpreter +{ + public class YesNtInterpreter_OLD + { + private readonly Dictionary variables = new Dictionary(); + private readonly Dictionary labels = new Dictionary(); + private List lines = new List(); + private Stack lastLabels = new(); + + public void Execute(string path) + { + lines.Clear(); + variables.Clear(); + labels.Clear(); + lastLabels.Clear(); + + LoadFile(path); + + string searchLabel = string.Empty; + + for (int lineNum = 0; lineNum < lines.Count; lineNum++) + { + string line = lines[lineNum].Trim().Replace("\r", ""); + + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + if (searchLabel == string.Empty) + { + if (line.Contains("%crl")) + { + line = line.Replace("%crl", ToSaveString(Console.ReadLine())); + } + if (line.Contains("%cr")) + { + line = line.Replace("%cr", ToSaveString(Console.ReadKey().KeyChar.ToString())); + } + + foreach (KeyValuePair variable in variables) + { + line = line.Replace($">{variable.Key}", variable.Value); + } + + if (line.Contains(" ")) + { + int index = line.IndexOf(' '); + if (line.Contains(" = ") && line.IndexOf('=') > index) + { + index = line.IndexOf('=') + 1; + } + + string cmd = line.Substring(0, index); + cmd += Calculate(line.Substring(index)); + line = cmd; + } + } + + if (line.StartsWith("%lbl ")) + { + string key = line.Substring(5).Trim(); + if (labels.ContainsKey(key)) + { + labels[key] = lineNum; + } + else + { + labels.Add(key, lineNum); + } + + if (searchLabel != string.Empty && searchLabel == key) + { + searchLabel = string.Empty; + } + continue; + } + + if (searchLabel != string.Empty) + { + continue; + } + + if (line.StartsWith("%imp ")) + { + string pat = line.Substring(5).Trim(); + lines.RemoveAt(lineNum); + LoadFile(pat, lineNum); + lineNum--; + } + else if (line.StartsWith("%jmp ")) + { + string key = FromSaveString(line.Substring(5).Trim()); + lastLabels.Push(lineNum); + + if (labels.ContainsKey(key)) + { + lineNum = labels[key]; + } + else + { + searchLabel = key; + } + } + else if (line.StartsWith("%jif ")) + { + if (line.Contains("|")) + { + string dat = line.Substring(5).Trim(); + string key = dat.Substring(0, dat.IndexOf('|')).Trim(); + string condition = dat.Substring(dat.IndexOf('|')).Replace("|", "").Trim(); + if (EvaluateCondition(condition)) + { + lastLabels.Push(lineNum); + if (labels.ContainsKey(key)) + { + lineNum = labels[key]; + } + else + { + searchLabel = key; + } + } + } + } + else if (line.Equals("%ret")) + { + lineNum = lastLabels.Pop(); + } + else if (line.Equals("%end")) + { + break; + } + else if (line.StartsWith("%cwl ")) + { + Console.WriteLine(FromSaveString(line.Substring(5))); + } + else if (line.StartsWith("%cw ")) + { + Console.Write(FromSaveString(line.Substring(4))); + } + else if (line.StartsWith("<")) + { + string[] parts = line.Split(" = "); + if (parts.Length == 2) + { + string key = parts[0].Replace("<", ""); + if (variables.ContainsKey(key)) + { + variables[key] = parts[1]; + } + else + { + variables.Add(key, parts[1]); + } + } + } + } + } + + private void LoadFile(string path, int index = 0) + { + if (!File.Exists(path)) + { + Console.WriteLine($"File \"{path}\" not found!"); + return; + } + + string input = File.ReadAllText(path); + string[] newLines = input.Split("\n"); + foreach (string line in newLines) + { + lines.Insert(index, line); + index++; + } + } + + private string ToSaveString(string input) + { + string output = ""; + foreach (char c in input) + { + output += $"\r{c}\r"; + } + return output; + } + + private string FromSaveString(string input) + { + return input.Replace("\r", ""); + } + + private bool EvaluateCondition(string input) + { + string[] parts = input.Split(" == "); + if (parts.Length == 2) + { + string part1 = FromSaveString(parts[0]); + string part2 = FromSaveString(parts[1]); + return part1 == part2; + } + + parts = input.Split(" != "); + if (parts.Length == 2) + { + string part1 = FromSaveString(parts[0]); + string part2 = FromSaveString(parts[1]); + return part1 != part2; + } + + parts = input.Split(" > "); + if (parts.Length == 2) + { + bool succ1 = double.TryParse(FromSaveString(parts[0]), out double part1); + bool succ2 = double.TryParse(FromSaveString(parts[1]), out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 > part2; + } + + parts = input.Split(" < "); + if (parts.Length == 2) + { + bool succ1 = double.TryParse(FromSaveString(parts[0]), out double part1); + bool succ2 = double.TryParse(FromSaveString(parts[1]), out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 < part2; + } + + parts = input.Split(" >= "); + if (parts.Length == 2) + { + bool succ1 = double.TryParse(FromSaveString(parts[0]), out double part1); + bool succ2 = double.TryParse(FromSaveString(parts[1]), out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 >= part2; + } + + parts = input.Split(" <= "); + if (parts.Length == 2) + { + bool succ1 = double.TryParse(FromSaveString(parts[0]), out double part1); + bool succ2 = double.TryParse(FromSaveString(parts[1]), out double part2); + if (!succ1 || !succ2) + { + return false; + } + + return part1 <= part2; + } + + return false; + } + + private string Calculate(string input, char op = '+') + { + string[] parts = input.Split($" {op} "); + string result = ""; + + double number = double.NaN; + int spacesToAdd = 0; + + foreach (string p in parts) + { + string part = p; + + switch (op) + { + case '+': + part = Calculate(part, '-'); + break; + + case '-': + part = Calculate(part, '*'); + break; + + case '*': + part = Calculate(part, '/'); + break; + } + if (double.TryParse(FromSaveString(part), out double num)) + { + if (double.IsNaN(number)) + { + spacesToAdd = CountSpaces(part); + number = num; + } + else + { + switch (op) + { + case '+': + number += num; + break; + + case '-': + number -= num; + break; + + case '*': + number *= num; + break; + + case '/': + number /= num; + break; + } + } + } + else + { + if (!double.IsNaN(number)) + { + result += new string(' ', spacesToAdd) + number; + } + + result += part; + + number = double.NaN; + } + } + + if (!double.IsNaN(number)) + { + result += new string(' ', spacesToAdd) + number; + } + + return result; + } + + private int CountSpaces(string input) + { + int count = 0; + while (input[count] == ' ') + { + count++; + } + + return count; + } + } +} \ No newline at end of file diff --git a/YesNt.CodeEditor/Editor.cs b/YesNt.CodeEditor/Editor.cs new file mode 100644 index 0000000..f5755cc --- /dev/null +++ b/YesNt.CodeEditor/Editor.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using YesNt.Interpreter; + +namespace YesNt.CodeEditor +{ + internal class TextEditor + { + private readonly YesNtInterpreter yesNtInterpreter = new(); + private readonly SyntaxHighlighter syntaxHighlighter; + private int lineOffset = 0; + private readonly List lines = new(); + private readonly List debugOutput = new(); + private readonly Point cursorPosition = new(0, 0); + private Mode mode = Mode.Command; + + private string currentPath = string.Empty; + + public TextEditor(string path) : this() + { + if (File.Exists(path)) + { + Load(path); + } + } + + public TextEditor() + { + yesNtInterpreter.Initialize(); + yesNtInterpreter.OnDebugOutput += YesNtInterpreter_OnDebugOutput; + yesNtInterpreter.OnLineExecuted += YesNtInterpreter_OnLineExecuted; + syntaxHighlighter = new(yesNtInterpreter.StatementInformation); + } + + public void Run() + { + Console.Clear(); + + Display(true); + do + { + Display(false); + } while (HandleInput()); + } + + private void Display(bool drawAll) + { + Console.CursorVisible = false; + + Console.SetCursorPosition(0, 0); + int padding = (Console.WindowHeight + lineOffset - 3).ToString().Length; + padding = Math.Max(padding, lines.Count.ToString().Length); + padding += 1; + for (int i = lineOffset; i < Console.WindowHeight + lineOffset - 2; i++) + { + Console.SetCursorPosition(0, i - lineOffset); + + string lineCountString = $"{i + 1}".PadRight(padding, ' ') + "| "; + if (i < lines.Count) + { + if (cursorPosition.Y == i || drawAll) + { + Console.Write(lineCountString); + string printLine = $"{lines[i].Substring(0, Math.Min(lines[i].Length, Console.WindowWidth))}".TrimEnd(); + syntaxHighlighter.Write(printLine); + Console.Write(new string(' ', Math.Max(Console.WindowWidth - lineCountString.Length - printLine.Length, 0))); + } + } + else if (drawAll || cursorPosition.Y == i) + { + Console.Write(lineCountString + new string(' ', Console.WindowWidth - lineCountString.Length)); + } + } + + Console.SetCursorPosition(0, Console.WindowHeight - 3); + Console.Write(new string('-', Console.WindowWidth)); + Console.SetCursorPosition(0, Console.WindowHeight - 2); + Console.Write(">>>" + new string(' ', Console.WindowWidth - 3)); + + Console.SetCursorPosition(Math.Min(cursorPosition.X + padding + 2, Console.WindowWidth - 1), cursorPosition.Y - lineOffset); + Console.CursorVisible = true; + } + + private bool HandleInput() + { + if (mode == Mode.Edit) + { + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + + if ((ConsoleModifiers.Alt & keyInfo.Modifiers) != 0 && keyInfo.Key == ConsoleKey.C) + { + mode = Mode.Command; + } + else if (keyInfo.Key == ConsoleKey.DownArrow) + { + cursorPosition.Y++; + if (cursorPosition.Y - lineOffset >= Console.WindowHeight - 3) + { + lineOffset++; + Display(true); + } + } + else if (keyInfo.Key == ConsoleKey.UpArrow) + { + if (cursorPosition.Y > 0) + { + cursorPosition.Y--; + if (cursorPosition.Y - lineOffset < 0 && lineOffset > 0) + { + lineOffset--; + Display(true); + } + } + } + else if (keyInfo.Key == ConsoleKey.LeftArrow) + { + if (cursorPosition.X > 0) + { + cursorPosition.X--; + } + } + else if (keyInfo.Key == ConsoleKey.RightArrow) + { + if (cursorPosition.X + 6 < Console.WindowWidth - 1) + { + cursorPosition.X++; + } + } + else if (keyInfo.Key == ConsoleKey.Enter) + { + while (lines.Count <= cursorPosition.Y) + { + lines.Add(""); + } + lines.Insert(cursorPosition.Y, ""); + + string line = lines[cursorPosition.Y + 1]; + + lines[cursorPosition.Y] = line.Substring(0, Math.Min(cursorPosition.X, line.Length)); + lines[cursorPosition.Y + 1] = line.Substring(Math.Min(cursorPosition.X, line.Length)); + + cursorPosition.X = 0; + cursorPosition.Y++; + + if (cursorPosition.Y - lineOffset >= Console.WindowHeight - 3) + { + lineOffset++; + } + Display(true); + } + else + { + while (lines.Count <= cursorPosition.Y) + { + lines.Add(""); + } + + while (lines[cursorPosition.Y].Length <= cursorPosition.X) + { + lines[cursorPosition.Y] += " "; + } + + if (keyInfo.Key == ConsoleKey.Backspace) + { + if (cursorPosition.X > 0) + { + lines[cursorPosition.Y] = lines[cursorPosition.Y].Remove(cursorPosition.X - 1, 1); + cursorPosition.X--; + } + else + { + if (cursorPosition.Y > 0) + { + if (string.IsNullOrWhiteSpace(lines[cursorPosition.Y - 1])) + { + lines.RemoveAt(--cursorPosition.Y); + } + else + { + cursorPosition.Y--; + cursorPosition.X = lines[cursorPosition.Y].TrimEnd().Length; + lines[cursorPosition.Y] += lines[cursorPosition.Y + 1]; + lines.RemoveAt(cursorPosition.Y + 1); + } + if (cursorPosition.Y - lineOffset < 0 && lineOffset > 0) + { + lineOffset--; + } + Display(true); + } + } + + while (lines.Count > 0 && string.IsNullOrWhiteSpace(lines[lines.Count - 1])) + { + lines.RemoveAt(lines.Count - 1); + } + } + else + { + char input = keyInfo.KeyChar; + if (!char.IsControl(input)) + { + lines[cursorPosition.Y] = lines[cursorPosition.Y].Insert(cursorPosition.X, input.ToString()); + cursorPosition.X++; + } + } + } + } + else if (mode == Mode.Command) + { + Console.SetCursorPosition(3, Console.WindowHeight - 2); + + string input = Console.ReadLine() ?? string.Empty; + string command = input.Split(' ')[0].Trim(); + string path; + + switch (command) + { + case "edit": + WriteStatus(string.Empty); + mode = Mode.Edit; + break; + + case "save": + Save(input, false); + break; + + case "run": + if (Save(input, true)) + { + Console.Clear(); + yesNtInterpreter.Execute(currentPath); + Console.ReadKey(); + } + break; + + case "debug": + if (Save(input, true)) + { + Console.Clear(); + yesNtInterpreter.Execute(currentPath, true); + Console.ReadKey(); + WriteStatus(string.Empty); + } + break; + + case "load": + if (input.Split(' ').Length == 2) + { + path = input.Split(' ')[1]; + } + else + { + WriteStatus("Invalid arguments!"); + break; + } + + if (!File.Exists(path)) + { + WriteStatus("File does not exist!"); + break; + } + + try + { + Load(path); + } + catch (Exception ex) + { + WriteStatus(ex.Message); + } + lineOffset = 0; + cursorPosition.X = 0; + cursorPosition.Y = 0; + break; + + case "new": + if (Save(input, false)) + { + lineOffset = 0; + cursorPosition.X = 0; + cursorPosition.Y = 0; + currentPath = string.Empty; + lines.Clear(); + WriteStatus(string.Empty); + } + break; + + case "exit": + return false; + + default: + WriteStatus("Command not found!"); + break; + } + Display(true); + } + return true; + } + + private bool Load(string path) + { + if (!File.Exists(path)) + { + WriteStatus("File does not exist!"); + return false; + } + + lines.Clear(); + foreach (string line in File.ReadAllLines(path)) + { + lines.Add(line.Replace("\n", "")); + } + currentPath = path; + WriteStatus("File Loaded!"); + return true; + } + + private bool Save(string input, bool loadIfExists) + { + string text = ""; + string path = ""; + + foreach (string line in lines) + { + text += line + "\n"; + } + text = text.TrimEnd('\n'); + + if (input.Split(' ').Length == 2) + { + path = input.Split(' ')[1]; + + if (currentPath.Trim() != path.Trim() && loadIfExists) + { + if (Load(path)) + { + return true; + } + else + { + return false; + } + } + currentPath = path; + } + else if (!string.IsNullOrWhiteSpace(currentPath)) + { + path = currentPath; + } + else if (input.Split(' ').Length > 2) + { + WriteStatus("Invalid arguments!"); + return false; + } + else + { + WriteStatus("File path is empty!"); + return false; + } + + try + { + File.WriteAllText(path, text); + WriteStatus("File Saved!"); + return true; + } + catch (Exception ex) + { + WriteStatus(ex.Message); + return false; + } + } + + private void WriteStatus(string input) + { + Console.SetCursorPosition(0, Console.WindowHeight - 1); + Console.Write(input + new string(' ', Console.WindowWidth - input.Length - 1)); + } + + private void YesNtInterpreter_OnDebugOutput(string output) + { + debugOutput.Add(output); + } + + private void YesNtInterpreter_OnLineExecuted(Interpreter.Runtime.DebugEventArgs e) + { + Console.ForegroundColor = ConsoleColor.Magenta; + if (e.OriginalLine == e.CurrentLine) + { + Console.WriteLine($"{Environment.NewLine}[{e.LineNumber}] [{e.CurrentLine}] ==>"); + } + else + { + Console.WriteLine($"{Environment.NewLine}[{e.LineNumber}] [{e.OriginalLine}] => [{e.CurrentLine}] ==>"); + } + Console.ResetColor(); + + foreach (string output in debugOutput) + { + Console.Write(output); + } + debugOutput.Clear(); + } + } + + internal class Point + { + public int X { get; set; } + public int Y { get; set; } + + public Point(int x, int y) + { + X = x; + Y = y; + } + } + + internal enum Mode + { + Edit, + Command + } +} \ No newline at end of file diff --git a/YesNt.CodeEditor/Program.cs b/YesNt.CodeEditor/Program.cs new file mode 100644 index 0000000..01f8547 --- /dev/null +++ b/YesNt.CodeEditor/Program.cs @@ -0,0 +1,29 @@ +using System; + +namespace YesNt.CodeEditor +{ + internal class Program + { + private static void Main(string[] args) + { + TextEditor textEditor; + + Console.CancelKeyPress += Console_CancelKeyPress; + if (args.Length > 0) + { + textEditor = new TextEditor(args[0]); + } + else + { + textEditor = new TextEditor(); + } + + textEditor.Run(); + } + + private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) + { + e.Cancel = true; + } + } +} \ No newline at end of file diff --git a/YesNt.CodeEditor/SyntaxHighlighter.cs b/YesNt.CodeEditor/SyntaxHighlighter.cs new file mode 100644 index 0000000..53f9b40 --- /dev/null +++ b/YesNt.CodeEditor/SyntaxHighlighter.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; + +using YesNt.Interpreter.Enums; +using YesNt.Interpreter.Runtime; +using YesNt.Interpreter.Utilities; + +namespace YesNt.CodeEditor +{ + internal class SyntaxHighlighter + { + private ReadOnlyCollection statementInformation; + + public SyntaxHighlighter(ReadOnlyCollection statementInformation) + { + this.statementInformation = statementInformation; + } + + public void Write(string input) + { + input = input.Replace("\0", string.Empty); + foreach (StatementInformation statement in statementInformation) + { + if (statement.IgnoreSyntaxHighlighting) + { + continue; + } + + string name = statement.SpaceAround switch + { + SpaceAround.StartEnd => $" {statement.Name.Trim()} ", + SpaceAround.Start => $" {statement.Name.Trim()}", + SpaceAround.End => $"{statement.Name.Trim()} ", + _ => statement.Name + }; + input = input.TrimEnd(); + if (statement.SearchMode == SearchMode.StartOfLine && input.StartsWith(name)) + { + input = AddColorInformation(input, input.Substring(0, name.Length), ConsoleColor.DarkGreen, statement.SearchMode); + } + if (statement.SearchMode == SearchMode.Contains && $"{input} ".Contains(name)) + { + input = AddColorInformation($"{input} ", $"{input} ".Substring($"{input} ".IndexOf(name), name.Length), ConsoleColor.Green, statement.SearchMode); + } + if (statement.SearchMode == SearchMode.EndOfLine && input.EndsWith(name)) + { + input = AddColorInformation(input, input.Substring(input.Length - name.Length), ConsoleColor.DarkYellow, statement.SearchMode); + } + if (statement.SearchMode == SearchMode.Exact && input.Equals(name)) + { + input = AddColorInformation(input, input, ConsoleColor.Red, statement.SearchMode); + } + } + + MatchCollection matches = Regex.Matches(input, @">[a-zA-Z0-9]+"); + for (int i = 0; i < matches.Count; i++) + { + input = AddColorInformation(input, matches[i].Value, ConsoleColor.Cyan, SearchMode.Contains); + } + + matches = Regex.Matches(input, @"^<[a-zA-Z0-9]+"); + for (int i = 0; i < matches.Count; i++) + { + input = AddColorInformation(input, matches[i].Value, ConsoleColor.DarkCyan, SearchMode.StartOfLine); + } + + foreach (string part in input.Split("\0")) + { + ConsoleColor consoleColor; + string messagePart = part; + + string stringColor = Regex.Match(messagePart, "(?<=(\\r))(.*)(?=\\r)").Value; + bool succ = int.TryParse(stringColor, out int colorIndex); + if (succ && colorIndex >= 0 && colorIndex < 16) + { + messagePart = messagePart.Replace($"\r{stringColor}\r", string.Empty); + consoleColor = (ConsoleColor)colorIndex; + } + else + { + consoleColor = ConsoleColor.White; + } + + if (consoleColor == Console.BackgroundColor || consoleColor == ConsoleColor.DarkGray) + { + consoleColor = ConsoleColor.White; + } + Console.ForegroundColor = consoleColor; + Console.Write(messagePart, consoleColor); + Console.ResetColor(); + } + } + + private string AddColorInformation(string originalString, string value, ConsoleColor color, SearchMode searchMode) + { + int spacesAtEnd = value.WhiteSpaceAtEnd(); + string reult = searchMode switch + { + SearchMode.StartOfLine => originalString.ReplaceFirstOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), + SearchMode.EndOfLine => originalString.ReplaceLastOccurrence(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)), + _ => originalString.Replace(value, $"\0\r{(int)color}\r{value.TrimEnd()}\0" + new string(' ', spacesAtEnd)) + }; + return reult; + } + } +} \ No newline at end of file diff --git a/YesNt.CodeEditor/YesNt.CodeEditor.csproj b/YesNt.CodeEditor/YesNt.CodeEditor.csproj new file mode 100644 index 0000000..f4ba822 --- /dev/null +++ b/YesNt.CodeEditor/YesNt.CodeEditor.csproj @@ -0,0 +1,12 @@ + + + + Exe + net5.0 + + + + + + +