这几周在实现一个简单词法器的过程中所学的

  我觉得这几周学到了很多和以前所知不太相同的东西,大致记录一下。

  最主要的一个方面,源自下面这段代码:

    type stateFn func(*lexer) stateFn

for state := startState; state != nil; {
state = state(lexer)
}
(这里是YouTube视频地址:http://youtu.be/HxaD_trXwRE
演讲的稿子在这里:http://rspace.googlecode.com/hg/slide/lex.html

  这段代码是用go语言写的(go语言在大部分情况下句尾无需加分号,for语句不需要加括号),下面是用类C的语法写的版本(注意:下面的代码不能执行):

    typedef stateFn (*stateFn)(lexer*);

for (stateFn state = startState; state != NULL;) {
state = state(lexer);
}

  单从语法上看,对我而言有两点很特别:

  1. 递归的函数类型定义。(C好像无法做到,C++可以通过特定技巧实现(链接:http://www.gotw.ca/gotw/057.htm))在这里,它这样定义了stateFn:返回stateFn类型的函数就是stateFn函数类型;当然,这个函数类型还得接受一个指向lexer类型的指针作为参数(典型的用C来实现OO设计的方法,本篇中暂不讨论)。
  2. 原来类似函数指针的机制能实现这样的程序逻辑表达。把函数看作普通变量,为其定义一个类型,就能把所有符合该类型定义的函数……同等对待。

  要体会这段代码的妙处,先要了解它的用处,最好还要像我这样体会没用它的难处,呵呵。

  用处:

  既然使用在词法器里,脑子里回忆起编译原理书上的随便一个DFA图(DFA:确定有限自动机)。针对词法器,最简单的,就是从起始状态开始,读入一个字符,跳到下一个状态,再读入一个字符,跳到下一个状态,……,最终跳到终止状态,整段代码就被读入了。

  实现中,往往使用一个特定的函数(比如:readNumber())来处理当前的状态(比如:发现读入了一个数字类型的起始字符)。那么,因为DFA里有多个状态,往往就添加多个这样的函数来对每个状态分别进行操作。

  显然,状态之间需要有完善的跳转机制,而机制就实现在这些函数里面。遇到什么字符的时候跳?跳到哪里?上面这段代码的用处就是从最抽象的层面表达了状态跳转的机制。

  我所遇到的难处:

  关于跳转到哪里,我想到两种设计。举个例子说明,假设当前程序运行在readNumber()里,也就是说,词法器处在读取数字的状态;假设readNumber()里有一个循环,每读入一个字符就判断其是否属于数字;当该循环读入一个不属于数字的字符时,要么跳转到DFA的起始状态,交由其判断逻辑来决定接下来是跳到操作符,还是跳到变量名……,要么直接在readNumber()内部实现判断逻辑,直接跳转到另一个内部状态。

  两种设计各有利弊。前者在不使用额外状态变量的情况下会导致在起始状态做不必要的判断;比如,如果是从readNumber()跳出,那么在起始状态的函数里就不需要再对其是否属于数字做判断。而后者将迫使实现中为每个状态函数添加有很大重复的判断逻辑代码,不好维护,特别是如果要在未来添加一个状态的话,就需要在所有状态函数中都添加其判断逻辑,牵一发而动全身;极端的实现例子,就是我的goto版了。

  两种设计我都尝试了,但总觉得缺了点什么。

  嘿嘿,妙处:
  1. “状态”是一个名词(英文:state),按照平常的思维,名词常可对应OO设计里的一个对象;而这里,状态在程序中的实质是函数,它是一段运行逻辑,不是一个数据对象。我在看到上面那段代码之前,只想到这儿。我不知道,原来函数可以和对象一样,被归在一个类型(stateFn)里。
  2. 词法器用的是DFA的模型,也就是说,它的结构是图,而且其图中的状态节点在逻辑上还有一定的层次之分。我在看到上面那段代码之前,也只想到这儿。钻在细节里的时候,我没想到,退一步看,所有的状态其实都是一样的:无论当前在哪个状态,它在停止前总会遇到一个跳转,然后跳到另一个状态。
  3. 结合上面两点思路,就导致了这段代码的设计。为所有状态函数定义一个类型(stateFn),让它们返回所要跳转到的下一个状态函数,把“进入状态->状态运行->跳转进入下一个状态”的过程放到for循环里面;这个循环结构,就像齿轮,做着最简单的旋转,却能带动各种各样的机械运动。
  4. 注意,其实这并没有解决上面提到的两种设计各有利弊的问题!这段代码所带来的好处在于为这个词法器实现了一个异常简单的操作接口,具体下面的状态函数之间的跳转关系到底怎么样,被完全规避了;这样,其实我就可以随便选择两种设计的平衡,而不用去担心给“用户”添加理解上的困难。

  这篇写到这里先吧,另加一句,我用C++实现了这个结构,用g++编译器-O3优化生成的程序就比goto版稍微慢一点点:)用函数指针其实和用goto在逻辑上有很大相似性的。

  注:上面提到C++可以使用特殊技巧实现递归的函数类型定义,下面是代码:

    struct stateFn;
typedef stateFn (*stateFP)();
struct stateFn {
stateFn(stateFP fp) : p(fp) {}
operator stateFP() { return p; } // 提供从stateFn到stateFP的隐式类型转换
stateFP p;
};

转载于:https://www.cnblogs.com/WangChuan/archive/2012/04/14/stateFn.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值