var ntimes = 0;
print "How much do you love this company? (1-10) ";
read_int ntimes;
var x = 0;
for x = 0 to ntimes do
print "Developers!";
end;
print "Who said sit down?!!!!!";
您可以将此示例程序与语言定义进行比较,从而更好地理解语法的工作原理。到这里为止,语言定义就完成了。
高级体系结构
编译器的任务就是将编程人员创建的高级任务转换为计算机处理器可以理解和执行的任务。换句话说,编译器将编译用“Good for Nothing”语言编写的程序并将该程序转换为 .NET CLR 可以执行的程序。编译器可以通过一系列转换步骤(将语言分为我们关心的部分并抛弃我们不需要的内容)来实现这一操作。编译器遵循常规软件设计原则,将称为“阶段”的松散耦合组件组合在一起以执行转换步骤。
图 2 显示了执行编译器的各阶段的组件:扫描器、分析器和代码生成器。在每个阶段中,对语言进行了进一步的分解,有关程序目标的信息将在下一阶段介绍。
0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://i.msdn.microsoft.com/cc136756.fig02.gif">
public abstract class Stmt
{
}
// var =
public class DeclareVar : Stmt
{
public string Ident;
public Expr Expr;
}
// print
public class Print : Stmt
{
public Expr Expr;
}
// =
public class Assign : Stmt
{
public string Ident;
public Expr Expr;
}
// for = to do end
public class ForLoop : Stmt
{
public string Ident;
public Expr From;
public Expr To;
public Stmt Body;
}
// read_int
public class ReadInt : Stmt
{
public string Ident;
}
// ;
public class Sequence : Stmt
{
public Stmt First;
public Stmt Second;
}
/* :=
* |
* |
* |
*/
public abstract class Expr
{
}
// := " * "
public class StringLiteral : Expr
{
public string Value;
}
// := +
public class IntLiteral : Expr
{
public int Value;
}
// := *
// := |
public class Variable : Expr
{
public string Ident;
}
// :=
public class ArithExpr : Expr
{
public Expr Left;
public Expr Right;
public BinOp Op;
}
// := + | - | * | /
public enum ArithOp
{
Add,
Sub,
Mul,
Div
}
“Good for Nothing”语言定义的快速概览显示了与来自 EBNF 语法的语言定义节点松散匹配的 AST。虽然抽象语法树会捕获这些元素的结构,但在封装语法时最好考虑一下语言定义。
可以通过多种算法进行分析,但探讨算法不在本文论述范围之内。总之,这些算法在遍历标记流以创建 AST 方面不同。在“Good for Nothing”编译器中,我使用了名为 LL(从左到右、最左派生)的自上而下的分析器。这仅意味着从左到右读取文本,并根据下一个可用输入标记构造 AST。
即使是经验最丰富的编译器黑客,也有可能在代码生成级别出错。最常见的故障是 IL 代码出错,这会导致堆栈中出现不平衡。如果发现 IL 出错(在加载程序集或对 IL 执行 JIT 时,具体取决于程序集的信任级别),则 CLR 通常将引发异常。使用名为 peverify.exe 的 SDK 工具可以方便地诊断和修复这些错误。该工具将对 IL 进行验证,确保代码正确并且可以安全地执行。
例如,下面显示了一些尝试将数字 10 与字符串“bad”相加的 IL 代码:
复制代码
ldc.i4 10
ldstr "bad"
add
对包含此错误 IL 的程序集运行 peverify 将导致发生以下错误:
复制代码
[IL]: Error: [C:/MSDNMagazine/Sample.exe : Sample::Main][offset 0x0000002][found ref 'System
.String'] Expected numeric type on the stack.
private void GenStmt(Stmt stmt)
{
if (stmt is Sequence)
{
Sequence seq = (Sequence)stmt;
this.GenStmt(seq.First);
this.GenStmt(seq.Second);
}
else if (stmt is DeclareVar)
{
...
}
else if (stmt is Assign)
{
...
}
else if (stmt is Print)
{
...
}
}
DeclareVar(用于声明一个变量)AST 节点的代码如下所示:
复制代码
else if (stmt is DeclareVar)
{
// declare a local
DeclareVar declare = (DeclareVar)stmt;
this.symbolTable[declare.Ident] =
this.il.DeclareLocal(this.TypeOfExpr(declare.Expr));
// set the initial value
Assign assign = new Assign();
assign.Ident = declare.Ident;
assign.Expr = declare.Expr;
this.GenStmt(assign);
}
这里需要完成的第一件事情是向符号表中添加变量。符号表是核心编译器数据结构,用于将符号标识符(在这种情况下为基于字符串的变量名称)与其类型、位置和程序中的范围相关联。“Good for Nothing”符号表很简单,因为所有变量声明都位于 Main 方法中。因此我使用简单的 Dictionary 将符号与 LocalBuilder 关联起来。
图 12 演示了如何将变量分配给 Store 方法中的表达式。通过符号表查询变量名称,然后将对应的 LocalBuilder 传递给 stloc IL 指令。这只会从堆栈弹出当前表达式并将表达式分配给本地变量。
Figure 12 存储变量表达式
复制代码
private void Store(string name, Type type)
{
if (this.symbolTable.ContainsKey(name))
{
Emit.LocalBuilder locb = this.symbolTable[name];
if (locb.LocalType == type)
{
this.il.Emit(Emit.OpCodes.Stloc, this.symbolTable[name]);
}
else
{
throw new Exception("'" + name + "' is of type " +
locb.LocalType.Name + " but attempted to store value of type " +
type.Name);
}
}
else
{
throw new Exception("undeclared variable '" + name + "'");
}
}
用于为 Print AST 节点生成 IL 的代码很有意义,因为在 BCL 方法中调用了该代码。将在堆栈上生成该表达式,而 IL 调用指令用于调用 System.Console.WriteLine 方法。Reflection 用于获取传递给调用指令所需的 WriteLine 方法句柄:
复制代码
else if (stmt is Print)
{
this.GenExpr(((Print)stmt).Expr, typeof(string));
this.il.Emit(Emit.OpCodes.Call,
typeof(System.Console).GetMethod("WriteLine",
new Type[] { typeof(string) }));
}
这里最复杂的代码就是为“Good for Nothing”for 循环生成 IL 的代码(参见
图 13)。这与商业编译器生成此种类型代码的方法很类似。但是,解释 for 循环最好的方式是查看生成的 IL(
图 14 中所示)。
Figure 14 For 循环 IL 代码
复制代码
// for x = 0
IL_0006: ldc.i4 0x0
IL_000b: stloc.0
// jump to the test
IL_000c: br IL_0023
// execute the loop body
IL_0011: ...
// increment the x variable by 1
IL_001b: ldloc.0
IL_001c: ldc.i4 0x1
IL_0021: add
IL_0022: stloc.0
// TEST
// load x, load 100, branch if
// x is less than 100
IL_0023: ldloc.0
IL_0024: ldc.i4 0x64
IL_0029: blt IL_0011
Figure 13 For 循环代码
复制代码
else if (stmt is ForLoop)
{
// example:
// var x = 0;
// for x = 0 to 100 do
// print "hello";
// end;
// x = 0
ForLoop forLoop = (ForLoop)stmt;
Assign assign = new Assign();
assign.Ident = forLoop.Ident;
assign.Expr = forLoop.From;
this.GenStmt(assign);
// jump to the test
Emit.Label test = this.il.DefineLabel();
this.il.Emit(Emit.OpCodes.Br, test);
// statements in the body of the for loop
Emit.Label body = this.il.DefineLabel();
this.il.MarkLabel(body);
this.GenStmt(forLoop.Body);
// to (increment the value of x)
this.il.Emit(Emit.OpCodes.Ldloc, this.symbolTable[forLoop.Ident]);
this.il.Emit(Emit.OpCodes.Ldc_I4, 1);
this.il.Emit(Emit.OpCodes.Add);
this.Store(forLoop.Ident, typeof(int));
// **test** does x equal 100? (do the test)
this.il.MarkLabel(test);
this.il.Emit(Emit.OpCodes.Ldloc, this.symbolTable[forLoop.Ident]);
this.GenExpr(forLoop.To, typeof(int));
this.il.Emit(Emit.OpCodes.Blt, body);
}
IL 代码首先分配初始 for 循环计数器,然后立即使用 IL 指令 br(分支)跳转到 for 循环测试。与 IL 代码左侧列出的标签相似的标签用于让运行时知道下一个指令分支到的地方。测试代码将使用 blt(如果小于则分支)指令检查变量 x 是否小于 100。如果为 True,则执行循环体,同时递增 x 变量,然后再次运行测试。
图 13 中的 for 循环代码生成对计数器变量执行分配和增量操作所需的代码。还可以对 ILGenerator 使用 MarkLabel 方法来生成分支指令可以分支到的标签。
总结...几乎涵盖所有方面
我已根据简单的 .NET 编译器演练了代码并探讨了一些基本原理。本文旨在为您提供构造编译器这个神秘世界的基础。尽管在网上您能够发现一些有价值的信息,但是还应参考一些书籍。我向您推荐以下书籍:《Compiling for the .NET Common Language Runtime》,作者:John Gough(Prentice Hall,2001);《Inside Microsoft IL Assembler》,作者:Serge Lidin(Microsoft Press
®,2002);《Programming Language Pragmatics》,作者:Michael L. Scott(Morgan Kaufmann,2000)以及《Compilers: Principles, Techniques, and Tools》,作者:Alfred V. Oho、Monica S. Lam、Ravi Sethi 和 Jeffrey Ullman(Addison Wesley,2006)。
.oracle层次查询(connect by)
oracle的emp表中包含了一列mgr指出谁是雇员的经理,由于经理也是雇员,所以经理的信息也存储在emp表中。这样emp表就是一个自引用表,表中的mgr列是一个自引用列,它指向emp表中的empno列,mgr表示一个员工的管理者,
select empno,mgr,ename,sal from e
SAPHANA平台有各种各样的应用场景,这也意味着客户的实施方法有许多种选择,关键是如何挑选最适合他们需求的实施方案。
在 《Implementing SAP HANA》这本书中,介绍了SAP平台在现实场景中的运作原理,并给出了实施建议和成功案例供参考。本系列文章节选自《Implementing SAP HANA》,介绍了行存储和列存储的各自特点,以及SAP HANA的数据存储方式如何提升空间压
学习Java有没有什么捷径?要想学好Java,首先要知道Java的大致分类。自从Sun推出Java以来,就力图使之无所不包,所以Java发展到现在,按应用来分主要分为三大块:J2SE,J2ME和J2EE,这也就是Sun ONE(Open Net Environment)体系。J2SE就是Java2的标准版,主要用于桌面应用软件的编程;J2ME主要应用于嵌入是系统开发,如手机和PDA的编程;J2EE
JSF 2.0 introduced annotation @ViewScoped; A bean annotated with this scope maintained its state as long as the user stays on the same view(reloads or navigation - no intervening views). One problem w
很多文档说Zookeeper是强一致性保证,事实不然。关于一致性模型请参考http://bit1129.iteye.com/blog/2155336
Zookeeper的数据同步协议
Zookeeper采用称为Quorum Based Protocol的数据同步协议。假如Zookeeper集群有N台Zookeeper服务器(N通常取奇数,3台能够满足数据可靠性同时
Spring Security提供了一个实现了可以缓存UserDetails的UserDetailsService实现类,CachingUserDetailsService。该类的构造接收一个用于真正加载UserDetails的UserDetailsService实现类。当需要加载UserDetails时,其首先会从缓存中获取,如果缓存中没