Publish the project.
This commit is contained in:
parent
0ee19236b0
commit
19ff31e4c9
47 changed files with 2923 additions and 0 deletions
9
projet/src/.merlin
Normal file
9
projet/src/.merlin
Normal file
|
@ -0,0 +1,9 @@
|
|||
S kremlin
|
||||
S alphalib
|
||||
B _build
|
||||
B _build/kremlin
|
||||
B _build/alphalib
|
||||
PKG unix
|
||||
PKG process
|
||||
PKG pprint
|
||||
PKG ppx_deriving.std
|
7
projet/src/CPS.ml
Normal file
7
projet/src/CPS.ml
Normal file
|
@ -0,0 +1,7 @@
|
|||
(* The source calculus. *)
|
||||
module S = Lambda
|
||||
(* The target calculus. *)
|
||||
module T = Tail
|
||||
|
||||
let cps_term (t : S.term) : T.term =
|
||||
assert false
|
4
projet/src/CPS.mli
Normal file
4
projet/src/CPS.mli
Normal file
|
@ -0,0 +1,4 @@
|
|||
(* Through a CPS transformation, the surface language [Lambda] is translated
|
||||
down to the intermediate language [Tail]. *)
|
||||
|
||||
val cps_term: Lambda.term -> Tail.term
|
54
projet/src/Cook.ml
Normal file
54
projet/src/Cook.ml
Normal file
|
@ -0,0 +1,54 @@
|
|||
open Error
|
||||
|
||||
(* The source calculus. *)
|
||||
module S = RawLambda
|
||||
(* The target calculus. *)
|
||||
module T = Lambda
|
||||
|
||||
(* Environments map strings to atoms. *)
|
||||
module Env =
|
||||
Map.Make(String)
|
||||
|
||||
(* [bind env x] creates a fresh atom [a] and extends the environment [env]
|
||||
with a mapping of [x] to [a]. *)
|
||||
let bind env x =
|
||||
let a = Atom.fresh x in
|
||||
Env.add x a env, a
|
||||
|
||||
let rec cook_term env { S.place; S.value } =
|
||||
match value with
|
||||
| S.Var x ->
|
||||
begin try
|
||||
T.Var (Env.find x env)
|
||||
with Not_found ->
|
||||
error place "Unbound variable: %s" x
|
||||
end
|
||||
| S.Lam (x, t) ->
|
||||
let env, x = bind env x in
|
||||
T.Lam (T.NoSelf, x, cook_term env t)
|
||||
| S.App (t1, t2) ->
|
||||
T.App (cook_term env t1, cook_term env t2)
|
||||
| S.Lit i ->
|
||||
T.Lit i
|
||||
| S.BinOp (t1, op, t2) ->
|
||||
T.BinOp (cook_term env t1, op, cook_term env t2)
|
||||
| S.Print t ->
|
||||
T.Print (cook_term env t)
|
||||
| S.Let (S.NonRecursive, x, t1, t2) ->
|
||||
let t1 = cook_term env t1 in
|
||||
let env, x = bind env x in
|
||||
let t2 = cook_term env t2 in
|
||||
T.Let (x, t1, t2)
|
||||
| S.Let (S.Recursive, f, { S.value = S.Lam (x, t1); _ }, t2) ->
|
||||
let env, f = bind env f in
|
||||
let x, t1 =
|
||||
let env, x = bind env x in
|
||||
x, cook_term env t1
|
||||
in
|
||||
let t2 = cook_term env t2 in
|
||||
T.Let (f, T.Lam (T.Self f, x, t1), t2)
|
||||
| S.Let (S.Recursive, _, { S.place; _ }, _) ->
|
||||
error place "the right-hand side of 'let rec' must be a lambda-abstraction"
|
||||
|
||||
let cook_term t =
|
||||
cook_term Env.empty t
|
15
projet/src/Cook.mli
Normal file
15
projet/src/Cook.mli
Normal file
|
@ -0,0 +1,15 @@
|
|||
(* This module translates [RawLambda] into [Lambda]. *)
|
||||
|
||||
(* This involves ensuring that every name is properly bound (otherwise, an
|
||||
error is reported) and switching from a representation of names as strings
|
||||
to a representation of names as atoms. *)
|
||||
|
||||
(* This also involves checking that the right-hand side of every [let]
|
||||
construct is a function (otherwise, an error is reported) and switching
|
||||
from a representation where [let] constructs can carry a [rec] annotation
|
||||
to a representation where functions can carry such an annotation. *)
|
||||
|
||||
(* This also involves dropping places (that is, source code locations), since
|
||||
they are no longer used after this phase. *)
|
||||
|
||||
val cook_term: RawLambda.term -> Lambda.term
|
7
projet/src/Defun.ml
Normal file
7
projet/src/Defun.ml
Normal file
|
@ -0,0 +1,7 @@
|
|||
(* The source calculus. *)
|
||||
module S = Tail
|
||||
(* The target calculus. *)
|
||||
module T = Top
|
||||
|
||||
let defun_term (t : S.term) : T.program =
|
||||
assert false
|
4
projet/src/Defun.mli
Normal file
4
projet/src/Defun.mli
Normal file
|
@ -0,0 +1,4 @@
|
|||
(* Through defunctionalization, the intermediate language [Tail] is translated
|
||||
down to the next intermediate language, [Top]. *)
|
||||
|
||||
val defun_term: Tail.term -> Top.program
|
40
projet/src/Error.ml
Normal file
40
projet/src/Error.ml
Normal file
|
@ -0,0 +1,40 @@
|
|||
open Lexing
|
||||
|
||||
type place =
|
||||
position * position
|
||||
|
||||
let place lexbuf : place =
|
||||
lexbuf.lex_start_p, lexbuf.lex_curr_p
|
||||
|
||||
let line p : int =
|
||||
p.pos_lnum
|
||||
|
||||
let column p : int =
|
||||
p.pos_cnum - p.pos_bol
|
||||
|
||||
let show place : string =
|
||||
let startp, endp = place in
|
||||
Printf.sprintf "File \"%s\", line %d, characters %d-%d"
|
||||
startp.pos_fname
|
||||
(line startp)
|
||||
(column startp)
|
||||
(endp.pos_cnum - startp.pos_bol) (* intentionally [startp.pos_bol] *)
|
||||
|
||||
let display continuation header place format =
|
||||
Printf.fprintf stderr "%s:\n" (show place);
|
||||
Printf.kfprintf
|
||||
continuation
|
||||
stderr
|
||||
(header ^^ format ^^ "\n%!")
|
||||
|
||||
let error place format =
|
||||
display
|
||||
(fun _ -> exit 1)
|
||||
"Error: "
|
||||
place format
|
||||
|
||||
let set_filename lexbuf filename =
|
||||
lexbuf.lex_curr_p <- { lexbuf.lex_curr_p with pos_fname = filename }
|
||||
|
||||
let pp_place formatter _place =
|
||||
Format.fprintf formatter "<>"
|
30
projet/src/Error.mli
Normal file
30
projet/src/Error.mli
Normal file
|
@ -0,0 +1,30 @@
|
|||
open Lexing
|
||||
|
||||
(* A place is a pair of a start position and an end position. *)
|
||||
|
||||
type place =
|
||||
position * position
|
||||
|
||||
(* [set_filename lexbuf filename] updates [lexbuf] to record the
|
||||
fact that the current file name is [filename]. This file name
|
||||
is later used in error messages. *)
|
||||
|
||||
val set_filename: lexbuf -> string -> unit
|
||||
|
||||
(* [place lexbuf] produces a pair of the current token's start and
|
||||
end positions. This function is useful when reporting an error
|
||||
during lexing. *)
|
||||
|
||||
val place: lexbuf -> place
|
||||
|
||||
(* [error place format ...] displays an error message and exits.
|
||||
The error message is located at [place]. The error message
|
||||
is composed based on [format] and the extra arguments [...]. *)
|
||||
|
||||
val error: place -> ('a, out_channel, unit, 'b) format4 -> 'a
|
||||
|
||||
(* [pp_place formatter place] prints a place. It is used by
|
||||
[@@deriving show] for data structures that contain places.
|
||||
As of now, it prints nothing. *)
|
||||
|
||||
val pp_place: Format.formatter -> place -> unit
|
323
projet/src/Finish.ml
Normal file
323
projet/src/Finish.ml
Normal file
|
@ -0,0 +1,323 @@
|
|||
(* The source calculus. *)
|
||||
module S = Top
|
||||
(* The target calculus. *)
|
||||
module T = C
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* [interval i j f] constructs the list [[f i; f (i + 1); ...; f (j - 1)]]. *)
|
||||
|
||||
let rec interval i j (f : int -> 'a) : 'a list =
|
||||
if i < j then
|
||||
f i :: interval (i + 1) j f
|
||||
else
|
||||
[]
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* [index xs] constructs a list of pairs, where each element of [xs] is paired
|
||||
with its index. Indices are 0-based. *)
|
||||
|
||||
let index (xs : 'a list) : (int * 'a) list =
|
||||
let n = List.length xs in
|
||||
let indices = interval 0 n (fun i -> i) in
|
||||
List.combine indices xs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The number of fields of a block, not counting its tag. *)
|
||||
|
||||
let block_num_fields b =
|
||||
match b with
|
||||
| S.Con (_, vs) ->
|
||||
List.length vs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A simple-minded way of ensuring that every atom is printed as a
|
||||
distinct string is to concatenate the atom's hint and identity,
|
||||
with an underscore in between. This is guaranteed to rule out
|
||||
collisions. *)
|
||||
|
||||
let var (x : S.variable) : T.ident =
|
||||
Printf.sprintf "%s_%d" (Atom.hint x) (Atom.identity x)
|
||||
|
||||
let evar (x : S.variable) : T.expr =
|
||||
T.Name (var x)
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Predefined C types and functions. *)
|
||||
|
||||
(* A universal type: every value is translated to a C value of type [univ].
|
||||
This is a union type (i.e., an untagged sum) of integers and pointers to
|
||||
memory blocks. *)
|
||||
|
||||
let univ : T.type_spec =
|
||||
T.Named "univ"
|
||||
|
||||
(* The type of integers. *)
|
||||
|
||||
let int : T.type_spec =
|
||||
T.Named "int"
|
||||
|
||||
(* The type [char] appears in the type of [main]. *)
|
||||
|
||||
let char : T.type_spec =
|
||||
T.Named "char"
|
||||
|
||||
let answer : T.type_spec =
|
||||
int
|
||||
(* Our functions never actually return, since they are tail recursive.
|
||||
We use [int] as their return type, since this is the return type of
|
||||
[main]. *)
|
||||
|
||||
let exit : T.expr =
|
||||
T.Name "exit"
|
||||
|
||||
let printf : T.expr =
|
||||
T.Name "printf"
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* [declare x init] constructs a local variable declaration for a variable [x]
|
||||
of type [univ]. [x] is optionally initialized according to [init]. *)
|
||||
|
||||
let declare (x : S.variable) (init : T.init option) : T.declaration =
|
||||
univ, None, [ T.Ident (var x), init ]
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Macro invocations. *)
|
||||
|
||||
let macro m es : T.expr =
|
||||
(* We disguise a macro invocation as a procedure call. *)
|
||||
T.Call (T.Name m, es)
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Integer literals; conversions between [univ] and [int]. *)
|
||||
|
||||
let iconst i : T.expr =
|
||||
T.Constant (Constant.Int64, string_of_int i)
|
||||
|
||||
let to_int v : T.expr =
|
||||
macro "TO_INT" [ v ]
|
||||
(* This is an unsafe conversion, of course. *)
|
||||
|
||||
let from_int v : T.expr =
|
||||
macro "FROM_INT" [ v ]
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The translation of values. *)
|
||||
|
||||
let finish_op = function
|
||||
| S.OpAdd ->
|
||||
T.K.Add
|
||||
| S.OpSub ->
|
||||
T.K.Sub
|
||||
| S.OpMul ->
|
||||
T.K.Mult
|
||||
| S.OpDiv ->
|
||||
T.K.Div
|
||||
|
||||
let rec finish_value (v : S.value) : T.expr =
|
||||
match v with
|
||||
| S.VVar x ->
|
||||
evar x
|
||||
| S.VLit i ->
|
||||
from_int (iconst i)
|
||||
| S.VBinOp (v1, op, v2) ->
|
||||
from_int (
|
||||
T.Op2 (
|
||||
finish_op op,
|
||||
to_int (finish_value v1),
|
||||
to_int (finish_value v2)
|
||||
)
|
||||
)
|
||||
|
||||
let finish_values vs =
|
||||
List.map finish_value vs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A macro for allocating a memory block. *)
|
||||
|
||||
let alloc b : T.expr =
|
||||
T.Call (T.Name "ALLOC", [ iconst (block_num_fields b) ])
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Macros for reading and initializing the tag of a memory block. *)
|
||||
|
||||
let read_tag (v : S.value) : T.expr =
|
||||
macro "GET_TAG" [ finish_value v ]
|
||||
|
||||
let set_tag (x : S.variable) (tag : S.tag) : T.stmt =
|
||||
T.Expr (macro "SET_TAG" [ evar x; iconst tag ])
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Macros for reading and setting a field in a memory block. *)
|
||||
|
||||
let read_field (v : S.value) (i : int) : T.expr =
|
||||
(* [i] is a 0-based field index. *)
|
||||
macro "GET_FIELD" [ finish_value v; iconst i ]
|
||||
|
||||
let read_field (v : S.value) (i, x) (t : T.stmt list) : T.stmt list =
|
||||
(* [x] is a variable, which is declared and initialized with
|
||||
the content of the [i]th field of the block [v]. *)
|
||||
T.DeclStmt (declare x (Some (T.InitExpr (read_field v i)))) ::
|
||||
t
|
||||
|
||||
let read_fields (v : S.value) xs (t : T.stmt list) : T.stmt list =
|
||||
(* [xs] are variables, which are declared and initialized with
|
||||
the contents of the fields of the block [v]. *)
|
||||
List.fold_right (read_field v) (index xs) t
|
||||
|
||||
let set_field x i (v : S.value) : T.stmt =
|
||||
T.Expr (macro "SET_FIELD" [ evar x; iconst i; finish_value v ])
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A sequence of instructions for initializing a memory block. *)
|
||||
|
||||
let init_block (x : S.variable) (b : S.block) : T.stmt list =
|
||||
match b with
|
||||
| S.Con (tag, vs) ->
|
||||
T.Comment "Initializing a memory block:" ::
|
||||
set_tag x tag ::
|
||||
List.mapi (set_field x) vs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Function calls, as expressions and as statements. *)
|
||||
|
||||
let ecall f args : T.expr =
|
||||
T.Call (f, args)
|
||||
|
||||
let scall f args : T.stmt =
|
||||
T.Expr (ecall f args)
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The translation of terms. *)
|
||||
|
||||
let rec finish_term (t : S.term) : C.stmt =
|
||||
match t with
|
||||
| S.Exit ->
|
||||
T.Compound [
|
||||
scall exit [ iconst 0 ]
|
||||
]
|
||||
| S.TailCall (f, vs) ->
|
||||
T.Return (Some (ecall (evar f) (finish_values vs)))
|
||||
| S.Print (v, t) ->
|
||||
T.Compound [
|
||||
scall printf [ T.Literal "%d\\n"; to_int (finish_value v) ];
|
||||
finish_term t
|
||||
]
|
||||
| S.LetVal (x, v1, t2) ->
|
||||
T.Compound [
|
||||
T.DeclStmt (declare x (Some (T.InitExpr (finish_value v1))));
|
||||
finish_term t2
|
||||
]
|
||||
| S.LetBlo (x, b1, t2) ->
|
||||
T.Compound (
|
||||
T.DeclStmt (declare x (Some (T.InitExpr (alloc b1)))) ::
|
||||
init_block x b1 @
|
||||
[ finish_term t2 ]
|
||||
)
|
||||
| S.Swi (v, bs) ->
|
||||
T.Switch (
|
||||
read_tag v,
|
||||
finish_branches v bs,
|
||||
default
|
||||
)
|
||||
|
||||
and default : T.stmt =
|
||||
(* This default [switch] branch should never be taken. *)
|
||||
T.Compound [
|
||||
scall printf [ T.Literal "Oops! A nonexistent case has been taken.\\n" ];
|
||||
scall exit [ iconst 42 ];
|
||||
]
|
||||
|
||||
and finish_branches v bs =
|
||||
List.map (finish_branch v) bs
|
||||
|
||||
and finish_branch v (S.Branch (tag, xs, t)) : T.expr * T.stmt =
|
||||
iconst tag,
|
||||
T.Compound (read_fields v xs [finish_term t])
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Function declarations. *)
|
||||
|
||||
(* We distinguish the function [main], whose type is imposed by the C standard,
|
||||
and ordinary functions, whose parameters have type [univ]. *)
|
||||
|
||||
(* A parameter of an ordinary function has type [univ]. *)
|
||||
|
||||
let param (x : S.variable) : T.param =
|
||||
univ, T.Ident (var x)
|
||||
|
||||
(* A declaration of an ordinary function. *)
|
||||
|
||||
let declare_ordinary_function f xs : T.declaration =
|
||||
answer, None, [ T.Function (None, T.Ident (var f), List.map param xs), None ]
|
||||
|
||||
(* The declaration of the main function. *)
|
||||
|
||||
let declare_main_function : T.declaration =
|
||||
let params = [
|
||||
int, T.Ident "argc";
|
||||
char, T.Pointer (T.Pointer (T.Ident "argv"))
|
||||
] in
|
||||
int, None, [ T.Function (None, T.Ident "main", params), None ]
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A function definition. *)
|
||||
|
||||
type decl_or_fun =
|
||||
T.declaration_or_function
|
||||
|
||||
let define (decl : T.declaration) (t : S.term) : decl_or_fun =
|
||||
T.Function (
|
||||
[], (* no comments *)
|
||||
false, (* not inlined *)
|
||||
decl,
|
||||
T.Compound [finish_term t]
|
||||
)
|
||||
|
||||
let define_ordinary_function (S.Fun (f, xs, t)) : decl_or_fun =
|
||||
define (declare_ordinary_function f xs) t
|
||||
|
||||
let define_main_function (t : S.term) : decl_or_fun =
|
||||
define declare_main_function t
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Because all functions are mutually recursive, their definitions must be
|
||||
preceded with their prototypes. *)
|
||||
|
||||
let prototype (f : decl_or_fun) : decl_or_fun =
|
||||
match f with
|
||||
| T.Function (_, _, declaration, _) ->
|
||||
T.Decl ([], declaration)
|
||||
| T.Decl _ ->
|
||||
assert false
|
||||
|
||||
let prototypes (fs : decl_or_fun list) : decl_or_fun list =
|
||||
List.map prototype fs @
|
||||
fs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The translation of a complete program. *)
|
||||
|
||||
let finish_program (S.Prog (decls, main) : S.program) : T.program =
|
||||
prototypes (
|
||||
define_main_function main ::
|
||||
List.map define_ordinary_function decls
|
||||
)
|
5
projet/src/Finish.mli
Normal file
5
projet/src/Finish.mli
Normal file
|
@ -0,0 +1,5 @@
|
|||
(* This function implements a translation of the intermediate language [Top]
|
||||
down to [C]. This transformation is mostly a matter of choosing appropriate
|
||||
C constructs to reflect the concepts of the language [Top]. *)
|
||||
|
||||
val finish_program: Top.program -> C.program
|
48
projet/src/Lambda.ml
Normal file
48
projet/src/Lambda.ml
Normal file
|
@ -0,0 +1,48 @@
|
|||
(* This language is the untyped lambda-calculus, extended with recursive
|
||||
lambda-abstractions, nonrecursive [let] bindings, integer literals and
|
||||
integer arithmetic operations, and the primitive operation [print]. *)
|
||||
|
||||
(* This language is really the same language as [RawLambda], with the
|
||||
following internal differences:
|
||||
|
||||
1. Instead of recursive [let] bindings, the language has recursive
|
||||
lambda-abstractions. A [let rec] definition whose right-hand side is not
|
||||
a lambda-abstraction is rejected during the translation of [RawLambda]
|
||||
to [Lambda].
|
||||
|
||||
2. Variables are represented by atoms (instead of strings). A term with an
|
||||
unbound variable is rejected during the translation of [RawLambda] to
|
||||
[Lambda].
|
||||
|
||||
3. Terms are no longer annotated with places. *)
|
||||
|
||||
(* Variables are atoms. *)
|
||||
|
||||
type variable =
|
||||
Atom.atom
|
||||
|
||||
(* Every lambda-abstraction is marked recursive or nonrecursive. Whereas a
|
||||
nonrecursive lambda-abstraction [fun x -> t] binds one variable [x], a
|
||||
recursive lambda-abstraction [fix f. fun x -> t] binds two variables [f]
|
||||
and [x]. The variable [f] is a self-reference. *)
|
||||
|
||||
and self =
|
||||
| Self of variable
|
||||
| NoSelf
|
||||
|
||||
and binop = RawLambda.binop =
|
||||
| OpAdd
|
||||
| OpSub
|
||||
| OpMul
|
||||
| OpDiv
|
||||
|
||||
and term =
|
||||
| Var of variable
|
||||
| Lam of self * variable * term
|
||||
| App of term * term
|
||||
| Lit of int
|
||||
| BinOp of term * binop * term
|
||||
| Print of term
|
||||
| Let of variable * term * term
|
||||
|
||||
[@@deriving show { with_path = false }]
|
97
projet/src/Lexer.mll
Normal file
97
projet/src/Lexer.mll
Normal file
|
@ -0,0 +1,97 @@
|
|||
{
|
||||
|
||||
open Lexing
|
||||
open Error
|
||||
open Parser
|
||||
open RawLambda
|
||||
|
||||
}
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Regular expressions. *)
|
||||
|
||||
let newline =
|
||||
('\010' | '\013' | "\013\010")
|
||||
|
||||
let whitespace =
|
||||
[ ' ' '\t' ]
|
||||
|
||||
let lowercase =
|
||||
['a'-'z' '\223'-'\246' '\248'-'\255' '_']
|
||||
|
||||
let uppercase =
|
||||
['A'-'Z' '\192'-'\214' '\216'-'\222']
|
||||
|
||||
let identchar =
|
||||
['A'-'Z' 'a'-'z' '_' '\192'-'\214' '\216'-'\246' '\248'-'\255' '0'-'9']
|
||||
|
||||
let digit =
|
||||
['0'-'9']
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The lexer. *)
|
||||
|
||||
rule entry = parse
|
||||
| "fun"
|
||||
{ FUN }
|
||||
| "in"
|
||||
{ IN }
|
||||
| "let"
|
||||
{ LET }
|
||||
| "print"
|
||||
{ PRINT }
|
||||
| "rec"
|
||||
{ REC }
|
||||
| "->"
|
||||
{ ARROW }
|
||||
| "="
|
||||
{ EQ }
|
||||
| "("
|
||||
{ LPAREN }
|
||||
| ")"
|
||||
{ RPAREN }
|
||||
| "+"
|
||||
{ ADDOP OpAdd }
|
||||
| "-"
|
||||
{ ADDOP OpSub }
|
||||
| "*"
|
||||
{ MULOP OpMul }
|
||||
| "/"
|
||||
{ MULOP OpDiv }
|
||||
| (lowercase identchar *) as x
|
||||
{ IDENT x }
|
||||
| digit+ as i
|
||||
{ try
|
||||
INTLITERAL (int_of_string i)
|
||||
with Failure _ ->
|
||||
error (place lexbuf) "invalid integer literal." }
|
||||
| "(*"
|
||||
{ ocamlcomment (place lexbuf) lexbuf; entry lexbuf }
|
||||
| newline
|
||||
{ new_line lexbuf; entry lexbuf }
|
||||
| whitespace+
|
||||
{ entry lexbuf }
|
||||
| eof
|
||||
{ EOF }
|
||||
| _ as c
|
||||
{ error (place lexbuf) "unexpected character: '%c'." c }
|
||||
|
||||
(* ------------------------------------------------------------------------ *)
|
||||
|
||||
(* Skip OCaml-style comments. Comments can be nested. This sub-lexer is
|
||||
parameterized with the place of the opening comment, so if an unterminated
|
||||
comment is detected, we can show where it was opened. *)
|
||||
|
||||
and ocamlcomment p = parse
|
||||
| "*)"
|
||||
{ () }
|
||||
| "(*"
|
||||
{ ocamlcomment (place lexbuf) lexbuf; ocamlcomment p lexbuf }
|
||||
| newline
|
||||
{ new_line lexbuf; ocamlcomment p lexbuf }
|
||||
| eof
|
||||
{ error p "unterminated comment." }
|
||||
| _
|
||||
{ ocamlcomment p lexbuf }
|
101
projet/src/Main.ml
Normal file
101
projet/src/Main.ml
Normal file
|
@ -0,0 +1,101 @@
|
|||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Parse the command line. *)
|
||||
|
||||
let debug =
|
||||
ref false
|
||||
|
||||
let filenames =
|
||||
ref []
|
||||
|
||||
let record filename =
|
||||
filenames := filename :: !filenames
|
||||
|
||||
let options =
|
||||
Arg.align [
|
||||
"--debug", Arg.Set debug, " Enable debugging output";
|
||||
]
|
||||
|
||||
let usage =
|
||||
Printf.sprintf "Usage: %s <options> <filename>" Sys.argv.(0)
|
||||
|
||||
let () =
|
||||
Arg.parse options record usage
|
||||
|
||||
let debug =
|
||||
!debug
|
||||
|
||||
let filenames =
|
||||
List.rev !filenames
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Printing a syntax tree in an intermediate language (for debugging). *)
|
||||
|
||||
let print_delimiter () =
|
||||
Printf.eprintf "----------------------------------------";
|
||||
Printf.eprintf "----------------------------------------\n"
|
||||
|
||||
let dump (phase : string) (show : 'term -> string) (t : 'term) =
|
||||
if debug then begin
|
||||
print_delimiter();
|
||||
Printf.eprintf "%s:\n\n%s\n\n%!" phase (show t)
|
||||
end;
|
||||
t
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Reading and parsing a file. *)
|
||||
|
||||
let read filename : RawLambda.term =
|
||||
try
|
||||
let contents = Utils.file_get_contents filename in
|
||||
let lexbuf = Lexing.from_string contents in
|
||||
Error.set_filename lexbuf filename;
|
||||
try
|
||||
Parser.entry Lexer.entry lexbuf
|
||||
with
|
||||
| Parser.Error ->
|
||||
Error.error (Error.place lexbuf) "Syntax error."
|
||||
with
|
||||
| Sys_error msg ->
|
||||
prerr_endline msg;
|
||||
exit 1
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Printing the final C program on the standard output channel. *)
|
||||
|
||||
let output (p : C.program) : unit =
|
||||
Printf.printf "#include<stdlib.h>\n";
|
||||
Printf.printf "#include<stdio.h>\n";
|
||||
Printf.printf "#include \"prologue.h\"\n\n";
|
||||
let print_program = PrintCommon.print_program PrintC.p_decl_or_function in
|
||||
let buf = Buffer.create 1024 in
|
||||
PrintCommon.printf_of_pprint_pretty print_program buf p;
|
||||
print_endline (Buffer.contents buf)
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The complete processing pipeline. Beautiful, isn't it? *)
|
||||
|
||||
let process filename =
|
||||
filename
|
||||
|> read
|
||||
|> dump "RawLambda" RawLambda.show_term
|
||||
|> Cook.cook_term
|
||||
|> dump "Lambda" Lambda.show_term
|
||||
|> CPS.cps_term
|
||||
|> dump "Tail" Tail.show_term
|
||||
|> Defun.defun_term
|
||||
|> dump "Top" Top.show_program
|
||||
|> Finish.finish_program
|
||||
|> dump "C" C.show_program
|
||||
|> output
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The main program. *)
|
||||
|
||||
let () =
|
||||
List.iter process filenames
|
28
projet/src/Makefile
Normal file
28
projet/src/Makefile
Normal file
|
@ -0,0 +1,28 @@
|
|||
SHELL := bash
|
||||
TARGET := Main.native
|
||||
JOUJOU := joujou
|
||||
DIRS := kremlin,alphalib
|
||||
OCAMLBUILD :=\
|
||||
ocamlbuild \
|
||||
-classic-display \
|
||||
-j 4 \
|
||||
-use-ocamlfind \
|
||||
-use-menhir \
|
||||
-menhir "menhir -lg 1 -la 1 --explain" \
|
||||
-Is $(DIRS) \
|
||||
|
||||
.PHONY: all test clean
|
||||
|
||||
all:
|
||||
@ $(OCAMLBUILD) -quiet $(TARGET)
|
||||
@ ln -sf $(TARGET) $(JOUJOU)
|
||||
|
||||
test: all
|
||||
@ make -C test test
|
||||
|
||||
clean:
|
||||
rm -f *~
|
||||
rm -f tests/*.c tests/*.out
|
||||
$(OCAMLBUILD) -clean
|
||||
rm -f $(TARGET) $(JOUJOU)
|
||||
$(MAKE) -C test clean
|
155
projet/src/Parser.mly
Normal file
155
projet/src/Parser.mly
Normal file
|
@ -0,0 +1,155 @@
|
|||
%token<string> IDENT
|
||||
%token<int> INTLITERAL
|
||||
%token FUN IN LET PRINT REC
|
||||
%token ARROW EQ LPAREN RPAREN
|
||||
%token<RawLambda.binop> MULOP ADDOP
|
||||
%token EOF
|
||||
|
||||
%start<RawLambda.term> entry
|
||||
|
||||
%{
|
||||
|
||||
open RawLambda
|
||||
|
||||
%}
|
||||
|
||||
%%
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A toplevel phrase is just a term. *)
|
||||
|
||||
entry:
|
||||
t = any_term EOF
|
||||
{ t }
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* The syntax of terms is stratified as follows:
|
||||
|
||||
atomic_term -- unambiguously delimited terms
|
||||
application_term -- n-ary applications of atomic terms
|
||||
multiplicative_term -- built using multiplication & division
|
||||
additive_term -- built using addition & subtraction
|
||||
any_term -- everything
|
||||
|
||||
A [match/with/end] construct is terminated with an [end] keyword, as in Coq,
|
||||
so it is an atomic term. *)
|
||||
|
||||
atomic_term_:
|
||||
| LPAREN t = any_term RPAREN
|
||||
{ t.value }
|
||||
| x = IDENT
|
||||
{ Var x }
|
||||
| i = INTLITERAL
|
||||
{ Lit i }
|
||||
|
||||
application_term_:
|
||||
| t = atomic_term_
|
||||
{ t }
|
||||
| t1 = placed(application_term_) t2 = placed(atomic_term_)
|
||||
{ App (t1, t2) }
|
||||
| PRINT t2 = placed(atomic_term_)
|
||||
{ Print t2 }
|
||||
|
||||
%inline multiplicative_term_:
|
||||
t = left_associative_level(application_term_, MULOP, mkbinop)
|
||||
{ t }
|
||||
|
||||
%inline additive_term_:
|
||||
t = left_associative_level(multiplicative_term_, ADDOP, mkbinop)
|
||||
{ t }
|
||||
|
||||
any_term_:
|
||||
| t = additive_term_
|
||||
{ t }
|
||||
| FUN x = IDENT ARROW t = any_term
|
||||
{ Lam (x, t) }
|
||||
| LET mode = recursive x = IDENT EQ t1 = any_term IN t2 = any_term
|
||||
{ Let (mode, x, t1, t2) }
|
||||
|
||||
%inline any_term:
|
||||
t = placed(any_term_)
|
||||
{ t }
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* An infix-left-associative-operator level in a hierarchy of arithmetic
|
||||
expressions. *)
|
||||
|
||||
(* [basis] is the next lower level in the hierarchy.
|
||||
[op] is the category of binary operators.
|
||||
[action] is a ternary sequencing construct. *)
|
||||
|
||||
left_associative_level(basis, op, action):
|
||||
| t = basis
|
||||
| t = action(
|
||||
left_associative_level(basis, op, action),
|
||||
op,
|
||||
basis
|
||||
)
|
||||
{ t }
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A ternary sequence whose semantic action builds a [BinOp] node. *)
|
||||
|
||||
%inline mkbinop(term1, op, term2):
|
||||
t1 = placed(term1) op = op t2 = placed(term2)
|
||||
{ BinOp (t1, op, t2) }
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A [let] construct carries an optional [rec] annotation. *)
|
||||
|
||||
recursive:
|
||||
| REC { Recursive }
|
||||
| { NonRecursive }
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A term is annotated with its start and end positions, for use in error
|
||||
messages. *)
|
||||
|
||||
%inline placed(X):
|
||||
x = X
|
||||
{ { place = ($startpos, $endpos); value = x } }
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* In a right-flexible list, the last delimiter is optional, i.e., [delim] can
|
||||
be viewed as a terminator or a separator, as desired. *)
|
||||
|
||||
(* There are several ways of expressing this. One could say it is either a
|
||||
separated list or a terminated list; this works if one uses right recursive
|
||||
lists. Or, one could say that it is a separated list followed with an
|
||||
optional delimiter; this works if one uses a left-recursive list. The
|
||||
following formulation is direct and seems most natural. It should lead to
|
||||
the smallest possible automaton. *)
|
||||
|
||||
right_flexible_list(delim, X):
|
||||
| (* nothing *)
|
||||
{ [] }
|
||||
| x = X
|
||||
{ [x] }
|
||||
| x = X delim xs = right_flexible_list(delim, X)
|
||||
{ x :: xs }
|
||||
|
||||
(* In a left-flexible list, the first delimiter is optional, i.e., [delim] can
|
||||
be viewed as an opening or as a separator, as desired. *)
|
||||
|
||||
(* Again, there are several ways of expressing this, and again, I suppose the
|
||||
following formulation is simplest. It is the mirror image of the above
|
||||
definition, so it is naturally left-recursive, this time. *)
|
||||
|
||||
reverse_left_flexible_list(delim, X):
|
||||
| (* nothing *)
|
||||
{ [] }
|
||||
| x = X
|
||||
{ [x] }
|
||||
| xs = reverse_left_flexible_list(delim, X) delim x = X
|
||||
{ x :: xs }
|
||||
|
||||
%inline left_flexible_list(delim, X):
|
||||
xs = reverse_left_flexible_list(delim, X)
|
||||
{ List.rev xs }
|
55
projet/src/RawLambda.ml
Normal file
55
projet/src/RawLambda.ml
Normal file
|
@ -0,0 +1,55 @@
|
|||
(* Variables are strings. *)
|
||||
|
||||
type variable =
|
||||
string
|
||||
|
||||
(* Every [let] binding is marked recursive or nonrecursive. *)
|
||||
|
||||
and recursive =
|
||||
| Recursive
|
||||
| NonRecursive
|
||||
|
||||
(* The four standard integer arithmetic operations are supported. *)
|
||||
|
||||
and binop =
|
||||
| OpAdd
|
||||
| OpSub
|
||||
| OpMul
|
||||
| OpDiv
|
||||
|
||||
(* This language is the untyped lambda-calculus, extended with possibly
|
||||
recursive [let] bindings, integer literals (that is, constants), integer
|
||||
arithmetic operations, and the primitive operation [print], which prints an
|
||||
integer value and returns it. *)
|
||||
|
||||
and term_ =
|
||||
| Var of variable
|
||||
| Lam of variable * term
|
||||
| App of term * term
|
||||
| Lit of int
|
||||
| BinOp of term * binop * term
|
||||
| Print of term
|
||||
| Let of recursive * variable * term * term
|
||||
|
||||
(* Every abstract syntax tree node of type [term] is annotated with a place,
|
||||
that is, a position in the source code. This allows us to produce a good
|
||||
error message when a problem is detected. *)
|
||||
|
||||
and term =
|
||||
term_ placed
|
||||
|
||||
(* A value of type ['a placed] can be thought of as a value of type ['a]
|
||||
decorated with a place. *)
|
||||
|
||||
and 'a placed = {
|
||||
place: Error.place;
|
||||
value: 'a
|
||||
}
|
||||
|
||||
(* The following annotation requests the automatic generation of a [show_]
|
||||
function for each of the types defined above. For instance, the function
|
||||
[show_term], of type [term -> string], converts a term to a string. These
|
||||
functions can be useful during debugging. Running with [--debug] causes
|
||||
every intermediate abstract syntax tree to be displayed in this way. *)
|
||||
|
||||
[@@deriving show { with_path = false }]
|
132
projet/src/Tail.ml
Normal file
132
projet/src/Tail.ml
Normal file
|
@ -0,0 +1,132 @@
|
|||
(* This intermediate language describes the result of the CPS transformation.
|
||||
It is a lambda-calculus where the ordering of computations is explicit and
|
||||
where every function call is a tail call.
|
||||
|
||||
The following syntactic categories are distinguished:
|
||||
|
||||
1. "Values" include variables, integer literals, and applications of the
|
||||
primitive integer operations to values. Instead of "values", they could
|
||||
also be referred to as "pure expressions". They are expressions whose
|
||||
evaluation terminates and has no side effect, not even memory
|
||||
allocation.
|
||||
|
||||
2. "Blocks" include lambda-abstractions. Even though lambda-abstractions
|
||||
are traditionally considered values, here, they are viewed as
|
||||
expressions whose evaluation has a side effect, namely, the allocation
|
||||
of a memory block.
|
||||
|
||||
3. "Terms" are expressions with side effects. Terms always appear in tail
|
||||
position: an examination of the syntax of terms shows that a term can be
|
||||
viewed as a sequence of [LetVal], [LetBlo] and [Print] instructions,
|
||||
terminated with either [Exit] or [TailCall]. This implies, in
|
||||
particular, that every call is a tail call.
|
||||
|
||||
In contrast with the surface language, where every lambda-abstraction has
|
||||
arity 1, in this calculus, lambda-abstractions of arbitrary arity are
|
||||
supported. A lambda-abstraction [Lam] carries a list of formal arguments
|
||||
and a function call [TailCall] carries a list of actual arguments. Partial
|
||||
applications or over-applications are not supported: it is the programmer's
|
||||
responsibility to ensure that every function call provides exactly as many
|
||||
arguments as expected by the called function. *)
|
||||
|
||||
type variable =
|
||||
Atom.atom
|
||||
|
||||
and self = Lambda.self =
|
||||
| Self of variable
|
||||
| NoSelf
|
||||
|
||||
and binop = Lambda.binop =
|
||||
| OpAdd
|
||||
| OpSub
|
||||
| OpMul
|
||||
| OpDiv
|
||||
|
||||
and value =
|
||||
| VVar of variable
|
||||
| VLit of int
|
||||
| VBinOp of value * binop * value
|
||||
|
||||
and block =
|
||||
| Lam of self * variable list * term
|
||||
|
||||
(* Terms include the following constructs:
|
||||
|
||||
- The primitive operation [Exit] stops the program.
|
||||
|
||||
- The tail call [TailCall (v, vs)] transfers control to the function [v]
|
||||
with actual arguments [vs]. (The value [v] should be a function and its
|
||||
arity should be the length of [vs].)
|
||||
|
||||
- The term [Print (v, t)] prints the value [v], then executes the term [t].
|
||||
(The value [v] should be a primitive integer value.)
|
||||
|
||||
- The term [LetVal (x, v, t)] binds the variable [x] to the value [v], then
|
||||
executes the term [t].
|
||||
|
||||
- The term [LetBlo (x, b, t)] allocates the memory block [b] and binds the
|
||||
variable [x] to its address, then executes the term [t]. *)
|
||||
|
||||
and term =
|
||||
| Exit
|
||||
| TailCall of value * value list
|
||||
| Print of value * term
|
||||
| LetVal of variable * value * term
|
||||
| LetBlo of variable * block * term
|
||||
|
||||
[@@deriving show { with_path = false }]
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Constructor functions. *)
|
||||
|
||||
let vvar x =
|
||||
VVar x
|
||||
|
||||
let vvars xs =
|
||||
List.map vvar xs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Computing the free variables of a value, block, or term. *)
|
||||
|
||||
open Atom.Set
|
||||
|
||||
let rec fv_value (v : value) =
|
||||
match v with
|
||||
| VVar x ->
|
||||
singleton x
|
||||
| VLit _ ->
|
||||
empty
|
||||
| VBinOp (v1, _, v2) ->
|
||||
union (fv_value v1) (fv_value v2)
|
||||
|
||||
and fv_values (vs : value list) =
|
||||
union_many fv_value vs
|
||||
|
||||
and fv_lambda (xs : variable list) (t : term) =
|
||||
diff (fv_term t) (of_list xs)
|
||||
|
||||
and fv_block (b : block) =
|
||||
match b with
|
||||
| Lam (NoSelf, xs, t) ->
|
||||
fv_lambda xs t
|
||||
| Lam (Self f, xs, t) ->
|
||||
remove f (fv_lambda xs t)
|
||||
|
||||
and fv_term (t : term) =
|
||||
match t with
|
||||
| Exit ->
|
||||
empty
|
||||
| TailCall (v, vs) ->
|
||||
fv_values (v :: vs)
|
||||
| Print (v1, t2) ->
|
||||
union (fv_value v1) (fv_term t2)
|
||||
| LetVal (x, v1, t2) ->
|
||||
union
|
||||
(fv_value v1)
|
||||
(remove x (fv_term t2))
|
||||
| LetBlo (x, b1, t2) ->
|
||||
union
|
||||
(fv_block b1)
|
||||
(remove x (fv_term t2))
|
94
projet/src/Top.ml
Normal file
94
projet/src/Top.ml
Normal file
|
@ -0,0 +1,94 @@
|
|||
(* This intermediate language describes the result of defunctionalization.
|
||||
It retains the key features of the previous calculus, [Tail], in that
|
||||
the ordering of computations is explicit and every function call is a
|
||||
tail call. Furthermore, lambda-abstractions disappear. A memory block
|
||||
[Con] now contains an integer tag followed with a number of fields,
|
||||
which hold values. A [switch] construct appears, which allows testing
|
||||
the tag of a memory block. A number of (closed, mutually recursive)
|
||||
functions can be defined at the top level. *)
|
||||
|
||||
type tag =
|
||||
int
|
||||
|
||||
and variable =
|
||||
Atom.atom
|
||||
|
||||
and binop = Tail.binop =
|
||||
| OpAdd
|
||||
| OpSub
|
||||
| OpMul
|
||||
| OpDiv
|
||||
|
||||
and value = Tail.value =
|
||||
| VVar of variable
|
||||
| VLit of int
|
||||
| VBinOp of value * binop * value
|
||||
|
||||
(* A block contains an integer tag, followed with a number of fields. *)
|
||||
|
||||
and block =
|
||||
| Con of tag * value list
|
||||
|
||||
(* The construct [Swi (v, branches)] reads the integer tag stored in the
|
||||
memory block at address [v] and performs a case analysis on this tag,
|
||||
transferring control to the appropriate branch. (The value [v] should be a
|
||||
pointer to a memory block.) *)
|
||||
|
||||
and term =
|
||||
| Exit
|
||||
| TailCall of variable * value list
|
||||
| Print of value * term
|
||||
| LetVal of variable * value * term
|
||||
| LetBlo of variable * block * term
|
||||
| Swi of value * branch list
|
||||
|
||||
(* A branch [tag xs -> t] is labeled with an integer tag [tag], and is
|
||||
executed if the memory block carries this tag. The variables [xs] are
|
||||
then bounds to the fields of the memory block. (The length of the list
|
||||
[xs] should be the number of fields of the memory block.) *)
|
||||
|
||||
and branch =
|
||||
| Branch of tag * variable list * term
|
||||
|
||||
(* A toplevel function declaration mentions the function's name, formal
|
||||
parameters, and body. *)
|
||||
|
||||
and function_declaration =
|
||||
| Fun of variable * variable list * term
|
||||
|
||||
(* A complete program consits of a set of toplevel function declarations
|
||||
and a term (the "main program"). The functions are considered mutually
|
||||
recursive: every function may refer to every function. *)
|
||||
|
||||
and program =
|
||||
| Prog of function_declaration list * term
|
||||
|
||||
[@@deriving show { with_path = false }]
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Constructor functions. *)
|
||||
|
||||
let vvar =
|
||||
Tail.vvar
|
||||
|
||||
let vvars =
|
||||
Tail.vvars
|
||||
|
||||
(* [let x_1 = v_1 in ... let x_n = v_n in t] *)
|
||||
|
||||
let rec sequential_let (xs : variable list) (vs : value list) (t : term) =
|
||||
match xs, vs with
|
||||
| [], [] ->
|
||||
t
|
||||
| x :: xs, v :: vs ->
|
||||
LetVal (x, v, sequential_let xs vs t)
|
||||
| _ ->
|
||||
assert false
|
||||
|
||||
(* [let x_1 = v_1 and ... x_n = v_n in t] *)
|
||||
|
||||
let parallel_let (xs : variable list) (vs : value list) (t : term) =
|
||||
assert (List.length xs = List.length vs);
|
||||
assert (Atom.Set.disjoint (Atom.Set.of_list xs) (Tail.fv_values vs));
|
||||
sequential_let xs vs t
|
8
projet/src/_tags
Normal file
8
projet/src/_tags
Normal file
|
@ -0,0 +1,8 @@
|
|||
true: \
|
||||
debug, \
|
||||
strict_sequence, \
|
||||
warn(A-3-4-30-44-42-45-50), \
|
||||
package(unix), \
|
||||
package(process), \
|
||||
package(pprint), \
|
||||
package(ppx_deriving.std)
|
215
projet/src/alphalib/Atom.ml
Normal file
215
projet/src/alphalib/Atom.ml
Normal file
|
@ -0,0 +1,215 @@
|
|||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* We impose maximal sharing on strings so as to reduce the total amount of
|
||||
space that they occupy. This is done using a weak hash set. *)
|
||||
|
||||
module StringStorage =
|
||||
Weak.Make(struct
|
||||
type t = string
|
||||
let equal (s1 : string) (s2 : string) = (s1 = s2)
|
||||
let hash = Hashtbl.hash
|
||||
end)
|
||||
|
||||
let share : string -> string =
|
||||
StringStorage.merge (StringStorage.create 128)
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Removing any trailing digits in a string. *)
|
||||
|
||||
let is_digit c =
|
||||
Char.code '0' <= Char.code c && Char.code c <= Char.code '9'
|
||||
|
||||
let remove_trailing_digits (s : string) : string =
|
||||
let n = ref (String.length s) in
|
||||
while !n > 0 && is_digit s.[!n-1] do n := !n-1 done;
|
||||
(* We assume that there is at least one non-digit character in the string. *)
|
||||
assert (!n > 0);
|
||||
String.sub s 0 !n
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* An atom is implemented as a pair of an integer identity and a string that
|
||||
serves as a printing hint. *)
|
||||
|
||||
(* We maintain the invariant that a hint is nonempty and does not end in a
|
||||
digit. This allows us to later produce unique identifiers, without risk of
|
||||
collisions, by concatenating a hint and a unique number. *)
|
||||
|
||||
(* To preserve space, hints are maximally shared. This is not essential for
|
||||
correctness, though. *)
|
||||
|
||||
type atom = { identity: int; hint: string }
|
||||
|
||||
and t = atom
|
||||
[@@deriving show { with_path = false }]
|
||||
|
||||
let identity a =
|
||||
a.identity
|
||||
|
||||
let hint a =
|
||||
a.hint
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A global integer counter holds the next available identity. *)
|
||||
|
||||
let counter =
|
||||
ref 0
|
||||
|
||||
let allocate () =
|
||||
let number = !counter in
|
||||
counter := number + 1;
|
||||
assert (number >= 0);
|
||||
number
|
||||
|
||||
(* [fresh hint] produces a fresh atom. *)
|
||||
|
||||
(* The argument [hint] must not be a string of digits. *)
|
||||
|
||||
let fresh hint =
|
||||
let identity = allocate()
|
||||
and hint = share (remove_trailing_digits hint) in
|
||||
{ identity; hint }
|
||||
|
||||
(* [copy a] returns a fresh atom modeled after the atom [a]. *)
|
||||
|
||||
let copy a =
|
||||
fresh a.hint
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Comparison of atoms. *)
|
||||
|
||||
let equal a b =
|
||||
a.identity = b.identity
|
||||
|
||||
let compare a b =
|
||||
(* Identities are always positive numbers (see [allocate] above)
|
||||
so I believe overflow is impossible here. *)
|
||||
a.identity - b.identity
|
||||
|
||||
let hash a =
|
||||
Hashtbl.hash a.identity
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* A scratch buffer for printing. *)
|
||||
|
||||
let scratch =
|
||||
Buffer.create 1024
|
||||
|
||||
(* [print_separated_sequence] prints a sequence of elements into the [scratch]
|
||||
buffer. The sequence is given by the higher-order iterator [iter], applied
|
||||
to the collection [xs]. The separator is the string [sep]. Each element is
|
||||
transformed to a string by the function [show]. *)
|
||||
|
||||
let print_separated_sequence show sep iter xs : unit =
|
||||
let first = ref true in
|
||||
iter (fun x ->
|
||||
if !first then begin
|
||||
Buffer.add_string scratch (show x);
|
||||
first := false
|
||||
end
|
||||
else begin
|
||||
Buffer.add_string scratch sep;
|
||||
Buffer.add_string scratch (show x)
|
||||
end
|
||||
) xs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Sets and maps. *)
|
||||
|
||||
module Order = struct
|
||||
type t = atom
|
||||
let compare = compare
|
||||
end
|
||||
|
||||
module Set = struct
|
||||
|
||||
include Set.Make(Order)
|
||||
|
||||
(* A disjointness test. *)
|
||||
|
||||
let disjoint xs ys =
|
||||
is_empty (inter xs ys)
|
||||
|
||||
(* Iterated union. *)
|
||||
|
||||
let union_many (f : 'a -> t) (xs : 'a list) : t =
|
||||
List.fold_left (fun accu x ->
|
||||
union accu (f x)
|
||||
) empty xs
|
||||
|
||||
(* Disjoint union. *)
|
||||
|
||||
exception NonDisjointUnion of atom
|
||||
|
||||
let disjoint_union xs ys =
|
||||
match choose (inter xs ys) with
|
||||
| exception Not_found ->
|
||||
(* The intersection of [xs] and [ys] is empty. Return their union. *)
|
||||
union xs ys
|
||||
| x ->
|
||||
(* The intersection contains [x]. Raise an exception. *)
|
||||
raise (NonDisjointUnion x)
|
||||
|
||||
let handle_NonDisjointUnion f x =
|
||||
try
|
||||
f x; true
|
||||
with NonDisjointUnion a ->
|
||||
Printf.eprintf "NonDisjointUnion: %s\n%!" (show a);
|
||||
false
|
||||
|
||||
(* Sets of atoms form a monoid under union. *)
|
||||
|
||||
class ['z] union_monoid = object
|
||||
method zero: 'z = empty
|
||||
method plus: 'z -> 'z -> 'z = union
|
||||
end
|
||||
|
||||
(* Sets of atoms form a monoid under disjoint union. *)
|
||||
|
||||
class ['z] disjoint_union_monoid = object
|
||||
method zero: 'z = empty
|
||||
method plus: 'z -> 'z -> 'z = disjoint_union
|
||||
end
|
||||
|
||||
(* These printing functions should be used for debugging purposes only. *)
|
||||
|
||||
let print_to_scratch xs =
|
||||
Buffer.clear scratch;
|
||||
Buffer.add_string scratch "{";
|
||||
print_separated_sequence show ", " iter xs;
|
||||
Buffer.add_string scratch "}"
|
||||
|
||||
let show xs =
|
||||
print_to_scratch xs;
|
||||
let result = Buffer.contents scratch in
|
||||
Buffer.reset scratch;
|
||||
result
|
||||
|
||||
let print oc xs =
|
||||
print_to_scratch xs;
|
||||
Buffer.output_buffer oc scratch;
|
||||
Buffer.reset scratch
|
||||
|
||||
end
|
||||
|
||||
module Map = struct
|
||||
|
||||
include Map.Make(Order)
|
||||
|
||||
(* This is O(nlog n), whereas in principle O(n) is possible.
|
||||
The abstraction barrier in OCaml's [Set] module hinders us. *)
|
||||
let domain m =
|
||||
fold (fun a _ accu -> Set.add a accu) m Set.empty
|
||||
|
||||
let codomain f m =
|
||||
fold (fun _ v accu -> Set.union (f v) accu) m Set.empty
|
||||
|
||||
end
|
||||
|
||||
type renaming =
|
||||
atom Map.t
|
71
projet/src/alphalib/Atom.mli
Normal file
71
projet/src/alphalib/Atom.mli
Normal file
|
@ -0,0 +1,71 @@
|
|||
(* TEMPORARY document *)
|
||||
|
||||
(* An atom is a pair of a unique integer identity and a (not necessarily
|
||||
unique) string. *)
|
||||
|
||||
type atom
|
||||
|
||||
and t = atom
|
||||
[@@deriving show]
|
||||
|
||||
val identity: atom -> int
|
||||
val hint: atom -> string
|
||||
|
||||
(* Producing fresh atoms. *)
|
||||
|
||||
val fresh: string -> atom
|
||||
val copy: atom -> atom
|
||||
|
||||
(* Comparison of atoms. *)
|
||||
|
||||
val equal: atom -> atom -> bool
|
||||
val compare: atom -> atom -> int
|
||||
val hash: atom -> int
|
||||
|
||||
(* Sets. *)
|
||||
|
||||
module Set : sig
|
||||
include Set.S with type elt = atom
|
||||
|
||||
val disjoint: t -> t -> bool
|
||||
|
||||
val union_many: ('a -> t) -> 'a list -> t
|
||||
|
||||
(* Disjoint union. *)
|
||||
|
||||
exception NonDisjointUnion of atom
|
||||
val disjoint_union: t -> t -> t
|
||||
|
||||
val handle_NonDisjointUnion: ('a -> unit) -> 'a -> bool
|
||||
|
||||
(* Sets of atoms form monoids under union and disjoint union. *)
|
||||
|
||||
class ['z] union_monoid : object
|
||||
constraint 'z = t
|
||||
method zero: 'z
|
||||
method plus: 'z -> 'z -> 'z
|
||||
end
|
||||
|
||||
class ['z] disjoint_union_monoid : object
|
||||
constraint 'z = t
|
||||
method zero: 'z
|
||||
method plus: 'z -> 'z -> 'z
|
||||
end
|
||||
|
||||
val show: t -> string
|
||||
val print: out_channel -> t -> unit
|
||||
end
|
||||
|
||||
(* Maps. *)
|
||||
|
||||
module Map : sig
|
||||
|
||||
include Map.S with type key = atom
|
||||
|
||||
val domain: 'a t -> Set.t
|
||||
val codomain: ('a -> Set.t) -> 'a t -> Set.t
|
||||
|
||||
end
|
||||
|
||||
type renaming =
|
||||
atom Map.t
|
126
projet/src/kremlin/C.ml
Normal file
126
projet/src/kremlin/C.ml
Normal file
|
@ -0,0 +1,126 @@
|
|||
|
||||
|
||||
module K = Constant
|
||||
open Common
|
||||
|
||||
(* This pretty-printer based on: http:/ /en.cppreference.com/w/c/language/declarations
|
||||
* Many cases are omitted from this bare-bones C grammar; hopefully, to be extended. *)
|
||||
type type_spec =
|
||||
| Int of Constant.width
|
||||
| Void
|
||||
| Named of ident
|
||||
| Struct of ident option * declaration list option
|
||||
(** Note: the option allows for zero-sized structs (GCC's C, C++) but as
|
||||
* of 2017/05/14 we never generate these. *)
|
||||
| Union of ident option * declaration list
|
||||
| Enum of ident option * ident list
|
||||
(** not encoding all the invariants here *)
|
||||
[@@deriving show { with_path = false }]
|
||||
|
||||
and storage_spec =
|
||||
| Typedef
|
||||
| Extern
|
||||
| Static
|
||||
|
||||
and declarator_and_init =
|
||||
declarator * init option
|
||||
|
||||
and declarator_and_inits =
|
||||
declarator_and_init list
|
||||
|
||||
and declarator =
|
||||
| Ident of ident
|
||||
| Pointer of declarator
|
||||
| Array of declarator * expr
|
||||
| Function of calling_convention option * declarator * params
|
||||
|
||||
and expr =
|
||||
| Op1 of K.op * expr
|
||||
| Op2 of K.op * expr * expr
|
||||
| Index of expr * expr
|
||||
| Deref of expr
|
||||
| Address of expr
|
||||
| Member of expr * expr
|
||||
| MemberP of expr * expr
|
||||
| Assign of expr * expr
|
||||
(** not covering *=, +=, etc. *)
|
||||
| Call of expr * expr list
|
||||
| Name of ident
|
||||
| Cast of type_name * expr
|
||||
| Literal of string
|
||||
| Constant of K.t
|
||||
| Bool of bool
|
||||
| Sizeof of expr
|
||||
| SizeofType of type_spec
|
||||
| CompoundLiteral of type_name * init list
|
||||
| MemberAccess of expr * ident
|
||||
| MemberAccessPointer of expr * ident
|
||||
| InlineComment of string * expr * string
|
||||
| Type of type_name
|
||||
(** note: these two not in the C grammar *)
|
||||
|
||||
(** this is a WILD approximation of the notion of "type name" in C _and_ a hack
|
||||
* because there's the invariant that the ident found at the bottom of the
|
||||
* [declarator] is irrelevant... *)
|
||||
and type_name =
|
||||
type_spec * declarator
|
||||
|
||||
and params =
|
||||
(* No support for old syntax, e.g. K&R, or [void f(void)]. *)
|
||||
param list
|
||||
|
||||
and param =
|
||||
(** Also approximate. *)
|
||||
type_spec * declarator
|
||||
|
||||
and declaration =
|
||||
type_spec * storage_spec option * declarator_and_inits
|
||||
|
||||
and ident =
|
||||
string
|
||||
|
||||
and init =
|
||||
| InitExpr of expr
|
||||
| Designated of designator * init
|
||||
| Initializer of init list
|
||||
|
||||
and designator =
|
||||
| Dot of ident
|
||||
| Bracket of int
|
||||
|
||||
(** Note: according to http:/ /en.cppreference.com/w/c/language/statements,
|
||||
* declarations can only be part of a compound statement... we do not enforce
|
||||
* this invariant via the type [stmt], but rather check it at runtime (see
|
||||
* [mk_compound_if]), as the risk of messing things up, naturally. *)
|
||||
type stmt =
|
||||
| Compound of stmt list
|
||||
| DeclStmt of declaration
|
||||
| Expr of expr
|
||||
| If of expr * stmt
|
||||
| IfElse of expr * stmt * stmt
|
||||
| While of expr * stmt
|
||||
| For of declaration_or_expr * expr * expr * stmt
|
||||
| Return of expr option
|
||||
| Switch of expr * (expr * stmt) list * stmt
|
||||
(** the last component is the default statement *)
|
||||
| Break
|
||||
| Comment of string
|
||||
(** note: this is not in the C grammar *)
|
||||
|
||||
and program =
|
||||
declaration_or_function list
|
||||
|
||||
and comment =
|
||||
string
|
||||
|
||||
and declaration_or_function =
|
||||
| Decl of comment list * declaration
|
||||
| Function of comment list * bool * declaration * stmt
|
||||
(** [stmt] _must_ be a compound statement; boolean is inline *)
|
||||
|
||||
and declaration_or_expr = [
|
||||
| `Decl of declaration
|
||||
| `Expr of expr
|
||||
| `Skip
|
||||
]
|
||||
[@@deriving show { with_path = false }]
|
19
projet/src/kremlin/Common.ml
Normal file
19
projet/src/kremlin/Common.ml
Normal file
|
@ -0,0 +1,19 @@
|
|||
type calling_convention =
|
||||
| StdCall
|
||||
| CDecl
|
||||
| FastCall
|
||||
[@@deriving show { with_path = false }]
|
||||
|
||||
type lifetime =
|
||||
| Eternal
|
||||
| Stack
|
||||
[@@deriving show { with_path = false }]
|
||||
|
||||
type flag =
|
||||
| Private
|
||||
| NoExtract
|
||||
| CInline
|
||||
| Substitute
|
||||
| GcType
|
||||
| Comment of string
|
||||
[@@deriving show { with_path = false }]
|
66
projet/src/kremlin/Constant.ml
Normal file
66
projet/src/kremlin/Constant.ml
Normal file
|
@ -0,0 +1,66 @@
|
|||
(** Machine integers. Don't repeat the same thing everywhere. *)
|
||||
|
||||
type t = width * string
|
||||
[@@deriving show]
|
||||
|
||||
and width =
|
||||
| UInt8 | UInt16 | UInt32 | UInt64 | Int8 | Int16 | Int32 | Int64
|
||||
| Bool
|
||||
| CInt (** Checked signed integers. *)
|
||||
|
||||
let bytes_of_width (w: width) =
|
||||
match w with
|
||||
| UInt8 -> 1
|
||||
| UInt16 -> 2
|
||||
| UInt32 -> 4
|
||||
| UInt64 -> 8
|
||||
| Int8 -> 1
|
||||
| Int16 -> 2
|
||||
| Int32 -> 4
|
||||
| Int64 -> 8
|
||||
| CInt -> 4
|
||||
| _ -> invalid_arg "bytes_of_width"
|
||||
|
||||
type op =
|
||||
(* Arithmetic operations *)
|
||||
| Add | AddW | Sub | SubW | Div | DivW | Mult | MultW | Mod
|
||||
(* Bitwise operations *)
|
||||
| BOr | BAnd | BXor | BShiftL | BShiftR | BNot
|
||||
(* Arithmetic comparisons / boolean comparisons *)
|
||||
| Eq | Neq | Lt | Lte | Gt | Gte
|
||||
(* Boolean operations *)
|
||||
| And | Or | Xor | Not
|
||||
(* Effectful operations *)
|
||||
| Assign | PreIncr | PreDecr | PostIncr | PostDecr
|
||||
(* Misc *)
|
||||
| Comma
|
||||
[@@deriving show { with_path = false }]
|
||||
|
||||
let unsigned_of_signed = function
|
||||
| Int8 -> UInt8
|
||||
| Int16 -> UInt16
|
||||
| Int32 -> UInt32
|
||||
| Int64 -> UInt64
|
||||
| CInt | UInt8 | UInt16 | UInt32 | UInt64 | Bool -> raise (Invalid_argument "unsigned_of_signed")
|
||||
|
||||
let is_signed = function
|
||||
| Int8 | Int16 | Int32 | Int64 | CInt -> true
|
||||
| UInt8 | UInt16 | UInt32 | UInt64 -> false
|
||||
| Bool -> raise (Invalid_argument "is_signed")
|
||||
|
||||
let is_unsigned w = not (is_signed w)
|
||||
|
||||
let without_wrap = function
|
||||
| AddW -> Add
|
||||
| SubW -> Sub
|
||||
| MultW -> Mult
|
||||
| DivW -> Div
|
||||
| _ -> raise (Invalid_argument "without_wrap")
|
||||
|
||||
let uint8_of_int i =
|
||||
assert (i < (1 lsl 8) && i >= 0);
|
||||
UInt8, string_of_int i
|
||||
|
||||
let uint32_of_int i =
|
||||
assert (i < (1 lsl 32) && i >= 0);
|
||||
UInt32, string_of_int i
|
7
projet/src/kremlin/Options.ml
Normal file
7
projet/src/kremlin/Options.ml
Normal file
|
@ -0,0 +1,7 @@
|
|||
(* By default, the C printer inserts a minimal amount of parentheses.
|
||||
However, this can trigger GCC and Clang's -Wparentheses warning,
|
||||
so there is an option to produce more parentheses than strictly
|
||||
necessary. *)
|
||||
|
||||
let parentheses =
|
||||
ref false
|
354
projet/src/kremlin/PrintC.ml
Normal file
354
projet/src/kremlin/PrintC.ml
Normal file
|
@ -0,0 +1,354 @@
|
|||
(** Pretty-printer that conforms with C syntax. Also defines the grammar of
|
||||
* concrete C syntax, as opposed to our idealistic, well-formed C*. *)
|
||||
|
||||
open PPrint
|
||||
open PrintCommon
|
||||
open C
|
||||
|
||||
(* Pretty-printing of actual C syntax *****************************************)
|
||||
|
||||
let p_storage_spec = function
|
||||
| Typedef -> string "typedef"
|
||||
| Extern -> string "extern"
|
||||
| Static -> string "static"
|
||||
|
||||
let rec p_type_spec = function
|
||||
| Int w -> print_width w ^^ string "_t"
|
||||
| Void -> string "void"
|
||||
| Named s -> string s
|
||||
| Union (name, decls) ->
|
||||
group (string "union" ^/^
|
||||
(match name with Some name -> string name ^^ break1 | None -> empty)) ^^
|
||||
braces_with_nesting (separate_map hardline (fun p -> group (p_declaration p ^^ semi)) decls)
|
||||
| Struct (name, decls) ->
|
||||
group (string "struct" ^/^
|
||||
(match name with Some name -> string name | None -> empty)) ^^
|
||||
(match decls with
|
||||
| Some decls ->
|
||||
break1 ^^ braces_with_nesting (separate_map hardline (fun p -> group (p_declaration p ^^ semi)) decls)
|
||||
| None ->
|
||||
empty)
|
||||
| Enum (name, tags) ->
|
||||
group (string "enum" ^/^
|
||||
(match name with Some name -> string name ^^ break1 | None -> empty)) ^^
|
||||
braces_with_nesting (separate_map (comma ^^ break1) string tags)
|
||||
|
||||
|
||||
and p_type_declarator d =
|
||||
let rec p_noptr = function
|
||||
| Ident n ->
|
||||
string n
|
||||
| Array (d, s) ->
|
||||
p_noptr d ^^ lbracket ^^ p_expr s ^^ rbracket
|
||||
| Function (cc, d, params) ->
|
||||
let cc = match cc with Some cc -> print_cc cc ^^ break1 | None -> empty in
|
||||
group (cc ^^ p_noptr d ^^ parens_with_nesting (separate_map (comma ^^ break 1) (fun (spec, decl) ->
|
||||
group (p_type_spec spec ^/^ p_any decl)
|
||||
) params))
|
||||
| d ->
|
||||
lparen ^^ p_any d ^^ rparen
|
||||
and p_any = function
|
||||
| Pointer d ->
|
||||
star ^^ p_any d
|
||||
| d ->
|
||||
p_noptr d
|
||||
in
|
||||
p_any d
|
||||
|
||||
and p_type_name (spec, decl) =
|
||||
match decl with
|
||||
| Ident "" ->
|
||||
p_type_spec spec
|
||||
| _ ->
|
||||
p_type_spec spec ^^ space ^^ p_type_declarator decl
|
||||
|
||||
(* http:/ /en.cppreference.com/w/c/language/operator_precedence *)
|
||||
and prec_of_op2 op =
|
||||
let open Constant in
|
||||
match op with
|
||||
| AddW | SubW | MultW | DivW -> failwith "[prec_of_op]: should've been desugared"
|
||||
| Add -> 4, 4, 4
|
||||
| Sub -> 4, 4, 3
|
||||
| Div -> 3, 3, 2
|
||||
| Mult -> 3, 3, 3
|
||||
| Mod -> 3, 3, 2
|
||||
| BOr -> 10, 10, 10
|
||||
| BAnd ->
|
||||
8, 8, 8
|
||||
| BXor | Xor -> 9, 9, 9
|
||||
| BShiftL | BShiftR ->
|
||||
5, 5, 4
|
||||
| Eq | Neq -> 7, 7, 7
|
||||
| Lt | Lte | Gt | Gte -> 6, 6, 5
|
||||
| And -> 11, 11, 11
|
||||
| Or -> 12, 12, 12
|
||||
| Assign -> 14, 13, 14
|
||||
| Comma -> 15, 15, 14
|
||||
| PreIncr | PostIncr | PreDecr | PostDecr | Not | BNot -> raise (Invalid_argument "prec_of_op2")
|
||||
|
||||
and prec_of_op1 op =
|
||||
let open Constant in
|
||||
match op with
|
||||
| PreDecr | PreIncr | Not | BNot -> 2
|
||||
| PostDecr | PostIncr -> 1
|
||||
| _ -> raise (Invalid_argument "prec_of_op1")
|
||||
|
||||
and is_prefix op =
|
||||
let open Constant in
|
||||
match op with
|
||||
| PreDecr | PreIncr | Not | BNot -> true
|
||||
| PostDecr | PostIncr -> false
|
||||
| _ -> raise (Invalid_argument "is_prefix")
|
||||
|
||||
(* The precedence level [curr] is the maximum precedence the current node should
|
||||
* have. If it doesn't, then it should be parenthesized. Lower numbers bind
|
||||
* tighter. *)
|
||||
and paren_if curr mine doc =
|
||||
if curr < mine then
|
||||
group (lparen ^^ doc ^^ rparen)
|
||||
else
|
||||
doc
|
||||
|
||||
(* [e] is an operand of [op]; is this likely to trigger GCC's -Wparentheses? If
|
||||
* so, downgrade the current precedence to 0 to force parenthses. *)
|
||||
and defeat_Wparentheses op e prec =
|
||||
let open Constant in
|
||||
if not !Options.parentheses then
|
||||
prec
|
||||
else match op, e with
|
||||
| (BShiftL | BShiftR | BXor | BOr | BAnd), Op2 ((Add | Sub | BOr | BXor), _, _) ->
|
||||
0
|
||||
| _ ->
|
||||
prec
|
||||
|
||||
and p_expr' curr = function
|
||||
| Op1 (op, e1) ->
|
||||
let mine = prec_of_op1 op in
|
||||
let e1 = p_expr' mine e1 in
|
||||
paren_if curr mine (if is_prefix op then print_op op ^^ e1 else e1 ^^ print_op op)
|
||||
| Op2 (op, e1, e2) ->
|
||||
let mine, left, right = prec_of_op2 op in
|
||||
let left = defeat_Wparentheses op e1 left in
|
||||
let right = defeat_Wparentheses op e2 right in
|
||||
let e1 = p_expr' left e1 in
|
||||
let e2 = p_expr' right e2 in
|
||||
paren_if curr mine (e1 ^/^ print_op op ^^ jump e2)
|
||||
| Index (e1, e2) ->
|
||||
let mine, left, right = 1, 1, 15 in
|
||||
let e1 = p_expr' left e1 in
|
||||
let e2 = p_expr' right e2 in
|
||||
paren_if curr mine (e1 ^^ lbracket ^^ e2 ^^ rbracket)
|
||||
| Assign (e1, e2) ->
|
||||
let mine, left, right = 14, 13, 14 in
|
||||
let e1 = p_expr' left e1 in
|
||||
let e2 = p_expr' right e2 in
|
||||
paren_if curr mine (group (e1 ^/^ equals) ^^ jump e2)
|
||||
| Call (e, es) ->
|
||||
let mine, left, arg = 1, 1, 14 in
|
||||
let e = p_expr' left e in
|
||||
let es = nest 2 (separate_map (comma ^^ break 1) (fun e -> group (p_expr' arg e)) es) in
|
||||
paren_if curr mine (e ^^ lparen ^^ es ^^ rparen)
|
||||
| Literal s ->
|
||||
dquote ^^ string s ^^ dquote
|
||||
| Constant (w, s) ->
|
||||
string s ^^ if K.is_unsigned w then string "U" else empty
|
||||
| Name s ->
|
||||
string s
|
||||
| Cast (t, e) ->
|
||||
let mine, right = 2, 2 in
|
||||
let e = group (p_expr' right e) in
|
||||
let t = p_type_name t in
|
||||
paren_if curr mine (lparen ^^ t ^^ rparen ^^ e)
|
||||
| Type t ->
|
||||
p_type_name t
|
||||
| Sizeof e ->
|
||||
let mine, right = 2, 2 in
|
||||
let e = p_expr' right e in
|
||||
paren_if curr mine (string "sizeof" ^^ space ^^ e)
|
||||
| SizeofType t ->
|
||||
string "sizeof" ^^ group (lparen ^^ p_type_spec t ^^ rparen)
|
||||
| Address e ->
|
||||
let mine, right = 2, 2 in
|
||||
let e = p_expr' right e in
|
||||
paren_if curr mine (ampersand ^^ e)
|
||||
| Deref e ->
|
||||
let mine, right = 2, 2 in
|
||||
let e = p_expr' right e in
|
||||
paren_if curr mine (star ^^ e)
|
||||
| Member _ | MemberP _ ->
|
||||
failwith "[p_expr']: not implemented"
|
||||
| Bool b ->
|
||||
string (string_of_bool b)
|
||||
| CompoundLiteral (t, init) ->
|
||||
(* NOTE: always parenthesize compound literal no matter what, because GCC
|
||||
* parses an application of a function to a compound literal as an n-ary
|
||||
* application. *)
|
||||
parens_with_nesting (
|
||||
lparen ^^ p_type_name t ^^ rparen ^^
|
||||
braces_with_nesting (separate_map (comma ^^ break1) p_init init)
|
||||
)
|
||||
| MemberAccess (expr, member) ->
|
||||
p_expr' 1 expr ^^ dot ^^ string member
|
||||
| MemberAccessPointer (expr, member) ->
|
||||
p_expr' 1 expr ^^ string "->" ^^ string member
|
||||
| InlineComment (s, e, s') ->
|
||||
surround 2 1 (p_comment s) (p_expr' curr e) (p_comment s')
|
||||
|
||||
and p_comment s =
|
||||
(* TODO: escape *)
|
||||
string "/* " ^^ nest 2 (flow space (words s)) ^^ string " */"
|
||||
|
||||
|
||||
and p_expr e = p_expr' 15 e
|
||||
|
||||
and p_init (i: init) =
|
||||
match i with
|
||||
| Designated (designator, i) ->
|
||||
group (p_designator designator ^^ space ^^ equals ^^ space ^^ p_init i)
|
||||
| InitExpr e ->
|
||||
p_expr' 14 e
|
||||
| Initializer inits ->
|
||||
let inits =
|
||||
if List.length inits > 4 then
|
||||
flow (comma ^^ break1) (List.map p_init inits)
|
||||
else
|
||||
separate_map (comma ^^ break1) p_init inits
|
||||
in
|
||||
braces_with_nesting inits
|
||||
|
||||
and p_designator = function
|
||||
| Dot ident ->
|
||||
dot ^^ string ident
|
||||
| Bracket i ->
|
||||
lbracket ^^ int i ^^ rbracket
|
||||
|
||||
and p_decl_and_init (decl, init) =
|
||||
group (p_type_declarator decl ^^ match init with
|
||||
| Some init ->
|
||||
space ^^ equals ^^ jump (p_init init)
|
||||
| None ->
|
||||
empty)
|
||||
|
||||
and p_declaration (spec, stor, decl_and_inits) =
|
||||
let stor = match stor with Some stor -> p_storage_spec stor ^^ space | None -> empty in
|
||||
stor ^^ group (p_type_spec spec) ^/^
|
||||
separate_map (comma ^^ break 1) p_decl_and_init decl_and_inits
|
||||
|
||||
(* This is abusing the definition of a compound statement to ensure it is printed with braces. *)
|
||||
let nest_if f stmt =
|
||||
match stmt with
|
||||
| Compound _ ->
|
||||
hardline ^^ f stmt
|
||||
| _ ->
|
||||
nest 2 (hardline ^^ f stmt)
|
||||
|
||||
(* A note on the classic dangling else problem. Remember that this is how things
|
||||
* are parsed (note the indentation):
|
||||
*
|
||||
* if (foo)
|
||||
* if (bar)
|
||||
* ...
|
||||
* else
|
||||
* ...
|
||||
*
|
||||
* And remember that this needs braces:
|
||||
*
|
||||
* if (foo) {
|
||||
* if (bar)
|
||||
* ...
|
||||
* } else
|
||||
* ...
|
||||
*
|
||||
* [protect_solo_if] adds braces to the latter case. However, GCC, unless
|
||||
* -Wnoparentheses is given, will produce a warning for the former case.
|
||||
* [protect_ite_if_needed] adds braces to the former case, when the user has
|
||||
* requested the extra, unnecessary parentheses needed to silence -Wparentheses.
|
||||
* *)
|
||||
let protect_solo_if s =
|
||||
match s with
|
||||
| If _ -> Compound [ s ]
|
||||
| _ -> s
|
||||
|
||||
let protect_ite_if_needed s =
|
||||
match s with
|
||||
| IfElse _ when !Options.parentheses -> Compound [ s ]
|
||||
| _ -> s
|
||||
|
||||
let p_or p x =
|
||||
match x with
|
||||
| Some x -> p x
|
||||
| None -> empty
|
||||
|
||||
let rec p_stmt (s: stmt) =
|
||||
(* [p_stmt] is responsible for appending [semi] and calling [group]! *)
|
||||
match s with
|
||||
| Compound stmts ->
|
||||
lbrace ^^ nest 2 (hardline ^^ separate_map hardline p_stmt stmts) ^^
|
||||
hardline ^^ rbrace
|
||||
| Expr expr ->
|
||||
group (p_expr expr ^^ semi)
|
||||
| Comment s ->
|
||||
group (string "/*" ^/^ separate break1 (words s) ^/^ string "*/")
|
||||
| For (decl, e2, e3, stmt) ->
|
||||
let init = match decl with
|
||||
| `Decl decl -> p_declaration decl
|
||||
| `Expr expr -> p_expr expr
|
||||
| `Skip -> empty
|
||||
in
|
||||
group (string "for" ^/^ lparen ^^ nest 2 (
|
||||
init ^^ semi ^^ break1 ^^
|
||||
p_expr e2 ^^ semi ^^ break1 ^^
|
||||
p_expr e3
|
||||
) ^^ rparen) ^^ nest_if p_stmt stmt
|
||||
| If (e, stmt) ->
|
||||
group (string "if" ^/^ lparen ^^ p_expr e ^^ rparen) ^^
|
||||
nest_if p_stmt (protect_ite_if_needed stmt)
|
||||
| IfElse (e, s1, s2) ->
|
||||
group (string "if" ^/^ lparen ^^ p_expr e ^^ rparen) ^^
|
||||
nest_if p_stmt (protect_solo_if s1) ^^ hardline ^^
|
||||
string "else" ^^
|
||||
(match s2 with
|
||||
| If _ | IfElse _ ->
|
||||
space ^^ p_stmt s2
|
||||
| _ ->
|
||||
nest_if p_stmt s2)
|
||||
| While (e, s) ->
|
||||
group (string "while" ^/^ lparen ^^ p_expr e ^^ rparen) ^^
|
||||
nest_if p_stmt s
|
||||
| Return None ->
|
||||
group (string "return" ^^ semi)
|
||||
| Return (Some e) ->
|
||||
group (string "return" ^^ jump (p_expr e ^^ semi))
|
||||
| DeclStmt d ->
|
||||
group (p_declaration d ^^ semi)
|
||||
| Switch (e, branches, default) ->
|
||||
group (string "switch" ^/^ lparen ^^ p_expr e ^^ rparen) ^/^
|
||||
braces_with_nesting (
|
||||
separate_map hardline (fun (e, s) ->
|
||||
group (string "case" ^/^ p_expr e ^^ colon) ^^ nest 2 (
|
||||
hardline ^^ p_stmt s
|
||||
)
|
||||
) branches ^^ hardline ^^
|
||||
string "default:" ^^ nest 2 (
|
||||
hardline ^^ p_stmt default
|
||||
)
|
||||
)
|
||||
| Break ->
|
||||
string "break" ^^ semi
|
||||
|
||||
let p_comments cs =
|
||||
separate_map hardline (fun c -> string ("/*\n" ^ c ^ "\n*/")) cs ^^
|
||||
if List.length cs > 0 then hardline else empty
|
||||
|
||||
let p_decl_or_function (df: declaration_or_function) =
|
||||
match df with
|
||||
| Decl (comments, d) ->
|
||||
p_comments comments ^^
|
||||
group (p_declaration d ^^ semi)
|
||||
| Function (comments, inline, d, stmt) ->
|
||||
p_comments comments ^^
|
||||
let inline = if inline then string "inline" ^^ space else empty in
|
||||
inline ^^ group (p_declaration d) ^/^ p_stmt stmt
|
||||
|
||||
let print_files =
|
||||
PrintCommon.print_files p_decl_or_function
|
96
projet/src/kremlin/PrintCommon.ml
Normal file
96
projet/src/kremlin/PrintCommon.ml
Normal file
|
@ -0,0 +1,96 @@
|
|||
open PPrint
|
||||
open Constant
|
||||
open Common
|
||||
|
||||
let break1 = break 1
|
||||
|
||||
let jump ?(indent=2) body =
|
||||
jump indent 1 body
|
||||
|
||||
let parens_with_nesting contents =
|
||||
surround 2 0 lparen contents rparen
|
||||
|
||||
let braces_with_nesting contents =
|
||||
surround 2 1 lbrace contents rbrace
|
||||
|
||||
let int i = string (string_of_int i)
|
||||
|
||||
let print_width = function
|
||||
| UInt8 -> string "uint8"
|
||||
| UInt16 -> string "uint16"
|
||||
| UInt32 -> string "uint32"
|
||||
| UInt64 -> string "uint64"
|
||||
| CInt -> string "krml_checked_int"
|
||||
| Int8 -> string "int8"
|
||||
| Int16 -> string "int16"
|
||||
| Int32 -> string "int32"
|
||||
| Int64 -> string "int64"
|
||||
| Bool -> string "bool"
|
||||
|
||||
let print_constant = function
|
||||
| w, s -> string s ^^ print_width w
|
||||
|
||||
let print_op = function
|
||||
| Add -> string "+"
|
||||
| AddW -> string "+w"
|
||||
| Sub -> string "-"
|
||||
| SubW -> string "-"
|
||||
| Div -> string "/"
|
||||
| DivW -> string "/w"
|
||||
| Mult -> string "*"
|
||||
| MultW -> string "*w"
|
||||
| Mod -> string "%"
|
||||
| BOr -> string "|"
|
||||
| BAnd -> string "&"
|
||||
| BXor -> string "^"
|
||||
| BNot -> string "~"
|
||||
| BShiftL -> string "<<"
|
||||
| BShiftR -> string ">>"
|
||||
| Eq -> string "=="
|
||||
| Neq -> string "!="
|
||||
| Lt -> string "<"
|
||||
| Lte -> string "<="
|
||||
| Gt -> string ">"
|
||||
| Gte -> string ">="
|
||||
| And -> string "&&"
|
||||
| Or -> string "||"
|
||||
| Xor -> string "^"
|
||||
| Not -> string "!"
|
||||
| PostIncr | PreIncr -> string "++"
|
||||
| PostDecr | PreDecr -> string "--"
|
||||
| Assign -> string "="
|
||||
| Comma -> string ","
|
||||
|
||||
let print_cc = function
|
||||
| CDecl -> string "__cdecl"
|
||||
| StdCall -> string "__stdcall"
|
||||
| FastCall -> string "__fastcall"
|
||||
|
||||
let print_lident (idents, ident) =
|
||||
separate_map dot string (idents @ [ ident ])
|
||||
|
||||
let print_program p decls =
|
||||
separate_map (hardline ^^ hardline) p decls
|
||||
|
||||
let print_files print_decl files =
|
||||
separate_map hardline (fun (f, p) ->
|
||||
string (String.uppercase f) ^^ colon ^^ jump (print_program print_decl p)
|
||||
) files
|
||||
|
||||
let printf_of_pprint f =
|
||||
fun buf t ->
|
||||
PPrint.ToBuffer.compact buf (f t)
|
||||
|
||||
let printf_of_pprint_pretty f =
|
||||
fun buf t ->
|
||||
PPrint.ToBuffer.pretty 0.95 Utils.twidth buf (f t)
|
||||
|
||||
let pdoc buf d =
|
||||
PPrint.ToBuffer.compact buf d
|
||||
|
||||
let english_join s =
|
||||
match List.rev s with
|
||||
| [] -> empty
|
||||
| [ x ] -> x
|
||||
| last :: first ->
|
||||
group (separate (comma ^^ break1) (List.rev first) ^/^ string "and" ^/^ last)
|
73
projet/src/kremlin/Utils.ml
Normal file
73
projet/src/kremlin/Utils.ml
Normal file
|
@ -0,0 +1,73 @@
|
|||
let try_finally f h = let result =
|
||||
try
|
||||
f ()
|
||||
with e ->
|
||||
h ();
|
||||
raise e
|
||||
in
|
||||
h ();
|
||||
result
|
||||
|
||||
let with_open_in file_path f =
|
||||
let c = open_in_bin file_path in
|
||||
try_finally (fun () ->
|
||||
f c
|
||||
) (fun () ->
|
||||
close_in c
|
||||
)
|
||||
|
||||
let with_open_out file_path f =
|
||||
let c = open_out file_path in
|
||||
try_finally (fun () ->
|
||||
f c
|
||||
) (fun () ->
|
||||
close_out c
|
||||
)
|
||||
|
||||
|
||||
let cp dst src = with_open_out dst (fun oc ->
|
||||
with_open_in src (fun ic ->
|
||||
let buf = Bytes.create 2048 in
|
||||
while
|
||||
let l = input ic buf 0 2048 in
|
||||
if l > 0 then begin
|
||||
output oc buf 0 l;
|
||||
true
|
||||
end else
|
||||
false
|
||||
do () done
|
||||
))
|
||||
|
||||
|
||||
let read ic =
|
||||
let buf = Buffer.create 4096 in
|
||||
let s = String.create 2048 in
|
||||
while begin
|
||||
let l = input ic s 0 (String.length s) in
|
||||
if l > 0 then begin
|
||||
Buffer.add_string buf (String.sub s 0 l);
|
||||
true
|
||||
end else begin
|
||||
false
|
||||
end
|
||||
end do () done;
|
||||
Buffer.contents buf
|
||||
|
||||
let file_get_contents f =
|
||||
with_open_in f read
|
||||
|
||||
(** Sniff the size of the terminal for optimal use of the width. *)
|
||||
let theight, twidth =
|
||||
let height, width = ref 0, ref 0 in
|
||||
match
|
||||
Scanf.sscanf (List.hd (Process.read_stdout "stty" [|"size"|])) "%d %d" (fun h w ->
|
||||
height := h;
|
||||
width := w);
|
||||
!height, !width
|
||||
with
|
||||
| exception _ ->
|
||||
24, 80
|
||||
| 0, 0 ->
|
||||
24, 80
|
||||
| h, w ->
|
||||
h, w
|
83
projet/src/prologue.h
Normal file
83
projet/src/prologue.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* A forward declaration of [block] -- see below. */
|
||||
|
||||
struct block;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* The type [univ] is the universal type of the values that we manipulate.
|
||||
A value is either an integer or a pointer to a heap-allocated memory
|
||||
block. A C union is used to represent this disjunction. There is no tag
|
||||
to distinguish between the two alternatives! The source program had
|
||||
better be well-typed. */
|
||||
|
||||
typedef union {
|
||||
|
||||
/* Either this is an integer... */
|
||||
int literal;
|
||||
|
||||
/* ... or this is a pointer to a block. */
|
||||
struct block* pointer;
|
||||
|
||||
} univ;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* The struct [block] describes the uniform layout of a heap-allocated
|
||||
memory block. A block begins with an integer tag and continues with a
|
||||
sequence of fields of type [univ]. */
|
||||
|
||||
struct block {
|
||||
|
||||
/* Every memory block begins with an integer tag. */
|
||||
int tag;
|
||||
|
||||
/* Then, we have a sequence of fields, whose number depends on the tag.
|
||||
This idiom is known in C99 as a flexible array member. */
|
||||
univ data[];
|
||||
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Basic operations on memory blocks. */
|
||||
|
||||
/* The macro [ALLOC(n)] allocates a block of [n] fields, and is used in a
|
||||
context where an expression of type [univ] is expected. We use a C
|
||||
"compound literal" containing a "designated initializer" to indicate
|
||||
that we wish to choose the "pointer" side of the union. We implicitly
|
||||
assume that the compiler inserts no padding between the "tag" and "data"
|
||||
parts of a memory block, so [(n + 1) * sizeof(univ)] is enough space to
|
||||
hold a block of [n] fields. */
|
||||
|
||||
#define ALLOC(n) \
|
||||
(univ) { .pointer = malloc((n + 1) * sizeof(univ)) }
|
||||
|
||||
/* In the following macros, [u] has type [univ], so [u.pointer] has type
|
||||
[struct block] and is (or should be) the address of a memory block.
|
||||
[i] is a field number; the numbering of fields is 0-based. */
|
||||
|
||||
#define GET_TAG(u) \
|
||||
(u.pointer->tag)
|
||||
|
||||
#define SET_TAG(u,t) \
|
||||
(u.pointer->tag = t)
|
||||
|
||||
#define GET_FIELD(u,i) \
|
||||
(u.pointer->data[i])
|
||||
|
||||
#define SET_FIELD(u,i,v) \
|
||||
(u.pointer->data[i]=v)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Basic operations on integers. */
|
||||
|
||||
/* The macro [FROM_INT(i)] converts the integer [i] to the type [univ]. */
|
||||
|
||||
#define FROM_INT(i) \
|
||||
(univ) { .literal = i }
|
||||
|
||||
/* The macro [TO_INT(u)] converts [u] to the type [int]. */
|
||||
|
||||
#define TO_INT(u) \
|
||||
u.literal
|
1
projet/src/test/.gitignore
vendored
Normal file
1
projet/src/test/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
test.native
|
1
projet/src/test/.merlin
Normal file
1
projet/src/test/.merlin
Normal file
|
@ -0,0 +1 @@
|
|||
B _build
|
21
projet/src/test/Makefile
Normal file
21
projet/src/test/Makefile
Normal file
|
@ -0,0 +1,21 @@
|
|||
OCAMLBUILD := ocamlbuild -use-ocamlfind -classic-display
|
||||
TARGET := test.native
|
||||
TESTS := ../tests
|
||||
|
||||
.PHONY: all test expected clean
|
||||
|
||||
all:
|
||||
@ $(OCAMLBUILD) $(TARGET)
|
||||
|
||||
test: all
|
||||
@ $(MAKE) -C ..
|
||||
@ ./$(TARGET) --verbosity 1
|
||||
|
||||
expected: all
|
||||
@ $(MAKE) -C ..
|
||||
@ ./$(TARGET) --verbosity 1 --create-expected
|
||||
|
||||
clean:
|
||||
@ rm -f *~ *.native *.byte
|
||||
@ $(OCAMLBUILD) -clean
|
||||
@ rm -f $(TESTS)/*.c $(TESTS)/*.exe $(TESTS)/*.out $(TESTS)/*.err $(TESTS)/*~
|
3
projet/src/test/_tags
Normal file
3
projet/src/test/_tags
Normal file
|
@ -0,0 +1,3 @@
|
|||
true: \
|
||||
warn(A-44), \
|
||||
package(unix)
|
138
projet/src/test/auxiliary.ml
Normal file
138
projet/src/test/auxiliary.ml
Normal file
|
@ -0,0 +1,138 @@
|
|||
type filename = string
|
||||
type command = string
|
||||
|
||||
let has_suffix suffix name =
|
||||
Filename.check_suffix name suffix
|
||||
|
||||
let fail format =
|
||||
Printf.kprintf failwith format
|
||||
|
||||
let try_finally f h =
|
||||
let result = try f() with e -> h(); raise e in
|
||||
h(); result
|
||||
|
||||
let with_process_code_result command (f : in_channel -> 'a) : int * 'a =
|
||||
let ic = Unix.open_process_in command in
|
||||
set_binary_mode_in ic false;
|
||||
match f ic with
|
||||
| exception e ->
|
||||
ignore (Unix.close_process_in ic); raise e
|
||||
| result ->
|
||||
match Unix.close_process_in ic with
|
||||
| Unix.WEXITED code ->
|
||||
code, result
|
||||
| Unix.WSIGNALED _
|
||||
| Unix.WSTOPPED _ ->
|
||||
99 (* arbitrary *), result
|
||||
|
||||
let with_process_result command (f : in_channel -> 'a) : 'a =
|
||||
let code, result = with_process_code_result command f in
|
||||
if code = 0 then
|
||||
result
|
||||
else
|
||||
fail "Command %S failed with exit code %d." command code
|
||||
|
||||
let with_open_in filename (f : in_channel -> 'a) : 'a =
|
||||
let ic = open_in filename in
|
||||
try_finally
|
||||
(fun () -> f ic)
|
||||
(fun () -> close_in_noerr ic)
|
||||
|
||||
let with_open_out filename (f : out_channel -> 'a) : 'a =
|
||||
let oc = open_out filename in
|
||||
try_finally
|
||||
(fun () -> f oc)
|
||||
(fun () -> close_out_noerr oc)
|
||||
|
||||
let chunk_size =
|
||||
16384
|
||||
|
||||
let exhaust (ic : in_channel) : string =
|
||||
let buffer = Buffer.create chunk_size in
|
||||
let chunk = Bytes.create chunk_size in
|
||||
let rec loop () =
|
||||
let length = input ic chunk 0 chunk_size in
|
||||
if length = 0 then
|
||||
Buffer.contents buffer
|
||||
else begin
|
||||
Buffer.add_subbytes buffer chunk 0 length;
|
||||
loop()
|
||||
end
|
||||
in
|
||||
loop()
|
||||
|
||||
let read_whole_file filename =
|
||||
with_open_in filename exhaust
|
||||
|
||||
let absolute_directory (path : string) : string =
|
||||
try
|
||||
let pwd = Sys.getcwd() in
|
||||
Sys.chdir path;
|
||||
let result = Sys.getcwd() in
|
||||
Sys.chdir pwd;
|
||||
result
|
||||
with Sys_error _ ->
|
||||
Printf.fprintf stderr "Error: the directory %s does not seem to exist.\n" path;
|
||||
exit 1
|
||||
|
||||
let get_number_of_cores () =
|
||||
try match Sys.os_type with
|
||||
| "Win32" ->
|
||||
int_of_string (Sys.getenv "NUMBER_OF_PROCESSORS")
|
||||
| _ ->
|
||||
with_process_result "getconf _NPROCESSORS_ONLN" (fun ic ->
|
||||
let ib = Scanf.Scanning.from_channel ic in
|
||||
Scanf.bscanf ib "%d" (fun n -> n)
|
||||
)
|
||||
with
|
||||
| Not_found
|
||||
| Sys_error _
|
||||
| Failure _
|
||||
| Scanf.Scan_failure _
|
||||
| End_of_file
|
||||
| Unix.Unix_error _ ->
|
||||
1
|
||||
|
||||
let silent command : command =
|
||||
command ^ " >/dev/null 2>/dev/null"
|
||||
|
||||
(* [groups eq xs] segments the list [xs] into a list of groups, where several
|
||||
consecutive elements belong in the same group if they are equivalent in the
|
||||
sense of the function [eq]. *)
|
||||
|
||||
(* The auxiliary function [groups1] deals with the case where we have an open
|
||||
group [group] of which [x] is a member. The auxiliary function [group0]
|
||||
deals with the case where we have no open group. [groups] is the list of
|
||||
closed groups found so far, and [ys] is the list of elements that remain to
|
||||
be examined. *)
|
||||
|
||||
let rec groups1 eq groups x group ys =
|
||||
match ys with
|
||||
| [] ->
|
||||
group :: groups
|
||||
| y :: ys ->
|
||||
if eq x y then
|
||||
groups1 eq groups x (y :: group) ys
|
||||
else
|
||||
groups0 eq (List.rev group :: groups) (y :: ys)
|
||||
|
||||
and groups0 eq groups ys =
|
||||
match ys with
|
||||
| [] ->
|
||||
groups
|
||||
| y :: ys ->
|
||||
groups1 eq groups y [y] ys
|
||||
|
||||
let groups eq (xs : 'a list) : 'a list list =
|
||||
List.rev (groups0 eq [] xs)
|
||||
|
||||
(* [sep ss] separates the nonempty strings in the list [ss] with a space,
|
||||
and concatenates everything, producing a single string. *)
|
||||
|
||||
let sep (ss : string list) : string =
|
||||
let ss = List.filter (fun s -> String.length s > 0) ss in
|
||||
match ss with
|
||||
| [] ->
|
||||
""
|
||||
| s :: ss ->
|
||||
List.fold_left (fun s1 s2 -> s1 ^ " " ^ s2) s ss
|
288
projet/src/test/test.ml
Normal file
288
projet/src/test/test.ml
Normal file
|
@ -0,0 +1,288 @@
|
|||
open Sys
|
||||
open Array
|
||||
open List
|
||||
open Filename
|
||||
open Printf
|
||||
open Auxiliary
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Settings. *)
|
||||
|
||||
let create_expected =
|
||||
ref false
|
||||
|
||||
let verbosity =
|
||||
ref 0
|
||||
|
||||
let usage =
|
||||
sprintf "Usage: %s\n" argv.(0)
|
||||
|
||||
let spec = Arg.align [
|
||||
"--create-expected", Arg.Set create_expected,
|
||||
" recreate the expected-output files";
|
||||
"--verbosity", Arg.Int ((:=) verbosity),
|
||||
" set the verbosity level (0-2)";
|
||||
]
|
||||
|
||||
let () =
|
||||
Arg.parse spec (fun _ -> ()) usage
|
||||
|
||||
let create_expected =
|
||||
!create_expected
|
||||
|
||||
let verbosity =
|
||||
!verbosity
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Logging. *)
|
||||
|
||||
(* 0 is minimal verbosity;
|
||||
1 shows some progress messages;
|
||||
2 is maximal verbosity. *)
|
||||
|
||||
let log level format =
|
||||
kprintf (fun s ->
|
||||
if level <= verbosity then
|
||||
print_string s
|
||||
) format
|
||||
|
||||
(* Extend [fail] to display an information message along the way.
|
||||
The message is immediately emitted by the worker, depending on
|
||||
the verbosity level, whereas the failure message is sent back
|
||||
to the master. *)
|
||||
|
||||
let fail id format =
|
||||
log 1 "[FAIL] %s\n%!" id;
|
||||
fail format
|
||||
|
||||
(* When issuing an external command, log it along the way. *)
|
||||
|
||||
let command cmd =
|
||||
log 2 "%s\n%!" cmd;
|
||||
command cmd
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Paths. *)
|
||||
|
||||
let root =
|
||||
absolute_directory ".."
|
||||
|
||||
let src =
|
||||
root
|
||||
|
||||
let good =
|
||||
root ^ "/tests"
|
||||
|
||||
let good_slash filename =
|
||||
good ^ "/" ^ filename
|
||||
|
||||
let joujou =
|
||||
src ^ "/joujou"
|
||||
|
||||
let gcc =
|
||||
sprintf "gcc -O2 -I %s" src
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Test files. *)
|
||||
|
||||
let thisfile =
|
||||
"this input file"
|
||||
|
||||
let lambda basename =
|
||||
basename ^ ".lambda"
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Test inputs and outputs. *)
|
||||
|
||||
(* A test input is a basename, without the .lambda extension. *)
|
||||
|
||||
type input =
|
||||
| PositiveTest of filename
|
||||
|
||||
type inputs = input list
|
||||
|
||||
let print_input = function
|
||||
| PositiveTest basename ->
|
||||
basename
|
||||
|
||||
type outcome =
|
||||
| OK
|
||||
| Fail of string (* message *)
|
||||
|
||||
let print_outcome = function
|
||||
| OK ->
|
||||
""
|
||||
| Fail msg ->
|
||||
msg
|
||||
|
||||
type output =
|
||||
input * outcome
|
||||
|
||||
type outputs = output list
|
||||
|
||||
let print_output (input, outcome) =
|
||||
printf "\n[FAIL] %s\n%s"
|
||||
(print_input input)
|
||||
(print_outcome outcome)
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Auxiliary functions. *)
|
||||
|
||||
let check_expected directory id result expected =
|
||||
let cmd = sep ["cd"; directory; "&&"; "cp"; "-f"; result; expected] in
|
||||
let copy() =
|
||||
if command cmd <> 0 then
|
||||
fail id "Failed to create %s.\n" expected
|
||||
in
|
||||
(* If we are supposed to create the [expected] file, do so. *)
|
||||
if create_expected then
|
||||
copy()
|
||||
(* Otherwise, check that the file [expected] exists. If it does not exist,
|
||||
create it by renaming [result] to [expected], then fail and invite the
|
||||
user to review the newly created file. *)
|
||||
else if not (file_exists (directory ^ "/" ^ expected)) then begin
|
||||
copy();
|
||||
let cmd = sep ["more"; directory ^ "/" ^ expected] in
|
||||
fail id "The file %s did not exist.\n\
|
||||
I have just created it. Please review it.\n %s\n"
|
||||
expected cmd
|
||||
end
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Running a positive test. *)
|
||||
|
||||
(*
|
||||
Conventions:
|
||||
The file %.c stores the output of joujou.
|
||||
The file %.exe is the compiled program produced by gcc.
|
||||
The file %.out stores the output of the compiled program.
|
||||
The file %.exp stores the expected output.
|
||||
The file %.err stores error output (at several stages).
|
||||
*)
|
||||
|
||||
let process_positive_test (base : string) : unit =
|
||||
|
||||
(* Display an information message. *)
|
||||
log 2 "Testing %s...\n%!" base;
|
||||
|
||||
(* A suggested command. *)
|
||||
let more filename =
|
||||
sep ["more"; good_slash filename]
|
||||
in
|
||||
|
||||
(* Run joujou. *)
|
||||
let source = lambda base in
|
||||
let c = base ^ ".c" in
|
||||
let errors = base ^ ".err" in
|
||||
let cmd = sep (
|
||||
"cd" :: good :: "&&" ::
|
||||
joujou :: source :: sprintf ">%s" c :: sprintf "2>%s" errors :: []
|
||||
) in
|
||||
if command cmd <> 0 then
|
||||
fail base "joujou fails on this source file.\n\
|
||||
To see the source file:\n %s\n\
|
||||
To see the error messages:\n %s\n"
|
||||
(more source) (more errors);
|
||||
|
||||
(* Run the C compiler. *)
|
||||
let binary = base ^ ".exe" in
|
||||
let errors = base ^ ".err" in
|
||||
let cmd = sep (
|
||||
"cd" :: good :: "&&" ::
|
||||
gcc :: c :: "-o" :: binary :: sprintf "2>%s" errors :: []
|
||||
) in
|
||||
if command cmd <> 0 then
|
||||
fail base "The C compiler rejects the C file.\n\
|
||||
To see the C file:\n %s\n\
|
||||
To see the compiler's error messages:\n %s\n"
|
||||
(more c) (more errors);
|
||||
|
||||
(* Run the compiled program. *)
|
||||
let out = base ^ ".out" in
|
||||
let cmd = sep (
|
||||
"cd" :: good :: "&&" ::
|
||||
sprintf "./%s" binary :: sprintf ">%s" out :: "2>&1" :: []
|
||||
) in
|
||||
if command cmd <> 0 then
|
||||
fail base "The compiled program fails.\n\
|
||||
To see its output:\n %s\n" (more out);
|
||||
|
||||
(* Check that the file [exp] exists. *)
|
||||
let exp = base ^ ".exp" in
|
||||
check_expected good base out exp;
|
||||
|
||||
(* Check that the output coincides with what was expected. *)
|
||||
let cmd = sep ("cd" :: good :: "&&" :: "diff" :: exp :: out :: []) in
|
||||
if command (silent cmd) <> 0 then
|
||||
fail base "joujou accepts this input file, but produces incorrect output.\n\
|
||||
To see the difference:\n (%s)\n"
|
||||
cmd;
|
||||
|
||||
(* Succeed. *)
|
||||
log 1 "[OK] %s\n%!" base
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Running a test. *)
|
||||
|
||||
let process input : output =
|
||||
try
|
||||
begin match input with
|
||||
| PositiveTest basenames ->
|
||||
process_positive_test basenames
|
||||
end;
|
||||
input, OK
|
||||
with Failure msg ->
|
||||
input, Fail msg
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* [run] runs a bunch of tests in sequence. *)
|
||||
|
||||
let run (inputs : inputs) : outputs =
|
||||
flush stdout; flush stderr;
|
||||
let outputs = map process inputs in
|
||||
outputs
|
||||
|
||||
(* -------------------------------------------------------------------------- *)
|
||||
|
||||
(* Main. *)
|
||||
|
||||
(* Menhir can accept several .mly files at once. By convention, if several
|
||||
files have the same name up to a numeric suffix, then they belong in a
|
||||
single group and should be fed together to Menhir. *)
|
||||
|
||||
let inputs directory : filename list =
|
||||
readdir directory
|
||||
|> to_list
|
||||
|> filter (has_suffix ".lambda")
|
||||
|> map chop_extension
|
||||
|> sort compare
|
||||
|
||||
let positive : inputs =
|
||||
inputs good
|
||||
|> map (fun basename -> PositiveTest basename)
|
||||
|
||||
let inputs =
|
||||
positive
|
||||
|
||||
let outputs : outputs =
|
||||
printf "Preparing to run %d tests...\n%!" (length inputs);
|
||||
run inputs
|
||||
|
||||
let successful, failed =
|
||||
partition (fun (_, o) -> o = OK) outputs
|
||||
|
||||
let () =
|
||||
printf "%d out of %d tests are successful.\n"
|
||||
(length successful) (length inputs);
|
||||
failed |> iter (fun (input, outcome) ->
|
||||
printf "\n[FAIL] %s\n%s" (print_input input) (print_outcome outcome)
|
||||
)
|
4
projet/src/tests/.gitignore
vendored
Normal file
4
projet/src/tests/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.err
|
||||
*.out
|
||||
*.exe
|
||||
*.c
|
20
projet/src/tests/bool.exp
Normal file
20
projet/src/tests/bool.exp
Normal file
|
@ -0,0 +1,20 @@
|
|||
1
|
||||
0
|
||||
1
|
||||
0
|
||||
0
|
||||
24
|
||||
120
|
||||
8
|
||||
13
|
||||
5
|
||||
0
|
||||
1
|
||||
7
|
||||
1
|
||||
1
|
||||
2
|
||||
6
|
||||
24
|
||||
120
|
||||
42
|
88
projet/src/tests/bool.lambda
Normal file
88
projet/src/tests/bool.lambda
Normal file
|
@ -0,0 +1,88 @@
|
|||
(* Thunks. *)
|
||||
let force = fun x -> x 0 in
|
||||
(* Church Booleans. *)
|
||||
let false = fun x -> fun y -> y in
|
||||
let true = fun x -> fun y -> x in
|
||||
let k = true in
|
||||
let cond = fun p -> fun x -> fun y -> p x y in
|
||||
let forcecond = fun p -> fun x -> fun y -> force (p x y) in
|
||||
let bool2int = fun b -> cond b 1 0 in
|
||||
let _ = print (bool2int true) in
|
||||
let _ = print (bool2int false) in
|
||||
(* Church pairs. *)
|
||||
let pair = fun x -> fun y -> fun p -> cond p x y in
|
||||
let fst = fun p -> p true in
|
||||
let snd = fun p -> p false in
|
||||
(* Church naturals. *)
|
||||
let zero = fun f -> fun x -> x in
|
||||
let one = fun f -> fun x -> f x in
|
||||
let plus = fun m -> fun n -> fun f -> fun x -> m f (n f x) in
|
||||
let two = plus one one in
|
||||
let three = plus one two in
|
||||
let four = plus two two in
|
||||
let five = plus four one in
|
||||
let six = plus four two in
|
||||
let succ = plus one in
|
||||
let mult = fun m -> fun n -> fun f -> m (n f) in
|
||||
let exp = fun m -> fun n -> n m in
|
||||
let is_zero = fun n -> n (k false) true in
|
||||
let convert = fun n -> n (fun x -> x + 1) 0 in
|
||||
let _ = print (bool2int (is_zero zero)) in
|
||||
let _ = print (bool2int (is_zero one)) in
|
||||
let _ = print (bool2int (is_zero two)) in
|
||||
(* Factorial, based on Church naturals. *)
|
||||
let loop = fun p ->
|
||||
let n = succ (fst p) in
|
||||
pair n (mult n (snd p))
|
||||
in
|
||||
let fact = fun n ->
|
||||
snd (n loop (pair zero one))
|
||||
in
|
||||
let _ = print (convert (fact four)) in
|
||||
let _ = print (convert (fact five)) in
|
||||
(* Fibonacci, based on Church naturals. *)
|
||||
let loop = fun p ->
|
||||
let fib1 = fst p in
|
||||
pair (plus fib1 (snd p)) fib1
|
||||
in
|
||||
let fib = fun n ->
|
||||
snd (n loop (pair one one))
|
||||
in
|
||||
let _ = print (convert (fib five)) in
|
||||
let _ = print (convert (fib six)) in
|
||||
(* Predecessor. *)
|
||||
let loop = fun p ->
|
||||
let pred = fst p in
|
||||
pair (succ pred) pred
|
||||
in
|
||||
let pred = fun n ->
|
||||
snd (n loop (pair zero zero))
|
||||
in
|
||||
let _ = print (convert (pred six)) in
|
||||
(* Comparison, yielding a Church Boolean. *)
|
||||
let geq = fun m -> fun n ->
|
||||
m pred n (k false) true
|
||||
in
|
||||
let _ = print (bool2int (geq four six)) in
|
||||
let _ = print (bool2int (geq six one)) in
|
||||
(* Iteration. *)
|
||||
let iter = fun f -> fun n ->
|
||||
n f (f one)
|
||||
in
|
||||
(* Ackermann's function. *)
|
||||
let ack = fun n -> n iter succ n in
|
||||
let _ = print (convert (ack two)) in
|
||||
(* A fixpoint. *)
|
||||
let fix = fun f -> (fun y -> f (fun z -> y y z)) (fun y -> f (fun z -> y y z)) in
|
||||
(* Another version of fact. *)
|
||||
let fact = fun n ->
|
||||
fix (fun fact -> fun n -> forcecond (is_zero n) (fun _ -> one) (fun _ -> mult n (fact (pred n)))) n
|
||||
in
|
||||
let _ = print (convert (fact zero)) in
|
||||
let _ = print (convert (fact one)) in
|
||||
let _ = print (convert (fact two)) in
|
||||
let _ = print (convert (fact three)) in
|
||||
let _ = print (convert (fact four)) in
|
||||
let _ = print (convert (fact five)) in
|
||||
|
||||
print 42
|
1
projet/src/tests/church.exp
Executable file
1
projet/src/tests/church.exp
Executable file
|
@ -0,0 +1 @@
|
|||
42
|
16
projet/src/tests/church.lambda
Normal file
16
projet/src/tests/church.lambda
Normal file
|
@ -0,0 +1,16 @@
|
|||
let i = fun x -> x in
|
||||
let k = fun x -> fun y -> x in
|
||||
let zero = fun f -> i in
|
||||
let one = fun f -> fun x -> f x in
|
||||
let plus = fun m -> fun n -> fun f -> fun x -> m f (n f x) in
|
||||
let succ = plus one in
|
||||
let mult = fun m -> fun n -> fun f -> m (n f) in
|
||||
let exp = fun m -> fun n -> n m in
|
||||
let two = succ one in
|
||||
let three = succ two in
|
||||
let six = mult two three in
|
||||
let seven = succ six in
|
||||
let twenty_one = mult three seven in
|
||||
let forty_two = mult two twenty_one in
|
||||
let convert = fun n -> n (fun x -> x + 1) 0 in
|
||||
print (convert forty_two)
|
1
projet/src/tests/hello.exp
Executable file
1
projet/src/tests/hello.exp
Executable file
|
@ -0,0 +1 @@
|
|||
42
|
1
projet/src/tests/hello.lambda
Normal file
1
projet/src/tests/hello.lambda
Normal file
|
@ -0,0 +1 @@
|
|||
print (21 + 21)
|
10
projet/src/tests/loop/loop.lambda
Normal file
10
projet/src/tests/loop/loop.lambda
Normal file
|
@ -0,0 +1,10 @@
|
|||
(* This program is supposed to never terminate.
|
||||
This can actually work if the C compiler is
|
||||
smart enough to recognize and optimize tail
|
||||
calls. It works for me with clang but not with
|
||||
gcc, for some reason. *)
|
||||
let rec loop = fun x ->
|
||||
let _ = print x in
|
||||
loop (x + 1)
|
||||
in
|
||||
loop 0
|
2
projet/src/tests/printprint.exp
Executable file
2
projet/src/tests/printprint.exp
Executable file
|
@ -0,0 +1,2 @@
|
|||
42
|
||||
42
|
2
projet/src/tests/printprint.lambda
Normal file
2
projet/src/tests/printprint.lambda
Normal file
|
@ -0,0 +1,2 @@
|
|||
(* Because [print] returns its argument, this program should print 42 twice. *)
|
||||
print (print 42)
|
BIN
projet/sujet.pdf
Normal file
BIN
projet/sujet.pdf
Normal file
Binary file not shown.
Loading…
Reference in a new issue