Skip to content

Commit 6d27c21

Browse files
committed
Add initial compiler from linear lambda-calculus to stellogen
1 parent debf3f9 commit 6d27c21

File tree

8 files changed

+303
-25
lines changed

8 files changed

+303
-25
lines changed

bin/cosmog.ml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
open Base
2+
open Cmdliner
3+
open Stellogen
4+
5+
let run input_file =
6+
let lexbuf = Sedlexing.Utf8.from_channel (Stdlib.open_in input_file) in
7+
let start_pos filename =
8+
{ Lexing.pos_fname = filename; pos_lnum = 1; pos_bol = 0; pos_cnum = 0 }
9+
in
10+
Sedlexing.set_position lexbuf (start_pos input_file);
11+
let lexer = Sedlexing.with_tokenizer Cosmog_lexer.read lexbuf in
12+
let parser =
13+
MenhirLib.Convert.Simplified.traditional2revised Cosmog_parser.expr_file
14+
in
15+
parser lexer |> Cosmog_compile.compile
16+
|> List.map ~f:Expr.Raw.to_string
17+
|> String.concat ~sep:"\n"
18+
|> fun s ->
19+
let oc = Stdlib.open_out "out.sg" in
20+
Stdlib.output_string oc s;
21+
Stdlib.close_out oc
22+
23+
let input_file_arg =
24+
let doc = "Input file to process." in
25+
Arg.(required & pos 0 (some string) None & info [] ~docv:"FILENAME" ~doc)
26+
27+
let wrap f input_file =
28+
try Ok (f input_file) with e -> Error (`Msg (Stdlib.Printexc.to_string e))
29+
30+
let compile_cmd =
31+
let term = Term.(const (wrap run) $ input_file_arg |> term_result) in
32+
Cmd.v (Cmd.info "compile" ~doc:"Compile a mini ML program") term
33+
34+
let default_cmd =
35+
let doc = "Cosmographer: compile mini-ML to Stellogen" in
36+
Cmd.group (Cmd.info "cosmog" ~doc) [ compile_cmd ]
37+
38+
let () = Stdlib.exit (Cmd.eval default_cmd)

bin/dune

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
(executables
2-
(public_names sgen)
3-
(names sgen)
2+
(public_names sgen cosmog)
3+
(names sgen cosmog)
44
(libraries stellogen base cmdliner))
55

66
(env

src/cosmog_compile.ml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
open Base
2+
3+
let pos x = Expr.Raw.Symbol ("+" ^ x)
4+
5+
let neg x = Expr.Raw.Symbol ("-" ^ x)
6+
7+
let out x = x ^ "_out"
8+
9+
let func es = Expr.Raw.List es
10+
11+
let var x = Expr.Raw.Var x
12+
13+
let star es = Expr.Raw.Cons es
14+
15+
let interact e = Expr.Raw.List [ Expr.Raw.Symbol "interact"; e ]
16+
17+
let group es = Expr.Raw.Group es
18+
19+
let def x t = Expr.Raw.List [ Expr.Raw.Symbol ":="; Expr.Raw.Symbol x; t ]
20+
21+
let show x = Expr.Raw.List [ Expr.Raw.Symbol "show"; x ]
22+
23+
let id x = Expr.Raw.Call (Expr.Raw.Symbol x)
24+
25+
let add s = function
26+
| Expr.Raw.Cons es -> Expr.Raw.Cons (Expr.Raw.Symbol s :: es)
27+
| e -> Expr.Raw.Cons [ Expr.Raw.Symbol s; e ]
28+
29+
let inject_lr = function
30+
| Expr.Raw.Cons [ Expr.Raw.List [ h1; a1 ]; Expr.Raw.List [ h2; a2 ] ] ->
31+
Expr.Raw.Cons
32+
[ Expr.Raw.List [ h1; add "l" a1 ]; Expr.Raw.List [ h2; add "r" a2 ] ]
33+
| _ -> failwith "Compiler error: could not apply inject_lr"
34+
35+
(* FIXME *)
36+
let rec compile_expr e =
37+
if not @@ Lambda.is_linear e then
38+
failwith
39+
(Printf.sprintf "Compiler error: term '%s' is not linear."
40+
(Lambda.to_string e) );
41+
match e.content with
42+
| Lambda.Var _ ->
43+
[ star [ func [ pos e.loc; var "X" ]; func [ pos (out e.loc); var "X" ] ] ]
44+
| Lambda.Fun (_x, _t) ->
45+
[ star [ func [ pos e.loc; var "X" ]; func [ pos (out e.loc); var "X" ] ]
46+
|> inject_lr
47+
]
48+
| Lambda.App (t1, t2) ->
49+
let cuts =
50+
star
51+
[ func [ neg (t1.loc ^ "_out"); var "X" ]
52+
; func [ neg (t2.loc ^ "_out"); var "X" ]
53+
]
54+
in
55+
let out = star [ func [ pos (e.loc ^ "_out"); var "X" ] ] in
56+
[ cuts; out ] @ compile_expr t1 @ compile_expr t2
57+
58+
let compile_decl = function
59+
| Lambda.Let (x, t) -> [ def x (group (compile_expr t)) ]
60+
| Lambda.Print x -> [ show (interact (id x)) ]
61+
62+
let compile : Lambda.program -> Expr.Raw.t list =
63+
fun e -> List.map ~f:compile_decl e |> List.concat

src/cosmog_lexer.ml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
open Cosmog_parser
2+
3+
let space = [%sedlex.regexp? Plus (' ' | '\t')]
4+
5+
let newline = [%sedlex.regexp? '\r' | '\n' | "\r\n"]
6+
7+
let rec comments lexbuf =
8+
let tok =
9+
match%sedlex lexbuf with
10+
| "*)" | eof -> read lexbuf
11+
| _ ->
12+
ignore (Sedlexing.next lexbuf);
13+
comments lexbuf
14+
in
15+
tok
16+
17+
and read lexbuf =
18+
match%sedlex lexbuf with
19+
| "fun" -> FUN
20+
| "let" -> LET
21+
| "print" -> PRINT
22+
| 'a' .. 'z', Star ('a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '\'') ->
23+
let id = Sedlexing.Utf8.lexeme lexbuf in
24+
IDENT id
25+
| '(' -> LPAR
26+
| ')' -> RPAR
27+
| "->" -> RARROW
28+
| "=" -> EQ
29+
| "(*" -> comments lexbuf
30+
| '"' -> string_literal lexbuf
31+
| space -> read lexbuf
32+
| newline -> read lexbuf
33+
| eof -> EOF
34+
| _ -> failwith "Unexpected symbol in lexing."
35+
36+
and string_literal lexbuf =
37+
let buffer = Buffer.create 32 in
38+
let rec loop () =
39+
match%sedlex lexbuf with
40+
| '"' -> STRING (Buffer.contents buffer)
41+
| '\\', any ->
42+
let escaped =
43+
match%sedlex lexbuf with
44+
| 'n' -> '\n'
45+
| 't' -> '\t'
46+
| '\\' -> '\\'
47+
| '"' -> '"'
48+
| _ -> failwith "Unknown escape sequence"
49+
in
50+
Buffer.add_char buffer escaped;
51+
loop ()
52+
| eof -> failwith "Unterminated string literal"
53+
| any ->
54+
Buffer.add_string buffer (Sedlexing.Utf8.lexeme lexbuf);
55+
loop ()
56+
| _ -> failwith "Invalid character in string literal"
57+
in
58+
loop ()

src/cosmog_parser.mly

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
%{
2+
open Lambda
3+
4+
let counter = ref 0
5+
6+
let fresh_loc content =
7+
let n = !counter in
8+
counter := n + 1;
9+
{ content; loc = string_of_int n }
10+
%}
11+
12+
%token <string> IDENT
13+
%token <string> STRING
14+
%token FUN
15+
%token RARROW
16+
%token EQ
17+
%token LET
18+
%token PRINT
19+
%token LPAR RPAR
20+
%token EOF
21+
22+
%start <Lambda.program> expr_file
23+
24+
%%
25+
26+
let delimited_opt(l, x, r) :=
27+
| ~=x; <>
28+
| ~=delimited(l, x, r); <>
29+
30+
let pars(x) == ~=delimited(LPAR, x, RPAR); <>
31+
32+
let expr_file :=
33+
| EOF; { [] }
34+
| es=decl+; EOF; { es }
35+
36+
let decl :=
37+
| LET; x=IDENT; EQ; e=expr; { Let (x, fresh_loc e) }
38+
| PRINT; ~=IDENT; <Print>
39+
40+
let expr :=
41+
| ~=pars(expr); <>
42+
| x=IDENT;
43+
{ Var x }
44+
| FUN; x=IDENT; RARROW; e=expr;
45+
{ Fun (fresh_loc x, fresh_loc e) }
46+
| LPAR; e1=expr; e2=expr; RPAR;
47+
{ App (fresh_loc e1, fresh_loc e2) }

src/dune

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@
1212
(menhir
1313
(modules parser)
1414
(flags --table --dump --explain))
15+
16+
(menhir
17+
(modules cosmog_parser)
18+
(flags --table --dump --explain))

src/expr.ml

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,6 @@ open Expr_err
55

66
let ( let* ) x f = Result.bind x ~f
77

8-
type ident = string
9-
10-
module Raw = struct
11-
type t =
12-
| Symbol of string
13-
| Var of ident
14-
| String of string
15-
| Focus of t
16-
| Call of t
17-
| List of t list
18-
| Stack of t list
19-
| Group of t list
20-
| Cons of t list
21-
| ConsWithParams of t list * t list
22-
| ConsWithBase of t list * t
23-
end
24-
25-
type expr =
26-
| Symbol of string
27-
| Var of ident
28-
| List of expr list
29-
[@@derive eq]
30-
318
let primitive = String.append "%"
329

3310
let nil_op = primitive "nil"
@@ -52,6 +29,53 @@ let incomp_op = "slice"
5229

5330
let group_op = "%group"
5431

32+
type ident = string
33+
34+
module Raw = struct
35+
type t =
36+
| Symbol of string
37+
| Var of ident
38+
| String of string
39+
| Focus of t
40+
| Call of t
41+
| List of t list
42+
| Stack of t list
43+
| Group of t list
44+
| Cons of t list
45+
| ConsWithParams of t list * t list
46+
| ConsWithBase of t list * t
47+
48+
let rec to_string : t -> string = function
49+
| Symbol s -> s
50+
| Var x -> x
51+
| String s -> Printf.sprintf "\"%s\"" s
52+
| Focus e -> Printf.sprintf "%s%s" focus_op (to_string e)
53+
| Call e -> Printf.sprintf "%s%s" call_op (to_string e)
54+
| List es ->
55+
Printf.sprintf "(%s)" (List.map ~f:to_string es |> String.concat ~sep:" ")
56+
| Stack es ->
57+
Printf.sprintf "<%s>" (List.map ~f:to_string es |> String.concat ~sep:" ")
58+
| Group es ->
59+
Printf.sprintf "{ %s }"
60+
(List.map ~f:to_string es |> String.concat ~sep:" ")
61+
| Cons es ->
62+
Printf.sprintf "[%s]" (List.map ~f:to_string es |> String.concat ~sep:" ")
63+
| ConsWithParams (es1, es2) ->
64+
Printf.sprintf "[%s | %s]"
65+
(List.map ~f:to_string es1 |> String.concat ~sep:" ")
66+
(List.map ~f:to_string es2 |> String.concat ~sep:" ")
67+
| ConsWithBase (es, e) ->
68+
Printf.sprintf "[%s|%s]"
69+
(List.map ~f:to_string es |> String.concat ~sep:" ")
70+
(to_string e)
71+
end
72+
73+
type expr =
74+
| Symbol of string
75+
| Var of ident
76+
| List of expr list
77+
[@@derive eq]
78+
5579
let rec to_string : expr -> string = function
5680
| Symbol s -> s
5781
| Var x -> x

src/lambda.ml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
open Base
2+
3+
type ident = string
4+
5+
type 'a loc =
6+
{ content : 'a
7+
; loc : string
8+
}
9+
10+
type expr =
11+
| Var of ident
12+
| Fun of (ident loc * expr loc)
13+
| App of (expr loc * expr loc)
14+
15+
type env = (ident * expr) list
16+
17+
type declaration =
18+
| Let of ident * expr loc
19+
| Print of ident
20+
21+
type program = declaration list
22+
23+
let rec to_string e =
24+
match e.content with
25+
| Var x -> x
26+
| Fun (x, t) -> Printf.sprintf "fun %s -> %s" x.content (to_string t)
27+
| App (t1, t2) -> Printf.sprintf "(%s %s)" (to_string t1) (to_string t2)
28+
29+
let rec free_vars e =
30+
match e.content with
31+
| Var x -> [ x ]
32+
| Fun (x, t) ->
33+
List.filter (free_vars t) ~f:(fun y -> not @@ equal_string x.content y)
34+
| App (t1, t2) -> free_vars t1 @ free_vars t2
35+
36+
let rec is_linear e =
37+
match e.content with
38+
| Var _ -> true
39+
| Fun (x, t) ->
40+
is_linear t
41+
&& List.length
42+
(List.filter (free_vars t) ~f:(fun y -> equal_string x.content y))
43+
= 1
44+
| App (t1, t2) -> is_linear t1 && is_linear t2

0 commit comments

Comments
 (0)