上一篇我们介绍了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)
控制台打印结果如下: