mirror of
https://github.com/LucasVbr/croissant.git
synced 2026-05-13 17:12:10 +00:00
feat: Add typecheck to the AST
This commit is contained in:
@@ -3,4 +3,4 @@
|
|||||||
(executable
|
(executable
|
||||||
(name main)
|
(name main)
|
||||||
(public_name croissant)
|
(public_name croissant)
|
||||||
(libraries analyzer ast))
|
(libraries ast))
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
(* bin/main.ml *)
|
(* bin/main.ml *)
|
||||||
|
|
||||||
open Printf
|
open Printf
|
||||||
open Ast.Print
|
open Ast
|
||||||
|
|
||||||
exception Error of string
|
exception Error of string
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ let () =
|
|||||||
if args_count >= 2 then printf "%s" help_message
|
if args_count >= 2 then printf "%s" help_message
|
||||||
else if args_count = 1 then
|
else if args_count = 1 then
|
||||||
let file_path = Sys.argv.(1) in
|
let file_path = Sys.argv.(1) in
|
||||||
let ast = string_of_source_file (Analyzer.analyze_file file_path) in
|
let ast = Print.string_of_source_file (Analyzer.analyze_file file_path) in
|
||||||
printf "%s\n" ast
|
printf "%s\n" ast
|
||||||
else raise (Error "interpreter from stdin is not implemented yet")
|
else raise (Error "interpreter from stdin is not implemented yet")
|
||||||
(* TODO: Implement interpreter from stdin *)
|
(* TODO: Implement interpreter from stdin *)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
open Printf
|
open Printf
|
||||||
|
open Typecheck
|
||||||
|
|
||||||
let analyze lexbuf =
|
let analyze lexbuf =
|
||||||
let ast =
|
let ast =
|
||||||
@@ -19,6 +20,7 @@ let analyze lexbuf =
|
|||||||
file_name line_num col_num "Erreur syntaxique" "Syntaxe incorrecte";
|
file_name line_num col_num "Erreur syntaxique" "Syntaxe incorrecte";
|
||||||
exit 1
|
exit 1
|
||||||
in
|
in
|
||||||
|
let _ = type_of_source_file ast in
|
||||||
ast
|
ast
|
||||||
|
|
||||||
let analyze_file file_name =
|
let analyze_file file_name =
|
||||||
+8
-1
@@ -1,3 +1,10 @@
|
|||||||
(library
|
(library
|
||||||
(name ast)
|
(name ast)
|
||||||
(modules syntax print))
|
(modules lexer parser syntax print typecheck analyzer)
|
||||||
|
)
|
||||||
|
|
||||||
|
(menhir
|
||||||
|
(modules parser))
|
||||||
|
|
||||||
|
(ocamllex
|
||||||
|
(modules lexer))
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* lib/parser.mly */
|
/* lib/parser.mly */
|
||||||
%{
|
%{
|
||||||
open Ast.Syntax
|
open Syntax
|
||||||
%}
|
%}
|
||||||
|
|
||||||
%token <int> INTEGER
|
%token <int> INTEGER
|
||||||
@@ -103,7 +103,7 @@ unary_expression:
|
|||||||
|
|
||||||
binary_expression:
|
binary_expression:
|
||||||
| e1=expression "+" e2=expression { BinaryExpression(Add, e1, e2) }
|
| e1=expression "+" e2=expression { BinaryExpression(Add, e1, e2) }
|
||||||
| e1=expression "-" e2=expression { BinaryExpression(Substract, e1, e2) }
|
| e1=expression "-" e2=expression { BinaryExpression(Subtract, e1, e2) }
|
||||||
| e1=expression "*" e2=expression { BinaryExpression(Multiply, e1, e2) }
|
| e1=expression "*" e2=expression { BinaryExpression(Multiply, e1, e2) }
|
||||||
| e1=expression "/" e2=expression { BinaryExpression(Divide, e1, e2) }
|
| e1=expression "/" e2=expression { BinaryExpression(Divide, e1, e2) }
|
||||||
| e1=expression "&&" e2=expression { BinaryExpression(AmpersandAmpersand, e1, e2) }
|
| e1=expression "&&" e2=expression { BinaryExpression(AmpersandAmpersand, e1, e2) }
|
||||||
+10
-10
@@ -11,10 +11,19 @@ let string_of_type = function
|
|||||||
| BooleanType -> "BooleanType"
|
| BooleanType -> "BooleanType"
|
||||||
| VoidType -> "VoidType"
|
| VoidType -> "VoidType"
|
||||||
|
|
||||||
|
(** [string_of_literal l] returns a string representation of the literal [l]. *)
|
||||||
|
let string_of_literal = function
|
||||||
|
| Integer i -> "Integer(" ^ string_of_int i ^ ")"
|
||||||
|
| Float f -> "Float(" ^ string_of_float f ^ ")"
|
||||||
|
| Character c -> "Character('" ^ Char.escaped c ^ "')"
|
||||||
|
| String s -> "String(\"" ^ s ^ "\")"
|
||||||
|
| Boolean b -> "Boolean(" ^ string_of_bool b ^ ")"
|
||||||
|
| Null -> "Null"
|
||||||
|
|
||||||
(** [string_of_binary_operator op] returns a string representation of the binary operator [op]. *)
|
(** [string_of_binary_operator op] returns a string representation of the binary operator [op]. *)
|
||||||
let string_of_binary_operator = function
|
let string_of_binary_operator = function
|
||||||
| Add -> "Add"
|
| Add -> "Add"
|
||||||
| Substract -> "Substract"
|
| Subtract -> "Subtract"
|
||||||
| Multiply -> "Multiply"
|
| Multiply -> "Multiply"
|
||||||
| Divide -> "Divide"
|
| Divide -> "Divide"
|
||||||
| AmpersandAmpersand -> "AmpersandAmpersand"
|
| AmpersandAmpersand -> "AmpersandAmpersand"
|
||||||
@@ -30,15 +39,6 @@ let string_of_binary_operator = function
|
|||||||
(** [string_of_unary_operator op] returns a string representation of the unary operator [op]. *)
|
(** [string_of_unary_operator op] returns a string representation of the unary operator [op]. *)
|
||||||
let string_of_unary_operator = function Negate -> "Negate" | Not -> "Not"
|
let string_of_unary_operator = function Negate -> "Negate" | Not -> "Not"
|
||||||
|
|
||||||
(** [string_of_literal l] returns a string representation of the literal [l]. *)
|
|
||||||
let string_of_literal = function
|
|
||||||
| Integer i -> "Integer(" ^ string_of_int i ^ ")"
|
|
||||||
| Float f -> "Float(" ^ string_of_float f ^ ")"
|
|
||||||
| Character c -> "Character('" ^ Char.escaped c ^ "')"
|
|
||||||
| String s -> "String(\"" ^ s ^ "\")"
|
|
||||||
| Boolean b -> "Boolean(" ^ string_of_bool b ^ ")"
|
|
||||||
| Null -> "Null"
|
|
||||||
|
|
||||||
(** [string_of_expression e] returns a string representation of the expression [e]. *)
|
(** [string_of_expression e] returns a string representation of the expression [e]. *)
|
||||||
let rec string_of_expression = function
|
let rec string_of_expression = function
|
||||||
| Literal l -> "Literal(" ^ string_of_literal l ^ ")"
|
| Literal l -> "Literal(" ^ string_of_literal l ^ ")"
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ type literal =
|
|||||||
|
|
||||||
type binary_operator =
|
type binary_operator =
|
||||||
| Add
|
| Add
|
||||||
| Substract
|
| Subtract
|
||||||
| Multiply
|
| Multiply
|
||||||
| Divide
|
| Divide
|
||||||
| AmpersandAmpersand
|
| AmpersandAmpersand
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
(* lib/ast/syntax.ml *)
|
||||||
|
|
||||||
|
open Syntax
|
||||||
|
|
||||||
|
exception Type_error of string
|
||||||
|
|
||||||
|
type environment = { variables : (string * _type) list }
|
||||||
|
|
||||||
|
(** [type_of_literal lit] returns the type of the given literal. *)
|
||||||
|
let type_of_literal (lit : literal) : _type =
|
||||||
|
match lit with
|
||||||
|
| Integer _ -> IntegerType
|
||||||
|
| Float _ -> FloatType
|
||||||
|
| Character _ -> CharacterType
|
||||||
|
| String _ -> StringType
|
||||||
|
| Boolean _ -> BooleanType
|
||||||
|
| Null -> VoidType
|
||||||
|
|
||||||
|
(** [type_of_expression env expr] returns the type of the given expression. *)
|
||||||
|
let rec type_of_expression (env : environment) (expr : expression) : _type =
|
||||||
|
match expr with
|
||||||
|
| Literal lit -> type_of_literal lit
|
||||||
|
| Identifier id -> (
|
||||||
|
try List.assoc id env.variables
|
||||||
|
with Not_found -> raise (Type_error ("Unknown variable " ^ id)))
|
||||||
|
| UnaryExpression (op, expr) -> (
|
||||||
|
let expr_type = type_of_expression env expr in
|
||||||
|
match op with
|
||||||
|
| Negate -> (
|
||||||
|
match expr_type with
|
||||||
|
| IntegerType | FloatType -> expr_type
|
||||||
|
| _ ->
|
||||||
|
raise
|
||||||
|
(Type_error "Negate operator can only be applied to numbers"))
|
||||||
|
| Not -> (
|
||||||
|
match expr_type with
|
||||||
|
| BooleanType -> BooleanType
|
||||||
|
| _ ->
|
||||||
|
raise (Type_error "Not operator can only be applied to booleans"))
|
||||||
|
)
|
||||||
|
| BinaryExpression (op, left, right) -> (
|
||||||
|
let left_type = type_of_expression env left in
|
||||||
|
let right_type = type_of_expression env right in
|
||||||
|
match op with
|
||||||
|
| Add | Subtract | Multiply | Divide -> (
|
||||||
|
match (left_type, right_type) with
|
||||||
|
| IntegerType, IntegerType -> IntegerType
|
||||||
|
| FloatType, FloatType -> FloatType
|
||||||
|
| IntegerType, FloatType | FloatType, IntegerType -> FloatType
|
||||||
|
| _ -> raise (Type_error "Arithmetic operations require numbers"))
|
||||||
|
| AmpersandAmpersand | BarBar -> (
|
||||||
|
match (left_type, right_type) with
|
||||||
|
| BooleanType, BooleanType -> BooleanType
|
||||||
|
| _ -> raise (Type_error "Logical operations require booleans"))
|
||||||
|
| EqualsEquals | ExclamationEquals -> BooleanType
|
||||||
|
| LessThan | LessThanEquals | GreaterThan | GreaterThanEquals -> (
|
||||||
|
match (left_type, right_type) with
|
||||||
|
| IntegerType, IntegerType | FloatType, FloatType -> BooleanType
|
||||||
|
| IntegerType, FloatType | FloatType, IntegerType -> BooleanType
|
||||||
|
| _ -> raise (Type_error "Comparison operations require numbers"))
|
||||||
|
| Assign -> (
|
||||||
|
match left with
|
||||||
|
| Identifier _ ->
|
||||||
|
if left_type = right_type then left_type
|
||||||
|
else raise (Type_error "Assignment requires matching types")
|
||||||
|
| _ ->
|
||||||
|
raise
|
||||||
|
(Type_error
|
||||||
|
"Assignment requires a variable on the left-hand side")))
|
||||||
|
|
||||||
|
(** [type_of_variable_declaration env var_decl] returns the environment after
|
||||||
|
processing the given variable declaration. *)
|
||||||
|
let type_of_variable_declaration (env : environment)
|
||||||
|
(VariableDeclaration (_type, id_expr, expr)) =
|
||||||
|
match id_expr with
|
||||||
|
| Identifier id ->
|
||||||
|
let expr_type = type_of_expression env expr in
|
||||||
|
if expr_type = _type then { variables = (id, _type) :: env.variables }
|
||||||
|
else
|
||||||
|
raise (Type_error ("Type mismatch in variable declaration for " ^ id))
|
||||||
|
| _ -> raise (Type_error "Variable name must be an identifier")
|
||||||
|
|
||||||
|
(** [type_of_statement env stmt] returns the environment after processing the
|
||||||
|
given statement. *)
|
||||||
|
let type_of_statement (env : environment) (stmt : statement) =
|
||||||
|
match stmt with
|
||||||
|
| ExpressionStatement expr ->
|
||||||
|
let _ = type_of_expression env expr in
|
||||||
|
env
|
||||||
|
| VariableStatement var_decls ->
|
||||||
|
List.fold_left type_of_variable_declaration env var_decls
|
||||||
|
|
||||||
|
(** [type_of_source_file (SourceFile stmts)] returns the environment after
|
||||||
|
processing the given source file. *)
|
||||||
|
let type_of_source_file (SourceFile stmts) =
|
||||||
|
let initial_env = { variables = [] } in
|
||||||
|
List.fold_left type_of_statement initial_env stmts
|
||||||
@@ -1,18 +1 @@
|
|||||||
;lib/dune
|
;lib/dune
|
||||||
|
|
||||||
(library
|
|
||||||
(name parser)
|
|
||||||
(modules parser)
|
|
||||||
(libraries ast))
|
|
||||||
|
|
||||||
(library
|
|
||||||
(name analyzer)
|
|
||||||
(modules lexer analyzer)
|
|
||||||
(libraries parser)
|
|
||||||
)
|
|
||||||
|
|
||||||
(menhir
|
|
||||||
(modules parser))
|
|
||||||
|
|
||||||
(ocamllex
|
|
||||||
(modules lexer))
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
; lib/eval/dune
|
||||||
|
|
||||||
|
;(library
|
||||||
|
; (name eval)
|
||||||
|
; (modules eval))
|
||||||
@@ -3,4 +3,9 @@
|
|||||||
(test
|
(test
|
||||||
(name print)
|
(name print)
|
||||||
(libraries alcotest ast)
|
(libraries alcotest ast)
|
||||||
|
)
|
||||||
|
|
||||||
|
(test
|
||||||
|
(name typecheck)
|
||||||
|
(libraries alcotest ast)
|
||||||
)
|
)
|
||||||
+25
-4
@@ -1,4 +1,4 @@
|
|||||||
(* test/print.ml *)
|
(* test/ast/print.ml *)
|
||||||
|
|
||||||
open Alcotest
|
open Alcotest
|
||||||
open Ast.Syntax
|
open Ast.Syntax
|
||||||
@@ -9,6 +9,7 @@ module To_test = struct
|
|||||||
let binary_operator = Ast.Print.string_of_binary_operator
|
let binary_operator = Ast.Print.string_of_binary_operator
|
||||||
let unary_operator = Ast.Print.string_of_unary_operator
|
let unary_operator = Ast.Print.string_of_unary_operator
|
||||||
let expression = Ast.Print.string_of_expression
|
let expression = Ast.Print.string_of_expression
|
||||||
|
let variable_declaration = Ast.Print.string_of_variable_declaration
|
||||||
let statement = Ast.Print.string_of_statement
|
let statement = Ast.Print.string_of_statement
|
||||||
let source_file = Ast.Print.string_of_source_file
|
let source_file = Ast.Print.string_of_source_file
|
||||||
end
|
end
|
||||||
@@ -50,7 +51,7 @@ let test_string_of_binary_operator () =
|
|||||||
let tests =
|
let tests =
|
||||||
[
|
[
|
||||||
("+", "Add", Add);
|
("+", "Add", Add);
|
||||||
("-", "Substract", Substract);
|
("-", "Subtract", Subtract);
|
||||||
("*", "Multiply", Multiply);
|
("*", "Multiply", Multiply);
|
||||||
("/", "Divide", Divide);
|
("/", "Divide", Divide);
|
||||||
("&&", "AmpersandAmpersand", AmpersandAmpersand);
|
("&&", "AmpersandAmpersand", AmpersandAmpersand);
|
||||||
@@ -94,18 +95,33 @@ let test_string_of_expression () =
|
|||||||
(check string) name expected (To_test.expression actual))
|
(check string) name expected (To_test.expression actual))
|
||||||
tests
|
tests
|
||||||
|
|
||||||
|
let test_string_of_variable_declaration () =
|
||||||
|
let tests =
|
||||||
|
[
|
||||||
|
( "var x: int = 42;",
|
||||||
|
"VariableDeclaration(IntegerType, Identifier(\"x\"), \
|
||||||
|
Literal(Integer(42)))",
|
||||||
|
VariableDeclaration (IntegerType, Identifier "x", Literal (Integer 42))
|
||||||
|
);
|
||||||
|
]
|
||||||
|
in
|
||||||
|
List.iter
|
||||||
|
(fun (name, expected, actual) ->
|
||||||
|
(check string) name expected (To_test.variable_declaration actual))
|
||||||
|
tests
|
||||||
|
|
||||||
let test_string_of_statement () =
|
let test_string_of_statement () =
|
||||||
let tests =
|
let tests =
|
||||||
[
|
[
|
||||||
( "42;",
|
( "42;",
|
||||||
"ExpressionStatement(Literal(Integer(42)))",
|
"ExpressionStatement(Literal(Integer(42)))",
|
||||||
ExpressionStatement (Literal (Integer 42)) );
|
ExpressionStatement (Literal (Integer 42)) );
|
||||||
( "int x;",
|
( "var x: int;",
|
||||||
"VariableStatement([VariableDeclaration(IntegerType, \
|
"VariableStatement([VariableDeclaration(IntegerType, \
|
||||||
Identifier(\"x\"), Literal(Null))])",
|
Identifier(\"x\"), Literal(Null))])",
|
||||||
VariableStatement
|
VariableStatement
|
||||||
[ VariableDeclaration (IntegerType, Identifier "x", Literal Null) ] );
|
[ VariableDeclaration (IntegerType, Identifier "x", Literal Null) ] );
|
||||||
( "int x = 42;",
|
( "var x: int = 42;",
|
||||||
"VariableStatement([VariableDeclaration(IntegerType, \
|
"VariableStatement([VariableDeclaration(IntegerType, \
|
||||||
Identifier(\"x\"), Literal(Integer(42)))])",
|
Identifier(\"x\"), Literal(Integer(42)))])",
|
||||||
VariableStatement
|
VariableStatement
|
||||||
@@ -152,6 +168,11 @@ let () =
|
|||||||
[ test_case "binary_operator" `Quick test_string_of_binary_operator ] );
|
[ test_case "binary_operator" `Quick test_string_of_binary_operator ] );
|
||||||
( "string_of_expression",
|
( "string_of_expression",
|
||||||
[ test_case "expression" `Quick test_string_of_expression ] );
|
[ test_case "expression" `Quick test_string_of_expression ] );
|
||||||
|
( "string_of_variable_declaration",
|
||||||
|
[
|
||||||
|
test_case "variable_declaration" `Quick
|
||||||
|
test_string_of_variable_declaration;
|
||||||
|
] );
|
||||||
( "string_of_statement",
|
( "string_of_statement",
|
||||||
[ test_case "statement" `Quick test_string_of_statement ] );
|
[ test_case "statement" `Quick test_string_of_statement ] );
|
||||||
( "string_of_source_file",
|
( "string_of_source_file",
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
(* test/ast/typecheck.ml *)
|
||||||
|
|
||||||
|
open Alcotest
|
||||||
|
open Ast.Syntax
|
||||||
|
open Ast.Typecheck
|
||||||
|
|
||||||
|
let should_be_valid function_to_test tests =
|
||||||
|
List.iter
|
||||||
|
(fun (name, expected, source) ->
|
||||||
|
let actual = function_to_test source in
|
||||||
|
check (of_pp Fmt.nop) name expected actual)
|
||||||
|
tests
|
||||||
|
|
||||||
|
let test_type_of_literal () =
|
||||||
|
let tests =
|
||||||
|
[
|
||||||
|
("1", IntegerType, Integer 1);
|
||||||
|
("1.0", FloatType, Float 1.0);
|
||||||
|
("true", BooleanType, Boolean true);
|
||||||
|
("false", BooleanType, Boolean false);
|
||||||
|
("\"hello\"", StringType, String "hello");
|
||||||
|
("'c'", CharacterType, Character 'c');
|
||||||
|
("null", VoidType, Null);
|
||||||
|
]
|
||||||
|
in
|
||||||
|
should_be_valid type_of_literal tests
|
||||||
|
|
||||||
|
let test_type_of_expression () =
|
||||||
|
let tests =
|
||||||
|
[
|
||||||
|
("42", IntegerType, Literal (Integer 42));
|
||||||
|
("foo", StringType, Identifier "foo");
|
||||||
|
(* TODO Add tests *)
|
||||||
|
]
|
||||||
|
and env : environment = { variables = [ ("foo", StringType) ] } in
|
||||||
|
should_be_valid (type_of_expression env) tests
|
||||||
|
(* TODO Tests errors *)
|
||||||
|
|
||||||
|
let test_type_of_variable_declaration () =
|
||||||
|
let tests =
|
||||||
|
[
|
||||||
|
( "var bar: booleen = true;",
|
||||||
|
{ variables = [ ("bar", BooleanType) ] },
|
||||||
|
VariableDeclaration
|
||||||
|
(BooleanType, Identifier "bar", Literal (Boolean true)) );
|
||||||
|
]
|
||||||
|
and env : environment = { variables = [] } in
|
||||||
|
should_be_valid (type_of_variable_declaration env) tests
|
||||||
|
(* TODO check other case should return an error *)
|
||||||
|
|
||||||
|
let test_type_of_statement () =
|
||||||
|
let tests = [ (* TODO Add Tests *) ] and env = { variables = [] } in
|
||||||
|
should_be_valid (type_of_statement env) tests
|
||||||
|
|
||||||
|
let test_type_of_source_file () =
|
||||||
|
let tests = [ (* TODO Add Tests *) ] in
|
||||||
|
should_be_valid type_of_source_file tests
|
||||||
|
|
||||||
|
let () =
|
||||||
|
run "ast.typecheck"
|
||||||
|
[
|
||||||
|
( "type_of_literal",
|
||||||
|
[ test_case "type_of_literal" `Quick test_type_of_literal ] );
|
||||||
|
( "type_of_expression",
|
||||||
|
[ test_case "type_of_expression" `Quick test_type_of_expression ] );
|
||||||
|
( "type_of_variable_declaration",
|
||||||
|
[
|
||||||
|
test_case "type_of_variable_declaration" `Quick
|
||||||
|
test_type_of_variable_declaration;
|
||||||
|
] );
|
||||||
|
( "type_of_statement",
|
||||||
|
[ test_case "type_of_statement" `Quick test_type_of_statement ] );
|
||||||
|
( "type_of_source_file",
|
||||||
|
[ test_case "type_of_source_file" `Quick test_type_of_source_file ] );
|
||||||
|
]
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
/*Hello, World!*/
|
/*Hello, World!*/
|
||||||
|
|
||||||
var a : entier = 1;
|
var a : chaine = "Hello World";
|
||||||
Reference in New Issue
Block a user