问题
I want to write a parser in F# and because of reasons I have to use Antlr. This means I have to define a Visitor class for every AST node I want to parse. Now I have the problem that there are some rules with cyclic dependencies like:
boolExpr : boolTerm 'or' boolTerm ;
boolTerm : boolAtom 'and' boolAtom ;
boolAtom : '(' boolExpr ')'
| ... ;
which means I need 3 visitor classes that have the same cyclic dependency and I want to have each of them in their own file
//BoolExprVisitor.fs
let boolExprVisitor = { new BaseVisitor<AST.BoolExpr>() with
override __.VisitBoolExpr(context: BoolExprContext) =
context.boolTerm() |> mapAccept boolTermVisitor |> AST.BoolExpr
}
//BoolTermVisitor.fs
let boolTermVisitor = { new BaseVisitor<AST.BoolTerm>() with
override __.VisitBoolTerm(context: BoolTermContext) =
context.boolAtom() |> mapAccept boolAtomVisitor |> AST.BoolTerm
}
//BoolAtomVisitor.fs
let boolAtomVisitor = { new BaseVisitor<AST.BoolAtom>() with
override __.VisitBoolAtom(context: BoolAtomContext) =
context.boolExpr() |> accept boolExprVisitor |> AST.BoolAtom
}
But F# doesn't like these cyclic dependencies. How can I make F# accept them or restructure my visitors to not need cyclid dependencies?
回答1:
For anyone coming across this problem in the future:
As rmunn said, the fact that I wanted the classes in different files was simply not good design. Also I did not need different AST nodes for BoolTerm, BoolAtom and BoolExpr as they could all be described as the same node BoolExpr.
My solution was to merge all of the boolean expression visitors into the same class (and merge all files for expression visitors into a single file):
//AST.fs
type BoolExpr =
| BoolConjunctionExpr of BoolOp * BoolExpr list
| ...
//ExpressionVisitors.fs
let boolExprVisitor = { new BaseVisitor<AST.BoolExpr>() with
override this.VisitBoolExpr(context: BoolExprContext) =
context.boolTerm() |> mapAccept this |> AST.BoolConjunctionExpr AST.Or
override this.VisitBoolTerm(context: BoolTermContext) =
context.boolAtom() |> mapAccept this |> AST.BoolConjunctionExpr AST.And
override this.VisitBoolAtom(context: BoolAtomContext) =
context.boolExpr() |> accept this
}
回答2:
I think if you don't want to create the visitor instances at the same time using and you have to resort to mutable variables. Create a mutable variable for one of the visitors with the value as unchecked default of its type. Then when you have the actual instance assign it to the variable.
//BoolExprVisitor.fs
module BoolExprVisitor =
let mutable boolTermVisitor = Unchecked.defaultof<BaseVisitor<AST.BoolTerm>>
let boolExprVisitor = { new BaseVisitor<AST.BoolExpr>() with
override __.VisitBoolExpr(context: BoolExprContext) =
context.boolTerm() |> mapAccept boolTermVisitor |> AST.BoolExpr
}
//BoolAtomVisitor.fs
module BoolAtomVisitor =
open BoolExprVisitor
let boolAtomVisitor = { new BaseVisitor<AST.BoolAtom>() with
override __.VisitBoolAtom(context: BoolAtomContext) =
context.boolExpr() |> accept boolExprVisitor |> AST.BoolAtom
}
//BoolTermVisitor.fs
module BoolTermVisitor
open BoolAtomVisitor
let boolTermVisitor = { new BaseVisitor<AST.BoolTerm>() with
override __.VisitBoolTerm(context: BoolTermContext) =
context.boolAtom() |> mapAccept boolAtomVisitor |> AST.BoolTerm
}
BoolExprVisitor.boolTermVisitor <- boolTermVisitor
Note that this is essentially the same thing as using createParserForwardedToRef in FParsec.
来源:https://stackoverflow.com/questions/50755391/cyclic-dependency-of-modules