F#解析器库FParsec数学公式解析【FParsec | 02】

      上一篇我们介绍了F#解析器库FParsec基本用法,其中给出了具体的示例,来说明如何解析浮点类型的值,解析括号中的值等,如果感兴趣的可以回到这篇博客进行阅读。本次将用讲解F#解析器库FParsec对数学公式的解析,并结构化输出构成AST。

     首先,我们定义一个数学公式这个领域的自定义类型:

type Expr = 
      | CstF of float
      | Var of string
      | Add of Expr * Expr  // +
      | Sub of Expr * Expr // -
      | Mul of Expr * Expr // *
      | Div of Expr * Expr // / 
      | Pow of Expr * Expr // ^ 
      | Sin of Expr  
      | Cos of Expr 
      | Exp of Expr 
      | Ln of Expr 

     其中的Expr是定义的数学表达式类型,其中由多个元素构成,比如 CstF of float表示浮点类型的数值,Var of string则表示文本类型的变量,Add of Expr * Expr表示数学的加法操作,其中左边可以是一个Expr类型,右边也可以是一个Expr类型。此程序属于模块:

module Yd.ExpParser.Ast

     如果要使用FParsec,则需要引入此库的命名空间:

open FParsec

    然后,需要用内置的浮点类型解析器,文本类型的解析器等构建一些工具方法:

//忽略空白字符
let ws = CharParsers.spaces
//忽略特定字符并忽略末尾的空白字符
let ch c = CharParsers.skipChar c >>. ws
//解析浮点类型的值,忽略末尾的空白字符,并转换成 CstF 类型
let num = CharParsers.pfloat .>> ws  |>> (fun x -> CstF x)
//变量标识符规则
let identifier =
        let isIdentifierFirstChar c = isLetter c || c = '_'
        let isIdentifierChar c = isLetter c || isDigit c || c = '_'  
        many1Satisfy2L isIdentifierFirstChar isIdentifierChar "identifier"
//忽略空白
let identifierws =  spaces >>.  identifier  .>> spaces 
//变量解析
let id = identifierws
      |>>(fun x -> Var x)
      .>>ws

     其次,创建操作符解析器

//创建一个新的具有优先级的操作符解析器
let opp = new OperatorPrecedenceParser<_,_,_>()
//重命名表达式解析器ExpressionParser,方便调用
let expr = opp.ExpressionParser
//括号中提取表达式
let bra_expr = ch '(' >>. expr .>> ch ')'
// 定义支持的术语terms,即操作符外的类型解析器
//id表示变量解析器,num表示浮点类型解析器,bra_expr表示表达式解析器
let terms = choice[ id; num; bra_expr]
opp.TermParser <- terms

     再次,需要定义操作符解析器的优先级类型:

opp.AddOperator(InfixOperator("+", ws,1, Associativity.Left, fun x y -> Add(x, y)))
opp.AddOperator(InfixOperator("-", ws,1, Associativity.Left, fun x y -> Sub(x, y)))
opp.AddOperator(InfixOperator("*", ws,2, Associativity.Left, fun x y -> Mul(x, y)))
opp.AddOperator(InfixOperator("/", ws,2, Associativity.Left, fun x y -> Div(x, y)))
opp.AddOperator(InfixOperator("^", ws,3, Associativity.Left, fun x y -> Pow(x, y)))

opp.AddOperator(PrefixOperator("sin", ws,4, true, fun x  -> Sin(x)))
opp.AddOperator(PrefixOperator("cos", ws,4, true, fun x  -> Cos(x)))
opp.AddOperator(PrefixOperator("exp", ws,4, true, fun x  -> Exp(x)))
opp.AddOperator(PrefixOperator("ln", ws,4, true, fun x  -> Ln(x)))

opp.AddOperator(PostfixOperator("!", ws,5, true, fun x  -> Factorial(x)))

    其中的Associativity.Left代表左结合性。下面给出最终的解析函数:

//忽略空白
let expr_ws = ws >>. expr .>> ws
//调用run方法调用字符解析器
let parse s = CharParsers.run expr_ws s

   最后,给出测试示例:

let test () =
    //测试用例
    printfn "%s => %A" "1.0+2.0+a" (parse "1.0+2.0+a")
    printfn "%s => %A" "(x + 1.0) * 2.0"  (parse "(x + 1.0) * 2.0")
    printfn "%s => %A" "(x + 2) * y / 3.2"  (parse "(x + 2) * y / 3.2")
    printfn "%s => %A" "(x+2)^7"  (parse "(x+2)^7")
    printfn "%s => %A" "sin( x + 2) + 3 "  (parse "sin( x + 2) + 3 ")
    printfn "%s => %A" "sin( x + 2) + cos(x * 2)"  (parse "sin( x + 2) + cos(x * 2)")
    printfn "%s => %A" "2+exp(3*y)"  (parse "2+exp(3*y)")
    printfn "%s => %A" "2+ln(7*x)"  (parse "2+ln(7*x)")
    printfn "%s => %A" "(7*x)!+2"  (parse "(7*x)!+2")

    运行后,结果如下:

1.0+2.0+a => Success: Add (Add (CstF 1.0, CstF 2.0), Var "a")
(x + 1.0) * 2.0 => Success: Mul (Add (Var "x", CstF 1.0), CstF 2.0)
(x + 2) * y / 3.2 => Success: Div (Mul (Add (Var "x", CstF 2.0), Var "y"), CstF 3.2)
(x+2)^7 => Success: Pow (Add (Var "x", CstF 2.0), CstF 7.0)
sin( x + 2) + 3  => Success: Add (Sin (Add (Var "x", CstF 2.0)), CstF 3.0)
sin( x + 2) + cos(x * 2) => Success: Add (Sin (Add (Var "x", CstF 2.0)), Cos (Mul (Var "x", CstF 2.0)))
2+exp(3*y) => Success: Add (CstF 2.0, Exp (Mul (CstF 3.0, Var "y")))
2+ln(7*x) => Success: Add (CstF 2.0, Ln (Mul (CstF 7.0, Var "x")))
(7*x)!+2 => Success: Add (Factorial (Mul (CstF 7.0, Var "x")), CstF 2.0)

    控制台打印结果如下:

5.jpg

(完)