OCaml and unary operation

254 阅读2分钟

这是我参与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必须有。与之相关的,目前的代码很难读。movret指令都有制表符,并且还有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)

上面的代码使用了管道操作符|>

好的,我们接下来添加add1sub1。这两个操作都是一元操作符,它们都只有一个参数。

我们首先开始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 entryentryret。会看起来很重复。我们重新整合一下:

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)

\