如何解析布尔表达式

时间:2018-05-08 18:07:28

标签: parsing recursion f# boolean

我对F#很陌生,我试图使用递归来解决问题。

该函数接收一个字符串,并返回一个bool。字符串被解析并进行评估。这是bool逻辑,所以

  • (T | F)返回true
  • (T&(T& T))返回true
  • ((T | T)&(T& F))返回false
  • (F)=返回false

我的想法是每次我找到a)时,用比较匹配的结果替换前一个(到那个)的字符串部分。反复这样做,直到只有T或F为止,返回true或false。

编辑: 我希望它接受字符串,并继续交换(和)与比较结果之间的内容,直到它归结为T或F.正在发生的是关于不完整的结构化构造的错误。错误发生在for循环中。

由于我对这种语言不熟悉,我不确定我做错了什么。你看到了吗?

let ComparisonSolver (comp:string) =

    let mutable trim = comp
    trim <- trim.Replace("(", "")
    trim <- trim.Replace(")", "")

    match trim with
    | "T" -> "T"
    | "F" -> "F"
    | "!T" -> "F"
    | "!F" -> "T"
    | "T&T" -> "T"
    | "F&F" -> "T"
    | "T&F" -> "F"
    | "F&T" -> "F"
    | "T|T" -> "T"
    | "F|F" -> "F"
    | "T|F" -> "T"
    | "F|T" -> "T"
    | _ -> ""

let rec BoolParser arg =
    let mutable args = arg

    if String.length arg = 1 then 
        match arg with
        | "T" -> true
        | "F" -> false
    else
        let mutable ParseStart = 0
        let endRange = String.length args

        for letter in [0 .. endRange]
            if args.[letter] = "(" then
                ParseStart <- letter
            else if args.[letter] = ")" then
                args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
                BoolParser args

let result = BoolParser "(T)&(F)"

2 个答案:

答案 0 :(得分:4)

您需要纠正一些事项。

  • for letter in [0 .. endRange]在结尾处遗漏了do - 它应该是for letter in [0 .. endRange] do
  • if循环中的for比较正在将charsstrings进行比较。您需要将"("")"替换为'('')'
  • for letter in [0 .. endRange]将超出范围:在F#中,数组构造[x..y]将从x转到y 包含。如果你有for (int i = 0; i <= array.Length; i++),那就像在C#中一样。在F#中,您还可以声明这样的循环:for i = 0 to endRange - 1 do
  • for letter in [0 .. endRange]将再次超出范围:它从0变为endrange,这是args的长度。但是args循环中的for缩短了,因此它最终会尝试从args中获取超出范围的字符。

现在,if..then..else语句存在问题,这是我认为你从一开始就看到的。

if args.[letter] = '(' then
 ParseStart <- letter
else if args.[letter] = ')' then
 args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
 BoolParser args

让我们将两个分支中的代码作为两个独立的函数。

第一个ParseStart <- letter会将letter分配给ParseStart。此函数返回unit,它是F#等效于void

第二个做:

args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
BoolParser args

此函数返回bool

现在,当您将它们放在if..then..else语句中时,您在一个分支中生成unit而在另一个分支中生成bool。在这种情况下,它不知道返回哪一个,因此它显示“表达式应该有类型”错误。

我强烈怀疑你想从外面打电话给BoolParser args  for / if循环。但它被缩进以便F#将其视为else if语句的一部分。

答案 1 :(得分:1)

解析布尔表达式的方法有很多种。查看优秀的库FParsec可能是个好主意。

http://www.quanttec.com/fparsec/

在F#中实现解析器的另一种方法是使用可以生成可读代码的活动模式

https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/active-patterns

很难通过Active Patterns提供良好的错误报告,但也许您可以从以下示例中找到一些启示:

let next s i = struct (s, i) |> Some

// Skips whitespace characters
let (|SkipWhitespace|_|) struct (s, i) =
  let rec loop j =
    if j < String.length s && s.[j] = ' ' then
      loop (j + 1)
    else
      next s j
  loop i

// Matches a specific character: ch
let (|Char|_|) ch struct (s, i) =
  if i < String.length s && s.[i] = ch then
    next s (i + 1)
  else
    None

// Matches a specific character: ch
//  and skips trailing whitespaces
let (|Token|_|) ch =
  function
  | Char ch (SkipWhitespace ps) -> Some ps
  | _                           -> None

// Parses the boolean expressions
let parse s =
  let rec term =
    function
    | Token 'T' ps                        -> Some (true, ps)
    | Token 'F' ps                        -> Some (false, ps)
    | Token '(' (Parse (v, Token ')' ps)) -> Some (v, ps)
    | _ -> None
  and opReducer p ch reducer = 
    let (|P|_|) ps = p ps
    let rec loop l =
      function
      | Token ch (P (r, ps))  -> loop (reducer l r) ps
      | Token ch _            -> None
      | ps                    -> Some (l, ps)
    function
    | P (l, ps) -> loop l ps
    | _         -> None 
  and andExpression ps  = opReducer term           '&' (&&) ps
  and orExpression ps   = opReducer andExpression  '|' (||) ps
  and parse ps          = orExpression ps
  and (|Parse|_|) ps    = parse ps
  match (struct (s, 0)) with
  | SkipWhitespace (Parse (v, _)) -> Some v
  | _                             -> None

module Tests = 
  // FsCheck allows us to get better confidence in that the parser actually works
  open FsCheck

  type Whitespace = 
    | Space

  type Ws = Ws of (Whitespace [])*(Whitespace [])

  type Expression =
    | Term  of Ws*bool
    | And   of Expression*Ws*Expression
    | Or    of Expression*Ws*Expression

    override x.ToString () =
      let orPrio              = 1
      let andPrio             = 2
      let sb                  = System.Text.StringBuilder 16
      let ch c                = sb.Append (c : char)  |> ignore
      let token (Ws (l, r)) c = 
        sb.Append (' ', l.Length) |> ignore
        sb.Append (c : char)      |> ignore
        sb.Append (' ', r.Length) |> ignore
      let enclose p1 p2 f     = 
        if p1 > p2 then ch '('; f (); ch ')'
        else f ()
      let rec loop prio =
        function
        | Term (ws, v)    -> token ws (if v then 'T' else 'F')
        | And  (l, ws, r) -> enclose prio andPrio <| fun () -> loop andPrio l; token ws '&' ;loop andPrio r
        | Or   (l, ws, r) -> enclose prio orPrio  <| fun () -> loop orPrio l ; token ws '|' ;loop orPrio r
      loop andPrio x
      sb.ToString ()

    member x.ToBool () =
      let rec loop =
        function
        | Term (_, v)     -> v
        | And  (l, _, r)  -> loop l && loop r
        | Or   (l, _, r)  -> loop l || loop r
      loop x

  type Properties() =
    static member ``Parsing expression shall succeed`` (expr : Expression) =
      let expected  = expr.ToBool ()  |> Some
      let str       = expr.ToString ()
      let actual    = str             |> parse
      expected      = actual

  let fscheck () =
    let config = { Config.Quick with MaxTest = 1000; MaxRejected = 1000 }
    Check.All<Properties> config