hakeの日記

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

Go言語 - 式の構文解析 - 演算子順位法

再起下降法と比較してのメリットは、演算子の優先度に応じてparse関数を作成する必要がなく、演算子の優先順位・結合方向をデータとして与えるだけで良いということ。

ident1 op1 ident2 op2 ident3 op3 ……

上記のような並びの式の場合、演算子op1とop2の優先順位と結合方向を比較して、ident2がident1とペアのノードとなるか、ident3以降とペアのノードとなるかを判断する。

構文

  expr    = primary { op primary }      # { }は0回以上の繰り返し
  primary = ident | '(' expr ')'
  op      = '=' | '+' | '-' | '*' | '/' | '^'
  ident   = 'A' … 'Z' 'a' … 'z'

primaryの中のカッコの処理は、通常の再起下降法で処理。並びの演算子の比較とノードの作成は、makeNode()関数で実施している。

ソース

package main

import (
	"fmt"
	"os"
)

type Lexer struct{
	buf   string
	size  int
	pos   int
}
func NewLexer(s string) *Lexer {
	l := new(Lexer)
	l.buf = s
	l.size = len(l.buf)
	l.pos = 0
	return l
}
func (self *Lexer) Get() (byte, int) {
	if self.pos >= self.size {
		return 0, self.pos
	} else {
		idx  := self.pos
		char := self.buf[idx]
		self.pos += 1
		return char, idx
	}
}
func (self *Lexer) Peek(offset int) (byte, int) {
	if self.pos + offset >= self.size {
		return 0, self.pos + offset
	} else {
		c := self.buf[self.pos + offset]
		return c, self.pos + offset
	}
}


type NodeType uint32
const (
	NodeIdent    NodeType = iota + 128 // 識別子
	NodeOp                             // 演算子
)


type Node struct {
	Type    NodeType
	Value   byte
	Left    *Node
	Right   *Node
}
func NewNode(t NodeType, char byte) *Node {
	n := new(Node)
	n.Type = t
	n.Value = char
	return n
}


// 引数のNodeを演算子の前置記法で表示
func (self *Node)String() string {
	switch self.Type {
	case NodeIdent:  return string(self.Value)
	case NodeOp:
		s := "(" + string(self.Value)+ " "
		s += self.Left.String() + " "
		s += self.Right.String() + ") "
		return s
	default: return "表示できない要素があります。"
	}
}

// 演算子情報
type OpInfo struct {
	Rank int    // 順位
	Dir  bool   // 結合方向 左結合ならば true
}

//
//  構文
//
//    expr    = primary { op primary }
//
//    primary = ident | '(' expr ')'
//
//    op      = '=' | '+' | '-' | '*' | '/' | '^'
//
//    ident   = 'A' … 'Z' 'a' … 'z'
//
//
type Parser struct{
	lx   *Lexer
	tbl  map[byte] OpInfo
}
func NewParser(s string) *Parser {
	p := new(Parser)
	p.lx = NewLexer(s)
	p.tbl = map[byte]OpInfo{
			'=': {0, false}, // 順位低 右結合
			'+': {1, true }, //        左結合
			'-': {1, true },
			'*': {2, true },
			'/': {2, true },
			'^': {3, false}, // 順位高
			}
	return p
}
func (self *Parser) Parse() *Node {
	leftNode := self.primary()
	c, _ := self.lx.Peek(0)
	b, _ := self.isOp(c)
	for (c != 0) && b {
		leftNode = self.makeNode(leftNode)
		c, _ = self.lx.Peek(0)
		b, _ = self.isOp(c)
	}
	return leftNode
}
func (self *Parser) makeNode(leftIdentNode *Node) *Node {
	curOpNode, curOpInfo := self.readOp()
	rightIdentNode := self.primary()
	c, _ := self.lx.Peek(0)
	b, _ := self.isOp(c)
	for (c != 0) && b {
		nextOpInfo := self.peekOp(0)
		if self.compareOp(curOpInfo, nextOpInfo) {
			rightIdentNode = self.makeNode(rightIdentNode)

		} else {
			break
		}
		c, _ = self.lx.Peek(0)
		b, _ = self.isOp(c)
	}
	curOpNode.Left  = leftIdentNode
	curOpNode.Right = rightIdentNode
	return curOpNode
}
// 次の演算子が優先の場合 true
//   現演算子:左結合  &  現 <  次
//   現演算子:右結合  &  現 <= 次
func (self *Parser) compareOp(cur OpInfo, next OpInfo) bool {
	if (cur.Dir == true) && (cur.Rank < next.Rank ){
		return true
	} else if (cur.Dir == false) && (cur.Rank <= next.Rank) {
		return true
	}
	return false
}

//
// primary = ident | '(' expr ')'
//
func (self *Parser) primary() *Node {
	var node *Node
	char, idx := self.lx.Get()
	switch {
	case char == '(':
		node = self.Parse()
		if char, idx = self.lx.Get(); char != ')' {
			ErrorP("文法エラー0( ')'ではありません)", char, idx)
		}
	case self.isIdent(char):
		node = NewNode(NodeIdent, char)
	default:
		ErrorP("文法エラー1(変数名ではありません)", char, idx)
	}
	return node
}
func (self *Parser) peekOp(i int) OpInfo {
	char, idx := self.lx.Peek(i)
	b, info := self.isOp(char)
	if b == false {
		ErrorP("文法エラー2(演算子ではありません)", char, idx)
	}
	return info
}
func (self *Parser) readOp() (*Node, OpInfo) {
	char, idx := self.lx.Get()
	b, info := self.isOp(char)
	if b == false {
		ErrorP("文法エラー3(演算子ではありません)", char, idx)
	}
	return NewNode(NodeOp, char), info
}
func (self *Parser) readIdent() *Node {
	char, idx := self.lx.Get()
	if self.isIdent(char) == false {
		ErrorP("文法エラー4(識別子ではありません)", char, idx)
	}
	return NewNode(NodeIdent, char)
}

// 演算子であるかチェック
func (self *Parser) isOp(char byte) (bool, OpInfo) {
	if info, b := self.tbl[char]; b {
		return true, info
	}
	return false, OpInfo{0, false}
}
// 変数(識別子)であるかチェック
func  (self *Parser)isIdent(char byte) bool {
	if ( char >= 'A' && char <= 'Z')||( char >= 'a' && char <= 'z') {
		return true
	}
	return false
}



func ErrorP(msg string, char byte, idx int) {
	var s string
	if char == 0 {
		s = "EOL"
	} else {
		s = string(char)
	}
	fmt.Println(msg, "char:", s, ", pos:", idx)
	os.Exit(1)
}


func main() {
	p := NewParser("a=b=c+(d=e*(f+g))")
	n := p.Parse()
	fmt.Print(n)
}

実行結果

(= a (= b (+ c (= d (* e (+ f g) ) ) ) ) )