From e563bce4f5445a61b0dfd6fd702ef106f46c0624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luc=C3=A0s?= <86352901+LucasVbr@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:33:58 +0200 Subject: [PATCH] feat: Support basic expression --- .gitignore | 1 + .ocamlformat | 2 ++ bin/dune | 6 ++++-- bin/main.ml | 20 +++++++++++++++++- dune-project | 34 ++++++++++++----------------- lib/ast.ml | 34 +++++++++++++++++++++++++++++ lib/dune | 21 +++++++++++++++++- lib/lexer.mll | 28 ++++++++++++++++++++++++ lib/parser.mly | 51 ++++++++++++++++++++++++++++++++++++++++++++ test/ast.ml | 54 +++++++++++++++++++++++++++++++++++++++++++++++ test/croissant.ml | 0 test/dune | 5 ++++- 12 files changed, 231 insertions(+), 25 deletions(-) create mode 100644 .ocamlformat create mode 100644 lib/ast.ml create mode 100644 lib/lexer.mll create mode 100644 lib/parser.mly create mode 100644 test/ast.ml delete mode 100644 test/croissant.ml diff --git a/.gitignore b/.gitignore index 2c26c86..5cce9f3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # Build folder _build/ +*.opam # Mac OS .DS_Store diff --git a/.ocamlformat b/.ocamlformat new file mode 100644 index 0000000..d2136f0 --- /dev/null +++ b/.ocamlformat @@ -0,0 +1,2 @@ +profile = default +version = 0.26.2 \ No newline at end of file diff --git a/bin/dune b/bin/dune index 1fc34ed..ed59a55 100644 --- a/bin/dune +++ b/bin/dune @@ -1,4 +1,6 @@ +;bin/dune + (executable - (public_name croissant) (name main) - (libraries croissant)) + (public_name croissant) + (libraries parser lexer ast)) diff --git a/bin/main.ml b/bin/main.ml index 7bf6048..9ac74ad 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -1 +1,19 @@ -let () = print_endline "Hello, World!" +(* bin/main.ml *) + +open Printf +open Ast + +let () = + let lexbuf = Lexing.from_channel stdin in + let res = + try Parser.main Lexer.token lexbuf with + | Lexer.Error c -> + fprintf stderr "Lexical error at line %d: Unknown character '%c'\n" + lexbuf.lex_curr_p.pos_lnum c; + exit 1 + | Parser.Error -> + fprintf stderr "Parse error at line %d:\n" lexbuf.lex_curr_p.pos_lnum; + exit 1 + in + let _ = res in + Printf.printf "%s\n" (string_of_source_file res) \ No newline at end of file diff --git a/dune-project b/dune-project index fffb0c1..f2b2546 100644 --- a/dune-project +++ b/dune-project @@ -1,26 +1,20 @@ -(lang dune 3.10) +(lang dune 3.4) (name croissant) - (generate_opam_files true) - -(source - (github username/reponame)) - -(authors "Author Name") - -(maintainers "Maintainer Name") - +(source (github LucasVbr/croissant)) +(authors "LucasVbr") +(maintainers "LucasVbr") (license LICENSE) - -(documentation https://url/to/documentation) +;(documentation https://url/to/documentation) (package - (name croissant) - (synopsis "A short synopsis") - (description "A longer description") - (depends ocaml dune) - (tags - (topics "to describe" your project))) - -; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project + (name croissant) + (synopsis "A short synopsis") + (description "A longer description") + (depends ocaml dune alcotest menhir ocamlformat) + (tags + ("Custom programming language" "French") + ) +) +(using menhir 2.1) diff --git a/lib/ast.ml b/lib/ast.ml new file mode 100644 index 0000000..c7a5d47 --- /dev/null +++ b/lib/ast.ml @@ -0,0 +1,34 @@ +(* lib/ast.ml *) + +type binary_operator = Add | Substract | Multiply | Divide + +type expression = + | IntegerLiteral of int + | BinaryExpression of binary_operator * expression * expression + +type statement = ExpressionStatement of expression +type source_file = SourceFile of statement list + +(* Print AST *) + +let string_of_binary_operator = function + | Add -> "Add" + | Substract -> "Substract" + | Multiply -> "Multiply" + | Divide -> "Divide" + +let rec string_of_expression = function + | IntegerLiteral i -> "IntegerLiteral(" ^ string_of_int i ^ ")" + | BinaryExpression (op, e1, e2) -> + "BinaryExpression(" + ^ string_of_binary_operator op + ^ ", " ^ string_of_expression e1 ^ ", " ^ string_of_expression e2 ^ ")" + +let string_of_statement = function + | ExpressionStatement e -> + "ExpressionStatement(" ^ string_of_expression e ^ ")" + +let string_of_source_file = function + | SourceFile stmts -> + let stmt_strings = List.map string_of_statement stmts in + "SourceFile([" ^ String.concat ", " stmt_strings ^ "])" diff --git a/lib/dune b/lib/dune index 130ec86..21805d8 100644 --- a/lib/dune +++ b/lib/dune @@ -1,2 +1,21 @@ +;lib/dune + (library - (name croissant)) + (name lexer) + (modules lexer) + (libraries parser)) + +(library + (name parser) + (modules parser) + (libraries ast)) + +(library + (name ast) + (modules ast)) + +(menhir + (modules parser)) + +(ocamllex + (modules lexer)) diff --git a/lib/lexer.mll b/lib/lexer.mll new file mode 100644 index 0000000..966eb6a --- /dev/null +++ b/lib/lexer.mll @@ -0,0 +1,28 @@ +(* lib/lexer.mll *) +{ + open Parser + exception Error of char +} + +let line_comment = "//" [^ '\n']* + +let digit = ['0'-'9'] +let integer = digit+ + +rule token = parse + | [' ' '\t'] | line_comment { token lexbuf } + | ['\n'] { Lexing.new_line lexbuf; token lexbuf } + + | '+' { PLUS } + | '-' { MINUS } + | '*' { TIMES } + | '/' { DIVIDE } + | ';' { SEMICOLON } + + | integer as lxm { INT(int_of_string lxm) } + + | '(' { LPAREN } + | ')' { RPAREN } + + | eof { EOF } + | _ as c { raise (Error c) } \ No newline at end of file diff --git a/lib/parser.mly b/lib/parser.mly new file mode 100644 index 0000000..78a4d8d --- /dev/null +++ b/lib/parser.mly @@ -0,0 +1,51 @@ +/* lib/parser.mly */ +%{ + open Ast +%} + +%token INT + +%token PLUS "+" +%token MINUS "-" +%token TIMES "*" +%token DIVIDE "/" + +%token LPAREN "(" +%token RPAREN ")" + +%token SEMICOLON ";" + +%token EOF + +%left "+" "-" +%left "*" "/" +//%nonassoc UMINUS + +%start main +%type main + +%% + +main: + | statements EOF { SourceFile($1) } + +statements: + | statement ";" { [$1] } + | statement ";" statements { $1 :: $3 } + +statement: + | expression { ExpressionStatement($1) } + +expression: + | literal { $1 } + | binary_expression { $1 } + | "(" expression ")" { $2 } + +literal: + | INT { IntegerLiteral($1) } + +binary_expression: + | e1=expression PLUS e2=expression { BinaryExpression(Add, e1, e2) } + | e1=expression MINUS e2=expression { BinaryExpression(Substract, e1, e2) } + | e1=expression TIMES e2=expression { BinaryExpression(Multiply, e1, e2) } + | e1=expression DIVIDE e2=expression { BinaryExpression(Divide, e1, e2) } \ No newline at end of file diff --git a/test/ast.ml b/test/ast.ml new file mode 100644 index 0000000..8ac9e05 --- /dev/null +++ b/test/ast.ml @@ -0,0 +1,54 @@ +(* test/ast.ml *) + +open Alcotest +open Ast + +let test_string_of_binary_operator () = + check string "+" "Add" (string_of_binary_operator Add); + check string "-" "Substract" (string_of_binary_operator Substract); + check string "*" "Multiply" (string_of_binary_operator Multiply); + check string "/" "Divide" (string_of_binary_operator Divide) + +let test_string_of_expression () = + let expr = BinaryExpression (Add, IntegerLiteral 1, IntegerLiteral 2) in + check string "1 + 2" + "BinaryExpression(Add, IntegerLiteral(1), IntegerLiteral(2))" + (string_of_expression expr) + +let test_string_of_statement () = + let stmt = ExpressionStatement (IntegerLiteral 42) in + check string "42;" "ExpressionStatement(IntegerLiteral(42))" + (string_of_statement stmt) + +let test_string_of_source_file () = + let source_file = + SourceFile + [ + ExpressionStatement (IntegerLiteral 1); + ExpressionStatement + (BinaryExpression (Add, IntegerLiteral 2, IntegerLiteral 3)); + ] + in + check string "1; 2 + 3;" + "SourceFile([ExpressionStatement(IntegerLiteral(1)), \ + ExpressionStatement(BinaryExpression(Add, IntegerLiteral(2), \ + IntegerLiteral(3)))])" + (string_of_source_file source_file) + +let () = + let open Alcotest in + run "AST tests" + [ + ( "string_of_binary_operator", + [ + test_case "string_of_binary_operator" `Quick + test_string_of_binary_operator; + ] ); + ( "string_of_expression", + [ test_case "string_of_expression" `Quick test_string_of_expression ] ); + ( "string_of_statement", + [ test_case "string_of_statement" `Quick test_string_of_statement ] ); + ( "string_of_source_file", + [ test_case "string_of_source_file" `Quick test_string_of_source_file ] + ); + ] \ No newline at end of file diff --git a/test/croissant.ml b/test/croissant.ml deleted file mode 100644 index e69de29..0000000 diff --git a/test/dune b/test/dune index 075a202..6b4976a 100644 --- a/test/dune +++ b/test/dune @@ -1,2 +1,5 @@ +;test/dune + (test - (name croissant)) + (name ast) + (libraries ast alcotest))