├── Kalk ├── bin │ └── Debug │ │ ├── Kalk.exe │ │ └── Kalk.exe.mdb ├── test-results │ └── Kalk.csproj.test-cache ├── EquationParseException.cs ├── Program.cs ├── Properties │ ├── MathFunction.cs │ └── AssemblyInfo.cs ├── PipelineTests.cs ├── Variables.cs ├── gtk-gui │ ├── generated.cs │ ├── MainWindow.cs │ └── gui.stetic ├── EvaluatePostfixTests.cs ├── TokenizeExpressionTests.cs ├── EvaluatePostfix.cs ├── InfixToPostfixTests.cs ├── Functions.cs ├── Kalk.csproj ├── MainWindow.cs └── InfixToPostfix.cs ├── README.md ├── Kalk.sln ├── LICENSE ├── Kalk.userprefs ├── .gitignore └── test-results └── {9200EDA3-7932-431A-B5FF-2ECC7A00AD0A}-default-2014-01-13.xml /Kalk/bin/Debug/Kalk.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamaluik/Kalk/master/Kalk/bin/Debug/Kalk.exe -------------------------------------------------------------------------------- /Kalk/bin/Debug/Kalk.exe.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamaluik/Kalk/master/Kalk/bin/Debug/Kalk.exe.mdb -------------------------------------------------------------------------------- /Kalk/test-results/Kalk.csproj.test-cache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamaluik/Kalk/master/Kalk/test-results/Kalk.csproj.test-cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kalk 2 | ==== 3 | 4 | A calculator I'm making for my own use to fix issues with stock calculations available, and also as a programming exercise. 5 | -------------------------------------------------------------------------------- /Kalk/EquationParseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kalk 4 | { 5 | public class EquationParseException : Exception 6 | { 7 | public EquationParseException (string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Kalk/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Gtk; 3 | 4 | namespace Kalk 5 | { 6 | class MainClass 7 | { 8 | public static void Main (string[] args) 9 | { 10 | Application.Init (); 11 | MainWindow win = new MainWindow (); 12 | win.Show (); 13 | Application.Run (); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Kalk/Properties/MathFunction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kalk 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | public class MathFunction : System.Attribute 7 | { 8 | public int nArgs = 0; 9 | 10 | public MathFunction (int nArgs) 11 | { 12 | this.nArgs = nArgs; 13 | } 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Kalk/PipelineTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace Kalk 5 | { 6 | [TestFixture] 7 | public class PipelineTests 8 | { 9 | private double _result; 10 | 11 | [Test] 12 | public void HandlesNegativeScientificNotation () 13 | { 14 | Given ("5*10^-3"); 15 | Expect (0.005); 16 | } 17 | 18 | private void Given (string expression) 19 | { 20 | string postFix = InfixToPostix.Transform (expression); 21 | _result = EvaluatePostfix.Transform (postFix); 22 | } 23 | 24 | private void Expect (double expected) 25 | { 26 | Assert.AreEqual (expected, _result); 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Kalk.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kalk", "Kalk\Kalk.csproj", "{54AD5E5A-C9A3-45AD-BDF9-E610FF29D308}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {54AD5E5A-C9A3-45AD-BDF9-E610FF29D308}.Debug|x86.ActiveCfg = Debug|x86 13 | {54AD5E5A-C9A3-45AD-BDF9-E610FF29D308}.Debug|x86.Build.0 = Debug|x86 14 | {54AD5E5A-C9A3-45AD-BDF9-E610FF29D308}.Release|x86.ActiveCfg = Release|x86 15 | {54AD5E5A-C9A3-45AD-BDF9-E610FF29D308}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(MonoDevelopProperties) = preSolution 18 | StartupItem = Kalk\Kalk.csproj 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /Kalk/Variables.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Kalk 5 | { 6 | public class Variables 7 | { 8 | public static Dictionary constants = new Dictionary (); 9 | public static Dictionary variables = new Dictionary (); 10 | 11 | static Variables () 12 | { 13 | constants.Add ("pi", Math.PI); 14 | 15 | foreach (char c in "abcdefg") { 16 | variables.Add (c.ToString (), 0); 17 | } 18 | } 19 | 20 | public static double EvaluateLiteral (string literal) 21 | { 22 | // if it's a constant or a variable, just spout back the value 23 | if (constants.ContainsKey (literal)) 24 | return constants [literal]; 25 | else if (variables.ContainsKey (literal)) 26 | return variables [literal]; 27 | 28 | // otherwise, it must be a numeric value 29 | double val = 0; 30 | if (!double.TryParse (literal, out val)) 31 | // TODO: not a value 32 | throw new EquationParseException ("Invalid literal: " + literal); 33 | return val; 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Kalk/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | [assembly: AssemblyTitle ("Kalk")] 7 | [assembly: AssemblyDescription ("")] 8 | [assembly: AssemblyConfiguration ("")] 9 | [assembly: AssemblyCompany ("Blazing Mammoth Games")] 10 | [assembly: AssemblyProduct ("")] 11 | [assembly: AssemblyCopyright ("kenton")] 12 | [assembly: AssemblyTrademark ("")] 13 | [assembly: AssemblyCulture ("")] 14 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 15 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 16 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 17 | [assembly: AssemblyVersion ("1.0.*")] 18 | // The following attributes are used to specify the signing key for the assembly, 19 | // if desired. See the Mono documentation for more information about signing. 20 | //[assembly: AssemblyDelaySign(false)] 21 | //[assembly: AssemblyKeyFile("")] 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kenton Hamaluik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Kalk.userprefs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /Kalk/gtk-gui/generated.cs: -------------------------------------------------------------------------------- 1 | 2 | // This file has been generated by the GUI designer. Do not modify. 3 | namespace Stetic 4 | { 5 | internal class Gui 6 | { 7 | private static bool initialized; 8 | 9 | internal static void Initialize (Gtk.Widget iconRenderer) 10 | { 11 | if ((Stetic.Gui.initialized == false)) { 12 | Stetic.Gui.initialized = true; 13 | } 14 | } 15 | } 16 | 17 | internal class IconLoader 18 | { 19 | public static Gdk.Pixbuf LoadIcon (Gtk.Widget widget, string name, Gtk.IconSize size) 20 | { 21 | Gdk.Pixbuf res = widget.RenderIcon (name, size, null); 22 | if ((res != null)) { 23 | return res; 24 | } else { 25 | int sz; 26 | int sy; 27 | global::Gtk.Icon.SizeLookup (size, out sz, out sy); 28 | try { 29 | return Gtk.IconTheme.Default.LoadIcon (name, sz, 0); 30 | } catch (System.Exception) { 31 | if ((name != "gtk-missing-image")) { 32 | return Stetic.IconLoader.LoadIcon (widget, "gtk-missing-image", size); 33 | } else { 34 | Gdk.Pixmap pmap = new Gdk.Pixmap (Gdk.Screen.Default.RootWindow, sz, sz); 35 | Gdk.GC gc = new Gdk.GC (pmap); 36 | gc.RgbFgColor = new Gdk.Color (255, 255, 255); 37 | pmap.DrawRectangle (gc, true, 0, 0, sz, sz); 38 | gc.RgbFgColor = new Gdk.Color (0, 0, 0); 39 | pmap.DrawRectangle (gc, false, 0, 0, (sz - 1), (sz - 1)); 40 | gc.SetLineAttributes (3, Gdk.LineStyle.Solid, Gdk.CapStyle.Round, Gdk.JoinStyle.Round); 41 | gc.RgbFgColor = new Gdk.Color (255, 0, 0); 42 | pmap.DrawLine (gc, (sz / 4), (sz / 4), ((sz - 1) - (sz / 4)), ((sz - 1) - (sz / 4))); 43 | pmap.DrawLine (gc, ((sz - 1) - (sz / 4)), (sz / 4), (sz / 4), ((sz - 1) - (sz / 4))); 44 | return Gdk.Pixbuf.FromDrawable (pmap, pmap.Colormap, 0, 0, 0, 0, sz, sz); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | internal class ActionGroups 52 | { 53 | public static Gtk.ActionGroup GetActionGroup (System.Type type) 54 | { 55 | return Stetic.ActionGroups.GetActionGroup (type.FullName); 56 | } 57 | 58 | public static Gtk.ActionGroup GetActionGroup (string name) 59 | { 60 | return null; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Kalk/EvaluatePostfixTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace Kalk 5 | { 6 | [TestFixture] 7 | public class EvaluatePostfixTests 8 | { 9 | double _result; 10 | 11 | [Test] 12 | public void EmptyExpressionResultsInZero () 13 | { 14 | Given (""); 15 | Expect (0); 16 | } 17 | 18 | [Test] 19 | public void SingleValueResultsInSame () 20 | { 21 | Given ("42"); 22 | Expect (42); 23 | } 24 | 25 | [Test] 26 | public void SimpleAdditionIsCorrect () 27 | { 28 | Given ("4 2 +"); 29 | Expect (6); 30 | } 31 | 32 | [Test] 33 | public void SimpleSubtractionIsCorrect () 34 | { 35 | Given ("4 2 -"); 36 | Expect (2); 37 | } 38 | 39 | [Test] 40 | public void SimpleMultiplicationIsCorrect () 41 | { 42 | Given ("4 2 *"); 43 | Expect (8); 44 | } 45 | 46 | [Test] 47 | public void SimpleDivisionIsCorrect () 48 | { 49 | Given ("4 2 /"); 50 | Expect (2); 51 | } 52 | 53 | [Test] 54 | public void SimpleExponentIsCorrect () 55 | { 56 | Given ("4 2 ^"); 57 | Expect (16); 58 | } 59 | 60 | [Test] 61 | public void SimpleFunctionIsCorrect () 62 | { 63 | Given ("4 sqrt"); 64 | Expect (2); 65 | } 66 | 67 | [Test] 68 | public void ATan2IsCorrect () 69 | { 70 | Given ("1 0 atan2"); 71 | Expect (90); 72 | } 73 | 74 | [Test] 75 | public void LongerExpressionIsCorrect () 76 | { 77 | Given ("5 1 2 + 4 * + 3 -"); 78 | Expect (14); 79 | } 80 | 81 | [Test] 82 | public void DecimalsAreCorrect () 83 | { 84 | Given ("42.42 3.14159 +"); 85 | Expect (45.56159); 86 | } 87 | 88 | [Test] 89 | public void HandlesNegativeExponent () 90 | { 91 | Given ("10 -3 ^"); 92 | Expect (0.001); 93 | } 94 | 95 | [Test] 96 | public void HandlesPositiveExponent () 97 | { 98 | Given ("10 3 ^"); 99 | Expect (1000); 100 | } 101 | 102 | private void Given (string expression) 103 | { 104 | _result = EvaluatePostfix.Transform (expression); 105 | } 106 | 107 | private void Expect (double expected) 108 | { 109 | Assert.That (_result, Is.EqualTo (expected)); 110 | } 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /Kalk/TokenizeExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | 5 | namespace Kalk 6 | { 7 | [TestFixture] 8 | public class TokenizeExpressionTests 9 | { 10 | string _result; 11 | 12 | [Test] 13 | public void HandlesSingleItem () 14 | { 15 | Given ("5"); 16 | Expect ("5"); 17 | } 18 | 19 | [Test] 20 | public void HandlesSimpleExpressionWithSpaces () 21 | { 22 | Given ("1 + 1"); 23 | Expect ("1|+|1"); 24 | } 25 | 26 | [Test] 27 | public void HandlesSimpleExpressionWithoutSpaces () 28 | { 29 | Given ("a*b"); 30 | Expect ("a|*|b"); 31 | } 32 | 33 | [Test] 34 | public void HandlesParentheses () 35 | { 36 | Given ("(a * 2)-3"); 37 | Expect ("(|a|*|2|)|-|3"); 38 | } 39 | 40 | [Test] 41 | public void HandlesSingleNegativeNumbers () 42 | { 43 | Given ("-2"); 44 | Expect ("-2"); 45 | } 46 | 47 | [Test] 48 | public void HandlesNegativeNumbersAfterOperator () 49 | { 50 | Given ("a + -2"); 51 | Expect ("a|+|-2"); 52 | } 53 | 54 | [Test] 55 | public void HandlesNegativeNumbersInParentheses () 56 | { 57 | Given ("(-2)"); 58 | Expect ("(|-2|)"); 59 | } 60 | 61 | [Test] 62 | public void HandlesSingleNegativeVariables () 63 | { 64 | Given ("-pi"); 65 | Expect ("(|-1|*|pi|)"); 66 | } 67 | 68 | [Test] 69 | public void HandlesSingleNegativeFunctions () 70 | { 71 | Given ("-sqrt(4)"); 72 | Expect ("-1|*|sqrt|(|4|)"); 73 | } 74 | 75 | [Test] 76 | public void HandlesNegativeVariablesInExpression () 77 | { 78 | Given ("5 * -pi"); 79 | Expect ("5|*|(|-1|*|pi|)"); 80 | } 81 | 82 | [Test] 83 | public void HandlesComplexCase () 84 | { 85 | Given ("(-2)*(-5^-1)"); 86 | Expect ("(|-2|)|*|(|-5|^|-1|)"); 87 | } 88 | 89 | private void Given (string expression) 90 | { 91 | List tokens = InfixToPostix.tokenizeExpression (expression); 92 | _result = string.Join ("|", tokens.ToArray ()); 93 | } 94 | 95 | private void Expect (string expected) 96 | { 97 | //Assert.That (_result, Is.EqualTo (expected)); 98 | Assert.AreEqual (expected, _result); 99 | } 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Kalk/EvaluatePostfix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Kalk 5 | { 6 | public class EvaluatePostfix 7 | { 8 | private static Dictionary _operatorShortcuts = new Dictionary (); 9 | 10 | static EvaluatePostfix () 11 | { 12 | _operatorShortcuts.Add ("+", "add"); 13 | _operatorShortcuts.Add ("-", "subtract"); 14 | _operatorShortcuts.Add ("*", "multiply"); 15 | _operatorShortcuts.Add ("/", "divide"); 16 | _operatorShortcuts.Add ("×", "multiply"); 17 | _operatorShortcuts.Add ("÷", "divide"); 18 | _operatorShortcuts.Add ("^", "exponent"); 19 | } 20 | 21 | public static double Transform (string expression) 22 | { 23 | // empty strings should return 0 24 | if (expression == null || expression.Trim () == "") 25 | return 0; 26 | 27 | // tokenize the expression 28 | // noting that things will be separated by spaces to make our lives 29 | // easier a priori 30 | string[] tokens = expression.Split (' '); 31 | 32 | // our stack of values 33 | Stack valueStack = new Stack (); 34 | 35 | // go through all of the tokens 36 | foreach (string token in tokens) { 37 | // determine if it is a value or an "operator" 38 | if (_operatorShortcuts.ContainsKey (token) || Functions.functionList.ContainsKey (token)) { 39 | // it's an "operator" 40 | // get it! 41 | Functions.Function function = null; 42 | if (_operatorShortcuts.ContainsKey (token)) 43 | function = Functions.functionList [_operatorShortcuts [token]]; 44 | else 45 | function = Functions.functionList [token]; 46 | 47 | // make sure we have enough values on the stack 48 | if (valueStack.Count < function.nArgs) 49 | // TODO: better exceptions 50 | throw new EquationParseException ("Not enough values to process!"); 51 | 52 | // now pop those values off 53 | double[] x = new double[function.nArgs]; 54 | for (int i = 0; i < function.nArgs; i++) 55 | x [i] = valueStack.Pop (); 56 | 57 | // evaluate it! 58 | double result = function.function (x); 59 | valueStack.Push (result); 60 | 61 | } else { 62 | // it's a value! 63 | valueStack.Push (Variables.EvaluateLiteral (token)); 64 | } 65 | } 66 | 67 | // now, we should only have a single value on the stack - the result 68 | // if so, return it 69 | if (valueStack.Count == 1) 70 | return valueStack.Pop (); 71 | 72 | // there are too many values! 73 | throw new EquationParseException ("Not enough operators for values!"); 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Kalk/InfixToPostfixTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace Kalk 5 | { 6 | [TestFixture] 7 | public class InfixToPostfixTests 8 | { 9 | string _result; 10 | 11 | [Test] 12 | public void EmptyExpressionResultsInSame () 13 | { 14 | Given (""); 15 | Expect (""); 16 | } 17 | 18 | [Test] 19 | public void NullExpressionProducesEmptyResults () 20 | { 21 | Given (null); 22 | Expect (""); 23 | } 24 | 25 | [Test] 26 | public void JustANumberResultsInSame () 27 | { 28 | Given ("42"); 29 | Expect ("42"); 30 | } 31 | 32 | [Test] 33 | public void HandlesASingleBinaryOperator () 34 | { 35 | Given ("4 + 2"); 36 | Expect ("4 2 +"); 37 | } 38 | 39 | [Test] 40 | public void HandlesMissingWhitespace () 41 | { 42 | Given ("4+2"); 43 | Expect ("4 2 +"); 44 | } 45 | 46 | [Test] 47 | public void HandlesMultipleOperatorsOfSamePrecedence () 48 | { 49 | Given ("a - 5 + 3"); 50 | Expect ("a 5 - 3 +"); 51 | } 52 | 53 | [Test] 54 | public void HandlesASingleDecimalNumber () 55 | { 56 | Given ("42.42"); 57 | Expect ("42.42"); 58 | } 59 | 60 | [Test] 61 | public void HandlesOperatorPrecedence () 62 | { 63 | Given ("2 + 9 * 6"); 64 | Expect ("2 9 6 * +"); 65 | } 66 | 67 | [Test] 68 | public void HandlesRightAssociativeOperators () 69 | { 70 | Given ("2 * 10 ^ 6"); 71 | Expect ("2 10 6 ^ *"); 72 | } 73 | 74 | [Test] 75 | public void HandlesMultipleAssociativeOperators () 76 | { 77 | Given ("2 ^ 3 ^ 4"); 78 | Expect ("2 3 4 ^ ^"); 79 | } 80 | 81 | [Test] 82 | public void HandlesBrackets () 83 | { 84 | Given ("( 3 + 4 )"); 85 | Expect ("3 4 +"); 86 | } 87 | 88 | [Test] 89 | public void HandlesBracketsAffectingOrderOfOperations () 90 | { 91 | Given ("( 3 + 4 ) * 5"); 92 | Expect ("3 4 + 5 *"); 93 | } 94 | 95 | [Test] 96 | public void HandlesNestedBrackets () 97 | { 98 | Given ("( 3 + ( 4 - 5 ) ) * 6"); 99 | Expect ("3 4 5 - + 6 *"); 100 | } 101 | 102 | [Test] 103 | public void HandlesTokensWithoutSpaces () 104 | { 105 | Given ("5+3*2"); 106 | Expect ("5 3 2 * +"); 107 | } 108 | 109 | [Test] 110 | public void HandlesFunctionCalls () 111 | { 112 | Given ("sin(3)"); 113 | Expect ("3 sin"); 114 | } 115 | 116 | [Test] 117 | public void HandlesNestedFunctions () 118 | { 119 | Given ("sin(cos(4))"); 120 | Expect ("4 cos sin"); 121 | } 122 | 123 | [Test] 124 | public void HandlesMultipleFunctionParameters () 125 | { 126 | Given ("atan2(3, 4)"); 127 | Expect ("3 4 atan2"); 128 | } 129 | 130 | [Test] 131 | public void HandlesComplexCase1 () 132 | { 133 | Given ("3 + 4 * 2 / (1 - 5) ^ 2 ^ 3"); 134 | Expect ("3 4 2 * 1 5 - 2 3 ^ ^ / +"); 135 | } 136 | 137 | [Test] 138 | public void HandlesComplexCase2 () 139 | { 140 | Given ("tan(4+5, 1 + a^2, (8+b)*10)"); 141 | Expect ("4 5 + 1 a 2 ^ + 8 b + 10 * tan"); 142 | } 143 | 144 | private void Given (string expression) 145 | { 146 | _result = InfixToPostix.Transform (expression); 147 | } 148 | 149 | private void Expect (string expected) 150 | { 151 | Assert.That (_result, Is.EqualTo (expected)); 152 | } 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /Kalk/Functions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | namespace Kalk 7 | { 8 | public class Functions 9 | { 10 | public static bool useDegrees = true; 11 | 12 | public delegate double FunctionDelegate (double[] x); 13 | 14 | public class Function 15 | { 16 | public FunctionDelegate function; 17 | public int nArgs = 0; 18 | 19 | public Function (FunctionDelegate function, int nArgs) 20 | { 21 | this.function = function; 22 | this.nArgs = nArgs; 23 | } 24 | } 25 | 26 | public static Dictionary functionList = new Dictionary (); 27 | 28 | static Functions () 29 | { 30 | MethodInfo[] functions = typeof(Functions).GetMethods ().Where (m => m.GetCustomAttributes (typeof(MathFunction), false).Length > 0).ToArray (); 31 | foreach (MethodInfo function in functions) { 32 | string name = function.Name; 33 | int nArgs = 0; 34 | foreach (object attrib in function.GetCustomAttributes(false)) { 35 | if (attrib.GetType ().Equals (typeof(MathFunction))) { 36 | nArgs = ((MathFunction)attrib).nArgs; 37 | break; 38 | } 39 | } 40 | FunctionDelegate d = (FunctionDelegate)Delegate.CreateDelegate (typeof(FunctionDelegate), 41 | function); 42 | functionList.Add (name.ToLower (), new Function (d, nArgs)); 43 | } 44 | } 45 | 46 | [MathFunction(2)] 47 | public static double Add (double[] x) 48 | { 49 | return x [0] + x [1]; 50 | } 51 | 52 | [MathFunction(2)] 53 | public static double Subtract (double[] x) 54 | { 55 | return x [1] - x [0]; 56 | } 57 | 58 | [MathFunction(2)] 59 | public static double Multiply (double[] x) 60 | { 61 | return x [0] * x [1]; 62 | } 63 | 64 | [MathFunction(2)] 65 | public static double Divide (double[] x) 66 | { 67 | return x [1] / x [0]; 68 | } 69 | 70 | [MathFunction(2)] 71 | public static double Exponent (double[] x) 72 | { 73 | return Math.Pow (x [1], x [0]); 74 | } 75 | 76 | [MathFunction(1)] 77 | public static double Sqrt (double[] x) 78 | { 79 | return Math.Sqrt (x [0]); 80 | } 81 | 82 | [MathFunction(1)] 83 | public static double Sin (double[] x) 84 | { 85 | return Math.Sin (x [0] * (useDegrees ? (Math.PI / 180.0) : (1))); 86 | } 87 | 88 | [MathFunction(1)] 89 | public static double Cos (double[] x) 90 | { 91 | return Math.Cos (x [0] * (useDegrees ? (Math.PI / 180.0) : (1))); 92 | } 93 | 94 | [MathFunction(1)] 95 | public static double Tan (double[] x) 96 | { 97 | return Math.Tan (x [0] * (useDegrees ? (Math.PI / 180.0) : (1))); 98 | } 99 | 100 | [MathFunction(1)] 101 | public static double Asin (double[] x) 102 | { 103 | return Math.Asin (x [0]) * (useDegrees ? 180.0 / Math.PI : 1); 104 | } 105 | 106 | [MathFunction(1)] 107 | public static double Acos (double[] x) 108 | { 109 | return Math.Acos (x [0]) * (useDegrees ? 180.0 / Math.PI : 1); 110 | } 111 | 112 | [MathFunction(1)] 113 | public static double Atan (double[] x) 114 | { 115 | return Math.Atan (x [0]) * (useDegrees ? 180.0 / Math.PI : 1); 116 | } 117 | 118 | [MathFunction(2)] 119 | public static double Atan2 (double[] x) 120 | { 121 | return Math.Atan2 (x [1], x [0]) * (useDegrees ? 180.0 / Math.PI : 1); 122 | } 123 | 124 | [MathFunction(1)] 125 | public static double Ln (double[] x) 126 | { 127 | return Math.Log (x [0]); 128 | } 129 | 130 | [MathFunction(1)] 131 | public static double Log (double[] x) 132 | { 133 | return Math.Log10 (x [0]); 134 | } 135 | } 136 | } 137 | 138 | -------------------------------------------------------------------------------- /Kalk/Kalk.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 10.0.0 7 | 2.0 8 | {54AD5E5A-C9A3-45AD-BDF9-E610FF29D308} 9 | WinExe 10 | Kalk 11 | Kalk 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug 18 | DEBUG; 19 | prompt 20 | 4 21 | false 22 | x86 23 | 24 | 25 | full 26 | true 27 | bin\Release 28 | prompt 29 | 4 30 | false 31 | x86 32 | 33 | 34 | 35 | 36 | False 37 | gtk-sharp-2.0 38 | 39 | 40 | False 41 | gtk-sharp-2.0 42 | 43 | 44 | False 45 | glib-sharp-2.0 46 | 47 | 48 | False 49 | glade-sharp-2.0 50 | 51 | 52 | False 53 | gtk-sharp-2.0 54 | 55 | 56 | False 57 | gtk-sharp-2.0 58 | 59 | 60 | 61 | False 62 | 63 | 64 | 65 | 66 | 67 | gui.stetic 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /test-results/{9200EDA3-7932-431A-B5FF-2ECC7A00AD0A}-default-2014-01-13.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 2014-01-13T18:46:09 6 | 7 | 0 8 | 0 9 | 1 10 | 0 11 | 0 12 | 0 13 | 0 14 | 16 | 17 | 2014-01-13T18:47:17 18 | 19 | 1 20 | 0 21 | 0 22 | 0 23 | 0 24 | 0 25 | 0 26 | 28 | 29 | 2014-01-13T18:48:58 30 | 31 | 2 32 | 0 33 | 1 34 | 0 35 | 0 36 | 0 37 | 0 38 | 40 | 41 | 2014-01-13T19:11:05 42 | 43 | 3 44 | 2 45 | 0 46 | 0 47 | 0 48 | 0 49 | 0 50 | 52 | 53 | 2014-01-13T19:12:15 54 | 55 | 4 56 | 1 57 | 0 58 | 0 59 | 0 60 | 0 61 | 0 62 | 64 | 65 | 2014-01-13T19:12:19 66 | 67 | 4 68 | 1 69 | 0 70 | 0 71 | 0 72 | 0 73 | 0 74 | 76 | 77 | 2014-01-13T19:13:52 78 | 79 | 4 80 | 0 81 | 1 82 | 0 83 | 0 84 | 0 85 | 0 86 | 88 | 89 | 2014-01-13T19:14:42 90 | 91 | 4 92 | 0 93 | 1 94 | 0 95 | 0 96 | 0 97 | 0 98 | 100 | 101 | 2014-01-13T19:19:42 102 | 103 | 5 104 | 0 105 | 0 106 | 0 107 | 0 108 | 0 109 | 0 110 | 112 | 113 | 2014-01-13T19:20:35 114 | 115 | 5 116 | 0 117 | 0 118 | 0 119 | 0 120 | 0 121 | 0 122 | 124 | 125 | 2014-01-13T19:21:04 126 | 127 | 5 128 | 0 129 | 0 130 | 0 131 | 0 132 | 0 133 | 0 134 | 136 | 137 | -------------------------------------------------------------------------------- /Kalk/MainWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Gtk; 3 | using System.Collections.Generic; 4 | 5 | public partial class MainWindow: Gtk.Window 6 | { 7 | private Gtk.TreeViewColumn equationColumn, resultColumn, varNameColumn, varValueColumn; 8 | private Gtk.ListStore equationHistoryStore, varInspectorStore; 9 | 10 | public MainWindow (): base (Gtk.WindowType.Toplevel) 11 | { 12 | Build (); 13 | 14 | // set up our notebook 15 | 16 | // set up our equation editor 17 | widget_equationEditor.GrabFocus (); 18 | widget_equationEditor.Text = ""; 19 | Pango.FontDescription fontDesc = new Pango.FontDescription (); 20 | fontDesc.Family = "Sans"; 21 | fontDesc.Size = (int)(18.0 * Pango.Scale.PangoScale); 22 | fontDesc.Weight = Pango.Weight.Normal; 23 | widget_equationEditor.ModifyFont (fontDesc); 24 | 25 | // our calculation results 26 | widget_calculationResult.Xalign = 1f; 27 | widget_calculationResult.ModifyFont (fontDesc); 28 | 29 | // and our equation history 30 | equationColumn = new Gtk.TreeViewColumn (); 31 | equationColumn.Title = "Equation"; 32 | resultColumn = new Gtk.TreeViewColumn (); 33 | resultColumn.Title = "Result"; 34 | widget_equationHistory.AppendColumn (equationColumn); 35 | widget_equationHistory.AppendColumn (resultColumn); 36 | 37 | // create the text cell to display it 38 | Gtk.CellRendererText equationHistoryCell = new Gtk.CellRendererText (); 39 | Gtk.CellRendererText resultHistoryCell = new Gtk.CellRendererText (); 40 | // add the cell to the column 41 | equationColumn.PackStart (equationHistoryCell, true); 42 | resultColumn.PackStart (resultHistoryCell, true); 43 | 44 | // tell the tree view wihich items to display 45 | equationColumn.AddAttribute (equationHistoryCell, "text", 0); 46 | resultColumn.AddAttribute (resultHistoryCell, "text", 1); 47 | 48 | // apply our model 49 | equationHistoryStore = new Gtk.ListStore (typeof(string), typeof(double)); 50 | widget_equationHistory.Model = equationHistoryStore; 51 | 52 | // set up our variable inspector 53 | varNameColumn = new Gtk.TreeViewColumn (); 54 | varNameColumn.Title = "Variable"; 55 | varValueColumn = new Gtk.TreeViewColumn (); 56 | varValueColumn.Title = "Value"; 57 | widget_variableInspector.AppendColumn (varNameColumn); 58 | widget_variableInspector.AppendColumn (varValueColumn); 59 | 60 | // create the text cell to display it 61 | Gtk.CellRendererText varNameCell = new Gtk.CellRendererText (); 62 | Gtk.CellRendererText varValueCell = new Gtk.CellRendererText (); 63 | varNameColumn.PackStart (varNameCell, true); 64 | varValueColumn.PackStart (varValueCell, true); 65 | varNameColumn.AddAttribute (varNameCell, "text", 0); 66 | varValueColumn.AddAttribute (varValueCell, "text", 1); 67 | varInspectorStore = new Gtk.ListStore (typeof(string), typeof(double)); 68 | widget_variableInspector.Model = varInspectorStore; 69 | 70 | // add all our variables to the variable inspector 71 | foreach (KeyValuePair entry in Kalk.Variables.variables) { 72 | varInspectorStore.AppendValues (entry.Key, entry.Value); 73 | } 74 | } 75 | 76 | protected void OnDeleteEvent (object sender, DeleteEventArgs a) 77 | { 78 | Application.Quit (); 79 | a.RetVal = true; 80 | } 81 | 82 | protected void OnAbout (object sender, EventArgs e) 83 | { 84 | MessageDialog aboutDialogue = new MessageDialog ( 85 | this, 86 | DialogFlags.Modal | DialogFlags.DestroyWithParent, 87 | MessageType.Info, 88 | ButtonsType.Close, 89 | "Kalk (c) 2014 Kenton Hamaluik\nkenton@hamaluik.com\nhamaluik.com"); 90 | aboutDialogue.Run (); 91 | aboutDialogue.Destroy (); 92 | } 93 | 94 | protected void OnQuit (object sender, EventArgs e) 95 | { 96 | Application.Quit (); 97 | } 98 | 99 | protected string fixBrackets (string expression) 100 | { 101 | // count the number of opening brackets 102 | int numOpening = 0, numClosing = 0; 103 | foreach (char c in expression) { 104 | if (c == '(') 105 | numOpening++; 106 | else if (c == ')') 107 | numClosing++; 108 | } 109 | 110 | // if we have more closing than opening, error 111 | if (numClosing > numOpening) 112 | throw new Kalk.EquationParseException ("More closing braces than opening ones!"); 113 | 114 | // if we have fewer closing than opening, add closing ones 115 | int delta = numOpening - numClosing; 116 | for (int i = 0; i < delta; i++) 117 | expression += ")"; 118 | return expression; 119 | } 120 | 121 | protected void OnSolve (object sender, EventArgs e) 122 | { 123 | // get the expression from the equation editor 124 | string expression = widget_equationEditor.Text; 125 | 126 | // check exceptions for parsing errors 127 | double result = 0; 128 | try { 129 | // fix the brackets 130 | 131 | // transform it into postfix notation 132 | expression = Kalk.InfixToPostix.Transform (expression); 133 | 134 | // and solve! 135 | result = Kalk.EvaluatePostfix.Transform (expression); 136 | } catch (Exception exc) { 137 | widget_statusBar.Push (widget_statusBar.GetContextId ("error"), "Error: " + exc.Message); 138 | return; 139 | } 140 | 141 | // show the result in the result window 142 | // TODO: engineering, scientific notation, etc 143 | widget_calculationResult.Text = result.ToString (); 144 | 145 | // add to our history 146 | equationHistoryStore.AppendValues (widget_equationEditor.Text, result); 147 | 148 | // clear our equation editor 149 | widget_equationEditor.Text = ""; 150 | 151 | // clear our status bar 152 | widget_statusBar.Push (widget_statusBar.GetContextId ("ok"), "Success"); 153 | } 154 | 155 | protected void OnCopyResult (object sender, EventArgs e) 156 | { 157 | Gtk.Clipboard clipboard = Gtk.Clipboard.Get (Gdk.Atom.Intern ("CLIPBOARD", false)); 158 | clipboard.Text = widget_calculationResult.Text; 159 | } 160 | 161 | protected void OnStoreResult (object sender, EventArgs e) 162 | { 163 | Gtk.Menu menuBox = new Gtk.Menu (); 164 | foreach (string variable in Kalk.Variables.variables.Keys) { 165 | Gtk.MenuItem storeInVar = new Gtk.MenuItem (variable); 166 | storeInVar.Activated += delegate(object btnSender, EventArgs eventArgs) { 167 | // store the value 168 | double value = double.Parse (widget_calculationResult.Text); 169 | Kalk.Variables.variables [variable] = value; 170 | 171 | // update the list view 172 | Gtk.TreeIter iter; 173 | bool valid = varInspectorStore.GetIterFirst (out iter); 174 | while (valid) { 175 | if (varInspectorStore.GetValue (iter, 0).Equals (variable)) { 176 | varInspectorStore.SetValue (iter, 1, value); 177 | break; 178 | } 179 | valid = varInspectorStore.IterNext (ref iter); 180 | } 181 | }; 182 | menuBox.Append (storeInVar); 183 | } 184 | menuBox.ShowAll (); 185 | menuBox.Popup (); 186 | } 187 | 188 | protected void OnVariableClicked (object o, RowActivatedArgs args) 189 | { 190 | // figure out what was clicked on 191 | Gtk.TreeIter iter; 192 | varInspectorStore.GetIter (out iter, args.Path); 193 | 194 | // get it's name and insert it 195 | string varName = varInspectorStore.GetValue (iter, 0).ToString (); 196 | 197 | /*if (widget_equationEditor.CursorPosition > 0 198 | && widget_equationEditor.Text [widget_equationEditor.CursorPosition] != ' ') 199 | widget_equationEditor.InsertText (" " + varName); 200 | else 201 | */ 202 | //widget_equationEditor.InsertText (varName); 203 | // TODO: insert text at the cursor instead of appending! 204 | int cursorPosition = widget_equationEditor.CursorPosition; 205 | //Console.Write ("Cursor pos: " + cursorPosition); 206 | widget_equationEditor.InsertText (varName, ref cursorPosition); 207 | //Console.WriteLine (" -> " + cursorPosition); 208 | //Console.WriteLine (.ToString ()); 209 | //widget_equationEditor.Text += varName; 210 | /*widget_equationEditor.GrabFocus (); 211 | widget_equationEditor.SelectRegion (cursorPosition, cursorPosition);*/ 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Kalk/InfixToPostfix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Kalk 6 | { 7 | public class InfixToPostix 8 | { 9 | public class OperatorAttributes 10 | { 11 | public readonly int precedence; 12 | public readonly bool leftAssociative; 13 | 14 | public OperatorAttributes (int precedence, bool leftAssociative) 15 | { 16 | this.precedence = precedence; 17 | this.leftAssociative = leftAssociative; 18 | } 19 | } 20 | 21 | private static Dictionary _operatorList = new Dictionary (); 22 | 23 | static InfixToPostix () 24 | { 25 | /*_operatorList.Add ("(", new OperatorAttributes (1, false)); 26 | _operatorList.Add (")", new OperatorAttributes (1, false)); 27 | _operatorList.Add ("^", new OperatorAttributes (2, false)); 28 | _operatorList.Add ("+", new OperatorAttributes (3, true)); 29 | _operatorList.Add ("-", new OperatorAttributes (3, true)); 30 | _operatorList.Add ("*", new OperatorAttributes (4, true)); 31 | _operatorList.Add ("/", new OperatorAttributes (4, true)); 32 | _operatorList.Add ("%", new OperatorAttributes (4, true));*/ 33 | 34 | /*_operatorList.Add ("(", new OperatorAttributes (5, false)); 35 | _operatorList.Add (")", new OperatorAttributes (5, false));*/ 36 | _operatorList.Add ("^", new OperatorAttributes (4, false)); 37 | _operatorList.Add ("*", new OperatorAttributes (3, true)); 38 | _operatorList.Add ("×", new OperatorAttributes (3, true)); 39 | _operatorList.Add ("/", new OperatorAttributes (3, true)); 40 | _operatorList.Add ("÷", new OperatorAttributes (3, true)); 41 | _operatorList.Add ("+", new OperatorAttributes (2, true)); 42 | _operatorList.Add ("-", new OperatorAttributes (2, true)); 43 | } 44 | 45 | private static bool isOperator (string expression) 46 | { 47 | return _operatorList.ContainsKey (expression); 48 | } 49 | 50 | private static bool isSyntacticDevice (string expression) 51 | { 52 | return 53 | expression.Equals ("(") || 54 | expression.Equals (")") || 55 | expression.Equals (","); 56 | } 57 | 58 | private static bool isLiteral (string expression) 59 | { 60 | return Regex.IsMatch (expression, @"(\w+|(\d+\.?\d+))"); 61 | } 62 | 63 | private static bool isVariable (string expression) 64 | { 65 | return Variables.variables.ContainsKey (expression); 66 | } 67 | 68 | private static bool isConstant (string expression) 69 | { 70 | return Variables.constants.ContainsKey (expression); 71 | } 72 | 73 | private static bool isWhiteSpace (string expression) 74 | { 75 | return Regex.IsMatch (expression, @"\w+"); 76 | } 77 | 78 | private static bool isFunction (string expression) 79 | { 80 | return Functions.functionList.ContainsKey (expression.ToLower ()); 81 | //return _definedFunctions.Contains (expression.ToLower ()); 82 | } 83 | 84 | public static List tokenizeExpression (string expression) 85 | { 86 | List tokens = new List (); 87 | 88 | // start going through the string, one character at a time, building up a buffer 89 | string buffer = ""; 90 | foreach (char c in expression) { 91 | // if we have an operator or parentheses, add it as a token 92 | if (isOperator (c.ToString ()) || isSyntacticDevice (c.ToString ())) { 93 | // handle negative numbers 94 | if (c == '-') { 95 | // figure out what to do with it 96 | // if this is the first token, add it to the buffer 97 | if (tokens.Count == 0 && buffer.Equals ("")) { 98 | buffer += c; 99 | } 100 | // if there was an operator before this, it should be a negative number 101 | else if (tokens.Count > 0 && 102 | (isOperator (tokens [tokens.Count - 1]) || tokens [tokens.Count - 1] == "(") && 103 | buffer.Equals ("")) { 104 | buffer += c; 105 | } 106 | // otherwise, do the normal thing 107 | else { 108 | // flush the buffer first 109 | if (buffer.Length > 0) { 110 | tokens.Add (buffer.Trim ()); 111 | buffer = ""; 112 | } 113 | tokens.Add (c.ToString ()); 114 | } 115 | } else { 116 | // flush the buffer first 117 | if (buffer.Length > 0) { 118 | tokens.Add (buffer.Trim ()); 119 | buffer = ""; 120 | } 121 | tokens.Add (c.ToString ()); 122 | } 123 | } else if (c == ' ') { 124 | // if it's whitespace, flush the buffer 125 | if (buffer.Length > 0) { 126 | tokens.Add (buffer.Trim ()); 127 | buffer = ""; 128 | } 129 | } else if (isLiteral (c.ToString ()) || c == '.') { 130 | // add to the buffer if it's any other reasonable input 131 | buffer += c; 132 | } else { 133 | // we have no clue what this is, alert an error! 134 | // TODO: more specific exceptions! 135 | throw new EquationParseException ("Undefined symbol: " + c); 136 | } 137 | } 138 | 139 | // if our buffer is not empty, add it as a final token 140 | if (buffer.Length > 0) 141 | tokens.Add (buffer); 142 | 143 | // now go through the list again and find anything negative that isn't a number 144 | List newTokens = new List (); 145 | foreach (string token in tokens) { 146 | if (token.StartsWith ("-")) { 147 | // it starts with a negative, specially handle it 148 | string literalPart = token.Substring (1); 149 | if (isVariable (literalPart) || isConstant (literalPart)) { 150 | // if the remaining part isn't a number, add (-1 * x) 151 | newTokens.Add ("("); 152 | newTokens.Add ("-1"); 153 | newTokens.Add ("*"); 154 | newTokens.Add (literalPart); 155 | newTokens.Add (")"); 156 | } else if (isFunction (literalPart)) { 157 | // need to specially deal with functions 158 | // just be lazy for now 159 | // TODO: a better way to do this! 160 | newTokens.Add ("-1"); 161 | newTokens.Add ("*"); 162 | newTokens.Add (literalPart); 163 | } else { 164 | // it must be a number, just pass it on as is 165 | newTokens.Add (token); 166 | } 167 | } else { 168 | // pass it on 169 | newTokens.Add (token); 170 | } 171 | } 172 | 173 | return newTokens; 174 | } 175 | 176 | public static string Transform (string expression) 177 | { 178 | // check for null input 179 | if (expression == null || expression.Trim () == "") 180 | return ""; 181 | 182 | string output = ""; 183 | Stack operatorStack = new Stack (); 184 | 185 | // tokenize our items 186 | List tokens = tokenizeExpression (expression); 187 | 188 | // TODO: parse errors 189 | 190 | // loop through each of the tokens 191 | foreach (string token in tokens) { 192 | // check if it's a function token 193 | if (isFunction (token)) 194 | operatorStack.Push (token); 195 | // if it's a number (or variable), add it to the output 196 | else if (isLiteral (token)) 197 | output += token + " "; 198 | // check if it's a function argument separator 199 | else if (token.Equals (",")) { 200 | if (operatorStack.Count == 0) 201 | // we have a parse error if we have an empty argument 202 | throw new EquationParseException ("Empty argument"); 203 | while (!operatorStack.Peek().Equals("(")) { 204 | output += operatorStack.Pop () + " "; 205 | if (operatorStack.Count == 0) 206 | // if we ran out of operators on the stack and we 207 | // didn't hit a '(', then we have an error as we 208 | // didn't open the parentheses before the function! 209 | throw new EquationParseException ("No function opening brace"); 210 | } 211 | } 212 | // push left parentheses onto the stack 213 | else if (token.Equals ("(")) 214 | operatorStack.Push (token); 215 | else if (token.Equals (")")) { 216 | while (operatorStack.Peek() != "(") { 217 | output += operatorStack.Pop () + " "; 218 | 219 | // make sure our operator stack contains a "(" 220 | // (if we run out of operators and we still haven't 221 | // hit a '(', we have a parse exception!) 222 | if (operatorStack.Count == 0) 223 | // TODO: more information about the equation parse exception 224 | throw new EquationParseException ("No matching opening brace"); 225 | } 226 | 227 | // pop the "(" off of the stack 228 | operatorStack.Pop (); 229 | 230 | // if the top operator is a function, add it to the output 231 | if (operatorStack.Count > 0 && isFunction (operatorStack.Peek ())) 232 | output += operatorStack.Pop () + " "; 233 | } 234 | // it must be an operator 235 | else if (_operatorList.ContainsKey (token)) { 236 | // figure out what to do with the operator 237 | while (operatorStack.Count > 0 && 238 | isOperator(operatorStack.Peek()) 239 | && 240 | ((_operatorList[token].leftAssociative && _operatorList[token].precedence == _operatorList[operatorStack.Peek()].precedence) 241 | || 242 | (_operatorList[token].precedence < _operatorList[operatorStack.Peek()].precedence))) 243 | output += operatorStack.Pop () + " "; 244 | 245 | // add the operator token to the stack 246 | operatorStack.Push (token); 247 | } else { 248 | // it's not recognized! 249 | // TODO: write custom exception for unrecognized input 250 | throw new EquationParseException ("Unrecognized token: " + token); 251 | } 252 | } 253 | 254 | // once we're done with all the tokens, finish off the operators 255 | // TODO: check for missing parenthesis 256 | while (operatorStack.Count > 0) 257 | output += operatorStack.Pop () + " "; 258 | 259 | // return the trimmed string 260 | return output.Trim (); 261 | } 262 | } 263 | } 264 | 265 | -------------------------------------------------------------------------------- /Kalk/gtk-gui/MainWindow.cs: -------------------------------------------------------------------------------- 1 | 2 | // This file has been generated by the GUI designer. Do not modify. 3 | public partial class MainWindow 4 | { 5 | private global::Gtk.UIManager UIManager; 6 | private global::Gtk.Action FileAction; 7 | private global::Gtk.Action QuitAction; 8 | private global::Gtk.Action HelpAction; 9 | private global::Gtk.Action AboutAction; 10 | private global::Gtk.VBox vbox1; 11 | private global::Gtk.MenuBar menubar1; 12 | private global::Gtk.Notebook notebook1; 13 | private global::Gtk.VBox vbox2; 14 | private global::Gtk.HPaned hpaned1; 15 | private global::Gtk.ScrolledWindow GtkScrolledWindow; 16 | private global::Gtk.TreeView widget_equationHistory; 17 | private global::Gtk.ScrolledWindow GtkScrolledWindow1; 18 | private global::Gtk.TreeView widget_variableInspector; 19 | private global::Gtk.HBox hbox1; 20 | private global::Gtk.Button button1; 21 | private global::Gtk.Button button2; 22 | private global::Gtk.Entry widget_calculationResult; 23 | private global::Gtk.Entry widget_equationEditor; 24 | private global::Gtk.Statusbar widget_statusBar; 25 | private global::Gtk.Label label2; 26 | 27 | protected virtual void Build () 28 | { 29 | global::Stetic.Gui.Initialize (this); 30 | // Widget MainWindow 31 | this.UIManager = new global::Gtk.UIManager (); 32 | global::Gtk.ActionGroup w1 = new global::Gtk.ActionGroup ("Default"); 33 | this.FileAction = new global::Gtk.Action ("FileAction", global::Mono.Unix.Catalog.GetString ("_File"), null, null); 34 | this.FileAction.ShortLabel = global::Mono.Unix.Catalog.GetString ("_File"); 35 | w1.Add (this.FileAction, null); 36 | this.QuitAction = new global::Gtk.Action ("QuitAction", global::Mono.Unix.Catalog.GetString ("_Quit"), null, null); 37 | this.QuitAction.ShortLabel = global::Mono.Unix.Catalog.GetString ("_Quit"); 38 | w1.Add (this.QuitAction, null); 39 | this.HelpAction = new global::Gtk.Action ("HelpAction", global::Mono.Unix.Catalog.GetString ("_Help"), null, null); 40 | this.HelpAction.ShortLabel = global::Mono.Unix.Catalog.GetString ("_Help"); 41 | w1.Add (this.HelpAction, null); 42 | this.AboutAction = new global::Gtk.Action ("AboutAction", global::Mono.Unix.Catalog.GetString ("_About"), null, null); 43 | this.AboutAction.ShortLabel = global::Mono.Unix.Catalog.GetString ("_About"); 44 | w1.Add (this.AboutAction, null); 45 | this.UIManager.InsertActionGroup (w1, 0); 46 | this.AddAccelGroup (this.UIManager.AccelGroup); 47 | this.Name = "MainWindow"; 48 | this.Title = global::Mono.Unix.Catalog.GetString ("Kalk"); 49 | this.Icon = global::Stetic.IconLoader.LoadIcon (this, "stock_edit", global::Gtk.IconSize.Dialog); 50 | this.TypeHint = ((global::Gdk.WindowTypeHint)(1)); 51 | this.WindowPosition = ((global::Gtk.WindowPosition)(4)); 52 | // Container child MainWindow.Gtk.Container+ContainerChild 53 | this.vbox1 = new global::Gtk.VBox (); 54 | this.vbox1.Name = "vbox1"; 55 | this.vbox1.Spacing = 6; 56 | // Container child vbox1.Gtk.Box+BoxChild 57 | this.UIManager.AddUiFromString (""); 58 | this.menubar1 = ((global::Gtk.MenuBar)(this.UIManager.GetWidget ("/menubar1"))); 59 | this.menubar1.Name = "menubar1"; 60 | this.vbox1.Add (this.menubar1); 61 | global::Gtk.Box.BoxChild w2 = ((global::Gtk.Box.BoxChild)(this.vbox1 [this.menubar1])); 62 | w2.Position = 0; 63 | w2.Expand = false; 64 | w2.Fill = false; 65 | // Container child vbox1.Gtk.Box+BoxChild 66 | this.notebook1 = new global::Gtk.Notebook (); 67 | this.notebook1.CanFocus = true; 68 | this.notebook1.Name = "notebook1"; 69 | this.notebook1.CurrentPage = 0; 70 | // Container child notebook1.Gtk.Notebook+NotebookChild 71 | this.vbox2 = new global::Gtk.VBox (); 72 | this.vbox2.Name = "vbox2"; 73 | this.vbox2.Spacing = 6; 74 | // Container child vbox2.Gtk.Box+BoxChild 75 | this.hpaned1 = new global::Gtk.HPaned (); 76 | this.hpaned1.CanFocus = true; 77 | this.hpaned1.Name = "hpaned1"; 78 | this.hpaned1.Position = 474; 79 | // Container child hpaned1.Gtk.Paned+PanedChild 80 | this.GtkScrolledWindow = new global::Gtk.ScrolledWindow (); 81 | this.GtkScrolledWindow.Name = "GtkScrolledWindow"; 82 | this.GtkScrolledWindow.ShadowType = ((global::Gtk.ShadowType)(1)); 83 | // Container child GtkScrolledWindow.Gtk.Container+ContainerChild 84 | this.widget_equationHistory = new global::Gtk.TreeView (); 85 | this.widget_equationHistory.CanFocus = true; 86 | this.widget_equationHistory.Name = "widget_equationHistory"; 87 | this.widget_equationHistory.EnableSearch = false; 88 | this.GtkScrolledWindow.Add (this.widget_equationHistory); 89 | this.hpaned1.Add (this.GtkScrolledWindow); 90 | global::Gtk.Paned.PanedChild w4 = ((global::Gtk.Paned.PanedChild)(this.hpaned1 [this.GtkScrolledWindow])); 91 | w4.Resize = false; 92 | // Container child hpaned1.Gtk.Paned+PanedChild 93 | this.GtkScrolledWindow1 = new global::Gtk.ScrolledWindow (); 94 | this.GtkScrolledWindow1.Name = "GtkScrolledWindow1"; 95 | this.GtkScrolledWindow1.ShadowType = ((global::Gtk.ShadowType)(1)); 96 | // Container child GtkScrolledWindow1.Gtk.Container+ContainerChild 97 | this.widget_variableInspector = new global::Gtk.TreeView (); 98 | this.widget_variableInspector.CanFocus = true; 99 | this.widget_variableInspector.Name = "widget_variableInspector"; 100 | this.widget_variableInspector.EnableSearch = false; 101 | this.GtkScrolledWindow1.Add (this.widget_variableInspector); 102 | this.hpaned1.Add (this.GtkScrolledWindow1); 103 | this.vbox2.Add (this.hpaned1); 104 | global::Gtk.Box.BoxChild w7 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hpaned1])); 105 | w7.Position = 0; 106 | // Container child vbox2.Gtk.Box+BoxChild 107 | this.hbox1 = new global::Gtk.HBox (); 108 | this.hbox1.Name = "hbox1"; 109 | this.hbox1.Spacing = 6; 110 | // Container child hbox1.Gtk.Box+BoxChild 111 | this.button1 = new global::Gtk.Button (); 112 | this.button1.CanFocus = true; 113 | this.button1.Name = "button1"; 114 | this.button1.UseUnderline = true; 115 | this.button1.Label = global::Mono.Unix.Catalog.GetString ("Copy"); 116 | this.hbox1.Add (this.button1); 117 | global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.button1])); 118 | w8.Position = 0; 119 | w8.Expand = false; 120 | w8.Fill = false; 121 | // Container child hbox1.Gtk.Box+BoxChild 122 | this.button2 = new global::Gtk.Button (); 123 | this.button2.CanFocus = true; 124 | this.button2.Name = "button2"; 125 | this.button2.UseUnderline = true; 126 | this.button2.Label = global::Mono.Unix.Catalog.GetString ("Store"); 127 | this.hbox1.Add (this.button2); 128 | global::Gtk.Box.BoxChild w9 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.button2])); 129 | w9.Position = 1; 130 | w9.Expand = false; 131 | w9.Fill = false; 132 | // Container child hbox1.Gtk.Box+BoxChild 133 | this.widget_calculationResult = new global::Gtk.Entry (); 134 | this.widget_calculationResult.CanFocus = true; 135 | this.widget_calculationResult.Name = "widget_calculationResult"; 136 | this.widget_calculationResult.Text = global::Mono.Unix.Catalog.GetString ("0"); 137 | this.widget_calculationResult.IsEditable = false; 138 | this.widget_calculationResult.InvisibleChar = '•'; 139 | this.hbox1.Add (this.widget_calculationResult); 140 | global::Gtk.Box.BoxChild w10 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.widget_calculationResult])); 141 | w10.Position = 2; 142 | this.vbox2.Add (this.hbox1); 143 | global::Gtk.Box.BoxChild w11 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox1])); 144 | w11.Position = 1; 145 | w11.Expand = false; 146 | w11.Fill = false; 147 | // Container child vbox2.Gtk.Box+BoxChild 148 | this.widget_equationEditor = new global::Gtk.Entry (); 149 | this.widget_equationEditor.CanFocus = true; 150 | this.widget_equationEditor.Name = "widget_equationEditor"; 151 | this.widget_equationEditor.IsEditable = true; 152 | this.widget_equationEditor.InvisibleChar = '•'; 153 | this.vbox2.Add (this.widget_equationEditor); 154 | global::Gtk.Box.BoxChild w12 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.widget_equationEditor])); 155 | w12.Position = 2; 156 | w12.Expand = false; 157 | w12.Fill = false; 158 | // Container child vbox2.Gtk.Box+BoxChild 159 | this.widget_statusBar = new global::Gtk.Statusbar (); 160 | this.widget_statusBar.Name = "widget_statusBar"; 161 | this.widget_statusBar.Spacing = 6; 162 | this.vbox2.Add (this.widget_statusBar); 163 | global::Gtk.Box.BoxChild w13 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.widget_statusBar])); 164 | w13.Position = 3; 165 | w13.Expand = false; 166 | w13.Fill = false; 167 | this.notebook1.Add (this.vbox2); 168 | // Notebook tab 169 | this.label2 = new global::Gtk.Label (); 170 | this.label2.Name = "label2"; 171 | this.label2.LabelProp = global::Mono.Unix.Catalog.GetString ("Calculator"); 172 | this.notebook1.SetTabLabel (this.vbox2, this.label2); 173 | this.label2.ShowAll (); 174 | this.vbox1.Add (this.notebook1); 175 | global::Gtk.Box.BoxChild w15 = ((global::Gtk.Box.BoxChild)(this.vbox1 [this.notebook1])); 176 | w15.Position = 1; 177 | this.Add (this.vbox1); 178 | if ((this.Child != null)) { 179 | this.Child.ShowAll (); 180 | } 181 | this.DefaultWidth = 651; 182 | this.DefaultHeight = 445; 183 | this.Show (); 184 | this.DeleteEvent += new global::Gtk.DeleteEventHandler (this.OnDeleteEvent); 185 | this.QuitAction.Activated += new global::System.EventHandler (this.OnQuit); 186 | this.AboutAction.Activated += new global::System.EventHandler (this.OnAbout); 187 | this.widget_variableInspector.RowActivated += new global::Gtk.RowActivatedHandler (this.OnVariableClicked); 188 | this.button1.Clicked += new global::System.EventHandler (this.OnCopyResult); 189 | this.button2.Clicked += new global::System.EventHandler (this.OnStoreResult); 190 | this.widget_equationEditor.Activated += new global::System.EventHandler (this.OnSolve); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Kalk/gtk-gui/gui.stetic: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .. 5 | 2.12 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Action 15 | _File 16 | _File 17 | 18 | 19 | Action 20 | _Quit 21 | _Quit 22 | 23 | 24 | 25 | Action 26 | _Help 27 | _Help 28 | 29 | 30 | Action 31 | _About 32 | _About 33 | 34 | 35 | 36 | 37 | Kalk 38 | stock:stock_edit Dialog 39 | Dialog 40 | CenterOnParent 41 | 42 | 43 | 44 | 45 | 6 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 0 60 | True 61 | False 62 | False 63 | 64 | 65 | 66 | 67 | 68 | True 69 | 0 70 | 71 | 72 | 73 | 6 74 | 75 | 76 | 77 | True 78 | 474 79 | 80 | 81 | 82 | In 83 | 84 | 85 | 86 | True 87 | True 88 | False 89 | 90 | 91 | 92 | 93 | False 94 | 95 | 96 | 97 | 98 | 99 | In 100 | 101 | 102 | 103 | True 104 | True 105 | False 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 0 114 | True 115 | 116 | 117 | 118 | 119 | 120 | 6 121 | 122 | 123 | 124 | True 125 | TextOnly 126 | Copy 127 | True 128 | 129 | 130 | 131 | 0 132 | True 133 | False 134 | False 135 | 136 | 137 | 138 | 139 | 140 | True 141 | TextOnly 142 | Store 143 | True 144 | 145 | 146 | 147 | 1 148 | True 149 | False 150 | False 151 | 152 | 153 | 154 | 155 | 156 | True 157 | 0 158 | False 159 | 160 | 161 | 162 | 2 163 | True 164 | 165 | 166 | 167 | 168 | 1 169 | True 170 | False 171 | False 172 | 173 | 174 | 175 | 176 | 177 | True 178 | True 179 | 180 | 181 | 182 | 183 | 2 184 | True 185 | False 186 | False 187 | 188 | 189 | 190 | 191 | 192 | 6 193 | 194 | 195 | 3 196 | True 197 | False 198 | False 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | Calculator 207 | 208 | 209 | tab 210 | 211 | 212 | 213 | 214 | 1 215 | True 216 | 217 | 218 | 219 | 220 | 221 | --------------------------------------------------------------------------------