零基础构建语言解释器

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:

在编写Interpreter之前,我们需要先了解Lexer(词法分析器),Parser(语法解析器),AST(抽象语法树)。

一般情况下,Interpreter在解释执行程序时,一般会经过如下步骤。

  1. Lexer读入程序代码,把代码转换token序列。
  2. Parser把读到的token序列转换为AST(大部分情况下,Lexer内嵌为Parser的一部分)。
  3. 对AST进行Lowering(化简AST)或者desugar(把语法糖的AST节点转换为标准等价AST节点)处理。
  4. Interpreter递归执行AST,AST决定了代码的执行顺序。

alternative text

介绍完了基本的一些概念,我们现在开始来实现语言解释器。

首先,我们需要先定制文法规则,一般情况下,我们只要制定好了文法规则,就可以找一些工具来帮我们生成AST,对于复杂的文法规则,手写Parser是非常麻烦并且乏味的。 这里,为了简单,我们选用S-Expression来作为我们的文法规则来实现Lambda Calculus(Lambda演算)的解释器。 通过该Interpreter的实现,大家可以很容易就搞明白程序设计语言中耳熟能详却又未必深刻了解的一些概念,比如:类型,Lexical Scope(词法作用域),Dynamic Scope(动态作用域),闭包等概念。

Lambda Calculus

虽然规则简单,Lambda演算却是一门强大的语言。它的三个元素分别是是:变量,函数,调用。用传统的表达法,它们看起来就是:
    变量:x
    函数:λx.t
    调用:t1 t2

每个程序语言里面都有这三个元素,只不过具体的语法不同,所以你其实每天都在使用 lambda calculus。换成S-Expression就是:
    变量:x
    函数:(lambda (x) e)
    调用:(e1 e2)

Parser的实现

在parse处理token序列之前,我们先对tokens序列做一下预处理,其转换规则如下例所示,详细源码可以在此查看

"(((lambda (x) (lambda (y) (+ x y))) 2) 3)" => [[[lambda [x] [lambda [y] [+ x y]]] 2] 3]     
function doParse(node) {
    var head;
    if (isArray(node)) {
        if (node.length == 0) {
            throw new SyntaxError("syntax error: " + node);
        } else {
            head = node[0];
            switch(head) {
                case 'def':
                    return parseDef(node);
                case 'lambda':
                    return parseLambda(node);
                default:
                    return parseApply(node);
            }
        }
    } else {
        return node;
    }
}

为了演示方便,除了变量,函数,调用Node之外,我这里还额外实现了一个节点def,它的主要功能是方便给变量及函数定制别名。

解析Lambda节点

function parseLambda(node) {
    var params, paramNames;
    if (node.length !== 3) {
        throw new SyntaxError("syntax error in function definition" + node);
    }
    params = node[1];
    if (!isArray(params)) {
        throw new SyntaxError("incorrect format of parameters: " + params);
    }
    paramNames = params.map(function(param, _){
        if (isArray(param)) {
            throw new SyntaxError("illegal argument name : " + param);
        }
        return param;
    });
    return {type: 'lambda', params: paramNames, body: doParse(node[2])};
}

解析Apply节点

function parseApply(node) {
    return {type: 'apply', func: doParse(node[0]), args: doParseList(aps.call(node, 1))};
}

解析Def节点

function parseDef(node) {
    if (node.length != 3) {
        throw new SyntaxError("incorrect format of definition " + node);
    }
    return {type: 'def', pattern: doParse(node[1]), value: doParse(node[2])}
}

(def if (lambda (p) (lambda (x) (lambda (y) ((p x) y))))) 转化的AST如下: alternative text

Interpreter的实现

function interpret(node, scope) {
    switch (node.type) {
        case 'def':
            return scope.define(node.pattern, interpret(node.value, scope));
        case 'apply':
            var closure = interpret(node.func, scope),
                args = node.args.map(function(arg) { return interpret(arg, scope); }),
                newScope;
            if (closure.type === 'closure') { // Lambda Closure
                newScope = closure.fn.params.reduce(function(ctx, param, i) {
                    ctx.define(param, args[i]);
                    return ctx;
                }, closure.env.copy()); // scope.copy() static scope vs. dynamic scope
                return interpret(closure.fn.body, newScope);
            } else { // BuiltIn Function
                return closure.apply(scope, args);
            }
        case 'lambda':
            return {type: 'closure', fn: node, env: scope};
        default:
            if (scope.defined(node)) {
                return scope.lookup(node);
            } else {
                throw new EvalError("Unknown variable " + JSON.stringify(node));
            }
    }
}

如果你想支持原生的数据类型,比如字符串和数字,default部分可以改为:

default:
    if (node[0] === '"' && node.slice(-1) === '"') {
        return node.slice(1, -1);
    } else if (!isNaN(parseFloat(node))) {
        return parseFloat(node);
    } else {
        if (scope.defined(node)) {
            return scope.lookup(node);
        } else {
            throw new EvalError("Unknown variable " + node);
        }
    }

程序设计语言中的一些概念

Closure(闭包)

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的表达式(通常是函数)。这些被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。 词法作用域(lexical scope)等同于静态作用域(static scope)。所谓的词法作用域其实是指作用域在词法解析阶段既确定了,不会改变。

在这里很容易看出,闭包的数据结构可以定义为,包含一个函数定义 f 和它定义时所在的环境 (struct Closure (f env))

(def add (lambda (x) (lambda (y) (+ x y)))) ;; add是一个全局函数,它是没有绑定任何自由变量的闭包,它的env就是全局的scope。
(def add5 (add 5)) ;; add5是一个全局函数,它是有捕获自由变量的闭包。
(add5 10) ;; Output: 15

动态作用域 vs. 词法作用域

本例中,以下代码决定语言程序语言使用的是词法作用域还是动态作用域。

newScope = closure.fn.params.reduce(function(ctx, param, i) {
    ctx.define(param, args[i]);
    return ctx;
}, closure.env.copy()); 
return interpret(closure.fn.body, newScope);

closure.env.copy()替换为scope.copy()就可以把当前的词法作用域替换为动态作用域。词法作用域总是关联定义该Closure的Scope环境,动态作用域则是使用执行该Closure的Scope环境。

类型

类型的本质在于它所定义的操作以及操作之间的关系和不变式。类型的实现关键在于满足类型规范的要求,而具体实现是可以变化的,使用者和测试用例都应该只依赖于类型规范而不依赖于具体实现。函数式的类型实现往往和类型规范是直接对应的,简单通用,但可能有性能问题,而命令式的类型实现往往会引入复杂的内部数据结构,但是其优点是高效。这两种实现并不是完全互斥的,有时候可以将二者相结合达到简单与高效的结合。下面我们就使用函数式的方式来实现数据类型。

自然数

下面的代码定义了0~9几个自然数(这种方式定义的自然数也称为Church数),并定义了+,*(幂)三种操作。

(def 0 (lambda (f) (lambda (x) x)))
(def succ (lambda (n) (lambda (f) (lambda (x) (f ((n f) x))))))
(def 1 (succ 0))
(def 2 (succ 1))
(def 3 (succ 2))
(def 4 (succ 3))
(def 5 (succ 4))
(def 6 (succ 5))
(def 7 (succ 6))
(def 8 (succ 7))
(def 9 (succ 8))
(def + (lambda (m) (lambda (n) (lambda (f) (lambda (x) ((m f) ((n f) x)))))))
(def * (lambda (m) (lambda (n) (lambda (f) (n (m f))))))
(def ** (lambda (a) (lambda (n) (n a)))) // exp

为了方便直观演示,我们这里还定义了几个辅助的内建函数。

env.define('inc', function(v){
    return v ? v + 1 : 1;
});
env.define('println', function(){
    console.log.apply(this, arguments);
});
env.define('nil', null);

我们再定义一个方法,用于把Church数转为普通的数字。

(def church->int (lambda (n) ((n (lambda (x) (inc x))) nil)))
((3 (lambda (x) (println (church->int 5))))
;; output:
;; 5
;; 5
;; 5

(church->int 0) ;; => undefined
(church->int 6) ;; => 6

(church->int ((+ 1) 2)) ;; => 3 
(church->int ((* 2) 3)) ;; => 6
(church->int ((** 2) 3)) ;; => 8
布尔类型

布尔值及其操作定义

(def true (lambda (x) (lambda (y) x)))');
(def false (lambda (x) (lambda (y) y)))');
(def and (lambda (p) (lambda (q) ((p q) false))))');
(def or (lambda (p) (lambda (q) ((p true) q))))');
(def not (lambda (p) ((p false) true)))))');
(def if (lambda (p) (lambda (x) (lambda (y) ((p x) y)))))');

为了方便测试并查看效果,我们新增一个内建函数。

env.define('assert-equals', function(n1, n2){
    if (n1 !== n2) {
        throw new EvalError(n1 + " must equals " + n2);
    } else {
        return true;
    }
});

测试布尔类型上的操作。

(assert-equals ((and true) true) true) 
(assert-equals ((and true) false) false) 
(assert-equals ((and false) true) false) 
(assert-equals ((and false) false) false) 

(assert-equals ((or true) true) true) 
(assert-equals ((or true) false) true)
(assert-equals ((or false) true) true)
(assert-equals ((or false) false) false)

(assert-equals (not false) true) 
(assert-equals (not true) false) 

(assert-equals (((if true) 3) 5) 3)
(assert-equals (((if false) 3) 5) 5)
LISP中常用的数据类型cons(有序对)
(def cons (lambda (x y) (lambda (p) (((if p) x) y)))))
(def car (lambda (cons) (cons true)))
(def cdr (lambda (cons) (cons false)))

验证cons类型及其操作。

(def c (cons 1 (cons 2 (cons 3 (cons 4 (cons 5 0))))))

(assert-equals (car c) 1)
(assert-equals (car (cdr c)) 2) 
(assert-equals (car (cdr (cdr c))) 3) 
目录
相关文章
|
8月前
|
安全 编译器 开发者
Python语言的配置解释器
Python语言的配置解释器
|
3月前
|
Python
Python 脚本高级编程:从基础到实践
本文介绍了Python脚本的高级概念与示例,涵盖函数的灵活应用、异常处理技巧、装饰器的使用方法、上下文管理器的实现以及并发与并行编程技术,展示了Python在自动化任务和数据操作中的强大功能。包括复杂函数参数处理、自定义装饰器、上下文管理器及多线程执行示例。
50 5
|
3月前
|
大数据 Python
Python 高级编程:深入探索高级代码实践
本文深入探讨了Python的四大高级特性:装饰器、生成器、上下文管理器及并发与并行编程。通过装饰器,我们能够在不改动原函数的基础上增添功能;生成器允许按需生成值,优化处理大数据;上下文管理器确保资源被妥善管理和释放;多线程等技术则助力高效完成并发任务。本文通过具体代码实例详细解析这些特性的应用方法,帮助读者提升Python编程水平。
158 5
|
6月前
|
数据采集 Java C语言
Python面向对象的高级动态可解释型脚本语言简介
Python是一种面向对象的高级动态可解释型脚本语言。
55 3
|
6月前
|
机器学习/深度学习 数据采集 人工智能
Python 是一种广泛使用的高级编程语言
【7月更文挑战第17天】Python 是一种广泛使用的高级编程语言
66 2
|
7月前
|
机器学习/深度学习 人工智能 BI
解析Python解释器:从基础到应用的完整指南
解析Python解释器:从基础到应用的完整指南
150 2
|
8月前
|
测试技术 持续交付 数据处理
Python动态类型深度解析与实践
Python动态类型深度解析与实践
493 1
|
8月前
|
开发者 Python
Python中的元编程:扩展语言的力量
【2月更文挑战第5天】本文将探讨Python中的元编程,介绍了元编程的概念和意义,并详细讨论了Python中常用的元编程技术,如装饰器、元类和动态类型。通过元编程,我们可以在不改变语言核心的情况下,扩展Python的功能和灵活性,为开发者提供更强大的工具和框架。
|
8月前
|
机器学习/深度学习 人工智能 IDE
Python是一种高级、解释型、交互式和面向对象的脚本语言
Python是一种高级、解释型、交互式和面向对象的脚本语言
75 2
|
8月前
|
Python
Python解释器小探究
Python解释器小探究
64 1