Cyclic dependency of modules

点点圈 提交于 2021-02-20 06:45:48

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!