hakeの日記

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

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()
}