#expression #lisp #lumen #lua #javascript #command #js

app lumen-language

Lua和JavaScript的Lisp

1个不稳定版本

使用旧的Rust 2015

0.1.0 2018年9月14日

#39#lisp

BSD-2-Clause

165KB
6K SLoC

JavaScript 3K SLoC Lua 3K SLoC Rust 43 SLoC

Lumen

Lumen是一个非常小巧、自托管的Lua和JavaScript的Lisp。它提供灵活的编译环境,具有可扩展的读取器、宏和可扩展的特殊形式,但在其他方面尽可能接近目标运行时环境。您可以在已安装Node.js、Lua或LuaJIT的机器上运行 bin/lumen 来开始使用。

简介

Lumen中的每段代码都是一个表达式,表达式可以计算为给定的值。Lumen有几种类型的表达式可以自我计算

> 17
17
> -5e4
-5000
> true
true
> false
false

字符串用引号括起来,可以包含换行符。特殊字符使用反斜杠转义

> "hi there"
"hi there"
> "one
two"
"one\ntwo"
> "a\"b"
"a\"b"

nil 代表空无,它不会被打印在命令行上

> nil
>

注释以 ; 开头,并一直延续到该行的末尾

> 7 ; everyone's favourite
7

列表包含其他值,并通过在括号中包含表达式来书写。通过将操作符放置在列表表达式的开头来调用操作符,并且可以使用 list 操作符来构造列表值

> (+ 10 2)
12
> (abs -10)
10
> (/ 128 2 2 2)
16
> (list 1 2 3 4)
(1 2 3 4)

列表可以包含通过其位置识别的值,以及通过列表中的名称识别的值

> (list 1 2 3)
(1 2 3)
> (list 1 2 a: 10 b: 20)
(1 2 b: 20 a: 10)

请注意,通过名称识别的值,即键,不会按任何特定的顺序出现。

可以使用 at 操作符从列表中获取位置值,使用 get 操作符获取键

> (at (list 1 2 3) 1)
2
> (get (list a: 10 b: 20) "b")
20

对于值为 true 的键的快捷方式,看起来像这样,称为标志

> (list :yes)
(yes: true)

变量

使用 definedefine-global 声明变量。使用 define 声明的变量可以在同一作用域中后续表达式中任何地方使用,而 define-global 使它们全局可用。

> (define-global zzz "ho")
> zzz
"ho"
> (do (define x 10) (+ x 9))
19

do 计算多个表达式,并且自身计算为最后一个表达式的值。

使用 let 引入有限作用域的变量

> (let (x 10 y 20)
    (+ x y))
30
> (let x 41 (+ x 1))
42
> (let x 1
    (let x 2
      (print x))
    (print x))
2
1

您可以看到,let 接受名称和值的列表,称为绑定,或者它可以与单个绑定一起使用。绑定之后可以跟多个表达式,这类似于 do

> (let (x 10 y 20)
    (print x)
    (+ x y))
10
30
> (let x 9
    (print "hi")
    (let y (+ x 1) y))
hi
10

赋值

可以使用 set 更新变量和列表值,它计算为它更新的值

> (let x 10
    (set x 15))
15
> (let x 10
    (set x 20)
    (+ x 5))
25
> (let a (list 1 2 3)
    (set (at a 1) "b")
    a)
(1 "b" 3)
> (let a (list foo: 17)
    (set (get a "foo") 19)
    a)
(foo: 19)

条件语句

条件评估使用 if 表达式。一个 if 表达式的值是条件评估为 true 的分支的值。

> (if true 10 20)
10
> (if false 10 20)
20
> (+ (if false 10 20) 5)
25

if 表达式可以有任意数量的分支。

> (if true 10)
10
> (if false 10)
>
> (if false 1 false 2 false 3 true 10)
10
> (if false 1 false 2 false 3 10)
10
> (if true 9 (do (print 10) 11))
9
> (if false 9 (do (print 10) 11))
10
11

比较值使用 = 操作符。

> (= 10 10)
true
> (if (= 10 10) "yes")
"yes"
> (if (= 10 "no") "yes")
>

列表是有唯一标识的值,因此包含相同值的两个独立列表不是同一个。

> (= (list 1 2 3) (list 1 2 3))
false

函数

Lumen 中的函数是值,就像数字和字符串一样。以 fn 开头的表达式评估为函数。

> (fn () 10)
function

可以通过将函数放在列表表达式的开头来调用函数。出现在 fn 名称之后的列表标识了函数的参数。

> (fn (a) (+ a 10))
function
> ((fn (a) (+ a 10)) 20)
30
> ((fn () 42))
42
> ((fn (a b) (+ a b)) 10 20)
30

因为函数是值,所以我们可以用变量来命名它们。调用由变量命名的函数时,适用相同的规则。

> (let f (fn () 42)
    (f))
42
> (let plus (fn (a b) (+ a b))
    (plus 10 20))
30

定义函数的最常见快捷方式是在以下方式中使用 definedefine-global

> (define-global f (n)
    (* n 10))
> (f 3)
30
> (do (define f (n) (* n 10))
      (print (f 3))
      (print (f 4))
      (f 2.5))
30
40
25

函数的参数列表可以包含位置参数和键参数,其中键的值是绑定名称。

> (let f (fn (a b: my-b) (+ a my-b))
    (f 13 b: 2))
15

如果键的值为 true,则使用与键相同的名称来绑定参数值。这使得定义带有标志的命名参数变得容易。

> (let f (fn (a b: true) (+ a b))
    (f 1 b: 2))
3
> (let f (fn (a :b) (+ a b)) ; use a flag
    (f 10 b: 20))
30

Lumen 中的参数总是可选的,那些没有提供参数的参数其值为 nil

> (let f (fn (a) a)
    (f))
>
> (let f (fn (:b) (if (= b nil) 10 20))
    (f a: 99))
10

函数可以通过指定单个参数而不是列表,或者使用 rest 键来接受可变数量的参数。

> (let f (fn xs (last xs))
    (f 1 2 3))
3
> (let f (fn (a rest: as) (+ a (last as)))
    (f 10 11 12 13))
23

解构

变量可以绑定到列表中的特定位置或键的值。

> (let ((a b c) (list 1 2 3))
    b)
2
> ((fn ((a b c)) c) (list 1 2 3))
3
> (let ((a b: my-b) (list 1 b: 2))
    my-b)
2
> ((fn ((a b: my-b)) (list a my-b)) (list 1 b: 2))
(1 2)
> (let ((a :b) (list 1 b: 2))
    b)
2

rest 键与解构一起使用,就像在函数参数中使用一样,它绑定列表的其余部分。

> (let ((a rest: as) (list 1 2 3))
    (list a as))
(1 (2 3))
> (let ((a :rest) (list 1 2 3))
    (list a rest))
(1 (2 3))

迭代

Lumen 有几种迭代机制。最简单的是 while 循环。

> (let i 3
    (while (> i 0)
      (print (dec i))))
2
1
0

从 0 迭代到 N 的简写是 for

> (for i 3
    (print i))
0
1
2

可以使用 each 来枚举列表的键和值。

> (each (k v) (list 1 2 a: 10 b: 20)
    (print (cat k " " v)))
1 1
2 2
b 20
a 10
> (each v (list 1 2 a: 10 b: 20) ; values only
    (print v))
1
2
20
10
> (each (k (a b)) ; destructuring
      (list (list 10 20) bar: (list "a" "b"))
    (print (cat k " " a " " b)))
1 10 20
bar a b

each 会以任意顺序绑定键和值。如果您只想获取列表的位置值,您可以使用 step 按顺序枚举它们。

> (step x (list 1 2 3)
    (print x))
1
2
3
> (step (a b) (list (list 1 2) (list 10 20)) ; destructuring
    (print a)
    (print b))
1
2
10
20

引号

可以使用 quote 操作符防止表达式被评估。

> (quote (1 2 3))
(1 2 3)

评估为自身的表达式不受引号的影响。

> (quote 10)
10
> (quote false)
false

引号名称和字符串会产生评估为引号表达式的字符串。

> (quote a)
"a"
> (quote "hereanother")
"\"hereanother\""
> (quote "two\nlines")
"\"two\\nlines\""

这同样适用于列表中的表达式。

> (quote (a b c))
("a" "b" "c")
> (quote (1 2 b: baz z: "frob"))
(1 2 b: "baz" z: "\"frob\"")

引号的简写是使用单个引号。

> '17
17
> '(1 2 3)
(1 2 3)
> '(a b c)
("a" "b" "c")

编写许多字符串的另一种方式是简单地引号它们的名称。

> 'a
"a"
> (let x '(a: 10 b: 20)
    (get x 'a))
10

当您想引号表达式的某些部分,但想评估其他部分时,请使用 quasiquoteunquote

> (let x 10
    (quasiquote (1 5 (unquote x))))
(1 5 10)

准引号的简写是 `,而 ,unquote 的简写。

> (let x 10 `(1 5 ,x))
(1 5 10)

取消引号表达式的一种不同方法是使用 unquote-splicing,它将嵌套列表中的值放入外层列表中。

> (let a '(1 2 3)
    (quasiquote (9 8 (unquote-splicing a))))
(9 8 1 2 3)

unquote-splicing 的简写是 ,@

> (let a '(1 2 3)
    `(9 8 ,@a))
(9 8 1 2 3)

宏允许您编写在评估之前操作表达式的函数。宏接受表达式作为参数并返回一个表达式。

> (define-macro when (condition rest: body)
    `(if ,condition (do ,@body)))
(macro: function)
> (when true
    (print 'hi)
    (+ 10 20))
hi
30
致谢

Lumen 是与 Daniel Gackle 共同构思和设计的,许多修复和改进由 Shawn Presser 贡献。

依赖项

~240KB