In my question to learn F#, I've decided to get one step closer to creating a programming language, and implement this simple Reverse Polish Notation "interpreter" of sorts.
It does not allow for parentheses in input ( )
, and only accepts expressions containing the following valid tokens:
0 1 2 3 4 5 6 7 8 9 + - * /
Here's a small list of sample inputs and outputs for reference:
2 2 +
->4
10 10 + 5 *
->100
2 2 * 4 + 2 *
-> 16
I have a few concerns here:
- Is this written in a proper "functional" way?
- Is there any way to shorten
evaluate_expr
? - Are there any glaring issues that I missed?
open System
open System.Collections.Generic
open System.Text.RegularExpressions
/// <summary>
/// Evaluate an expression pair, like '2 2 +'.
/// <summary>
/// <param name="operand">The operand to use.</param>
let evaluate_expr_pair (a: string) (b: string) (operand: string) =
match operand with
| "+" -> (Int64.Parse(a) + Int64.Parse(b)).ToString()
| "-" -> (Int64.Parse(a) - Int64.Parse(b)).ToString()
| "*" -> (Int64.Parse(a) * Int64.Parse(b)).ToString()
| "/" -> (Int64.Parse(a) / Int64.Parse(b)).ToString()
| _ -> ""
/// <summary>
/// Evaluate a tokenized expression, such as '[| "2"; "2"; "+" |]',
/// and return a result, in this case, '2'.
/// </summary>
/// <param name="expr">The expression to evaluate.</param>
let evaluate_expr (expr: string[]) =
let program_stack = new Stack<string>()
for token in expr do
program_stack.Push(token)
match token with
| "+" -> let operand = program_stack.Pop()
let b = program_stack.Pop()
let a = program_stack.Pop()
program_stack.Push(evaluate_expr_pair a b operand)
| "-" -> let operand = program_stack.Pop()
let b = program_stack.Pop()
let a = program_stack.Pop()
program_stack.Push(evaluate_expr_pair a b operand)
| "*" -> let operand = program_stack.Pop()
let b = program_stack.Pop()
let a = program_stack.Pop()
program_stack.Push(evaluate_expr_pair a b operand)
| "/" -> let operand = program_stack.Pop()
let b = program_stack.Pop()
let a = program_stack.Pop()
program_stack.Push(evaluate_expr_pair a b operand)
| _ -> ()
program_stack.Pop()
/// <summary>
/// Tokenize an input expression, such as '2 2 + 5 *'.
/// </summary>
/// <param name="expr">The expression to tokenize.</param>
let tokenize_expr (expr: string) =
let split_pattern = "\s*(\+|\-|\*|\/)\s*|\s+"
let split_regex = new Regex(split_pattern)
let result = split_regex.Split(expr)
result.[0..result.Length - 2]
/// <summary>
/// Check all the tokens of an expression to make sure
/// that they are all legal.
/// </summary>
/// <param name="expr">The expression to check.</param>
let check_expr (expr: string) =
let valid_token_pattern = "[\s0-9\+\-\*\/]"
let valid_token_regex = new Regex(valid_token_pattern)
for token in expr do
if valid_token_regex.Match(token.ToString()).Success then
()
else
Console.WriteLine(String.Format("Invalid token \"{0}\".", token))
[<EntryPoint>]
let main argv =
while true do
Console.Write(": ")
let input_expr = Console.ReadLine()
input_expr |> check_expr
let tokenized_expr = input_expr |> tokenize_expr
let result = tokenized_expr |> evaluate_expr
Console.Write("\n ")
Console.Write(result)
Console.Write("\n\n")
0
match token with
block atevaluate_expr
seems very redundantly repetitive. \$\endgroup\$