首页 > 其他分享 >Monkey 01 lexer 词法分析器

Monkey 01 lexer 词法分析器

时间:2024-07-15 22:08:52浏览次数:19  
标签:case ch Monkey newToken lexer tok token 01

此处可以下载每章代码https://interpreterbook.com/waiig_code_1.7.zip

 

首先,词法分析器是`输入字符串,输出词法单元token`.

要定义词法分析器,首先要定义token

token具有两个属性,一个是token的类型,另一个是token的字面量或者说能打印出来的值

//token/token.go
package token
type TokenType string

type Token struct {
	Type    TokenType
	Literal string
}

const (
	ILLEGAL = "ILLEGAL"
	EOF     = "EOF"

	IDENT = "IDENT" //标识符
	INT   = "INT"   //目前仅处理整型

	//运算符
	ASSIGN   = "="
	PLUS     = "+"
	MINUS    = "-"
	BANG     = "!"
	ASTERISK = "*"
	SLASH    = "/"

	LT = "<"
	GT = ">"

	EQ     = "==" //多字符的运算符
	NOT_EQ = "!="

	//分隔符
	COMMA     = ","
	SEMICOLON = ";"

	LPAREN = "("
	RPAREN = ")"
	LBRACE = "{"
	RBRACE = "}"

	//关键字
	FUNCTION = "FUNCTION"
	LET      = "LET"
	TRUE     = "TRUE"
	FALSE    = "FALSE"
	IF       = "IF"
	ELSE     = "ELSE"
	RETURN   = "RETURN"
)

 对于一个词法分析器,需要知道字符串、当前位置、当前字符,也可以加一个下个字符来方便操作

//lexer/lexer.go
package lexer

type Lexer struct {
	input        string
	position     int  // 输入字符串的当前位置
	readPosition int  // 读取位置,即当前字符的下一个位置
	ch           byte // 当前字符,要想支持UTF8等需要换成rune,同时修改获取下一个字符的函数
}

然后增加一个读取一个字符和查看下一个字符的函数

//lexer/lexer.go
func (l *Lexer) readChar() { // 读取下一个字符,移动指针
	if l.readPosition >= len(l.input) {
		l.ch = 0
	} else {
		l.ch = l.input[l.readPosition]
	}
	l.position = l.readPosition
	l.readPosition++
}
func (l *Lexer) peekChar() byte { //查看下一个字符,不移动指针,用来读取两个字符的token
	if l.readPosition >= len(l.input) {
		return 0
	} else {
		return l.input[l.readPosition]
	}
}

词法分析器初始化后就可以读取一个字符

//lexer/lexer.go
func New(input string) *Lexer { // 初始化
	l := &Lexer{input: input}
	l.readChar()
	return l
}

然后接下来的核心就是不断读取字符,判断是哪个标识符,然后获得词法单元token了

func newToken(tokenType token.TokenType, ch byte) token.Token {//获得一个新的词法单元token
	return token.Token{Type: tokenType, Literal: string(ch)}
}

空格、换行等对我们来说应该跳过,遇到就应该果断读取下一个字符

//lexer/lexer.go
func (l *Lexer) skipWhitespace() { //跳过空格
	for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
		l.readChar()
	}
}

对于每个词法单元,获取它的字面量

//lexer/lexer.go
func (l *Lexer) readIdentifier() string { //读取一个字符串作为标识符的字面量
	position := l.position
	for isLetter(l.ch) {
		l.readChar()
	}
	return l.input[position:l.position]
}

语句中的词法单元无外乎是字母、数字和符号。

如果遇到字母,得到的要么是关键字,要么就是标识符(变量名),当然下划线也是合法的标识符的一部分

//lexer/lexer.go
func isLetter(ch byte) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}

需要判断是否是某个标识符,直接在token.go里完成这个判断

//token/token.go
var keywords = map[string]TokenType{
	"fn":     FUNCTION,
	"let":    LET,
	"true":   TRUE,
	"false":  FALSE,
	"if":     IF,
	"else":   ELSE,
	"return": RETURN,
}

func LookupIdent(ident string) TokenType { //检查是否为关键字
	if tok, ok := keywords[ident]; ok { //在keywords里找indent的值赋给tok,如果不行ok为false
		return tok
	}
	return IDENT
}

然后把获取到的类型,字面量赋给新的token返回即可。

如果遇到数字就读取数字,然后类型设置为整型,字面量读进去返回。

//lexer/lexer.go
func isDigit(ch byte) bool {
	return '0' <= ch && ch <= '9'
}
func (l *Lexer) readNumber() string { 
	position := l.position
	for isDigit(l.ch) {
		l.readChar()
	}
	return l.input[position:l.position]
}

 剩下就是各种符号了,有单字符符号和双字符符号之分

综上,核心函数就是这样了

//lexer/lexer.go
func (l *Lexer) NextToken() token.Token { // 返回下一个token
	var tok token.Token
	l.skipWhitespace() //跳过空格

	switch l.ch {
	case '=':
		if l.peekChar() == '=' { //判断是否是==
			ch := l.ch //避免丢失当前变量
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = token.Token{Type: token.EQ, Literal: literal}
		} else {
			tok = newToken(token.ASSIGN, l.ch)
		}
	case '+':
		tok = newToken(token.PLUS, l.ch)
	case '-':
		tok = newToken(token.MINUS, l.ch)
	case '!':
		if l.peekChar() == '=' { //判断是否是!=
			ch := l.ch
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = token.Token{Type: token.NOT_EQ, Literal: literal}
		} else {
			tok = newToken(token.BANG, l.ch)
		}
	case '*':
		tok = newToken(token.ASTERISK, l.ch)
	case '/':
		tok = newToken(token.SLASH, l.ch)
	case ';':
		tok = newToken(token.SEMICOLON, l.ch)
	case '(':
		tok = newToken(token.LPAREN, l.ch)
	case ')':
		tok = newToken(token.RPAREN, l.ch)
	case ',':
		tok = newToken(token.COMMA, l.ch)
	case '{':
		tok = newToken(token.LBRACE, l.ch)
	case '}':
		tok = newToken(token.RBRACE, l.ch)
	case '<':
		tok = newToken(token.LT, l.ch)
	case '>':
		tok = newToken(token.GT, l.ch)
	case 0:
		tok.Literal = ""
		tok.Type = token.EOF
	default:
		if isLetter(l.ch) {
			tok.Literal = l.readIdentifier()          //字面量
			tok.Type = token.LookupIdent(tok.Literal) //是否关键字
			return tok                                //调用readIdentifier已经读完了这个字符串,无需继续执行readchar了
		} else if isDigit(l.ch) {
			tok.Type = token.INT
			tok.Literal = l.readNumber()
			return tok
		} else {
			tok = newToken(token.ILLEGAL, l.ch)        //非法字符
		}
	}

	l.readChar()
	return tok
}

 

标签:case,ch,Monkey,newToken,lexer,tok,token,01
From: https://www.cnblogs.com/qbning/p/18304040

相关文章

  • 转型Web3开发第二课:Dapp开发入门基础 | 01 | 安装MetaMask
    前言完成了《转型Web3开发第一课》之后,得到了不少读者的认可,很多都在问什么时候开始下一课,近期终于抽出了时间开始搞起这第二课。这第二课的主题为「Dapp开发入门基础」,即想要转型做Dapp开发的人员,不管是做前端开发、后端开发、智能合约开发,都需要掌握的基础知识。这......
  • 牛客TOP101:反转链表
    文章目录1.题目描述2.解题思路3.代码实现1.题目描述2.解题思路  简单粗暴的写法,就是从头到尾挨个将所有结点的指向翻转即可。需要注意的是,翻转之后会失去原有指向的结点,所以需要提前保存。  具体做法就是,使用cur标记当前结点,代表这我们将要翻转这个结点......
  • 打卡信奥刷题(332)用Scratch图形化工具信奥B3739[普及组/提高] [信息与未来 2018] 整数
    [信息与未来2018]整数乘方题目描述定义aaa的nnn次幂......
  • 「杂题乱刷2」CF1015D Walking Between Houses
    duel到的。题目链接CF1015DWalkingBetweenHouses解题思路一道细节题。思路很简单,肯定是一开始能走的越多越好,因此就有一种较好实现的方案,先每次走\(n-1\)格,但由于每次至少要走一格,因此如果不够走了就把能走的都走掉,之后全走\(1\)步即可。时间复杂度\(O(k)\)。参......
  • AI预测福彩3D采取888=3策略+和值012路或胆码测试7月15日新模型预测第33弹
        周末去外地出差,断更了两天,今天开始恢复每日一发~        今天咱们继续验证新模型的8码定位=3,重点是预测8码定位=3+和值012+胆码。有些朋友看到我最近几篇文章没有给大家提供缩水后的预测详情,在这里解释下:其实我每篇文章中既有8码定位,也有和值012路,也有胆码......
  • (138)SRAM接口--->(001)基于FPGA实现SRAM接口
    1目录(a)FPGA简介(b)IC简介(c)Verilog简介(d)基于FPGA实现SRAM接口(e)结束1FPGA简介(a)FPGA(FieldProgrammableGateArray)是在PAL(可编程阵列逻辑)、GAL(通用阵列逻辑)等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电......
  • P3304 [SDOI2013] 直径
    原题链接题解先随便找一条直径,然后标记这些边,然后看看直径上的点有没有不需要经过标记边的路径,使得其长度等于该点到直径端点的路径长度code#include<bits/stdc++.h>#definelllonglongusingnamespacestd;structedge{llto,val;};vector<edge>G[200005];l......
  • 代码随想录算法训练营第22天 |二叉树part07:235. 二叉搜索树的最近公共祖先、701.二叉
    代码随想录算法训练营第22天|二叉树part07:235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点235.二叉搜索树的最近公共祖先https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/description/代码随想录:htt......
  • ORA-01092 ORA-00604 ORA-08103故障处理---惜分飞
    联系:手机/微信(+8617813235971)QQ(107644445)标题:ORA-01092ORA-00604ORA-08103故障处理作者:惜分飞©版权所有[未经本人同意,不得以任何形式转载,否则有进一步追究法律责任的权利.]数据库启动报SQL>alterdatabaseopen;alterdatabaseopen*第1行出现错误:O......
  • 折腾记:尝试Hyper-V Server2019 部署配置
    镜像下载(微软官方)下载地址使用rufus写入U盘https://rufus.ie/zh/正常安装系统下载配置脚本https://file.uhsea.com/2407/247fe11846307d5eacedeb96a94f39e5MF.ps1https://www.doracloud.cn/downloads/hypervps1-cn.html打开powershell命令行中输入startpowershell就可......