这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」
一元表达式(Unary operations)
(add1 x)
x 加一(sub1 x)
x 减一
优化 s-expression
接着s-expression一讲中, 如果我们想解析如下的表达式
let e1 = parse "(*(+2 6 4) 3)"
其中,parse
输入一个字符串,然后转换成对应的字符串。之前的compile.ml
修改,
open S_exp
let compile (program: s_exp): string =
match program with
| Num n ->
String.concat "\n"
[ "global _entry";
"_entry:";
Printf.sprintf "\tmov rax, %d" n;
"\tret" ]
简单起见,我们先知考虑数字。这样我们的编辑器会发出错误,“non-exhaustive pattern matching”,这意味着s-expression没有匹配。
open S_exp
exception BadExpression of s_exp
let compile (program: s_exp): string =
match program with
| Num n ->
String.concat "\n"
[ "global _entry";
"_entry:";
Printf.sprintf "\tmov rax, %d" n;
"\tret" ]
| e -> raise (BadExpression e)
这里有几个问题:首先,目前为止我们的编译器只能在MacOS上运行;在Linux上,标签不能有下划线,但是MacOS必须有。与之相关的,目前的代码很难读。mov
和ret
指令都有制表符,并且还有springf
系统调用,等等。
我们可以使用s-expression来解决这个问题。
使用 Asm 库来重新编写我们的代码
open Asm
(* some definitions elided... *)
let compile (exp: s_exp) : string =
match exp with
| Num n ->
[Global "entry"; Label "entry"; Mov (Reg Rax, Imm n); Ret]
|> List.map string_of_directive |> String.concat "\n"
| _ -> raise (BadExpression exp)
上面的代码使用了管道操作符|>
。
好的,我们接下来添加add1
和sub1
。这两个操作都是一元操作符,它们都只有一个参数。
我们首先开始add1
。在汇编语言中,(add1 50)
很容易实现:
global entry
entry:
mov rax, 50
add rax, 1
ret
所以,我们可以添加操作:
let compile (exp: s_exp) : string =
match exp with
| Num n ->
[Global "entry"; Label "entry"; Mov (Reg Rax, Imm n); Ret]
|> List.map string_of_directive |> String.concat "\n"
| Lst [Sym "add1"; arg] ->
(* what should we put here? *)
| _ -> raise (BadExpression exp)
如果怎么做,会出现一些问题。首先,我们需要包含global entry
、entry
和ret
。会看起来很重复。我们重新整合一下:
let compile_exp (exp: s_exp) : directive list =
match exp with
| Num n ->
[Mov (Reg Rax, Imm n)]
| Lst [Sym "add1"; arg] ->
(* what should we put here? *)
| _ -> raise (BadExpression exp)
let compile (program : s_exp) : string =
[Global "entry"; Label "entry"] @
compile_exp program @
[Ret] |> List.map string_of_directive |> String.concat "\n"
我们添加了一个辅助函数compile_exp
,这里我们不需要关系样板代码比如我们的标记或者返回的指令。这样整个的add1
操作如下:
let rec compile_exp (exp: s_exp) : directive list =
match exp with
| Num n ->
[Mov (Reg Rax, Imm n)]
| Lst [Sym "add1"; arg] ->
compile_exp arg @
[Add (Reg Rax, Imm 1)]
| _ -> raise (BadExpression exp)
于此类似,sub1
的操作如下:
let rec compile_exp (exp: s_exp) : directive list =
match exp with
| Num n ->
[Mov (Reg Rax, Imm n)]
| Lst [Sym "add1"; arg] ->
compile_exp arg @
[Add (Reg Rax, Imm 1)]
| Lst [Sym "sub1"; arg] ->
compile_exp arg @
[Sub (Reg Rax, Imm 1)]
| _ -> raise (BadExpression exp)
\