1
0
mirror of https://github.com/chylex/Query.git synced 2025-07-05 06:38:54 +02:00

Compare commits

..

5 Commits

26 changed files with 554 additions and 588 deletions

View File

@ -7,7 +7,7 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
public NumberWithUnit VisitNumber(Expression.Number number) { public NumberWithUnit VisitNumber(Expression.Number number) {
return new NumberWithUnit(number.NumberToken.Value, null); return new NumberWithUnit(number.NumberToken.Value, null);
} }
public NumberWithUnit VisitNumbersWithUnits(Expression.NumbersWithUnits numbersWithUnits) { public NumberWithUnit VisitNumbersWithUnits(Expression.NumbersWithUnits numbersWithUnits) {
NumberWithUnit result = new Number.Rational(0); NumberWithUnit result = new Number.Rational(0);
@ -17,24 +17,24 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
return result; return result;
} }
public NumberWithUnit VisitGrouping(Expression.Grouping grouping) { public NumberWithUnit VisitGrouping(Expression.Grouping grouping) {
return Evaluate(grouping.Expression); return Evaluate(grouping.Expression);
} }
public NumberWithUnit VisitUnary(Expression.Unary unary) { public NumberWithUnit VisitUnary(Expression.Unary unary) {
(Token.Simple op, Expression right) = unary; (Token.Simple op, Expression right) = unary;
return op.Type switch { return op.Type switch {
SimpleTokenType.PLUS => +Evaluate(right), SimpleTokenType.PLUS => +Evaluate(right),
SimpleTokenType.MINUS => -Evaluate(right), SimpleTokenType.MINUS => -Evaluate(right),
_ => throw new CalculatorException("Unsupported unary operator: " + op.Type) _ => throw new CalculatorException("Unsupported unary operator: " + op.Type)
}; };
} }
public NumberWithUnit VisitBinary(Expression.Binary binary) { public NumberWithUnit VisitBinary(Expression.Binary binary) {
(Expression left, Token.Simple op, Expression right) = binary; (Expression left, Token.Simple op, Expression right) = binary;
return op.Type switch { return op.Type switch {
SimpleTokenType.PLUS => Evaluate(left) + Evaluate(right), SimpleTokenType.PLUS => Evaluate(left) + Evaluate(right),
SimpleTokenType.MINUS => Evaluate(left) - Evaluate(right), SimpleTokenType.MINUS => Evaluate(left) - Evaluate(right),
@ -45,10 +45,10 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
_ => throw new CalculatorException("Unsupported binary operator: " + op.Type) _ => throw new CalculatorException("Unsupported binary operator: " + op.Type)
}; };
} }
public NumberWithUnit VisitUnitAssignment(Expression.UnitAssignment unitAssignment) { public NumberWithUnit VisitUnitAssignment(Expression.UnitAssignment unitAssignment) {
(Expression left, Unit right) = unitAssignment; (Expression left, Unit right) = unitAssignment;
NumberWithUnit number = Evaluate(left); NumberWithUnit number = Evaluate(left);
if (number.Unit is null) { if (number.Unit is null) {
@ -58,13 +58,13 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
throw new CalculatorException("Expression already has a unit, cannot assign a new unit: " + right); throw new CalculatorException("Expression already has a unit, cannot assign a new unit: " + right);
} }
} }
public NumberWithUnit VisitUnitConversion(Expression.UnitConversion unitConversion) { public NumberWithUnit VisitUnitConversion(Expression.UnitConversion unitConversion) {
(Expression left, Unit unit) = unitConversion; (Expression left, Unit unit) = unitConversion;
return Evaluate(left).ConvertTo(unit); return Evaluate(left).ConvertTo(unit);
} }
private NumberWithUnit Evaluate(Expression expression) { private NumberWithUnit Evaluate(Expression expression) {
return expression.Accept(this); return expression.Accept(this);
} }

View File

@ -18,63 +18,77 @@ public abstract record Number : IAdditionOperators<Number, Number, Number>,
IAdditiveIdentity<Number, Number.Rational>, IAdditiveIdentity<Number, Number.Rational>,
IMultiplicativeIdentity<Number, Number.Rational> { IMultiplicativeIdentity<Number, Number.Rational> {
protected abstract decimal AsDecimal { get; } protected abstract decimal AsDecimal { get; }
public abstract Number Pow(Number exponent); public abstract Number Pow(Number exponent);
public abstract string ToString(IFormatProvider? formatProvider); public abstract string ToString(IFormatProvider? formatProvider);
public sealed override string ToString() { public sealed override string ToString() {
return ToString(CultureInfo.InvariantCulture); return ToString(CultureInfo.InvariantCulture);
} }
/// <summary> /// <summary>
/// Represents an integer number with arbitrary precision. /// Represents an integer number with arbitrary precision.
/// </summary> /// </summary>
public sealed record Rational(BigRational Value) : Number { public sealed record Rational(BigRational Value) : Number {
protected override decimal AsDecimal => (decimal) Value; protected override decimal AsDecimal => (decimal) Value;
public override Number Pow(Number exponent) { public override Number Pow(Number exponent) {
if (exponent is Rational { Value: {} rationalExponent }) { if (exponent is Rational { Value: {} rationalExponent }) {
Fraction fractionExponent = rationalExponent.GetImproperFraction(); Fraction fractionExponent = rationalExponent.GetImproperFraction();
if (fractionExponent.Denominator == 1 && fractionExponent.Numerator >= 0) {
if (fractionExponent.Numerator >= 0 && fractionExponent.Denominator == 1) {
try { try {
return new Rational(BigRational.Pow(Value, fractionExponent.Numerator)); return new Rational(BigRational.Pow(Value, fractionExponent.Numerator));
} catch (OverflowException) {} } catch (OverflowException) {}
} }
if (fractionExponent.Numerator == 1 && fractionExponent.Denominator > 1) {
Number result = PowAsDecimal(exponent);
BigRational assumedPerfectPowerRoot = new BigRational(decimal.Floor(result.AsDecimal));
BigRational assumedPerfectPower = BigRational.Pow(assumedPerfectPowerRoot, fractionExponent.Denominator);
return assumedPerfectPower == Value ? assumedPerfectPowerRoot : result;
}
}
return PowAsDecimal(exponent);
Number PowAsDecimal(Number number) {
return new Decimal(AsDecimal).Pow(number);
} }
return new Decimal(AsDecimal).Pow(exponent);
} }
public override string ToString(IFormatProvider? formatProvider) { public override string ToString(IFormatProvider? formatProvider) {
Fraction fraction = Value.GetImproperFraction(); Fraction fraction = Value.GetImproperFraction();
return fraction.Denominator == 1 ? fraction.Numerator.ToString(formatProvider) : AsDecimal.ToString(formatProvider); return fraction.Denominator == 1 ? fraction.Numerator.ToString(formatProvider) : AsDecimal.ToString(formatProvider);
} }
} }
/// <summary> /// <summary>
/// Represents a decimal number with limited precision. /// Represents a decimal number with limited precision.
/// </summary> /// </summary>
public sealed record Decimal(decimal Value) : Number { public sealed record Decimal(decimal Value) : Number {
public Decimal(double value) : this((decimal) value) {} public Decimal(double value) : this((decimal) value) {}
protected override decimal AsDecimal => Value; protected override decimal AsDecimal => Value;
public override Number Pow(Number exponent) { public override Number Pow(Number exponent) {
double doubleValue = (double) Value; double doubleValue = (double) Value;
double doubleExponent = (double) exponent.AsDecimal; double doubleExponent = (double) exponent.AsDecimal;
return new Decimal(System.Math.Pow(doubleValue, doubleExponent)); return new Decimal(System.Math.Pow(doubleValue, doubleExponent));
} }
public override string ToString(IFormatProvider? formatProvider) { public override string ToString(IFormatProvider? formatProvider) {
return Value.ToString(formatProvider); return Value.ToString(formatProvider);
} }
} }
public static implicit operator Number(BigRational value) { public static implicit operator Number(BigRational value) {
return new Rational(value); return new Rational(value);
} }
public static implicit operator Number(int value) { public static implicit operator Number(int value) {
return new Rational(value); return new Rational(value);
} }
@ -85,65 +99,65 @@ public abstract record Number : IAdditionOperators<Number, Number, Number>,
public static Rational AdditiveIdentity => new (BigInteger.Zero); public static Rational AdditiveIdentity => new (BigInteger.Zero);
public static Rational MultiplicativeIdentity => new (BigInteger.One); public static Rational MultiplicativeIdentity => new (BigInteger.One);
public static Number Add(Number left, Number right) { public static Number Add(Number left, Number right) {
return left + right; return left + right;
} }
public static Number Subtract(Number left, Number right) { public static Number Subtract(Number left, Number right) {
return left - right; return left - right;
} }
public static Number Multiply(Number left, Number right) { public static Number Multiply(Number left, Number right) {
return left * right; return left * right;
} }
public static Number Divide(Number left, Number right) { public static Number Divide(Number left, Number right) {
return left / right; return left / right;
} }
public static Number Remainder(Number left, Number right) { public static Number Remainder(Number left, Number right) {
return left % right; return left % right;
} }
public static Number Pow(Number left, Number right) { public static Number Pow(Number left, Number right) {
return left.Pow(right); return left.Pow(right);
} }
public static Number operator +(Number value) { public static Number operator +(Number value) {
return value; return value;
} }
public static Number operator -(Number value) { public static Number operator -(Number value) {
return Operate(value, BigRational.Negate, decimal.Negate); return Operate(value, BigRational.Negate, decimal.Negate);
} }
public static Number operator +(Number left, Number right) { public static Number operator +(Number left, Number right) {
return Operate(left, right, BigRational.Add, decimal.Add); return Operate(left, right, BigRational.Add, decimal.Add);
} }
public static Number operator -(Number left, Number right) { public static Number operator -(Number left, Number right) {
return Operate(left, right, BigRational.Subtract, decimal.Subtract); return Operate(left, right, BigRational.Subtract, decimal.Subtract);
} }
public static Number operator *(Number left, Number right) { public static Number operator *(Number left, Number right) {
return Operate(left, right, BigRational.Multiply, decimal.Multiply); return Operate(left, right, BigRational.Multiply, decimal.Multiply);
} }
public static Number operator /(Number left, Number right) { public static Number operator /(Number left, Number right) {
return Operate(left, right, BigRational.Divide, decimal.Divide); return Operate(left, right, BigRational.Divide, decimal.Divide);
} }
public static Number operator %(Number left, Number right) { public static Number operator %(Number left, Number right) {
return Operate(left, right, BigRational.Mod, decimal.Remainder); return Operate(left, right, BigRational.Mod, decimal.Remainder);
} }
private static Number Operate(Number value, Func<BigRational, BigRational> rationalOperation, Func<decimal, decimal> decimalOperation) { private static Number Operate(Number value, Func<BigRational, BigRational> rationalOperation, Func<decimal, decimal> decimalOperation) {
return value is Rational rational return value is Rational rational
? new Rational(rationalOperation(rational.Value)) ? new Rational(rationalOperation(rational.Value))
: new Decimal(decimalOperation(value.AsDecimal)); : new Decimal(decimalOperation(value.AsDecimal));
} }
private static Number Operate(Number left, Number right, Func<BigRational, BigRational, BigRational> rationalOperation, Func<decimal, decimal, decimal> decimalOperation) { private static Number Operate(Number left, Number right, Func<BigRational, BigRational, BigRational> rationalOperation, Func<decimal, decimal, decimal> decimalOperation) {
return left is Rational leftRational && right is Rational rightRational return left is Rational leftRational && right is Rational rightRational
? new Rational(rationalOperation(leftRational.Value, rightRational.Value)) ? new Rational(rationalOperation(leftRational.Value, rightRational.Value))

View File

@ -24,28 +24,28 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
throw new ArithmeticException("Cannot convert '" + Unit + "' to '" + targetUnit + "'"); throw new ArithmeticException("Cannot convert '" + Unit + "' to '" + targetUnit + "'");
} }
} }
public string ToString(IFormatProvider? formatProvider) { public string ToString(IFormatProvider? formatProvider) {
string number = Number.ToString(formatProvider); string number = Number.ToString(formatProvider);
return Unit == null ? number : number + " " + Unit; return Unit == null ? number : number + " " + Unit;
} }
public override string ToString() { public override string ToString() {
return ToString(CultureInfo.InvariantCulture); return ToString(CultureInfo.InvariantCulture);
} }
public static implicit operator NumberWithUnit(Number number) { public static implicit operator NumberWithUnit(Number number) {
return new NumberWithUnit(number, null); return new NumberWithUnit(number, null);
} }
public static NumberWithUnit operator +(NumberWithUnit value) { public static NumberWithUnit operator +(NumberWithUnit value) {
return value with { Number = +value.Number }; return value with { Number = +value.Number };
} }
public static NumberWithUnit operator -(NumberWithUnit value) { public static NumberWithUnit operator -(NumberWithUnit value) {
return value with { Number = -value.Number }; return value with { Number = -value.Number };
} }
public static NumberWithUnit operator +(NumberWithUnit left, NumberWithUnit right) { public static NumberWithUnit operator +(NumberWithUnit left, NumberWithUnit right) {
return Operate(left, right, Number.Add, static (leftNumber, leftUnit, rightNumber, rightUnit) => { return Operate(left, right, Number.Add, static (leftNumber, leftUnit, rightNumber, rightUnit) => {
if (leftUnit == rightUnit) { if (leftUnit == rightUnit) {
@ -59,7 +59,7 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
} }
}); });
} }
public static NumberWithUnit operator -(NumberWithUnit left, NumberWithUnit right) { public static NumberWithUnit operator -(NumberWithUnit left, NumberWithUnit right) {
return Operate(left, right, Number.Subtract, static (leftNumber, leftUnit, rightNumber, rightUnit) => { return Operate(left, right, Number.Subtract, static (leftNumber, leftUnit, rightNumber, rightUnit) => {
if (leftUnit == rightUnit) { if (leftUnit == rightUnit) {
@ -73,23 +73,23 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
} }
}); });
} }
public static NumberWithUnit operator *(NumberWithUnit left, NumberWithUnit right) { public static NumberWithUnit operator *(NumberWithUnit left, NumberWithUnit right) {
return OperateWithoutUnits(left, right, Number.Multiply, "Cannot multiply"); return OperateWithoutUnits(left, right, Number.Multiply, "Cannot multiply");
} }
public static NumberWithUnit operator /(NumberWithUnit left, NumberWithUnit right) { public static NumberWithUnit operator /(NumberWithUnit left, NumberWithUnit right) {
return OperateWithoutUnits(left, right, Number.Divide, "Cannot divide"); return OperateWithoutUnits(left, right, Number.Divide, "Cannot divide");
} }
public static NumberWithUnit operator %(NumberWithUnit left, NumberWithUnit right) { public static NumberWithUnit operator %(NumberWithUnit left, NumberWithUnit right) {
return OperateWithoutUnits(left, right, Number.Remainder, "Cannot modulo"); return OperateWithoutUnits(left, right, Number.Remainder, "Cannot modulo");
} }
public NumberWithUnit Pow(NumberWithUnit exponent) { public NumberWithUnit Pow(NumberWithUnit exponent) {
return OperateWithoutUnits(this, exponent, Number.Pow, "Cannot exponentiate"); return OperateWithoutUnits(this, exponent, Number.Pow, "Cannot exponentiate");
} }
private static NumberWithUnit Operate(NumberWithUnit left, NumberWithUnit right, Func<Number, Number, Number> withoutUnitsOperation, Func<Number, Unit, Number, Unit, NumberWithUnit> withUnitsOperation) { private static NumberWithUnit Operate(NumberWithUnit left, NumberWithUnit right, Func<Number, Number, Number> withoutUnitsOperation, Func<Number, Unit, Number, Unit, NumberWithUnit> withUnitsOperation) {
if (right.Unit is null) { if (right.Unit is null) {
return left with { Number = withoutUnitsOperation(left.Number, right.Number) }; return left with { Number = withoutUnitsOperation(left.Number, right.Number) };
@ -101,7 +101,7 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
return withUnitsOperation(left.Number, left.Unit, right.Number, right.Unit); return withUnitsOperation(left.Number, left.Unit, right.Number, right.Unit);
} }
} }
private static NumberWithUnit OperateWithoutUnits(NumberWithUnit left, NumberWithUnit right, Func<Number, Number, Number> withoutUnitsOperation, string messagePrefix) { private static NumberWithUnit OperateWithoutUnits(NumberWithUnit left, NumberWithUnit right, Func<Number, Number, Number> withoutUnitsOperation, string messagePrefix) {
return Operate(left, right, withoutUnitsOperation, (_, leftUnit, _, rightUnit) => throw new ArithmeticException(messagePrefix + " '" + leftUnit + "' and '" + rightUnit + "'")); return Operate(left, right, withoutUnitsOperation, (_, leftUnit, _, rightUnit) => throw new ArithmeticException(messagePrefix + " '" + leftUnit + "' and '" + rightUnit + "'"));
} }

View File

@ -14,7 +14,7 @@ sealed class UnitUniverse(
FrozenDictionary<Unit, Func<Number, Number>> unitToConversionFromPrimaryUnit FrozenDictionary<Unit, Func<Number, Number>> unitToConversionFromPrimaryUnit
) { ) {
public ImmutableArray<Unit> AllUnits => unitToConversionToPrimaryUnit.Keys; public ImmutableArray<Unit> AllUnits => unitToConversionToPrimaryUnit.Keys;
internal bool TryConvert(Number value, Unit fromUnit, Unit toUnit, [NotNullWhen(true)] out Number? converted) { internal bool TryConvert(Number value, Unit fromUnit, Unit toUnit, [NotNullWhen(true)] out Number? converted) {
if (fromUnit == toUnit) { if (fromUnit == toUnit) {
converted = value; converted = value;
@ -29,7 +29,7 @@ sealed class UnitUniverse(
return false; return false;
} }
} }
internal sealed record SI(string ShortPrefix, string LongPrefix, int Factor) { internal sealed record SI(string ShortPrefix, string LongPrefix, int Factor) {
internal static readonly List<SI> All = [ internal static readonly List<SI> All = [
new SI("Q", "quetta", 30), new SI("Q", "quetta", 30),
@ -58,58 +58,58 @@ sealed class UnitUniverse(
new SI("q", "quecto", -30) new SI("q", "quecto", -30)
]; ];
} }
internal sealed class Builder { internal sealed class Builder {
private readonly Unit primaryUnit; private readonly Unit primaryUnit;
private readonly Dictionary<Unit, Func<Number, Number>> unitToConversionToPrimaryUnit = new (ReferenceEqualityComparer.Instance); private readonly Dictionary<Unit, Func<Number, Number>> unitToConversionToPrimaryUnit = new (ReferenceEqualityComparer.Instance);
private readonly Dictionary<Unit, Func<Number, Number>> unitToConversionFromPrimaryUnit = new (ReferenceEqualityComparer.Instance); private readonly Dictionary<Unit, Func<Number, Number>> unitToConversionFromPrimaryUnit = new (ReferenceEqualityComparer.Instance);
public Builder(Unit primaryUnit) { public Builder(Unit primaryUnit) {
this.primaryUnit = primaryUnit; this.primaryUnit = primaryUnit;
AddUnit(primaryUnit, 1); AddUnit(primaryUnit, 1);
} }
public Builder AddUnit(Unit unit, Func<Number, Number> convertToPrimaryUnit, Func<Number, Number> convertFromPrimaryUnit) { public Builder AddUnit(Unit unit, Func<Number, Number> convertToPrimaryUnit, Func<Number, Number> convertFromPrimaryUnit) {
unitToConversionToPrimaryUnit.Add(unit, convertToPrimaryUnit); unitToConversionToPrimaryUnit.Add(unit, convertToPrimaryUnit);
unitToConversionFromPrimaryUnit.Add(unit, convertFromPrimaryUnit); unitToConversionFromPrimaryUnit.Add(unit, convertFromPrimaryUnit);
return this; return this;
} }
public Builder AddUnit(Unit unit, Number amountInPrimaryUnit) { public Builder AddUnit(Unit unit, Number amountInPrimaryUnit) {
return AddUnit(unit, number => number * amountInPrimaryUnit, number => number / amountInPrimaryUnit); return AddUnit(unit, number => number * amountInPrimaryUnit, number => number / amountInPrimaryUnit);
} }
private void AddUnitSI(SI si, Func<SI, Unit> unitFactory, Func<int, int> factorModifier) { private void AddUnitSI(SI si, Func<SI, Unit> unitFactory, Func<int, int> factorModifier) {
int factor = factorModifier(si.Factor); int factor = factorModifier(si.Factor);
BigInteger powerOfTen = BigInteger.Pow(10, System.Math.Abs(factor)); BigInteger powerOfTen = BigInteger.Pow(10, System.Math.Abs(factor));
BigRational amountInPrimaryUnit = factor > 0 ? new BigRational(powerOfTen) : new BigRational(1, powerOfTen); BigRational amountInPrimaryUnit = factor > 0 ? new BigRational(powerOfTen) : new BigRational(1, powerOfTen);
AddUnit(unitFactory(si), amountInPrimaryUnit); AddUnit(unitFactory(si), amountInPrimaryUnit);
} }
public Builder AddSI(Func<SI, Unit> unitFactory, Func<int, int> factorModifier) { public Builder AddSI(Func<SI, Unit> unitFactory, Func<int, int> factorModifier) {
foreach (SI si in SI.All) { foreach (SI si in SI.All) {
AddUnitSI(si, unitFactory, factorModifier); AddUnitSI(si, unitFactory, factorModifier);
} }
return this; return this;
} }
public Builder AddSI(Func<int, int> factorModifier) { public Builder AddSI(Func<int, int> factorModifier) {
Unit PrefixPrimaryUnit(SI si) { Unit PrefixPrimaryUnit(SI si) {
return new Unit(si.ShortPrefix + primaryUnit.ShortName, [..primaryUnit.LongNames.Select(longName => si.LongPrefix + longName)]); return new Unit(si.ShortPrefix + primaryUnit.ShortName, [..primaryUnit.LongNames.Select(longName => si.LongPrefix + longName)]);
} }
foreach (SI si in SI.All) { foreach (SI si in SI.All) {
AddUnitSI(si, PrefixPrimaryUnit, factorModifier); AddUnitSI(si, PrefixPrimaryUnit, factorModifier);
} }
return this; return this;
} }
public Builder AddSI() { public Builder AddSI() {
return AddSI(static factor => factor); return AddSI(static factor => factor);
} }
public UnitUniverse Build() { public UnitUniverse Build() {
return new UnitUniverse( return new UnitUniverse(
unitToConversionToPrimaryUnit.ToFrozenDictionary(ReferenceEqualityComparer.Instance), unitToConversionToPrimaryUnit.ToFrozenDictionary(ReferenceEqualityComparer.Instance),

View File

@ -9,7 +9,7 @@ sealed class UnitUniverses {
private readonly FrozenDictionary<Unit, UnitUniverse> unitToUniverse; private readonly FrozenDictionary<Unit, UnitUniverse> unitToUniverse;
public WordLookupTrieNode UnitLookupByWords { get; } public WordLookupTrieNode UnitLookupByWords { get; }
internal UnitUniverses(params UnitUniverse[] universes) { internal UnitUniverses(params UnitUniverse[] universes) {
Dictionary<Unit, UnitUniverse> unitToUniverseBuilder = new (ReferenceEqualityComparer.Instance); Dictionary<Unit, UnitUniverse> unitToUniverseBuilder = new (ReferenceEqualityComparer.Instance);
WordLookupTrieNode.Builder unitLookupByWordsBuilder = new (); WordLookupTrieNode.Builder unitLookupByWordsBuilder = new ();
@ -20,7 +20,7 @@ sealed class UnitUniverses {
unitLookupByWordsBuilder.Add(unit); unitLookupByWordsBuilder.Add(unit);
} }
} }
unitToUniverse = unitToUniverseBuilder.ToFrozenDictionary(ReferenceEqualityComparer.Instance); unitToUniverse = unitToUniverseBuilder.ToFrozenDictionary(ReferenceEqualityComparer.Instance);
UnitLookupByWords = unitLookupByWordsBuilder.Build(); UnitLookupByWords = unitLookupByWordsBuilder.Build();
} }
@ -58,14 +58,14 @@ sealed class UnitUniverses {
private void Add(string name, Unit unit) { private void Add(string name, Unit unit) {
Node node = root; Node node = root;
string[] words = name.Split(' '); string[] words = name.Split(' ');
foreach (string word in words.AsSpan(..^1)) { foreach (string word in words.AsSpan(..^1)) {
node = node.Child(word); node = node.Child(word);
} }
node.Children.Add(words[^1], Node.Create(unit)); node.Children.Add(words[^1], Node.Create(unit));
} }
public WordLookupTrieNode Build() { public WordLookupTrieNode Build() {
return Build(root); return Build(root);
} }

View File

@ -17,22 +17,22 @@ static class Units {
public static UnitUniverse Angle { get; } = AngleUniverse().Build(); public static UnitUniverse Angle { get; } = AngleUniverse().Build();
public static UnitUniverse Temperature { get; } = TemperatureUniverse().Build(); public static UnitUniverse Temperature { get; } = TemperatureUniverse().Build();
public static UnitUniverse InformationEntropy { get; } = InformationEntropyUniverse().Build(); public static UnitUniverse InformationEntropy { get; } = InformationEntropyUniverse().Build();
public static UnitUniverses All { get; } = new (Time, Length, Mass, Area, Volume, Angle, Temperature, InformationEntropy); public static UnitUniverses All { get; } = new (Time, Length, Mass, Area, Volume, Angle, Temperature, InformationEntropy);
private static UnitUniverse.Builder TimeUniverse() { private static UnitUniverse.Builder TimeUniverse() {
var minute = 60; var minute = 60;
var hour = minute * 60; var hour = minute * 60;
var day = hour * 24; var day = hour * 24;
var week = day * 7; var week = day * 7;
return new UnitUniverse.Builder(new Unit("s", Pluralize("second"))) return new UnitUniverse.Builder(new Unit("s", Pluralize("second")))
.AddUnit(new Unit("min", Pluralize("minute")), minute) .AddUnit(new Unit("min", Pluralize("minute")), minute)
.AddUnit(new Unit("h", Pluralize("hour")), hour) .AddUnit(new Unit("h", Pluralize("hour")), hour)
.AddUnit(new Unit("d", Pluralize("day")), day) .AddUnit(new Unit("d", Pluralize("day")), day)
.AddUnit(new Unit("wk", Pluralize("week")), week); .AddUnit(new Unit("wk", Pluralize("week")), week);
} }
private static UnitUniverse.Builder LengthUniverse() { private static UnitUniverse.Builder LengthUniverse() {
var inch = Parse("0", "0254"); var inch = Parse("0", "0254");
var foot = inch * 12; var foot = inch * 12;
@ -41,7 +41,7 @@ static class Units {
var mile = yard * 1760; var mile = yard * 1760;
var nauticalMile = 1_852; var nauticalMile = 1_852;
var lightYear = 9_460_730_472_580_800; var lightYear = 9_460_730_472_580_800;
return new UnitUniverse.Builder(new Unit("m", Pluralize("meter", "metre"))) return new UnitUniverse.Builder(new Unit("m", Pluralize("meter", "metre")))
.AddSI() .AddSI()
.AddUnit(new Unit("in", [ "inch", "inches", "\"" ]), inch) .AddUnit(new Unit("in", [ "inch", "inches", "\"" ]), inch)
@ -52,13 +52,13 @@ static class Units {
.AddUnit(new Unit("nmi", Pluralize("nautical mile")), nauticalMile) .AddUnit(new Unit("nmi", Pluralize("nautical mile")), nauticalMile)
.AddUnit(new Unit("ly", Pluralize("light-year", "light year")), lightYear); .AddUnit(new Unit("ly", Pluralize("light-year", "light year")), lightYear);
} }
private static UnitUniverse.Builder MassUniverse() { private static UnitUniverse.Builder MassUniverse() {
var pound = Parse("453", "59237"); var pound = Parse("453", "59237");
var stone = pound * 14; var stone = pound * 14;
var ounce = pound / 16; var ounce = pound / 16;
var dram = ounce / 16; var dram = ounce / 16;
return new UnitUniverse.Builder(new Unit("g", Pluralize("gram"))) return new UnitUniverse.Builder(new Unit("g", Pluralize("gram")))
.AddSI() .AddSI()
.AddUnit(new Unit("lb", [ "lbs", "pound", "pounds" ]), pound) .AddUnit(new Unit("lb", [ "lbs", "pound", "pounds" ]), pound)
@ -66,7 +66,7 @@ static class Units {
.AddUnit(new Unit("oz", Pluralize("ounce")), ounce) .AddUnit(new Unit("oz", Pluralize("ounce")), ounce)
.AddUnit(new Unit("dr", Pluralize("dram")), dram); .AddUnit(new Unit("dr", Pluralize("dram")), dram);
} }
private static UnitUniverse.Builder AreaUniverse() { private static UnitUniverse.Builder AreaUniverse() {
static Unit SquareMeter(string shortPrefix, string longPrefix) { static Unit SquareMeter(string shortPrefix, string longPrefix) {
return new Unit(shortPrefix + "m2", [ return new Unit(shortPrefix + "m2", [
@ -80,13 +80,13 @@ static class Units {
..Pluralize($"square {longPrefix}meter", $"square {longPrefix}metre") ..Pluralize($"square {longPrefix}meter", $"square {longPrefix}metre")
]); ]);
} }
return new UnitUniverse.Builder(SquareMeter(string.Empty, string.Empty)) return new UnitUniverse.Builder(SquareMeter(string.Empty, string.Empty))
.AddSI(static si => SquareMeter(si.ShortPrefix, si.LongPrefix), static factor => factor * 2) .AddSI(static si => SquareMeter(si.ShortPrefix, si.LongPrefix), static factor => factor * 2)
.AddUnit(new Unit("a", Pluralize("are")), 100) .AddUnit(new Unit("a", Pluralize("are")), 100)
.AddUnit(new Unit("ha", Pluralize("hectare")), 10_000); .AddUnit(new Unit("ha", Pluralize("hectare")), 10_000);
} }
private static UnitUniverse.Builder VolumeUniverse() { private static UnitUniverse.Builder VolumeUniverse() {
static Unit CubicMeter(string shortPrefix, string longPrefix) { static Unit CubicMeter(string shortPrefix, string longPrefix) {
return new Unit(shortPrefix + "m3", [ return new Unit(shortPrefix + "m3", [
@ -99,31 +99,31 @@ static class Units {
..Pluralize($"cubic {longPrefix}meter", $"cubic {longPrefix}metre") ..Pluralize($"cubic {longPrefix}meter", $"cubic {longPrefix}metre")
]); ]);
} }
return new UnitUniverse.Builder(new Unit("l", Pluralize("litre", "liter"))) return new UnitUniverse.Builder(new Unit("l", Pluralize("litre", "liter")))
.AddSI() .AddSI()
.AddUnit(CubicMeter(string.Empty, string.Empty), 1000) .AddUnit(CubicMeter(string.Empty, string.Empty), 1000)
.AddSI(static si => CubicMeter(si.ShortPrefix, si.LongPrefix), static factor => (factor * 3) + 3); .AddSI(static si => CubicMeter(si.ShortPrefix, si.LongPrefix), static factor => (factor * 3) + 3);
} }
private static UnitUniverse.Builder AngleUniverse() { private static UnitUniverse.Builder AngleUniverse() {
return new UnitUniverse.Builder(new Unit("deg", [ "°", "degree", "degrees" ])) return new UnitUniverse.Builder(new Unit("deg", [ "°", "degree", "degrees" ]))
.AddUnit(new Unit("rad", Pluralize("radian")), new Number.Decimal((decimal) System.Math.PI / 180M)) .AddUnit(new Unit("rad", Pluralize("radian")), new Number.Decimal((decimal) System.Math.PI / 180M))
.AddUnit(new Unit("grad", Pluralize("gradian", "grade", "gon")), Ratio(9, 10)); .AddUnit(new Unit("grad", Pluralize("gradian", "grade", "gon")), Ratio(9, 10));
} }
private static BigRational KelvinOffset { get; } = Parse("273", "15"); private static BigRational KelvinOffset { get; } = Parse("273", "15");
private static UnitUniverse.Builder TemperatureUniverse() { private static UnitUniverse.Builder TemperatureUniverse() {
return new UnitUniverse.Builder(new Unit("°C", [ "C", "Celsius", "celsius" ])) return new UnitUniverse.Builder(new Unit("°C", [ "C", "Celsius", "celsius" ]))
.AddUnit(new Unit("°F", [ "F", "Fahrenheit", "fahrenheit" ]), static f => (f - 32) * Ratio(5, 9), static c => c * Ratio(9, 5) + 32) .AddUnit(new Unit("°F", [ "F", "Fahrenheit", "fahrenheit" ]), static f => (f - 32) * Ratio(5, 9), static c => c * Ratio(9, 5) + 32)
.AddUnit(new Unit("K", [ "Kelvin", "kelvin" ]), static k => k - KelvinOffset, static c => c + KelvinOffset); .AddUnit(new Unit("K", [ "Kelvin", "kelvin" ]), static k => k - KelvinOffset, static c => c + KelvinOffset);
} }
private static UnitUniverse.Builder InformationEntropyUniverse() { private static UnitUniverse.Builder InformationEntropyUniverse() {
var bit = Ratio(1, 8); var bit = Ratio(1, 8);
var nibble = bit * 4; var nibble = bit * 4;
return new UnitUniverse.Builder(new Unit("B", Pluralize("byte"))) return new UnitUniverse.Builder(new Unit("B", Pluralize("byte")))
.AddSI() .AddSI()
.AddUnit(new Unit("b", Pluralize("bit")), bit) .AddUnit(new Unit("b", Pluralize("bit")), bit)
@ -137,19 +137,19 @@ static class Units {
.AddUnit(new Unit("ZiB", Pluralize("zebibyte")), Pow(1024, 7)) .AddUnit(new Unit("ZiB", Pluralize("zebibyte")), Pow(1024, 7))
.AddUnit(new Unit("YiB", Pluralize("yobibyte")), Pow(1024, 8)); .AddUnit(new Unit("YiB", Pluralize("yobibyte")), Pow(1024, 8));
} }
private static BigRational Parse(string integerPart, string fractionalPart) { private static BigRational Parse(string integerPart, string fractionalPart) {
return Tokenizer.ParseNumber(integerPart, fractionalPart); return Tokenizer.ParseNumber(integerPart, fractionalPart);
} }
private static BigRational Ratio(long numerator, long denominator) { private static BigRational Ratio(long numerator, long denominator) {
return new BigRational(numerator, denominator); return new BigRational(numerator, denominator);
} }
private static BigRational Pow(int value, int exponent) { private static BigRational Pow(int value, int exponent) {
return BigRational.Pow(value, exponent); return BigRational.Pow(value, exponent);
} }
private static ImmutableArray<string> Pluralize(params string[] names) { private static ImmutableArray<string> Pluralize(params string[] names) {
return [..names.SelectMany(static name => new [] { name, name + "s" })]; return [..names.SelectMany(static name => new [] { name, name + "s" })];
} }

View File

@ -6,9 +6,9 @@ namespace Calculator.Parser;
public abstract record Expression { public abstract record Expression {
private Expression() {} private Expression() {}
public abstract T Accept<T>(ExpressionVisitor<T> visitor); public abstract T Accept<T>(ExpressionVisitor<T> visitor);
public sealed record Number(Token.Number NumberToken) : Expression { public sealed record Number(Token.Number NumberToken) : Expression {
public override T Accept<T>(ExpressionVisitor<T> visitor) { public override T Accept<T>(ExpressionVisitor<T> visitor) {
return visitor.VisitNumber(this); return visitor.VisitNumber(this);
@ -19,7 +19,7 @@ public abstract record Expression {
public override T Accept<T>(ExpressionVisitor<T> visitor) { public override T Accept<T>(ExpressionVisitor<T> visitor) {
return visitor.VisitNumbersWithUnits(this); return visitor.VisitNumbersWithUnits(this);
} }
public override string ToString() { public override string ToString() {
return nameof(NumbersWithUnits) + " { " + string.Join(", ", NumberTokensWithUnits.Select(static (number, unit) => number + " " + unit)) + " }"; return nameof(NumbersWithUnits) + " { " + string.Join(", ", NumberTokensWithUnits.Select(static (number, unit) => number + " " + unit)) + " }";
} }
@ -30,25 +30,25 @@ public abstract record Expression {
return visitor.VisitGrouping(this); return visitor.VisitGrouping(this);
} }
} }
public sealed record Unary(Token.Simple Operator, Expression Right) : Expression { public sealed record Unary(Token.Simple Operator, Expression Right) : Expression {
public override T Accept<T>(ExpressionVisitor<T> visitor) { public override T Accept<T>(ExpressionVisitor<T> visitor) {
return visitor.VisitUnary(this); return visitor.VisitUnary(this);
} }
} }
public sealed record Binary(Expression Left, Token.Simple Operator, Expression Right) : Expression { public sealed record Binary(Expression Left, Token.Simple Operator, Expression Right) : Expression {
public override T Accept<T>(ExpressionVisitor<T> visitor) { public override T Accept<T>(ExpressionVisitor<T> visitor) {
return visitor.VisitBinary(this); return visitor.VisitBinary(this);
} }
} }
public sealed record UnitAssignment(Expression Left, Unit Unit) : Expression { public sealed record UnitAssignment(Expression Left, Unit Unit) : Expression {
public override T Accept<T>(ExpressionVisitor<T> visitor) { public override T Accept<T>(ExpressionVisitor<T> visitor) {
return visitor.VisitUnitAssignment(this); return visitor.VisitUnitAssignment(this);
} }
} }
public sealed record UnitConversion(Expression Left, Unit Unit) : Expression { public sealed record UnitConversion(Expression Left, Unit Unit) : Expression {
public override T Accept<T>(ExpressionVisitor<T> visitor) { public override T Accept<T>(ExpressionVisitor<T> visitor) {
return visitor.VisitUnitConversion(this); return visitor.VisitUnitConversion(this);

View File

@ -2,16 +2,16 @@
public interface ExpressionVisitor<T> { public interface ExpressionVisitor<T> {
T VisitNumber(Expression.Number number); T VisitNumber(Expression.Number number);
T VisitNumbersWithUnits(Expression.NumbersWithUnits numbersWithUnits); T VisitNumbersWithUnits(Expression.NumbersWithUnits numbersWithUnits);
T VisitGrouping(Expression.Grouping grouping); T VisitGrouping(Expression.Grouping grouping);
T VisitUnary(Expression.Unary unary); T VisitUnary(Expression.Unary unary);
T VisitBinary(Expression.Binary binary); T VisitBinary(Expression.Binary binary);
T VisitUnitAssignment(Expression.UnitAssignment unitAssignment); T VisitUnitAssignment(Expression.UnitAssignment unitAssignment);
T VisitUnitConversion(Expression.UnitConversion unitConversion); T VisitUnitConversion(Expression.UnitConversion unitConversion);
} }

View File

@ -7,94 +7,94 @@ namespace Calculator.Parser;
public sealed class Parser(ImmutableArray<Token> tokens) { public sealed class Parser(ImmutableArray<Token> tokens) {
private int current = 0; private int current = 0;
private bool IsEOF => current >= tokens.Length; private bool IsEOF => current >= tokens.Length;
private static readonly ImmutableArray<SimpleTokenType> PLUS_MINUS = [ private static readonly ImmutableArray<SimpleTokenType> PLUS_MINUS = [
SimpleTokenType.PLUS, SimpleTokenType.PLUS,
SimpleTokenType.MINUS SimpleTokenType.MINUS
]; ];
private static readonly ImmutableArray<SimpleTokenType> STAR_SLASH_PERCENT = [ private static readonly ImmutableArray<SimpleTokenType> STAR_SLASH_PERCENT = [
SimpleTokenType.STAR, SimpleTokenType.STAR,
SimpleTokenType.SLASH, SimpleTokenType.SLASH,
SimpleTokenType.PERCENT SimpleTokenType.PERCENT
]; ];
private static readonly ImmutableArray<SimpleTokenType> CARET = [ private static readonly ImmutableArray<SimpleTokenType> CARET = [
SimpleTokenType.CARET SimpleTokenType.CARET
]; ];
private bool Match(SimpleTokenType expectedTokenType, [NotNullWhen(true)] out Token.Simple? token) { private bool Match(SimpleTokenType expectedTokenType, [NotNullWhen(true)] out Token.Simple? token) {
return Match(simpleToken => simpleToken.Type == expectedTokenType, out token); return Match(simpleToken => simpleToken.Type == expectedTokenType, out token);
} }
private bool Match(ImmutableArray<SimpleTokenType> expectedTokenTypes, [NotNullWhen(true)] out Token.Simple? token) { private bool Match(ImmutableArray<SimpleTokenType> expectedTokenTypes, [NotNullWhen(true)] out Token.Simple? token) {
return Match(simpleToken => expectedTokenTypes.Contains(simpleToken.Type), out token); return Match(simpleToken => expectedTokenTypes.Contains(simpleToken.Type), out token);
} }
private bool Match<T>([NotNullWhen(true)] out T? token) where T : Token { private bool Match<T>([NotNullWhen(true)] out T? token) where T : Token {
return Match(static _ => true, out token); return Match(static _ => true, out token);
} }
private bool Match<T>(Predicate<T> predicate, [NotNullWhen(true)] out T? token) where T : Token { private bool Match<T>(Predicate<T> predicate, [NotNullWhen(true)] out T? token) where T : Token {
if (!IsEOF && tokens[current] is T t && predicate(t)) { if (!IsEOF && tokens[current] is T t && predicate(t)) {
current++; current++;
token = t; token = t;
return true; return true;
} }
token = null; token = null;
return false; return false;
} }
public Expression Parse() { public Expression Parse() {
Expression term = Term(); Expression term = Term();
if (!IsEOF) { if (!IsEOF) {
throw new ParseException("Incomplete expression"); throw new ParseException("Incomplete expression");
} }
return term; return term;
} }
private Expression Term() { private Expression Term() {
return Binary(Factor, PLUS_MINUS); return Binary(Factor, PLUS_MINUS);
} }
private Expression Factor() { private Expression Factor() {
return Binary(Exponentiation, STAR_SLASH_PERCENT); return Binary(Exponentiation, STAR_SLASH_PERCENT);
} }
private Expression Exponentiation() { private Expression Exponentiation() {
return Binary(Conversion, CARET); return Binary(Conversion, CARET);
} }
private Expression Binary(Func<Expression> term, ImmutableArray<SimpleTokenType> expectedTokenTypes) { private Expression Binary(Func<Expression> term, ImmutableArray<SimpleTokenType> expectedTokenTypes) {
Expression left = term(); Expression left = term();
while (Match(expectedTokenTypes, out Token.Simple? op)) { while (Match(expectedTokenTypes, out Token.Simple? op)) {
Expression right = term(); Expression right = term();
left = new Expression.Binary(left, op, right); left = new Expression.Binary(left, op, right);
} }
return left; return left;
} }
private Expression Conversion() { private Expression Conversion() {
Expression left = Unary(); Expression left = Unary();
while (MatchUnitConversionOperator()) { while (MatchUnitConversionOperator()) {
if (!MatchUnit(out Unit? unit)) { if (!MatchUnit(out Unit? unit)) {
throw new ParseException("Expected a unit literal"); throw new ParseException("Expected a unit literal");
} }
left = new Expression.UnitConversion(left, unit); left = new Expression.UnitConversion(left, unit);
} }
return left; return left;
} }
private Expression Unary() { private Expression Unary() {
if (Match(PLUS_MINUS, out Token.Simple? op)) { if (Match(PLUS_MINUS, out Token.Simple? op)) {
Expression right = Unary(); Expression right = Unary();
@ -104,56 +104,56 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
return Primary(); return Primary();
} }
} }
private Expression Primary() { private Expression Primary() {
if (Match(LiteralPredicate, out Token? literal)) { if (Match(LiteralPredicate, out Token? literal)) {
if (literal is not Token.Number number) { if (literal is not Token.Number number) {
throw new ParseException("Expected a number literal"); throw new ParseException("Expected a number literal");
} }
Expression expression; Expression expression;
if (!MatchUnit(out Unit? unit)) { if (!MatchUnit(out Unit? unit)) {
expression = new Expression.Number(number); expression = new Expression.Number(number);
} }
else { else {
var numbersWithUnits = ImmutableArray.CreateBuilder<(Token.Number, Unit)>(); var numbersWithUnits = ImmutableArray.CreateBuilder<(Token.Number, Unit)>();
numbersWithUnits.Add((number, unit)); numbersWithUnits.Add((number, unit));
while (MatchNumberWithUnit(out var numberWithUnit)) { while (MatchNumberWithUnit(out var numberWithUnit)) {
numbersWithUnits.Add(numberWithUnit.Value); numbersWithUnits.Add(numberWithUnit.Value);
} }
expression = new Expression.NumbersWithUnits(numbersWithUnits.ToImmutable()); expression = new Expression.NumbersWithUnits(numbersWithUnits.ToImmutable());
} }
if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) { if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) {
expression = new Expression.Binary(expression, new Token.Simple(SimpleTokenType.STAR), InsideParentheses()); expression = new Expression.Binary(expression, new Token.Simple(SimpleTokenType.STAR), InsideParentheses());
} }
return expression; return expression;
} }
if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) { if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) {
return new Expression.Grouping(InsideParentheses()); return new Expression.Grouping(InsideParentheses());
} }
throw new ParseException("Unexpected token type: " + tokens[current]); throw new ParseException("Unexpected token type: " + tokens[current]);
} }
private static bool LiteralPredicate(Token token) { private static bool LiteralPredicate(Token token) {
return token is Token.Text or Token.Number; return token is Token.Text or Token.Number;
} }
private Expression InsideParentheses() { private Expression InsideParentheses() {
Expression term = Term(); Expression term = Term();
if (!Match(SimpleTokenType.RIGHT_PARENTHESIS, out _)) { if (!Match(SimpleTokenType.RIGHT_PARENTHESIS, out _)) {
throw new ParseException("Expected ')' after expression."); throw new ParseException("Expected ')' after expression.");
} }
int position = current; int position = current;
if (MatchUnitConversionOperator()) { if (MatchUnitConversionOperator()) {
if (MatchUnit(out Unit? toUnit)) { if (MatchUnit(out Unit? toUnit)) {
return new Expression.UnitConversion(term, toUnit); return new Expression.UnitConversion(term, toUnit);
@ -162,7 +162,7 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
current = position; current = position;
} }
} }
if (MatchUnit(out Unit? unit)) { if (MatchUnit(out Unit? unit)) {
return new Expression.UnitAssignment(term, unit); return new Expression.UnitAssignment(term, unit);
} }
@ -170,33 +170,33 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
return term; return term;
} }
} }
private bool MatchNumberWithUnit([NotNullWhen(true)] out (Token.Number, Unit)? numberWithUnit) { private bool MatchNumberWithUnit([NotNullWhen(true)] out (Token.Number, Unit)? numberWithUnit) {
if (!Match(out Token.Number? number)) { if (!Match(out Token.Number? number)) {
numberWithUnit = null; numberWithUnit = null;
return false; return false;
} }
if (!MatchUnit(out Unit? unit)) { if (!MatchUnit(out Unit? unit)) {
throw new ParseException("Expected a unit literal"); throw new ParseException("Expected a unit literal");
} }
numberWithUnit = (number, unit); numberWithUnit = (number, unit);
return true; return true;
} }
private bool MatchUnit([NotNullWhen(true)] out Unit? unit) { private bool MatchUnit([NotNullWhen(true)] out Unit? unit) {
int position = current; int position = current;
UnitUniverses.WordLookupTrieNode node = Units.All.UnitLookupByWords; UnitUniverses.WordLookupTrieNode node = Units.All.UnitLookupByWords;
// ReSharper disable once AccessToModifiedClosure // ReSharper disable once AccessToModifiedClosure
while (Match(token => node.Children.ContainsKey(token.Value), out Token.Text? text)) { while (Match(token => node.Children.ContainsKey(token.Value), out Token.Text? text)) {
node = node.Children[text.Value]; node = node.Children[text.Value];
} }
unit = node.Unit; unit = node.Unit;
if (unit != null) { if (unit != null) {
return true; return true;
} }
@ -205,7 +205,7 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
return false; return false;
} }
} }
private bool MatchUnitConversionOperator() { private bool MatchUnitConversionOperator() {
return Match<Token.Text>(static text => text.Value is "to" or "in", out _); return Match<Token.Text>(static text => text.Value is "to" or "in", out _);
} }

View File

@ -7,7 +7,7 @@ public enum SimpleTokenType {
SLASH, SLASH,
PERCENT, PERCENT,
CARET, CARET,
LEFT_PARENTHESIS, LEFT_PARENTHESIS,
RIGHT_PARENTHESIS RIGHT_PARENTHESIS
} }

View File

@ -5,7 +5,7 @@ namespace Calculator.Parser;
public abstract record Token { public abstract record Token {
private Token() {} private Token() {}
public sealed record Simple(SimpleTokenType Type) : Token { public sealed record Simple(SimpleTokenType Type) : Token {
#pragma warning disable CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. #pragma warning disable CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
public override string ToString() { public override string ToString() {
@ -23,13 +23,13 @@ public abstract record Token {
} }
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. #pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
} }
public sealed record Text(string Value) : Token { public sealed record Text(string Value) : Token {
public override string ToString() { public override string ToString() {
return Value; return Value;
} }
} }
public sealed record Number(Math.Number Value) : Token { public sealed record Number(Math.Number Value) : Token {
public override string ToString() { public override string ToString() {
return Value.ToString(CultureInfo.InvariantCulture); return Value.ToString(CultureInfo.InvariantCulture);

View File

@ -9,114 +9,114 @@ namespace Calculator.Parser;
public sealed class Tokenizer(string input) { public sealed class Tokenizer(string input) {
private int position = 0; private int position = 0;
private bool IsEOF => position >= input.Length; private bool IsEOF => position >= input.Length;
private char Advance() { private char Advance() {
return input[position++]; return input[position++];
} }
private bool Match(char c) { private bool Match(char c) {
return Match(found => found == c, out _); return Match(found => found == c, out _);
} }
private bool Match(Predicate<char> predicate, out char c) { private bool Match(Predicate<char> predicate, out char c) {
if (IsEOF) { if (IsEOF) {
c = default; c = default;
return false; return false;
} }
c = input[position]; c = input[position];
if (!predicate(c)) { if (!predicate(c)) {
return false; return false;
} }
position++; position++;
return true; return true;
} }
private void MatchWhile(StringBuilder result, Predicate<char> predicate) { private void MatchWhile(StringBuilder result, Predicate<char> predicate) {
while (Match(predicate, out char c)) { while (Match(predicate, out char c)) {
result.Append(c); result.Append(c);
} }
} }
private string MatchWhile(Predicate<char> predicate) { private string MatchWhile(Predicate<char> predicate) {
var result = new StringBuilder(); var result = new StringBuilder();
MatchWhile(result, predicate); MatchWhile(result, predicate);
return result.ToString(); return result.ToString();
} }
private string MatchRest(char firstChar, Predicate<char> predicate) { private string MatchRest(char firstChar, Predicate<char> predicate) {
var result = new StringBuilder(); var result = new StringBuilder();
result.Append(firstChar); result.Append(firstChar);
MatchWhile(result, predicate); MatchWhile(result, predicate);
return result.ToString(); return result.ToString();
} }
public ImmutableArray<Token> Scan() { public ImmutableArray<Token> Scan() {
ImmutableArray<Token>.Builder tokens = ImmutableArray.CreateBuilder<Token>(); ImmutableArray<Token>.Builder tokens = ImmutableArray.CreateBuilder<Token>();
void AddToken(Token token) { void AddToken(Token token) {
tokens.Add(token); tokens.Add(token);
} }
void AddSimpleToken(SimpleTokenType tokenType) { void AddSimpleToken(SimpleTokenType tokenType) {
AddToken(new Token.Simple(tokenType)); AddToken(new Token.Simple(tokenType));
} }
while (!IsEOF) { while (!IsEOF) {
char c = Advance(); char c = Advance();
switch (c) { switch (c) {
case ' ': case ' ':
// Ignore whitespace. // Ignore whitespace.
break; break;
case '+': case '+':
AddSimpleToken(SimpleTokenType.PLUS); AddSimpleToken(SimpleTokenType.PLUS);
break; break;
case '-': case '-':
AddSimpleToken(SimpleTokenType.MINUS); AddSimpleToken(SimpleTokenType.MINUS);
break; break;
case '*': case '*':
AddSimpleToken(SimpleTokenType.STAR); AddSimpleToken(SimpleTokenType.STAR);
break; break;
case '/': case '/':
AddSimpleToken(SimpleTokenType.SLASH); AddSimpleToken(SimpleTokenType.SLASH);
break; break;
case '%': case '%':
AddSimpleToken(SimpleTokenType.PERCENT); AddSimpleToken(SimpleTokenType.PERCENT);
break; break;
case '^': case '^':
AddSimpleToken(SimpleTokenType.CARET); AddSimpleToken(SimpleTokenType.CARET);
break; break;
case '(': case '(':
AddSimpleToken(SimpleTokenType.LEFT_PARENTHESIS); AddSimpleToken(SimpleTokenType.LEFT_PARENTHESIS);
break; break;
case ')': case ')':
AddSimpleToken(SimpleTokenType.RIGHT_PARENTHESIS); AddSimpleToken(SimpleTokenType.RIGHT_PARENTHESIS);
break; break;
case '"' or '\'': case '"' or '\'':
AddToken(new Token.Text(c.ToString())); AddToken(new Token.Text(c.ToString()));
break; break;
case '°': case '°':
case {} when char.IsLetter(c): case {} when char.IsLetter(c):
AddToken(new Token.Text(MatchRest(c, char.IsLetterOrDigit))); AddToken(new Token.Text(MatchRest(c, char.IsLetterOrDigit)));
break; break;
case {} when char.IsAsciiDigit(c): case {} when char.IsAsciiDigit(c):
string integerPart = MatchRest(c, char.IsAsciiDigit); string integerPart = MatchRest(c, char.IsAsciiDigit);
if (Match('.')) { if (Match('.')) {
string fractionalPart = MatchWhile(char.IsAsciiDigit); string fractionalPart = MatchWhile(char.IsAsciiDigit);
AddToken(new Token.Number(ParseNumber(integerPart, fractionalPart))); AddToken(new Token.Number(ParseNumber(integerPart, fractionalPart)));
@ -124,17 +124,17 @@ public sealed class Tokenizer(string input) {
else { else {
AddToken(new Token.Number(ParseNumber(integerPart))); AddToken(new Token.Number(ParseNumber(integerPart)));
} }
break; break;
default: default:
throw new TokenizationException("Unexpected character: " + c, c); throw new TokenizationException("Unexpected character: " + c, c);
} }
} }
return tokens.ToImmutable(); return tokens.ToImmutable();
} }
internal static BigRational ParseNumber(string integerPart, string? fractionalPart = null) { internal static BigRational ParseNumber(string integerPart, string? fractionalPart = null) {
if (fractionalPart == null) { if (fractionalPart == null) {
return new BigRational(BigInteger.Parse(integerPart, NumberStyles.Integer, CultureInfo.InvariantCulture)); return new BigRational(BigInteger.Parse(integerPart, NumberStyles.Integer, CultureInfo.InvariantCulture));

View File

@ -11,7 +11,7 @@ sealed class CalculatorApp : IApp {
ImmutableArray<Token> tokens = new Tokenizer(command).Scan(); ImmutableArray<Token> tokens = new Tokenizer(command).Scan();
Expression expression = new Parser(tokens).Parse(); Expression expression = new Parser(tokens).Parse();
NumberWithUnit result = expression.Accept(new CalculatorExpressionVisitor()); NumberWithUnit result = expression.Accept(new CalculatorExpressionVisitor());
output = result.ToString(); output = result.ToString();
return true; return true;
} }

View File

@ -14,11 +14,11 @@ sealed class KillProcessApp : IApp {
} }
int succeeded = 0, failed = 0; int succeeded = 0, failed = 0;
foreach (string processName in args[1..]) { foreach (string processName in args[1..]) {
try { try {
Process[] processes = Process.GetProcessesByName(processName.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) ? processName[..^4] : processName); Process[] processes = Process.GetProcessesByName(processName.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) ? processName[..^4] : processName);
foreach (Process process in processes) { foreach (Process process in processes) {
try { try {
process.Kill(); process.Kill();
@ -26,21 +26,21 @@ sealed class KillProcessApp : IApp {
} catch { } catch {
++failed; ++failed;
} }
process.Close(); process.Close();
} }
} catch { } catch {
++failed; ++failed;
} }
} }
var build = new StringBuilder(); var build = new StringBuilder();
build.Append("Killed ").Append(succeeded).Append(" process").Append(succeeded == 1 ? "" : "es"); build.Append("Killed ").Append(succeeded).Append(" process").Append(succeeded == 1 ? "" : "es");
if (failed > 0) { if (failed > 0) {
build.Append(", failed ").Append(failed); build.Append(", failed ").Append(failed);
} }
output = build.Append('.').ToString(); output = build.Append('.').ToString();
return true; return true;
} }

View File

@ -10,7 +10,7 @@ sealed class MemeApp : IApp {
{ "flip", @"(╯°□°)╯︵ ┻━┻" }, { "flip", @"(╯°□°)╯︵ ┻━┻" },
{ "tableflip", @"(╯°□°)╯︵ ┻━┻" } { "tableflip", @"(╯°□°)╯︵ ┻━┻" }
}; };
public bool TryRun(string command, [NotNullWhen(true)] out string? output) { public bool TryRun(string command, [NotNullWhen(true)] out string? output) {
return Map.TryGetValue(command, out output); return Map.TryGetValue(command, out output);
} }

View File

@ -5,19 +5,19 @@ namespace Query.Command;
sealed class CommandHistory { sealed class CommandHistory {
private readonly List<string> queries = []; private readonly List<string> queries = [];
private readonly List<string> results = []; private readonly List<string> results = [];
public IList<string> Queries => queries; public IList<string> Queries => queries;
public IList<string> Results => results; public IList<string> Results => results;
public void AddQuery(string text) { public void AddQuery(string text) {
queries.Add(text); queries.Add(text);
} }
public void AddResult(string text) { public void AddResult(string text) {
results.Add(text); results.Add(text);
} }
public void Clear() { public void Clear() {
queries.Clear(); queries.Clear();
results.Clear(); results.Clear();

View File

@ -10,7 +10,7 @@ sealed class CommandProcessor {
new KillProcessApp(), new KillProcessApp(),
new CalculatorApp() new CalculatorApp()
]; ];
public string Run(string command) { public string Run(string command) {
try { try {
foreach (IApp app in apps) { foreach (IApp app in apps) {
@ -18,7 +18,7 @@ sealed class CommandProcessor {
return output; return output;
} }
} }
return "Unknown command."; return "Unknown command.";
} catch (Exception e) { } catch (Exception e) {
throw new CommandException(e.Message, e); throw new CommandException(e.Message, e);

View File

@ -6,57 +6,57 @@ namespace Query.Form;
sealed class KeyboardHook { sealed class KeyboardHook {
public event EventHandler? Triggered; public event EventHandler? Triggered;
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly NativeMethods.HookProc keyboardHookDelegate; private readonly NativeMethods.HookProc keyboardHookDelegate;
private IntPtr keyboardHook; private IntPtr keyboardHook;
public KeyboardHook() { public KeyboardHook() {
keyboardHookDelegate = KeyboardHookProc; keyboardHookDelegate = KeyboardHookProc;
} }
public void StartHook() { public void StartHook() {
if (keyboardHook != IntPtr.Zero) { if (keyboardHook != IntPtr.Zero) {
NativeMethods.UnhookWindowsHookEx(keyboardHook); NativeMethods.UnhookWindowsHookEx(keyboardHook);
} }
keyboardHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_KEYBOARD_LL, keyboardHookDelegate, IntPtr.Zero, 0); keyboardHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_KEYBOARD_LL, keyboardHookDelegate, IntPtr.Zero, 0);
} }
public void StopHook() { public void StopHook() {
if (keyboardHook != IntPtr.Zero) { if (keyboardHook != IntPtr.Zero) {
NativeMethods.UnhookWindowsHookEx(keyboardHook); NativeMethods.UnhookWindowsHookEx(keyboardHook);
keyboardHook = IntPtr.Zero; keyboardHook = IntPtr.Zero;
} }
} }
private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam) { private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
if (wParam == NativeMethods.WM_KEYDOWN) { if (wParam == NativeMethods.WM_KEYDOWN) {
Keys key = (Keys) Marshal.ReadInt32(lParam); Keys key = (Keys) Marshal.ReadInt32(lParam);
if (key is Keys.LWin or Keys.RWin && Control.ModifierKeys.HasFlag(Keys.Control)) { if (key is Keys.LWin or Keys.RWin && Control.ModifierKeys.HasFlag(Keys.Control)) {
Triggered?.Invoke(this, EventArgs.Empty); Triggered?.Invoke(this, EventArgs.Empty);
return NativeMethods.HookHandled; return NativeMethods.HookHandled;
} }
} }
return NativeMethods.CallNextHookEx(keyboardHook, nCode, wParam, lParam); return NativeMethods.CallNextHookEx(keyboardHook, nCode, wParam, lParam);
} }
private static class NativeMethods { private static class NativeMethods {
public const int WH_KEYBOARD_LL = 13; public const int WH_KEYBOARD_LL = 13;
public const int WM_KEYDOWN = 0x0100; public const int WM_KEYDOWN = 0x0100;
public static readonly IntPtr HookHandled = new (-1); public static readonly IntPtr HookHandled = new (-1);
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool UnhookWindowsHookEx(IntPtr idHook); public static extern bool UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam); public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
} }

View File

@ -1,135 +1,133 @@
namespace Query.Form { namespace Query.Form {
sealed partial class MainForm { sealed partial class MainForm {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
/// </summary> /// </summary>
private System.ComponentModel.IContainer components = null; private System.ComponentModel.IContainer components = null;
/// <summary> /// <summary>
/// Clean up any resources being used. /// Clean up any resources being used.
/// </summary> /// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) { protected override void Dispose(bool disposing) {
if (disposing && (components != null)) { if (disposing && (components != null)) {
components.Dispose(); components.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
#region Windows Form Designer generated code #region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
queryLog = new Query.Form.QueryHistoryLog();
queryBox = new Query.Form.QueryTextBox();
trayIcon = new System.Windows.Forms.NotifyIcon(components);
contextMenuTray = new System.Windows.Forms.ContextMenuStrip(components);
showToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
hookToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
contextMenuTray.SuspendLayout();
SuspendLayout();
//
// queryLog
//
queryLog.Anchor = ((System.Windows.Forms.AnchorStyles) (((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right));
queryLog.BackColor = System.Drawing.Color.Transparent;
queryLog.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) 238));
queryLog.Location = new System.Drawing.Point(7, 8);
queryLog.Margin = new System.Windows.Forms.Padding(7, 8, 7, 8);
queryLog.Name = "queryLog";
queryLog.Size = new System.Drawing.Size(696, 296);
queryLog.TabIndex = 1;
//
// queryBox
//
queryBox.BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 36)), ((int) ((byte) 36)), ((int) ((byte) 36)));
queryBox.Dock = System.Windows.Forms.DockStyle.Bottom;
queryBox.Location = new System.Drawing.Point(0, 312);
queryBox.Margin = new System.Windows.Forms.Padding(0);
queryBox.Name = "queryBox";
queryBox.Size = new System.Drawing.Size(709, 50);
queryBox.TabIndex = 0;
//
// trayIcon
//
trayIcon.ContextMenuStrip = contextMenuTray;
trayIcon.Icon = ((System.Drawing.Icon) resources.GetObject("trayIcon.Icon"));
trayIcon.Text = "Query";
trayIcon.Visible = true;
trayIcon.Click += trayIcon_Click;
//
// contextMenuTray
//
contextMenuTray.ImageScalingSize = new System.Drawing.Size(20, 20);
contextMenuTray.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
showToolStripMenuItem, hookToolStripMenuItem, exitToolStripMenuItem
});
contextMenuTray.Name = "contextMenuTray";
contextMenuTray.ShowImageMargin = false;
contextMenuTray.ShowItemToolTips = false;
contextMenuTray.Size = new System.Drawing.Size(90, 76);
//
// showToolStripMenuItem
//
showToolStripMenuItem.Name = "showToolStripMenuItem";
showToolStripMenuItem.Size = new System.Drawing.Size(89, 24);
showToolStripMenuItem.Text = "Show";
showToolStripMenuItem.Click += showToolStripMenuItem_Click;
//
// hookToolStripMenuItem
//
hookToolStripMenuItem.Name = "hookToolStripMenuItem";
hookToolStripMenuItem.Size = new System.Drawing.Size(89, 24);
hookToolStripMenuItem.Text = "Hook";
hookToolStripMenuItem.Click += hookToolStripMenuItem_Click;
//
// exitToolStripMenuItem
//
exitToolStripMenuItem.Name = "exitToolStripMenuItem";
exitToolStripMenuItem.Size = new System.Drawing.Size(89, 24);
exitToolStripMenuItem.Text = "Exit";
exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
//
// MainForm
//
AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 64)), ((int) ((byte) 64)), ((int) ((byte) 64)));
ClientSize = new System.Drawing.Size(709, 362);
ControlBox = false;
Controls.Add(queryLog);
Controls.Add(queryBox);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
Icon = ((System.Drawing.Icon) resources.GetObject("$this.Icon"));
Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
MaximizeBox = false;
MinimizeBox = false;
ShowInTaskbar = false;
Text = "Query";
Deactivate += MainForm_Deactivate;
Shown += MainForm_Shown;
LocationChanged += MainForm_LocationChanged;
SizeChanged += MainForm_SizeChanged;
contextMenuTray.ResumeLayout(false);
ResumeLayout(false);
}
/// <summary> #endregion
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
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();
this.hookToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.contextMenuTray.SuspendLayout();
this.SuspendLayout();
//
// queryLog
//
this.queryLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.queryLog.BackColor = System.Drawing.Color.Transparent;
this.queryLog.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.queryLog.Location = new System.Drawing.Point(5, 5);
this.queryLog.Margin = new System.Windows.Forms.Padding(5);
this.queryLog.Name = "queryLog";
this.queryLog.Size = new System.Drawing.Size(522, 192);
this.queryLog.TabIndex = 1;
//
// queryBox
//
this.queryBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.queryBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(36)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
this.queryBox.Location = new System.Drawing.Point(0, 202);
this.queryBox.Margin = new System.Windows.Forms.Padding(0);
this.queryBox.Name = "queryBox";
this.queryBox.Size = new System.Drawing.Size(532, 33);
this.queryBox.TabIndex = 0;
//
// trayIcon
//
this.trayIcon.ContextMenuStrip = this.contextMenuTray;
this.trayIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("trayIcon.Icon")));
this.trayIcon.Text = "Query";
this.trayIcon.Visible = true;
this.trayIcon.Click += new System.EventHandler(this.trayIcon_Click);
//
// contextMenuTray
//
this.contextMenuTray.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.showToolStripMenuItem,
this.hookToolStripMenuItem,
this.exitToolStripMenuItem});
this.contextMenuTray.Name = "contextMenuTray";
this.contextMenuTray.ShowImageMargin = false;
this.contextMenuTray.ShowItemToolTips = false;
this.contextMenuTray.Size = new System.Drawing.Size(128, 92);
//
// showToolStripMenuItem
//
this.showToolStripMenuItem.Name = "showToolStripMenuItem";
this.showToolStripMenuItem.Size = new System.Drawing.Size(127, 22);
this.showToolStripMenuItem.Text = "Show";
this.showToolStripMenuItem.Click += new System.EventHandler(this.showToolStripMenuItem_Click);
//
// hookToolStripMenuItem
//
this.hookToolStripMenuItem.Name = "hookToolStripMenuItem";
this.hookToolStripMenuItem.Size = new System.Drawing.Size(127, 22);
this.hookToolStripMenuItem.Text = "Hook";
this.hookToolStripMenuItem.Click += new System.EventHandler(this.hookToolStripMenuItem_Click);
//
// exitToolStripMenuItem
//
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
this.exitToolStripMenuItem.Size = new System.Drawing.Size(127, 22);
this.exitToolStripMenuItem.Text = "Exit";
this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.ClientSize = new System.Drawing.Size(532, 235);
this.ControlBox = false;
this.Controls.Add(this.queryLog);
this.Controls.Add(this.queryBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "MainForm";
this.ShowInTaskbar = false;
this.Text = "Query";
this.Deactivate += new System.EventHandler(this.MainForm_Deactivate);
this.Shown += new System.EventHandler(this.MainForm_Shown);
this.contextMenuTray.ResumeLayout(false);
this.ResumeLayout(false);
} private Query.Form.QueryTextBox queryBox;
private Query.Form.QueryHistoryLog queryLog;
#endregion private System.Windows.Forms.NotifyIcon trayIcon;
private System.Windows.Forms.ContextMenuStrip contextMenuTray;
private QueryTextBox queryBox; private System.Windows.Forms.ToolStripMenuItem showToolStripMenuItem;
private QueryHistoryLog queryLog; private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
private System.Windows.Forms.NotifyIcon trayIcon; private System.Windows.Forms.ToolStripMenuItem hookToolStripMenuItem;
private System.Windows.Forms.ContextMenuStrip contextMenuTray; }
private System.Windows.Forms.ToolStripMenuItem showToolStripMenuItem; }
private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem hookToolStripMenuItem;
}
}

View File

@ -8,33 +8,33 @@ namespace Query.Form;
sealed partial class MainForm : System.Windows.Forms.Form { sealed partial class MainForm : System.Windows.Forms.Form {
private readonly CommandProcessor processor; private readonly CommandProcessor processor;
private readonly CommandHistory history; private readonly CommandHistory history;
private readonly Timer focusTimer; private readonly Timer focusTimer;
private readonly KeyboardHook keyboardHook; private readonly KeyboardHook keyboardHook;
private bool isLoaded; private bool isLoaded;
public MainForm() { public MainForm() {
InitializeComponent(); InitializeComponent();
processor = new CommandProcessor(); processor = new CommandProcessor();
history = new CommandHistory(); history = new CommandHistory();
queryBox.Setup(history, str => queryLog.AddEntry(str, QueryHistoryLog.EntryType.Information)); queryBox.Setup(history, str => queryLog.AddEntry(str, QueryHistoryLog.EntryType.Information));
keyboardHook = new KeyboardHook(); keyboardHook = new KeyboardHook();
keyboardHook.Triggered += keyboardHook_Triggered; keyboardHook.Triggered += keyboardHook_Triggered;
focusTimer = new Timer { focusTimer = new Timer {
Interval = 1 Interval = 1
}; };
focusTimer.Tick += focusTimer_Tick; focusTimer.Tick += focusTimer_Tick;
Disposed += MainForm_Disposed; Disposed += MainForm_Disposed;
queryBox.CommandRan += queryBox_CommandRan; queryBox.CommandRan += queryBox_CommandRan;
} }
private void SetShown(bool show) { private void SetShown(bool show) {
if (show) { if (show) {
focusTimer.Start(); focusTimer.Start();
@ -43,63 +43,77 @@ sealed partial class MainForm : System.Windows.Forms.Form {
Hide(); Hide();
} }
} }
private void MainForm_Shown(object? sender, EventArgs e) { private void MainForm_Shown(object? sender, EventArgs e) {
if (Screen.PrimaryScreen is {} primaryScreen) { FixLocation();
Rectangle screenRect = primaryScreen.WorkingArea;
Location = new Point(screenRect.X + screenRect.Width - Width, screenRect.Y + screenRect.Height - Height);
}
if (!isLoaded) { if (!isLoaded) {
isLoaded = true; isLoaded = true;
keyboardHook.StartHook(); keyboardHook.StartHook();
} }
} }
private void MainForm_LocationChanged(object sender, EventArgs e) {
FixLocation();
}
private void MainForm_SizeChanged(object sender, EventArgs e) {
FixLocation();
}
private bool isMoving = false;
private void FixLocation() {
if (!isMoving && Screen.FromControl(this) is {} screen) {
Rectangle screenRect = screen.WorkingArea;
isMoving = true;
Location = new Point(screenRect.X + screenRect.Width - Width, screenRect.Y + screenRect.Height - Height);
isMoving = false;
}
}
private void MainForm_Deactivate(object? sender, EventArgs e) { private void MainForm_Deactivate(object? sender, EventArgs e) {
SetShown(false); SetShown(false);
} }
private void MainForm_Disposed(object? sender, EventArgs e) { private void MainForm_Disposed(object? sender, EventArgs e) {
keyboardHook.StopHook(); keyboardHook.StopHook();
} }
private void trayIcon_Click(object? sender, EventArgs e) { private void trayIcon_Click(object? sender, EventArgs e) {
if (((MouseEventArgs) e).Button == MouseButtons.Left) { if (((MouseEventArgs) e).Button == MouseButtons.Left) {
SetShown(true); SetShown(true);
} }
} }
private void showToolStripMenuItem_Click(object? sender, EventArgs e) { private void showToolStripMenuItem_Click(object? sender, EventArgs e) {
SetShown(true); SetShown(true);
} }
private void hookToolStripMenuItem_Click(object? sender, EventArgs e) { private void hookToolStripMenuItem_Click(object? sender, EventArgs e) {
keyboardHook.StopHook(); keyboardHook.StopHook();
keyboardHook.StartHook(); keyboardHook.StartHook();
} }
private void exitToolStripMenuItem_Click(object? sender, EventArgs e) { private void exitToolStripMenuItem_Click(object? sender, EventArgs e) {
Application.Exit(); Application.Exit();
} }
private void keyboardHook_Triggered(object? sender, EventArgs e) { private void keyboardHook_Triggered(object? sender, EventArgs e) {
SetShown(!Visible); SetShown(!Visible);
} }
private void focusTimer_Tick(object? sender, EventArgs e) { private void focusTimer_Tick(object? sender, EventArgs e) {
WindowState = FormWindowState.Minimized;
Show(); Show();
Activate(); Activate();
WindowState = FormWindowState.Normal;
queryBox.Focus(); queryBox.Focus();
focusTimer.Stop(); focusTimer.Stop();
} }
private void queryBox_CommandRan(object? sender, CommandEventArgs e) { private void queryBox_CommandRan(object? sender, CommandEventArgs e) {
string command = e.Command; string command = e.Command;
if (command is "exit" or "quit") { if (command is "exit" or "quit") {
Application.Exit(); Application.Exit();
} }
@ -113,16 +127,16 @@ sealed partial class MainForm : System.Windows.Forms.Form {
else { else {
try { try {
string result = processor.Run(command); string result = processor.Run(command);
queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput); queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput);
history.AddQuery(command); history.AddQuery(command);
queryLog.AddEntry(result, QueryHistoryLog.EntryType.CommandResult); queryLog.AddEntry(result, QueryHistoryLog.EntryType.CommandResult);
history.AddResult(result); history.AddResult(result);
} catch (CommandException ex) { } catch (CommandException ex) {
queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput); queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput);
history.AddQuery(command); history.AddQuery(command);
queryLog.AddEntry(ex.Message, QueryHistoryLog.EntryType.Error); queryLog.AddEntry(ex.Message, QueryHistoryLog.EntryType.Error);
} }
} }

View File

@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
@ -112,19 +53,19 @@
<value>2.0</value> <value>2.0</value>
</resheader> </resheader>
<resheader name="reader"> <resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value> <value>17, 17</value>
</metadata> </metadata>
<metadata name="contextMenuTray.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="contextMenuTray.TrayLocation" type="System.Drawing.Point, System.Drawing.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>112, 17</value> <value>112, 17</value>
</metadata> </metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <assembly alias="System.Drawing.Common" name="System.Drawing.Common, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<data name="trayIcon.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="trayIcon.Icon" type="System.Drawing.Icon, System.Drawing.Common" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
AAABAAEAGBgAAAEAIACICQAAFgAAACgAAAAYAAAAMAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAABAAEAGBgAAAEAIACICQAAFgAAACgAAAAYAAAAMAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAkJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQk AAAkJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQk
@ -170,7 +111,7 @@
AEE= AEE=
</value> </value>
</data> </data>
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing.Common" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
AAABAAEAGBgAAAEAIACICQAAFgAAACgAAAAYAAAAMAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAABAAEAGBgAAAEAIACICQAAFgAAACgAAAAYAAAAMAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAkJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQk AAAkJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQk

View File

@ -1,57 +1,58 @@
namespace Query.Form { namespace Query.Form {
partial class QueryHistoryLog { partial class QueryHistoryLog {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
/// </summary> /// </summary>
private System.ComponentModel.IContainer components = null; private System.ComponentModel.IContainer components = null;
/// <summary> /// <summary>
/// Clean up any resources being used. /// Clean up any resources being used.
/// </summary> /// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) { protected override void Dispose(bool disposing) {
if (disposing && (components != null)) { if (disposing && (components != null)) {
components.Dispose(); components.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
#region Component Designer generated code #region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
container = new System.Windows.Forms.FlowLayoutPanel();
SuspendLayout();
//
// container
//
container.AutoScroll = true;
container.Dock = System.Windows.Forms.DockStyle.Fill;
container.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
container.ForeColor = System.Drawing.Color.FromArgb(((int) ((byte) 240)), ((int) ((byte) 240)), ((int) ((byte) 240)));
container.Location = new System.Drawing.Point(0, 0);
container.Margin = new System.Windows.Forms.Padding(4);
container.Name = "container";
container.Size = new System.Drawing.Size(195, 191);
container.TabIndex = 0;
container.WrapContents = false;
//
// QueryHistoryLog
//
AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
BackColor = System.Drawing.Color.Transparent;
Controls.Add(container);
Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) 238));
Margin = new System.Windows.Forms.Padding(4);
Size = new System.Drawing.Size(195, 191);
ResumeLayout(false);
}
/// <summary> #endregion
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.container = new System.Windows.Forms.FlowLayoutPanel();
this.SuspendLayout();
//
// container
//
this.container.AutoScroll = true;
this.container.Dock = System.Windows.Forms.DockStyle.Fill;
this.container.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.container.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240)))));
this.container.Location = new System.Drawing.Point(0, 0);
this.container.Name = "container";
this.container.Size = new System.Drawing.Size(150, 150);
this.container.TabIndex = 0;
this.container.WrapContents = false;
//
// QueryHistoryLog
//
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 22F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Transparent;
this.Controls.Add(this.container);
this.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.Name = "QueryHistoryLog";
this.ResumeLayout(false);
} private System.Windows.Forms.FlowLayoutPanel container;
}
#endregion
private System.Windows.Forms.FlowLayoutPanel container;
}
} }

View File

@ -11,21 +11,21 @@ sealed partial class QueryHistoryLog : UserControl {
Information, Information,
Error Error
} }
private static readonly Dictionary<EntryType, Color> EntryColorMap = new () { private static readonly Dictionary<EntryType, Color> EntryColorMap = new () {
{ EntryType.UserInput, Color.FromArgb(160, 160, 160) }, { EntryType.UserInput, Color.FromArgb(160, 160, 160) },
{ EntryType.CommandResult, Color.FromArgb(240, 240, 240) }, { EntryType.CommandResult, Color.FromArgb(240, 240, 240) },
{ EntryType.Information, Color.FromArgb(160, 255, 140) }, { EntryType.Information, Color.FromArgb(160, 255, 140) },
{ EntryType.Error, Color.FromArgb(255, 40, 40) } { EntryType.Error, Color.FromArgb(255, 40, 40) }
}; };
public QueryHistoryLog() { public QueryHistoryLog() {
InitializeComponent(); InitializeComponent();
} }
public void AddEntry(string text, EntryType type) { public void AddEntry(string text, EntryType type) {
int width = container.Width - SystemInformation.VerticalScrollBarWidth; int width = container.Width - SystemInformation.VerticalScrollBarWidth;
Label label = new Label { Label label = new Label {
AutoSize = true, AutoSize = true,
Font = container.Font, Font = container.Font,
@ -35,11 +35,11 @@ sealed partial class QueryHistoryLog : UserControl {
MaximumSize = new Size(width, 0), MaximumSize = new Size(width, 0),
Width = width Width = width
}; };
container.Controls.Add(label); container.Controls.Add(label);
container.AutoScrollPosition = new Point(0, container.VerticalScroll.Maximum); container.AutoScrollPosition = new Point(0, container.VerticalScroll.Maximum);
} }
public void ClearEntries() { public void ClearEntries() {
container.Controls.Clear(); container.Controls.Clear();
} }

View File

@ -1,61 +1,58 @@
namespace Query.Form { namespace Query.Form {
partial class QueryTextBox { partial class QueryTextBox {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
/// </summary> /// </summary>
private System.ComponentModel.IContainer components = null; private System.ComponentModel.IContainer components = null;
/// <summary> /// <summary>
/// Clean up any resources being used. /// Clean up any resources being used.
/// </summary> /// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) { protected override void Dispose(bool disposing) {
if (disposing && (components != null)) { if (disposing && (components != null)) {
components.Dispose(); components.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
#region Component Designer generated code #region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
tb = new Query.Form.QueryTextBox.CustomTextBox();
SuspendLayout();
//
// tb
//
tb.Anchor = ((System.Windows.Forms.AnchorStyles) (((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right));
tb.BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 36)), ((int) ((byte) 36)), ((int) ((byte) 36)));
tb.BorderStyle = System.Windows.Forms.BorderStyle.None;
tb.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) 238));
tb.ForeColor = System.Drawing.Color.FromArgb(((int) ((byte) 240)), ((int) ((byte) 240)), ((int) ((byte) 240)));
tb.Location = new System.Drawing.Point(11, 11);
tb.Margin = new System.Windows.Forms.Padding(11);
tb.Name = "tb";
tb.Size = new System.Drawing.Size(670, 28);
tb.TabIndex = 0;
//
// QueryTextBox
//
AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 36)), ((int) ((byte) 36)), ((int) ((byte) 36)));
Controls.Add(tb);
Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
Size = new System.Drawing.Size(692, 50);
ResumeLayout(false);
PerformLayout();
}
/// <summary> #endregion
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.tb = new CustomTextBox();
this.SuspendLayout();
//
// tb
//
this.tb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tb.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(36)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
this.tb.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.tb.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.tb.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240)))));
this.tb.Location = new System.Drawing.Point(5, 6);
this.tb.Margin = new System.Windows.Forms.Padding(5);
this.tb.Name = "tb";
this.tb.Size = new System.Drawing.Size(509, 23);
this.tb.TabIndex = 0;
//
// QueryTextBox
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(36)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
this.Controls.Add(this.tb);
this.Name = "QueryTextBox";
this.Size = new System.Drawing.Size(519, 33);
this.ResumeLayout(false);
this.PerformLayout();
} private Query.Form.QueryTextBox.CustomTextBox tb;
}
#endregion
private CustomTextBox tb;
}
} }

View File

@ -8,84 +8,84 @@ namespace Query.Form;
sealed partial class QueryTextBox : UserControl { sealed partial class QueryTextBox : UserControl {
public event EventHandler<CommandEventArgs>? CommandRan; public event EventHandler<CommandEventArgs>? CommandRan;
private CommandHistory history = null!; private CommandHistory history = null!;
private Action<string> log = null!; private Action<string> log = null!;
public QueryTextBox() { public QueryTextBox() {
InitializeComponent(); InitializeComponent();
} }
public void Setup(CommandHistory historyObj, Action<string> logFunc) { public void Setup(CommandHistory historyObj, Action<string> logFunc) {
history = historyObj; history = historyObj;
log = logFunc; log = logFunc;
} }
private void OnCommandRan() { private void OnCommandRan() {
CommandRan?.Invoke(this, new CommandEventArgs(tb.Text)); CommandRan?.Invoke(this, new CommandEventArgs(tb.Text));
} }
private sealed class CustomTextBox : TextBox { private sealed class CustomTextBox : TextBox {
private string lastInputStr = string.Empty; private string lastInputStr = string.Empty;
private int lastInputPos = 0; private int lastInputPos = 0;
private bool doResetHistoryMemory; private bool doResetHistoryMemory;
private bool lastArrowShift; private bool lastArrowShift;
private int historyOffset; private int historyOffset;
public CustomTextBox() { public CustomTextBox() {
TextChanged += CustomTextBox_TextChanged; TextChanged += CustomTextBox_TextChanged;
} }
protected override void OnKeyDown(KeyEventArgs e) { protected override void OnKeyDown(KeyEventArgs e) {
QueryTextBox input = (QueryTextBox) Parent!; QueryTextBox input = (QueryTextBox) Parent!;
CommandHistory history = input.history; CommandHistory history = input.history;
Keys key = e.KeyCode; Keys key = e.KeyCode;
bool handled = false; bool handled = false;
switch (key) { switch (key) {
case Keys.Enter: case Keys.Enter:
if (Text != string.Empty) { if (Text != string.Empty) {
input.OnCommandRan(); input.OnCommandRan();
Text = string.Empty; Text = string.Empty;
doResetHistoryMemory = true; doResetHistoryMemory = true;
handled = true; handled = true;
} }
break; break;
case Keys.Up: case Keys.Up:
if (lastArrowShift != e.Shift) { if (lastArrowShift != e.Shift) {
lastArrowShift = e.Shift; lastArrowShift = e.Shift;
historyOffset = 0; historyOffset = 0;
} }
--historyOffset; --historyOffset;
if (InsertFromHistory(e.Shift ? history.Results : history.Queries)) { if (InsertFromHistory(e.Shift ? history.Results : history.Queries)) {
++historyOffset; ++historyOffset;
} }
handled = true; handled = true;
break; break;
case Keys.Down: case Keys.Down:
if (lastArrowShift != e.Shift) { if (lastArrowShift != e.Shift) {
lastArrowShift = e.Shift; lastArrowShift = e.Shift;
historyOffset = 0; historyOffset = 0;
} }
++historyOffset; ++historyOffset;
if (InsertFromHistory(e.Shift ? history.Results : history.Queries)) { if (InsertFromHistory(e.Shift ? history.Results : history.Queries)) {
--historyOffset; --historyOffset;
} }
handled = true; handled = true;
break; break;
case Keys.C: case Keys.C:
if (e.Modifiers == Keys.Control) { if (e.Modifiers == Keys.Control) {
if (SelectionLength == 0 && history.Results.Count > 0) { if (SelectionLength == 0 && history.Results.Count > 0) {
@ -94,47 +94,47 @@ sealed partial class QueryTextBox : UserControl {
handled = true; handled = true;
} }
} }
break; break;
} }
if (!handled && key != Keys.ControlKey && key != Keys.ShiftKey && key != Keys.Menu) { if (!handled && key != Keys.ControlKey && key != Keys.ShiftKey && key != Keys.Menu) {
doResetHistoryMemory = true; doResetHistoryMemory = true;
} }
e.Handled = e.SuppressKeyPress = handled; e.Handled = e.SuppressKeyPress = handled;
base.OnKeyDown(e); base.OnKeyDown(e);
} }
protected override void OnKeyUp(KeyEventArgs e) { protected override void OnKeyUp(KeyEventArgs e) {
base.OnKeyUp(e); base.OnKeyUp(e);
if (doResetHistoryMemory) { if (doResetHistoryMemory) {
doResetHistoryMemory = false; doResetHistoryMemory = false;
ResetHistoryMemory(); ResetHistoryMemory();
} }
} }
private void CustomTextBox_TextChanged(object? sender, EventArgs e) { private void CustomTextBox_TextChanged(object? sender, EventArgs e) {
ResetHistoryMemory(); ResetHistoryMemory();
} }
// Management // Management
private void ResetHistoryMemory() { private void ResetHistoryMemory() {
lastInputStr = Text; lastInputStr = Text;
lastInputPos = SelectionStart; lastInputPos = SelectionStart;
historyOffset = 0; historyOffset = 0;
} }
private bool InsertFromHistory(IList<string> collection) { private bool InsertFromHistory(IList<string> collection) {
if (collection.Count == 0) { if (collection.Count == 0) {
return true; return true;
} }
int index = collection.Count + historyOffset; int index = collection.Count + historyOffset;
bool wasClamped = false; bool wasClamped = false;
if (index < 0) { if (index < 0) {
index = 0; index = 0;
wasClamped = true; wasClamped = true;
@ -143,13 +143,13 @@ sealed partial class QueryTextBox : UserControl {
index = collection.Count - 1; index = collection.Count - 1;
wasClamped = true; wasClamped = true;
} }
TextChanged -= CustomTextBox_TextChanged; TextChanged -= CustomTextBox_TextChanged;
Text = lastInputStr.Insert(lastInputPos, collection[index]); Text = lastInputStr.Insert(lastInputPos, collection[index]);
SelectionStart = lastInputPos + collection[index].Length; SelectionStart = lastInputPos + collection[index].Length;
SelectionLength = 0; SelectionLength = 0;
TextChanged += CustomTextBox_TextChanged; TextChanged += CustomTextBox_TextChanged;
return wasClamped; return wasClamped;
} }

View File

@ -8,6 +8,7 @@ static class Program {
[STAThread] [STAThread]
private static void Main() { private static void Main() {
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm()); Application.Run(new MainForm());
} }