diff --git a/AppCalc/App.cs b/AppCalc/App.cs deleted file mode 100644 index 090da90..0000000 --- a/AppCalc/App.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Base; - -namespace AppCalc; - -public sealed partial class App : IApp { - private static readonly Regex RegexValidCharacters = GetRegexValidCharacters(); - private static readonly Regex RegexTokenSeparator = GetRegexTokenSeparator(); - private static readonly Regex RegexRecurringDecimal = GetRegexRecurringDecimal(); - - [GeneratedRegex(@"^[\s\d\.\-+*/%^]+$", RegexOptions.Compiled)] - private static partial Regex GetRegexValidCharacters(); - - [GeneratedRegex(@"((?<!\d)-?(((\d+)?\.\d+(\.\.\.)?)|\d+))|[^\d\s]", RegexOptions.ExplicitCapture | RegexOptions.Compiled)] - private static partial Regex GetRegexTokenSeparator(); - - [GeneratedRegex(@"\b(?:(\d+?\.\d{0,}?)(\d+?)\2+|([\d+?\.]*))\b", RegexOptions.Compiled)] - private static partial Regex GetRegexRecurringDecimal(); - - private static readonly char[] SplitSpace = [ ' ' ]; - - public string[] RecognizedNames => [ - "calc", - "calculate", - "calculator" - ]; - - public MatchConfidence GetConfidence(Command cmd) { - return RegexValidCharacters.IsMatch(cmd.Text) ? MatchConfidence.Possible : MatchConfidence.None; - } - - string? IApp.ProcessCommand(Command cmd) { - return ParseAndProcessExpression(cmd.Text); - } - - private static string ParseAndProcessExpression(string text) { - // text = RegexBalancedParentheses.Replace(text, match => " "+ParseAndProcessExpression(match.Groups[1].Value)+" "); // parens are handled as apps - - string expression = RegexTokenSeparator.Replace(text, static match => " " + match.Value + " "); - string[] tokens = expression.Split(SplitSpace, StringSplitOptions.RemoveEmptyEntries); - - decimal result = ProcessExpression(tokens); - - if (Math.Abs(result - decimal.Round(result)) < 0.0000000000000000000000000010M) { - return decimal.Round(result).ToString(CultureInfo.InvariantCulture); - } - - string res = result.ToString(CultureInfo.InvariantCulture); - bool hasDecimalPoint = decimal.Round(result) != result; - - if (res.Length == 30 && hasDecimalPoint && res.IndexOf('.') < 15) { // Length 30 uses all available bytes - res = res[..^1]; - - Match match = RegexRecurringDecimal.Match(res); - - if (match.Groups[2].Success) { - string repeating = match.Groups[2].Value; - - StringBuilder build = new StringBuilder(34); - build.Append(match.Groups[1].Value); - - do { - build.Append(repeating); - } while (build.Length + repeating.Length <= res.Length); - - return build.Append(repeating[..(1 + build.Length - res.Length)]).Append("...").ToString(); - } - } - else if (hasDecimalPoint) { - res = res.TrimEnd('0'); - } - - return res; - } - - private static decimal ProcessExpression(string[] tokens) { - bool isPostfix; - - if (tokens.Length < 3) { - isPostfix = false; - } - else { - try { - ParseNumberToken(tokens[0]); - } catch (CommandException) { - throw new CommandException("Prefix notation is not supported."); - } - - try { - ParseNumberToken(tokens[1]); - isPostfix = true; - } catch (CommandException) { - isPostfix = false; - } - } - - if (isPostfix) { - return ProcessPostfixExpression(tokens); - } - else { - return ProcessPostfixExpression(ConvertInfixToPostfix(tokens)); - } - } - - private static IEnumerable<string> ConvertInfixToPostfix(IEnumerable<string> tokens) { - Stack<string> operators = new Stack<string>(); - - foreach (string token in tokens) { - if (Operators.With2Operands.Contains(token)) { - int currentPrecedence = Operators.GetPrecedence(token); - bool currentRightAssociative = Operators.IsRightAssociative(token); - - while (operators.Count > 0) { - int topPrecedence = Operators.GetPrecedence(operators.Peek()); - - if ((currentRightAssociative && currentPrecedence < topPrecedence) || (!currentRightAssociative && currentPrecedence <= topPrecedence)) { - yield return operators.Pop(); - } - else { - break; - } - } - - operators.Push(token); - } - else { - yield return ParseNumberToken(token).ToString(CultureInfo.InvariantCulture); - } - } - - while (operators.Count > 0) { - yield return operators.Pop(); - } - } - - private static decimal ProcessPostfixExpression(IEnumerable<string> tokens) { - Stack<decimal> stack = new Stack<decimal>(); - - foreach (string token in tokens) { - decimal operand1, operand2; - - if (token == "-" && stack.Count == 1) { - operand2 = stack.Pop(); - operand1 = 0M; - } - else if (Operators.With2Operands.Contains(token)) { - if (stack.Count < 2) { - throw new CommandException("Incorrect syntax, not enough numbers in stack."); - } - - operand2 = stack.Pop(); - operand1 = stack.Pop(); - } - else { - operand1 = operand2 = 0M; - } - - switch (token) { - case "+": - stack.Push(operand1 + operand2); - break; - case "-": - stack.Push(operand1 - operand2); - break; - case "*": - stack.Push(operand1 * operand2); - break; - - case "/": - if (operand2 == 0M) { - throw new CommandException("Cannot divide by zero."); - } - - stack.Push(operand1 / operand2); - break; - - case "%": - if (operand2 == 0M) { - throw new CommandException("Cannot divide by zero."); - } - - stack.Push(operand1 % operand2); - break; - - case "^": - if (operand1 == 0M && operand2 == 0M) { - throw new CommandException("Cannot evaluate 0 to the power of 0."); - } - else if (operand1 < 0M && Math.Abs(operand2) < 1M) { - throw new CommandException("Cannot evaluate a root of a negative number."); - } - - try { - stack.Push((decimal) Math.Pow((double) operand1, (double) operand2)); - } catch (OverflowException ex) { - throw new CommandException("Number overflow.", ex); - } - - break; - - default: - stack.Push(ParseNumberToken(token)); - break; - } - } - - if (stack.Count != 1) { - throw new CommandException("Incorrect syntax, too many numbers in stack."); - } - - return stack.Pop(); - } - - private static decimal ParseNumberToken(string token) { - string str = token; - - if (str.StartsWith("-.")) { - str = "-0" + str[1..]; - } - else if (str[0] == '.') { - str = "0" + str; - } - - if (str.EndsWith("...")) { - string truncated = str[..^3]; - - if (truncated.Contains('.')) { - str = truncated; - } - } - - if (decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out decimal value)) { - if (value.ToString(CultureInfo.InvariantCulture) != str) { - throw new CommandException("Provided number is outside of decimal range: " + token); - } - - return value; - } - else { - throw new CommandException("Invalid token, expected a number: " + token); - } - } -} diff --git a/AppCalc/AppCalc.csproj b/AppCalc/AppCalc.csproj deleted file mode 100644 index b56525f..0000000 --- a/AppCalc/AppCalc.csproj +++ /dev/null @@ -1,11 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> - </PropertyGroup> - - <ItemGroup> - <ProjectReference Include="..\Base\Base.csproj" /> - </ItemGroup> - -</Project> diff --git a/AppCalc/Operators.cs b/AppCalc/Operators.cs deleted file mode 100644 index 2a44478..0000000 --- a/AppCalc/Operators.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace AppCalc; - -static class Operators { - internal static readonly string[] With2Operands = [ "+", "-", "*", "/", "%", "^" ]; - - internal static int GetPrecedence(string token) { - return token switch { - "^" => 4, - "*" or "/" or "%" => 3, - "+" or "-" => 2, - _ => 1 - }; - } - - internal static bool IsRightAssociative(string token) { - return token == "^"; - } -} diff --git a/AppConv/App.cs b/AppConv/App.cs deleted file mode 100644 index c9f05fc..0000000 --- a/AppConv/App.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Linq; -using AppConv.General; -using AppConv.Units; -using Base; - -namespace AppConv; - -public sealed class App : IApp { - private static readonly IUnitType[] Processors = [ - new Temperature(), - new Weight(), - new Length(), - new Area(), - new Volume(), - new Angle(), - new Storage(), - new Radix() - ]; - - public string[] RecognizedNames => [ - "conv", - "convert" - ]; - - public MatchConfidence GetConfidence(Command cmd) { - return cmd.Text.Contains(" to ", StringComparison.InvariantCultureIgnoreCase) ? MatchConfidence.Possible : MatchConfidence.None; - } - - public string ProcessCommand(Command cmd) { - string[] data = cmd.Text.Split([ " to " ], 2, StringSplitOptions.None); - - string src = data[0].Trim(); - string dst = data[1].Trim(); - - if (src.Length == 0 || dst.Length == 0) { - throw new CommandException("Unrecognized conversion app syntax."); - } - - string result = string.Empty; - IUnitType? used = Processors.FirstOrDefault(processor => processor.TryProcess(src, dst, out result)); - - if (used == null) { - throw new CommandException("Could not recognize conversion units."); - } - - return result; - } -} diff --git a/AppConv/AppConv.csproj b/AppConv/AppConv.csproj deleted file mode 100644 index 18e4cc9..0000000 --- a/AppConv/AppConv.csproj +++ /dev/null @@ -1,11 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <OutputType>Library</OutputType> - </PropertyGroup> - - <ItemGroup> - <ProjectReference Include="..\Base\Base.csproj" /> - </ItemGroup> - -</Project> diff --git a/AppConv/General/DecimalUnitConverterBase.cs b/AppConv/General/DecimalUnitConverterBase.cs deleted file mode 100644 index ffe1e52..0000000 --- a/AppConv/General/DecimalUnitConverterBase.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace AppConv.General; - -abstract class DecimalUnitConverterBase<T> : IUnitType where T : struct { - internal sealed class DecimalFuncMap : Dictionary<T, Func<decimal, decimal>> { - public DecimalFuncMap() {} - public DecimalFuncMap(int capacity) : base(capacity) {} - } - - internal sealed class NameMap : Dictionary<string, T> { - public NameMap() {} - public NameMap(int capacity) : base(capacity) {} - } - - protected abstract NameMap Names { get; } - protected abstract DecimalFuncMap ConvertTo { get; } - protected abstract DecimalFuncMap ConvertFrom { get; } - - protected virtual int Precision => 0; - - protected virtual bool CaseCheck => false; - - protected virtual NumberStyles NumberStyle => NumberStyles.Float & ~NumberStyles.AllowLeadingSign; - - protected virtual string ProcessSrc(string src) { - return src; - } - - protected virtual string ProcessDst(string dst) { - return dst; - } - - protected abstract bool IsValueInvalid(T value); - - protected virtual decimal Convert(decimal value, T from, T to) { - return ConvertFrom[to](ConvertTo[from](value)); - } - - protected virtual string Format(decimal value) { - if (Precision > 0) { - decimal truncated = decimal.Truncate(value); - - if (value == truncated) { - return truncated.ToString(CultureInfo.InvariantCulture); - } - } - - int decimalPlaces = Precision; - - if (Math.Abs(value) < 1M) { - double fractionalPart = (double) Math.Abs(value % 1M); - int fractionalZeroCount = -(int) Math.Ceiling(Math.Log(fractionalPart, 10D)); - - decimalPlaces = Math.Min(28, fractionalZeroCount + Precision); - } - - string result = decimal.Round(value, decimalPlaces, MidpointRounding.ToEven).ToString(CultureInfo.InvariantCulture); - - if (decimalPlaces > 0) { - result = result.TrimEnd('0').TrimEnd('.'); - } - - return result; - } - - public bool TryProcess(string src, string dst, out string result) { - src = ProcessSrc(src); - dst = ProcessDst(dst); - - var pairs = new KeyValuePair<string, T>[2]; - - for (int index = 0; index < 2; index++) { - string str = index == 0 ? src : dst; - - if (CaseCheck) { - List<KeyValuePair<string, T>> list = Names.Where(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase) && (str.Length == kvp.Key.Length || !char.IsLetter(str[str.Length - kvp.Key.Length - 1]))).ToList(); - - if (list.Count == 1) { - pairs[index] = list[0]; - } - else { - pairs[index] = list.FirstOrDefault(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCulture)); - } - } - else { - pairs[index] = Names.FirstOrDefault(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase) && (str.Length == kvp.Key.Length || !char.IsLetter(str[str.Length - kvp.Key.Length - 1]))); - } - - if (IsValueInvalid(pairs[index].Value)) { - result = string.Empty; - return false; - } - - if (index == 0) { - src = src[..^pairs[index].Key.Length].TrimEnd(); - } - } - - if (decimal.TryParse(src, NumberStyle, CultureInfo.InvariantCulture, out decimal value)) { - result = Format(Convert(value, pairs[0].Value, pairs[1].Value)); - return true; - } - else { - result = string.Empty; - return false; - } - } -} diff --git a/AppConv/General/DecimalUnitConverterSimple.cs b/AppConv/General/DecimalUnitConverterSimple.cs deleted file mode 100644 index 818a78e..0000000 --- a/AppConv/General/DecimalUnitConverterSimple.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; - -namespace AppConv.General; - -abstract class DecimalUnitConverterSimple<T> : DecimalUnitConverterBase<T> where T : struct { - // ReSharper disable once StaticMemberInGenericType - private static readonly Func<decimal, decimal> FuncNoChange = static val => val; - - private readonly DecimalFuncMap MapFrom = new (); - private readonly DecimalFuncMap MapTo = new (); - - private int invalidUnitObject = -1; - - protected sealed override NameMap Names { get ; } = new (); - - protected sealed override DecimalFuncMap ConvertFrom => MapFrom; - - protected sealed override DecimalFuncMap ConvertTo => MapTo; - - protected override int Precision => 3; - - protected override bool CaseCheck => true; - - protected void AddUnit(T unitObject, params string[] names) { - foreach (string name in names) { - Names.Add(name, unitObject); - } - - ConvertFrom.Add(unitObject, FuncNoChange); - ConvertTo.Add(unitObject, FuncNoChange); - } - - protected void SetUnitFactor(T unitObject, decimal factor) { - ConvertFrom[unitObject] = val => val * factor; - ConvertTo[unitObject] = val => val / factor; - } - - protected void SetInvalidUnitObject(T unitObject) { - invalidUnitObject = (int) (object) unitObject; - } - - protected sealed override bool IsValueInvalid(T value) { - return (int) (object) value == invalidUnitObject; - } -} diff --git a/AppConv/General/IUnitType.cs b/AppConv/General/IUnitType.cs deleted file mode 100644 index 670d2df..0000000 --- a/AppConv/General/IUnitType.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace AppConv.General; - -interface IUnitType { - bool TryProcess(string src, string dst, out string result); -} diff --git a/AppConv/Units/Angle.cs b/AppConv/Units/Angle.cs deleted file mode 100644 index 2bd266f..0000000 --- a/AppConv/Units/Angle.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using AppConv.General; - -namespace AppConv.Units; - -sealed class Angle : DecimalUnitConverterSimple<Angle.Units> { - internal enum Units { - Invalid = 0, - Degree, - Radian, - Gradian - } - - protected override int Precision => 4; - - public Angle() { - AddUnit(Units.Degree, "deg", "degree", "degrees", "arc degree", "arc degrees", "arcdegree", "arcdegrees", "°"); - AddUnit(Units.Radian, "rad", "radian", "radians"); - AddUnit(Units.Gradian, "grad", "grade", "gon", "gradian", "gradians"); - - SetUnitFactor(Units.Radian, (decimal) Math.PI / 180M); - SetUnitFactor(Units.Gradian, 10M / 9M); - - SetInvalidUnitObject(Units.Invalid); - } - - // TODO convert degree notation 15°24'9" -} diff --git a/AppConv/Units/Area.cs b/AppConv/Units/Area.cs deleted file mode 100644 index f94183a..0000000 --- a/AppConv/Units/Area.cs +++ /dev/null @@ -1,58 +0,0 @@ -using AppConv.General; - -namespace AppConv.Units; - -sealed class Area : DecimalUnitConverterSimple<Area.Units> { - internal enum Units { - Invalid = 0, - SquareMM, - SquareCM, - SquareDM, - SquareM, - SquareKM, - SquareMile, - SquareYard, - SquareFoot, - SquareInch, - Acre, - Centiare, - Deciare, - Are, - Decare, - Hectare - } - - public Area() { - AddUnit(Units.SquareMM, "mm2", "square mm", "square millimeter", "square millimeters", "square millimetre", "square millimetres"); - AddUnit(Units.SquareCM, "cm2", "square cm", "square centimeter", "square centimeters", "square centimetre", "square centimetres"); - AddUnit(Units.SquareDM, "dm2", "square dm", "square decimeter", "square decimeters", "square decimetre", "square decimetres"); - AddUnit(Units.SquareM, "m2", "square m", "square meter", "square meters", "square metre", "square metres"); - AddUnit(Units.SquareKM, "km2", "square km", "square kilometer", "square kilometers", "square kilometre", "square kilometres"); - AddUnit(Units.SquareMile, "mi2", "sq mi", "sq mile", "sq miles", "square mi", "square mile", "square miles"); - AddUnit(Units.SquareYard, "yd2", "sq yd", "sq yard", "sq yards", "square yd", "square yard", "square yards"); - AddUnit(Units.SquareFoot, "ft2", "sq ft", "sq foot", "sq feet", "square ft", "square foot", "square feet"); - AddUnit(Units.SquareInch, "in2", "sq in", "sq inch", "sq inches", "square in", "square inch", "square inches"); - AddUnit(Units.Acre, "ac", "acre", "acres"); - AddUnit(Units.Centiare, "ca", "centiare", "centiares"); - AddUnit(Units.Deciare, "da", "deciare", "deciares"); // da is not canon but w/e - AddUnit(Units.Are, "a", "are", "ares"); - AddUnit(Units.Decare, "daa", "decare", "decares"); - AddUnit(Units.Hectare, "ha", "hectare", "hectares"); - - SetUnitFactor(Units.SquareMM, 1E+6M); - SetUnitFactor(Units.SquareCM, 1E+4M); - SetUnitFactor(Units.SquareDM, 1E+2M); - SetUnitFactor(Units.SquareKM, 1E-6M); - SetUnitFactor(Units.SquareMile, 3.8610215854245E-7M); - SetUnitFactor(Units.SquareYard, 1.1959900463011M); - SetUnitFactor(Units.SquareFoot, 10.76391041671M); - SetUnitFactor(Units.SquareInch, 1550.0031000062M); - SetUnitFactor(Units.Acre, 2.4710538146717E-4M); - SetUnitFactor(Units.Deciare, 1E-1M); - SetUnitFactor(Units.Are, 1E-2M); - SetUnitFactor(Units.Decare, 1E-3M); - SetUnitFactor(Units.Hectare, 1E-4M); - - SetInvalidUnitObject(Units.Invalid); - } -} diff --git a/AppConv/Units/Length.cs b/AppConv/Units/Length.cs deleted file mode 100644 index 9ca6106..0000000 --- a/AppConv/Units/Length.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using AppConv.General; -using AppConv.Utils; - -namespace AppConv.Units; - -sealed class Length : DecimalUnitConverterSimple<Length.Units> { - internal enum Units { - Invalid = 0, - Meter, - Inch, - Foot, - Yard, - Mile - } - - private static readonly string[] NamesInch = [ "in", "inch", "inches", "\"", "''" ]; - private static readonly string[] NamesFoot = [ "ft", "foot", "feet", "'" ]; - - private static readonly char[] Separator = [ ' ' ]; - - public Length() { - AddUnit(Units.Meter, "m", "meter", "metre", "meters", "metres"); - AddUnit(Units.Inch, NamesInch); - AddUnit(Units.Foot, NamesFoot); - AddUnit(Units.Yard, "yd", "yard", "yards"); - AddUnit(Units.Mile, "mi", "mile", "miles"); - - SetUnitFactor(Units.Inch, 39.37007874M); - SetUnitFactor(Units.Foot, 3.280839895M); - SetUnitFactor(Units.Yard, 1.093613298M); - SetUnitFactor(Units.Mile, 0.0006213711922M); - - SetInvalidUnitObject(Units.Invalid); - - SI.AddSupport(Units.Meter, [ "m" ], [ "meter", "metre", "meters", "metres" ], ConvertFrom, ConvertTo, Names); - } - - protected override string ProcessSrc(string src) { - string updatedStr = src; - - updatedStr = updatedStr.Replace("&", " "); - updatedStr = updatedStr.Replace(",", " "); - - string? inchName = NamesInch.FirstOrDefault(name => src.Contains(name, StringComparison.OrdinalIgnoreCase)); - - if (inchName == null) { - return src; - } - - int inchIndex = src.IndexOf(inchName, StringComparison.OrdinalIgnoreCase); - updatedStr = updatedStr.Remove(inchIndex, inchName.Length).Insert(inchIndex, new string(' ', inchName.Length)); - - string? footName = NamesFoot.FirstOrDefault(name => updatedStr.Contains(name, StringComparison.OrdinalIgnoreCase)); - - if (footName == null) { - return src; - } - - int footIndex = updatedStr.IndexOf(footName, StringComparison.OrdinalIgnoreCase); - updatedStr = updatedStr.Remove(footIndex, footName.Length).Insert(footIndex, new string(' ', footName.Length)); - - string[] tokens = updatedStr.Split(Separator, StringSplitOptions.RemoveEmptyEntries); - decimal[] numbers = new decimal[2]; - int numberIndex = 0; - - foreach (string token in tokens) { - if (decimal.TryParse(token.Trim(), NumberStyle, CultureInfo.InvariantCulture, out decimal number)) { - if (numberIndex < numbers.Length) { - numbers[numberIndex++] = number; - } - else { - return src; - } - } - } - - if (numberIndex != numbers.Length) { - return src; - } - - decimal srcFeet = numbers[footIndex < inchIndex ? 0 : 1]; - decimal srcInches = numbers[inchIndex < footIndex ? 0 : 1]; - - return srcInches + srcFeet * 12M + " in"; - } -} diff --git a/AppConv/Units/Radix.cs b/AppConv/Units/Radix.cs deleted file mode 100644 index de39f1b..0000000 --- a/AppConv/Units/Radix.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AppConv.General; -using AppConv.Utils; -using Base; - -namespace AppConv.Units; - -sealed class Radix : IUnitType { - private static readonly Dictionary<string, int> RadixDescriptions = new () { - { "UNARY", 1 }, - { "BINARY", 2 }, - { "BIN", 2 }, - { "TERNARY", 3 }, - { "QUATERNARY", 4 }, - { "QUINARY", 5 }, - { "SENARY", 6 }, - { "OCTAL", 8 }, - { "OCT", 8 }, - { "DECIMAL", 10 }, - { "DEC", 10 }, - { "UNDECIMAL", 11 }, - { "DUODECIMAL", 12 }, - { "DOZENAL", 12 }, - { "TRIDECIMAL", 13 }, - { "TETRADECIMAL", 14 }, - { "PENTADECIMAL", 15 }, - { "HEXADECIMAL", 16 }, - { "HEX", 16 } - }; - - static Radix() { - for (int baseNumber = 1; baseNumber <= 16; baseNumber++) { - RadixDescriptions.Add("RADIX " + baseNumber, baseNumber); - RadixDescriptions.Add("BASE " + baseNumber, baseNumber); - } - } - - public bool TryProcess(string src, string dst, out string result) { - if (!RadixDescriptions.TryGetValue(dst.ToUpperInvariant(), out int targetBase)) { - result = string.Empty; - return false; - } - - if (!ParseSrc(src, out string contents, out int sourceBase)) { - result = string.Empty; - return false; - } - - if (contents[0] == '-') { - throw new CommandException("Negative numbers are not supported."); - } - else if (!RadixConversion.IsBaseValid(sourceBase) || !RadixConversion.IsBaseValid(targetBase)) { - throw new CommandException("Only bases between 1 and 16 allowed."); - } - else if (!RadixConversion.IsNumberValid(contents, sourceBase)) { - throw new CommandException("The input is not a valid base " + sourceBase + " number: " + contents); - } - - if (sourceBase == targetBase) { - result = src; - return true; - } - - try { - result = RadixConversion.Do(contents, sourceBase, targetBase); - } catch (OverflowException) { - throw new CommandException("The number has overflown."); - } - - return true; - } - - private static bool ParseSrc(string src, out string sourceContent, out int sourceBase) { - if (src.All(static chr => chr is >= '0' and <= '9')) { - sourceContent = src; - sourceBase = 10; - return true; - } - - string upper = src.ToUpperInvariant(); - - if (upper.StartsWith("0X")) { - sourceContent = upper[2..]; - sourceBase = 16; - return true; - } - - if (upper.StartsWith("0B")) { - sourceContent = upper[2..]; - sourceBase = 2; - return true; - } - - int fromIndex = src.IndexOf("FROM", StringComparison.InvariantCultureIgnoreCase); - - if (fromIndex != -1) { - src = src.Remove(fromIndex, 4); - } - - foreach (KeyValuePair<string, int> kvp in RadixDescriptions) { - if (src.StartsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase)) { - sourceContent = src[kvp.Key.Length..].Trim(); - sourceBase = kvp.Value; - return true; - } - else if (src.EndsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase)) { - sourceContent = src[..^kvp.Key.Length].Trim(); - sourceBase = kvp.Value; - return true; - } - } - - sourceContent = string.Empty; - sourceBase = 0; - return false; - } -} diff --git a/AppConv/Units/Storage.cs b/AppConv/Units/Storage.cs deleted file mode 100644 index c870984..0000000 --- a/AppConv/Units/Storage.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using AppConv.General; -using AppConv.Utils; - -namespace AppConv.Units; - -sealed class Storage : DecimalUnitConverterSimple<Storage.Units> { - internal enum Units { - Invalid = 0, - Byte, - Bit - } - - [SuppressMessage("ReSharper", "PossibleLossOfFraction")] - public Storage() { - AddUnit(Units.Byte, "B", "byte", "bytes"); - AddUnit(Units.Bit, "b", "bit", "bits"); - - SetUnitFactor(Units.Bit, 8M); - - SetInvalidUnitObject(Units.Invalid); - - var bitConversionProperties = new SI.ExtededProperties { - FactorPredicate = static factor => factor > 0 && factor % 3 == 0, - FromFunctionGenerator = static exponent => () => (decimal) Math.Pow(1024, -(exponent / 3)), - ToFunctionGenerator = static exponent => () => (decimal) Math.Pow(1024, exponent / 3) - }; - - SI.AddSupportCustom(Units.Byte, [ "B" ], [ "byte", "bytes" ], ConvertFrom, ConvertTo, Names, bitConversionProperties); - SI.AddSupportCustom(Units.Bit, [ "b" ], [ "bit", "bits" ], ConvertFrom, ConvertTo, Names, bitConversionProperties); - } -} diff --git a/AppConv/Units/Temperature.cs b/AppConv/Units/Temperature.cs deleted file mode 100644 index 5382f3d..0000000 --- a/AppConv/Units/Temperature.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Globalization; -using System.Text.RegularExpressions; -using AppConv.General; - -namespace AppConv.Units; - -sealed partial class Temperature : DecimalUnitConverterBase<Temperature.Units> { - internal enum Units { - Invalid = 0, - Celsius, - Kelvin, - Fahrenheit, - Rankine, - Delisle, - Newton, - Reaumur, - Romer - } - - private static readonly NameMap UnitNames = new (21) { - { "C", Units.Celsius }, - { "Celsius", Units.Celsius }, - { "K", Units.Kelvin }, - { "Kelvin", Units.Kelvin }, - { "F", Units.Fahrenheit }, - { "Fahrenheit", Units.Fahrenheit }, - { "R", Units.Rankine }, - { "Ra", Units.Rankine }, - { "Rankine", Units.Rankine }, - { "De", Units.Delisle }, - { "Delisle", Units.Delisle }, - { "N", Units.Newton }, - { "Newton", Units.Newton }, - { "Re", Units.Reaumur }, - { "Ré", Units.Reaumur }, - { "Reaumur", Units.Reaumur }, - { "Réaumur", Units.Reaumur }, - { "Ro", Units.Romer }, - { "Rø", Units.Romer }, - { "Romer", Units.Romer }, - { "Rømer", Units.Romer } - }; - - private static readonly DecimalFuncMap FromCelsius = new (8) { - { Units.Celsius, static val => val }, - { Units.Kelvin, static val => val + 273.15M }, - { Units.Fahrenheit, static val => val * 1.8M + 32M }, - { Units.Rankine, static val => (val + 273.15M) * 1.8M }, - { Units.Delisle, static val => (100M - val) * 1.5M }, - { Units.Newton, static val => val * 0.33M }, - { Units.Reaumur, static val => val * 0.8M }, - { Units.Romer, static val => val * 0.525M + 7.5M } - }; - - private static readonly DecimalFuncMap ToCelsius = new (8) { - { Units.Celsius, static val => val }, - { Units.Kelvin, static val => val - 273.15M }, - { Units.Fahrenheit, static val => (val - 32M) * 5M / 9M }, - { Units.Rankine, static val => (val - 491.67M) * 5M / 9M }, - { Units.Delisle, static val => 100M - val * 2M / 3M }, - { Units.Newton, static val => val * 100M / 33M }, - { Units.Reaumur, static val => val * 1.25M }, - { Units.Romer, static val => (val - 7.5M) * 40M / 21M } - }; - - private static readonly Regex RegexCleanup = GetRegexCleanup(); - - [GeneratedRegex("deg(?:rees?)?|°", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.CultureInvariant)] - private static partial Regex GetRegexCleanup(); - - protected override NameMap Names => UnitNames; - - protected override DecimalFuncMap ConvertFrom => FromCelsius; - - protected override DecimalFuncMap ConvertTo => ToCelsius; - - protected override int Precision => 2; - - protected override NumberStyles NumberStyle => NumberStyles.Float; - - protected override string ProcessSrc(string src) { - return RegexCleanup.Replace(src, ""); - } - - protected override string ProcessDst(string dst) { - return RegexCleanup.Replace(dst, ""); - } - - protected override bool IsValueInvalid(Units value) { - return value == Units.Invalid; - } -} diff --git a/AppConv/Units/Volume.cs b/AppConv/Units/Volume.cs deleted file mode 100644 index 2dde3be..0000000 --- a/AppConv/Units/Volume.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AppConv.General; -using AppConv.Utils; - -namespace AppConv.Units; - -sealed class Volume : DecimalUnitConverterSimple<Volume.Units> { - internal enum Units { - Invalid = 0, - Liter, - CubicMM, - CubicCM, - CubicDM, - CubicM, - CubicKM - } - - public Volume() { - AddUnit(Units.Liter, "l", "liter", "liters", "litre", "litres"); - AddUnit(Units.CubicMM, "mm3", "cubic mm", "cubic millimeter", "cubic millimeters", "cubic millimetre", "cubic millimetres"); - AddUnit(Units.CubicCM, "cm3", "cubic cm", "cubic centimeter", "cubic centimeters", "cubic centimetre", "cubic centimetres"); - AddUnit(Units.CubicDM, "dm3", "cubic dm", "cubic decimeter", "cubic decimeters", "cubic decimetre", "cubic decimetres"); - AddUnit(Units.CubicM, "m3", "cubic m", "cubic meter", "cubic meters", "cubic metre", "cubic metres"); - AddUnit(Units.CubicKM, "km3", "cubic km", "cubic kilometer", "cubic kilometers", "cubic kilometre", "cubic kilometres"); - - SetUnitFactor(Units.CubicMM, 1000000M); - SetUnitFactor(Units.CubicCM, 1000M); - SetUnitFactor(Units.CubicM, 0.001M); - SetUnitFactor(Units.CubicKM, 1E-12M); - - SetInvalidUnitObject(Units.Invalid); - - SI.AddSupport(Units.Liter, [ "l" ], [ "liter", "litre", "liters", "litres" ], ConvertFrom, ConvertTo, Names); - } -} diff --git a/AppConv/Units/Weight.cs b/AppConv/Units/Weight.cs deleted file mode 100644 index 845484b..0000000 --- a/AppConv/Units/Weight.cs +++ /dev/null @@ -1,29 +0,0 @@ -using AppConv.General; -using AppConv.Utils; - -namespace AppConv.Units; - -sealed class Weight : DecimalUnitConverterSimple<Weight.Units> { - internal enum Units { - Invalid = 0, - Gram, - Pound, - Ounce, - Stone - } - - public Weight() { - AddUnit(Units.Gram, "g", "gram", "grams"); - AddUnit(Units.Pound, "lb", "lbs", "pound", "pounds"); - AddUnit(Units.Ounce, "oz", "ounce", "ounces"); - AddUnit(Units.Stone, "st", "stone", "stones"); - - SetUnitFactor(Units.Pound, 0.0022046226218M); - SetUnitFactor(Units.Ounce, 0.03527396195M); - SetUnitFactor(Units.Stone, 0.0001574730444177697M); - - SetInvalidUnitObject(Units.Invalid); - - SI.AddSupport(Units.Gram, [ "g" ], [ "gram", "grams" ], ConvertFrom, ConvertTo, Names); - } -} diff --git a/AppConv/Utils/RadixConversion.cs b/AppConv/Utils/RadixConversion.cs deleted file mode 100644 index 3d3844a..0000000 --- a/AppConv/Utils/RadixConversion.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; - -namespace AppConv.Utils; - -static class RadixConversion { - private const string Characters = "0123456789ABCDEF"; - - public static bool IsBaseValid(int checkedBase) { - return checkedBase is >= 1 and <= 16; - } - - public static bool IsNumberValid(string contents, int checkedBase) { - if (checkedBase == 1) { - return contents.All(static chr => chr == '1'); - } - - if (IsBaseValid(checkedBase)) { - return contents.Select(static chr => Characters.IndexOf(char.ToUpper(chr))).All(index => index != -1 && index < checkedBase); - } - - return false; - } - - public static string Do(string contents, int fromBase, int toBase) { // TODO biginteger - if (fromBase == 1) { - contents = contents.Length.ToString(CultureInfo.InvariantCulture); - fromBase = 10; - } - - long wip; - - if (fromBase == 10) { - wip = long.Parse(contents, NumberStyles.None, CultureInfo.InvariantCulture); - } - else { - wip = 0; - - for (int chr = 0; chr < contents.Length; chr++) { - int index = Characters.IndexOf(char.ToUpperInvariant(contents[chr])); - - if (index > 0) { - wip += index * (long) Math.Pow(fromBase, contents.Length - chr - 1); - - if (wip < 0) { - throw new OverflowException(); - } - } - } - } - - if (toBase == 1) { - if (wip <= int.MaxValue) { - return new string('1', (int) wip); - } - else { - throw new OverflowException(); - } - } - else if (wip < toBase) { - return Characters[(int) wip].ToString(); - } - else { - var converted = new StringBuilder(); - - while (wip >= toBase) { - int index = (int) (wip % toBase); - converted.Insert(0, Characters[index]); - - wip /= toBase; - } - - return converted.Insert(0, Characters[(int) wip]).ToString(); - } - } -} diff --git a/AppConv/Utils/SI.cs b/AppConv/Utils/SI.cs deleted file mode 100644 index 793e0c4..0000000 --- a/AppConv/Utils/SI.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Generic; -using AppConv.General; - -namespace AppConv.Utils; - -static class SI { - private static readonly List<Tuple<string, string, int>> Factors = [ - new Tuple<string, string, int>("yotta", "Y", 24), - new Tuple<string, string, int>("zetta", "Z", 21), - new Tuple<string, string, int>("exa", "E", 18), - new Tuple<string, string, int>("peta", "P", 15), - new Tuple<string, string, int>("tera", "T", 12), - new Tuple<string, string, int>("giga", "G", 9), - new Tuple<string, string, int>("mega", "M", 6), - new Tuple<string, string, int>("kilo", "k", 3), - new Tuple<string, string, int>("hecto", "h", 2), - new Tuple<string, string, int>("deca", "da", 1), - new Tuple<string, string, int>("deci", "d", -1), - new Tuple<string, string, int>("centi", "c", -2), - new Tuple<string, string, int>("milli", "m", -3), - new Tuple<string, string, int>("micro", "μ", -6), - new Tuple<string, string, int>("nano", "n", -9), - new Tuple<string, string, int>("pico", "p", -12), - new Tuple<string, string, int>("femto", "f", -15), - new Tuple<string, string, int>("atto", "a", -18), - new Tuple<string, string, int>("zepto", "z", -21), - new Tuple<string, string, int>("yocto", "y", -24) - ]; - - public static void AddSupport<T>(T unitObject, string[] unitShortNames, string[] unitLongNames, DecimalUnitConverterBase<T>.DecimalFuncMap funcFrom, DecimalUnitConverterBase<T>.DecimalFuncMap funcTo, DecimalUnitConverterBase<T>.NameMap nameMap) where T : struct { - int enumCounter = 1000 + Factors.Count * (int) (object) unitObject; - - Func<decimal, decimal> convertFrom = funcFrom[unitObject]; - Func<decimal, decimal> convertTo = funcTo[unitObject]; - - foreach ((string unitLongNamePrefix, string unitShotNamePrefix, int exponent) in Factors) { - T enumObject = (T) (object) enumCounter++; - - foreach (string unitShortName in unitShortNames) { - nameMap.Add(unitShotNamePrefix + unitShortName, enumObject); - } - - foreach (string unitLongName in unitLongNames) { - nameMap.Add(unitLongNamePrefix + unitLongName, enumObject); - } - - funcFrom.Add(enumObject, val => convertFrom(val) * (decimal) Math.Pow(10, -exponent)); - funcTo.Add(enumObject, val => convertTo(val) * (decimal) Math.Pow(10, exponent)); - } - } - - public static void AddSupportCustom<T>(T unitObject, string[] unitShortNames, string[] unitLongNames, DecimalUnitConverterBase<T>.DecimalFuncMap funcFrom, DecimalUnitConverterBase<T>.DecimalFuncMap funcTo, DecimalUnitConverterBase<T>.NameMap nameMap, ExtededProperties extendedProps) where T : struct { - int enumCounter = 1000 + Factors.Count * (int) (object) unitObject; - - Func<decimal, decimal> convertFrom = funcFrom[unitObject]; - Func<decimal, decimal> convertTo = funcTo[unitObject]; - - foreach ((string unitLongNamePrefix, string unitShortNamePrefix, int exponent) in Factors) { - if (extendedProps.FactorPredicate != null && !extendedProps.FactorPredicate(exponent)) { - continue; - } - - T enumObject = (T) (object) enumCounter++; - - foreach (string unitShortName in unitShortNames) { - nameMap.Add(unitShortNamePrefix + unitShortName, enumObject); - } - - foreach (string unitLongName in unitLongNames) { - nameMap.Add(unitLongNamePrefix + unitLongName, enumObject); - } - - Func<decimal> genFrom = extendedProps.FromFunctionGenerator(exponent); - Func<decimal> genTo = extendedProps.ToFunctionGenerator(exponent); - - funcFrom.Add(enumObject, val => convertFrom(val) * genFrom()); - funcTo.Add(enumObject, val => convertTo(val) * genTo()); - } - } - - internal sealed class ExtededProperties { - public required Predicate<int> FactorPredicate { get; init; } - public required Func<int, Func<decimal>> FromFunctionGenerator { get; init; } - public required Func<int, Func<decimal>> ToFunctionGenerator { get; init; } - } -} diff --git a/AppMeme/App.cs b/AppMeme/App.cs deleted file mode 100644 index f4fdd34..0000000 --- a/AppMeme/App.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using Base; - -namespace AppMeme; - -public sealed class App : IApp { - private static readonly Dictionary<string, string> Map = new () { - { "shrug", @"¯\_(ツ)_/¯" }, - { "lenny", @"( ͡° ͜ʖ ͡°)" }, - { "flip", @"(╯°□°)╯︵ ┻━┻" }, - { "tableflip", @"(╯°□°)╯︵ ┻━┻" } - }; - - public string[] RecognizedNames => [ - "meme" - ]; - - public MatchConfidence GetConfidence(Command cmd) { - return Map.ContainsKey(cmd.Text) ? MatchConfidence.Full : MatchConfidence.None; - } - - public string ProcessCommand(Command cmd) { - return Map[cmd.Text]; - } -} diff --git a/AppMeme/AppMeme.csproj b/AppMeme/AppMeme.csproj deleted file mode 100644 index 18e4cc9..0000000 --- a/AppMeme/AppMeme.csproj +++ /dev/null @@ -1,11 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <OutputType>Library</OutputType> - </PropertyGroup> - - <ItemGroup> - <ProjectReference Include="..\Base\Base.csproj" /> - </ItemGroup> - -</Project> diff --git a/AppWindows/App.cs b/AppWindows/App.cs deleted file mode 100644 index aee4e92..0000000 --- a/AppWindows/App.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Linq; -using AppSys.Handlers; -using Base; - -namespace AppSys; - -public sealed class App : IApp { - private static readonly IHandler[] Handlers = [ - new HandlerProcesses() - ]; - - public string[] RecognizedNames => [ - "sys", - "os", - "win" - ]; - - public MatchConfidence GetConfidence(Command cmd) { - return Handlers.Any(handler => handler.Matches(cmd)) ? MatchConfidence.Full : MatchConfidence.None; - } - - public string? ProcessCommand(Command cmd) { - return Handlers.First(handler => handler.Matches(cmd)).Handle(cmd); - } -} diff --git a/AppWindows/AppSys.csproj b/AppWindows/AppSys.csproj deleted file mode 100644 index 18e4cc9..0000000 --- a/AppWindows/AppSys.csproj +++ /dev/null @@ -1,11 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <OutputType>Library</OutputType> - </PropertyGroup> - - <ItemGroup> - <ProjectReference Include="..\Base\Base.csproj" /> - </ItemGroup> - -</Project> diff --git a/AppWindows/Handlers/HandlerProcesses.cs b/AppWindows/Handlers/HandlerProcesses.cs deleted file mode 100644 index c68beee..0000000 --- a/AppWindows/Handlers/HandlerProcesses.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text; -using Base; - -namespace AppSys.Handlers; - -sealed class HandlerProcesses : IHandler { - public bool Matches(Command cmd) { - return cmd.Text.StartsWith("kill ", StringComparison.InvariantCultureIgnoreCase); - } - - public string Handle(Command cmd) { - string[] processNames = cmd.Text["kill ".Length..].Split(',', ';'); - int succeeded = 0, failed = 0; - - foreach (string processName in processNames) { - try { - Process[] processes = Process.GetProcessesByName(processName.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) ? processName[..^4] : processName); - - foreach (Process process in processes) { - try { - process.Kill(); - ++succeeded; - } catch { - ++failed; - } - - process.Close(); - } - } catch { - ++failed; - } - } - - if (succeeded == 0 && failed == 0 && (cmd.Text.Equals("kill me", StringComparison.InvariantCultureIgnoreCase) || cmd.Text.StartsWith("kill me ", StringComparison.InvariantCultureIgnoreCase) || cmd.Text.StartsWith("kill me,", StringComparison.InvariantCultureIgnoreCase))) { - return "No."; - } - - var build = new StringBuilder(); - build.Append("Killed ").Append(succeeded).Append(" process").Append(succeeded == 1 ? "" : "es"); - - if (failed > 0) { - build.Append(", failed ").Append(failed); - } - - return build.Append('.').ToString(); - } -} diff --git a/AppWindows/IHandler.cs b/AppWindows/IHandler.cs deleted file mode 100644 index 8327fbb..0000000 --- a/AppWindows/IHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Base; - -namespace AppSys; - -interface IHandler { - bool Matches(Command cmd); - string? Handle(Command cmd); -} diff --git a/Base/Base.csproj b/Base/Base.csproj deleted file mode 100644 index d627524..0000000 --- a/Base/Base.csproj +++ /dev/null @@ -1,7 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <OutputType>Library</OutputType> - </PropertyGroup> - -</Project> diff --git a/Base/Command.cs b/Base/Command.cs deleted file mode 100644 index fcbb102..0000000 --- a/Base/Command.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Linq; -using System.Text.RegularExpressions; -using Base.Utils; - -namespace Base; - -public sealed class Command(string text) { - private static readonly Regex RegexBalancedBrackets = new (RegexUtils.Balance(@"\[", @"\]"), RegexOptions.Compiled); - private static readonly Regex RegexBalancedParentheses = new (RegexUtils.Balance(@"\(", @"\)"), RegexOptions.Compiled); - - public string Text { get; } = text; - - public string? PotentialAppName { - get { - int firstSpace = Text.IndexOf(' '); - - if (firstSpace == -1) { - return null; - } - - string firstToken = Text[..firstSpace]; - - if (!firstToken.All(char.IsLetter)) { - return null; - } - - return firstToken; - } - } - - public bool IsSingleToken => !Text.Contains(' '); - - public Command ReplaceBrackets(MatchEvaluator evaluator) { - return new Command(RegexBalancedParentheses.Replace(RegexBalancedBrackets.Replace(Text, evaluator), evaluator)); - } -} diff --git a/Base/CommandEventArgs.cs b/Base/CommandEventArgs.cs deleted file mode 100644 index a320d9c..0000000 --- a/Base/CommandEventArgs.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Base; - -public sealed class CommandEventArgs(string text) : EventArgs { - public Command Command { get; private set; } = new (text); -} diff --git a/Base/IApp.cs b/Base/IApp.cs deleted file mode 100644 index 13bc6dd..0000000 --- a/Base/IApp.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Base; - -public interface IApp { - string[] RecognizedNames { get; } - - MatchConfidence GetConfidence(Command cmd); - string? ProcessCommand(Command cmd); -} diff --git a/Base/MatchConfidence.cs b/Base/MatchConfidence.cs deleted file mode 100644 index 65a5cba..0000000 --- a/Base/MatchConfidence.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Base; - -public enum MatchConfidence { - None = 0, - Low = 1, - Possible = 2, - Full = 3 -} diff --git a/Base/Utils/RegexUtils.cs b/Base/Utils/RegexUtils.cs deleted file mode 100644 index 9a1aab1..0000000 --- a/Base/Utils/RegexUtils.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text; -using System.Text.RegularExpressions; - -namespace Base.Utils; - -public static class RegexUtils { - public const RegexOptions Text = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled; - - internal static string Balance(string escapedStart, string escapedEnd) { // \(((?>[^()]+|\((?<n>)|\)(?<-n>))+(?(n)(?!)))\) - return new StringBuilder() - .Append(escapedStart) - .Append("((?>[^") - .Append(escapedStart) - .Append(escapedEnd) - .Append("]+|") - .Append(escapedStart) - .Append("(?<n>)|") - .Append(escapedEnd) - .Append("(?<-n>))+(?(n)(?!)))") - .Append(escapedEnd) - .ToString(); - } -} diff --git a/Query.sln b/Query.sln index 8895696..d678aa1 100644 --- a/Query.sln +++ b/Query.sln @@ -5,18 +5,6 @@ VisualStudioVersion = 12.0.40629.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Query", "Query\Query.csproj", "{1A2176AF-3885-4619-8F85-4C751A5ABA8F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Base", "Base\Base.csproj", "{66CF4F71-50DD-4C65-AB96-35D1193FFB50}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apps", "Apps", "{6434DC0B-7270-4002-857B-53F8839C9CA6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppCalc", "AppCalc\AppCalc.csproj", "{C7A21640-CAF3-40E8-AA6A-793181BD28AA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppConv", "AppConv\AppConv.csproj", "{1AC5280A-81D1-4E02-9122-DB358734FFB4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppMeme", "AppMeme\AppMeme.csproj", "{4E27ADC4-FCBE-451E-A1F9-8050DA22A6E1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppSys", "AppWindows\AppSys.csproj", "{E71AFA58-A144-4170-AF7B-05730C04CF59}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calculator", "Calculator\Calculator.csproj", "{C4B1529F-943C-4C30-A40A-4A4C248FC92F}" EndProject Global @@ -29,26 +17,6 @@ Global {1A2176AF-3885-4619-8F85-4C751A5ABA8F}.Debug|Any CPU.Build.0 = Debug|Any CPU {1A2176AF-3885-4619-8F85-4C751A5ABA8F}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A2176AF-3885-4619-8F85-4C751A5ABA8F}.Release|Any CPU.Build.0 = Release|Any CPU - {66CF4F71-50DD-4C65-AB96-35D1193FFB50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66CF4F71-50DD-4C65-AB96-35D1193FFB50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66CF4F71-50DD-4C65-AB96-35D1193FFB50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66CF4F71-50DD-4C65-AB96-35D1193FFB50}.Release|Any CPU.Build.0 = Release|Any CPU - {C7A21640-CAF3-40E8-AA6A-793181BD28AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C7A21640-CAF3-40E8-AA6A-793181BD28AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C7A21640-CAF3-40E8-AA6A-793181BD28AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C7A21640-CAF3-40E8-AA6A-793181BD28AA}.Release|Any CPU.Build.0 = Release|Any CPU - {1AC5280A-81D1-4E02-9122-DB358734FFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1AC5280A-81D1-4E02-9122-DB358734FFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1AC5280A-81D1-4E02-9122-DB358734FFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1AC5280A-81D1-4E02-9122-DB358734FFB4}.Release|Any CPU.Build.0 = Release|Any CPU - {4E27ADC4-FCBE-451E-A1F9-8050DA22A6E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E27ADC4-FCBE-451E-A1F9-8050DA22A6E1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E27ADC4-FCBE-451E-A1F9-8050DA22A6E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E27ADC4-FCBE-451E-A1F9-8050DA22A6E1}.Release|Any CPU.Build.0 = Release|Any CPU - {E71AFA58-A144-4170-AF7B-05730C04CF59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E71AFA58-A144-4170-AF7B-05730C04CF59}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E71AFA58-A144-4170-AF7B-05730C04CF59}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E71AFA58-A144-4170-AF7B-05730C04CF59}.Release|Any CPU.Build.0 = Release|Any CPU {C4B1529F-943C-4C30-A40A-4A4C248FC92F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C4B1529F-943C-4C30-A40A-4A4C248FC92F}.Debug|Any CPU.Build.0 = Debug|Any CPU {C4B1529F-943C-4C30-A40A-4A4C248FC92F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -58,9 +26,5 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {C7A21640-CAF3-40E8-AA6A-793181BD28AA} = {6434DC0B-7270-4002-857B-53F8839C9CA6} - {1AC5280A-81D1-4E02-9122-DB358734FFB4} = {6434DC0B-7270-4002-857B-53F8839C9CA6} - {4E27ADC4-FCBE-451E-A1F9-8050DA22A6E1} = {6434DC0B-7270-4002-857B-53F8839C9CA6} - {E71AFA58-A144-4170-AF7B-05730C04CF59} = {6434DC0B-7270-4002-857B-53F8839C9CA6} EndGlobalSection EndGlobal diff --git a/Query/Apps/CalculatorApp.cs b/Query/Apps/CalculatorApp.cs new file mode 100644 index 0000000..a87b2ae --- /dev/null +++ b/Query/Apps/CalculatorApp.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Calculator; +using Calculator.Math; +using Calculator.Parser; + +namespace Query.Apps; + +sealed class CalculatorApp : IApp { + public bool TryRun(string command, [NotNullWhen(true)] out string? output) { + ImmutableArray<Token> tokens = new Tokenizer(command).Scan(); + Expression expression = new Parser(tokens).Parse(); + NumberWithUnit result = expression.Accept(new CalculatorExpressionVisitor()); + + output = result.ToString(); + return true; + } +} diff --git a/Query/Apps/KillProcessApp.cs b/Query/Apps/KillProcessApp.cs new file mode 100644 index 0000000..0efd120 --- /dev/null +++ b/Query/Apps/KillProcessApp.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace Query.Apps; + +sealed class KillProcessApp : IApp { + public bool TryRun(string command, [NotNullWhen(true)] out string? output) { + string[] args = command.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + if (args is not ["kill", _, ..]) { + output = null; + return false; + } + + int succeeded = 0, failed = 0; + + foreach (string processName in args[1..]) { + try { + Process[] processes = Process.GetProcessesByName(processName.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) ? processName[..^4] : processName); + + foreach (Process process in processes) { + try { + process.Kill(); + ++succeeded; + } catch { + ++failed; + } + + process.Close(); + } + } catch { + ++failed; + } + } + + var build = new StringBuilder(); + build.Append("Killed ").Append(succeeded).Append(" process").Append(succeeded == 1 ? "" : "es"); + + if (failed > 0) { + build.Append(", failed ").Append(failed); + } + + output = build.Append('.').ToString(); + return true; + } +} diff --git a/Query/Apps/MemeApp.cs b/Query/Apps/MemeApp.cs new file mode 100644 index 0000000..20e3c72 --- /dev/null +++ b/Query/Apps/MemeApp.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Query.Apps; + +sealed class MemeApp : IApp { + private static readonly Dictionary<string, string> Map = new () { + { "shrug", @"¯\_(ツ)_/¯" }, + { "lenny", @"( ͡° ͜ʖ ͡°)" }, + { "flip", @"(╯°□°)╯︵ ┻━┻" }, + { "tableflip", @"(╯°□°)╯︵ ┻━┻" } + }; + + public bool TryRun(string command, [NotNullWhen(true)] out string? output) { + return Map.TryGetValue(command, out output); + } +} diff --git a/Query/Command/CommandEventArgs.cs b/Query/Command/CommandEventArgs.cs new file mode 100644 index 0000000..6d6dd9a --- /dev/null +++ b/Query/Command/CommandEventArgs.cs @@ -0,0 +1,7 @@ +using System; + +namespace Query.Command; + +sealed class CommandEventArgs(string command) : EventArgs { + public string Command => command; +}; diff --git a/Base/CommandException.cs b/Query/Command/CommandException.cs similarity index 72% rename from Base/CommandException.cs rename to Query/Command/CommandException.cs index 0851ad7..29224fa 100644 --- a/Base/CommandException.cs +++ b/Query/Command/CommandException.cs @@ -1,8 +1,8 @@ using System; -namespace Base; +namespace Query.Command; -public sealed class CommandException : Exception { +sealed class CommandException : Exception { public CommandException(string message) : base(message) {} public CommandException(string message, Exception innerException) : base(message, innerException) {} } diff --git a/Query/Core/CommandHistory.cs b/Query/Command/CommandHistory.cs similarity index 94% rename from Query/Core/CommandHistory.cs rename to Query/Command/CommandHistory.cs index 26adfcb..395c101 100644 --- a/Query/Core/CommandHistory.cs +++ b/Query/Command/CommandHistory.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Query.Core; +namespace Query.Command; sealed class CommandHistory { private readonly List<string> queries = []; diff --git a/Query/Command/CommandProcessor.cs b/Query/Command/CommandProcessor.cs new file mode 100644 index 0000000..6c1b735 --- /dev/null +++ b/Query/Command/CommandProcessor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Query.Apps; + +namespace Query.Command; + +sealed class CommandProcessor { + private readonly List<IApp> apps = [ + new MemeApp(), + new KillProcessApp(), + new CalculatorApp() + ]; + + public string Run(string command) { + try { + foreach (IApp app in apps) { + if (app.TryRun(command, out string? output)) { + return output; + } + } + + return "Unknown command."; + } catch (Exception e) { + throw new CommandException(e.Message, e); + } + } +} diff --git a/Query/Core/CommandProcessor.cs b/Query/Core/CommandProcessor.cs deleted file mode 100644 index 16bb561..0000000 --- a/Query/Core/CommandProcessor.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Base; - -namespace Query.Core; - -sealed class CommandProcessor { - private readonly Dictionary<string, IApp> appNames = new (8); - private readonly HashSet<IApp> appSet = []; - - public Func<string, bool>? SingleTokenProcessor { get; init; } - - public void AddApp<T>() where T : IApp, new() { - IApp app = new T(); - - foreach (string name in app.RecognizedNames) { - appNames.Add(name, app); - appSet.Add(app); - } - } - - public string? Run(Command cmd) { - cmd = cmd.ReplaceBrackets(match => Run(new Command(match.Groups[1].Value))!); - - string? appName = cmd.PotentialAppName; - - if (appName != null && appNames.TryGetValue(appName.ToLowerInvariant(), out var app)) { - return app.ProcessCommand(new Command(cmd.Text[(appName.Length + 1)..])); - } - - if (cmd.IsSingleToken && SingleTokenProcessor != null && SingleTokenProcessor(cmd.Text)) { - return null; - } - - var list = appSet.Select(iapp => new { App = iapp, Confidence = iapp.GetConfidence(cmd) }).OrderByDescending(static obj => obj.Confidence).Where(static obj => obj.Confidence != MatchConfidence.None).ToList(); - - if (list.Count == 0) { - throw new CommandException("Could not find any suitable app, please write the app name and press Up Arrow."); - } - else if (list.Count == 1) { - app = list[0].App; - } - else { - List<IApp> plausible = [list[0].App]; - MatchConfidence topConfidence = list[0].Confidence; - - for (int index = 1; index < list.Count; index++) { - if (list[index].Confidence == topConfidence) { - plausible.Add(list[index].App); - } - } - - if (plausible.Count == 1) { - app = plausible.First(); - } - else { - throw new CommandException("Command is ambiguous, please write the app name and press Up Arrow. Suggested apps: " + string.Join(", ", plausible.Select(static iapp => iapp.RecognizedNames.First()))); - } - } - - return app.ProcessCommand(cmd); - } -} diff --git a/Query/Core/KeyboardHook.cs b/Query/Form/KeyboardHook.cs similarity index 98% rename from Query/Core/KeyboardHook.cs rename to Query/Form/KeyboardHook.cs index 6ec89ec..0f28e00 100644 --- a/Query/Core/KeyboardHook.cs +++ b/Query/Form/KeyboardHook.cs @@ -2,7 +2,7 @@ using System.Runtime.InteropServices; using System.Windows.Forms; -namespace Query.Core; +namespace Query.Form; sealed class KeyboardHook { public event EventHandler? Triggered; diff --git a/Query/MainForm.Designer.cs b/Query/Form/MainForm.Designer.cs similarity index 96% rename from Query/MainForm.Designer.cs rename to Query/Form/MainForm.Designer.cs index 2390ac2..5e6618b 100644 --- a/Query/MainForm.Designer.cs +++ b/Query/Form/MainForm.Designer.cs @@ -1,4 +1,4 @@ -namespace Query { +namespace Query.Form { sealed partial class MainForm { /// <summary> /// Required designer variable. @@ -25,8 +25,8 @@ private void InitializeComponent() { this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); - this.queryLog = new Query.Controls.QueryHistoryLog(); - this.queryBox = new Query.Controls.QueryTextBox(); + this.queryLog = new QueryHistoryLog(); + this.queryBox = new QueryTextBox(); this.trayIcon = new System.Windows.Forms.NotifyIcon(this.components); this.contextMenuTray = new System.Windows.Forms.ContextMenuStrip(this.components); this.showToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -124,8 +124,8 @@ #endregion - private Query.Controls.QueryTextBox queryBox; - private Controls.QueryHistoryLog queryLog; + private QueryTextBox queryBox; + private QueryHistoryLog queryLog; private System.Windows.Forms.NotifyIcon trayIcon; private System.Windows.Forms.ContextMenuStrip contextMenuTray; private System.Windows.Forms.ToolStripMenuItem showToolStripMenuItem; diff --git a/Query/MainForm.cs b/Query/Form/MainForm.cs similarity index 68% rename from Query/MainForm.cs rename to Query/Form/MainForm.cs index 3a36119..88d7d8d 100644 --- a/Query/MainForm.cs +++ b/Query/Form/MainForm.cs @@ -1,13 +1,11 @@ using System; using System.Drawing; using System.Windows.Forms; -using Base; -using Query.Controls; -using Query.Core; +using Query.Command; -namespace Query; +namespace Query.Form; -sealed partial class MainForm : Form { +sealed partial class MainForm : System.Windows.Forms.Form { private readonly CommandProcessor processor; private readonly CommandHistory history; @@ -19,14 +17,7 @@ sealed partial class MainForm : Form { public MainForm() { InitializeComponent(); - processor = new CommandProcessor { - SingleTokenProcessor = ProcessSingleToken - }; - - processor.AddApp<AppCalc.App>(); - processor.AddApp<AppConv.App>(); - processor.AddApp<AppMeme.App>(); - processor.AddApp<AppSys.App>(); + processor = new CommandProcessor(); history = new CommandHistory(); queryBox.Setup(history, str => queryLog.AddEntry(str, QueryHistoryLog.EntryType.Information)); @@ -107,42 +98,33 @@ sealed partial class MainForm : Form { } private void queryBox_CommandRan(object? sender, CommandEventArgs e) { - try { - string? result = processor.Run(e.Command); + string command = e.Command; - if (result != null) { - queryLog.AddEntry("> " + e.Command.Text, QueryHistoryLog.EntryType.UserInput); - history.AddQuery(e.Command.Text); + if (command is "exit" or "quit") { + Application.Exit(); + } + else if (command == "clear") { + queryLog.ClearEntries(); + history.Clear(); + } + else if (command == "hide") { + Hide(); + } + else { + try { + string result = processor.Run(command); + + queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput); + history.AddQuery(command); queryLog.AddEntry(result, QueryHistoryLog.EntryType.CommandResult); history.AddResult(result); + } catch (CommandException ex) { + queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput); + history.AddQuery(command); + + queryLog.AddEntry(ex.Message, QueryHistoryLog.EntryType.Error); } - } catch (CommandException ex) { - queryLog.AddEntry("> " + e.Command.Text, QueryHistoryLog.EntryType.UserInput); - history.AddQuery(e.Command.Text); - - queryLog.AddEntry(ex.Message, QueryHistoryLog.EntryType.Error); - } - } - - private bool ProcessSingleToken(string token) { - switch (token) { - case "exit": - case "quit": - Application.Exit(); - return true; - - case "clear": - queryLog.ClearEntries(); - history.Clear(); - return true; - - case "hide": - Hide(); - return true; - - default: - return false; } } } diff --git a/Query/MainForm.resx b/Query/Form/MainForm.resx similarity index 100% rename from Query/MainForm.resx rename to Query/Form/MainForm.resx diff --git a/Query/Controls/QueryHistoryLog.Designer.cs b/Query/Form/QueryHistoryLog.Designer.cs similarity index 98% rename from Query/Controls/QueryHistoryLog.Designer.cs rename to Query/Form/QueryHistoryLog.Designer.cs index 2858324..cd36149 100644 --- a/Query/Controls/QueryHistoryLog.Designer.cs +++ b/Query/Form/QueryHistoryLog.Designer.cs @@ -1,4 +1,4 @@ -namespace Query.Controls { +namespace Query.Form { partial class QueryHistoryLog { /// <summary> /// Required designer variable. diff --git a/Query/Controls/QueryHistoryLog.cs b/Query/Form/QueryHistoryLog.cs similarity index 97% rename from Query/Controls/QueryHistoryLog.cs rename to Query/Form/QueryHistoryLog.cs index 167988d..c20ad74 100644 --- a/Query/Controls/QueryHistoryLog.cs +++ b/Query/Form/QueryHistoryLog.cs @@ -2,7 +2,7 @@ using System.Drawing; using System.Windows.Forms; -namespace Query.Controls; +namespace Query.Form; sealed partial class QueryHistoryLog : UserControl { public enum EntryType { diff --git a/Query/Controls/QueryTextBox.Designer.cs b/Query/Form/QueryTextBox.Designer.cs similarity index 98% rename from Query/Controls/QueryTextBox.Designer.cs rename to Query/Form/QueryTextBox.Designer.cs index 46234f2..17ba2a2 100644 --- a/Query/Controls/QueryTextBox.Designer.cs +++ b/Query/Form/QueryTextBox.Designer.cs @@ -1,4 +1,4 @@ -namespace Query.Controls { +namespace Query.Form { partial class QueryTextBox { /// <summary> /// Required designer variable. diff --git a/Query/Controls/QueryTextBox.cs b/Query/Form/QueryTextBox.cs similarity index 98% rename from Query/Controls/QueryTextBox.cs rename to Query/Form/QueryTextBox.cs index ae2164c..11d6be7 100644 --- a/Query/Controls/QueryTextBox.cs +++ b/Query/Form/QueryTextBox.cs @@ -2,10 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Forms; -using Base; -using Query.Core; +using Query.Command; -namespace Query.Controls; +namespace Query.Form; sealed partial class QueryTextBox : UserControl { public event EventHandler<CommandEventArgs>? CommandRan; diff --git a/Query/IApp.cs b/Query/IApp.cs new file mode 100644 index 0000000..1be7593 --- /dev/null +++ b/Query/IApp.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Query; + +interface IApp { + bool TryRun(string command, [NotNullWhen(true)] out string? output); +} diff --git a/Query/Program.cs b/Query/Program.cs index ae95f9a..753d7cc 100644 --- a/Query/Program.cs +++ b/Query/Program.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Forms; +using Query.Form; namespace Query; diff --git a/Query/Query.csproj b/Query/Query.csproj index fea2dbf..8129acd 100644 --- a/Query/Query.csproj +++ b/Query/Query.csproj @@ -15,14 +15,13 @@ <Compile Update="Controls\QueryTextBox.cs"> <SubType>UserControl</SubType> </Compile> + <Compile Update="Form\MainForm.cs"> + <SubType>Form</SubType> + </Compile> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\AppCalc\AppCalc.csproj" /> - <ProjectReference Include="..\AppConv\AppConv.csproj" /> - <ProjectReference Include="..\AppMeme\AppMeme.csproj" /> - <ProjectReference Include="..\AppWindows\AppSys.csproj" /> - <ProjectReference Include="..\Base\Base.csproj" /> + <ProjectReference Include="..\Calculator\Calculator.csproj" /> </ItemGroup> <ItemGroup>