本篇文章内的源码: 这里
当我们自顶向下的语法分析时,就需要采用最左推导方式。
而这个时候,如果产生式左部和产生式右部首字符一样(即A→Aα),那么推导就可能陷入无限循环。
例如:
文法G
1. S -> Sa | b
推导
S => Sa => Saa => ... => Sa...a
因此对于:
A→Aα 形式产生式的文法称为是直接左递归。A,存在一步以上的推导,形成了 A =>+ Aα,称为间接左递归。
例如:
A→Bβ和B -> Aβ可以得到A => Bβ => Aββ
文法中不能包含这两种形式,不然最左推导就没办法进行。
那有人问了,如果产生式右部中间包含和产生式左部相同的字符,允不允许呢?
- 大多数情况下,是允许的,因为我们采用的是最左推导,只要这个产生式右部在这个非终结符之前有其他终结符(即
A→αA),那么就可以确定推导时,用不用这个产生。- 如果产生式右部在这个非终结符之前是非终结符(即
A→BAβ),那么就要看B能不能推导出空串ε了,如果可以,那么最终还是会推导出A =>+ Aβ, 也属于间接左递归。
例如:
文法G
1. S -> Sa | b
那么该如何改造呢?
其实我们仔细分析一个这组产生式,我们发现它可以进行如下推导:
S => b
或者
S => Sa
=> Saa
...
=> Sa...a
=> ba..a
你会惊奇的发现,它能推导出 b 和 (a)* (即由0个a或者无数个a生成的文法符号串)。其实就可以改造成:
文法G
1. S -> bS'
2. S' -> aA' | ε
S'就可以表示 (a)* ,因此S'必须有空产生式S' -> ε,否则就得不到0个a的空串。
因此消除直接左递归 算法的一般形式:
A -> Aα1 | Aα2 | ... | Aαn | β1 | β2 | ... | βm
(其中这里数字表示下标,而不是一个终结符, α 和 β都是文法符号串)
(αi != ε, 且 βi 不以A开头)
改造成
A -> β1A' | β2A' | ... | βmA'
A' -> α1A' | α2A' | ... | αnA' | ε
例如:
文法G:
1. S -> Aa | b
2. A -> c | Sd
间接左递归
S => Aa
=> Sda
=> Aada
=> Sdada
消除间接左递归的方法就是直接带入消除,即
文法G:
1. S -> Aa | b
2. A -> c | Aad | bd
即将能形成间接左递归产生式中的非终结符,替换成这个非终结符的产生式组。
因此消除间接左递归 算法的一般形式:
按照某个顺序将非终结符进行排序,为A1,A2,...,An
for (从1到n的每个i) {
for (从1到i-1的每个j) {
将每个形如Ai -> Ajβ的产生式,用Aj 的产生式组Aj -> α1 | α2 | ... | αk 替换;
得到产生式 Ai -> Ajβ 替换后的产生式组 Ai -> α1β | α2β | ... | αkβ。
}
}
这个算法看起来描述很多,其实理解起来很简单:
Ai的每一个产生式右部首字符,是不是前面已经遍历过的非终结符Aj 。
这个就是为什么需要对非终结符进行排序
Aj的产生式组当前非终结符Ai这个产生式中的非终结符Aj,这样当前这个产生式变成了多个产生式了。Symbol 类/**
* @author by xinhao 2021/8/13
* 表示文法中字母表中一个符号,包括终结符和非终结符
*/
public class Symbol implements Comparable<Symbol> {
public static final String EPSILON = "ε";
// 文法结束符号
public static final Symbol END = new Symbol("$", true);
// 表示符号的值, 这里用 String 表示,而不是 char,
// 是因为有些符号我们不好用一个 char 表示。 比如 A 对应的 A'
private final String label;
private final boolean isTerminator;
// 外部不能创建
Symbol(String label, boolean isTerminator) {
this.label = label;
this.isTerminator = isTerminator;
}
public String getLabel() {
return label;
}
public boolean isTerminator() {
return isTerminator;
}
// 这里只判断 label 值
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Symbol symbol = (Symbol) o;
return Objects.equals(label, symbol.label);
}
@Override
public int hashCode() {
return Objects.hash(label);
}
@Override
public String toString() {
return this.label;
}
@Override
public int compareTo(Symbol o) {
return label.compareTo(o.label);
}
}
用来表示文法中字母表中一个符号,包括终结符和非终结符
Alphabet 类/**
* @author by xinhao 2021/8/13
* 字母表, 这个类不用实例化。用来记录字母表中所有的符号
*/
public abstract class Alphabet {
/**
* 字母表中所有的符号
*/
public static final Map<String, Symbol> ALL_SYMBOL = new HashMap<>();
// 初始化
static {
for (int index = 0; index <= 9; index++) {
ALL_SYMBOL.put("" + index, new Symbol("" + index, true));
}
// a-z
for (char ch = 97; ch <= 122; ch++) {
ALL_SYMBOL.put(String.valueOf(ch), new Symbol(String.valueOf(ch), true));
}
// A - Z
for (char ch = 65; ch <= 90; ch++) {
ALL_SYMBOL.put(String.valueOf(ch), new Symbol(String.valueOf(ch), false));
}
ALL_SYMBOL.put("+", new Symbol("+", true));
ALL_SYMBOL.put("-", new Symbol("-", true));
ALL_SYMBOL.put("*", new Symbol("*", true));
ALL_SYMBOL.put("(", new Symbol("(", true));
ALL_SYMBOL.put(")", new Symbol(")", true));
}
public static Symbol addSymbol(char ch) {
return addSymbol(ch, true);
}
public static Symbol addSymbol(char ch, boolean isTerminator) {
return addSymbol(String.valueOf(ch), isTerminator);
}
public static Symbol addSymbol(String label) {
return addSymbol(label, true);
}
public static Symbol addSymbol(String label, boolean isTerminator) {
Symbol symbol = new Symbol(label, isTerminator);
ALL_SYMBOL.put(label, symbol);
return symbol;
}
public static Symbol getSymbol(char ch) {
return getSymbol(String.valueOf(ch));
}
public static Symbol getSymbol(String label) {
return ALL_SYMBOL.get(label);
}
}
表示字母表,用来记录字母表中所有的符号。
Production 类/**
* @author by xinhao 2021/8/13
* 文法中的产生式
*/
public class Production {
public static final List<Symbol> EMPTY = new ArrayList<>(0);
/** 产生式左边,而且必须是非终结符 */
private final Symbol left;
/** 这里的 List<Symbol> 希望是不可变的,你们可以自己引入 ImmutableList */
private final List<Symbol> right;
/** 表示这个产生式不是左递归产生式 */
private final boolean isLeftEliminate;
....
}
用来表示文法中的一个产生式,例如 A -> bB。
Grammar 类public class Grammar {
// 开始符号
private final Symbol start;
/**
* 这里为什么用 map 类型?
* S -> aA | bB
* A -> aaaa
* A -> abab | ε
* B -> bbb
* B -> bababa
* B -> ε
* 所以对于同一个左部 Symbol (A) 其实是有多个产生式的,就是产生式租
* 使用 LinkedHashMap ,因为产生式是要有记录顺序的
*/
private final LinkedHashMap<Symbol, List<Production>> productions;
...
}
表示一个文法,包含文法开始符号和产生式组,终结符号集合非终结符号集没有包含在这个里面。
public class Production {
......
public boolean isLeftEliminate() {
if (right.isEmpty()) {
return false;
}
// 产生式左部 与 产生式第一个字母相等,表示它是一个直接递归的产生式。
return left.equals(right.get(0));
}
}
/**
* 消除直接左递归
* 就是将这种格式 P -> Pa|Pb|Pc|Pd|e|f|g|h
* 转成:
* P -> eP'|fP'|gP'|hP'
* p'-> aP'|bP'|cP'|dP'|ε
*
*/
public static Grammar eliminateDeepLeftRecursion(Grammar grammar) {
List<Production> productions = new ArrayList<>();
for (Map.Entry<Symbol, List<Production>> entry : grammar.getProductions().entrySet()) {
// 不包含直接左递归的 产生式集合
List<Production> noLeftEliminateProductions = new ArrayList<>();
// 包含直接左递归的 产生式集合; 即 A -> Aa 这种形式
List<Production> leftEliminateProductions = new ArrayList<>();
for (Production production : entry.getValue()) {
if (production.isLeftEliminate()) {
leftEliminateProductions.add(production);
} else {
noLeftEliminateProductions.add(production);
}
}
// 表示这个 entry.getKey() 对应的产生式组 List<Production> 没有直接左递归产生式
if (leftEliminateProductions.isEmpty()) {
// 不用做任何修改,直接将 noLeftEliminateProductions 添加到 productions
productions.addAll(noLeftEliminateProductions);
} else {
// 需要进行转换了, 即 A 变成 A'
Symbol newKey = Alphabet.addSymbol(entry.getKey().getLabel() + "'", false);
// 将原来的 P -> e|f|g|h
// 变成 P -> eP'|fP'|gP'|hP'
for (Production production : noLeftEliminateProductions) {
// 创建新的产生式右部 e 变成 eP' 或者 f 变成 fP'
List<Symbol> newRight = production.addSymbolToRight(newKey);
// 这个产生式的 left 还是 原来的
productions.add(Production.create(entry.getKey(), newRight));
}
// 将原来的 P -> Pa|Pb|Pc|Pd
// 变成 P -> aP'|bP'|cP'|dP'|ε
for (Production production : leftEliminateProductions) {
// 创建新的产生式右部 Pa 变成 aP' 或者 Pd 变成 dP'
List<Symbol> newRight = production.addSymbolToRightAndRemoveFirst(newKey);
// 这个产生式的左部,就必须是新的 key 了
productions.add(Production.create(newKey, newRight));
}
// 最后对这个新左部,必须添加 空产生式
productions.add(Production.createEmpty(newKey));
}
}
return Grammar.create(grammar.getStart(), productions);
}
其实算法很简单,就是遍历文法中,每一个非终结符对应的产生式组,查看是否包含直接左递归的形式,如果包含就将它变成两个产生式组:
P -> Pa|Pb|Pc|Pd|e|f|g|h
变成:
P -> eP'|fP'|gP'|hP'
p'-> aP'|bP'|cP'|dP'|ε
/**
* 消除间接左递归
* 如同 S -> Aa|b A -> Ac|Sd
* 会产生 S ==> Aa ==> Sda
* 消除间接左递归变成
* S -> Aa|b
* A -> Ac|Aad|bd
*/
public static Grammar eliminateIndirectLeftRecursion(Grammar grammar) {
// 记录之前遍历的 产生式
LinkedHashMap<Symbol, List<Production>> preProductionsMap = new LinkedHashMap<>();
for (Map.Entry<Symbol, List<Production>> entry : grammar.getProductions().entrySet()) {
List<Production> entryProductions = new ArrayList<>();
for (Production production : entry.getValue()) {
// 得到产生式右部第一个字符,例如 A -> Sd 其中 S 就是第一个字符
Symbol symbol = production.getRightFirst();
// 这个产生式右部第一个字符之前出现过,例如 S -> Aa A -> Sd ,这个 S 就在之前出现过,
// 那么就产生了间接左递归,那么就需要做转换了。就使用 S 的产生式组,替换它在A的中产生式。
// symbol = null 没有关系,因为我们不会将 null 存入 preProductionsMap 集合中
if (preProductionsMap.containsKey(symbol)) {
// 将 symbol 添加到 leftEliminateSet 集合中,表示它是间接左递归
List<Production> eliminateList = preProductionsMap.get(symbol);
for (Production eliminateProduction : eliminateList) {
// 例如 S -> Aa|b A -> Ac|Sd,
// 这里 eliminateList 就是 [Aa, b], 要用它替换产生式[Sd],变成 [Aad, bd]
entryProductions.add(Production.create(production.getLeft(),
production.replaceRightFirst(eliminateProduction)));
}
} else {
// 不是间接左递归,那么就直接添加
entryProductions.add(production);
}
}
preProductionsMap.put(entry.getKey(), entryProductions);
}
return Grammar.create(grammar.getStart(), preProductionsMap);
}
算法其实也很简单,按照一定顺序,遍历文法中非终结符对应的产生式组,如果发现有产生式右部的首字符与之前遍历过的非终结符相同,说明有间接左递归现象,那么就要这个非终结符对应的产生式组替换这个非终结符。
但是注意这里判断是否有间接左递归,只是靠产生式右部的首字符与之前遍历过的非终结符相同;其实这个是不完整的。
- 例如有三个产生式
A -> BB -> CAC -> ε,那么非终结符B是可以推导出B => CA => A的,所以这种情况也属于间接左递归。- 所以判断间接左递归,不止要看首字符,还要包括产生式右部包含前面出现的字符(即
B -> αA),那么文法符号串α的串首终结符集FIRST(α)是否包含空串 ε。这个算法以后再介绍。
public static void main(String[] args) {
Symbol start = Alphabet.getSymbol('S');
List<Production> productions = new ArrayList<>();
productions.addAll(Production.createList(start, "Aa", "b", "ε"));
productions.addAll(Production.createList(Alphabet.getSymbol('A'), "Ac", "Sd"));
Grammar grammar = Grammar.create(start, productions);
Grammar indirectGrammar = GrammarUtils.eliminateIndirectLeftRecursion(grammar);
Grammar deepGrammar = GrammarUtils.eliminateDeepLeftRecursion(indirectGrammar);
System.out.println(grammar);
System.out.println(indirectGrammar);
System.out.println(deepGrammar);
}
运行结果:
开始符号:S
产生式:
S->Aa|b|ε
A->Ac|Sd
开始符号:S
产生式:
S->Aa|b|ε
A->Ac|Aad|bd|d
开始符号:S
产生式:
S->Aa|b|ε
A->bdA'|dA'
A'->cA'|adA'|ε
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.