贡献者: addis
可以参考 语法手册(非官方) 和 GPT。
src/flisp/flisp 就是 REPL。
julia/src 或 julia/src/flisp 有许多 scm 文件和 lsp 文件(都在 flsp 中)。
julia-parser.scm 是语法解析,ast.scm 是语法树工具
aliases.scm 中定义了许多 scheme 中的函数别名等。所以 .scm 结尾的文件都需要先运行该文件才可以正常工作。
aliases.scm 定义的语法需要注明。
(typeof 123) 和 (typeof '123) 都是 fixnum,数字的符号和数字不区分
(typeof 'abc) 是 symbol,又如 '+
'abc 是 (quote abc) 的语法糖
(typeof 1.23e4) 是 double
(typeof "abc") 是 array byte
(typeof #\a) 是 wchar 是字符
(typeof #t) 是 boolean,还有 #f
(typeof '()) 是 null
(typeof (cons 1 2)) 是 pair
(typeof (vector 1 2 3)) 是 vector
(typeof (lambda (a b) (+ a b))) 是 function
(typeof aref) 是 builtin(内建函数)
(+ 1 2 3) 加法,另有 -, *, /, <, <=, >, >=,
(define a 1) 定义变量
(print 变量1 变量2 ...) 打印变量值,princ 也可以。前者会给每个字符串添加双引号,后者不会,后者会把 \n 打成换行符。二者都会返回 #t。所以在 REPL 中后面会出现 #t 并不是打印的内容而是返回值。
(define a 123) 然后 `(1 ,a) 会得到 (1 123)。
(define a '(1 2 3)) 然后 `(0 ,@a 4) 得到 (0 1 2 3 4)。
(eq 1 1)(eq 是 eq? 的别名)检查是否相等,返回 #t
(= 3 3.0) 数值比较,即使是不同类型也能比。
^ 或函数,可以自己定义 (define (expt a b) (exp (* b (log a))))。现在就可以 (expt 2 4) 得 16。
(cons 1 2) 显示为 (1 . 2),(cons 1 (cons 2 (cons 3 4))) 显示为 (1 2 3 . 4)
(list 1 2 3 4) 和 '(1 2 3 4) 等效,显示为 (1 2 3 4)
(length 列表或数组或字符串) 返回长度(字符串返回字节数),(length= 列表 长度) 检查长度是否符合预期。
(aref (vector 10 11 12) 0) 获取第 0 个元素 10
(aset! 数组 索引 值) 可以改变数组元素值。如 (define x (vector 1 2 3 4)),(aset! x 2 9)
(car 变量) 和 (cdr 变量) 分别取第 1 和 2 个元素
(cadr 变量) 等效于 (car (cdr 变量)),以此类推。所以取第 n 个元素(从 0 数)就是 cadd...dr 中就有 n 个 d。
(list 1 2 3) 等效为 (cons 1 (cons 2 (cons 3 '()))),本质上是用二叉树表示单链表。类型仍然是 pair。
(list* 1 2 3) 会把最后一个入参直接放在二叉树的末尾,不以 null 结尾,显示为 (1 2 . 3)。一般用法是 (list* 1 2 '(3 4)) 得到正常的列表 (1 2 3 4)
(append '(1 2) '(3 4)) 可以合并两个列表得到 (1 2 3 4)
(append! my_list '(3 4)) 可以直接修改 my_list。例如 (define ll '(1 2 3)),(append! ll '(4 5))。第二个参数不能是一个数字,一个数字也要写成 '(4)
(set! 变量名 值) 可以把 值 赋值给变量名(可以不存在),并返回该值。例如 (set! a (+ a 2)) 相当于 a += 2。
(prog1 语句1 语句2 ...) 依次执行每个语句并返回第一个语句的值。
(every 函数 列表) 会检查函数作用在每个元素上是否都返回 #t
*argv* 里面是命令行参数的列表。REPL 中第一个是 flisp 命令本身。若 ./flisp my_file.lsp arg1 arg2 则第一个是 my_file.lsp。
(load "路径/文件名") 可以执行另一个文件中的 lsp 源码,返回最后一个语句的值(若为空则返回 #t)
(make-system-image "文件名") 可以把当前的环境(已经定义的函数、全局变量等)保存到一个二进制文件中。
boolean 的函数称为 predicate,习惯上以 ? 结尾
#t):(number? 42),(odd? 3),(string? "abc"),(null? '()),(pair? '(1 2)),(even? 6),(eq? 1 1)(判断是否同一对象),(eqv? 1 1)(比较值和类型)。
and, or, not 是逻辑算符,只有入参是 #t, #f 才能正常工作。
(map 函数 列表) 会把函数依次作用在列表的每个元素并返回一个新的列表。例如 (map abs '(-1 -2 3 -4)) 得到 (1 2 3 4)。又如 (map (lambda (x) (* x x)) '(1 2 3)) 得到 (1 4 9)。
*名字* 只是个普通名字,习惯上用这种格式表示它是一个全局变量(声明在任何函数体之外)。
(memq 元素 列表) uses eq? comparison — identity comparison. 即判断一个元素是否在列表中。
(memv 元素 列表) uses eqv? comparison — similar but a bit looser (numbers, characters).
(member 元素 列表) uses equal? comparison — deep structural equality.
(reverse! '(1 2 3)) 反转列表得到 (3 2 1)
(unwind-protect (princ "doing work\n") (princ "cleanup\n")) 在执行第一个入参的命令时无论是正常退出还是抛出异常,都会执行第二个入参的命令。
(with-bindings ((全局变量 值) ...) 语句 ...) 和 (let ...) 类似,但是临时改变全局变量但语句结束后全局变量又恢复原来的值。全局变量叫做 Dynamic binding,局部变量叫做 Lexical binding。
(define t (table)) 创建空字典
(define t (table :a 1 :b 2 :name "Ada")) 初始化字典,注意任何对象都能作为 key
(table? t) 检查类型,(typeof t) 返回 table
(put! t :a 42) 插入或更新,返回字典本身
(get t :a) 查询
(get t :some_name 默认值)
(has? t :a) 是否存在 key
(del! t :a) 删除键值对
(if (> 2 1) 'big 'small) 返回 'big
(begin 语句1 语句2 ...),begin 会返回最后一个语句。
(let () 语句1 语句2 ...) 括号允许为空。
(if (> 3 2)
(begin
(princ "3 > 2\n")
(princ "This is true branch\n"))
(begin
(princ "3 <= 2\n")
(princ "This is false branch\n")))
(define (grade-score s)
(if (>= s 90)
'A
(if (>= s 80)
'B
(if (>= s 70)
'C
(if (>= s 60)
'D
'F)))))
又或者用 cond
(define (grade-score s)
(cond
((>= s 90) 'A)
((>= s 80) 'B)
((>= s 70) 'C)
((>= s 60) 'D)
(else 'F)))
(cond ((= x 1) 'one) ((= x 2) 'two) ((= x 3) 'three) (else 'big))
(for-each print '(1 2 3)),也可以支持多入参的函数
(for-each (lambda (a b)
(print (+ a b))) '(1 2 3) '(10 20 30))
1+2+3+4
(define (sum-up i sum)
(if (> i 5)
sum
(sum-up (+ i 1) (+ sum i))))
(sum-up 1 0)
也可以用 let
(let my-loop ((i 1) (sum 0))
(if (> i 4)
sum
(my-loop (+ i 1) (+ sum i))))
还有一种等效写法(letrec 允许定义的变量互相依赖,以及依赖自身)
(letrec ((loop-fun (lambda (i sum)
(if (> i 5)
sum
(loop-fun (+ i 1) (+ sum i))))))
(loop-fun 1 0))
int f() {
int a = 1, b = 2, c = 3;
for (int i = 0; i < 100; i++) {
a = a + b;
b = b + c;
c = c + a;
}
return a + b + c;
}
翻译为
(define (f i a b c)
(if (>= i 100)
(+ a b c)
(f (+ i 1)
(+ a b)
(+ b c)
(+ c a))))
(define (string->list s)
(let loop ((i (sizeof s)) (l '()))
(if (= i 0)
l
(let ((newi (- i 1)))
(loop newi (cons (string.char s newi) l))))))
(trycatch 要执行的命令 异常处理函数)
(trycatch
(begin
(princ "Before error\n")
(error "Something went wrong!")
(princ "After error\n")) ; never reached
(lambda (e)
(princ "Caught exception: " e "\n")))
(string 变量) 可以把其他类型转为字符串:string 且支持多入参。如 (string 1.2 ", " 3.4) 得到 "1.2, 3.4"
(length "你好吗") 和 (sizeof "你好吗") 返回字节数 9
(string.count "你好吗") 返回字符数 3
(string "abc" "def" "ghi") 拼接字符串
(string.join '("abc" "def") ", ") 得到 "abc, def"
(symbol "abc") 把字符串转为符号。
(string.char 字符串 索引) 获取某个字符。
(string.sub "abcde" 1 3) 获取子字符串,得到 "bc"
(string.inc 字符串 索引 字符数) 向后跳 字符数 个字符,返回新索引(编码 utf-8)。索引是字节为单位的。索引 可以不在字符起始位置。string.dec 同理
(string.find "abcdef" "cde") 和 (string.find "abcdef" #\c) 查找字符串,都返回 2
(define (square n) (* n n)) 定义函数 (square 3) 调用
(define (f1 a b) (+ a b)) 不完全是 (define f1 (lambda (a b) (+ a b))) 的语法糖,函数对象会记录函数名为 f1,无论赋值给谁。
(define f2 f1) 可以赋值函数对象,但对象里面记录的函数名不会变。
(null? '()) 返回 #t
(define (函数名 形参1 (形参2 默认值) ...) 语句1 语句2 ...)。如 (define (myfun1 x (y 3.14)) (+ x y)) 返回函数体中最后一个语句的值。
let 结构
(let ((var1 val1)
(var2 val2)
...)
body-expr-1
body-expr-2
...
body-expr-n)
可以定义局部变量 var*,并在后面的语句中使用,返回最后语句的结果。返回后这些局部变量无定义。
let* 可以让后定义的局部变量依赖于先声明的:(let* ((a 3) (b (+ a 1))) (* a b)) 返回 12。
(define (函数名 参数1 参数1 . 参数n) 函数体)。参数n 相当于 matlab varargin。例如 (define (show first . rest) (princ "First: " first "\n") (princ "Rest: " rest "\n")),然后 (show 1 2 3 4) 会显示 First: 1 Rest: (2 3 4)。
(apply + '(1 2 3)) 得 6。
vector)
(define (list-set lst n val)
(if (null? lst)
'()
(if (= n 0)
(cons val (cdr lst))
(cons (car lst)
(list-set (cdr lst) (- n 1) val)))))
使用:(list-set '(1 2 3 4) 2 99) 得到 (1 2 99 4)。
(path.cwd) 返回当前目录,(path.cwd 新目录) 改变当前目录 新目录 可以是相对的。
(file 文件名 :write :create) 打开文件用于写入,若没有就创建。返回一个 iostream 类型,以下称为 流。
:read — open for reading.
:write — open for writing.
:create — create the file if it doesn’t exist.
:append — open for appending (writing to end of file).
:truncate — truncate (clear) existing file contents when opening for writing.
:text — text mode (default).
:binary — binary mode.
(io.write 流 "abc\n") 写入并返回写入的字符数。
(io.close 流) 关闭文件,若成功返回 #t
(io.eof? 流) 检查是否读到了文件末尾
(iostream? 变量) 判断变量是否为流
(io.getc 流) 读取一个字符,指针加一
(io.peekc 流) 读取一个字符,指针不动
(io.putc 流) 写入一个字符
(buffer) 创建类似 std::sstream,类型也是 iostream,操作和文件流类似。
(io.seek 流 索引) 移动指针到索引
(io.pos 流) 返回当前索引
aliases.scm 定义了一个类似于 std::stringstream 的构造函数
(define (open-input-string str)
(let ((b (buffer))) (io.write b str) (io.seek b 0) b))
(define-macro (name arg1 arg2 ...) body...) 定义一个宏。
prog1 宏,若要重命名为 begin0,则用 (define-macro (begin0 first . rest) `(prog1 ,first ,@rest))