F#, 70 lines
Ok, I implement a monadic parser combinator library, and then use that library to solve this problem. All told it's still just 70 lines of readable code.
I assume exponentiation associates to the right, and the other operators associate to the left. Everything works on floats (System.Doubles). I did not do any error handling for bad inputs or divide-by-zero.
// Core Parser Library
open System
let Fail() = fun i -> None
type ParseMonad() =
member p.Return x = fun i -> Some(x,i)
member p.Bind(m,f) = fun i ->
match m i with
| Some(x,i2) -> f x i2
| None -> None
let parse = ParseMonad()
let (<|>) p1 p2 = fun i ->
match p1 i with
| Some r -> Some(r)
| None -> p2 i
let Sat pred = fun i ->
match i with
| [] -> None
| c::cs -> if pred c then Some(c, cs) else None
// Auxiliary Parser Library
let Digit = Sat Char.IsDigit
let Lit (c : char) r =
parse { let! _ = Sat ((=) c)
return r }
let Opt p = p <|> parse { return [] }
let rec Many p = Opt (Many1 p)
and Many1 p = parse { let! x = p
let! xs = Many p
return x :: xs }
let Num = parse {
let! sign = Opt(Lit '-' ['-'])
let! beforeDec = Many Digit
let! rest = parse { let! dec = Lit '.' '.'
let! afterDec = Many Digit
return dec :: afterDec } |> Opt
let s = new string(List.concat([sign;beforeDec;rest])
|> List.to_array)
match(try Some(float s) with e -> None)with
| Some(r) -> return r
| None -> return! Fail() }
let Chainl1 p op =
let rec Help x = parse { let! f = op
let! y = p
return! Help (f x y) }
<|> parse { return x }
parse { let! x = p
return! Help x }
let rec Chainr1 p op =
parse { let! x = p
return! parse { let! f = op
let! y = Chainr1 p op
return f x y }
<|> parse { return x } }
// Expression grammar of this code-golf question
let AddOp = Lit '+' (fun x y -> 0. + x + y)
<|> Lit '-' (fun x y -> 0. + x - y)
let MulOp = Lit '*' (fun x y -> 0. + x * y)
<|> Lit '/' (fun x y -> 0. + x / y)
let ExpOp = Lit '^' (fun x y -> Math.Pow(x,y))
let rec Expr = Chainl1 Term AddOp
and Term = Chainl1 Factor MulOp
and Factor = Chainr1 Part ExpOp
and Part = Num <|> Paren
and Paren = parse { do! Lit '(' ()
let! e = Expr
do! Lit ')' ()
return e }
let CodeGolf (s:string) =
match Expr(Seq.to_list(s.ToCharArray())) with
| None -> "bad input"
| Some(r,_) -> r.ToString()
// Examples
printfn "%s" (CodeGolf "1.1+2.2+10^2^3") // 100000003.3
printfn "%s" (CodeGolf "10+3.14/2") // 11.57
printfn "%s" (CodeGolf "(10+3.14)/2") // 6.57
printfn "%s" (CodeGolf "-1^(-3*4/-6)") // 1
printfn "%s" (CodeGolf "-2^(2^(4-1))") // 256
printfn "%s" (CodeGolf "2*6/4^2*4/3") // 1
The parser representation type is
type P<'a> = char list -> option<'a * char list>
by the way, a common one for non-error-handling parsers.