Eslisp-ECMAScript/JavaScript的S表达式语法,带有类似Lisp的宏

2020-10-28 05:07:51

这并不神奇:它只是estree AST格式的S表达式编码。宏是返回对象的普通JS函数,这些对象只在编译时存在。这意味着宏可以放在NPM上来分发您自己的语言特性,如下所示。

岩心小,靠近JS。此核心eslisp与estree抽象语法树格式紧密对应,因此与输出JSP清晰匹配。除非您使用宏,否则它纯粹是一个语法适配器。

最大用户控制。用户必须能够轻松地扩展语言以满足他们的需要,并且能够独立于核心语言发布他们的功能。

用户定义的宏必须像对待内置宏一样对待,并且只是普通的JS函数。这意味着您可以用编译成JavaScript的任何语言编写它们,将它们放在NPM上并需要它们。

;宏是绑定到名称的函数,在代码上操作。这个;检查是否设置了`$DEBUG`环境变量,如果设置了,;返回对`console.log`的调用,该调用还包括传入的代码字符串。(MACRO DEBUG(lambda(Expression)(if(.。进程环境调试)(返回`((.。控制台日志);将输入表达式编译为JavaScript,并将其转换为字符串。、(、(.。此字符串)((.。此编译程序为ToJs)((.。此编译)表达式)";=";,表达式))(返回NULL)(var fib;斐波那契数序列(lambda(X);有条件编译日志记录代码(Debug X);基本斐波那契算法(开关x(0(返回0))(1(返回1))(默认(return(+(fib(-x 1))(fib(-x 2)。

Var fib=function(X){console.log(';x';,';=';,x);开关(X){案例0:返回0;案例1:返回1;默认:返回fib(x-1)+fib(x-2);}};

请注意,生成的console.log还具有变量x ASA字符串的名称。尝试将调试调用更改为(DEBUG((.。Math.power)(+x1)2),并观察日志记录代码更改为Math.pow(x+1,2)也在第一个字符串中。(您可以在runkihere上的浏览器中对其进行编辑。)。

Var fib=function(X){switch(X){case 0:return 0;case 1:return 1;default:return fib(x-1)+fib(x-2);}};

与通常将调试代码隐藏在布尔标志后面的技术相比,您的输出代码更小。

这实际上记录了产生结果的表达式。在JS中,如果不每次都手动编写,就不能做到这一点,因为您不能在编译时调用编译器。

我希望JavaScript是同形的,并且用相同的语言编写模块宏。我觉得这是那个方向可能的相邻位置。Sweet.js是为宏而存在的,但是它们编写起来很笨拙,而且不是JavaScript。存在各种JavaScript lisp,但大多数都具有过于努力地成为Lisp(而不仅仅是一种JS语法)的特性,并且没有一个具有仅仅是JS函数的宏。

我想要一种我能适应的语言。当我需要回指条件,或者条件编译或者文件内容内联(如BRF),或者我最喜欢的库的特定于领域的语言,或者出于某种奇怪的原因,在编译期间黑客攻击NASA并通过grep运行我所有的While循环的疯狂事情时,我希望能够自己创建该语言功能,如果它存在,则需要从NPM获得它,从而使该语言更适用于这项工作,并在将来为其他人做得更好。

S表达式在概念上也相当漂亮;它们只是嵌套列表,最小限度地表示抽象语法树,众所周知它们很棒,所以让我们使用有效的。

当然,这也有很大的黑客价值。LISP宏是继薄荷冰淇淋之后最酷的东西。我还需要这么说吗?

这是核心语言的快速概述。有关更完整的文档,请参阅基本参考或测试套件。

;从分号到行尾的所有内容都是注释。hello;这是一个原子。";hello";;这是一个字符串。(Hello";hello";);这是一个包含原子和字符串的列表。();这是一个空列表。

所有的eslisp代码都是通过在编译时调用宏来构造的。有内置的宏来生成javascript运算符、循环结构、表达式、语句…。编写任意JavaScript所需的一切。

通过编写一个列表来调用宏,该列表将其名称作为第一个元素,并将其参数作为其余元素:

;";.";宏编译为属性访问。A)(B)(.。A b5c";yo&34;);宏编译为加法。(+12);...。对于您从JS期望的";-";、";*";和";%#34;也是如此。

如果(.。A)语法感觉单调乏味,您可能喜欢eslisp-Propertify转换宏,它允许您编写a.b。

如果列表的第一个元素不是作用域中的宏名称,它将编译为函数调用:

;";=";宏编译为变量声明。(var x(+1(*23);调用属性访问表达式的结果((.。控制台日志)";嗨";)。

;";IF";宏编译为IF语句(如果午餐时间;参数1成为条件(BLOCK(var Lunch(Find Food));参数2为后件(午餐))(WriteMoreCode));参数3(可选)为替代。

注意BLOCK语句((BLOCK...))。必须说得很清楚。因为它太常见了,其他接受BLOCK语句作为最后一条语句的宏对此也有好处:它们只是假设您的意思是其余的都处于阻塞状态。

例如。Lambda宏(创建函数表达式)将其第一个参数视为函数的参数名列表,其余参数视为正文中的语句。

(Var N 10)(WHILE(--n);第一个参数是有循环条件的(Hello N);其余参数是循环体语句(hello(-n 1)。

您可以使用显式BLOCK语句((BLOCK...))。任何允许含蓄的地方,如果你想的话。

宏是在编译时运行的函数。无论它们返回什么,都会成为编译代码的一部分。用户定义的宏和预定义的编译器宏被同等对待。

在文档/目录中有一个更全面的教程来介绍eslisp宏。这些只是一些亮点。

您可以将宏的别名命名为您认为方便的名称,或者屏蔽任何您不想使用的名称。

(宏a数组)(A 1)(数组1);原始文件仍然有效...(宏数组);...除非我们故意屏蔽它(数组1)。

使用this上下文调用宏函数,该上下文包含便于使用宏参数的方法,例如this.valuate(编译并运行参数并返回结果)和this.atom(创建新闻表达式原子)。

(宏add2(lambda(X)(var xPlusTwo(+(.。此评估)x)2)(RETURN((.。这个原子)xPlusTwo)(。控制台日志)(Add240)。

(宏LOG-AND-DELETE(lambda(VarName)(return(array`(.。控制台日志)(.。Json stringify),varName))`(delete,varName)(日志删除某些变量)。

从宏返回NULL没有任何意义。这对于编译副作用和条件编译非常方便。

;如果设置了`$DEBUG`环境变量(宏调试(lambda(Statement)(Return(?:(.。Process env debug)语句NULL)(DEBUG((.。控制台日志)";调试输出";))(是)。

因为宏是JS函数,而JS函数可以是闭包,所以您甚至可以创建共享状态的宏。一种方法是将它们放入立即调用的函数表达式(LIFE)中,在对象中返回它们,然后将其传递给宏。对象的每个属性都作为宏导入,并且生命周期中的变量在它们之间共享。

(宏((lambda()(Var X 0);对所有宏函数(Return(对象增量(lambda()(Return((.。这个原子)(++x)递减(lambda()()(return((.。这个原子)(--x)get(lambda()(return((.。这个原子)x)(increment)(decrement)(get)。

宏的第二个参数需要计算为函数,但它可以是任意的,因此您可以将宏函数放在单独的文件中并执行以下操作-。

这意味着您可以在NPM上发布eslisp宏。建议使用名称前缀eslisp-和关键字eslisp-宏。有些已经存在了。

从命令行运行ESLC时,要在编译期间将转换宏应用于eslisp文件,请提供--Transform<;宏名称>;参数(缩写为-t;)。例如,。

使用eslisp-Propertify将所有包含点的原子转换为成员表达式。可以多次指定该标志。

如果需要在$PATH中使用ESLC,请安装NPM--global eslisp。(您可能需要sudo。)。然后ESLC程序将eslisp代码作为输入输出JavaScript。

如果在没有参数的情况下交互运行,编译器将加载一个REPL,您可以在该REPL中键入命令以对其进行测试。

您还可以将eslisp用作模块:它导出一个函数,该函数接受一串eslisp代码作为输入,并输出一串JavaScript,如果发现错误,就会抛出错误。

简而言之:使用一个预定义宏表将S表达式转换为SpiderMonkey AST,然后将其提供给escodegen,由escodegen输出JS。其中一些宏允许定义更多宏,这些宏被添加到表中,从那时起就像预定义的宏一样工作。