Go言語 - 字句解析プログラム
勉強がてら字句解析プログラムを作成してみました。
Lexser.Scan()を実行する毎に、sample.txtから一文字(rune)ずつ読み込み、1トークンを読み込んだら、そのトークンの書かれている行、桁、トークン種類、トークンリテラル文字列を表示します。
数字はNUMBERトークン、ダブルクォートで囲まれた文字列はSTRINGトークン、英字とアンダースコアで始まる英数字列はIDENTトークンとなります。ただし英数字列の内、TokenTableに登録されているものは予約語として対応する種類のトークンになります。
ファイル終端(EOF)に到達後にLexser.Scan()が実行された場合はEOFトークンが返されます。
sample.txt
package main import "fmt" var abc int abc = 10
結果
line: 1 col: 0 kind: IDENT str: package line: 1 col: 8 kind: IDENT str: main line: 3 col: 0 kind: IDENT str: import line: 3 col: 7 kind: STRING str: fmt line: 5 col: 0 kind: VAR str: var line: 5 col: 4 kind: IDENT str: abc line: 5 col: 8 kind: INT str: int line: 6 col: 0 kind: IDENT str: abc line: 6 col: 4 kind: OTHER str: = line: 6 col: 6 kind: NUMBER str: 10 line: 7 col: 0 kind: EOF str: <EOF> line: 7 col: 0 kind: EOF str: <EOF>
lexer.go
package main import ( "fmt" "os" "bufio" "io" ) const ( EOF = iota + 1 IDENT NUMBER STRING OTHER VAR INT ) var TokenList = []string{ // Lexer.Print()用 "NULL", "EOF", "IDENT", "NUMBER", "STRING", "OTHER", "VAR", "INT", } var TokenTable = map[string]int{ // 予約語 "var": VAR, "int": INT, } type Lexer struct{ r *bufio.Reader buffer string // トークンのリテラル格納 line int // トークンのある行 col int // トークン開始カラム pos int // 現在カラム位置 ch rune // 現在の文字 kind int // トークン種類 list []string table map[string]int } func NewLexer(fp io.Reader) *Lexer { lx := &Lexer{} lx.r = bufio.NewReader(fp) return lx } func (self *Lexer)Init(list []string, table map[string]int) { self.buffer = "" self.line = 1 self.pos = -1 self.list = list self.table = table self.nextChar() } func (self *Lexer)nextChar() { ch, _, err := self.r.ReadRune() self.pos++ if err != nil { self.ch = 0 return } self.ch = ch } func (self *Lexer)isNumber() bool { if (self.ch >= '0')&&(self.ch <= '9'){ return true } return false } func (self *Lexer)isLetter() bool { if ((self.ch >= 'a')&&(self.ch <= 'z')) || ((self.ch >= 'A')&&(self.ch <= 'Z')) || self.ch == '_' { return true } return false } func (self *Lexer)isIDENT() bool { if self.isNumber() || self.isLetter() { return true } return false } func (self *Lexer)isWhiteSpace() bool { if self.ch == ' ' || self.ch == '\t' || self.ch == '\r' || self.ch == '\n' { return true } return false } func (self *Lexer)scanNumber() { var s []rune self.kind = NUMBER self.col = self.pos s = append(s, self.ch) self.nextChar() for self.isNumber() { s = append(s, self.ch) self.nextChar() } self.buffer = string(s) } func (self *Lexer)scanIDENT() { var s []rune self.kind = IDENT self.col = self.pos s = append(s, self.ch) self.nextChar() for self.isIDENT() { s = append(s, self.ch) self.nextChar() } ss := string(s) if v, b := self.table[ss]; b { self.kind = v } self.buffer = string(ss) } func (self *Lexer)scanString() { var s []rune self.kind = STRING self.col = self.pos self.nextChar() for self.ch != '"' { s = append(s, self.ch) self.nextChar() } self.nextChar() self.buffer = string(s) } func (self *Lexer)skipWhiteSpace() { for self.isWhiteSpace() { if self.ch == '\n' { self.line++ self.pos = -1 } self.nextChar() } } func (self *Lexer)Scan() (b bool) { b = true self.buffer = "" self.skipWhiteSpace() if self.ch == 0 { // EOFの場合 b = false self.kind = EOF self.col = self.pos self.buffer = "<EOF>" } else if self.isNumber() { self.scanNumber() } else if self.isLetter() { self.scanIDENT() } else if self.ch =='"' { self.scanString() } else { self.kind = OTHER self.col = self.pos self.buffer = string(self.ch) self.nextChar() } return } func (self *Lexer)Print() { fmt.Printf("line: %2d col: %2d kind: %8s str: %s\n", self.line, self.col, self.list[self.kind], self.buffer) } func main() { fp, err := os.Open("sample.txt") if err != nil{ fmt.Println("File cannot open") os.Exit(1) } defer fp.Close() lx := NewLexer(fp) lx.Init(TokenList, TokenTable) // 初期化 for lx.Scan() { // 字句を取得(if EOF then false) lx.Print() // 取得した字句情報の表示 } // EOF検出後にSCANした場合 lx.Scan() lx.Print() lx.Scan() lx.Print() }