(* FSKalc, a simple calculator implemented as a tree-walker interpreter *) open FParsec open System (* Lexer *) let manyCharsBetween popen pclose pchar = popen >>? manyCharsTill pchar pclose let anyStringBetween popen pclose = manyCharsBetween popen pclose anyChar let pcomment: Parser = (skipString "(*" |> anyStringBetween <| skipString "*)") |>> ignore //let pwhitespace: Parser = (pcomment <|> spaces) |> many1 |>> ignore let pwhitespace = spaces let numberFormat = NumberLiteralOptions.AllowBinary ||| NumberLiteralOptions.AllowExponent ||| NumberLiteralOptions.AllowFraction ||| NumberLiteralOptions.AllowFractionWOIntegerPart ||| NumberLiteralOptions.AllowHexadecimal ||| NumberLiteralOptions.AllowOctal let pdouble = numberLiteral numberFormat "number" |>> (fun nl -> double nl.String) let pnumber: Parser = pdouble .>> pwhitespace let pidentifier: Parser = let isIdentChar c = isLetter c || c = '_' many1SatisfyL isIdentChar "identifier" .>> pwhitespace type Symbol = | Caret | Minus | Plus | Slash | Star | SlashSlash | Percent let pleftparen = skipString "(" .>> pwhitespace let prightparen = skipString ")" .>> pwhitespace let pequal = skipString "=" .>> pwhitespace let pcomma = skipString "," .>> pwhitespace let pbang = skipString "!" .>> pwhitespace let ppower = stringReturn "^" <| Caret .>> pwhitespace let pminus = stringReturn "-" <| Minus .>> pwhitespace let pplus = stringReturn "+" <| Plus .>> pwhitespace let pstar = stringReturn "*" <| Star .>> pwhitespace let pslashslash = skipString "/" >>? (stringReturn "/" <| SlashSlash .>> pwhitespace) let pslash = stringReturn "/" <| Slash .>> pwhitespace let ppercent = stringReturn "%" <| Percent .>> pwhitespace (* Parser - expressions prefixed by 'e' - statements prefixed by 's' *) // EXPRESSIONS type Expression = | Number of double | VarGet of string | Grouping of Expression | FuncCall of Expression * list | Power of Expression * list | Negate of Expression | Factor of Expression * list | Term of Expression * list let expr, exprref = createParserForwardedToRef() // literals and function call let enumber = pnumber |>> Number let evarget = pidentifier |>> VarGet // parentheses let egrouping = pleftparen >>. (expr) .>> prightparen |>> Grouping let efunccall = ((pidentifier |>> VarGet) .>>? pbang) .>>. (sepBy expr pcomma) |>> FuncCall let primary = efunccall <|> enumber <|> evarget <|> egrouping // power let epower = choice [ primary .>>.? many1 (ppower >>. primary) |>> Power primary ] // unary let enegate = choice [ pminus >>? epower |>> Negate epower ] // factor let pfactorop = pstar <|> pslashslash <|> pslash <|> ppercent let pimplicitmul: Parser = stringReturn "" <| Star let efactor = choice [ enegate .>>.? many1 ((pfactorop .>>. enegate) <|> (pimplicitmul .>>. epower)) |>> Factor enegate ] let ptermop = pplus <|> pminus let eterm = choice [ efactor .>>.? many1 (ptermop .>>. efactor) |>> Term efactor ] do exprref := eterm // STATEMENTS type Statement = | ExprStatement of Expression | VarSet of string * Expression | FuncDef of string * List * Expression let sexprstmt = expr |>> ExprStatement let sfuncdef = pidentifier .>>.? (((sepBy1 pidentifier pcomma)) .>>.? (pequal >>. expr)) |>> (fun (a, (b, c)) -> FuncDef (a, b, c)) let svarset = pidentifier .>>.? (pequal >>. expr) |>> VarSet let stmt = choice [ sfuncdef // first, so it falls through if multiple args not provided svarset // second so it falls through if no = sexprstmt ] // REPL oriented => only 1 statement at once let program = pwhitespace >>. stmt .>> eof type Value = | Number of double | Native of string | Function of List * Expression | Nil | Fail of string // Store global variables let mutable globals = new Collections.Generic.Dictionary() // natives let mutable natives = new Collections.Generic.Dictionary -> Value)>() // Natives let wrap1ArgNative name func = let native (args: List) = match args.Length with | 1 -> let arg = args[0] match arg with | Number x -> Number (func x) | Fail _ -> arg | _ -> Fail $"Invalid argument to '{name}'." | _ -> Fail $"Invalid argument count to '{name}', expect 1, got {args.Length}." globals[name] <- Native name natives[name] <- native wrap1ArgNative "sin" Math.Sin wrap1ArgNative "cos" Math.Cos wrap1ArgNative "tan" Math.Tan wrap1ArgNative "arcsin" Math.Asin wrap1ArgNative "arccos" Math.Acos wrap1ArgNative "arctan" Math.Atan wrap1ArgNative "ln" Math.Log wrap1ArgNative "log" Math.Log10 wrap1ArgNative "exp" Math.Exp wrap1ArgNative "sqrt" Math.Sqrt let wrap2ArgNative name func = let native (args: List) = match args.Length with | 2 -> let arg = args[0] let arg2 = args[1] match arg, arg2 with | Number x, Number y -> Number (func x y) | Fail _, _ -> arg | _, Fail _ -> arg2 | _ -> Fail $"Invalid argument to '{name}'." | _ -> Fail $"Invalid argument count to '{name}', expect 2, got {args.Length}." globals[name] <- Native name natives[name] <- native let logx logBase logArg = Math.Log2(logArg) / Math.Log2(logBase) let rtx rootBase rootArg = rootArg ** (1.0/rootBase) wrap2ArgNative "logx" logx wrap2ArgNative "rtx" rtx // native values globals["pi"] <- Number Math.PI globals["tau"] <- Number Math.Tau globals["e"] <- Number Math.E globals["inf"] <- Number infinity globals["nan"] <- Number nan globals["ans"] <- Number 0 // exec let varGet key = let suc, value = globals.TryGetValue(key) match suc with | true -> value | false -> $"Attempt to get the value of undefined global variable {key}." |> Fail let applyOp op left right = match op, left, right with | Plus, Number x, Number y -> Number (x + y) | Minus, Number x, Number y -> Number (x - y) | Star, Number x, Number y -> Number (x * y) | Slash, Number x, Number y -> Number (x / y) | Percent, Number x, Number y -> Number (x % y) | SlashSlash, Number x, Number y -> Number (floor (x / y)) | Caret, Number x, Number y -> Number (x ** y) | _, Fail _, _ -> left | _, _, Fail _ -> right | _ -> Fail $"Invalid operation {op} on {left} and {right}." let negate value = match value with | Number x -> Number -x | Fail _ -> value | _ -> Fail $"Attempt to negate invalid type {value}." let rec execExpr expr = match expr with | Expression.Number num -> Number num | VarGet key -> varGet key | Factor (left, rights) | Term (left, rights) -> // left associative ops let mutable res = execExpr left for (op, right) in rights do res <- execExpr right |> applyOp op res res | Negate expr -> negate <| execExpr expr | Power (left, rights) -> // right associative ops // parser only creates a power node if rights contains at least 1 element let mutable res = execExpr rights[rights.Length - 1] let mutable x = rights.Length - 2 while x >= 0 do res <- applyOp Caret (execExpr rights[x]) res x <- x - 1 applyOp Caret (execExpr left) res | Grouping expr -> execExpr expr | FuncCall (expr, args) -> let func = execExpr expr let args = args |> List.map execExpr match func with | Function (parameters, f) -> let argLen = args.Length let paramLen = parameters.Length if paramLen <> argLen then Fail $"Invalid number of arguments, expected {paramLen}, got {argLen}" else List.iter2 (fun k v -> globals[k] <- v) parameters args execExpr f | Native nat -> natives[nat] args | Fail _ -> func | _ -> Fail $"Attempt to call an invalid type {func}" let printValue value = match value with | Number x -> printfn "%A" x | _ -> printfn "%A" value let execStmt stmt = match stmt with | ExprStatement expr -> let res = execExpr expr match res with | Fail _ -> () | _ -> globals["ans"] <- res printValue res | VarSet (name, expr) -> let res = execExpr expr match res with | Fail _ -> () | _ -> globals[name] <- res printValue res | FuncDef (name, parameters, expr) -> globals[name] <- Function (parameters, expr) printfn "FSKalc" while true do let source = Console.ReadLine () match run program source with | Success(result, _, _) -> execStmt result | Failure(errorMsg, _, _) -> printfn "Parser error: %s" errorMsg