English | 简体中文
基于纯 C++20 和它的 STL。
- C++17 for
std::varient
(变体),折叠表达式,结构化绑定,if
的初始化语句,等等; - C++20 for
std::unordered_set
的contains
、std::string
的starts_with
和ends_with
。
mkdir -p _build && cd _build
cmake ..
make
emehcs_demo
仓库中可以在 exec
目录下找到对应平台的可执行文件直接使用。
./emehcs # 启动 REPL
./emehcs filename.scm # 解释运行 Scheme/Emehcs 源文件
Emehcs 它的所有语句都由列表(List)构成,组成语言的基本单位是原子(Atom),所有的求值均为函数调用。
举例一个最基本的加法运算:
(+ 1 2)
; => 3
举例嵌套的基本运算:
(+ 2 (* 3 2))
; => 8
Emehcs 的单行注释由 ;
开头。(暂时不支持注释,不要写)
Emehcs 包含以下基本数据类型,以及与它们对应的 C++ 数据结构:
Emehcs 数据类型 | C++ 数据类型 |
---|---|
Atom | struct {std::string} |
List | std::deque<ValueP> |
DottedList | std::pair<std::deque<ValueP>, ValueP> |
Number | std::int64_t |
Char | char |
String | std::string |
Bool | bool |
Function | struct {std::deque<std::string>, std::deque<ValueP>, EnvironmentP} |
因为 Lisp 的主旨是用函数表示数据,所以暂时不支持复杂型数据结构。
列举各种类型的字面量:
以字母或符号开头的、包含若干字母或符号或数字的串,如 atom
、number42
、--perhaps-an-atom
。
用于调用函数、求值用的列表,使用一对圆括号内含元素表示,元素之间使用若干空白字符隔开。如:
; 不可以求值的用于存储数据的列表要使用单引号引用
'(1 2 3)
; 函数调用
(+ 1 2 3)
; 这下面两行也是一个列表
'(#\a 3
#\b #\c)
在 Lisp 中列表是由点列表组成的,但是在 Emehcs 中,点列表的意义不明,可以自行测试 car
、cdr
、cons
等函数。
内部由一个列表和单个元素组成,与列表同样使用圆括号内含元素表示,但是在尾元素的前面需要额外添加一个 Atom .
的元素,表示这是一个点列表。列表可以为空,但是尾部元素一定不能为空。如
'(1 . 3)
'(1 2 3 . 4)
'( . 1)
Emehcs 的数字面量目前只可以表示非负整数,但是数可以存储负整数。分别有十进制、~~二进制、~~八进制、十六进制的字面量。比如:
; 十进制整数 42
42
; 使用 `-0` 函数计算得到的负数 -42
(-0 42)
; 二进制数(论文里写了这个,但是并不支持)
;;;#b10100010
; 八进制数
#o12345670
; 十六进制数
#xABCD
#xabcd
字符字面量以 #\
开头,后接一个字符,该字符可以为转义字符:
; 字符 a
#\a
; 字符 1
#\1
; 换行符
#\\n
; Scheme 的空格表示暂时不支持
;;;#\space
使用一对双引号包括,可以转义:
"hello \\ \"world\"\n"
二择,#t
和 #f
,分别用来表示布尔真值和布尔假值。
函数使用 lambda 表达式表示。lambda 表达式由 lambda
关键字、参数列表和函数体构成,函数体的最后一个部分为其返回值。举例:
; 普通的加法函数
(lambda (x y)
(+ x y))
; 函数变量作为形式参数
(lambda (foo x)
(foo (* 2 x)))
; 高阶函数
(lambda (binary-func p1)
(lambda (p2)
(binary-func p1 p2)))
; 顺序式过程(不推荐在面向函数的语言中这么写代码
; 参数列表可以为空
(define s "aaa")
(lambda ()
(string-replace! s #\a #\b)
(string-replace! s #\b #\c))
; => "ccc"
目前只可以定义在全局环境中的全局变量(在 Emehcs 中函数也是变量或者值)。
使用 define
关键字:
; 定义一个整数
(define god 42)
; 定义一个字符串
(define str "hello world")
; 定义一个函数(使用 lambda 表达式)
(define foo (lambda (x y)
(+ x y)))
; 定义一个函数(使用 define 提供的特殊形式)
(define (foo2 x y) ; 将函数名 foo2 放到参数列表中,将 lambda 关键字替换成 define
(+ x y))
; 求斐波那契
(define (fib n) (fib-iter n 1))
(define (fib-iter n i)
(if (|| (= n 1) (= n 2))
1
(+ (fib (- n 1)) (fib (- n 2))))
(fib 10)
Emehcs 提供两种特殊函数作为分支语句,if
和 cond
。使用方法如下:
(if condition true-clause false-clause)
(cond (condition1 clause1)
(condition2 clause2)
...
(else else-clause))
举例,如:
(if (/= 1 42) "Normal" "World ends")
; => "Normal"
(cond ((> 3 4) 1)
((< 4 3) 2)
(#t 3)) ; 使用 #t 或 else 均可
; => 3
Emehcs 暂时不提供循环语句。可以用尾递归实现循环,比如:
(define (iota-sum n)
(if (= n 0)
0
(+ n (iota-sum (- n 1)))))
(iota-sum 1000)
; => 500500
但是要注意现阶段 Emehcs 还没有尾递归优化,所以上面的递归参数再大点程序就会崩溃了。
暂时没有时间为每一个函数写文档,我写点注释,函数功能基本跟 Scheme 等同:
const ... BuiltInFunctor{
{"quote", funcQuote},
{"if", funcIf},
{"cond", funcCond},
{"define", funcDefine},
// `string` 是用字符拼接字符串的函数,可以接受若干字符
{"string", funcString},
};
// 单参数函数
const ... UnaryOps {
{"-0", numericUnopMinus},
{"not", boolBoolUnopNot},
{"car", listCar},
{"cdr", listCdr},
{"load", loadFromFile},
{"list->string", strList2String},
{"string->list", strString2List},
{"string-copy", strStringCopy},
{"string?", strIsString},
{"string-length", strStringLength},
{"string-null?", strIsStringNull},
{"string-hash", strStringHash},
{"string-capitalized?", strIsStringCapitalized},
{"string-upper-case?", strIsStringUpperCase},
{"string-lower-case?", strIsStringLowerCase},
{"string-capitalize", strStringCapitalize},
{"string-capitalize!", strStringCapitalizeBang},
{"string-downcase", strStringDowncase},
{"string-downcase!", strStringDowncaseBang},
{"string-upcase", strStringUpcase},
{"string-upcase!", strStringUpcaseBang},
{"string-trim", strStringTrim},
{"string-trim-left", strStringTrimLeft},
{"string-trim-right", strStringTrimRight},
{"print", lispPrintString},
};
// 二参数函数
const ... BinaryOps {
{"=", numBoolBinopEq},
{"<", numBoolBinopL},
{"<=", numBoolBinopLe},
{">", numBoolBinopG},
{">=", numBoolBinopGe},
{"/=", numBoolBinopNeq},
{"string=?", strBoolBinopEq},
{"string<?", strBoolBinopL},
{"string<=?", strBoolBinopLe},
{"string>?", strBoolBinopG},
{"string>=?", strBoolBinopGe},
{"cons", listCons},
{"eq?", eqv},
{"eqv?", eqv},
{"make-string", strMakeString},
{"assert", EqAssert},
{"string-ref", strStringRef},
{"string-head", strStringHead},
{"string-tail", strStringTail},
{"string-pad-left", strStringPadLeft},
{"string-pad-right", strStringPadRight},
{"substring?", strIsSubstring},
{"string-prefix?", strIsStringPrefix},
{"string-suffix?", strIsStringSuffix},
{"vector-8b-ref", strVector8bRef},
};
// 至少二参数的可以多参数的函数
const ... FoldOps {
{"+", numericBinopPlus},
{"-", numericBinopMinus},
{"*", numericBinopTimes},
{"/", numericBinopDivide},
{"mod", numericBinopMod},
{"quotient", numericBinopQuot},
{"remainder", numericBinopRem},
{"&&", boolBoolBinopAnd},
{"||", boolBoolBinopOr},
{"string-append", strStringAppend}
};
// 三参数函数
const ... TernaryOps {
{"substring", strSubstring},
{"string-set!", strStringSetBang},
{"string-replace", strStringReplace},
{"string-replace!", strStringReplaceBang},
};
(load filename)
这是一个比较特殊的函数,可以导入已经写好的 Emehcs/Scheme 源代码。
比如在 REPL 环境中直接运行测试代码:
>>> (load "string.scm")
Test (assert (make-string 1 #\a) "a")
Test (assert (make-string 3 #\a) "aaa")
Test (assert (string) "")
Test (assert (string #\a #\b #\c) "abc")
Test (assert (list->string '(#\a #\b #\c)) "abc")
...
All test cases were correct and finished.
eval[0]: #t
>>> %
暂时只支持一个特殊的文件头,*test
,可以写成 * test
。放在 Emehcs/Scheme 源代码的第一行才能识别。配合 assert
函数使用,具体可以参考 string.scm
提供的样例。
好了,你现在已经完全学会怎么使用 Emehcs 了,那么尝试用它帮我写一些测试代码吧!可以使用 GitHub 的 Pull Request 功能提交新的 Emehcs/Scheme 测试脚本帮助我完善我的 Emehcs 程序。