我已经有一个解析器用于我一直在研究的语言。让它解释困难吗?我觉得它很简单。解析和语法检查完成。我只有一棵物体树。每次创建对象时,我都会创建一个分支并存储其类型(string,int,float,class / obj)。然后,每次将新成员添加到对象时,我都会创建一个分支并重复。
我试着让它听起来很简单。我仍然需要检查对象A是否可以添加到对象B等。
在完成AST和语法检查之后它是否真的相当简单,还是还有更多的工作?
答案 0 :(得分:4)
通常,您需要构建符号表并进行类型检查。对于一些语言,你可以动态地做到这一点;对于其他人来说,我认为几乎必须首先进行名称解析和类型检查,否则你将无法很好地解释它(C ++会浮现在脑海中)。
一旦你构建了符号表,你几乎可以通过按照执行顺序走树并执行操作符所说的来编写解释器。基本算术很简单。字符串和动态存储管理更难;你要弄清楚如何处理存储分配和deallocatoin,对于管理存储的语言,你最终必须实现某种垃圾收集器。在这一点上,生活很快变得复杂。
您可能会发现您未考虑的语言需求功能。异常处理?多次退出?本地范围? Lambda表达式?闭包?你会很快发现有多少现代语言使它们变得有用。
当你开始编写更复杂的程序时,你需要一个调试器。断点?一小步?可变检查?更新?从任意地点开始? Read-eval-print loop?
您仍然需要将语言与外部库联系起来;大多数人都想与游戏机和文件交谈;你想要缓冲文件还是一次只能使用1个字符并且相应的性能命中?您将与characater表示(7位ascii?8位?带有非单位宽字符的UTF8?完整的Unicode?)和标准支持库(字符串连接,搜索,数字转换[包括双向精确浮点转换])争论,大量算术,浮点陷阱,....如果你想要一个有用的编程语言,问题列表会很长。
解释器核心可能很小。你会发现其他东西可能会燃烧一到两个数量级的努力。在这里的某个地方,如果你想让任何人使用语言,你需要记录你做出的所有选择。如果你在某个人运行大量应用程序之后更改解释器,天堂会帮助你。
接下来,有人会抱怨表现。现在,您可以调整实现,并开始重新编写您编写解释器而不是编译器的事实。
享受。如果你有一个AST,你几乎没有划过表面。如果您确实接受了这一点,您将学会真正欣赏现代语言提供的开箱即用的功能,以及提供它所花费的精力。
答案 1 :(得分:3)
这取决于您希望为其编写解释器的语言的复杂程度以及您选择的工具。简单的口译员很简单。
对于支持更高阶函数和数字的语言,请考虑以下作为Haskell中AST的定义:
data Exp = Lam String Exp
| App Exp Exp
| Var String
| Num Int
现在你可以为它编写一个解释器作为简单的“eval”函数:
eval (App e1 e2) env = (unF (eval e1 env)) (eval e2 env)
eval (Lam x e) env = F (\v -> (eval e ((x,v):env)))
eval (Num n) env = N n
eval (Var x) env = case (lookup x env) of
Just v -> v
Nothing -> error ("Unbound variable " ++ x)
就是这样。少数无聊的支持定义如下。
data Val = F (Val -> Val) | N Int
unF (F x) = x
instance Show Val where
show (F _) = "<procedure>"
show (N n) = show n
换句话说,如果您将上面三个代码块粘贴到Haskell源文件中,您将拥有一个可用的解释器,您可以使用ghci进行如下测试:
*Main> eval (App (Lam "x" (Var "x")) (Num 1)) []
1
*Main> eval (Var "x") []
*** Exception: Unbound variable x
您可以阅读有关在经典SICP或EOPL或little book中创建语言的信息。如果您希望构建类型化语言,则可能需要阅读更多内容。
那就是说,如果你打算建立语言,我可能会强烈推荐大量阅读。首先,它非常有益。其次,那些不知道如何创造语言的人(世界上有许多因各种历史原因而变得非常受欢迎)给世界造成了太多可怕的语言,我们对它们感到困惑。
答案 2 :(得分:0)
我说完成AST后,困难的部分(实际上最有趣的部分)就开始了。
看看LLVM,它有很多语言的绑定(我只使用C ++和Haskell,我不能说其他语言),它应该可以帮助你编写一个及时的编译器为你的语言。实际上,LLVM使编写编译器比解释器更容易!