Lush/编译
Lush 是一种解释型语言,但单个函数和类方法可以编译,要么是为了加快速度,要么是为了启用内联 C/C++ 代码。
函数和类方法可以编译,只需进行一些修改。以下是一个我们想要编译的简单函数
;; Prints the size, sum, and mean of a vector
(de print-mean (vec)
(let* ((v-size (idx-dim vec 0))
(v-sum (idx-sum vec)
(v-mean (/ v-sum v-size))
(printf "Sum = %f, size = %d, mean = %f\n" v-sum v-size v-mean)
())
在下面,我们添加了一些类型声明来使函数可编译,并在其后添加了编译命令
;; Prints the size, sum, and mean of a vector
(de print-mean (vec)
((-idx1- (-double-)) vec)
(let* ((v-size (idx-dim vec 0))
(v-sum (idx-sum vec)
(v-mean (/ v-sum v-size))
((-int-) v-size)
(printf "Sum = %f, size = %d, mean = %f\n" v-sum v-size v-mean)
())
; compile "print-mean"
(dhc-make () print-mean)
要使 Lush 函数可编译,必须
- 声明任何类型不明确的对象的类型。
- 仅使用可编译函数和表达式。
- 在源文件末尾的编译语句中列出该函数。
在上面的函数中,紧随参数之后的几行声明了参数类型。请注意,该函数没有声明其他任何变量的类型。这是因为您只需要为函数参数和非双精度数值类型声明类型。 这是因为除非指定,否则函数参数类型在编译时并不明显,而 Lush 假定所有数字都是 double
,除非另行说明。
所有类型声明都采用以下形式
((type_name) var_name)
例如
((-int-) my-int)
以下列出了可编译 Lush 类型的类型名称,以及它们相应的 C++ 等效类型
Lush 类型 | C++ 类型 | 描述 |
---|---|---|
-int-
|
int
|
32 位整数 |
-float-
|
float
|
32 位实数 |
-double-
|
double
|
64 位实数 |
-short-
|
short
|
16 位整数 |
-byte-
|
char
|
8 位整数 |
-ubyte-
|
unsigned char
|
8 位无符号整数 |
-gptr-
|
void*
|
类型无关指针(大小取决于平台)。 |
-gptr- "std::string"
|
std::string*
|
类型特定指针(大小与 -gptr- /void* 相同) |
-str-
|
?
|
Lush 字符串 |
-idx- (-double-)
|
?
|
Lush 张量(-double- 可以是任何基本类型名称。) |
-obj- (htable)
|
?
|
任何其他类(htable 是 Lush 哈希表的类名)。 |
* 张量不能包含类型特定指针。换句话说,以下情况不允许
;; NOT ALLOWED: a tensor of char* pointers ((-idx1- (-gptr- "char")) my-char-pointer-tensor)
但以下情况可以
;; A tensor of void* pointers ((-idx2- (-gptr-)) my-pointer-tensor)
不幸的是,并非所有 Lush 函数或表达式都可编译。大多数不可编译的函数是那些涉及列表操作的函数。更一般地说,Lush 的任何“Lisp 式”特性(lambda 表达式、钩子、代码操作)通常都不可编译。要查看特定函数是否可编译,请使用 compilablep
函数
? (compilablep double-matrix)
= t
? (compilablep range)
= ()
请注意,compilablep
只能接受单个函数和宏名称,不能接受复杂的 Lush 表达式。
虽然列表操作通常不可编译,但包含列表的表达式不一定不可编译。例如,idx-transclone
函数必须接受列表作为其参数之一,但仍然可编译。
在使您的函数适合编译后,您必须在源文件末尾调用编译函数才能实际编译它们。以下是一个函数和两个类方法,后面是编译函数 dhc-make
(de some-func (...)
... )
(de my-class object
... )
(defmethod my-class method-1 (...)
... )
(defmethod my-class method-2 (...)
... )
(dhc-make ()
(some-func)
(my-class method-1
method-2))
编译函数实际上执行 3 件事
- 将函数/方法转录成等效的 C 代码。
src-dir/src-file.lsh
文件的 C 源文件将位于src-dir/C/src_file.c
中。 - 使用 gcc 将此 C 源文件编译成目标文件。
- 将生成的目標文件链接到内存中。
只有当 C 源文件比 Lush 源文件旧时,编译函数才会运行。
您可以使用 #{ ... #}
结构将 C/C++ 代码插入 Lush 函数中。您可以非常密集地交织 Lush 和原生代码
下表显示了如何从 C/C++ 代码访问不同的 Lush 对象
类型 | Lush 名称 | C/C++ 名称 |
---|---|---|
基本类型 | my-double
|
$my_double
|
张量类型 | my-float-tensor
|
float* raw_data = IDX_PTR( $my_float_tensor, float )
|
字符串 | my-str
|
char* c_string = $my_str->data
|
对象槽 | :my-obj:some-slot
|
$my_obj->some_slot
|
对象方法 | (==> my-obj some-method arg1)
|
$my_obj->vtbl->M_some_method($arg1)
|
全局函数 | (add-numbers num1 num2)
|
C_add_numbers($num1, $num2)
|
全局常量 | @MY_CONSTANT
|
MY_CONSTANT
|
您可以将预处理器语句和其他 C 代码直接放入编译函数中
如果有很多这样的函数,您可以将它们放在 .h 和 .c 文件中,以提高可读性
(dhc-make ()
#{
#include "c_funcs.h"
#}
lush-func-1
lush-func-2
''...''
#{
#include "c_funcs.c"
#})
libload 可能返回 null;请注意。
lushmake。正确的 lushmake 语法?我使用的方式是,它不理会依赖关系。
将lush函数编译成C代码的一个问题是,你在函数中实例化的任何lush对象都会成为C版本函数中的参数。这些只在lush函数的C版本中出现的参数被称为“隐藏参数”。lush函数的签名(例如它接受的参数数量和类型)可能会发生变化。这是因为
返回nil
(也称为()
)的lush函数将在其C化身中返回一个char
。当您确实需要您的函数不返回任何内容时,这可能是一个问题,例如当将其用作某些期望void返回函数的C API 的回调函数时
在my-src.lsh
中返回nil
的函数
(de my-callback ()
...
())
...在C/my_src.c
中变成了一个返回char
的函数
char* C_my_callback(){
...
}
在这种情况下,您最好的选择是在一个返回void的C函数中包装lush函数。那么问题是:如何编译这个包装函数?最简单的方法是在my-src.lsh
结尾的编译函数中直接放入它
(dhc-make ()
#{ void callback_wrapper(); #}
my-callback
''... other lush functions & methods ...''
''...''
''...''
#{ void callback_wrapper(){ C_my_callback(); } #} )
栈还是堆?:
包装的C++类的动态分配:类型转换错误
在决定是否真的要编译lush代码之前,有几个问题需要考虑。
to be compiled can be more of a hassle than writing the same code in c/c++, for a number of reasons:
- 可编译的Lush不如C++功能丰富或视觉上整洁
- Lush编译器错误可能比C/C++编译器错误更加不透明,尤其是在涉及宏的情况下。
- 调试能力的损失