hakeの日記

Windows環境でプログラミングの勉強をしています。

Go言語 - PEGで構文解析 - 構文木の作成

PEGで構文木を作成するプログラムを書いてみました。
簡易化の為に、識別子(ident)と代入演算子(=)と加算演算(+)のみ解析して、ポーランド表記で出力します。

構文

root <- expression EOT /
        expression <.+> EOT /    エラー用
        <.+> EOT                 エラー用

EOT <- !.

expression <- assign / 
              additive

assign     <- ident <'='> expression

additive   <- factor ( <'+'> factor )*

factor     <- '(' expression ')' /
              ident

ident      <- <[0-9a-zA-Z]+>

goyaccでは、演算子に結合方向や優先順位を指定できましたが、PEGではできないようなので構文のなかで記述します。
代入演算子は右結合であるためassignの構文にて'='の右側にassign自身(とadditive)が来るようにして、"="の右側を先に評価するようにしています。加算演算子は左結合の為、additiveの構文においてadditiveより優先順位の高いfactorを加算演算子で接続して左から順に評価するようにしています。

構文木の作成

構文木の枝葉を構成する構造体としてNode型を定義しています。フィールド変数Kindにノードの種類、構文木の葉の場合はValueにident名を保存し、枝の場合は子となるNodeへのポインタをLeft/Rightに保存します。関数String() string はNode型構造体をPrintしたときにポーランド表記で表示させるための処理です。
各構文のアクションとして、parserがidentを検出した場合には葉となるノードを作成してAST型構造体(p.a)内部のstackに積みます。

ident      <- <[0-9a-zA-Z]+>  { n := p.a.NewValue(text)
                                p.a.Push(n)
                              } /


構文assign, additiveの場合は演算子で右側の要素が評価された時点でstackのトップと2段目に積まれているNodeを取り立し、新たに作成したNodeの子にし、その新しいNodeを改めてstackに積みます。
また演算子の文字列を読み取るためにAST構造体に演算子用stack(opstack)を用意して、演算子の評価の直後に取得した演算子文字列を積んでいます。読み取った演算子文字列はノード作成時にとりだしてNode構造体のフィールド変数Valueに保存します。

assign     <- ident <'='>       { p.a.PushOp(text)}
                    expression  { n2 := p.a.Pop()
                                  n1 := p.a.Pop()
                                  n := p.a.NewNode(NodeAssign, n1, n2)
                                  p.a.Push(n)
                                }

ast.peg

package main

type Parser Peg {
    a AST
}

root <- expression EOT  { p.a.root = p.a.Pop() } /
        expression <.+> { p.a.Err(begin) } EOT /
        <.+>            { p.a.Err(begin) } EOT

EOT <- !.

expression <- assign / 
              additive

assign     <- ident <'='>       { p.a.PushOp(text)}
                    expression  { n2 := p.a.Pop()
                                  n1 := p.a.Pop()
                                  n := p.a.NewNode(NodeAssign, n1, n2)
                                  p.a.Push(n)
                                }

additive   <- factor (
                      <'+'>     { p.a.PushOp(text)}
                      factor    { n2 := p.a.Pop()
                                  n1 := p.a.Pop()
                                  n := p.a.NewNode(NodeAdd, n1, n2)
                                  p.a.Push(n)
                                } 
                     )*

factor     <- '(' expression ')' /
              ident                {// identを先にすると上手くいかない?
                                   }

ident      <- <[0-9a-zA-Z]+>  { n := p.a.NewValue(text)
                                p.a.Push(n)
                              }

ast.go

package main

import (
    "fmt"
)

type NodeType uint8
const (
    NodeIdent    NodeType = iota + 1
    NodeAssign
    NodeAdd
)

type Node struct {
    Kind   NodeType
    Value  string
    Left   *Node
    Right  *Node
}

// 解析した式をPN表記で表示
func (self *Node) String() string {
    if self.Kind == NodeIdent {
        s := " " + self.Value
        return s
    }
    s := fmt.Sprint(" (",self.Value)
    s += self.Left.String()
    s += self.Right.String()
    s += fmt.Sprint(" )")
    return s
}

type AST struct {
    root   *Node
    stack  []*Node
    
    opstack  []string
}

func (self *AST) Push(n *Node) {
    self.stack = append(self.stack, n)
}

func (self *AST) Pop() *Node {
    var l int   = len(self.stack)
    var n *Node = self.stack[l-1]
    self.stack = self.stack[:l-1]
    return n
}

func (self *AST) PushOp(s string) {
    self.opstack = append(self.opstack, s)
}

func (self *AST) PopOp() string {
    var l int   = len(self.opstack)
    var s string = self.opstack[l-1]
    self.opstack = self.opstack[:l-1]
    return s
}


func (self *AST) NewValue(s string) *Node {
    n := new(Node)
    n.Kind = NodeIdent
    n.Value = s
    return n
}

func (self *AST) NewNode(t NodeType, l *Node, r *Node) *Node {
    n := new(Node)
    n.Kind  = t

    n.Value = self.PopOp()
    n.Left  = l
    n.Right = r
    return n
}

func (self *AST) Root() *Node {
    return self.root
}

//func (self *AST) Init() {   // Debug用にstack bottomにダミーを積む
//    n := new(Node)
//    n.Kind = NodeIdent
//    n.Value = "Stack Bottom"
//    self.Push(n)
//}

func (self *AST) Err(i int) {
    fmt.Printf("\n!!文法エラー!! (%d 文字目)\n", i)
}


func main() {
//    const s string = "a+b+c"
//    const s string = "a+(b+c)"
//    const s string = "a=b=c"
//    const s string = "a=b+c"
//    const s string = "(a=b)=c" // Error
    const s string = "a=b+(c=1+2)"

    parser := &Parser{Buffer: s}
    parser.Init()
//    parser.a.Init()
    err := parser.Parse()
    if err != nil {
        fmt.Println(err)
    } else {
        parser.Execute()
        root := parser.a.Root()
        fmt.Println("入力:", s)
        fmt.Println("出力:", root)
    }
}

実行結果

入力: a+b+c
出力:  (+ (+ a b ) c )


入力: a+(b+c)
出力:  (+ a (+ b c ) )


入力: a=b=c
出力:  (= a (= b c ) )


入力: a=b+c
出力:  (= a (+ b c ) )


!!文法エラー!! (5 文字目)
入力: (a=b)=c
出力: <nil>


入力: a=b+(c=1+2)
出力:  (= a (+ b (= c (+ 1 2 ) ) ) )