28、Ruby 外部 DSL 与 Gem 打包全解析

Ruby 外部 DSL 与 Gem 打包全解析

1. Ruby 外部 DSL 的魅力与挑战

1.1 Treetop 助力语法解析

Treetop 能让我们清晰地描述语法,避免被解析的细枝末节所困扰。使用 Treetop 时,先将语言描述存储在以 .tt 结尾的文件中,例如 ez_ripper_statement.tt ,然后通过 treetop 编译器运行:

tt ez_ripper_statement.tt

运行此命令后,Treetop 会创建一个名为 ez_ripper_statement.rb 的文件,并在其中生成一个 EzRipperStatementParser 类,该类可用于解析 EzRipper 语句。之后就可以像使用普通 Ruby 代码一样使用它:

require 'treetop'
require 'ez_ripper_statement'
statement =  "replace '/document/author' 'Russ Olsen'"
parser = EzRipperStatementParser.new
parse_tree = parser.parse( statement )

运行这段代码,语句会被解析成树形结构,方便我们进行后续处理。

1.2 外部 DSL 与内部 DSL 的对比

外部 DSL 和内部 DSL 的优缺点是相互对立的。内部 DSL 能免费使用 Ruby 的所有功能,如注释、循环、 if 语句和变量等;而外部 DSL 则需要为每个功能付出努力,至少要进行解析。以 HAML 为例,它是一种简洁的 HTML 模板语言,代码如下:

%html
%body
#main
Today is
= Time.new

运行后会生成如下 HTML 代码:

<html>
<body>
<div id='main'>
Today is
2010-09-19 15:10:01 -0400
</div>
</body>
</html>

HAML 的解析依赖正则表达式和一些巧妙的手工代码。以下是 HAML 的 parse_line 方法的部分代码:

def process_line(text, index)
  @index = index + 1
  case text[0]
  when DIV_CLASS; render_div(text)
  when DIV_ID
    return push_plain(text) if text[1] == ?{
    render_div(text)
  when ELEMENT; render_tag(text)
  when COMMENT; render_comment(text[1..-1].strip)
  when SANITIZE
    return push_plain(text[3..-1].strip,
                      :escape_html => true) if text[1..2] == "=="
    return push_script(text[2..-1].strip,
                      :escape_html => true) if text[1] == SCRIPT
    return push_flat_script(text[2..-1].strip,
                      :escape_html => true) if text[1] == FLAT_SCRIPT
    return push_plain(text[1..-1].strip,
                      :escape_html => true) if text[1] == ?\s
    push_plain text
    # and on and on and on...
  end
end

可以看出,编写 HAML 解析器需要付出很大的努力,但结果是值得的。在开始构建外部 DSL 之前,需要问自己:这种语言是否值得花费这些精力?

1.3 内外 DSL 界限模糊

HAML 表明内部 DSL 和外部 DSL 的界限并不清晰。在 HAML 示例中,我们可以直接使用普通的 Ruby 代码,如 Time.new 。在 EzRipper 中也可以添加新命令来执行任意 Ruby 代码,例如:

execute '/document/author' 'puts "the author is #{el.text}"'

只需要在 EzRipper parse_statement 方法中添加几行代码实现:

when /execute\s+'(.*?)'\s+'(.*?)'$/
  @ripper.on_path( $1 ) { |el| eval( $2 ) }

这种 execute 语句为外部 DSL 打开了通往内部 Ruby 代码的大门。

1.4 现实中的外部 DSL 案例

1.4.1 ERB 模板引擎

在 HAML 出现之前,大多数 Rails 应用使用 ERB 进行模板处理。例如:

Today is <%= Time.new %>

运行后会得到类似如下的结果:

Today is 2009-10-18 00:25:35 -0400

ERB 使用 String split 方法和正则表达式来分割输入。它定义的正则表达式如下:

SplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>)|(\n)/
1.4.2 Cucumber 测试工具

Cucumber 结合了外部 DSL 和内部 DSL。它允许我们用结构化的自然语言编写验收测试,例如:

Feature: Count words in a document
In order to be sure that documents hold on to their content
Start with an empty document and add some text to it
and check to see that the text is actually there
Scenario:
Given that we have a document with 1000 words
When I count the words
Then the count should be 1000

我们可以将这种自然语言描述转化为可执行的测试。为此,需要创建“步骤描述”,使用内部 DSL 编写:

Given /^that we have a document with (\d+) words$/ do |n|
  @document = Document.new( 'russ', 'a test' )
  @document.content = 'crypozoology ' * n.to_i
end
When /^I count the words$/ do
  @count = @document.word_count
end
Then /^the count should be (\d+)$/ do |n|
  @count.should == n.to_i
end

Cucumber 会使用步骤描述中的正则表达式将其与功能描述结合,最终得到一个既像自然语言规范又可执行的测试。

1.4.3 Treetop 自身

Treetop 是一个具有复杂解析器的外部 DSL 示例。它的解析器使用 Treetop 自身编写,以下是 Treetop 规则的规则:

rule parsing_rule
  'rule' space nonterminal space ('do' space)?
  parsing_expression space 'end' <ParsingRule>
end

1.5 外部 DSL 的总结

外部 DSL 形式多样,从简单的字符串处理到使用正则表达式,再到借助解析器生成工具(如 Treetop)。无论简单还是复杂,外部 DSL 能摆脱 Ruby 语法的限制,但也失去了免费的 Ruby 解析器。在构建 DSL 时,需要在内部 DSL 的便捷低成本和外部 DSL 的高成本与高自由度之间做出选择。

1.6 外部 DSL 相关要点总结表格

要点 详细内容
Treetop 使用流程 1. 编写 .tt 文件;2. 运行 tt 命令;3. 使用生成的解析器类
外部 DSL 与内部 DSL 对比 内部 DSL 免费使用 Ruby 功能,外部 DSL 需自行解析
HAML 特点 简洁的 HTML 模板语言,解析依赖正则表达式和手工代码
内外 DSL 融合 可在外部 DSL 中执行内部 Ruby 代码
现实案例 ERB、Cucumber、Treetop

1.7 外部 DSL 处理流程 mermaid 流程图

graph TD;
    A[编写外部 DSL 代码] --> B[选择解析方式];
    B --> C{简单处理};
    C -- 是 --> D[使用字符串方法];
    C -- 否 --> E{正则表达式};
    E -- 是 --> F[使用正则解析];
    E -- 否 --> G{使用工具};
    G -- 是 --> H[Treetop 等工具];
    G -- 否 --> I[手工编写解析器];
    D --> J[解析完成];
    F --> J;
    H --> J;
    I --> J;

2. Ruby 宝石(Gems)的使用与创建

2.1 宝石的消费

如果你使用 Ruby 编程有一段时间了,很可能已经是宝石的消费者。例如, ruby - mp3info 宝石可以让你读写 MP3 文件中的信息标签。使用步骤如下:
1. 安装宝石:

gem install ruby - mp3info

在 Unix、Linux 或 OS X 系统上,可能需要使用 sudo 命令:

sudo gem install ruby - mp3info
  1. 使用宝石:
require 'mp3info'
Mp3Info.open( 'money.mp3' ) do |info|
  puts "title: #{info.tag.title}"
  puts "artist: #{info.tag.artist}"
  puts "album: #{info.tag.album}"
end

2.2 宝石的版本管理

宝石系统的一个重要特性是完整的版本管理支持。每个宝石都有版本号,大多数宝石存在多个版本。可以使用 gem list 命令查看某个宝石的可用版本:

gem list -a --remote ruby - mp3info

安装宝石时,默认会安装最新版本。如果需要指定版本,可以使用 --version 选项:

gem install --version 0.4 ruby - mp3info

系统可以同时安装同一个宝石的多个版本。在代码中,如果需要指定特定版本的宝石,可以使用 gem 方法:

gem 'ruby - mp3info', '=0.5'
require 'mp3info'

gem 方法的版本号参数还支持更通用的表达式,如 '>0.4' '<=0.5'

2.3 宝石的技术原理

RubyGems 的技术原理相对简单。宝石开发者将代码打包成一个标准化的存档文件,其中不仅包含代码,还包含大量有用的元数据,如宝石版本号和依赖的其他宝石。打包好的宝石文件上传到知名的仓库, gem install 命令会从这里获取宝石。

宝石文件实际上是一个 TAR 文件,打开后会发现里面包含两个压缩的 TAR 文件。第一个内部 TAR 文件包含宝石的实际内容,如 README 文件、Ruby 源文件和可执行文件;第二个内部 TAR 文件包含元数据。

安装宝石时,Ruby 会将其解压到一个指定的目录,之后在执行 require load 时会搜索该目录。

2.4 宝石的创建

创建宝石主要需要完成两件关键的事情:

2.4.1 组织项目目录

项目目录结构需要遵循标准的宝石布局。以 document 宝石为例,目录结构如下:

document/
├── Rakefile
├── README
├── document.gemspec
├── lib/
│   └── document.rb
└── spec/
    └── document_spec.rb
  • 顶级目录名与宝石名相同。
  • README 文件用于提供宝石的说明信息。
  • lib 目录用于存放 Ruby 代码,通常主 Ruby 文件的名称与宝石名相同,方便用户使用 require 'document' 引入宝石。
  • spec 目录用于存放单元测试文件。

如果宝石比较复杂,包含多个源文件,应在 lib 目录下创建一个与宝石名相同的子目录,并将代码放在该子目录中。例如 text 宝石的目录结构:

text/
├── Rakefile
├── README.rdoc
├── text.rb
├── lib/
│   └── text/
│       ├── metaphone.rb
│       ├── soundex.rb
│       └── ...
└── test/
    ├── test_metaphone.rb
    ├── test_soundex.rb
    └── ...

lib 目录下的 text.rb 文件通常会引入子目录中的文件:

require 'text/util'
require 'text/double_metaphone'
require 'text/levenshtein'
require 'text/metaphone'
require 'text/porter_stemming'
require 'text/soundex'
require 'text/version'
2.4.2 创建宝石规格文件(gemspec)

宝石规格文件是一个包含 Ruby 代码的文件,用于创建 Gem::Specification 类的实例。以下是 document 宝石的 gemspec 文件示例:

Gem::Specification.new do |s|
  s.name = "document"
  s.version = "1.0.1"
  s.authors = ["Russ Olsen"]
  s.date = %q{2010 - 01 - 01}
  s.description = 'Document - Simple document class'
  s.summary = s.description
  s.email = 'russ@russolsen.com'
  s.files = ['README', 'lib/document.rb','spec/document_spec.rb']
  s.homepage = 'http://www.russolsen.com'
  s.has_rdoc = true
  s.rubyforge_project = 'simple_document'
end

如果宝石依赖其他宝石,可以在 gemspec 文件中添加依赖信息,例如:

s.add_dependency('text')

2.5 宝石创建步骤总结列表

  1. 组织项目目录,遵循标准宝石布局。
  2. 创建 gemspec 文件,定义宝石的元数据和依赖信息。

2.6 宝石相关要点总结表格

要点 详细内容
宝石消费 1. 安装宝石;2. 使用 require 引入宝石
宝石版本管理 使用 gem list 查看版本, gem install --version 指定版本安装, gem 方法指定版本使用
宝石技术原理 打包成 TAR 文件,包含代码和元数据,上传到仓库
宝石创建 1. 组织项目目录;2. 创建 gemspec 文件

2.7 宝石创建流程 mermaid 流程图

graph TD;
    A[确定宝石名称] --> B[创建项目目录];
    B --> C[编写 Ruby 代码];
    C --> D[编写单元测试];
    D --> E[创建 gemspec 文件];
    E --> F[定义元数据和依赖];
    F --> G[打包宝石];
    G --> H[上传到仓库];

综上所述,无论是 Ruby 外部 DSL 还是宝石(Gems),都为 Ruby 开发者提供了强大的工具和灵活的选择。在实际开发中,我们可以根据具体需求选择合适的技术方案,充分发挥 Ruby 的优势。

内容概要:本文围绕六自由度机械臂的人工神经网络(ANN)设计展开,重点研究了正向逆向运动学求解、正向动力学控制以及基于拉格朗日-欧拉法推导逆向动力学方程,并通过Matlab代码实现相关算法。文章结合理论推导仿真实践,利用人工神经网络对复杂的非线性关系进行建模逼近,提升机械臂运动控制的精度效率。同时涵盖了路径规划中的RRT算法B样条优化方法,形成从运动学到动力学再到轨迹优化的完整技术链条。; 适合人群:具备一定机器人学、自动控制理论基础,熟悉Matlab编程,从事智能控制、机器人控制、运动学六自由度机械臂ANN人工神经网络设计:正向逆向运动学求解、正向动力学控制、拉格朗日-欧拉法推导逆向动力学方程(Matlab代码实现)建模等相关方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握机械臂正/逆运动学的数学建模ANN求解方法;②理解拉格朗日-欧拉法在动力学建模中的应用;③实现基于神经网络的动力学补偿高精度轨迹跟踪控制;④结合RRTB样条完成平滑路径规划优化。; 阅读建议:建议读者结合Matlab代码动手实践,先从运动学建模入手,逐步深入动力学分析神经网络训练,注重理论推导仿真实验的结合,以充分理解机械臂控制系统的设计流程优化策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值