Rob van der Woude's Scripting Pages
Powered by GeSHi

Source code for clcalc.cs

(view source code of clcalc.cs as plain text)

  1. using System;
  2. using System.CodeDom.Compiler;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text.RegularExpressions;
  8. using System.Threading;
  9.  
  10.  
  11. namespace RobvanderWoude
  12. {
  13. 	class ClCalc
  14. 	{
  15. 		static readonly string progver = "2.01";
  16.  
  17. 		static string expression; // calculated end result of input expression
  18. 		static string expressionstring; // corrected uncalculated input expression
  19. 		static SortedList<string, string> functions; // list of available Math functions
  20. 		static bool debugmode = false; // in debug mode, intermediate values for expression and expressionstring are shown
  21. 		static bool error = false;
  22.  
  23.  
  24. 		static int Main( string[] args )
  25. 		{
  26. #if DEBUG
  27. 			debugmode = true;
  28. #endif
  29.  
  30. 			#region Initialize
  31.  
  32. 			// force decimal dots only, instead of a mixture of dots and commas
  33. 			Thread.CurrentThread.CurrentCulture = new CultureInfo( "en-US" );
  34.  
  35. 			CreateFunctionsList( );
  36.  
  37. 			#endregion Initialize
  38.  
  39.  
  40. 			#region Parse Command Line
  41.  
  42. 			if ( args.Contains( "/?" ) )
  43. 			{
  44. 				return ShowHelp( );
  45. 			}
  46.  
  47. 			// join all arguments into a single string expression
  48. 			expression = string.Join( " ", args ).Trim( );
  49. 			if ( Regex.IsMatch( expression, "/DEBUG", RegexOptions.IgnoreCase ) )
  50. 			{
  51. 				debugmode = true;
  52. 				expression = Regex.Replace( expression, @"\s?/DEBUG\s?", " ", RegexOptions.IgnoreCase ).Trim( );
  53. 			}
  54.  
  55. 			if ( string.IsNullOrWhiteSpace( expression ) )
  56. 			{
  57. 				return InteractiveConsole( );
  58. 			}
  59.  
  60. 			expressionstring = expression;
  61.  
  62. 			DebugOutput( "Initial input" );
  63.  
  64. 			#endregion Parse Command Line
  65.  
  66.  
  67. 			EvaluateExpression( );
  68.  
  69. 			// Display result
  70. 			Console.Error.Write( "{0} = ", expressionstring );
  71. 			Console.WriteLine( "{0}", expression );
  72.  
  73. 			// Exit program
  74. 			int rc = 0;
  75. 			int.TryParse( Calculate( string.Format( "Math.Round( {0} )", expression ) ), out rc );
  76. 			return rc;
  77. 		}
  78.  
  79.  
  80. 		public static string Calculate( string exp )
  81. 		{
  82. 			// Based on code by Reed Copsey, Jr
  83. 			// https://social.msdn.microsoft.com/Forums/vstudio/en-US/2f737e51-bc99-4dc1-9a01-f0a4f7e8aa64/how-to-get-a-list-of-math-methods?forum=csharpgeneral#2af1c643-689a-4469-ad3c-20a3c8b6a24e-isAnswer
  84. 			Regex regex;
  85. 			double result;
  86.  
  87. 			#region RegEx Patterns
  88.  
  89. 			// Math function with single numeric parameter (double)
  90. 			string pattern1 = @"Math\.(\w+)\(\s*([-\d][\d\.]*)\s*\)";
  91. 			// Math function with two numeric parameters (both double)
  92. 			string pattern2 = @"Math\.(\w+)\(\s*([-\d][\d\.]*)\s*,\s*([-\d][\d\.]*)\s*\)";
  93. 			// Random function without parameters
  94. 			string patternrandom0 = @"(?:Math\.)?Random\(\s*\)";
  95. 			// Random function with 1 parameter
  96. 			string patternrandom1 = @"(?:Math\.)?Random\(\s*([-\d][\d\.]*)\s*\)";
  97. 			// Random function with 2 parameters
  98. 			string patternrandom2 = @"(?:Math\.)?Random\(\s*([-\d][\d\.]*)\s*,\s*([-\d][\d\.]*)\s*\)";
  99. 			// Math.Round with 2 numeric parameters (double, int)
  100. 			string patternround2 = @"Math\.Round\(\s*([-\d][\d\.]*)\s*,\s*(\d+)\s*\)";
  101. 			// Math.BigMul with 2 int parameters
  102. 			string patternbigmul = @"Math\.BigMul\(\s*(\d+)\s*,\s*(\d+)\s*\)";
  103.  
  104. 			#endregion RegEx Patterns
  105.  
  106. 			if ( Regex.IsMatch( exp, @"Random\(", RegexOptions.IgnoreCase ) )
  107. 			{
  108. 				#region Random
  109.  
  110. 				int seed = DateTime.Now.Millisecond;
  111. 				Random random = new Random( seed );
  112. 				if ( Regex.IsMatch( exp, patternrandom0, RegexOptions.IgnoreCase ) )
  113. 				{
  114. 					// Random function without parameters
  115. 					regex = new Regex( patternrandom0, RegexOptions.IgnoreCase );
  116. 					MatchCollection matches = regex.Matches( exp );
  117. 					foreach ( Match match in matches )
  118. 					{
  119. 						return random.NextDouble( ).ToString( new CultureInfo( "en-US" ) );
  120. 					}
  121. 				}
  122. 				else if ( Regex.IsMatch( exp, patternrandom1, RegexOptions.IgnoreCase ) )
  123. 				{
  124. 					// Random function with 1 parameter
  125. 					regex = new Regex( patternrandom1, RegexOptions.IgnoreCase );
  126. 					MatchCollection matches = regex.Matches( exp );
  127. 					foreach ( Match match in matches )
  128. 					{
  129. 						if ( int.TryParse( match.Groups[1].Value, out int upperlimit ) )
  130. 						{
  131. 							return random.Next( upperlimit ).ToString( );
  132. 						}
  133. 					}
  134. 				}
  135. 				else if ( Regex.IsMatch( exp, patternrandom2, RegexOptions.IgnoreCase ) )
  136. 				{
  137. 					// Random function with 2 parameters
  138. 					regex = new Regex( patternrandom2, RegexOptions.IgnoreCase );
  139. 					MatchCollection matches = regex.Matches( exp );
  140. 					foreach ( Match match in matches )
  141. 					{
  142. 						if ( int.TryParse( match.Groups[1].Value, out int lowerlimit ) && int.TryParse( match.Groups[2].Value, out int upperlimit ) && upperlimit > lowerlimit )
  143. 						{
  144. 							return random.Next( lowerlimit, upperlimit ).ToString( );
  145. 						}
  146. 					}
  147. 				}
  148. 			}
  149. 			else if ( Regex.IsMatch( exp, patternround2, RegexOptions.IgnoreCase ) )
  150. 			{
  151. 				// Math.Round with 2 numeric parameters (double, int)
  152. 				regex = new Regex( patternround2, RegexOptions.IgnoreCase );
  153. 				MatchCollection matches = regex.Matches( exp );
  154. 				foreach ( Match match in matches )
  155. 				{
  156. 					if ( double.TryParse( match.Groups[1].Value, out double val ) && int.TryParse( match.Groups[2].Value, out int decimals ) )
  157. 					{
  158. 						Type[] types = { typeof( double ), typeof( int ) };
  159. 						var methodInfo = typeof( Math ).GetMethod( "Round", types );
  160. 						object[] valobj = { val, decimals };
  161. 						result = (double)methodInfo.Invoke( null, valobj );
  162. 						return result.ToString( new CultureInfo( "en-US" ) );
  163. 					}
  164. 				}
  165.  
  166. 				#endregion Random
  167. 			}
  168. 			else if ( Regex.IsMatch( exp, patternbigmul, RegexOptions.IgnoreCase ) )
  169. 			{
  170. 				#region BigMul
  171.  
  172. 				// Math.BigMul with 2 int parameters
  173. 				regex = new Regex( patternbigmul, RegexOptions.IgnoreCase );
  174. 				MatchCollection matches = regex.Matches( exp );
  175. 				foreach ( Match match in matches )
  176. 				{
  177. 					if ( int.TryParse( match.Groups[1].Value, out int val1 ) && int.TryParse( match.Groups[2].Value, out int val2 ) )
  178. 					{
  179. 						Type[] types = { typeof( int ), typeof( int ) };
  180. 						var methodInfo = typeof( Math ).GetMethod( "BigMul", types );
  181. 						object[] valobj = { val1, val2 };
  182. 						result = (Int64)methodInfo.Invoke( null, valobj );
  183. 						return result.ToString( new CultureInfo( "en-US" ) );
  184. 					}
  185. 				}
  186.  
  187. 				#endregion BigMul
  188. 			}
  189. 			else if ( Regex.IsMatch( exp, pattern1, RegexOptions.IgnoreCase ) )
  190. 			{
  191. 				#region Math Single Parameter
  192.  
  193. 				// Math function with single numeric parameter (double)
  194. 				regex = new Regex( pattern1, RegexOptions.IgnoreCase );
  195. 				MatchCollection matches = regex.Matches( exp );
  196. 				foreach ( Match match in matches )
  197. 				{
  198. 					string func = match.Groups[1].Value;
  199. 					if ( double.TryParse( match.Groups[2].Value, out double val ) )
  200. 					{
  201. 						Type[] types = { typeof( double ) };
  202. 						var methodInfo = typeof( Math ).GetMethod( func, types );
  203. 						if ( methodInfo != null )
  204. 						{
  205. 							object[] valobj = { val };
  206. 							result = (double)methodInfo.Invoke( null, valobj );
  207. 							return result.ToString( new CultureInfo( "en-US" ) );
  208. 						}
  209. 						error = true;
  210. 						return "Unknown Function Math." + func;
  211. 					}
  212. 				}
  213.  
  214. 				#endregion Math Single Parameter
  215. 			}
  216. 			else if ( Regex.IsMatch( exp, pattern2, RegexOptions.IgnoreCase ) )
  217. 			{
  218. 				#region Math Two Parameters
  219.  
  220. 				// Math function with two numeric parameters (both double)
  221. 				regex = new Regex( pattern2, RegexOptions.IgnoreCase );
  222. 				MatchCollection matches = regex.Matches( exp );
  223. 				foreach ( Match match in matches )
  224. 				{
  225. 					string func = match.Groups[1].Value;
  226. 					if ( double.TryParse( match.Groups[2].Value, out double val1 ) && double.TryParse( match.Groups[3].Value, out double val2 ) )
  227. 					{
  228. 						Type[] types = { typeof( double ), typeof( double ) };
  229. 						var methodInfo = typeof( Math ).GetMethod( func, types );
  230. 						if ( methodInfo != null )
  231. 						{
  232. 							object[] valobj = { val1, val2 };
  233. 							result = (double)methodInfo.Invoke( null, valobj );
  234. 							return result.ToString( new CultureInfo( "en-US" ) );
  235. 						}
  236. 						error = true;
  237. 						return "Unknown Function Math." + func;
  238. 					}
  239. 				}
  240.  
  241. 				#endregion Math Two Parameters
  242. 			}
  243. 			else
  244. 			{
  245. 				#region Numerical Expressions
  246.  
  247. 				// use JScript to evaluate numerical expressions with standard operators
  248. 				try
  249. 				{
  250. 					result = JScriptEvaluator.EvalToDouble( exp );
  251. 					return result.ToString( new CultureInfo( "en-US" ) );
  252. 				}
  253. 				catch ( Exception )
  254. 				{
  255. 					// no action
  256. 				}
  257.  
  258. 				#endregion Numerical Expressions
  259. 			}
  260. 			return string.Empty;
  261. 		}
  262.  
  263.  
  264. 		public static void CreateFunctionsList( )
  265. 		{
  266. 			var methods = typeof( Math ).GetMethods( BindingFlags.Public | BindingFlags.Static ).Select( m => m.Name ).ToList( );
  267. 			methods = methods.Distinct( ).ToList( );
  268. 			methods.Remove( "DivRem" ); // this function cannot be implemented easily on the command line
  269. 			functions = new SortedList<string, string>( );
  270. 			foreach ( string method in methods )
  271. 			{
  272. 				functions.Add( method, method );
  273. 			}
  274. 			// for JScript Math compatibility
  275. 			functions.Add( "Ceil", "Ceiling" );
  276. 			functions.Add( "Int", "Truncate" );
  277. 		}
  278.  
  279.  
  280. 		public static void DebugOutput( string description )
  281. 		{
  282. 			if ( debugmode )
  283. 			{
  284. 				Console.Error.Write( "{0}:\t{1} = ", description, expressionstring );
  285. 				Console.WriteLine( "{0}", expression );
  286. 			}
  287. 		}
  288.  
  289.  
  290. 		public static void EnvVarSubst( )
  291. 		{
  292. 			string pattern = @"(?<!%)%([A-Z]\w*)%(?!%)"; // (?<!%) = not preceded by % and (?!%) = not followed by %
  293. 			Regex regex = new Regex( pattern, RegexOptions.IgnoreCase );
  294. 			if ( regex.IsMatch( expression ) )
  295. 			{
  296. 				MatchCollection matches = regex.Matches( expression );
  297. 				foreach ( Match match in matches )
  298. 				{
  299. 					expression = expression.Replace( match.Value, Environment.GetEnvironmentVariable( match.Groups[1].Value ) );
  300. 				}
  301. 				DebugOutput( "Env. variables" );
  302. 			}
  303. 		}
  304.  
  305.  
  306. 		public static void EvaluateExpression( )
  307. 		{
  308. 			if ( !error ) // in case of errors, do not proceed
  309. 			{
  310. 				#region Evaluate pi and e
  311.  
  312. 				string pattern = @"(^|[-\s\*/%+\(\)])(e|pi)([-\s\*/%+\(\)]|$)";
  313. 				Regex regex = new Regex( pattern );
  314. 				MatchCollection matches = regex.Matches( expression );
  315. 				foreach ( Match match in matches )
  316. 				{
  317. 					expression = expression.Replace( match.Value, string.Format( "{0}{1}{2}", match.Groups[1].Value, Calculate( "Math." + match.Groups[2].Value.ToUpper( ) ), match.Groups[3].Value ) );
  318. 					expressionstring = expressionstring.Replace( match.Value, string.Format( "{0}Math.{1}{2}", match.Groups[1].Value, match.Groups[2].Value.ToUpper( ), match.Groups[3].Value ) );
  319.  
  320. 					DebugOutput( "pi and e" );
  321. 				}
  322.  
  323. 				#endregion Evaluate pi and e
  324.  
  325.  
  326. 				#region Evaluate Math.PI and Math.E
  327.  
  328. 				pattern = @"Math\.(E|PI)";
  329. 				regex = new Regex( pattern, RegexOptions.IgnoreCase );
  330. 				matches = regex.Matches( expression );
  331. 				foreach ( Match match in matches )
  332. 				{
  333. 					expression = expression.Replace( match.Value, Calculate( string.Format( "Math.{0}", match.Groups[1].Value.ToUpper( ) ) ) );
  334. 					expressionstring = expressionstring.Replace( match.Value, string.Format( "Math.{0}", match.Groups[1].Value.ToUpper( ) ) );
  335.  
  336. 					DebugOutput( "Math.PI and E" );
  337. 				}
  338.  
  339. 				#endregion Evaluate Math.PI and Math.E
  340.  
  341.  
  342. 				#region Evaluate Calls to Random
  343.  
  344. 				expression = Regex.Replace( expression, @"Math\.Random", "Random", RegexOptions.IgnoreCase );
  345. 				expressionstring = Regex.Replace( expressionstring, @"Math\.Random", "Random", RegexOptions.IgnoreCase );
  346.  
  347. 				pattern = @"Math\.Random\(([^\(\)]+)\)";
  348. 				regex = new Regex( pattern, RegexOptions.IgnoreCase );
  349. 				if ( regex.IsMatch( expression ) )
  350. 				{
  351. 					matches = regex.Matches( expression );
  352. 					foreach ( Match match in matches )
  353. 					{
  354. 						string replacement = string.Format( "Random({0})", match.Groups[1].Value );
  355. 						expression = regex.Replace( expression, Calculate( replacement ) );
  356. 						expressionstring = regex.Replace( expressionstring, replacement );
  357.  
  358. 						DebugOutput( "Random calls" );
  359. 					}
  360. 				}
  361.  
  362. 				#endregion Evaluate Calls to Random
  363.  
  364.  
  365. 				#region Math Function Names Capitalization
  366.  
  367. 				// Correct capitalization of Math functions and translate JScript specific function names to .NET equivalents
  368. 				foreach ( string func in functions.Keys )
  369. 				{
  370. 					pattern = string.Format( @"(Math\.)?{0}\(", func );
  371. 					regex = new Regex( pattern, RegexOptions.IgnoreCase );
  372. 					if ( regex.IsMatch( expression ) )
  373. 					{
  374. 						matches = regex.Matches( expression );
  375. 						foreach ( Match match in matches )
  376. 						{
  377. 							expression = regex.Replace( expression, string.Format( "Math.{0}(", functions[func] ) );
  378. 							expressionstring = regex.Replace( expressionstring, string.Format( "Math.{0}(", functions[func] ) );
  379.  
  380. 							DebugOutput( "Capitalization" );
  381. 						}
  382. 					}
  383. 				}
  384.  
  385. 				#endregion Math Function Names Capitalization
  386.  
  387.  
  388. 				#region Evaluate Expressions Loop
  389.  
  390. 				bool completed;
  391. 				do
  392. 				{
  393. 					completed = true;
  394.  
  395. 					expression = expression.Trim( );
  396.  
  397. 					#region Numeric Only
  398.  
  399. 					// Evaluate all expressions between parentheses, numeric only
  400. 					pattern = @"\(([-\s\d\.\*<>|&/%+]*)\)";
  401. 					regex = new Regex( pattern, RegexOptions.IgnoreCase );
  402. 					if ( regex.IsMatch( expression ) )
  403. 					{
  404. 						matches = regex.Matches( expression );
  405. 						completed = Regex.IsMatch( matches[0].Groups[1].Value, @"^\s*[-\d][\d\.]*\s*$" );
  406. 						matches = regex.Matches( expression );
  407. 						foreach ( Match match in matches )
  408. 						{
  409. 							expression = expression.Replace( match.Value, string.Format( "( {0} )", Calculate( match.Groups[1].Value ) ) );
  410.  
  411. 							DebugOutput( "Numeric only" );
  412. 						}
  413. 					}
  414.  
  415. 					#endregion Numeric Only
  416.  
  417. 					#region Random Calls
  418.  
  419. 					// Evaluate all Random calls with numeric parameters only
  420. 					string patternrandom0 = @"Random\((\s*)\)";
  421. 					regex = new Regex( patternrandom0, RegexOptions.IgnoreCase );
  422. 					int seed = DateTime.Now.Millisecond;
  423. 					Random random = new Random( seed );
  424. 					if ( regex.IsMatch( expression ) )
  425. 					{
  426. 						completed = false;
  427. 						matches = regex.Matches( expression );
  428. 						foreach ( Match match in matches )
  429. 						{
  430. 							expression = expression.Replace( match.Value, random.NextDouble( ).ToString( new CultureInfo( "en-US" ) ) );
  431.  
  432. 							DebugOutput( "Random 0 params" );
  433. 						}
  434. 					}
  435. 					string patternrandom1 = @"Random\(\s*(\d+)\s*\)";
  436. 					regex = new Regex( patternrandom1, RegexOptions.IgnoreCase );
  437. 					if ( regex.IsMatch( expression ) )
  438. 					{
  439. 						completed = false;
  440. 						matches = regex.Matches( expression );
  441. 						foreach ( Match match in matches )
  442. 						{
  443. 							if ( int.TryParse( match.Groups[1].Value, out int singleparam ) )
  444. 							{
  445. 								expression = expression.Replace( match.Value, Calculate( string.Format( "Random( {0} )", singleparam ) ) );
  446. 							}
  447.  
  448. 							DebugOutput( "Random 1 param" );
  449. 						}
  450. 					}
  451. 					string patternrandom2 = @"Random\(\s*(\d+)\s*,\s*(\d+)\s*\)";
  452. 					regex = new Regex( patternrandom2, RegexOptions.IgnoreCase );
  453. 					if ( regex.IsMatch( expression ) )
  454. 					{
  455. 						completed = false;
  456. 						matches = regex.Matches( expression );
  457. 						foreach ( Match match in matches )
  458. 						{
  459. 							if ( int.TryParse( match.Groups[1].Value, out int lowerlimit ) && int.TryParse( match.Groups[2].Value, out int upperlimit ) )
  460. 							{
  461. 								expression = expression.Replace( match.Value, Calculate( string.Format( "Random( {0}, {1} )", lowerlimit, upperlimit ) ) );
  462. 							}
  463.  
  464. 							DebugOutput( "Random 2 params" );
  465. 						}
  466. 					}
  467.  
  468. 					#endregion Random Calls
  469.  
  470. 					#region Math Calls With Numeric Parameters
  471.  
  472. 					// Evaluate all Math functions with numeric parameters only
  473. 					pattern = @"Math\.(\w+)\(([^\(\)]+)\)";
  474. 					regex = new Regex( pattern, RegexOptions.IgnoreCase );
  475. 					if ( regex.IsMatch( expression ) )
  476. 					{
  477. 						completed = false;
  478. 						matches = regex.Matches( expression );
  479. 						foreach ( Match match in matches )
  480. 						{
  481. 							expression = expression.Replace( match.Value, Calculate( match.Value ) );
  482.  
  483. 							DebugOutput( "Math numeric" );
  484. 						}
  485. 					}
  486.  
  487. 					#endregion Math Calls With Numeric Parameters
  488.  
  489. 				} while ( !completed && !error ); // loop must be executed at least once without ANY match for both RegExs
  490.  
  491. 				#endregion Evaluate Expressions Loop
  492.  
  493.  
  494. 				#region Final Evaluation
  495.  
  496. 				if ( !error )
  497. 				{
  498. 					expression = Calculate( expression );
  499.  
  500. 					DebugOutput( "Final run" );
  501. 				}
  502.  
  503. 				#endregion Final Evaluation
  504. 			}
  505. 		}
  506.  
  507.  
  508. 		public static int InteractiveConsole( )
  509. 		{
  510. 			expression = string.Empty;
  511. 			bool debugprompt = true;
  512. 			bool quit = false;
  513. 			while ( !quit )
  514. 			{
  515. 				#region Prompt
  516.  
  517. 				if ( debugprompt )
  518. 				{
  519. 					Console.ForegroundColor = ConsoleColor.DarkYellow;
  520. 					Console.WriteLine( "Debug mode is {0}", debugmode ? "ON" : "OFF" );
  521. 					Console.WriteLine( "Type DEBUG and press Enter to toggle debug mode." );
  522. 					debugprompt = false;
  523. 				}
  524. 				Console.ForegroundColor = ConsoleColor.Green;
  525. 				Console.WriteLine( "\nType an expression to calculate, or ? for help, or Q to quit, and press Enter:\n" );
  526. 				Console.Write( "CLCALC:> " );
  527. 				Console.ResetColor( );
  528.  
  529. 				#endregion Prompt
  530.  
  531. 				// Read keyboard input
  532. 				expression = Console.ReadLine( );
  533.  
  534. 				// "Q" for Quit
  535. 				if ( expression.ToUpper( ).Trim( ) == "Q" )
  536. 				{
  537. 					quit = true;
  538. 				}
  539. 				else
  540. 				{
  541. 					Console.WriteLine( );
  542.  
  543. 					// "?" or "HELP" for help on screen
  544. 					if ( expression.ToUpper( ).Trim( ) == "HELP" || expression.Trim( ) == "?" )
  545. 					{
  546. 						ShowHelpInteractive( );
  547. 					}
  548. 					else if ( !error )
  549. 					{
  550. 						#region Toggle DEBUG Mode
  551.  
  552. 						// "DEBUG" to toggle debug mode
  553. 						if ( expression.ToUpper( ).Contains( "DEBUG" ) )
  554. 						{
  555. 							debugmode = !debugmode; // toggle debug mode
  556. 							expression = string.Empty;
  557. 							expressionstring = string.Empty;
  558. 							Console.ForegroundColor = ConsoleColor.DarkYellow;
  559. 							Console.WriteLine( "Debug mode is {0}", debugmode ? "ON" : "OFF"  );
  560. 							Console.WriteLine( "Type DEBUG and press Enter to toggle debug mode." );
  561. 							Console.ResetColor( );
  562. 						}
  563.  
  564. 						#endregion Toggle DEBUG Mode
  565.  
  566. 						if ( !string.IsNullOrWhiteSpace( expression ) )
  567. 						{
  568. 							expressionstring = expression;
  569.  
  570. 							// in interactive mode, we don't have the "automatic" evaluation of environment
  571. 							// variables like we have on the command line, so we need to handle that ourselves
  572. 							EnvVarSubst( );
  573.  
  574. 							EvaluateExpression( );
  575. 						}
  576. 					}
  577. 					error = false;
  578. 					if ( !string.IsNullOrWhiteSpace( expressionstring ) && !string.IsNullOrWhiteSpace( expression ) )
  579. 					{
  580. 						Console.WriteLine( "\n{0} = {1}\n", expressionstring, expression );
  581. 					}
  582. 				}
  583. 			}
  584. 			return 0;
  585. 		}
  586.  
  587.  
  588. 		public static void ListFunctions( )
  589. 		{
  590. 			List<string> funcs = new List<string>( );
  591. 			foreach ( string function in functions.Keys )
  592. 			{
  593. 				if ( function == functions[function] )
  594. 				{
  595. 					funcs.Add( string.Format( "Math.{0}", function ) );
  596. 				}
  597. 				else
  598. 				{
  599. 					funcs.Add( string.Format( "Math.{0} *", function ) );
  600. 				}
  601. 			}
  602. 			funcs.Add( "Random *" );
  603. 			funcs.Sort( );
  604.  
  605. 			for ( int i = 0; i < funcs.Count; i++ )
  606. 			{
  607. 				if ( i % 4 == 3 )
  608. 				{
  609. 					Console.Error.WriteLine( funcs[i] );
  610. 				}
  611. 				else
  612. 				{
  613. 					Console.Error.Write( "{0,-20}", funcs[i] );
  614. 				}
  615. 			}
  616. 		}
  617.  
  618.  
  619. 		public static string RequiredDotNETVersion( )
  620. 		{
  621. 			// Get the required .NET Framework version
  622. 			// By Fernando Gonzalez Sanchez on StackOverflow.com
  623. 			// https://stackoverflow.com/a/18623516
  624. 			object[] list = Assembly.GetExecutingAssembly( ).GetCustomAttributes( true );
  625. 			var attribute = list.OfType<System.Runtime.Versioning.TargetFrameworkAttribute>( ).First( ); // requires Linq
  626. 			return attribute.FrameworkDisplayName;
  627. 		}
  628.  
  629.  
  630. 		public static int ShowHelp( params string[] errorMessage )
  631. 		{
  632. 			#region Error Message
  633.  
  634. 			if ( errorMessage.Length > 0 )
  635. 			{
  636. 				List<string> errargs = new List<string>( errorMessage );
  637. 				errargs.RemoveAt( 0 );
  638. 				Console.Error.WriteLine( );
  639. 				Console.ForegroundColor = ConsoleColor.Red;
  640. 				Console.Error.Write( "ERROR:\t" );
  641. 				Console.ForegroundColor = ConsoleColor.White;
  642. 				Console.Error.WriteLine( errorMessage[0], errargs.ToArray( ) );
  643. 				Console.ResetColor( );
  644. 			}
  645.  
  646. 			#endregion Error Message
  647.  
  648.  
  649. 			#region Help Text
  650.  
  651. 			Console.Error.Write( string.Format( @"
  652. ClCalc.exe,  Version {0}
  653. Command Line Calculator
  654.  
  655. Usage:     CLCALC  [ expression ]  [ /DEBUG ]
  656.  
  657. Run the program without expression to open an interactive console.
  658.  
  659. Available Operators:
  660. ====================
  661. + - * / % << >> & |
  662.  
  663. Available Constants:
  664. ====================
  665. Math.PI (pi) and Math.E (e)
  666.  
  667. Available Functions:
  668. ====================
  669. ", progver ) );
  670.  
  671. 			ListFunctions( );
  672.  
  673. 			Console.Error.Write( string.Format( @"
  674. * Math.Ceil is equivalent of Math.Ceiling, Math.Int of Math.Truncate;
  675.   these equivalents were created for backwards compatibility with ClCalc 1.*
  676.   Random is an added function, not part of the .NET System.Math class but of
  677.   the .NET System.Random class.
  678.   Function names are not case sensitive, but constants e and pi are.
  679.   The Math. prefix is not mandatory in the input, except for upper case
  680.   Math.E and Math.PI.
  681.  
  682.  
  683.  
  684. For more details on functions and parameters, navigate to:
  685. https://learn.microsoft.com/en-us/dotnet/api/system.math?view=netframework-4.5
  686.  
  687. EXAMPLES
  688. ========
  689.  
  690. Expression:                           Evaluates To:         Remark:
  691. ===========                           =============         =======
  692. {0}%var1% & %var2%{0}                      0 or 1               AND operator
  693. {0}%var1% | %var2%{0}                      0 or 1               OR operator
  694. {0}16 >> 1{0}                              8                    shift right
  695. {0}16 << 2{0}                             64                    shift left
  696. {0}16 % 3{0}                               1                    use whitespace!
  697. Math.Abs( -12 )                       12
  698. Math.Acos( 0.707106781186548 )         0.785398163397448
  699. Math.Asin( 0.707106781186548 )         0.785398163397448
  700. Math.Atan( 1 )                         0.785398163397448
  701. Math.Atan2( 1, 1 )                     0.785398163397448
  702. Math.BigMul( 2147483647, 2147483647 )  4.61168601413242E+18 Int64 result
  703. Math.Ceiling( Math.PI )                4                    round up to int
  704. Math.Cos( pi / 4 )                     0.707106781186548
  705. Math.Cosh( 1 )                         1.54308063481524
  706. Math.Exp( -1 ) * Math.E                1
  707. Math.Floor( Math.E )                   2                    round down to int
  708. Math.IEEERemainder( 4, 3 )             1                    like   {0}4 % 3{0}
  709. Math.IEEERemainder( 5, 3 )            -1                    unlike {0}5 % 3{0}
  710. Math.Log( Math.E )                     1                    natural logarithm
  711. Math.Log10( 1000 )                     3                    base 10 logarithm
  712. Math.Max( -10, -12 )                 -10
  713. Math.Min( -10, -12 )                 -12
  714. Math.Pow( 2, 8 )                     256
  715. Math.Round( 12.5 )                    13
  716. Math.Sign( -3 )                       -1
  717. Math.Sin( pi / 4 )                     0.707106781186548
  718. Math.Sinh( 1.5 )                       2.12927945509482
  719. Math.Sqrt( 9 )                         3
  720. Math.Tan( pi / 4 )                     1
  721. Math.Tanh( 2 )                         0.964027580075817
  722. Math.Truncate( -12.6 )               -12
  723. Random( )                              0 <= result < 1      floating point
  724. Random( 100 )                          0 <= result < 100    integer
  725. Random( 1, 101 )                       1 <= result < 101    integer
  726. pi / Math.Acos( Math.Pow(0.5, 0.5) )   4                    nested Math calls
  727.  
  728. Notes:
  729. ======
  730. In non-interactive mode, the result is shown on screen and the exit code
  731. ({0}errorlevel{0}) equals the rounded value of result, or 0 in case of
  732. error or overflow.
  733. In non-interactive mode, the result is shown in Standard Output stream,
  734. and the initial expression in Standard Error stream, to allow capturing
  735. or redirection of the result only.
  736. In interactive console mode type DEBUG to toggle debug mode.
  737. If expression contains special characters like < or > or | or & it is
  738. highly recommended to enclose the entire expression in doublequotes and
  739. use whitespace around these operators, e.g. {0}16 << 1{0}.
  740. Available constants are case sensitive, function names are not.
  741. {0}Math.PI{0} may be abbreviated to {0}pi{0} (lower case), {0}Math.E{0} to {0}e{0}.
  742. In ClCalc all Math functions are limited to 2 parameters at most, usually of
  743. types Double or Int32.
  744. This program requires {1}.
  745. Culture is set to en-US, so use and expect decimal dots, not commas.
  746. See this web page for methods available in .NET's Math class:
  747. https://learn.microsoft.com/en-us/dotnet/api/system.math?view=netframework-4.5
  748.  
  749. Credits:
  750. ========
  751. Originally based on Eval function (using JScript) by {0}markman{0}:
  752. https://www.codeproject.com/Articles/11939/Evaluate-C-Code-Eval-Function
  753.  
  754. Written by Rob van der Woude
  755. https://www.robvanderwoude.com
  756. ", (char)34, RequiredDotNETVersion( ) ) );
  757.  
  758. 			#endregion Help Text
  759.  
  760.  
  761. 			return 0;
  762. 		}
  763.  
  764.  
  765. 		public static void ShowHelpInteractive( )
  766. 		{
  767. 			Console.WriteLine( "\nAvailable Operators:" );
  768. 			Console.WriteLine( "====================" );
  769. 			Console.WriteLine( "+ - * / % << >> & |\n" );
  770. 			Console.WriteLine( "Available Constants:" );
  771. 			Console.WriteLine( "====================" );
  772. 			Console.WriteLine( "Math.PI (pi) and Math.E (e)\n" );
  773. 			Console.WriteLine( "Available Functions:" );
  774. 			Console.WriteLine( "====================" );
  775.  
  776. 			ListFunctions( );
  777.  
  778. 			Console.WriteLine( "\nFunction names are not case sensitive, but constants e and pi are." );
  779. 			Console.WriteLine( "The Math. prefix is not mandatory in the input, except for upper case" );
  780. 			Console.WriteLine( "Math.E an Math.PI." );
  781. 			Console.WriteLine( "\nFor more details, run CLCALC with the /? command line switch." );
  782. 		}
  783. 	}
  784.  
  785.  
  786. 	public class JScriptEvaluator
  787. 	{
  788. 		// Eval function using JScript, by "markman"
  789. 		// https://www.codeproject.com/Articles/11939/Evaluate-C-Code-Eval-Function
  790.  
  791.  
  792. 		public static int EvalToInteger( string statement )
  793. 		{
  794. 			string s = EvalToString( statement );
  795. 			return int.Parse( s.ToString( ) );
  796. 		}
  797.  
  798.  
  799. 		public static double EvalToDouble( string statement )
  800. 		{
  801. 			string s = EvalToString( statement );
  802. 			return double.Parse( s );
  803. 		}
  804.  
  805.  
  806. 		public static string EvalToString( string statement )
  807. 		{
  808. 			object o = EvalToObject( statement );
  809. 			return o.ToString( );
  810. 		}
  811.  
  812.  
  813. 		public static object EvalToObject( string statement )
  814. 		{
  815. 			return _evaluatorType.InvokeMember(
  816. 							  "Eval",
  817. 							  BindingFlags.InvokeMethod,
  818. 							  null,
  819. 							  _evaluator,
  820. 							  new object[] { statement }
  821. 						);
  822. 		}
  823.  
  824.  
  825. 		static JScriptEvaluator( )
  826. 		{
  827. 			CodeDomProvider provider = new Microsoft.JScript.JScriptCodeProvider( );
  828.  
  829. 			CompilerParameters parameters;
  830. 			parameters = new CompilerParameters
  831. 			{
  832. 				GenerateInMemory = true
  833. 			};
  834.  
  835. 			CompilerResults results;
  836. 			results = provider.CompileAssemblyFromSource( parameters, _jscriptSource );
  837.  
  838. 			Assembly assembly = results.CompiledAssembly;
  839. 			_evaluatorType = assembly.GetType( "Evaluator.Evaluator" );
  840.  
  841. 			_evaluator = Activator.CreateInstance( _evaluatorType );
  842. 		}
  843.  
  844. 		private static object _evaluator = null;
  845. 		private static Type _evaluatorType = null;
  846. 		private static readonly string _jscriptSource =
  847. 			  @"package Evaluator
  848.                   {
  849.                      class Evaluator
  850.                      {
  851.                            public function Eval(expr : String) : String
  852.                            {
  853.                               return eval(expr);
  854.                            }
  855.                      }
  856.                   }";
  857. 	}
  858. }

page last modified: 2024-04-16; loaded in 0.0452 seconds