Scala IF-ELSEIF-ELSE用于控制流的简单解析器组合器

时间:2018-06-20 01:26:20

标签: scala parser-combinators

我正在编写一个解析器组合器来解析简单的控制流语句 并执行一些代码。语言的结构大致是这样-

async function _getParents(child) {//see https://tutorialzine.com/2017/07/javascript-async-await-explained
    // queries our main DB of nodes to get all nodes n with... 
    // n.currId = child.prevId or child.sources.includes(n.currId)
    let data = {
        "sort": {"timestamp": {"order": "desc"}},
        "query": {
            "bool": {
                "should": [
                    {"match": {"currId": child.prevId}},
                    //{"match": {"currId": child.sources}}
                ],
                "should": child.sources.map((id) => {return {"match": {"currId": id}}})
            }//TODO: use pure ES query to match against ES sources array
        }
    }

    let nodes = []
    let res = await axios.get("http://myesserver:9200/myindex/mytype/_search", {
            // for submiting to elasticsearch, see https://stackoverflow.com/a/45292081/8236733
            params: {
                source: JSON.stringify(data),
                source_content_type: 'application/json'
            }
        })

    console.log('NEW PARENT NODES')
    nodes = res['data']['hits']['hits'].map(record => {return record['_source']} )
    console.log(nodes)
    return nodes
}
async function _getChildren(parent) {// see https://tutorialzine.com/2017/07/javascript-async-await-explained
    // queries our main DB of nodes to get all nodes n with... 
    // n.prevId == parent.currId or n.sources.includes(parent.currId) 
    let data = {
        "sort": {"timestamp": {"order": "desc"}},
        "query": {
            "bool": {
                "should": [
                    {"match": {"prevId": parent.currId}},
                    {"match": {"sources": parent.currId}}
                ]
            }
        }
    }

    let nodes = []
    let res = await axios.get("http://myesserver:9200/myindex/mytype/_search", {
            // for submiting to elasticsearch, see https://stackoverflow.com/a/45292081/8236733
            params: {
                source: JSON.stringify(data),
                source_content_type: 'application/json'
            }
        })

    console.log('NEW CHILD NODES')
    nodes = res['data']['hits']['hits'].map(record => {return record['_source']} )
    console.log(nodes)
    return nodes
}
/**
 *
 *
 * @param {*} root
 * @param {(v)=>{}} stopper callback for conditional to determine whether to visit a node
 * @param {(v)=>{}} visit callback for what to do when visit each node
 * @param {(v)=>{}} getNext callback for how to get the children of a node
 */
async function _dfs(root, stopper, visit, getNext) {//see https://tutorialzine.com/2017/07/javascript-async-await-explained
    /* getting children */
    /* using DFS method */
    console.log('starting wth root node:')
    console.log(root)
    let s = []
    s.push(root)
    console.log('initialized stack (FIXME: not showing correctly for some reason):')
    console.log(s)
    while (s.length > 0) {
        let v = s.pop()
        console.log('popped:')
        console.log(v)
        console.log(v.sources)
        if (stopper(v)) {
            /* save this node for the graph */
            visit(v)                          
            /* get the children of this node */
            let kids = await getNext(v)                     
            s = s.concat(kids)
            console.log('concated stack:')
            console.log(s)
        }
    }
}
/**
 *
 *
 * @param {*} startn the node of the graph that we initially start with
 * @param {*} nodes
 * @param {*} edges
 */
async function buildGraph(startn, nodes, edges) {
    // Had to do async all the way down b/c of the async axios requests to the ES server
    //see https://tutorialzine.com/2017/07/javascript-async-await-explained

    /* getting ancestors */
    // if wanted to get siblings as well, this would be the DFS branch to implement that in
    let discovered = []
    await _dfs(startn, 
        (v)=>{return !discovered.includes(v.currId)}, 
        (v)=>{
            discovered.push(v.currId)
            nodes.push({id: v.currId, label: v.currLocation})
            if (v.prevId != 'NULL') {
                edges.push({from: v.prevId, to: v.currId})
            } 
            if (v.sources.length > 0 && v.sources[0] != 'NULL') {
                for (let i=0; i < v.sources.length; i++) {
                    edges.push({from: v.sources[i], to: v.currId})
                }
            }
        }, 
        (v)=>{return _getParents(v)})

    console.log('completed collecting ancestor nodes')
    console.log(nodes)

    /* getting children */
    // remove root from discovered, so can do another dfs
    for (var i=discovered.length-1; i>=0; i--) {
        if (discovered[i] === startn.currId) {
            discovered.splice(i, 1);
            // break;       //<-- Uncomment  if only the first term has to be removed
        }
    }
    await _dfs(startn, 
        (v)=>{return !discovered.includes(v.currId)}, 
        (v)=>{
            discovered.push(v.currId)
            // can't add origin node with same id to graph again in react-graph-vis
            if (v.currId != startn.currId) {
                nodes.push({id: v.currId, label: v.currLocation})
            }
            if (v.prevId != 'NULL') {
                edges.push({from: v.prevId, to: v.currId})
            } 
            if (v.sources.length > 0 && v.sources[0] != 'NULL') {
                for (let i=0; i < v.sources.length; i++) {
                    edges.push({from: v.sources[i], to: v.currId})
                }
            }
        }, 
        (v)=>{return _getChildren(v)})

    console.log('completed collecting child nodes')
    console.log(nodes)
}

let nodes = []
let edges = []
buildGraph(this.selectedNode, nodes, edges).then(res => {
    console.log('buildGraph promise result:')
    console.log(res, nodes, edges)
    this.setState({nodes: nodes, edges: edges})
})

例如,在上述情况下,我应该得到GOTO NODE-1-> NODE-3,而在对else表达式求值后,我得到了false,组合器的代码概述如下:

val resultId = 200
  val s = s"""(IF $resultId == 100 GOTO NODE-1-->NODE-2) (ELSE IF $resultId > 100 GOTO NODE-1-->NODE-3) (ELSE GOTO NODE-1-->NODE-4)""".stripMargin
  private val result= new ConditionalParserCombinator().run(s)

我正在打印result.get,结果为false。

**其他详细信息-Block,ExpressionBlock都是案例类,对以后我可能会做的一些事情很有用**

1 个答案:

答案 0 :(得分:1)

我认为它更干净,可以将表达式解析为您可以理解的类型(这意味着我已经为其定义了定制的Product / Case类),然后对其求值-这是两件不同的事情。事后看来,我不确定为什么我将两者混为一谈。这是可行的逻辑-

def IF = "IF"
def ELSE = "ELSE"
def ELSEIF = ELSE ~ IF
def NULL = "NULL"
def GOTO = "GOTO"
def dataType: Parser[DataType] = "[" ~ "Integer" ~ "]" ^^ { e ⇒ DataType("", "Integer") }
def node_id = wholeNumber | floatingPointNumber | stringLiteral
def NODE = "NODE" ~ "-" ~ node_id ^^ (e ⇒ ParseableNode(e._2, DataType({}, "Unit")))
def EDGE = NODE ~ "-->" ~ NODE ^^ (e ⇒ EdgeExpression(e._1._1, e._2))
def lhs = ident | wholeNumber | floatingPointNumber | stringLiteral
def rhs = ident | wholeNumber | floatingPointNumber | stringLiteral | NULL
def operator = "==" | "*" | "/" | "||" | "&&" | ">" | "<" | ">=" | "<="
def block = GOTO ~ EDGE
def expression_block(expType: ConditionalKind) = dataType ~ lhs ~ operator ~ rhs ~ block ^^ {
  case dataType ~ lhs ~ operator ~ rhs ~ block ⇒ ExpressionBlock(ParseableNode(lhs, dataType), ParseableNode(rhs, dataType), operator, block._2, expType)
}
def ifExpression = IF ~ expression_block(ConditionalKind("IF")) ^^ {
  case "IF" ~ expression_block ⇒ ExpressionBlock(expression_block.lhs, expression_block.rhs, expression_block.operator, expression_block.block, expression_block.conditionalKind)
}
def elseIFExpression = ELSEIF ~ expression_block(ConditionalKind("ELSEIF")) ^^ {
  case "ELSE" ~ "IF" ~ expression_block ⇒ ExpressionBlock(expression_block.lhs, expression_block.rhs, expression_block.operator, expression_block.block, expression_block.conditionalKind)
}
def elseExpression = ELSE ~ block ^^ { case "ELSE" ~ block ⇒ Block(block._2) }
override def grammar = log(ifExpression)("ifexpression") ~ log(elseIFExpression)("elseifexpression") ~ log(elseExpression)("elseexpression") ^^ {
  case ifExpression ~ elseIFExpression ~ elseExpression ⇒
    ConditionalExpressions(List(ifExpression, elseIFExpression), elseExpression)
}

上面的逻辑在经过这样的评估后有效-

object BasicSelectorExpressionEvaluator extends EvaluatorLike {

override def eval(parseable: Parseable) = parseable match {
  case ConditionalExpressions(ifElseIfs, otherwise) ⇒
    val mappedIfElseIfs: immutable.Seq[Block] = ifElseIfs.map { e ⇒
      println(s"e ==>$e")
      e.operator match {
        case "==" ⇒ if (e.lhs == e.rhs) {
          println("mached ==")
          Block(e.block)
        } else Block.Unit
        case "<" ⇒ if (e.lhs.value.toInt < e.rhs.value.toInt) {
          println("matched <")
          Block(e.block)
        } else Block.Unit
        case ">" ⇒ if (e.lhs.value.toInt > e.rhs.value.toInt) {
          println("matched >")
          Block(e.block)
        } else Block.Unit
        case "<=" ⇒ if (e.lhs.value.toInt <= e.rhs.value.toInt) {
          println("mached <=")
          Block(e.block)
        } else Block.Unit
        case ">=" ⇒ if (e.lhs.value.toInt >= e.rhs.value.toInt) {
          println("mached >=")
          Block(e.block)
        } else Block.Unit
      }
    }
    val filteredMappedIFElseIfs = mappedIfElseIfs.filterNot(e ⇒ e.equals(Block.Unit))
    println(s"filteredMappedIFElseIfs == $filteredMappedIFElseIfs")
    if (filteredMappedIFElseIfs.nonEmpty) PResult(filteredMappedIFElseIfs.head.block) else PResult(otherwise.block)
}

}

因此上述内容可以解析此语法-

val s = s""" IF [Integer] $resultId == 100 GOTO NODE-1-->NODE-2 ELSE IF [Integer] $resultId > 100 GOTO NODE-1-->NODE-3 ELSE GOTO NODE-1-->NODE-4""".stripMargin

可以做得更好,例如语法似乎通过在每个If上嵌入数据类型来违反DRY,但我想人们可以从中衍生出一些东西。

编辑-也要注意-这个toInt东西有点难看,需要进行更好的设计,一旦这样做,我可能会发布更新。现在所有语法都可以工作了,我需要重做所有语法-欢迎提出建议/改进,还在学习中。