LR(1)分析器程序golang代码实现

LR(1) 分析器程序golang实现

前言

由于课程要求(编译原理),所以花了大概一周的时间,断断续续的写出了这个基于LR(1)文法的分析器程序

一、实验目的

构造LR分析程序,利用它进行语法分析,判断给出的符号串是否为该文法识别的句子,了解 LR(K)分析方法是严格的从左向右扫描,和自底向上的语法分析方法。

二、实验预习提示

1、使用 LR 的优点:

(1)LR 分析器能够构造来识别所有能用上下文无关文法写的程序设计语言的结构。
(2)LR 分析方法是已知的最一般的无回溯移进-归约方法,它能够和其他移进-归约方法
一样有效地实现。
(3)LR 方法能分析的文法类是预测分析法能分析的文法类的真超集。
(4)LR 分析器能及时察觉语法错误,快到自左向右扫描输入的最大可能。
为了使一个文法是 LR 的,只要保证当句柄出现在栈顶时,自左向右扫描的移进-归约
分析器能够及时识别它便足够了。当句柄出现在栈顶时,LR 分析器必须要扫描整个栈就可
以知道这一点,栈顶的状态符号包含了所需要的一切信息。如果仅知道栈内的文法符号就能
确定栈顶是什么句柄。LR 分析表的转移函数本质上就是这样的有限自动机。不过,这个有
限自动机不需要根据每步动作读栈,因为,如果这个识别句柄的有限自动机自底向上读栈中
的文法符号的话,它达到的状态正是这时栈顶的状态符号所表示的状态,所以,LR 分析器
可以从栈顶的状态确定它需要从栈中了解的一切。

2、LR 分析器由三个部分组成:

(1)总控程序,也可以称为驱动程序。对所有的 LR 分析器总控程序都是相同的。
(2)分析表或分析函数,不同的文法分析表将不同,同一个文法采用的 LR 分析器不同
时,分析表将不同,分析表又可以分为动作表(ACTION)和状态转换(GOTO)表两
个部分,它们都可用二维数组表示。
(3)分析栈,包括文法符号栈和相应的状态栈,它们均是先进后出栈。
分析器的动作就是由栈顶状态和当前输入符号所决定。

  • LR(1) 分析器根据输入文法进行生产
  • 读取文法后进行拓广, 消除回溯, 消除左递归等处理过程
  • 然后根据拓广后的文法进行递归推导,求项目集规范族,构建出识别活前缀DFA自动机 采用有向图进行存储
  • 然后根据DFA生成一个LR(1)分析表 , Action表和Goto表,用状态转移函数进行状态的转移
  • 读取一个输入串按照以下流程进行分析,并输出分析步骤,分析结果

LR 分析器结构:
LR(1)分析器程序golang代码实现_第1张图片

其中 Sn 为状态栈 ,m 为文法符号栈 。
状态转换表用 GOTO[i,X]=j ,表示,规定当栈顶状态为 i,遇到当前文法符号为 X 时应转向状态 j,X 为终结符或非终结符。
ACTION[i,a]规定了栈顶状态为 i 时遇到输入符号 a 应执行动作有四种可能:
(1)移进:
action[i,a]= Sj:状态 j 移入到状态栈,把 a 移入到文法符号栈,其中 i,j 表示状态
号。
(2)归约:
action[i,a]=rk:当在栈顶形成句柄时,则归约为相应的非终结符 A,即文法中有
A->B 的产生式,若 B 的长度为 R(即|B|=R),则从状态栈和文法符号栈中自顶向下去掉
R 个符号,即栈指针 SP 减去 R,并把 A 移入文法符号栈内,j=GOTO[i,A]移进状态栈,
其中 i 为修改指针后的栈顶状态。
(3)接受 acc:
当归约到文法符号栈中只剩文法的开始符号 S 时,并且输入符号串已结束即当前
输入符是’$',则为分析成功。
(4)报错:
当遇到状态栈顶为某一状态下出现不该遇到的文法符号时,则报错,说明输入端不
是该文法能接受的符号串。
LR(1)分析器程序golang代码实现_第2张图片

三、 实验过程

编写好程序后用以下文法和输入串测试:
文法:

E -> E+T|T|+
T -> T*F|F
F -> (E)|i

输入串:
i*i+i

结果截图:
LR(1)分析器程序golang代码实现_第3张图片

LR(1)分析器程序golang代码实现_第4张图片

贴出部分代码:

lr1 主控程序

package LR_1_analyzer

import (
	"fmt"
	"log"
	"project02/syntaxAnalysis/version_1/gramp"
	"project02/syntaxAnalysis/version_1/queue"
	"project02/syntaxAnalysis/version_1/stack"
	"strings"
	"time"

	"github.com/jedib0t/go-pretty/table"
)

// LR(1) 分析器程序
/*
	计算步骤:
			1. 拓广文法
			2. 构造识别活前缀的DFA(项目集规范族)
			3. 构造LR1分析表
			4. 分析程序
*/

var (
	lR              *LR1
	NumGramMap      = make(map[string][]int) // 记录非终结符对应的文法编号
	StateListStack  = stack.NewStack()       // 状态栈
	SymbolListStack = stack.NewStack()       // 字符栈
	inputQueue      = queue.New()            // 输入串队列
	TAB             = table.NewWriter()      // 步骤表
)

// LR1 分析器数据结构
type LR1 struct {
	grammar    []Prd // 拓广后的文法
	*DFA             // DFA自动机
	*ParsTable       // 分析表
}

// 记录产生式
type Prd struct {
	LeftPart  string
	RightPart gramp.Production
}

// 返回一个LR 根据文法创建LR分析器
func NewLR1Dev(gramFilePath string) *LR1 {

	{
		// 初始化
		lR = &LR1{}

		// 处理
		gramp.GramAnalyStore(gramFilePath) // 读入文法
		gramp.RemoveRecall()               // 消除回溯
		gramp.LeftRecursion()              // 消除左递归
		expandGram()                       // 拓广文法
		gramp.GetFirstSet2()               // 求first集
		gramp.GetFollowSet()               // 求follow集
		NewDFA()                           // 构建DFA
		NewTable()                         // 构建分析表
	}
	return lR
}

// ########################################################## LR1方法 #################################################################
// 分析程序
func (lr *LR1) AnalyProcedInput(input string) {
	// 输入串存入队列
	for _, str := range input {
		inputQueue.Add(string(str))
	}
	inputQueue.Add("$")
	StateListStack.Push(0) // 状态栈里面初始状态为 0

	{
		// t.Style().Options.SeparateRows = true
		TAB.AppendHeader(table.Row{"状态栈", "符号栈", "输入字符串", "动作"})
	}

	// 按步骤打印出分析结果
	PrintStepResult := func(action behaviour) { // 传入动作
		r1 := strings.Join(StateListStack.GetSliceString(), "")
		r2 := strings.Join(SymbolListStack.GetSliceString(), "")
		r3 := strings.Join(inputQueue.GetSliceString(), "")
		r4 := ""
		switch action.Type {
		case ACCEPT: // 接受
			r4 = "接受"
		case GMSOAA: // 规约
			r4 = "根据产生式" + lR.grammar[action.ID].LeftPart + lR.grammar[action.ID].RightPart.Join("") + "进行规约"
		case MOVEIN: // 移进
			r4 = "移入"
		default:
			log.Fatal("输入串不合规")
		}
		TAB.AppendRow(table.Row{r1, r2, r3, r4})
	}

	// 进行处理
	for inputQueue.Length() > 0 {
		action, ok := QueryTable(StateListStack.Peek().(int), inputQueue.Peek().(string)) // 查表获取下一个到达状态
		if !ok {
			return
		}
		PrintStepResult(action)
		switch action.Type {
		case ACCEPT: // 接受
			acceptAction()
		case GMSOAA: // 规约
			gmsoaaAction(action.ID)
		case MOVEIN: // 移进
			moveInAction(action.ID)
		default:
			log.Fatal("输入串不合规")
		}
	}

	// fmt.Println(TAB.Render())

}


// 移进
func moveInAction(id int) {
	StateListStack.Push(id)             // 压入状态栈
	sym := inputQueue.Remove().(string) // 取出栈顶字符
	SymbolListStack.Push(sym)           // 压入符号栈
}

// 规约
func gmsoaaAction(pid int) {
	prod := lR.grammar[pid]
	if prod.RightPart.IsEmpty() { // 如果是空产生式
		SymbolListStack.Push(prod.LeftPart)
		gotoAct, ok := QueryTable(StateListStack.Peek().(int), prod.LeftPart) // 查表获取下一个GOTO的状态
		if !ok {
			return
		}
		StateListStack.Push(gotoAct.ID) // 移入状态栈
	} else { // 非空产生式
		length := len(prod.RightPart)
		var i int
		for i = length - 1; i > -1; i-- {
			if prod.RightPart[i].Name != SymbolListStack.Pop().(string) { // 一个一个取出进行规约
				break
			}
		}

		// 字符能够匹配产生式
		if i == -1 {
			SymbolListStack.Push(prod.LeftPart) // 规约的左部放入符号栈
			// 状态回退
			for a := 0; a < length; a++ {
				StateListStack.Pop() // 移除栈顶状态
			}
			gotoAct, ok := QueryTable(StateListStack.Peek().(int), prod.LeftPart) // 查表获取下一个GOTO的状态
			if !ok {
				return
			}
			StateListStack.Push(gotoAct.ID) // 移入状态栈
		} else {
			fmt.Println("i==:", i)
			log.Fatal("分析出现问题,无法识别输入串,输入串不合规")
		}
	}
}

// 接受
func acceptAction() {
	// log.Println("succeed: 该输入串是合规的》》》》")
	inputQueue.Remove()
}

// 查表
func QueryTable(state int, symbol string) (behaviour, bool) {
	if col, ok := lR.Table[symbol]; ok {
		return col[state], true
	} else {
		log.Println("该输入串存在非法字符,不符合文法规则")
		return behaviour{}, false
	}
}

func (lr *LR1) PrintResult() {
	fmt.Println("正在编译文法...")
	time.Sleep(time.Second * 3)
	fmt.Println("文法:")
	for i, v := range lr.grammar {
		fmt.Printf("\t(%v)  %v ==> %v\n", i, v.LeftPart, v.RightPart.Join(""))
		time.Sleep(time.Millisecond * 100)
	}
	fmt.Println("计算First集...")
	time.Sleep(time.Second * 2)
	gramp.PrintFirstSet()

	fmt.Println("计算Follow集...")
	time.Sleep(time.Second * 2)
	gramp.PrintFollowSet()

	fmt.Println()
	fmt.Println("构建DFA...")
	time.Sleep(time.Second * 2)
	fmt.Println("进行状态推导.......")
	time.Sleep(time.Second * 3)
	for _, v := range lR.AllStates {
		v.PrintResult()
		time.Sleep(time.Millisecond * 200)
	}

	fmt.Println()
	fmt.Println("生成LR(1)分析表....")
	time.Sleep(time.Second)
	GetParsTable()

	fmt.Println()
	fmt.Println("进行文法分析....")
	time.Sleep(time.Second * 3)
	fmt.Println(TAB.Render())
}

DFA生成

package LR_1_analyzer

import (
	"fmt"
	"project02/syntaxAnalysis/version_1/gramp"
	"project02/syntaxAnalysis/version_1/queue"
	"project02/syntaxAnalysis/version_1/set"
	"strconv"
	"strings"
)

// 1.拓广文法
func expandGram() {
	// gramp.PrintRes() // 原文法
	S0 := []string{"S`"}
	sym := gramp.Symbol{
		Name: gramp.Cfg.Sign,
		Type: 1,
	}
	prd := gramp.Production{sym}
	gramp.Cfg.Vn = append(S0, gramp.Cfg.Vn...)
	gramp.Cfg.Sign = S0[0]
	gramp.Cfg.Prod[S0[0]] = []gramp.Production{prd}

	// 进行文法编号排序
	cnt := 0
	gramp.Cfg.Range(func(k string, v []gramp.Production) {
		for _, value := range v {
			prd := Prd{}
			prd.LeftPart = k
			prd.RightPart = value
			lR.grammar = append(lR.grammar, prd)
			NumGramMap[k] = append(NumGramMap[k], cnt)
			cnt++
		}
	})

}

// 构造识别活前缀的DFA
type DFA struct {
	StartState *State   // 起始状态
	AllStates  []*State // 所有状态的集合
	countSt    int      // 当前状态数
}

// 存储DFA状态
type State struct {
	ID            int                     // 当前状态编号
	ItemSet       []Item                  // 包含的项目集
	Transitions   map[gramp.Symbol]*State // 状态出发的边和可以到达的状态
	ItemIndex     map[gramp.Symbol][]int  // 记录边对应的项目的索引
	ItemSetLength int                     // 记录ItemSet 长度
}

// 单个项目
type Item struct {
	Pid      int          // 产生式编号
	TagPosit int          // 项目标记符位置 记录 . 的位置
	FwdSChar []string     // 向前搜索符号
	flowSyb  gramp.Symbol // 后继符号
}

var (
	tasksQueue   = queue.New()          // 状态队列
	itemSetCheck = make(map[string]int) // 记录项目第一次出现所在的状态ID
)

// 初始化
func NewDFA() {
	lR.DFA = &DFA{}
	lR.DFA.StartState = &State{
		ID:            0,
		Transitions:   make(map[gramp.Symbol]*State),
		ItemIndex:     make(map[gramp.Symbol][]int),
		ItemSetLength: 0,
	}
	lR.DFA.AllStates = []*State{lR.StartState}
	lR.DFA.countSt = 1
	buildDFA() // 构建识别活前缀的DFA
}

// ########################################################## 处理步骤 #################################################################

// 2. 构造识别活前缀的DFA(项目集规范族)
func buildDFA() {
	// 进行状态推导  基于S0状态开始
	initState_0()
	// 处理状态队列
	proStateQueue()
}

// 初始化状态 S0  StartState
func initState_0() {
	// 第一个项目
	item := Item{
		Pid:      0,
		TagPosit: 0, // 前面有0个符号 记录 . 的位置
		FwdSChar: []string{"$"},
		flowSyb:  lR.grammar[0].RightPart[0], // 后继符号
	}
	lR.StartState.AddNewEdgeAndState(item) // 添加 边状态,标记item
	lR.StartState.ItemSet = append(lR.StartState.ItemSet, item)
	lR.StartState.ItemSet = append(lR.StartState.ItemSet, lR.StartState.closure(lR.StartState.ItemSet)...) // 求闭包
	lR.StartState.uniqueItemSet()                                                                          // 去重
	lR.StartState.stateTransferFunc()                                                                      // 状态转移
	// lR.StartState.PrintResult()

}

// step2 广度优先遍历 计算出其余的项目
func proStateQueue() {
	// 遍历队列
	for tasksQueue.Length() > 0 {
		ste := tasksQueue.Remove().(*State) // 获取状态
		for _, item := range ste.ItemSet {  // 初始化原项目 添加状态 边 等操作
			ste.AddNewEdgeAndState(item)
		}
		ste.ItemSet = append(ste.ItemSet, ste.closure(ste.ItemSet)...) // 求闭包
		ste.uniqueItemSet()                                            // 去重
		ste.stateTransferFunc()                                        // 状态转移
	}

}

// ########################################################## State方法 #################################################################

// 求闭包
func (st *State) closure(itemSet []Item) []Item {
	result := []Item{}
	for _, it := range itemSet {
		if it.flowSyb.Type == 1 { // 非终结符
			for _, v := range NumGramMap[it.flowSyb.Name] { // 查询产生式集合
				item := Item{
					Pid:      v,
					TagPosit: 0,
					FwdSChar: getFirst(it),               // 向前搜索符
					flowSyb:  lR.grammar[v].RightPart[0], // 后继符号
				}

				// 产生边 添加记录到state
				st.AddNewEdgeAndState(item)

				// 存入
				result = append(result, item)

				if item.flowSyb.Type == 1 { // 非终结符继续求闭包
					result = append(result, st.closure([]Item{item})...)
				}
			}
		}
	}
	return result
}

// 添加新的边和到达的状态 标记项目
func (st *State) AddNewEdgeAndState(item Item) {
	if _, ok := st.Transitions[item.flowSyb]; !ok {
		if item.flowSyb.Type == 3 { // 为空没有后继状态
			st.Transitions[item.flowSyb] = &State{
				ID:          -1,
				ItemSet:     []Item{},
				Transitions: make(map[gramp.Symbol]*State),
			}
		} else {
			if id, ok := item.IsExisted(); ok { // 判断当前项目是否已经存在(如果已经存在则不用产生新的边 状态)
				st.Transitions[item.flowSyb] = lR.AllStates[id].Transitions[item.flowSyb]
			} else {
				st.Transitions[item.flowSyb] = newState()
				// 标记项目
				item.AddFlag(st.ID)
			}
		}
	}
}

// 新建状态
func newState() *State {
	lR.countSt++
	sts := &State{
		ID:          lR.countSt - 1,
		ItemSet:     []Item{},
		Transitions: make(map[gramp.Symbol]*State),
	}
	tasksQueue.Add(sts)
	lR.AllStates = append(lR.AllStates, sts)
	return sts
}

// 打印结果
func (s *State) PrintResult() {
	fmt.Println("状态ID: ", s.ID)
	fmt.Println("项目集: {")
	for _, v := range s.ItemSet {
		str := lR.grammar[v.Pid].RightPart[:v.TagPosit].Join("") + "•" + lR.grammar[v.Pid].RightPart[v.TagPosit:].Join("")
		fmt.Printf("\t\t[%-2v -> %-4v, %-4v]\t后继符号:%v \t后继状态:S%v\t\n",
			lR.grammar[v.Pid].LeftPart,
			str,
			strings.Join(v.FwdSChar, "|"),
			v.flowSyb.Name,
			s.Transitions[v.flowSyb].ID,
		)
	}
	fmt.Println("\t}\n")
}

// 状态转移函数
func (ste *State) stateTransferFunc() {
	for _, v := range ste.ItemSet { // 项目转移到下一可达状态 v -> itm
		if v.flowSyb.Type != 3 && ste.ID != ste.Transitions[v.flowSyb].ID {
			itm := Item{
				Pid:      v.Pid,
				TagPosit: v.TagPosit + 1,
				FwdSChar: v.FwdSChar, // 向前搜索符
			}

			if itm.TagPosit >= len(lR.grammar[itm.Pid].RightPart) {
				itm.flowSyb = gramp.EMPTYITEM[0] // 规约项没有后继符号
			} else {
				itm.flowSyb = lR.grammar[itm.Pid].RightPart[itm.TagPosit]
			}

			ste.Transitions[v.flowSyb].ItemSet = append(ste.Transitions[v.flowSyb].ItemSet, itm)
			ste.Transitions[v.flowSyb].uniqueItemSet() // 去重
		}
	}
}

// 去重Item
func (s *State) uniqueItemSet() {
	result := []Item{}
	st := set.NewSet[string]()
	for _, v := range s.ItemSet {
		flag := strconv.Itoa(v.Pid) + strconv.Itoa(v.TagPosit) + strings.Join(v.FwdSChar, "_")

		if !st.IScontain(flag) {

			result = append(result, v)
			st.Add(flag)
		}
	}

	s.ItemSet = result
}

// ####################################################### Item ###############################################################

// 求向前搜索符(first集)
func getFirst(item Item) []string {
	fist := func(production gramp.Production) ([]string, bool) { // 是否继承展望符:  true 则继承,false 不继承
		var restr []string
		for _, sym := range production {
			if sym.Type == 0 { // 终结符直接返回
				restr = append(restr, sym.Name)
				return restr, false
			}

			// 非终结符
			fST := gramp.First(sym.Name)
			if i, ok := gramp.IScontainVt(sym.Name, "空"); ok {
				restr = append(restr, fST[0:i]...)
				restr = append(restr, fST[i+1:]...)
			} else {
				restr = append(restr, fST...)
				return restr, false
			}
		}

		return restr, true
	}

	prod := lR.grammar[item.Pid].RightPart

	if len(prod) > item.TagPosit+1 {
		fst, ok := fist(prod[item.TagPosit+1:])
		if ok {
			fst = append(fst, item.FwdSChar...)
			return fst
		}
		return fst
	}

	return item.FwdSChar
}

// 将结构体转为字符串
func (itm Item) ToString() string {
	return fmt.Sprintf("%v_%v_%s_%s_%d",
		itm.Pid,
		itm.TagPosit,
		strings.Join(itm.FwdSChar, ""),
		itm.flowSyb.Name,
		itm.flowSyb.Type)
}

// 添加记录到哈希表 itemSetCheck
func (itm Item) AddFlag(id int) {
	flag := itm.ToString()
	if _, ok := itemSetCheck[flag]; !ok {
		itemSetCheck[flag] = id
	}
}

// 检查 itemCheck 是否存在
func (itm Item) IsExisted() (int, bool) {
	flag := itm.ToString()
	idx, ok := itemSetCheck[flag]
	return idx, ok
}

生成分析表


package LR_1_analyzer

import (
	"fmt"
	"project02/syntaxAnalysis/version_1/gramp"
	"strconv"

	"github.com/jedib0t/go-pretty/table"
)

// 3. 构造LR1分析表

// LR1分析表数据结构
type ParsTable struct {
	Table   map[string][]behaviour
	SymList []gramp.Symbol
}

const (
	DEFAULT = iota
	ACCEPT  // 接受
	MOVEIN  // 移进
	GMSOAA  // 规约
)

// 描述动作
type behaviour struct {
	ID   int // 状态S 或者 产生式R 的编号
	Type int // 动作的类型
}

// 新建一个分析表
func NewTable() {
	lR.ParsTable = &ParsTable{
		Table:   make(map[string][]behaviour, 0),
		SymList: make([]gramp.Symbol, 0),
	}
	// 填入表项
	// action表 终结符
	for _, v := range gramp.Cfg.Vt {
		lR.ParsTable.Table[v] = make([]behaviour, lR.countSt)
		vsym := gramp.Symbol{
			Name: v,
			Type: gramp.TREMCHAR,
		}
		lR.ParsTable.SymList = append(lR.ParsTable.SymList, vsym)
	}

	// $
	lR.ParsTable.Table["$"] = make([]behaviour, lR.countSt)
	vSym := gramp.Symbol{
		Name: "$",
		Type: gramp.SPEPCHAR,
	}
	lR.ParsTable.SymList = append(lR.ParsTable.SymList, vSym)

	// Goto表 非终结符
	for _, v := range gramp.Cfg.Vn {
		if v != gramp.Cfg.Sign {
			lR.ParsTable.Table[v] = make([]behaviour, lR.countSt)
			vsym := gramp.Symbol{
				Name: v,
				Type: gramp.NOTECHAR,
			}
			lR.ParsTable.SymList = append(lR.ParsTable.SymList, vsym)
		}
	}

	// 构建分析表
	buildParsTable()

}

// 构建分析表
func buildParsTable() {
	for i := 0; i < lR.countSt; i++ {
		behvirMap := getbehaviourOrGoto(i) // 获取动作
		for _, v := range lR.SymList {
			if behavir, ok := behvirMap[v.Name]; ok {
				lR.Table[v.Name][i] = behavir
			}
		}
	}
}

// 获取执行的动作
func getbehaviourOrGoto(idx int) map[string]behaviour {
	resultMap := make(map[string]behaviour, 0)
	ste := lR.AllStates[idx]
	for _, item := range ste.ItemSet {
		bh := behaviour{}
		if item.flowSyb.Type == 3 { // 空  规约项
			if lR.grammar[item.Pid].LeftPart == gramp.Cfg.Sign { // 接受项
				bh.ID = -1
				bh.Type = ACCEPT // 接受
				resultMap["$"] = bh
			} else {
				bh.ID = item.Pid // 产生式id
				bh.Type = GMSOAA // 规约
				for _, serchar := range item.FwdSChar {
					resultMap[serchar] = bh
				}
			}
		} else { // 移进项
			bh.ID = ste.Transitions[item.flowSyb].ID // 记录到达的状态ID
			bh.Type = MOVEIN                         // 移进
			resultMap[item.flowSyb.Name] = bh        // 存入
		}
	}

	return resultMap
}

// 打印分析表
func GetParsTable() {
	t := table.NewWriter()
	style := table.Style{
		Name:    "StyleColoredBright",
		Box:     table.StyleBoxDefault,
		Color:   table.ColorOptionsBright,
		Options: table.OptionsNoBordersAndSeparators,
		Title:   table.TitleOptionsDark,
	}

	t.SetStyle(style)

	// t.Style().Options.SeparateRows = true
	header := table.Row{"状态"}
	for _, v := range lR.SymList {
		header = append(header, v.Name)
	}
	t.AppendHeader(header)

	for i := 0; i < lR.countSt; i++ {
		row := table.Row{i}

		for _, v := range lR.SymList {
			strn := ""
			switch lR.Table[v.Name][i].Type {
			case ACCEPT:
				strn = "ACCEPT"
			case GMSOAA:
				strn = "R" + strconv.Itoa(lR.Table[v.Name][i].ID)
			case MOVEIN:
				if v.Type == gramp.NOTECHAR {
					strn = strconv.Itoa(lR.Table[v.Name][i].ID)
				} else {
					strn = "S" + strconv.Itoa(lR.Table[v.Name][i].ID)
				}
			}

			row = append(row, strn)
		}
		t.AppendRow(row)
	}

	fmt.Println(t.Render())
}

你可能感兴趣的:(go,golang)