使用Scala解析文本并使用标记表示它

时间:2015-01-26 02:38:21

标签: scala

我试图将Golang模板语言的一小部分转换为Scala感到沮丧。

以下是lex.go源代码的关键部分:https://github.com/golang/go/blob/master/src/text/template/parse/lex.go

测试在这里:https://github.com/golang/go/blob/master/src/text/template/parse/lex_test.go

基本上这个"班级"获取一个字符串并返回一个" itemType"的数组。在模板字符串中,特殊标记的开头和结尾使用花括号{{ and }}

例如:

"{{for}}"

返回包含4个项目的数组:

item{itemLeftDelim, 0, "{{" }   // scala case class would be Item(ItemLeftDelim, 0, "")
item{itemIdentifier, 0, "for"}
item{itemRightDelim, 0, "}}"}
item{itemEOF, 0, ""}

实际通话如下:

l := lex("for", `{{for}}`, "{{", "}}")      // you pass in the start and end delimeters {{ and }}

for {
    item := l.nextItem()
    items = append(items, item)
    if item.typ == itemEOF || item.typ == itemError {
        break
    }
}
return

源代码的关键部分如下:

// itemType identifies the type of lex items.
type itemType int

const (
    itemError        itemType = iota // error occurred; value is text of error

    itemEOF

    itemLeftDelim  // left action delimiter
    // .............. skipped
)

const (
    leftDelim    = "{{"
    rightDelim   = "}}"
    leftComment  = "/*"
    rightComment = "*/"
)

// item represents a token or text string returned from the scanner.
type item struct {
    typ itemType // The type of this item.
    pos Pos      // The starting position, in bytes, of this item in the input string.
    val string   // The value of this item.
}

// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFn


// lexer holds the state of the scanner.
type lexer struct {
    name       string    // the name of the input; used only for error reports
    input      string    // the string being scanned
    leftDelim  string    // start of action
    rightDelim string    // end of action
    state      stateFn   // the next lexing function to enter
    pos        Pos       // current position in the input
    start      Pos       // start position of this item
    width      Pos       // width of last rune read from input
    lastPos    Pos       // position of most recent item returned by nextItem
    items      chan item // channel of scanned items
    parenDepth int       // nesting depth of ( ) exprs
}



// lex creates a new scanner for the input string.
func lex(name, input, left, right string) *lexer {
    if left == "" {
        left = leftDelim
    }
    if right == "" {
        right = rightDelim
    }
    l := &lexer{
        name:       name,
        input:      input,
        leftDelim:  left,
        rightDelim: right,
        items:      make(chan item),
    }
    go l.run()
    return l
}

// run runs the state machine for the lexer.
func (l *lexer) run() {
    for l.state = lexText; l.state != nil; {
        l.state = l.state(l)
    }
}

// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
    item := <-l.items
    l.lastPos = item.pos
    return item
}



// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
    l.items <- item{t, l.start, l.input[l.start:l.pos]}
    l.start = l.pos
}


// lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn {
    for {
        if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
            if l.pos > l.start {
                l.emit(itemText)
            }
            return lexLeftDelim
        }
        if l.next() == eof {
            break
        }
    }
    // Correctly reached EOF.
    if l.pos > l.start {
        l.emit(itemText)
    }
    l.emit(itemEOF)
    return nil
}

// next returns the next rune in the input.
func (l *lexer) next() rune {
    if int(l.pos) >= len(l.input) {
        l.width = 0
        return eof
    }
    r, w := utf8.DecodeRuneInString(l.input[l.pos:])
    l.width = Pos(w)
    l.pos += l.width
    return r
}


// lexLeftDelim scans the left delimiter, which is known to be present.
func lexLeftDelim(l *lexer) stateFn {
    l.pos += Pos(len(l.leftDelim))
    if strings.HasPrefix(l.input[l.pos:], leftComment) {
        return lexComment
    }
    l.emit(itemLeftDelim)
    l.parenDepth = 0
    return lexInsideAction
}

// lexRightDelim scans the right delimiter, which is known to be present.
func lexRightDelim(l *lexer) stateFn {
    l.pos += Pos(len(l.rightDelim))
    l.emit(itemRightDelim)
    return lexText
}


// there are more stateFn

所以我能够写出item和itemType:

case class Item(typ: ItemType, pos: Int, v: String)


sealed trait ItemType

case object ItemError extends ItemType
case object ItemEOF extends ItemType
case object ItemLeftDelim extends ItemType
...
..
.

stateFn和Lex的定义:

trait StateFn extends (Lexer => StateFn) {
}

我基本上真的停留在这里的主要部分。所以事情似乎就像这样:

  1. 创建一个Lex,然后&#34;去l.run()&#34;叫做。
  2. 运行是循环,循环直到EOF或找到错误 循环使用lexText进行初始化,lexText扫描直到找到{{,然后它将消息发送到包含所有前面文本类型&#39; itemText&#39;的通道,并传递一个&#39;项目&#39; ;。然后它返回函数lexLeftDelim。 lexLeftDelim做同样的事情,它会发送一条消息&#39; item&#39; itemLeftDelim。的类型。
  3. 它会一直解析字符串,直到达到EOF为止。
  4. 我无法在scala中思考,但我知道我可以在这里使用Actor向其传递消息&#39; Item&#39;。

    返回一个函数的部分,我问我在这里得到了一些好主意:How to model recursive function types?

    即使在此之后,我真的很沮丧,我似乎可以将这些概念粘合在一起。 我并不是在寻找能够为我实现整个事情的人,但是如果有人能够编写足够的代码来解析一个简单的字符串,例如&#34; {{}}&#34;那将是真棒。如果他们能解释为什么他们做了一个很棒的设计。

    我为Lex创建了一个案例类:

    case class Lex(
        name: String,
        input: String,
        leftDelim: String,
        rightDelim: String,
        state: StateFn,
        var pos: Int = 0,
        var start: Int = 0, 
        var width: Int = 0,
        var lastPos: Int = 0,
        var parenDepth: Int = 0
        ) {
    
        def next(): Option[String] = {
            if (this.pos >= this.input.length) {
                this.width = 0
                return None
            }
            this.width = 1
            val nextChar = this.input.drop(this.pos).take(1)
            this.pos += 1
            Some(nextChar)
        }
    
    }
    

    第一个stateFn是LexText,到目前为止我有:

    object LexText extends StateFn {
        def apply(l: Lexer) = {
            while {
                if (l.input.startsWith(l.leftDelim)) {
                    if (l.pos > l.start) {
                        // ????????? emit itemText using an actor?
                    }
                    return LexLeftDelim
                }
                if (l.next() == None) {
                    break
                }
            }
    
            if(l.pos > l.start) {
                // emit itemText
            }
            // emit EOF
            return None // ?? nil? how can I support an Option[StateFn]
        }
    }
    

    我需要有关获取Actor设置的指导,以及主要的运行循环:

    func (l *lexer) run() {
        for l.state = lexText; l.state != nil; {
            l.state = l.state(l)
        }
    }
    

    这是一个有趣的问题域我试图使用Scala解决,到目前为止我有点困惑希望其他人发现它有趣并且可以使用我迄今为止的一点点并提供一些代码和批评如果我是做得对不对。

    我内心深处知道我不应该改变,但我仍然在功能书的前几页:)

1 个答案:

答案 0 :(得分:3)

如果你将go代码字面翻译成Scala,你将得到非常单一的代码。通过使用parser combinators,您可能会获得更多可维护性(并且更短!)Scala版本。互联网上有很多关于它们的资源。


import scala.util.parsing.combinator._

sealed trait ItemType
case object LeftDelim extends ItemType
case object RightDelim extends ItemType
case object Identifier extends ItemType

case class Item(ty: ItemType, token: String)

object ItemParser extends RegexParsers {
  def left: Parser[Item]  = """\{\{""".r ^^ { _ => Item(LeftDelim, "{{") }
  def right: Parser[Item] = """\}\}""".r ^^ { _ => Item(RightDelim, "}}") }
  def ident: Parser[Item] = """[a-z]+""".r ^^ { x => Item(Identifier, x) }

  def item: Parser[Item] = left | right | ident

  def items: Parser[List[Item]] = rep(item)
}

// ItemParser.parse(ItemParser.items, "{{foo}}")
// res5: ItemParser.ParseResult[List[Item]] = 
// [1.8] parsed: List(Item(LeftDelim,{{), Item(Identifier,foo), Item(RightDelim,}}))

添加空格跳过或可配置的左右分隔符是微不足道的。