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?
There are a couple of options to get rid of circular dependencies. For a longer chain, A -> B -> C -> D -> A , if one of the references is removed (for instance, the D -> A reference), the cyclic reference pattern is broken, as well. For simpler patterns, such as A -> B -> A , refactoring may be necessary.
When two or more modules rely on each other, this is referred to as a circular dependency. This is due to the fact that each module is defined in terms of the others. Python cyclic imports. Let's take these two python files as an example. # file1.
A circular or cyclic dependency is a situation where two or more independent modules or components rely on each other to function properly. This is referred to as mutual recursion. Circular dependency generally occurs in a modular framework while defining a dependency between modules or components.
A dependency cycle is a relationship between two or more domains that lead to a situation where a slave domain depends on itself, or a master domain depends on one of its slave domains. The Logical Domains Manager determines whether a dependency cycle exists before adding a dependency.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With