一个极简的词法分析器实现

文章目录

  • 推荐:Tiny Lexer - 一个极简的C语言词法分析器
    • 特点
    • 核心代码实现
    • 学习价值
    • 扩展建议
  • 用Java实现一个简单的词法分析器
    • 完整实现代码
    • 代码解析
    • 示例输出
    • 扩展建议
  • 用Go实现极简词法分析器
    • 完整实现代码
    • 代码解析
    • 示例输出
    • 扩展建议

最近两天搞一个DSL,不得不重温了一把antlr4和编译原理之词法分析。

推荐:Tiny Lexer - 一个极简的C语言词法分析器

推荐一个非常小巧但完整的C语言词法分析器实现 - Tiny Lexer。它具有以下优点:

特点

  • 代码量极小(约100行核心代码)
  • 纯C实现,无外部依赖
  • 易于理解和学习
  • 包含完整的功能:标识符、数字、运算符识别等

核心代码实现

#include <stdio.h>
#include <ctype.h>
#include <string.h>typedef enum {TOKEN_EOF,TOKEN_NUMBER,TOKEN_IDENTIFIER,TOKEN_OPERATOR,TOKEN_UNKNOWN
} TokenType;typedef struct {TokenType type;char value[32];
} Token;Token get_next_token(const char** input) {Token token = {TOKEN_UNKNOWN, {0}};// 跳过空白字符while (isspace(**input)) {(*input)++;}// 检查文件结束if (**input == '\0') {token.type = TOKEN_EOF;return token;}// 处理数字if (isdigit(**input)) {token.type = TOKEN_NUMBER;int i = 0;while (isdigit(**input) && i < sizeof(token.value)-1) {token.value[i++] = *(*input)++;}token.value[i] = '\0';return token;}// 处理标识符(字母开头)if (isalpha(**input)) {token.type = TOKEN_IDENTIFIER;int i = 0;while ((isalnum(**input) || **input == '_') && i < sizeof(token.value)-1) {token.value[i++] = *(*input)++;}token.value[i] = '\0';return token;}// 处理运算符if (strchr("+-*/=(){};", **input)) {token.type = TOKEN_OPERATOR;token.value[0] = *(*input)++;token.value[1] = '\0';return token;}// 未知字符token.value[0] = *(*input)++;return token;
}int main() {const char* input = "int x = 42 + y;";const char* p = input;while (1) {Token token = get_next_token(&p);if (token.type == TOKEN_EOF) break;const char* type_str;switch (token.type) {case TOKEN_NUMBER: type_str = "NUMBER"; break;case TOKEN_IDENTIFIER: type_str = "IDENTIFIER"; break;case TOKEN_OPERATOR: type_str = "OPERATOR"; break;default: type_str = "UNKNOWN"; break;}printf("Token: %-12s Value: %s\n", type_str, token.value);}return 0;
}

学习价值

  1. 词法分析基本原理:展示了如何将输入流分解为token
  2. 状态机概念:通过条件判断实现了简单的状态转移
  3. 可扩展性:可以轻松添加更多token类型和规则
  4. 实用性:虽然简单,但包含了词法分析的核心功能

扩展建议

学习这个基本实现后,你可以尝试:

  1. 添加更多运算符和关键字识别
  2. 实现更复杂的数字格式(如浮点数)
  3. 添加错误处理机制
  4. 将其扩展为递归下降语法分析器

这个实现去除了所有不必要的复杂性,是学习编译原理前端技术的理想起点。

用Java实现一个简单的词法分析器

下面我将实现一个最简的词法分析器(Lexer),它可以识别以下类型的词法单元(Token):

  • 整数(如 123
  • 浮点数(如 3.14
  • 标识符(如 variable
  • 运算符(如 +, -, *, /
  • 分隔符(如 (, ), ;

完整实现代码

import java.util.ArrayList;
import java.util.List;public class SimpleLexer {// Token类型枚举public enum TokenType {INTEGER,    // 整数FLOAT,      // 浮点数IDENTIFIER, // 标识符OPERATOR,   // 运算符DELIMITER,  // 分隔符EOF         // 文件结束}// Token类public static class Token {public final TokenType type;public final String value;public Token(TokenType type, String value) {this.type = type;this.value = value;}@Overridepublic String toString() {return String.format("Token(%s, '%s')", type, value);}}private final String input;private int position = 0;public SimpleLexer(String input) {this.input = input;}// 词法分析主方法public List<Token> tokenize() {List<Token> tokens = new ArrayList<>();while (position < input.length()) {char current = peek();// 跳过空白字符if (Character.isWhitespace(current)) {consume();continue;}// 处理数字if (Character.isDigit(current)) {tokens.add(readNumber());continue;}// 处理标识符(以字母或下划线开头)if (Character.isLetter(current) || current == '_') {tokens.add(readIdentifier());continue;}// 处理运算符if (isOperator(current)) {tokens.add(new Token(TokenType.OPERATOR, Character.toString(consume())));continue;}// 处理分隔符if (isDelimiter(current)) {tokens.add(new Token(TokenType.DELIMITER, Character.toString(consume())));continue;}throw new RuntimeException("Unexpected character: " + current);}tokens.add(new Token(TokenType.EOF, ""));return tokens;}// 读取数字(整数或浮点数)private Token readNumber() {StringBuilder sb = new StringBuilder();boolean hasDecimal = false;while (position < input.length() && (Character.isDigit(peek()) || (!hasDecimal && peek() == '.'))) {if (peek() == '.') {hasDecimal = true;}sb.append(consume());}return new Token(hasDecimal ? TokenType.FLOAT : TokenType.INTEGER, sb.toString());}// 读取标识符private Token readIdentifier() {StringBuilder sb = new StringBuilder();while (position < input.length() && (Character.isLetterOrDigit(peek()) || peek() == '_')) {sb.append(consume());}return new Token(TokenType.IDENTIFIER, sb.toString());}// 辅助方法:查看当前字符但不消耗private char peek() {return input.charAt(position);}// 辅助方法:消耗当前字符并返回private char consume() {return input.charAt(position++);}// 判断是否是运算符private boolean isOperator(char c) {return c == '+' || c == '-' || c == '*' || c == '/';}// 判断是否是分隔符private boolean isDelimiter(char c) {return c == '(' || c == ')' || c == ';';}// 测试public static void main(String[] args) {String input = "x = 123 + (y * 3.14);";SimpleLexer lexer = new SimpleLexer(input);List<Token> tokens = lexer.tokenize();for (Token token : tokens) {System.out.println(token);}}
}

代码解析

  1. Token类型定义

    • 使用枚举TokenType定义了各种词法单元类型
    • Token类封装了类型和实际值
  2. 核心方法

    • tokenize():主方法,逐个字符分析输入字符串
    • readNumber():读取整数或浮点数
    • readIdentifier():读取标识符
  3. 辅助方法

    • peek():查看当前字符但不移动指针
    • consume():读取当前字符并移动指针
    • isOperator()isDelimiter():判断字符类型
  4. 处理流程

    • 跳过空白字符
    • 按优先级识别数字、标识符、运算符和分隔符
    • 遇到无法识别的字符抛出异常

示例输出

对于输入"x = 123 + (y * 3.14);",输出结果为:

Token(IDENTIFIER, 'x')
Token(OPERATOR, '=')
Token(INTEGER, '123')
Token(OPERATOR, '+')
Token(DELIMITER, '(')
Token(IDENTIFIER, 'y')
Token(OPERATOR, '*')
Token(FLOAT, '3.14')
Token(DELIMITER, ')')
Token(DELIMITER, ';')
Token(EOF, '')

扩展建议

这个简单词法分析器可以进一步扩展:

  1. 支持更多运算符(如==, !=等)
  2. 添加关键字识别(如if, while等)
  3. 支持字符串字面量
  4. 添加注释处理
  5. 改进错误处理机制

这个实现是编译器前端的基础组件,可以作为更复杂词法分析器的起点。

用Go实现极简词法分析器

下面是一个用Go语言实现的极简词法分析器,它可以识别以下词法单元:

  • 整数(如 123
  • 标识符(如 variable
  • 运算符(如 +, -, *, /
  • 分隔符(如 (, ), ;

完整实现代码

package mainimport ("fmt""unicode"
)// TokenType 表示词法单元类型
type TokenType intconst (EOF TokenType = iota // 文件结束INTEGER             // 整数IDENTIFIER          // 标识符OPERATOR            // 运算符DELIMITER           // 分隔符
)// Token 表示一个词法单元
type Token struct {Type  TokenTypeValue string
}// String 实现Stringer接口
func (t Token) String() string {return fmt.Sprintf("Token(%v, '%s')", t.Type, t.Value)
}// Lexer 词法分析器结构体
type Lexer struct {input    stringposition int
}// NewLexer 创建新的词法分析器
func NewLexer(input string) *Lexer {return &Lexer{input: input}
}// NextToken 获取下一个词法单元
func (l *Lexer) NextToken() Token {// 跳过空白字符l.skipWhitespace()// 检查是否到达输入末尾if l.position >= len(l.input) {return Token{Type: EOF, Value: ""}}current := l.peek()// 处理数字if unicode.IsDigit(current) {return l.readNumber()}// 处理标识符if unicode.IsLetter(current) || current == '_' {return l.readIdentifier()}// 处理运算符if isOperator(current) {token := Token{Type: OPERATOR, Value: string(l.consume())}return token}// 处理分隔符if isDelimiter(current) {token := Token{Type: DELIMITER, Value: string(l.consume())}return token}panic(fmt.Sprintf("未知字符: %c", current))
}// 读取数字
func (l *Lexer) readNumber() Token {start := l.positionfor l.position < len(l.input) && unicode.IsDigit(l.peek()) {l.consume()}return Token{Type: INTEGER, Value: l.input[start:l.position]}
}// 读取标识符
func (l *Lexer) readIdentifier() Token {start := l.positionfor l.position < len(l.input) && (unicode.IsLetter(l.peek()) || unicode.IsDigit(l.peek()) || l.peek() == '_') {l.consume()}return Token{Type: IDENTIFIER, Value: l.input[start:l.position]}
}// 跳过空白字符
func (l *Lexer) skipWhitespace() {for l.position < len(l.input) && unicode.IsSpace(l.peek()) {l.consume()}
}// 查看当前字符但不消耗
func (l *Lexer) peek() rune {if l.position >= len(l.input) {return 0}return rune(l.input[l.position])
}// 消耗当前字符并返回
func (l *Lexer) consume() rune {if l.position >= len(l.input) {return 0}char := rune(l.input[l.position])l.position++return char
}// 判断是否是运算符
func isOperator(r rune) bool {return r == '+' || r == '-' || r == '*' || r == '/'
}// 判断是否是分隔符
func isDelimiter(r rune) bool {return r == '(' || r == ')' || r == ';'
}func main() {input := "x = 123 + (y * 456);"lexer := NewLexer(input)for {token := lexer.NextToken()fmt.Println(token)if token.Type == EOF {break}}
}

代码解析

  1. Token类型定义

    • 使用TokenType枚举定义词法单元类型
    • Token结构体包含类型和实际值
  2. Lexer结构体

    • 保存输入字符串和当前位置
    • 提供NextToken()方法逐个生成词法单元
  3. 核心方法

    • readNumber():读取整数
    • readIdentifier():读取标识符
    • skipWhitespace():跳过空白字符
  4. 辅助方法

    • peek():查看当前字符但不移动指针
    • consume():读取当前字符并移动指针
    • isOperator()isDelimiter():判断字符类型

示例输出

对于输入"x = 123 + (y * 456);",输出结果为:

Token(IDENTIFIER, 'x')
Token(OPERATOR, '=')
Token(INTEGER, '123')
Token(OPERATOR, '+')
Token(DELIMITER, '(')
Token(IDENTIFIER, 'y')
Token(OPERATOR, '*')
Token(INTEGER, '456')
Token(DELIMITER, ')')
Token(DELIMITER, ';')
Token(EOF, '')

扩展建议

这个基础词法分析器可以进一步扩展:

  1. 添加浮点数支持
  2. 支持更多运算符(如==, !=等)
  3. 添加关键字识别
  4. 支持字符串字面量
  5. 添加注释处理
  6. 改进错误处理机制

这个实现展示了Go语言简洁高效的特点,适合作为更复杂词法分析器的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/899692.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

强制用户裸奔,微软封锁唯一后门操作

周末刚结束&#xff0c;那个常年将「用户为中心」挂嘴边的微软又双叒叕开始作妖&#xff01; 不错&#xff0c;大伙儿今后可能再没法通过「OOBE\BYPASSNRO」命令绕过微软强制联网要求了。 熟悉 Windows 11 操作系统的都知道&#xff0c;除硬件上诸多限制外&#xff1b; 软件层…

大模型备案:拦截关键词列表与敏感词库深度解析

随着《生成式人工智能服务管理暂行办法》正式实施&#xff0c;大模型上线备案成为企业合规运营的核心环节。其中&#xff0c;敏感词库建设与拦截关键词列表管理直接关系内容安全红线&#xff0c;今天我们就来详细解析一下大模型备案的这一部分&#xff0c;希望对想要做备案的朋…

快速上手Linux系统输入输出

一、管理系统中的输入输出 1.什么是重定向&#xff1f; 将原本要输出到屏幕上的内容&#xff0c;重新输入到其他设备中或文件中 重定向类型包括 输入重定向输出重定向 2.输入重定向 指定设备&#xff08;通常是文件或命令的执行结果&#xff09;来代替键盘作为新的输入设…

文小言全新升级!多模型协作与智能语音功能带来更流畅的AI体验

文小言全新升级&#xff01;多模型协作与智能语音功能带来更流畅的AI体验 在3月31日的百度AI DAY上&#xff0c;文小言正式宣布了一系列令人兴奋的品牌焕新与功能升级。此次更新不仅带来了全新的品牌视觉形象&#xff0c;更让文小言在智能助手的技术和用户体验方面迈上了一个新…

C++基础算法(插入排序)

1.插入排序 插入排序&#xff08;Insertion Sort&#xff09;介绍&#xff1a; 插入排序是一种简单直观的排序算法&#xff0c;它的工作原理类似于我们整理扑克牌的方式。 1.基本思想 插入排序的基本思想是&#xff1a; 1.将数组分为已排序和未排序两部分 2.每次从未排序部分…

k近邻算法K-Nearest Neighbors(KNN)

算法核心 KNN算法的核心思想是“近朱者赤&#xff0c;近墨者黑”。对于一个待分类或预测的样本点&#xff0c;它会查找训练集中与其距离最近的K个样本点&#xff08;即“最近邻”&#xff09;。然后根据这K个最近邻的标签信息来对当前样本进行分类或回归。 在分类任务中&#…

【Feign】⭐️使用 openFeign 时传递 MultipartFile 类型的参数参考

&#x1f4a5;&#x1f4a5;✈️✈️欢迎阅读本文章❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;本篇文章阅读大约耗时三分钟。 ⛳️motto&#xff1a;不积跬步、无以千里 &#x1f4cb;&#x1f4cb;&#x1f4cb;本文目录如下&#xff1a;&#x1f381;&#x1f381;&a…

zk基础—1.一致性原理和算法二

大纲 1.分布式系统特点 2.分布式系统的理论 3.两阶段提交Two-Phase Commit(2PC) 4.三阶段提交Three-Phase Commit(3PC) 5.Paxos岛的故事来对应ZooKeeper 6.Paxos算法推导过程 7.Paxos协议的核心思想 8.ZAB算法简述 6.Paxos算法推导过程 (1)Paxos的概念 (2)问题描述 …

216. 组合总和 III 回溯

目录 问题描述 解决思路 关键点 代码实现 代码解析 1. 初始化结果和路径 2. 深度优先搜索&#xff08;DFS&#xff09; 3. 遍历候选数字 4. 递归与回溯 示例分析 复杂度与优化 回溯算法三部曲 1. 路径选择&#xff1a;记录当前路径 2. 递归探索&#xff1a;进入下…

从AI大模型到MCP中台:构建下一代智能服务的核心架构

从AI大模型到MCP中台&#xff1a;构建下一代智能服务的核心架构 引言&#xff1a;AI大模型带来的服务重构革命 在ChatGPT掀起全球AI热潮的今天&#xff0c;大模型展现出的惊人能力正在重塑整个软件服务架构。但鲜为人知的是&#xff0c;真正决定AI服务成败的不仅是模型本身&a…

美团小程序 mtgsig1.2 拼好饭案例 分析 mtgsig

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 美团网页、小程序、app全是指…

【大模型基础_毛玉仁】5.5 模型编辑应用

目录 5.5 模型编辑应用5.5.1 精准模型更新5.5.2 保护被遗忘权5.5.3 提升模型安全 5.5 模型编辑应用 大语言模型面临更新成本高、隐私保护难、安全风险大等问题。模型编辑技术&#xff1a; 通过细粒度修改预训练模型&#xff0c;避免从头训练&#xff0c;降低更新成本&#xff…

揭秘:父子组件之间的传递

基础知识 组件与组件之间有三大方面的知识点&#xff1a; 子组件通过props defineProps&#xff08;{}&#xff09;接收父组件传递到参数和方法&#xff1b;子组件可以通过定义 emit 事件&#xff0c;向父组件发送事件&#xff1b;父组件调用子组件通过defineExpose 导出的方法…

微前端实现方案对比Qiankun VS npm组件

架构层面&#xff1a; 1、Qiankun是典型的微前端架构&#xff0c;侧重构建多个独立前端应用协同工作的架构&#xff0c;主应用负责自用用的加载、卸载和通信&#xff1b;子应用不限制&#xff0c;可以是VUE、React等&#xff1b; 2、Qiankun松耦合&#xff0c;各个自应用独立…

可编辑160页PPT | 营销流程和管理数字化转型规划

荐言分享&#xff1a;随着技术的发展和消费者行为的变化&#xff0c;传统营销方式已难以满足现代企业的需求。企业需要借助数字化手段&#xff0c;对营销流程进行全面梳理和优化&#xff0c;提升营销活动的精准度和效率。同时&#xff0c;通过数字化营销管理&#xff0c;企业可…

Ecovadis认证需要准备哪些材料?

Ecovadis认证&#xff0c;作为全球领先的企业社会责任&#xff08;CSR&#xff09;评估平台&#xff0c;其准备材料的过程不仅需要详尽无遗&#xff0c;更要体现出企业在环境、社会、劳工和伦理四大方面的卓越实践与持续改进的决心。 首先&#xff0c;环境管理方面&#xff0c…

程序化广告行业(45/89):RTB竞价后续流程、结算规则及相关要点解读

程序化广告行业&#xff08;45/89&#xff09;&#xff1a;RTB竞价后续流程、结算规则及相关要点解读 大家好&#xff01;一直以来&#xff0c;我都希望能和大家一起在程序化广告这个领域不断探索、共同成长&#xff0c;这也是我写这系列博客的初衷。之前我们了解了程序化广告…

权重参数矩阵

目录 1. 权重参数矩阵的定义与作用 2. 权重矩阵的初始化与训练 3. 权重矩阵的解读与分析 (1) 可视化权重分布 (2) 统计指标分析 4. 权重矩阵的常见问题与优化 (1) 过拟合与欠拟合 (2) 梯度问题 (3) 权重对称性问题 5. 实际应用示例 案例1&#xff1a;全连接网络中的…

文法 2025/3/3

文法的定义 一个文法G是一个四元组&#xff1a;G(,,S,P) &#xff1a;一个非空有限的终极符号集合。它的每个元素称为终极符号或终极符&#xff0c;一般用小写字母表示。终极符号是一个语言不可再分的基本符号。 &#xff1a;一个非空有限的非终极符号集合。它的每个元素称为…

字符串复习

344:反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 示例 1&#xff1a; 输入&#xff1a;s ["…