创建自定义的 PHP 语法解析器

7
我正在思考如何创建一个PHP版本的CSS和JS库,其中之一是“Less CSS”,它是一种动态样式表语言。其基本思想是允许您创建更多包含实体的动态CSS规则,“常规” CSS不支持这些实体,例如mixin,function等,然后最终“Less CSS”将这些语法编译为常规CSS。
另一个行为类似的有趣的JS库是“CoffeeScript”,您可以编写“更整洁、更简单”的代码,然后将其编译成常规Javascript。
那么该如何通过PHP创建一个类似的简单接口呢?只是为了概念证明;我只是在尝试学习东西。让我们以扩展类的简单用例作为例子。
class a
{
    function a_test()
    {
        echo "This is test in a ";
    }
}

class b extends a
{
    function b_test()
    {
        parent::a_test();
        echo "This is test in b";
    }
}

$b = new b();
$b->b_test();

假设我想让用户编写类b,例如:
class b[a] //would mean b extends a
{
    function b_test()
    {
        [a_test] //would mean parent::a_test()
        echo "This is test in b";
    }
}

让它们后来将该代码“解析”为常规PHP(通常通过运行单独的命令/进程来完成)。我的问题是如何创建这样的东西。它可以在PHP中完成吗,还是需要使用类似C / C ++的东西。如果我要解决这个问题,应该如何着手?是否有在线资源?非常感谢任何提示!


我很确定这是不可能的。 - poudigne
@PLAudet是错误的;这绝对不是不可能的。您可以使用任何能够读写文件并进行字符串操作的语言来完成,包括PHP和C/C++。当然,C/C++会更快。最终,您所做的就是获取一个输入文件,读取它,解析它,决定要对其进行什么操作,并输出一个PHP文件。但这个问题太大了,不适合在Stack Overflow上提问。我建议您开始阅读有关标记化和解析的内容。 - KRyan
一个人的限制只在于自己的想象力。编程语言只是另一个人的应用程序。 - Mihai Stancu
1个回答

8

语言转码器并不像人们想象的那么容易。

你提供的例子可以很容易地使用 preg_replace 实现,它查找类定义并将 [a] 替换为 extends a

但更复杂的功能需要一个包含多个较小逻辑代码块的 转码器

在大多数程序员的术语中,人们错误地称转码器为 编译器,但编译器和转码器之间的区别在于编译器读取源代码并输出原始二进制机器代码,而转码器读取源代码并输出(另一种)源代码。

例如,PHP(或JavaScript)运行时既不是编译器也不是转码器,它是解释器。

但是关于术语的讨论就到此为止,让我们谈谈转码器:

要构建转码器,您必须首先构建一个分词器,它将源代码分解为标记,这意味着如果它看到整个单词(如'class'或类的名称或'function'或函数的名称),它会捕获该单词并将其视为标记。当它遇到另一个标记,例如开放式圆括号、开放式大括号或方括号等时,它会将其视为另一个标记。

幸运的是,PHP 中可识别的所有标记都已经可以轻松地通过 PHP 随附的函数 token_get_all 扫描。您可能会遇到一些麻烦,因为 PHP 假设您如何使用符号,但总的来说,您可以利用这个函数。

分词器创建了一个包含它找到的所有标记的平面列表,并将其提供给解析器。

解析器是转码器的第二阶段,它读取标记列表并决定诸如“如果 token[0] 是类且 token[1] 是 name_value,则我们有一个类”等等。在运行完整个标记列表之后,我们应该有一个抽象语法树。

抽象语法树是一种仅保留源代码相关信息的符号结构。

$ast = array(
    'my_derived_class' => array(
        'implements' => array(
            'my_interface_1',
            'my_interface_2',
            'my_interface_3'),
        'extends' => 'my_base_class',
        'members' => array(
            'my_property_name' => 'my_default_value',
            'my_method_name' => array( /* ... */ )
        )
    )
);

获得抽象语法树后,您需要遍历它并输出目标源代码。真正棘手的部分是解析器(取决于解析的语言复杂程度),可能需要回溯算法或其他形式的模式匹配来区分彼此相似的情况。我建议阅读 Terence Parr 的书籍 http://pragprog.com/book/tpdsl/language-implementation-patterns ,其中详细描述了编写转码器所需的设计模式。在 Terrence 的书中,您将了解为什么某些语言(如 HTML 或 CSS)在结构上比 PHP 或 JavaScript 简单得多,以及这与语言解析器的复杂性有何关系。

你在第一句话中打错了'think'。我本来想编辑,但想让你知道这样可以更快地进行更改。 - Sly
2
如果您在我的答案中添加了关于token_get_all的部分,我将删除我的答案。这个更详细。 - Sly
是的,采用这个并重新利用它可能比自己编写更容易。我不知道这个库,谢谢@duskwuff。 - Mihai Stancu
讲解得非常清楚!谢谢Mihai。 - Undefined Variable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接