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>