-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbash_codegen.ml
More file actions
108 lines (98 loc) · 4.52 KB
/
Copy pathbash_codegen.ml
File metadata and controls
108 lines (98 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
(* SPDX-License-Identifier: MPL-2.0 *)
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell *)
(** Bash/POSIX shell emitter (heavily restricted MVP).
Bash is genuinely limited: no first-class numbers, no closures, no
structures. We accept Int-only programs whose entry returns Int (used
as the shell exit code). Functions are emitted as Bash functions that
[echo] their result; callers read it via [$(fn args)]. *)
open Ast
exception Bash_unsupported of string
let unsupported m = raise (Bash_unsupported m)
let bash_reserved = [
"if"; "then"; "else"; "elif"; "fi"; "case"; "esac";
"for"; "while"; "until"; "do"; "done"; "function"; "return";
"in"; "break"; "continue"; "exit"; "true"; "false";
]
let mangle s = if List.mem s bash_reserved then s ^ "_" else s
(* Bash arithmetic only: every expression must evaluate to an integer. *)
let rec gen_expr (e : expr) : string =
match e with
| ExprLit (LitInt (n, _)) -> string_of_int n
| ExprLit (LitBool (true, _)) -> "1"
| ExprLit (LitBool (false, _))-> "0"
| ExprLit _ -> unsupported "Bash backend accepts only Int/Bool literals"
| ExprVar id -> "$" ^ mangle id.name
| ExprBinary (a, op, b) ->
let s = match op with
| OpAdd -> "+" | OpSub -> "-" | OpMul -> "*" | OpDiv -> "/" | OpMod -> "%"
| OpEq -> "==" | OpNe -> "!="
| OpLt -> "<" | OpLe -> "<=" | OpGt -> ">" | OpGe -> ">="
| OpAnd -> "&&" | OpOr -> "||"
| OpBitAnd -> "&" | OpBitOr -> "|" | OpBitXor -> "^"
| OpShl -> "<<" | OpShr -> ">>"
| OpConcat -> unsupported "string concat not supported in Bash backend"
in
"(" ^ gen_expr a ^ " " ^ s ^ " " ^ gen_expr b ^ ")"
| ExprUnary (OpNeg, x) -> "(-" ^ gen_expr x ^ ")"
| ExprUnary (OpNot, x) -> "(! " ^ gen_expr x ^ ")"
| ExprUnary _ -> unsupported "unary op not supported in Bash backend"
| ExprApp (callee, args) ->
let name = match callee with
| ExprVar id -> id.name
| _ -> unsupported "indirect call not supported in Bash backend"
in
let arg_strs = List.map gen_expr args in
(* Call a Bash function and capture its echo: $(fn 1 2) *)
Printf.sprintf "$(%s %s)" (mangle name) (String.concat " " arg_strs)
| ExprIf { ei_cond; ei_then; ei_else } ->
let c = gen_expr ei_cond in
let t = gen_expr ei_then in
let f = match ei_else with Some e -> gen_expr e | None -> "0" in
(* Bash arithmetic ternary: $((c ? t : f)) — works in all $((...)) *)
Printf.sprintf "(%s ? %s : %s)" c t f
| ExprLet { el_pat = PatVar _; _ } ->
unsupported "expression-position let not supported in Bash backend"
| ExprSpan (inner, _) -> gen_expr inner
| ExprBlock _ -> unsupported "block expressions not supported in Bash backend"
| _ -> unsupported "expression form not supported in Bash backend"
let gen_function (fd : fn_decl) : string =
let name = mangle fd.fd_name.name in
let params = fd.fd_params in
let body_expr = match fd.fd_body with
| FnExpr e -> e
| FnBlock { blk_stmts = []; blk_expr = Some e } -> e
| FnBlock _ ->
unsupported "Bash backend accepts only single-expression function bodies"
| FnExtern ->
unsupported "Bash backend has no story for `extern fn` (no host-link concept)"
in
let body_str = gen_expr body_expr in
let buf = Buffer.create 128 in
Buffer.add_string buf (name ^ "() {\n");
List.iteri (fun i (p : param) ->
Buffer.add_string buf
(Printf.sprintf " local %s=$%d\n" (mangle p.p_name.name) (i + 1))
) params;
Buffer.add_string buf (Printf.sprintf " echo $((%s))\n}\n\n" body_str);
Buffer.contents buf
let generate (program : program) (_symbols : Symbol.t) : string =
let buf = Buffer.create 1024 in
Buffer.add_string buf "#!/usr/bin/env bash\n";
Buffer.add_string buf "# Generated by AffineScript compiler\n";
Buffer.add_string buf "# SPDX-License-Identifier: MPL-2.0\n";
Buffer.add_string buf "set -euo pipefail\n\n";
List.iter (function
| TopFn fd -> Buffer.add_string buf (gen_function fd)
| _ -> ()
) program.prog_decls;
let has_main = List.exists (function
| TopFn fd -> fd.fd_name.name = "main"
| _ -> false) program.prog_decls in
if has_main then Buffer.add_string buf "exit $(main)\n";
Buffer.contents buf
let codegen_bash (program : program) (symbols : Symbol.t) : (string, string) result =
try Ok (generate program symbols)
with
| Bash_unsupported msg -> Error ("Bash backend: " ^ msg)
| Failure msg -> Error ("Bash codegen error: " ^ msg)
| e -> Error ("Bash codegen error: " ^ Printexc.to_string e)